Kod źródłowy funkcji fork

Przyjęta konwencja:

Kod źródłowy (oraz komentarz dodany przez autora kodu) jest pisany czcionką o stałej szerokości znaków, komentarz dodany – czcionką proporcjonalną, czerwoną.
Komentarz następuje po fragmencie kodu do którego się odnosi.
Funkcje duplikujące struktury procesu, niejako pomocnicze w stosunku do algorytmu, nie zostały skomentowane aż tak dokładnie jak główna część algorytmu. Krótki opis ich treści, znaczenia, działania oraz zwracanych wartości umieszczono po nagłówku funkcji.


/*
 *  linux/kernel/fork.c
 *
 *  Copyright (C) 1991, 1992 Linus Torvalds
 */

/*
 *  'fork.c' contains the help-routines for the 'fork' system call
 *  (see also system_call.s).
 *  Fork is rather simple, once you get the hang of it, but the memory
 *  management can be a bitch. See 'mm/mm.c': 'copy_page_tables()'
 */
 
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/ldt.h>
#include <linux/smp.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/pgtable.h>
 /*
Włączenie plików z definicjami wykorzystywanych przez algorytm struktur, definicji i makr.
Opisu zawartości ważniejszych plików szukaj w rozdziale "Bibliografia" opisu algorytmu funkcji fork.  */

int nr_tasks=1;
/*
Inicjacja zmiennych systemowych wykorzystywanych przez algorytm fork.
Nr_tasks to liczba zadań istniejących w systemie.  */

int nr_running=1;
/*
Liczba wykonujących się zadań.
Zmienna ma większe zastosowanie w systemach wieloprocesorowych.  */
 
unsigned long int total_forks=0;   /* Handle normal Linux uptimes. */
/*
Liczba wykonanych od startu systemu rozwidleń procesów.  */

int last_pid=0;
/*
Ostatnio przydzielony w wyniku wykonania forka identyfikator procesu.  */
 




static inline int find_empty_process (void)
/*
Funkcja sprawdza, czy ograniczenia nałożone na system pozwalają na utworzenie nowego procesu.
Daje w wyniku numer wolnej pozycji w tablicy procesów (do zajęcia przez nowy proces) lub liczbę ujemną (wartość -EAGAIN  czyli -11, definicja w errno.h) w przypadku niepowodzenia - zbyt dużej liczby procesów w systemie bądź przekroczenia limitu danego użytkownika. */

{

/*
Upewniamy się, czy liczba istniejących w systemie procesów nie przekracza (konfigurowalnej) wartości maksymalnej.
W systemie można uruchomić nie więcej niż  NR_TASKS  zadań - to ograniczenie fizyczne, tyle wynosi wielkość tablicy procesów - lecz ostatni utworzony proces, właśnie o numerze  NR_TASKS, mógłby niechybnie doprowadzić do blokady całego systemu - nie wiemy bowiem nic o strukturze działających w systemie zadań, w szczególności o tym, czy i kiedy się one zakończą. Aby temu zapobiec, istnieje stała  MIN_TASKS_LEFT_FOR_ROOT, określająca liczbę miejsc w tablicy procesów zarezerwowanych dla administratora systemu. On to, w sytuacji krytycznej, jest w stanie zlikwidować zaistniałą blokadę, uruchamiając dodatkowe procesy i wysyłając (na przykład) sygnały kill do wybranych zadań.  Własność ta, jakkolwiek pożyteczna, ogranicza “zwykłym” użytkownikom liczbę zadań obecnych w systemie do wartości różnicy  NR_TASKS - MIN_TASKS_LEFT_FOR_ROOT, i z tą właśnie wielkością porównywana jest liczba uruchomionych procesów przed próbą utworzenia nowego.

Pole current->uid  określa identyfikator użytkownika, który chce utworzyć nowy proces - current  jest procesem wywołującym funkcję fork. W przypadku "zwykłego" użytkownika identyfikator ten jest liczbą dodatnią, administrator ma identyfikator zero. Warunek (current->uid) jest więc spełniony (zwraca true) dla dowolnego użytkownika prócz administratora systemu - jeśli więc okaże się, że liczba działających w systemie procesów przekracza dopuszczalną wartość, a użytkownik wywołujący forka nie jest administratorem systemu, funkcja zwróci wartość -EAGAIN (czyli -11, choć tak naprawdę ważne jest jedynie to, że jest to liczba ujemna, a nie pozycja w tablicy procesów, patrz: kod funkcji do_fork). */

/*
Następnie sprawdzamy, czy właściciel procesu wywołującego forka nie przekroczył dopuszczalnej liczby uruchomionych procesów. Liczba ta może być ustalona dla każdego użytkownika z osobna - domyślnie odpowiada za nią stała MAX_TASKS_PER_USER (równa standardowo połowie NR_TASKS) i jest zawarta w polu  rlim[RLIMIT_NPROC].rlim_cur struktury opisującej proces (pole rlim to dziesięcioelementowa tablica rekordów zawierająca dane o różnych organiczeniach,  RLIMIT_NPROC odpowiada właśnie za liczbę możliwych do uruchomienia procesów). Ograniczenie to nie dotyczy administratora systemu. */ /*
Pętla for_each_task odpowiada za zliczenie procesów, których właścicielem jest użytkownik wywołujący forka. Błąd (i wynikająca stąd niemozność uruchomienia nowego procesu) powstaje w sytuacji, gdy przekroczony zostanie limit liczby procesów należących do tego użytkownika.

Mimo pozornej nieefektywności jest to najskuteczniejszy i najbezpieczniejszy sposób na sprawdzenie owego limitu - system nie przechowuje bowiem w żadnym dodatkowym miejscu informacji o liczbie procesów uruchomionych przez każdego użytkownika.
Aby uniknąć przeszukiwania całej tablicy procesów można by w jakimś miejscu zapamiętywać te dane (dla każdego użytkownika, przynajmniej dla zarejestrowanych w systemie!), ale nie obyłoby się wówczas bez zmiany w algorytmie exit (tak naprawdę nie tylko jego), co nieco skomplikowałoby całe spojrzenie na mechanizm tworzenia procesu. */

/*
Jeśli wszystkie warunki dotyczące stałych systemowych pozostają spełnione, wystarczy jeszcze tylko wyszukać w tablicy procesów miejsce dla nowego zadania. */ }
/* Jeśli znaleźliśmy się poza pętlą przeglądającą tablicę zadań, to wszystkie miejsca w tablicy są zajęte i mimo uprawnień administratora nie uda nam się utworzyć nowego procesu - funkcja kończy się błędem. */
 

 
static int get_pid (unsigned long flags)
/*
Jak łatwo odgadnąć, funkcja daje w wyniku numer pidu nowego procesu.
Jeśli dopuściliśmy klonowanie pidu, jej rezultatem będzie pid procesu-ojca.

Algorytm zawarty w tej i następnych funkcjach rozpoczyna się od sprawdzenia tzw. flag klonowania - parametrów, które określają, czy dana informacja o procesie macierzystym ma zostać powielona w potomku, czy też ma jej odpowiadać zupełnie nowa struktura. Mimo, że sam fork nie pozwala na podanie żadnych parametrów (w wywołaniu funkcji w treści programu użytkownika), jest to wsparcie dla innej funkcji systemowej - clone). */

{

/*
Jak widać, można sklonować nawet pid procesu! Algorytm fork przejdzie jednak przez tę instrukcję bezboleśnie - założenia o wywołaniu funkcji nie dopuszczają klonowania identyfikatora. */

repeat:

/*
Jeśli przekroczyliśmy dla nowego identyfikatora (zwiększonego o jeden w stosunku do starego) wartość FFFF8000 (32768), to liczymy od nowa.
Last_pid to zmienna definiowana na początku fork.c - jest jej wówczas nadawana wartość zero.  */ /*
Dla każdego istniejącego w systemie procesu sprawdzamy: jesli któraś z wartości jest równa last_pid + 1, czyli identyfikatorowi, który ma zostać nadany nowemu procesowi - rozpoczynamy od nowa. Sprawdzamy w ten sposób, czy nie istnieje w systemie zadanie o tym samym identyfikatorze co nowy. Pole pgrp określa identyfikator grupy procesów, do której należy sprawdzany proces (w jakimkolwiek stanie), zaś session to identyfikator sesji związanej z procesem. Jak widać, identyfikator nowego procesu nie może być równy żadnej z tych wartości dla żadnego z istniejących procesów.

Optymalizacja na pierwszy rzut oka nieefektywnej pętli przeglądającej wszystkie zadania mogłaby polegać na utworzeniu listy wolnych identyfikatorów - ale wymagałoby to wykonania na tej liście jakiejś akcji przy wywołaniu exit, czego pewnie autor forka chciał uniknąć. Poza tym, należałoby - chociażby dla zgodności z ideą algorytmu, bo ma to zapewne głębszy sens - zapamiętywać i odpowiednio modyfikować wszystkie ze sprawdzanych wartości (pid, pgrp i session), co mogłoby znacznie zmniejszyć zysk z wprowadzenia takiej zmiany do systemu. */

}
/*
Jeśli nie wykryliśmy konfliktu, możemy przydzielić nowemu procesowi ustalony przed chwilą pid.
Jak widzimy, w przypadku konieczności otrzymania nowego pidu (flaga CLONE_PID nieustawiona), gdyby stała ograniczająca wartość identyfikatora nie była aż tak (w porównaniu z maksymalną liczbą działających procesów) - duża, w konstrukcji funkcji kryłoby sie niebezpieczeństwo trwałego zapętlenia, a przynajmniej sporego opóźnienia wykonania. W rzeczywistym systemie lat dziewięćdziesiątych, z wielkością tablicy procesów ustaloną rozsądnie (w pliku tasks.h) na paręset procesów taki przypadek nam nie grozi, a prostota pętli na pewno wiele ułatwia. */
 



static inline int dup_mmap (struct mm_struct * mm)
/*
Mechanizm działania forka jest prosty, a kod krótki i przejrzysty - największy problem (jak można się przekonać na przykładzie tej i następnej funkcji) sprawia zarządzanie pamięcią.
Funkcja duplikuje strony pamięci rodzica na zasadzie copy-on-write (strony są dzielone do momentu próby zapisu - dopiero wówczas powstają fizyczne kopie).
W przypadku braku pamięci funkcja zwraca wartość -ENOMEM, 0 –- gdy operacja zakończy się sukcesem. */
 
{ }




static inline int copy_mm (unsigned long clone_flags, struct task_struct * tsk)

/*
Funkcja kopiuje struktury pamieci procesu, korzystając ze zdefiniowanej powyżej funkcji dup_mmap. Zerowane są dane statystyczne dotyczące dostępu do pamięci.
Warto zwrócić uwagę na strukturę tej funkcji i następnych, przyjemnie regularną: w zależności od flag odpowiedzialnych za klonowanie kopiujemy strukturę mm_struct albo zwiększamy tylko licznik odwołań (count) do struktury ojca.
Funkcje "copy_*" (to pierwsza z nich, pozostałe poniżej) zwracają zero w przypadku powodzenia, inną wartość (tu: -ENOMEM,  dla innych funkcji z "rodziny" jest to -1) w przypadku błędu. Klonowanie zawsze kończy się sukcesem. */

{

free_mm: }
 



static inline int copy_fs (unsigned long clone_flags, struct task_struct * tsk)
/*
Funkcja kopiuje dane dotyczące związku procesu z systemem plików - informację o masce praw dostępu nadawanych nowo utworzonym plikom oraz o katalogu macierzystym i aktualnym właściciela procesu.
Zwraca zero, jeśli zakończy się pomyślnie, -1 w przypadku błędu. */

{

}
  


static inline int copy_files(unsigned long clone_flags, struct task_struct * tsk)
/*
Funkcja kopiuje informację o deskryptorach otwartych plików używanych przez proces (potomek dziedziczy je od procesu macierzystego) oraz zwiększa liczniki odwołań do odpowiednich i-węzłów.
Zwraca wartość 0, gdy zakończy się sukcesem, -1 w przypadku błędu. */

{

}
 



static inline int copy_sighand(unsigned long clone_flags, struct task_struct * tsk)
/*
Kopiuje tablicę funkcji-akcji obsługujących sygnały.
Zwraca 0 w przypadku sukcesu, -1 w razie niepowodzenia. */

{

}


/*
 * Ok, this is the main fork-routine. It copies the system process
 * information (task[nr]) and sets up the necessary registers. It
 * also copies the data segment in its entirety.
 */

int do_fork (unsigned long clone_flags, unsigned long usp, struct pt_regs *regs)
/*
Tresc funkcji fork. */
 
{

/*
Pomocnicza zmienna, przechowująca pozycję w tablicy procesów, pod którą umieszczony zostanie nowo utworzony proces. */ /*
Jeśli wyjdziemy z funkcji, to z błędem -ENOMEM, czyli braku pamięci, pierwsze operacje algorytmu polegają bowiem na sprawdzeniu dostępności pamięci. */ /*
Pomocnicza zmienna –- wskaźnik na przydzielony obszar pamięci dla stosu jądra. */ /*
Zmienna przechowująca wszystkie ważne informacje o tworzonym procesie. */ /*
Przydzielamy pamięć na strukturę task_struct nowego procesu. */ /*
Jeśli funkcja kmalloc nie zwróciła wskaźnika do struktury, fork nie mógł się udać. Zwrócimy błąd braku pamięci. */ /*
Przydzielamy pamięć na stos jądra. */ /*
Fork nie uda się także, gdy alloc_kernel_stack zwróci NULL; skaczemy do odpowiedniej etykiety. */ /*
Jeśli doszliśmy do tego miejsca i funkcja skończy się błędem, to nie będzie to już błąd pamięci, lecz inny, np. brak miejsca w tablicy procesów. */ /*
Sprawdzamy, czy “formalnie” mozna utworzyc nowy proces (czy nie przekroczone zostały wartości stałych itd.) – patrz: kod funkcji (wyżej). */ /*
Jeśli funkcja find_empty_process zakończyła się błędem (wartość -EAGAIN, -11, zamiast miejsca w tablicy procesów), zwalniamy przydzieloną wcześniej pamięć. */ /*
Pod adres wskazywany przez p wpisujemy to, co jest pod adresem current: kopiujemy informację o procesie macierzystym, teraz będziemy tylko musieli zmienić zawartość niektórych pól. */ /*
Pole exec_domain określa domenę uruchomieniową procesu (może być ona dzielona przez wiele procesów). Zwiększamy licznik określający liczbę odwołań do struktury. */ /*
Pole binfmt opisuje strukturę pliku wykonywalnego i określa postać funkcji odpowiedzialnych za rozpoznanie i załadowanie pliku wykonywalnego. Zwiększamy liczbę odwołań do struktury, */ /*
Proces nie wykonał exec. */ /*
Proces nie podlega wymianie. */ /*
Wstawiamy informację o stosie jądra procesu - kopię stosu procesu macierzystego, by nowy proces symulował powrót z wywołania forka, jak ojciec. */ /*
Proces zmienia stan. Nie odpowiada na żadne sygnały - jest w trakcie tworzenia swoich struktur i wykonanie jakiejkolwiek akcji, na przykład po odebraniu sygnału, nie miałoby sensu. Wartość jest ustawiana na TASK_RUNNING przez wywołanie funkcji wake_up_process w końcowej części forka. */ /*
Zerowanie flag procesu – nie wywołano funkcji ptrace, nie śledzimy wywołań funkcji systemowych, nie używamy przywilejów administratora. */ /*
Ustawienie flagi procesu: wywołano fork, nie wywołano exec. */ /*
Ustalenie identyfikatora procesu - patrz: kod funkcji (wyżej). */ /*
Ustalenie pustych dowiązań w kolejce run_queue. Zmieniane przez wake_up_process na zakończenie forka. */ /*
Ojcem nowego procesu jest proces wywołujący funkcję fork. */ /*
Proces nie ma dzieci, wskaźnik na najmłodsze dziecko jest pusty. */ /*
Zerujemy pole zapamietujące rodzaje sygnałów, ktore nadeszły do procesu. */ /*
Inicjujemy kolejkę wait_chldexit - strukturę ojca wykorzystywaną do oczekiwania na zakończenie potomka. */ /*
Zerujemy pola odpowiedzialne za czas wykonywania się procesu. Pole it_real_value określa rzeczywisty czas wykonania - jest to wartość zegara uruchamianego w chwili rozpoczęcia działania procesu, pole it_virt_value określa właściwy czas wykonania procesu, bez czasu spędzonego przez jądro na obsługę procesu, zaś pole it_prof_value to czas wykonania procesu włącznie z czasem poświęconym mu przez jądro.  */ /*
Wyłączamy obsługę sygnałów SIGALRM, SIGVTALRM i SIGPROF, wysyłanych do procesu przez odpowiednie zegary w określonych w tych polach odstępach czasu. */ /*
Uruchamiamy zegar czasu rzeczywistego. */ /*
Przywództwo grupie procesów nie podlega dziedziczeniu. */ /*
Pole pamiętające - dla lidera grupy procesów, w przypadku zerwania łączności z terminalem sterującym grupy - wartość numeru grupy procesów. Zerowane, nowy proces nie jest przecież liderem grupy.*/ /*
W tym miejscu ustalane są wartości zmiennych określających czas wykonania w trybie użytkownika i w trybie jądra, a także sumy czasów wykonania dzieci naszego procesu w obu tych trybach. */

#ifdef __SMP__

#endif
/*
Segment instrukcji tylko dla systemów wieloprocesorowych. */ /*
Ustalamy czas rozpoczęcia działania przez proces, wpisując w to miejsce wskazanie globalnego zegara - zmienna jiffies określa czas, jaki upłynął od startu systemu (zawiera liczbę tyknięć zegara). */ /*
Wpisujemy nowy proces do tablicy procesów, w znalezione miejsce. */ /*
Ustanawiamy dowiązania między nowym procesem a innymi z listy procesów (makro w sched.h, dopisuje nowy proces do dwukierunkowej listy cyklicznej, korzystając z pól w samym task_struct). */ /*
Zwiększamy liczbę zadań w systemie (zmniejszana przy wykonaniu exit). */ /*
I znów błąd będzie dotyczył pamięci, jeśli fork zakończy się błędem - kopiujemy informację o procesie:  */ /*
Wykonujemy procedury kopiujące odpowiednie struktury ojca do struktur syna.
Kodu procedur copy_files, copy_fs, copy_sighand i copy_mm szukaj wyżej w tym pliku.*/ /*
Najważniejsza część funkcji: rozwidlenie. Od tego miejsca istnieją już dwa osobne procesy: ojciec, który kontynuuje wykonanie funkcji i potomek, który będzie miał złudzenie, że sam zakończył właśnie wykonanie forka. Copy_thread odpowiada za właściwe zakończenie forka; proces potomny zachowa się tak, jakby sam wykonał fork i powrócił z wywołania z wartością zero.
Funkcja copy_thread zdefiniowana jest w pliku process.c i odwołuje się bezpośrednio do rejestrów komputera - odważnym polecam jej lekturę. */ /*
Określamy jako puste pole odpowiedzialne za przechowywanie informacji potrzebnych do anulowania operacji semaforowych. */ /*
Proces podlega wymianie (od tej chwili). */ /*
Określamy rodzaj sygnału przesyłanego ojcu procesu przez potomka przy jego zakończeniu. Standardowo jest to SIGCHLD, ewentualny zmieniony przez zastosowanie odpowiednich flag. */ /*
Pole counter zawiera ilość czasu (liczbę tyknięć zegara), jaki pozostał procesowi do chwili wywłaszczenia go przez jądro. W systemie Linux założono, że nowo tworzonemu procesowi pozwoli się działac przez połowę czasu, jaki jeszcze pozostał ojcu. Założenie to ma wpływ na kolejność wykonania procesów - proces szeregujący wznowi w pierwszej kolejności ojca nowego procesu, a dopiero później jego samego, priorytet procesu zależy bowiem właśnie od wartości zmiennej counter (więcej informacji o algorytmie działania procesu szeregującego znajdziesz w rozdziale dotyczącym szeregowania procesów. */ /*
Funkcja wake_up_process zmienia stan zadania (syna) na TASK_RUNNING i dodaje go do kolejki run_queue. */ /*
Inkrementujemy zmienną odpowiedzialną za liczbę wykonanych od startu systemu forków. */ /*
Wszystko OK. Zwracamy numer utworzonego procesu (dziecko dostanie i tak wartość zero, zadba o to funkcja copy_thread). */

bad_fork_cleanup_sighand:

/*
Usuwamy struktury przydzielone w wyniku wywołania copy_sighand i następnie, kaskadowo, struktury powstałe po copy_fs i copy_files. */

bad_fork_cleanup_fs:

bad_fork_cleanup_files: bad_fork_cleanup: /*
Odwrotność części wypełniającej task_struct nowego procesu. Oczywiście, przywracamy poprzednie wartości tylko zmiennym globalnym oraz dostępnym poprzez wskaźniki. */

bad_fork_free_stack:

/*
Zwalniamy pamięć przydzieloną stosowi jądra niedoszłego procesu potomnego. */

bad_fork_free_p:

/*
Kfree to odwrotność kmalloc - zwalniamy pamięć przydzieloną na strukturę task_struct. */
 
bad_fork: }
/*
Nic jeszcze nie zrobiliśmy, nie przydzieliliśmy żadnej pamięci ani nie zmieniliśmy wartości żadnej zmiennej - wystarczy zwrócić błąd. */


Maciej Ogrodniczuk
26 stycznia 1998 r.