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.