8.5 SMP
Do spisu tresci tematu 8
8.5 SMP (Symmetric MultiProcessing)
Spis tresci
Wprowadzenie
Wzajemne wykluczanie wewnatrz jednoprocesorowego jadra Linuxa...
...i jego wplyw na jadro wieloprocesorowe; funkcje
lock_kernel, unlock_kernel
Migracja od systemu jednoprocesorowego do systemu wieloprocesorowego:
inicjalizacja systemu,
szeregowanie zadan,
zarzadzanie pamiecia,
uzupelnienia.
Przyszle wersje jadra wspierajacego SMP
Bibliografia
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:
zaden proces wykonujacy sie w trybie jadra nie moze zostac wywlaszczony przez
inny proces wykonujacy sie w trybie jadra, jezeli sam nie zadecyduje o zawieszeniu
dzialania. Ta zasada zapewnia atomowe wykonywanie fragmentow kodu jadra w stosunku
do pozostalych procesow i upraszcza wiele operacji.
obsluga przerwan moze wywlaszczyc proces wykonujacy sie w trybie jadra, ale po
zakonczeniu obslugi przerwania jadro musi powrocic do wykonywania tego procesu.
Proces wykonujacy sie w trybie jadra moze wylaczyc obsluge przerwan i zagwarantowac,
ze nie wydarzy sie taka przerwa w pracy.
obsluga przerwania nie bedzie wywlaszczona przez proces wykonujacy sie w trybie
jadra. Oznacza to, ze obsluga przerwania wykona sie do konca lub zostanie
wywlaszczona przez obsluge innego przerwania.
...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
Pliki zrodlowe Linuxa:
arch/i386/smp.h (naglowki funkcji i struktury)
arch/i386/smp_lock.h (funkcje zakladania i zdejmowania blokady z jadra)
Dokumentacja projektu Linux/SMP
Inne uzyteczne dowiazania:
Parallel Processing
Using PCs and Linux (zawiera
m.in. realizacje watkow dla Linuxa, oprogramowanie dla pamieci dzielonej w systemie
z SMP)
Linux Parallel
Processing HOWTO
Autor: Jan Czerminski