Plik kernel/fork.c
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
Wewnętrzna funkcja jądra tworząca nowy proces.
Parametry
- clone_flags
- flagi klonowania. Najniższy bajt
określa sygnał wysyłany do ojca po zakończeniu procesu potomnego
(zwykle SIGCHLD). Pozostałe trzy bajty określają jakie struktury /
własności ojca mają być współdzielone z procesem potomnym.
- stack_start
- adres stosu potomka
- regs
- struktura zawierająca wartości rejestrów
ojca z momentu wywołania funkcji systemowej.
- stack_size
- rozmiar stosu. Nieużywane na
większości architektur.
Algorytm do_fork
- Jeśli ustawiono CLONE_PID, a pid procesu jest różny od zera, zwróć
błąd (-EPERM)
- Zaalokuj pamięć na unię task_union dla nowego procesu - funkcja
alloc_task_struct(). Unia ta zawiera stos trybu jądra procesu
i jego deskryptor. Odtąd na deskryptor tworzonego procesu wskazuje
lokalna zmienna p.
- Skopiuj na p wartość deskryptora bieżącego procesu (wskazywanego
przez makro current)
- Sprawdź czy bieżący proces nie próbuje przekroczyć przypisanego mu limitu
ilości procesów (p->rlim[RLIMIT_NRPROC] <=
p->user->processes). Jeśli tak, zwróć błąd -EAGAIN.
- Zwiększ licznik procesów użytkownika i referencji do struktury
użytkownika (pola p->user->processes i p->user->__count).
- Jeśli przekroczono dopuszczlną liczbę procesów w systemie
(nr_threads >= max_threads) zwróć błąd (-EAGAIN).
- Jeśli proces należy do modułowej domeny wykonania, zwiększ
licznik użycia tej domeny (get_exec_domain()).
- Jeśli proces należy do zmodularyzowanego formatu plików
wykonywalnych, zwiększ licznik odniesień do tego modułu.
- Wyczyść pola did_exec, swappable oraz ustal stan nowego
procesu na TASK_UNINTERRUPTIBLE.
- Ustaw flagi nowego procesu w zależności od clone_flags -
funkcja copy_flags(). Wyczyść flagi korzystania z przywilejów
superużytkownika i obliczeń zmiennopozycyjnych (PF_SUPERPRIV i
PF_USEDFPU). Ustaw flagę PF_FORKNOEXEC. Jeśli proces nie ma być
śledzony wyczyść znacznik p->ptrace.
- Ustaw pola, które nie mogą być odziedziczone po ojcu
- Ustaw pid (get_pid).
- Wyzeruj wskaźniki listy procesów gotowych i najmłodszego
dziecka.
- Zainicjuj kolejkę wait_chldexit do czekania na
dzieci.
- Jeśli przekazano flagę CLONE_VFORK, utwórz i zainicjuj
wskaźnik do struktury completion, służącej do czekania ojca
na zwolnienie pamięci przez syna. W przeciwnym przypadku wyczyść
to pole.
- Wyczyść informacje o sygnałach, które przyszły do procesu.
- Wyzeruj czasy spędzone przez proces w trybie jądra i użytkownika.
- Wyczyść flagę przywództwa sesji.
- Ustaw czas rozpoczęcia procesu (p->start_time=jiffies).
- Ustaw pola odpowiedzialne za zarządzanie otwartymi plikami,
związkiem z systemem plików, pamięcią i obsługą sygnałów. Odpowiednie
struktury są kopiowane z deskryptora ojca, lub, jeśli określono to w
clone_flags, nowy proces bezpośrednio się do nich odwołuje
zwiększając licznik referencji. W wypadku niepowodzenia zwróć
wartość (-ENOMEM). Wszystkie poniższe funkcje biorą jako parametry
flagi klonowania i strukturę tworzonego procesu.
- p->files : copy_files(). Ustawianie struktury files_struct opisującej deskryptory otwartych plików nowego procesu.
- p->fs : copy_fs(). Ustawianie struktury fs_struct
określające miejsce wykonania w systemie plików nowego procesu.
- p->sig : copy_sighand(). Ustawianie struktury
signal_struct odpowiedzialnej za przechowywanie funkcji
obsługujących sygnały. Informacja o otrzymanych przez proces
sygnałach i tych blokowanych jest trzymana bezpośrednio w
task_struct (pola sigpending i
blocked), więc nie podlega współdzieleniu.
- p->mm : copy_mm(). Funkcja kopiuje strukturę mm i
ustawia wszystkim stronom znacznik tylko-do-odczytu ( o ile nie
ustawiono flagi CLONE_VM). Strony pamięci nie są bezpośrednio kopiowane -
stosowana jest strategia kopiowania przy
zapisie.
- Ustal kontekst nowego procesu. Funkcja copy_thread()
ustawia adres bazowy stosu jądra dziecka i kopiuje na niego wartości
rejestrów ojca. Ustawia położenie stosu użytkownika na przekazany
parametr. Wskaźnik instrukcji ustawia na funkję ret_from_fork i
wpisuje do rejestru eax (wynik wywołania systemowego) 0. Dzięki temu
pierwszą akcją procesu potomnego będzie wykonanie powrotu z funkcji
systemowej i otrzyma jako wynik funkcji 0.
- Wyzeruj pole semundo odpowiadające ze historię operacji
semaforów IPC.
- Ustaw p->exit_signal na podany (clone_flags & CSIGNAL oraz
p->pdeath_signal na 0. Ustaw znacznik p->swappable.
- Rozdziel po równo dynamiczny priorytet (counter) ojca
między ojca i syna, aby zachować równowagę w systemie.
- Ustaw pola relacji rodzicielskich (jeśli przekazano flagę
CLONE_PARENT lub CLONE_THREAD to ojcem
procesu potomnego zostaje ojciec procesu wywołującego funkcję
do_fork()). Jeśli ustawiono CLONE_THREAD, to dodaj proces do grupy
wątków ojca.
- Wstaw deskryptor utworzonego procesu do listy procesów
(makro SET_LINKS).
- Wstaw deskryptor nowego procesu do tablicy haszującej
pidhash- funkcja hash_pid()
- Zwiększ licznik procesów w systemie.
- Jeśli proces potomny jest śledzony, wyślij do niego sygnał SIGSTOP.
- Wstaw proces do listy procesów wykonywanych (runqueue) -
funkcja wake_up_process().
- Zwiększ licznik dokonanych forków (rozwidleń?) w systemie.
- Jeśli ustawiono flagę CLONE_VFORK to zawieś ojca na
zainicjowanej wcześniej strukturze completion dziecka
- wait_for_completion(p->vfork_done). Zostanie
obudzony, gdy proces potomny wywoła funkcję mm_release().
- Zwróć PID utworzonego procesu.
Obsługa błędów
Funkcja do_fork() ma zaimplementowaną bardzo dokładną obsługę błędów. W
wypadku rozpozniania błędu nie następuje bezpośredni powrót z funkcji,
lecz skok do jednej z wielu etykiet i zwolnienie posiadanych w danej
chwili zasobów. Zatem nawet zakończone błędem wywołanie systemowe nie
pozostawia systemu w stanie niespójnym.