Abstrakcyjny agent wykonujący program użytkowy (aplikację) i dostarczający środowisko operacyjne do jego wykonania. Najważniejsze składowe tego pojęcia to program i procesor wirtualny. Procesy mogą ubiegać się o przydział zasobów lub o wykonanie specyficznych usług.
Funkcjonalny składnik systemu komputerowego wykorzystywany przez proces do tworzenia środowiska operacyjnego.
Zestaw podprogramów obsługujących procesy. Jądro zawiaduje stanami procesów, przydziela i zwalnia zasoby oraz wykonuje usługi na rzecz procesów. Interfejs procesów z jądrem składa się z ustalonego zbioru wywołań systemowych (ang. system calls).
Uwagi:
Procesy nie mają bezpośredniego dostępu do warstwy sprzętowej komputera (tzn. programy użytkowe napisane są dla pewnej wirtualnej, a nie rzeczywistej maszyny). Sprzęt i jądro zapewniają odwzorowanie instrukcji na rzeczywiste operacje maszynowe.
W środowisku wieloprogramowym wiele procesów może działać równocześnie i niezależnie od siebie. Jądro musi im w sposób dynamiczny przydzielać zasoby niezbędne do działania oraz zagwarantować bezpieczeństwo (ochronę przed ingerencją innych procesów).
Mechanizmy sprzętowe, które są wykorzystywane w tym celu:
Instrukcje uprzywilejowane i ochrona pamięci. Tryb użytkownika (podporządkowany) i tryb jądra (uprzywilejowany).
Mechanizm przerwań i wyjątków (zdarzenia asynchroniczne i synchroniczne). Blokowanie (maskowanie) przerwań.
Zawartość rejestrów ogólnych i sterujących procesora; w szczególności:
licznika rozkazów (PC, od program counter)
wskaźnika wierzchołka stosu (SP, od stack pointer)
słowa stanu procesora (PSW, od processor status word)
rejestrów zarządzania pamięcią
Gdy następuje skok ze śladem, na stosie procesu zapisany zostaje adres powrotu (adres instrukcji następującej po CALL, czyli bieżąca zawartość PC), a do licznika rozkazów załadowany zostaje adres podprogramu (argument instrukcji CALL). Ostatnią instrukcją podprogramu musi być RET; powoduje ona zdjęcie ze stosu adresu powrotu i załadowanie go do licznika rozkazów; w ten sposób sterowanie wraca do programu wywołującego.
W systemach uniksowych każdy proces używa dwóch stosów. Stos użytkownika jest wykorzystywany w trybie użytkownika, a stos jądra - w trybie jądra. Gdy następuje wywołanie systemowe, na stosie jądra oprócz adresu powrotu zapisane zostaje także bieżące PSW. Argument instrukcji INT (zwany numerem przerwania) określa adres w systemowej tablicy, zwanej wektorem przerwań, pod którym zapisany jest adres docelowej funkcji (podprogramu obsługi przerwania). Nie wchodząc w szczegóły techniczne, które mogą zależeć od konkretnej architektury, możemy przyjąć, że przed przekazaniem sterowania wywołanej funkcji procesor przechodzi w tryb jądra i blokuje przerwania. Wywołana w ten sposób funkcja systemowa wykonuje się zawsze w kontekście procesu. Powrót z wywołania systemowego następuje wskutek wykonania instrukcji IRET; powoduje ona zdjęcie ze stosu informacji zapisanych tam przez INT i załadowanie ich do odpowiednich rejestrów procesora. (W szczególności, następuje przy tym przywrócenie poprzedniego trybu pracy procesora i odblokowanie przerwań).
Uwaga: Istnieją uprzywilejowane instrukcje, pozwalające jawnie blokować i odblokowywać przerwania, dzięki czemu w trybie jądra możliwa jest kontrolowana obsługa przerwań.
Gdy następuje przełączenie kontekstu, cały kontekst bieżącego procesu zostaje zapamiętany w strukturze zwanej niekiedy PCB (od process control block), stanowiącej część przestrzeni adresowej procesu. Rejestry procesora zostają następnie załadowane kontekstem nowego procesu (z jego PCB) i nowy proces rozpoczyna (lub wznawia) działanie.
wykonywany_w_trybie_użytkownika (ang. user_running),
wykonywany_w_trybie_jądra (ang. kernel_running),
gotowy (ang. ready),
zawieszony (ang. asleep).
Uwagi: Stan gotowy odpowiada procesowi oczekującemu wyłącznie na przydział procesora. Stan zawieszony odnosi się do procesu, który nie może się wykonywać, gdyż oczekuje na zajście pewnego zewnętrznego zdarzenia (np. przydział innego zasobu niż procesor). Dwa pierwsze stany mają znaczenie wyłącznie koncepcyjne (procesy wykonywane mają w rzeczywistości przypisany stan gotowy).
Diagram przejść
Rysunek - źródło: Bach? Boveti?
kolejka procesów gotowych,
kolejki procesów zawieszonych (założymy istnienie typu danych: wait_queue).
Są to wewnętrzne podprogramy jądra (nie odpowiadają im żadne wywołania systemowe).
Uwaga: nazwy funkcji są czysto umowne.
Krótki opis:
swtch: znajduje pierwszy proces w kolejce procesów gotowych i inicjuje zamianę kontekstu (w efekcie nowy proces zacznie się wykonywać od miejsca, w którym był przerwany);
sleep: wstawia proces do danej kolejki procesów zawieszonych, zmienia jego stan na zawieszony, a następnie wywołuje funkcję swtch;
wakeup: przegląda wszystkie procesy zawieszone w danej kolejce procesów zawieszonych, zmienia ich stan na gotowy i przenosi do kolejki procesów gotowych.
Ćwiczenie 1
Zaprojektować implementację operacji P i V na semaforze ogólnym w systemie jednoprocesorowym, korzystając z funkcji sleep i wakeup.
type
semaphore = record
count: integer; { wartość semafora }
wait: wait_queue; { kolejka procesów }
end;
{ Operacja P (Linuksowa funkcja down) }
procedure P (var: s: semaphore); { tryb jądra }
begin
while (s.count <= 0) do sleep(s.wait);
dec(s.count);
end;
{ Operacja V (Linuksowa funkcja up) }
procedure V (var s: semaphore); { tryb jądra }
begin
inc(s.count);
wakeup(s.wait);
end;
Dla uproszczenia zakładamy, że blokowanie przerwań w trybie jądra jest realizowane automatycznie.
Ćwiczenie 2
Dlaczego powyższe rozwiązanie jest niepoprawne w systemie wieloprocesorowym?
rozważyć, jak wysokopoziomowa instrukcja dec jest realizowana na poziomie asemblera (kilka instrukcji maszynowych);
rozważyć następujący scenariusz: proces A zmniejszył wartość count do 0; proces B wykonujący się równolegle stwierdził, że wartość count jest równa 0; proces A wykonał procedurę V zanim B zdążył się zawiesić; proces B zawiesił się, chociaż wartość count jest dodatnia. Jeśli żaden inny proces nie użyje tego semafora, to B zablokuje się na zawsze. (To się nazywa the Lost Wakeup Problem.)
Ćwiczenie 3
Załóżmy, że w zestawie instrukcji procesora istnieje atomowa instrukcja
tsl register, lock
o następującej semantyce (lock oznacza adres pamięci operacyjnej):
register := lock;
lock := 1;
Korzystając z tej instrukcji zaimplementować operacje enter_region i leave_region zapewniające wyłączność dostępu do sekcji krytycznej w systemie wieloprocesorowym.
enter_region:
tsl register, lock
cmp register, #0
jne enter_region
ret
leave_region:
move lock, #0
ret
procedure enter_region (var lock: boolean);
begin
while TSL (lock) do
end;
procedure leave_region (var lock: boolean);
begin
lock := false
end;
Uwagi: Jest to implementacja sekcji krytycznej za pomocą tzw. aktywnego oczekiwania, co może prowadzić do spadku wydajności procesora. Ponadto w niektórych architekturach efekt niepodzielności instrukcji tsl jest realizowany przez blokowanie magistrali systemowej, co jeszcze pogłębia problem. Lepiej więc zastosować następujący wariant (dlaczego?):
procedure enter_region (var lock: boolean);
begin
while TSL (lock) do
while lock do;
end;
Ćwiczenie 3a (rezerwowe lub do domu):
Zakładając, że w repertuarze rozkazów maszynowych występuje niepodzielna instrukcja xchg R, X zamieniająca miejscami zawartości rejestru R i komórki pamięci o adresie X zaimplementuj operacje enter_region i leave_region zapewniające wzajemne wykluczanie procesów.
Ćwiczenie 4
Rozważmy następującą implementację operacji na semaforze ogólnym:
type
semaphore = record
lock: boolean;
count: integer; { wartość semafora }
wait: wait_queue; { kolejka procesów }
end;
{ Operacja P }
procedure P (var: s: semaphore); {tryb jądra}
begin
enter_region(s.lock);
while (s.count <= 0) do begin
leave_region(s.lock);
sleep(s.wait);
enter_region(s.lock);
end;
dec(s.count);
leave_region(s.lock)
end;
{ Operacja V }
procedure V (var s: semaphore); {tryb jądra}
begin
enter_region(s.lock);
inc(s.count);
wakeup(s.wait);
leave_region(s.lock);
end;
Czy takie rozwiązanie będzie poprawne w systemie wieloprocesorowym? Jeśli nie, to dlaczego? (ciągle the Lost Wakeup Problem). Czy można poprawić rozwiązanie umieszczając operacje semaforowe w jeszcze innych miejscach?
Prawidłowe rozwiązanie wymaga, by badanie wartości semafora i przeniesienie procesu w stan zawieszony odbywało się w jednej sekcji krytycznej. Jednak procesowi nie wolno zawiesić się wewnątrz sekcji krytycznej!
Janina Mincer-Daszkiewicz |