Do spisu tresci tematu 8

8.5 SMP (Symmetric MultiProcessing)





Spis tresci


Wprowadzenie

Skrot SMP rozwija sie w Symmetric MultiProcessing, co mozna przetlumaczyc jako symetryczne przetwarzanie na wielu procesorach (spotyka sie rowniez niezbyt czytelne okreslenie symetryczne wieloprzetwarzanie). W dalszej czesci tekstu bedziemy uzywac skrotu SMP dla wiekszej latwosci czytania.
Jak sama nazwa tlumaczy, SMP wprowadza rzeczywista rownoleglosc do pracy w Linuxie, w odroznieniu od procesow wykonujacych sie wspolbieznie na jednym procesorze. SMP zostalo zaimplementowane w jadrze Linuxa poczawszy od wersji 1.2.33. Pierwsze testy zostaly przeprowadzone na dwuprocesorowej plycie firmy AsusTek. Obecnie SMP jest dostepne na platformach PC i SPARC. Trwaja prace nad przeniesieniem go na inne platformy.

Wzajemne wykluczanie wewnatrz jednoprocesorowego jadra Linuxa...

Jadro Linuxa oparte jest na pewnych zalozeniach dotyczacych bezpieczenstwa:

...i jego wplyw na jadro wieloprocesorowe

Jadro wieloprocesorowe zachowuje powyzsze zalozenia w celu uproszczenia implementacji systemu. Pojedyncza blokada jest zakladana na wszystkie procesory. Zalozenie tej blokady jest niezbedne do dostepu do przestrzeni jadra. Kazdy procesor moze zalozyc te blokade i wowczas moze przechodzic w tryb jadra w celu obsluzenia przerwan lub innym kiedykolwiek zachodzi potrzeba tak dlugo, jak nie zwolni blokady. Ta blokada zapewnia, ze proces wykonujacy sie w trybie jadra nie zostanie wywlaszczony i blokowanie przerwan w trybie jadra dziala prawidlowo (poniewaz tylko procesor, ktory zalozyl blokade moze pracowac w trybie jadra, tylko procesy wykonujace sie w trybie jadra moga wylaczac przerwania i tylko procesor, ktory zalozyl blokade moze obslugiwac przerwania).

Funkcja lock_kernel()

Funkcja zaklada blokade na jadro dla danego procesora

DEFINICJA: extern __inline void lock_kernel(void)

Funkcja zapamietuje ustawienia procesora (save_flags()). W petli sprawdza (atomowa instrukcja set_bit()), czy uzyskala blokade jadra. Jezeli w wyniku okazalo sie, ze aktualny procesor ma dostep do jadra, czyli zalozyl blokade, to sterowanie opuszcza petle. W przeciwnym wypadku "wisimy" w petli wywolujac funkcje smp_spins(). Funkcja ta powolana jest do uwolnienia procesora od pracy i w wyniku do zmniejszenia wydzielanego przez procesor ciepla. W wersji Linux/SMP dla procesorow Intela bedzie do tego w przyszlosci wykorzystana instrukcja "hlt".
W trakcie oczekiwania moze nadejsc przerwanie od procesora bedacego chwilowym posiadaczem blokady, wiec petla obslugujaca czekanie na uzyskanie blokady obsluguje rowniez przerwanie od procesora - posiadacza blokady.
Po uzyskaniu blokady zwiekszamy licznik kernel_counter. Procesor moze zalozyc wiecej niz raz blokade na jadro i zmienna kernel_counter wskazuje, ile razy procesor zalozyl blokade. Zmienna kernel_counter jest typu unsigned long.
Jako ostatnia operacja przywracane sa ustawienia procesora (restore_flags()).

Uwaga: Funkcje save_flags() i restore_flags() nie zostaly tu opisane ze wzgledu na to, ze sa napisane w asemblerze konkretnych procesorow.

Funkcja unlock_kernel()

Funkcja zdejmuje blokade z jadra

DEFINICJA: extern __inline void unlock_kernel(void)

Funkcja zapamietuje ustawienia procesora (save_flags()). Sprawdzana jest wartosc licznika kernel_counter. Jezeli wynosi ona 0, to jest to powazny blad systemowy - oznacza to, ze aktualny posiadacz blokady w rzeczywistosci nie zalozyl jej ani razu. Wywolywana jest funkcja panic().
W przeciwnym przypadku zmniejszany jest licznik kernel_counter. Jezeli wartosc licznika po zmniejszeniu wynosi 0, to funkcja oznacza, ze zaden procesor nie blokuje dostepu do jadra. Jako ostatnia operacja przywracane sa ustawienia procesora (restore_flags()).

Uwaga: Funkcje save_flags() i restore_flags() nie zostaly tu opisane ze wzgledu na to, ze sa napisane w asemblerze konkretnych procesorow.


Migracja od systemu jednoprocesorowego do systemu wieloprocesorowego

W niniejszym punkcie, stanowiacym glowna czesc tematu, omawiamy zmiany we fragmentach kodu jadra niezaleznych od sprzetu.

Inicjalizacja systemu

Pierwszym problemem pojawiajacym sie w jadrze wieloprocesorowym jest uruchamianie procesorow. Specyfikacja Linux/SMP stwierdza, ze jeden procesor rozpoczyna wykonywanie jadra od standardowego poczatku - funkcji start_kernel(). Uznaje sie, ze pozostale procesory nie zostaly wlaczone lub ze nie wykonuja tego fragmentu kodu. Pierwszy procesor wykonuje standardowa inicjalizacje Linuxa i uruchamia stronicowanie, obsluge przerwan sprzetowych i programowych. Po uzyskaniu informacji o procesorze inicjujacym prace, wywolywana jest funkcja

void smp_store_cpu_info(int processor_id),

ktora zapamietuje informacje o danym procesorze w pewnej tablicy. Na kazdy procesor przypada jedna taka tablica. W tablicy tej zapamietuje sie m.in. charakterystyke predkosci procesora mierzona w BogoMIPS.
Po wykonaniu inicjalizaji jadra wywolywana jest funkcja

void smp_boot_cpus(void),

ktora uruchamia pozostale procesory i nakazuje im wywolanie funkcji start_kernel() z rejestrami stronicowania i pozostala informacja o sterowaniu systemem juz zaladowana. Procesory te omijaja wstepna inicjalizacje poza wywolaniem funkcji inicjalizujacych obsluge przerwan programowych i sprzetowych, ktora to inicjalizacja jest wymagana przez niektore procesory przy ich uruchamianiu. Spodziewana jest usuniecie tej roznicy w kolejnych wydaniach jadra Linuxa.
Kazdy procesor poza procesorem startujacym wywoluje funkcje

void smp_callin(void),

ktora wykonuje koncowa inicjalizacje procesora w systemie i zajmuje procesor na pewien okres czasu, podczas gdy procesor startujacy tworzy dostatetecznie duzo jalowych watkow dla kazdego procesor. Jest to niezbedna operacja, poniewaz scheduler zaklada, ze zawsze istnieje watek, ktory musi byc wykonywany. Po utworzeniu jalowych watkow i utworzeniu procesu init wywolywana jest funkcja

void smp_commence(void).

Funkcja ta konczy inicjalizacje i informuje system o przygotowanym trybie wieloprocesorowym. Wszystkie procesy zajete wykonywaniem funkcji smp_callin() sa zwalniane, aby wykonywac jalowe procesy, ktore wykonuja, gdy nie ma zadnej pracy do wykonania.

Szeregowanie zadan

Podstawowe zasady szeregowania zadan nie zostaly zmienione w jadrze wieloprocesorowym. Pole procesora zostalo dodane do kazdego zadania, w ktorym to polu umieszcza sie liczbe procesorow wykonujacych dane zadanie lub stala magiczna (NO_PROC_ID) oznaczajaca, ze zadanie nie jest przydzielone zadnemu procesorowi.
Kazdy procesor sam wykonuje algorytm szeregowania i wybiera kolejne zadanie do wykonania sposrod procesow nie przydzielonych innemu procesorowi.
Istnieja pewne korzysci plynace z wykonywania procesu na tym samym procesorze, szczegolnie na plytach z osobna pamiecia cache drugiego poziomu dla kazdego procesora. W zwiazku z tym wybor procesu jest podyktowany przyporzadkowaniem go do poprzedniego procesora. W jadrze zdefiniowana jest stala PROC_CHANGE_PENALTY, ktora okresla stopien zaleznosci wyboru procesora dla danego procesu od poprzedniego procesora.
Zmienna current w jadrze uzywana jest jako globalny znacznik aktualnego procesu. W specyfikacji Linux/SMP przeksztalcona jest w makrodefinicje, ktora rozszerza sie do current_set[smp_processor_id()]. To umozliwia prace niemal calego jadra nieswiadoma istnienia wielu procesorow w systemie, ale nadal pozwala modulom jadra swiadomym wieloprocesorowosci na widzenie wszystkich wykonujacych sie procesow.
Zmienione wywolanie funkcji systemowej fork() generuje wiele procesow o ID 0, dopoki jadro wieloprocesorow nie zacznie pracowac. Jest to konieczna modyfikacja, poniewaz procesem numer 1 musi byc proces init i pozadana jest sytuacja, ze wszystkie watki systemowe maja numer 0.
Ostatnim zmodyfikowanym obszarem wewnatrz szeregowania procesow jest zapisanie w kodzie jadra jednoprocesorowego testu na jalowe watki jako task[0] i testu na proces init jako task[1]. W systemie wieloprocesorowym jest wiele watkow jalowych, wiec nalezy zamienic powyzsze testy na, odpowiednio, test, czy ID procesu jest 0 i szukanie procesu o ID 1.

Zarzadzanie pamiecia

Zarzadzanie pamiecia w obecnych wydaniach jadra Linuxa funkcjonuje prawidlowo w systemach wieloprocesorowych pod warunkiem uzycia blokad obszarow pamieci. Zmian wymagaja niektore fragmenty kodu jadra zalezne od konkretnych procesorow.

Uzupelnienia

Przenosna czesc kodu jadra zwiazanego z SMP opiera sie na malym zestawie zmiennych i funkcji. Sa to

int smp_processor_id(void),

ktora zwraca identyfikacje procesora, na ktorym wywoluje sie funkcje. Przyjmuje sie, ze to wywolanie nigdy nie konczy sie bledem. Oznacza to, ze przy inicjalizacji wymagane moga byc dodatkowe testy.

int smp_num_cpus

Jest to liczba procesorow w systemie.

void smp_message_pass(int target, int msg, unsigned long data, int wait)

Ta funkcja przesyla komunikaty pomiedzy procesorami. W momencie publikacji dokumentacji Linux/SMP autorzy nie podjeli sie opisu tej funkcji ze wzgledu na spodziewane zmiany w jej kodzie. Najnowszych informacji nalezy szukac na stronie Linux/SMP (odsylacz w bibliografii).


Przyszle wersje jadra wspierajacego SMP

Przewiduje sie przejscie na bardziej wydajne korzystanie z przestrzeni jadra przez podzial jej na mniejsze obszary posiadajace wlasne blokady dostepu (ang. fine grained locking - w przeciwienstwie do jadra z pojedyncza blokada - ang. coarse grained locking). W obecnym wydaniu jadro dziala dobrze z zadaniami ukierunkowanymi na wykorzystanie procesora, lecz zadania ukierunkowane na wejscie/wyjscie potrafia sprowadzic system do wydajnosci porownywalnej z systemem jednoprocesorowym.

Bibliografia

  1. Pliki zrodlowe Linuxa:
  2. Dokumentacja projektu Linux/SMP

    Inne uzyteczne dowiazania:

  3. Parallel Processing Using PCs and Linux (zawiera m.in. realizacje watkow dla Linuxa, oprogramowanie dla pamieci dzielonej w systemie z SMP)
  4. Linux Parallel Processing HOWTO

Autor: Jan Czerminski