W systemach uniksowych bardzo ważną funkcją jest fork()
.
W zasadzie ta funkcja zwraca nie tyle int, ile
pid_t, ale w definicji typu pid_t jest typedef
int pid_t
, więc spokojnie możesz deklarować
zmienne na pidy jako inty. Chociaż z drugiej strony, typ
pid_t w jakimś celu został zdefiniowany, mianowicie aby kod był
przenośny. W końcu nie w każdym systemie operacyjnym PIDy muszą być liczbami
całkowitymi. Jeśli piszesz coś większego, to rozsądnie będzie używać typu
pid_t.
Funkcja powoduje rozdzielenie procesu na dwie części: proces macierzysty
(czyli ten, który wywołał fork
a) i proces potomny (tzw. potworek
:]) Funkcja teoretycznie kopiuje przestrzeń adresową rodzica (w Linuksie
kopiowane są tylko te fragmenty, w których dokonał zapisu albo rodzic, albo
potworek, czyli tylko te, które się różnią, co pozwala oszczędzić i pamięć,
i czas potrzebny na kopiowanie pamięci). Potworek ma wszystko to samo, co
rodzic, w tym otwarte deskryptory plików i standardowe
wejście/wyjście. Dlatego też przy używaniu fork()
należy
albo szybko wyjść z rodzica, albo zamknąć deskryptor stdin w potworku.
Jednoczesne pobieranie danych scanf
em w potworku i rodzicu może
prowadzić do tego, że nie wiadomo, co poszło do którego procesu. O ile można
to sprawdzić doświadczalnie na Linuksie, to nie będzie to kod przenośny między
un*xami. Pod Linuksem proces macierzysty dostaje czas procesora przed
procesami potomnymi, a na takim Solarisie odwrotnie.
Funkcja fork
zwraca jako wartość pid procesu potomnego. Cały
trick polega na tym, żeby rozróżnić w programie, czy jesteśmy w rodzicu, czy
w potworku (proces potomny jest niemal idealną kopią [-> man
fork] procesu macierzystego). Otóż fork()
zwraca pid
potworka tylko w rodzicu, a w potworku zwraca 0. Można to ładnie
wykorzystać:
int main(void) { if ( fork() ) /* rodzic */ puts("Jestem w rodzicu"); else /* potworek */ puts("Jestem w potworku"); return 0; } |
O fork
u nie ma co dużo mówić, funkcja jaka jest - każdy widzi.
Mogę natomiast powiedzieć o kilku innych fajnych funkcjach, które są w tej
okolicy. Najpierw wait()
. Deklaracja nie zachwyca,
pid_t wait(int*)
. Funkcja czeka, aż
jakiś proces potomny się zakończy, po czym zwraca jego pid, a w argumencie
zapisuje kod zwrócony przez proces potomny (z paroma dodatkowymi informacjami,
poczytaj man wait). Jeśli argument był NULL, to
wait()
nic nigdzie nie zapisuje z oczywistych powodów.
Wykorzystanie?
int main(void) { pid_t pid, pid_z_wait; if (( pid = fork() )) /* rodzic */ { pid_z_wait = wait(NULL); printf("Potworek %d sie skonczyl\n", pid_z_wait); } else /* potworek */ puts("Jestem w potworku"); return 0; } |
Fakt, który potworek się skończył, robi znaczenie przy większej ilości
fork
ów.
Notka: w linijce z if
em zastosowałem podwójne
nawiasy okrągłe. Kompilator może wyświetlić ostrzerzenie, jeśli
w if
ie dokonuje się przypisania (początkujący w C/C++ często
ładują przypisanie = zamiast porównania ==). Dołożenie nawiasu wokół
przypisania likwiduje to ostrzerzenie.
Co jednak, jeśli proces potomny skończy się wcześniej, niż wywoła się funkcja
wait()
? Taki proces funkcjonuje jako zombie, czyli już
umarły, ale jeszcze chodzi ;) Taki zombie czeka tylko na wait()
,
żeby móc powiedzieć rodzicowi "Skończyłem się z takim a takim kodem błędu"
i zdechnąć. Po zakończeniu rodzica procesem rodzicielskim niezakończonych
potworków staje się init, który co jakiś czas wywołuje funkcję
wait()
. W przypadku procesów zombie wait()
kończy
się natychmiast, bez zwykłego opóźnienia wynikającego z czekania na
zakończenie procesu potomnego, ale to chyba jest oczywiste.
Zwrócę jeszcze twoją uwagę na dwie podobne funkcje: pid_t
waitpid(pid_t pid, int *status, int
opcje)
i pid_t wait4(pid_t pid,
int *status, int opcje, struct rusage
*zasoby)
. Pierwsza czeka na określonego potworka, druga robi to
samo, ale zapisuje jeszcze strukturę opisującą zasoby. Wstawiając za ostatni
argument NULL efekt jest identyczny, jak przy
waitpid()
. Po dokładniejsze informacje odsyłam do manuala.
To już wprawdzie nie są funkcje związane bezpośrednio z fork
iem,
ale funkcjonują :) w tych okolicach. pid_t
getpid(void)
i pid_t
getppid(void)
, pierwsza zwraca pid procesu, druga zwraca
pid procesu rodzicielskiego (chciałoby się napisać macierzystego, ale pisałem
w rodzaju męskim, więc niech już zostanie).
To by było na tyle o forkowaniu.