2008-03-10
Metodyka i Techniki Programowania
04 Procesy i wÄ…tki
dr inż. Rafał Stankiewicz
Krótkie wprowadzenie.
Procesem jest każdy program, który został uruchomiony i jest wykonywany. Każdy proces ma odrębne niezależne
struktury danych, kodu, rejestry, stosy itd. Proces jest niezależnym bytem w systemie i może działać niezależnie od
innych procesów (z wyjątkiem sytuacji, gdy celowo zaprogramowano mechanizmy współpracy między procesami).
WÄ…tek jest tworzony jest wewnÄ…trz procesu i wykorzystuje jego zasoby. Dla wÄ…tku tworzone sÄ… na nowo tylko
niezbędne struktury resztę współdzieli z innymi wątkami/procesem. Przykładowo, zmiany wykonane na zmiennych
globalnych sÄ… widoczne w innych wÄ…tkach, ze wszystkimi tego skutkami.
PROCESY
Każdy proces ma swój unikalny identyfikator w systemie PID (ang. Process identifier). Procesy w systemie UNIX
powstają poprzez mechanizm rozwidlania procesów. Każdy nowy proces jest tworzony przez inny proces. Proces
tworzący zwany jest procesem macierzystym, a proces nowy potomnym. Dla każdego procesu można więc podać
identyfikator procesu, który go utworzył PPID (ang. Parent PID). Więcej o rozwidlaniu procesów poniżej.
Wyświetlenie listy procesów w systemie umożliwia polecenie ps. Poszczególne opcje pozwalają na wyświetlenie
różnorodnych informacji o procesie. Szczegółowo o możliwościach polecenia ps, opcjach i wyświetlanych
informacjach można przeczytać np. w manualu (man ps).
Proces może zostać uruchomiony:
·ð na pierwszym planie (np.: ./program), wówczas przez czas dziaÅ‚ania programu nie ma dostÄ™pu do
wiersza poleceń terminala. Proces przejmuje kontrolę nad standardowym wejściem (stdin).
·ð w tle (np.: ./program &), wówczas proces dziaÅ‚a niejako na drugim planie nie blokujÄ…c dostÄ™pu do
terminala (można wykonywać inne polecenia, uruchamiać inne procesy).
Możliwe jest wymuszenie przerwania działania procesu w dowolnym momencie przez wysłanie do niego
odpowiedniego sygnału. Do wysyłania sygnałów służą:
·ð komenda kill [-signal] pid
·ð pewne kombinacje klawiszy np.: ^C wysÅ‚anie sygnaÅ‚u INT (interrupt) do procesu przerywa dziaÅ‚anie
bieżącego procesu działającego na pierwszym planie
Działający proces może zostać zatrzymany (uśpiony) na pewien czas. Proces wówczas nie jest wykonywany (nie
jest mu przydzielany czas procesora). Zatrzymanie procesu następuje poprzez wysłanie do niego sygnału TSTP lub
STOP. Sygnał TSTP można wysłać do procesu przez naciśnięcie kombinacji klawiszy ^Z (pod warunkiem, że proces
działa na pierwszym planie).
Uśpiony proces może zostać ponownie przywrócony do działania. Można tego dokonać wysyłając do procesu sygnał
CONT. Oprócz komendy kill można też użyć jedno z dwóch poleceń:
·ð fg przywrócenie do dziaÅ‚ania na pierwszy plan
·ð bg przywrócenie do dziaÅ‚ania w tle.
Ponadto, poleceniem fg można przenieść na pierwszy plan proces działający w tle.
Użyteczne jest polecenie jobs które wyświetla listę procesów uśpionych oraz działających w tle.
Jak już wspomniano, procesy tworzone są przy pomocy tego samego mechanizmu rozwidlania procesów
(wywołanie funkcji fork). Jeżeli proces wywoła funkcję fork, wówczas w systemie powstanie nowy proces
(potomny) będący jego kopią. Dla każdego procesu zawsze można określić, który inny proces go utworzył (z
wyjątkiem procesu init). W momencie utworzenia proces otrzymuje swój unikalny identyfikator PID przydzielony
przez system. Nie da się przewidzieć wartości PID. Proces init jest pierwszym procesem powstającym przy starcie
systemu. Jedynie proces init ma zawsze taki sam numer PID równy 1. Każdy proces dziedziczy też od swojego
rodzica jego identyfikator jako PPID.
Do uzyskania wartości PID oraz PPID procesu służą następujące funkcje języka C:
pid_t getpid();
pid_t getppid();
Do rozwidlania procesów służy funkcja:
pid_t fork();
Funkcja fork zwraca wartość zero w procesie potomnym, natomiast w procesie tworzącym (macierzystym)
zwracana jest wartość identyfikatora PID utworzonego procesu (w ten sposób proces macierzysty zna PID
swojego potomka). W przypadku, gdy nie udało się utworzyć procesu potomnego zwracana jest wartość 1.
Przykład
Przykład przedstawia mechanizm rozwidlania procesów i przydzielania identyfikatorów PID, PPID.
Załóżmy, że kod programu rower.c jest następujący (pominięto biblioteki):
main(){
int a;
a=fork();
sleep(2);
printf( %d\n ,a);
return 0;
}
Kolejne etapy wykonania procesu wyglÄ…dajÄ… nastepujÄ…co:
./rower
1
PID=1202
PPID=1097
2
a=fork();
proces macierzysty proces potomny
(parent, rodzic ) (child, dziecko )
./rower
PID=1298
3
PPID=1202
a=1298
a=0
sleep(2); sleep(2);
printf( %d\n ,a); printf( %d\n ,a);
return 0; return 0;
·ð W chwili (1) uruchomiono program ./rower. Interpreter poleceÅ„ (shell) miaÅ‚ PID równy 1097.
Procesowi ./rower przydzielony został PID=1202, zaś jego PPID jest równy PID-owi procesu
shell a 1097.
·ð W chwili (2) proces ./rower wykonaÅ‚ funkcjÄ™ fork() i utworzyÅ‚ w systemie swojÄ… nowÄ… kopiÄ™.
Nowy proces zwany jest procesem potomnym, zaś proces, który wywołał funkcję fork()
procesem macierzystym. Nowo powstałemu procesowi został przydzielony PID równy 1298. Jego
PPID jest równy PID-owi procesu macierzystego, czyli 1202.
·ð Funkcja fork() zwróciÅ‚a w procesie macierzystym wartość 1298 (PID potomka), zaÅ› w procesie
potomnym wartość 0. Oznacza to, że w chwili (3) wartości zmiennej a w obu procesach będą
odpowiednio 1298 i 0.
Obydwa procesy majÄ… ten sam kod i sÄ… niejako na tym samym etapie wykonywania swojego kodu.
Kolejną operacją, jaką wykonają obydwa procesy będzie funkcja sleep(2). Następnie oba procesy
wykonają funkcję printf i zakończą swoje działanie.
Mechanizm rozwidlania procesów powoduje utworzenie tylko kopii procesu, co daje ograniczone możliwości
tworzenia różnorodnych procesów. Aby było możliwe uruchamianie innych programów konieczny jest jeszcze
mechanizm podmiany kodu procesu.
Przykładowo, gdy uruchamiamy program ls, w pierwszej chwili powstaje kopia procesu naszego shell a, której kod
jest następnie podmieniany na kod programu ls.
Każdy proces może w dowolnym momencie podmienić swój kod na inny (czyli zupełnie zmienić swoje własności i
funkcje). W momencie podmiany kodu nie zmieniają się identyfikatory procesu. Kod procesu, który decyduje się na
podmianę swojego kodu (oraz jego zestaw zmiennch) są bezpowrotnie tracone i zastępowane nowym kodem i
nowymi strukturami danych.
Dostępna jest rodzina funkcji w języku C służących do podmiany kodu. Jedną z nich jest funkcja
int execl(const char *path, const char *arg0, ..., const char *argn, char *
/*NULL*/);
Oczekiwanie na zakończenie procesów potomnych
Procesy macierzysty i potomny powinny kończyć swoje działanie w kolejności odwrotnej niż powstawały., tzn.
proces macierzysty nie powinien zakończyć się wcześniej niż jego procesy potomne. Jednakże jest to możliwe. Jeśliby
się tak stało, PPID w procesie potomnym utraci ważność (nie ma już procesu o takim identyfikatorze). Istnieje
niebezpieczeństwo, że zwolniony identyfikator procesu zostanie przydzielony przez system innemu procesowi.
Dlatego też osierocony proces zostaje przejęty przez proces init, a jego PPID ustawiony na 1. Sytuacja taka jest
jednak nienormalna (w niektórych systemach osierocone procesy nie mogą się poprawnie zakończyć i pozostają w
systemie jako tzw. procesy-duchy, zajmujÄ…ce niepotrzebnie zasoby komputera).
Należy zapewnić, aby proces macierzysty poczekał na zakończenie swoich procesów potomnych i odebrał od nich
kod zakończenia procesu. W tym celu proces macierzysty powinien wywołać funkcję wait tyle razy ile utworzył
procesów potomnych. Funkcja ta ma następującą składnię:
#include
#include
int wait(int *stat_loc);
Funkcja zwraca identyfikator zakończonego procesu potomnego lub -1 jeżeli wszystkie procesy potomne się już
zakończyły. Jedno wywołanie funkcji wait oczekuje tylko na zakończenie jednego procesu potomnego. Jeżeli nie
wiemy ile mamy procesów potomnych, należy wykonywać w pętli funkcję wait, aż do momentu, kiedy zwróci
wartość -1 (nie ma już procesów potomnych).
W zmiennej wskazywanej przez stat_loc zapisywana jest liczba szesnastkowa w postaci: XXYY, gdzie XX to kod
zakończenia procesu potomnego, zaś YY to numer sygnału, który spowodował zakończenie procesu potomnego lub
0, jeśli proces zakończył się samodzielnie.
WTKI
Technicznie, wątek to niezależny strumień instrukcji, który może być wykonywany jednocześnie z innym
strumieniem instrukcji danego procesu. Jest to jak gdyby procedura , która może zostać wykonana niezależnie od
głównego części danego procesu.
Czym jest wątek i czym różni się od procesu można zobrazować w następujący sposób:
Pojedynczy proces (bez wątków) wykonując swoją główną cześć kodu (funkcja main) może wywoływać
inne funkcje. Opuszcza wówczas wykonywanie programu głównego i przechodzi do wykonania kodu
wywołanej funkcji. Po skończeniu wykonywania funkcji wraca do wykonywania głównej części kodu.
Wątek można sobie wyobrazić jako wywołanie funkcji, przy czym program główny jest wykonywany
dalej, równolegle z wątkiem. Proces wywołujący wątek i sam wątek (wywołana funkcja) wykonują się
równocześnie.
Z kolei nowy proces (powstały w wyniku rozwidlania procesów) jest natomiast zupełnie niezależny od
procesu, który go utworzył. Wszystkie struktury danych, kodu, rejestry itp. ma niezależne i odrębne.
Stwarza to szereg nowych możliwości, z których najważniejsze jest tworzenie procesów o różnym kodzie
(dzięki mechanizmowi podmiany kodu).
Jak już wspomniano, wątek tworzony jest wewnątrz procesu i wykorzystuje jego zasoby. Dla wątku tworzone są
tylko niezbędne struktury takie jak: wskaznik stosu, rejestry, ustawienia planowania CPU, zestaw obsługi sygnałów,
dane specyficzne dla wątku (ID wątki, itd.) Resztę struktur procesu wątek współdzieli z innymi wątkami/procesem.
Wątek istnieje dopóki istnieje proces go tworzący lub do momentu kiedy sam się zakończy lub jego działanie
zostanie przerwane z zewnątrz (np. przez proces tworzący lub przez sygnał wysłany przez zupełnie inny proces).
Realizacja wątków i możliwość programowania ich w języku C jest wspierana (pod UNIX-em) przez bibliotekę
pthreads.h, która definiuje około 60 funkcji niezbędnych do tworzenia i obsługi wątków.
Podstawową motywacją do wykorzystania wątków jest zwiększenie potencjalnej efektywności programu. W
porównaniu do procesów (aplikacji wieloprocesowych) wątki mają mniejsze wymagania związane z ich obsługą i
tworzeniem, przez co są szybsze i wydajniejsze (korzystają ze wspólnych obszarów pamięci współdzielą część
zmiennych, pliki itp.) Wątki mają zastosowanie w tworzeniu dużych aplikacji wielowątkowych, w których wiele
operacji musi wykonywać się jednocześnie. Aplikacje takie mogą być tworzone również jako wieloprocesowe (przy
użyciu współpracujących procesów) jednak zakres możliwości jest inny.
Przykładem sytuacji, w której rozwidlania procesów (wieloporcesowości) nie da się zastąpić wielowątkowością jest
chociażby uruchamianie programów w interpretera poleceń (shell-a). Używany shell najpierw wykonuje funkcję
fork (tworzy swoją kopię) a następnie podmienia kod tej kopii na kod programu, który uruchamiamy. Bez
rozwidlania procesów byłoby to niemożliwe (wszystkie programy musiałyby być niejako z góry wbudowane we
wszechpotężny wielowątkowy system operacyjny).
SYGNAAY
Sygnał to informacja dla procesu, że wystąpiło jakieś zdarzenie. Sygnały są wysyłane przez:
·ð jÄ…dro do procesu,
·ð proces do innego procesu.
Sygnały są zwykle asynchroniczne tzn. nie da się przewidzieć momentu ich pojawienia się. Proces może w dowolnym
momencie otrzymać sygnał. Winien wówczas przerwać pracę i zareagować na otrzymany sygnał (wykonać
odpowiednie operacje). Dlatego też sygnały nazywa się inaczej przerwaniami programowymi.
W systemie UNIX do każdego typu sygnału przypisane są określone czynności domyślne, które powinien wykonać
proces po otrzymaniu danego sygnału. Mogą to być:
·ð zatrzymanie procesu
·ð zakoÅ„czenie procesu
·ð zakoÅ„czenie procesu z zapisaniem obrazu pamiÄ™ci (utworzenie pliku core)
·ð ignorowanie
Sygnał można wysłać:
·ð poleceniem kill
kill -signal pid
np.: kill -INT 2367
Listę sygnałów można wypisać poleceniem kill -l
·ð funkcjÄ… kill
int kill(int pid, int sig);
·ð naciskajÄ…c klawisz terminala (patrz tabela)
·ð przez jÄ…dro: bÅ‚Ä™dy operacji, adresacji, arytmetyczne, pojawienie siÄ™ wysokopriorytetowych danych w gniezdzie
itp.
Każdy proces może zawierać swoje funkcje do obsługi sygnałów (nie będziemy się tym jednak zajmować). Proces
może w związku z tym również ignorować sygnały. Istnieją dwa sygnały, tzw. niezawodne, które działają zawsze:
SIGKILL i SIGSTOP.
Przykłady sygnałów:
SIGNAL ID Action Event Command Key
SIGHUP 1 Exit Hangup kill -HUP pid
SIGINT 2 Exit Interrupt kill -INT pid ^C
SIGQUIT 3 Core Quit kill -QUIT pid ^\
SIGKILL 9 Exit Killed kill -9 pid
SIGPIPE 13 Exit Broken Pipe kill -PIPE pid
SIGTERM 15 Exit Terminated kill -TERM pid
SIGSTOP 23 Stop Stopped (signal) kill -STOP pid
SIGTSTP 24 Stop Stopped (user) kill -TSTP pid ^Z ^Y
Wyszukiwarka
Podobne podstrony:
05 procesy metamorficzne
Lab 05 id 2241678 Nieznany
7 04 11 procesy i watki
SO 05 Watki
05 Getting Started with Lab 0
lab 1 01 wprowadzenie do mathcada 1 3
001 PodstAutom Wprowadzenieid 05
05 Podręcznik Proces produkcji wina
05 Wprowadzenie do metodyki RUP
cwiczenie 6 amylazy i enzymy pektynolityczne zastosowanie enzymow w procesach technologii zywnosci
Lab Wprowadzenie do jezyka C
lab Wprowadzenie
Lab Wprowadzenie do systemu UNIX
Lab Wprowadzenie do jezyka C
ZarzÄ…dzanie procesami wprowadzenie2
więcej podobnych podstron