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. */
{
if (nr_tasks >= NR_TASKS -
MIN_TASKS_LEFT_FOR_ROOT) {
}
/*
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). */
if (current -> uid) {
long max_tasks = current -> rlim [RLIMIT_NPROC].
rlim_cur;
max_tasks--; /* count the new process..
*/
/*
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. */
if (max_tasks < nr_tasks) {
struct task_struct *p;
for_each_task (p) {
if (p -> uid == current -> uid)
}
}
}
/*
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. */
for (i = 0 ; i < NR_TASKS ; i++) {
}
/*
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). */
{
struct task_struct *p;
if (flags & CLONE_PID)
/*
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:
if ((++last_pid) & 0xffff8000)
/*
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. */
for_each_task (p) {
if (p->pid == last_pid ||
p->pgrp == last_pid ||
p->session == last_pid)
}
/*
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.
*/
{
if (!(clone_flags & CLONE_VM)) {
struct mm_struct * mm = kmalloc(sizeof(*tsk->mm),
GFP_KERNEL);
if (!mm)
*mm = *current->mm;
mm->count = 1;
mm->def_flags = 0;
mm->mmap_sem = MUTEX;
tsk->mm = mm;
tsk->min_flt = tsk->maj_flt = 0;
tsk->cmin_flt = tsk->cmaj_flt = 0;
tsk->nswap = tsk->cnswap = 0;
if (new_page_tables(tsk)) {
tsk->mm = NULL;
exit_mmap(mm);
goto free_mm;
}
if (dup_mmap(mm)) {
tsk->mm = NULL;
exit_mmap(mm);
free_page_tables(mm);
free_mm:
kfree(mm);
return -ENOMEM;
}
return 0;
}
SET_PAGE_DIR(tsk, current->mm->pgd);
current->mm->count++;
return 0;
}
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. */
{
if (clone_flags & CLONE_FS) {
current->fs->count++;
return 0;
}
tsk->fs = kmalloc(sizeof(*tsk->fs), GFP_KERNEL);
if (!tsk->fs)
tsk->fs->count = 1;
tsk->fs->umask = current->fs->umask;
if ((tsk->fs->root = current->fs->root))
tsk->fs->root->i_count++;
if ((tsk->fs->pwd = current->fs->pwd))
return 0;
}
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. */
{
int i;
struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;
oldf = current->files;
if (clone_flags & CLONE_FILES) {
}
newf = kmalloc(sizeof(*newf), GFP_KERNEL);
tsk->files = newf;
if (!newf)
newf->count = 1;
newf->close_on_exec = oldf->close_on_exec;
newf->open_fds = oldf->open_fds;
old_fds = oldf->fd;
new_fds = newf->fd;
for (i = NR_OPEN; i != 0; i--) {
struct file * f = *old_fds;
old_fds++;
*new_fds = f;
new_fds++;
if (f)
}
return 0;
}
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. */
{
if (clone_flags & CLONE_SIGHAND) {
current->sig->count++;
return 0;
}
tsk->sig = kmalloc(sizeof(*tsk->sig), GFP_KERNEL);
if (!tsk->sig)
tsk->sig->count = 1;
memcpy(tsk->sig->action, current->sig->action, sizeof(tsk->sig->action));
return 0;
}
/*
* 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. */
p = (struct task_struct *) kmalloc(sizeof(*p),
GFP_KERNEL);
/*
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. */
new_stack = alloc_kernel_stack();
/*
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. */
nr = find_empty_process();
/*
Sprawdzamy, czy formalnie mozna utworzyc nowy
proces (czy nie przekroczone zostały wartości stałych itd.) patrz: kod
funkcji (wyżej). */
if (nr < 0)
goto bad_fork_free_stack;
/*
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. */
if (p->exec_domain && p->exec_domain->use_count)
(*p->exec_domain->use_count)++;
/*
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.
*/
if (p->binfmt && p->binfmt->use_count)
(*p->binfmt->use_count)++;
/*
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. */
p->kernel_stack_page = new_stack;
*(unsigned long *) p->kernel_stack_page = STACK_MAGIC;
/*
Wstawiamy informację o stosie jądra procesu -
kopię stosu procesu macierzystego, by nowy proces symulował powrót z wywołania
forka, jak ojciec. */
p->state = TASK_UNINTERRUPTIBLE;
/*
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. */
p->flags &= ~(PF_PTRACED|PF_TRACESYS|PF_SUPERPRIV);
/*
Zerowanie flag procesu nie wywołano funkcji
ptrace, nie śledzimy wywołań funkcji systemowych,
nie używamy przywilejów administratora. */
p->flags |= PF_FORKNOEXEC;
/*
Ustawienie flagi procesu: wywołano fork,
nie wywołano exec. */
p->pid = get_pid(clone_flags);
/*
Ustalenie identyfikatora procesu - patrz: kod
funkcji (wyżej). */
p->next_run = NULL;
p->prev_run = NULL;
/*
Ustalenie pustych dowiązań w kolejce run_queue.
Zmieniane przez wake_up_process na zakończenie
forka. */
p->p_pptr = p->p_opptr = current;
/*
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. */
init_waitqueue(&p->wait_chldexit);
/*
Inicjujemy kolejkę wait_chldexit
- strukturę ojca wykorzystywaną do oczekiwania na zakończenie potomka.
*/
p->it_real_value = p->it_virt_value = p->it_prof_value
= 0;
/*
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. */
p->it_real_incr = p->it_virt_incr = p->it_prof_incr
= 0;
/*
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. */
init_timer(&p->real_timer);
p->real_timer.data = (unsigned long) p;
/*
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.*/
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
/*
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__
p->processor = NO_PROC_ID;
p->lock_depth = 1;
#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: */
/* copy all the process information */
if (copy_files(clone_flags, p))
if (copy_fs(clone_flags, p))
goto bad_fork_cleanup_files;
if (copy_sighand(clone_flags, p))
goto bad_fork_cleanup_fs;
if (copy_mm(clone_flags, p))
goto bad_fork_cleanup_sighand;
/*
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.*/
copy_thread(nr, clone_flags, usp, p, regs);
/*
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). */
p->exit_signal = clone_flags & CSIGNAL;
/*
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. */
p->counter = current->counter >> 1;
/*
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:
if (p->exec_domain && p->exec_domain->use_count)
(*p->exec_domain->use_count)--;
if (p->binfmt && p->binfmt->use_count)
(*p->binfmt->use_count)--;
task[nr] = NULL;
REMOVE_LINKS(p);
nr_tasks--;
/*
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:
free_kernel_stack(new_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.