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.
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ąć. Zombie automatycznie są likwidowani po zakończeniu rodzica (bo
i po co umarlak, jak nie ma komu co powiedzieć?). W przypadku procesów zombie
wait
kończy się natychmiast, bez zwykłego opóźnienia wynikającego
z czekania na zakończenie procesu, 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.