Tworzenie procesów w systemie Linux

W systemie Linux można tworzyć zarówno procesy ciężkie jak i lekkie (wątki). Udostępnione są w tym celu trzy wywołania systemowe: fork(), vfork(), clone().

Tworzenie procesów ciężkich

fork(void)
Standardowa funkcja systemów Unixowych do tworzenia nowego procesu. Proces potomny otrzymuje kopię segmentów kodu i danych procesu ojca i rozpoczyna wykonanie od instrukcji za wywołaniem fork. W Linuxie stosowana jest semantyka kopiowania przy zapisie (COPY-ON-WRITE). Proces potomny nie otrzymuje oddzielnych kopi stron ojca, a jedynie wskaźniki do oryginałów. Zwiększa się licznik referencji do stron i zaznacza jako tylko do odczytu. W wypadku próby zapisu przez któryś z procesów generowany jest błąd dostępu i dopiero wtedy system operacyjny tworzy kopię strony, zapisując ją. Unika się w ten sposób bezsensownego narzutu kopiowania pamięci, gdy proces potomny od razu wykona funkcję execve().

vfork(void)
Funkcja rodem ze starych Unixów, bez semantyki kopiowania przy zapisie. Proces ojca i syna współdzielą pamięć, lecz proces ojcowski jest blokowany do czasu zwolnienia pamięci przez syna (przy wywołaniu funkcji systemowych execve() lub exit()).W Linuxie istnieje dla zgodności, lecz nie ma potrzeby jej używania.

Tworzenie wątków

Tworzenie wątków jądra (opartych na procesach) w Linuxie realizowane jest przez funkcję biblioteczną __clone():
pid_t __clone(int (*fn)(void*), void *child_stack, unsigned long flag, void *args)
Funkcja ta w glibc2 występuje też pod nazwą clone()
fn
funkcja, którą ma wykonywać proces potomny. Proces potomny kończy się wraz z zakończeniem tej funkcji.
*args
parametry dla funkcji fn.
flags
flagi klonowania wraz z sygnałem wysyłanym przez dziecko przy śmierci.
child_stack
wskaźnik stosu użytkownika dla nowego procesu.
Funkcja __clone() korzysta z ukrytego przed programistą wywołania systemowego clone (czyli funkcji sys_clone()) przekazując flagi i adres stosu. Następnie po kodzie powrotu poznaje, czy znajduje się w procesie potomnym i, jeśli tak jest, wywołuje funkcję fn. Po jej wykonaniu zmusza proces do zakończenia przekazując jej wynik jako kod zwracany przez proces potomny.

Ukryte wywołanie systemowe clone

Wywołanie systemowe clone jest specyficzne Linuxowi. Rozwija się z makra _syscall2(int,clone, int,flags, void*,child_stack).

Nie należy go mylić z funkcją biblioteczną __clone()/clone() omówioną w poprzednim punkcie. Jest to wywołanie niedostępne dla programisty i w przeciwieństwie do omówionej funkcji __clone()/clone() bierze tylko dwa parametry: flagi klonowania oraz adres stosu dziecka.

Wywołania systemowe fork() i vfork() są szczególnymi przypadkami wywołania clone(). Wywołanie fork() jest równoznaczne semantycznie z clone(SIGCHLD, 0), a wywołanie vfork() - clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0).

Przejście w tryb jądra

Wszystkie omówione wywołania korzystają z przerwania int 0x80 aby wywołać odpowiadającą im funkcję systemową. Obsługująca przerwanie funkcja asemblera sys_call() pobiera z rejestrów numer funkcji do wywołania (zdefiniowany w unistd.h), zapisuje na stosie rejestry procesu i wywołuje odpowiednią funkcję systemową. Zgodnie z przyjętą regułą funkcje te nazywają się odpowiednio sys_fork(), sys_vfork() i sys_clone(). Otrzymują one jako argument strukturę pt_regs zawierającą zachowane na stosie wartości rejestrów, z których odczytują potrzebne parametry.

Właściwa praca

Wszystkie z wymienionych funkcji sys_xxxwywołują w końcu funkcję do_fork() z odpowiednimi parametrami. Jako adres stosu dziecka przekazywany jest wskaźnik stosu rodzica, lub w przypadku clone() - podany wskaźnik, jeśli był niezerowy. Dokładnie widać to w kodzie źródłowym.

Funkcja do_fork() tworzy nowy proces zgodnie z przekazanymi jej argumentami i wstawia go do struktur jądra. Nowy proces rozpocznie wykonywanie od funkcji asemblerowej ret_from_fork, która sprawi, że proces potomny powróci z wywołania systemowego tak jak każdy inny. Odróżnia go jedynie zerowy kod powrotu.