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ł forka) 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 scanfem 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 forku 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 ifem zastosowałem podwójne
nawiasy okrągłe. Kompilator może wyświetlić ostrzerzenie, jeśli
w ifie 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 forkiem,
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.