2.1.2 Algorytm exit

Spis tresci


Wprowadzenie

W systemie Linux procesy koncza sie wykonujac funkcje systemowa exit. Proces wywolujacy exit przechodzi do stanu zombie, zwalnia swoje zasoby i oczyszcza kontekst, z wyjatkiem pozycji w tablicy procesow. Sposob wywolania funkcji jest nastepujacy: exit (status); przy czym status jest wartoscia calkowita przekazywana procesowi macierzystemu do zbadania. Przyjmuje sie zasade, ze liczba zero oznacza poprawne zakonczenie sie procesu, a wartosc statusu rozna od zera wskazuje na wystapienie bledu. Procesy moga wolac exit jawnie badz niejawnie na zakonczenie programu; funkcja inicjujaca dolaczana do wszystkich programow w C wywoluje exit po powrocie programu z funkcji main. Z drugiej strony jadro moze wywolac funkcje exit na potrzeby procesu po nadejsciu nie przechwytywanego sygnalu. W tym przypadku wartoscia status jest numer sygnalu.


Krotki opis algorytmu

algorytm exit
wejscie : kod zakonczenia procesu;
wyjscie : brak;

{

Zapisz dane o pracy procesu do pliku rozliczeniowego;
if (uzywal semaforow)
Odwroc efekty wykonania operacji semaforowych;

Zwolnij pamiec przydzielona procesowi na segmenty danych, kodu, tablice stron ;
Zamknij otwarte pliki;
Zwolnij i-wezly biezacego i glownego katalogu;
Zamknij tablice obslugi sygnalow;
Ustaw stan procesu na ZOMBIE;
Ustaw wartosc exit_code;
Powiadom proces macierzysty o swojej smierci;
for each (potomek) {

Uczyn proces init (1) procesem macierzystym wszystkich procesow potomnych, jesli ktores z dzieci bylo w stanie ZOMBIE, to wyslij do init sygnal smierci potomka;

}
if (lider grupy procesow zwiazanej z terminalem sterujacym)

Wyslij sygnal zawieszenia do wszystkich procesow zwiazanych z tym terminalem;

Wywolaj szeregowanie procesow (bez powrotu);

}


Zapisywanie danych do pliku rozliczeniowego

W trakcie wykonywania funkcji systemowej exit jadro zapisuje rekord danych o pracy procesu do globalnego pliku rozliczeniowego. Do ustalania nazwy pliku rozliczeniowego sluzy funkcja systemowa acct (char* acct_file). Parametr acct_file jest nazwa pliku, do ktorego chcemy zapisywac dane statystyczne. Jesli parametr acct_file jest pustym napisem, to jadro wylacza tryb zbierania danych. Definicja struktury acct opisujacej rekord rozliczeniowy znajduje sie w pliku linux/acct.h .

#define ACCT_COMM 16
struct acct { 
   char ac_comm[ACCT_COMM];         /* Nazwa programu*/ 
   time_t ac_utime;                 /* Czas w trybie uzytkownika */ 
   time_t ac_stime;                 /* Czas w trybie jadra */ 
   time_t ac_etime;                 /* Czas pracy w sek. */ 
   time_t ac_btime;                 /* Czas rozpoczecia pracy */ 
   uid_t ac_uid;                    /* User ID */ 
   gid_t ac_gid;                    /* Group ID */ 
   dev_t ac_tty;                    /* Identyfikator terminala */ 
   char ac_flag;                    /* Info o zakonczonym procesie */ 
   long ac_minflt;                  /* Bledy strony*/ 
   long ac_majflt; 
   long ac_exitcode;                /* Kod zakonczenia programu */ 
};

Na pole ac_comm kopiowana jest nazwa (pole comm ze struktury procesu)procesu wykonujacego exit. Kolejne dwa pola opisuja ilosc czasu spedzonego przez proces wykonujacy exit w trybie jadra i w trybie uzytkownika. Pole ac_etime przechowuje calkowity czas pracy procesu w sekundach (uwaga: nie jest to ac_utime + ac_stime, tylko czas od utworzenia do smierci procesu), a pole ac_btime czas (w sekundach) rozpoczecia pracy przez proces (mierzony od polnocy 1 stycznia 1970 roku). Na ac_uid i ac_gid sa kopiowane identyfikator uzytkownika i grupy procesu wykonujacego exit (pola uid i gid ze struktury procesu). Na ac_tty wpisywany jest identyfikator calkowity terminala. Jest on liczony w nastepujacy sposob: jezeli proces wykonujacy exit nie byl zwiazany z terminalem (pole tty z task_struct jest rowne NULL), to identyfikator terminala wynosi -1 wpp. jest liczony za pomoca funkcji kdev_t_to_nr (zdefiniowana w linux/kdev_t.h) zwracajacej dla terminala unikatowa wartosc calkowita. Na pole ac_flag wpisywana jest informacja o zakonczonym procesie. Jest ona zapisywana w postaci jednej z czterech flag: Fragment z pliku linux/acct.h:

#define AFORK 0001     /* proces wykonanal fork bez exec'a 
#define ASU 0002       /* proces uzywal przywilejow super-user'a 
#define ACORE 0004     /* zostal wykonany zrzut pamieci (dumped core) 
#define AXSIG 0010     /* proces zostal zabity przez sygnal 

Na ac_minflt, ac_majflt wpisywana jest ilosc malych i duzych bledow stron (w celu rozliczenia zuzycia pamieci przez proces). Na ac_exitcode zapisywany jest kod zakonczenia procesu (parametr wywolania z funkcji exit).

Modyfikacja wartosci uzywanych semaforow

Mozliwa jest taka sytuacja, ze proces wykonujacy exit korzystal z semforow. Moze to doprowadzic do sytuacji niepozadanej z punktu widzenia programisty, np.: proces wykonujacy exit moze opuscic semafor, uniemozliwiajac w ten sposob wejscie do sekcji krytycznej innym procesom i otrzymac sygnal SIGKILL. Linux stara sie zapobiegac takim sytuacjom. Proces wykonujacy funkcje systemowa semop moze ustawic opcje SEM_UNDO (szczegoly na stronie o semaforach). Powoduje to, ze w momencie zakonczenia wykonywania procesu jadro odwraca efekty wykonania wszystkich operacji semaforowych realizowanych przez proces. Odbywa sie to w nastepujacy sposob: kazdy proces posiada wskaznik do struktur uniewaznien (semundo ze struktury procesu) polaczonych w liste. Kazdy element takiej listy trzyma tablice wartosci dostosowawczych dla zestawu semaforow (dla kazdego semafora z zestawu trzymana jest wartosc o jaka ten proces zmodyfikowal zmienna semaforowa (ze znakiem przeciwnym)).
PRZYKLAD

Proces X wykonal 3 razy operacje opuszczenia semafora I z flaga SEM_UNDO. Powoduje to, ze wartosc dostosowawcza dla semafora I wynosi 3.
Teraz proces X wykonuje operacje podniesienia semafora I. Wartosc dostosowawcza wynosi 2.

W momencie zakonczenia wykonywania procesu jadro przeglada wszystkie struktury uniewaznien tego procesu i wykonuje operacje na semaforach okreslone przez wartosci w tablicy wartosci dostosowawczych. Powoduje to odwrocenie efektow wykonania wszystkich operacji semaforowych realizowanych przez ten proces. Nastepnie jadro przechodzi kolejke procesow wstrzymanych na kazdym z semaforow, ktorych wartosc jadro zmodyfikowalo o wartosc dostosowawcza i probuje wykonac operacje wymagane przez te procesy. Efekt koncowy jest taki, ze procesy, ktore moga przejsc przez tak zmodyfikowany semafor, kontynuja swoje dzialanie, zas pozostale nadal czekaja na kolejce procesow wstrzymanych.

Zwalnianie pamieci przydzielonej procesowi

Zwalniana jest pamiec zwiazana z procesem (pole mm ze struktury procesu). Jest to pamiec przeznaczona na segmenty danych, kodu, tablice stron. Jezeli struktura mm jest wspoldzielona przez kilka procesow, to proces wykonujacy exit tylko zmniejsza licznik odwolan do niej, wpp. jadro odzyskuje pamiec przydzielona uzytkownikowi. Odbywa sie to tak, ze jadro zwalnia pamiec trzymana przez proces w mapie zajetych przez niego obszarow pamieci (pole mmap ze struktury pamieci), zwalniane sa i-wezly zwiazane z tymi obszarami, odbywa sie zwalnianie tablicy stron pamieci. Zwalniana jest pamiec przydzielona na strukture mm.

Zamykanie otwartych plikow

Podczas wykonywania funkcji exit zamykane sa otwarte pliki. Jadro sprawdza czy struktura files (ze struktury procesu) procesu wykonujacego exit nie jest wspoldzielona przez kilka procesow. Jezeli tak, to proces tylko dekrementuje licznik odwolan do tej struktury wpp. nastepuje przegladanie deskryptorow otwartych plikow i zamykanie tych plikow. Zwalniana jest pamiec przydzielona na strukture files.

Zwalnianie i-wezlow

Podczas wykonywania funkcji exit zwalniane sa i-wezly biezacego katalogu i katalogu korzenia systemu plikow. Fizycznie zwalnianie odbywa sie tylko wtedy, gdy struktura fs (ze struktury procesu) nie jest wspoldzielona wpp. zmniejszany jest tylko licznik odwolan do tej struktury. Zwalniana jest pamiec zarezerwowana na informacje o systemie plikow (fs).

Zwalnianie tablicy obslugi sygnalow

W trakcie wykonywania funkcji exit jadro usuwa tablice oslugi sygnalow dla procesu. Jezeli zaden proces nie wspoldzieli obslugi sygnalow (z procesem wykonujacym exit) to jadro usuwa tablice ich obslugi, wpp. dekrementuje licznik odwolan do tej tablicy.

Ustawianie pol state i exit_code.

W trakcie wykonywania funkcji exit stan procesu jest ustawiany na ZOMBIE (pole state ze struktury procesu). Jest to niezbedne do tego aby po wywolaniu szeregowania procesow sterowanie nie powrocilo do funkcji exit (patrz zakonczenie - wywolanie szeregowania procesow). Ustawiany jest takze kod zakonczenia procesu (exit_code ze struktury procesu) na wartosc przekazywana przez parametr wywolania funkcji exit. Wartosc ta (znajdujaca sie w polu exit_code) jest pozniej zwracana przez funkcje wait (status) w polu statusu.

Powiadamianie procesow 'spokrewnionych' o swojej smierci

1.Powiadomienie procesu macierzystego. Powiadomienie procesu macierzystego o wlasnej smierci odbywa sie za pomoca przeslania sygnalu. Jezeli proces macierzysty byl wstrzymany w oczekiwaniu na zakonczenie procesu potomka (wywolal funkcje wait), to jest budzony i wstawiany do kolejki procesow gotowych do wykonania. Mozliwe sa dwa przypadki: - procesem macierzystym jest Init; - procesem macierzystym jest inny proces; Gdy procesem macierzystym jest Init, to jest on powiadamiany o smierci potomka przez sygnal SIGCHLD. Gdy ojcem jest inny proces, to przesylany jest sygnal zapisany w polu exit_signal (ze struktury procesu)procesu wykonujacego exit. Standardowo w polu tym znajduje sie sygnal SIGCHLD. Zmiana wartosci pola exit_signal jest mozliwa tylko przez rekompilacje jadra (zmiana pierwszego parametru w funkcji sys_fork asm/.../kernel/process.c). Jest to zmiana globalna, obejmujaca wszystkie procesy stworzone tak zmodyfikowana funkcja fork.

2. Przebudowa drzewa procesow, adopcja procesow. Wszystkie procesy bedace potomkami procesu wykonujacego exit sa adoptowane przez proces Init (gdy go nie ma lub wlasnie Init wykonuje exit, to sa one adoptowane przez proces zerowy - task[0]). Proces przekazany do adopcji moze byc w stanie ZOMBIE. Wtedy jest on rowniez adoptowany, lecz do procesu Init jest wysylany sygnal SIGCHLD, powodujacy, ze w najblizszym czasie jadro usunie go z tablicy i drzewa procesow. Kazdy proces zna swoje najblizsze otoczenie w drzewie procesow. Realizowane jest to za pomoca wskaznikow (struktura procesu):

- p_pptr (wskaznik do ojca);
- p_cptr (wskaznik do listy dzieci);
- p_ysptr (wskaznik do 'poprzedniego' brata);
- p_osptr (wskaznik do 'nastepnego' brata);

W przypadku adopcji proces jest dolaczany na poczatek listy dzieci (p_cptr) procesu Init. Wskaznik do pola p_pptr jest modyfikowany, by wskazywal na Init. Proces wykonujacy exit jest usuwany z drzewa procesow i tablicy procesow w efekcie wywolania funkcji wait.

3. Smierc lidera grupy zwiazanej z terminalem sterujacym Jezeli proces wykonujacy exit jest procesem sterujacym terminalem (zazwyczaj jest to shell wywolywany po zgloszeniu sie uzytkownika do systemu), to do wszystkich procesow zwiazanych z tym terminalem jest wysylany sygnal zawieszenia (SIGHUP), ktorgo domyslna obsluga jest zakonczenie procesu. Wysylany jest takze sygnal wznowienia (SIGCONT), ktorego zadaniem jest wznowienie procesow wstrzymanych (TASK_STOPPED). Wznowiony w ten sposob proces moze zareagowac na otrzymany wczesniej sygnal SIGHUP i zakonczyc swoje dzialanie.

Zakonczenie - wywolanie szeregowania procesow

Na zakonczenie funkcji systemowej exit wywolywane jest szeregowanie procesow, ktore wybiera nowy proces do wykonywania w procesorze. Ustawienie stanu procesu wykonujacego exit na ZOMBIE zapewnia nam, ze funkcja szeregowania procesow nie wybierze tego procesu do wykonania. Zatem sterowanie nie powroci do funkcji exit. Proces ZOMBIE bedzie spokojnie spoczywal w tablicy procesow, a jadro bedzie obslugiwac inne procesy.


Bibliografia

1. Pliki zrodlowe Linuxa :


Autor: Wojciech Kilian