• Nie Znaleziono Wyników

Tworzenie procesów i wątków

4. PROCESY I WĄTKI

4.2. Zarządzanie procesami i wątkami

4.2.2. Tworzenie procesów i wątków

Podczas tworzenia procesu system operacyjny zapisuje wszystkie informacje o nowym procesie, takie jak identyfikator procesu, dane o rozmieszczeniu w pamięci, poziom uprzywilejowania itp., w odpowiednich strukturach danych (w systemie UNIX – deskryptor procesu, a w Windows NT – obiekt-proces). Powstanie nowego deskryptora procesu w systemie UNIX oznacza, że pojawił się nowy pretendent do zasobów systemu komputerowego. Od tego momentu system operacyjny podczas udostępniania zasobów komputera musi uwzględnić potrzeby nowego procesu.

W wyniku stworzenia nowego procesu system operacyjny przydziela procesowi określony obszar pamięci, a następnie ładuje w to miejsce zawartość odpowiedniego pliku wykonywalnego z pamięci masowej (obraz procesu) oraz, jeśli istnieje taka konieczność, dokonuje odpowiednich zmian w programie, zależnie od rozmieszczenia w pamięci. W tym zakresie podsystem zarządzania procesami ściśle współpracuje z podsystemami zarządzania pamięcią oraz systemem plików.

W systemie wielowątkowym, po stworzeniu procesu, system operacyjny tworzy co najmniej jeden wątek wykonania. Podczas tworzenia wątku,

analogicznie jak podczas tworzenia procesu, system operacyjny generuje specjalną strukturę informacyjną zawierającą między innymi identyfikator wątku, informację o prawach dostępu i przydzielonym priorytecie oraz o aktualnym stanie wątku. W pozycji wyjściowej wątek (lub proces w systemie operacyjnym obsługującym wyłącznie procesy jednowątkowe) znajduje się w stanie wstrzymania. Moment wyboru wątku do wykonania odbywa się zgodnie z przyjętym w systemie operacyjnym algorytmem szeregowania wątków, na podstawie aktualnego stanu wszystkich wątków w systemie.

Dany wątek może zwrócić się do systemu operacyjnego o stworzenie nowego wątku potomnego. W systemach operacyjnych stosowane są różne zasady współdziałania wątków macierzystych i wątków potomnych. Np. w jednych systemach operacyjnych wykonywanie wątku macierzystego jest zsynchronizowane z jego potomkami, a w szczególności po zakończeniu wątku macierzystego kończone są wszystkie wątki potomne. W innych systemach operacyjnych wątki potomne mogą wykonywać się asynchronicznie w stosunku do wątku rodzicielskiego.

Jako przykład rozpatrzmy operację tworzenia procesu w powszechnie stosowanej wersji V Release 4 systemu UNIX. W tej wersji systemu nie były stosowane wątki, a jednostką przydziału procesora był proces. Do zarządzania procesami system operacyjny wykorzystuje trzy podstawowe struktury danych związane z procesem: deskryptor procesu, u-obszar oraz tzw. sprzętowy kontekst procesu. Struktury te określane są mianem kontekstu procesu.

Deskryptor procesu zawiera informacje o procesie niezbędne dla jądra, które są

dostępne podczas całego cyklu życia procesu, niezależnie od stanu, w jakim aktualnie proces się znajduje, a także od tego czy jest aktualnie w pamięci, czy też został przeniesiony na dysk.

Do głównych pól deskryptora procesu można zaliczyć:

 stan procesu,

 informacje pozwalające na zlokalizowanie procesu i jego u-obszaru w pamięci głównej lub masowej,

 identyfikator procesu PID (ang. Process Identifier),

 identyfikatory procesów spokrewnionych,

 identyfikator użytkownika UID (ang. User Identifier), który utworzył proces,

 deskryptor zdarzenia, gdy proces jest w stanie uśpionym,

 parametry szeregowania,

 pole sygnałów (sygnały wysłane do procesu i jeszcze nieodebrane),

 różnego rodzaju liczniki wykorzystywane do celów rozliczeniowych oraz wyliczania priorytetu procesu.

Deskryptory poszczególnych procesów są przechowywane w przestrzeni pamięci przeznaczonej dla jądra, w postaci tablicy procesów. W oparciu o informacje zawarte w tablicy procesów system operacyjny wykonuje operacje związane z szeregowaniem i synchronizowaniem procesów. Część informacji o procesie system operacyjny UNIX przechowuje w u-obszarze, zapisanym

z wirtualnej przestrzeni adresowej systemu operacyjnego, lecz przechowywanym wraz z obrazem procesu. Do obszaru tego system ma dostęp wyłącznie w momencie, gdy dany proces jest aktywny.

Ze względu na to, że proces może być wykonywany w trybie użytkownika (realizacja programu użytkowego) oraz w trybie jądra (realizacja wywołań systemowych), w procesie jest przechowywany stos dla trybu użytkownika oraz dla trybu jądra. Poprzez te stosy są przekazywane parametry od programu użytkowego do funkcji systemowych wykonywanych w trybie jądra. Trzeba przy tym zwrócić uwagę, że wykonywanie funkcji systemowej odbywa się w kontekście danego procesu (podczas wywołania funkcji systemowej nie ma miejsca przełączenie kontekstu).

Sprzętowy kontekst procesu zawiera zbiór informacji ściśle związanych

z konkretną platformą sprzętową, a w szczególności z architekturą procesora, niezbędnych do kontynuowania procesu, poczynając od miejsca, w którym wcześniej system operacyjny przerwał jego wykonywanie. W skład sprzętowego kontekstu procesu wchodzą następujące informacje:

 stan licznika rozkazów,

 stan rejestru flagowego,

 stany rejestrów adresowych danych oraz stosu,

 zawartości rejestrów roboczych procesora.

Sprzętowy kontekst procesu, analogicznie do deskryptora procesu oraz u-obszaru, dostępny jest wyłącznie dla programów jądra, czyli jest zapisany w wirtualnej przestrzeni adresowej systemu operacyjnego, jednak jest przechowywany wraz z obrazem procesu.

Tworzenie nowego procesu w systemie UNIX odbywa się poprzez wywołanie funkcji systemowej fork(). Proces wywołujący tę funkcję określany jest mianem procesu macierzystego, natomiast nowo utworzony – procesem potomnym. Funkcja tworzy nowy proces będący „klonem” procesu macierzystego. Proces potomny posiada identyczny kontekst jak proces macierzysty, lecz ma przydzielony inny identyfikator. Od tego momentu w obydwu procesach wykonywane są współbieżnie te same programy, poczynając od następnej instrukcji po funkcji fork(). Funkcja fork() wraca różne wartości w procesie macierzystym i procesie potomnym: w procesie macierzystym – identyfikator procesu potomnego, w procesie potomnym – 0 (zero). Uproszczony algorytm realizowany przez funkcję fork() można przedstawić następująco:

wejście: brak

wyjście: w procesie macierzystym PID (ang.

Process Identifier) potomka w procesie potomnym 0

{

sprawdź dostępność zasobów jądra;

pobierz wolną pozycję w tablicy procesów, określ unikatowy PID;

sprawdź, czy użytkownik nie wykonuje zbyt wielu procesów;

zaznacz, że potomek jest w stanie „tworzony”; skopiuj dane z pozycji w tablicy procesów

związanej z procesem macierzystym do pozycji związanej z procesem potomnym;

utwórz w pamięci kopię kontekstu procesu

macierzystego (u-obszar, program, dane, stos); if (wykonywany proces jest procesem macierzystym) {

zmień stan potomka na „gotowy do wykonania”; return(PID potomka); } else { return(0); } }

W wyniku wykonania funkcji fork(), w procesie potomnym wykonywany jest ten sam program co w procesie macierzystym, co z praktycznego punktu widzenia wydaje się bezsensowne. Stworzenie nowego procesu ma praktyczne znaczenie jedynie w przypadku, gdy w procesie potomnym zostanie wykonany nowy program. W systemie UNIX jest to realizowane przy pomocy wywołania jednej z funkcji należących do grupy exec(), które powodują załadowanie do procesu nowego programu, zapisanego na dysku w postaci pliku wykonywalnego. W związku z tym programy wywołujące funkcję systemową fork() posiadają najczęściej następującą budowę:

pid=fork(); if (pid == 0)

{operacje wykonywane w procesie potomnym

exec(plik zawierający obraz nowego procesu)} else

{operacje wykonywane w procesie macierzystym}

Współbieżne wykonywanie procesu macierzystego i potomnego może być realizowane na dwa sposoby:

 procesy wykonują się w pełni niezależnie,

 proces macierzysty, po powołaniu do życia procesu potomnego, wstrzymuje wykonanie i oczekuje na zakończenie pracy potomka. W drugim przypadku w procesie macierzystym, po wykonaniu funkcji fork(), wykonana zostaje funkcja wait() powodująca wstrzymanie biegu procesu i oczekiwanie na zakończenie pracy procesu potomnego.

Proces tworzenia wątku jest znacznie mniej pracochłonny od tworzenia nowego procesu. Proces tworzenia nowego wątku prześledzimy na przykładzie

systemu operacyjnego Windows NT. Do tworzenia nowego wątku w systemie operacyjnym przewidziano funkcję systemową CreateThread(). Nieco upraszczając, można powiedzieć, że funkcja ta realizuje następujące operacje:

 tworzenie stosu trybu użytkownika dla nowego wątku w przestrzeni adresowej procesu,

 inicjalizacja sprzętowego kontekstu wątku,

 tworzenie i inicjalizacja bloku wykonawczego wątku (ang. ETHREAD – Executive Thread Block), zawierającego podstawowe informacje o wątku,

 inkrementacja licznika wątków w bloku wykonawczym procesu (ang. EPROCESS – Executive Process Block),

 określenie identyfikatora nowego wątku,

 inicjalizacja stosu trybu jądra dla nowego wątku,

 wyznaczenie uchwytu (ang. Handle) do nowo utworzonego wątku i zwrócenie tego uchwytu do programu wykonującego wywołanie funkcji CreateThread().