Valid HTML 4.01!


Scenariusz do ćwiczeń 1
Pojęcia podstawowe, implementacja semafora

Spis treści



Pojęcia podstawowe

Proces

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.

Zasób

Funkcjonalny składnik systemu komputerowego wykorzystywany przez proces do tworzenia środowiska operacyjnego.

System operacyjny (jądro systemu 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.



Zagadnienia szczegółowe do przypomnienia i dyskusji

Wieloprogramowość

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:

Kontekst procesu

Zawartość rejestrów ogólnych i sterujących procesora; w szczególności: 

Mechanizm skoku ze śladem (CALL) i powrotu z podprogramu (RET)

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.

Mechanizm wywołania systemowego  (INT, SYSCALL) i  powrotu z przerwania (IRET)

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ń.

Mechanizm zamiany kontekstu (ang. context switch)

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.

Podstawowe stany procesu:

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ść

Diagram przejsc
Rysunek - źródło: Bach? Boveti?

Kolejki procesów:

Funkcje systemowe: swtch, sleep, wakeup

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.



Ćwiczenia

Ć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?

  1. rozważyć, jak wysokopoziomowa instrukcja dec jest realizowana na poziomie asemblera (kilka instrukcji maszynowych);

  2. 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.

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