/* * linux/kernel/exit.c * * Copyright (C) 1991, 1992 Linus Torvalds */ #undef DEBUG_PROC_TREE #include <linux/wait.h> #include <linux/errno.h> #include <linux/signal.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/resource.h> #include <linux/mm.h> #include <linux/tty.h> #include <linux/malloc.h> #include <linux/interrupt.h> #include <asm/segment.h> #include <asm/pgtable.h> extern void sem_exit (void); extern int acct_process (long exitcode); extern void kerneld_exit(void); int getrusage(struct task_struct *, int, struct rusage *);
/* Poniższa procedura generuje sygnał, tzn. ustawia odpowiedni bit w polu signal w task_struct procesu p. Sygnał nie zostanie wysłany, jeżeli proces p go ignoruje (z wyjątkiem SIGCHLD, gdyż nie jest on tak naprawdę ignorowany przy SIG_IGN). W przeciwnym przypadku proces odebrałby sygnał i tak czy tak by nic nie zrobił. Parametry:
static inline void generate(unsigned long sig, struct task_struct * p) { /* mask jest słowem z sig-tym bitem ustawionym na 1, a resztš wyzerowanš */ unsigned long mask = 1 << (sig-1);
/*sa opisuje obsługę "naszego" sygnału przez proces p. W szczególności sa->sa_handler mówi nam, czy sygnał jest ignorowany. Notabene: pole sa_handler zawiera albo wskaźnik do procedury, albo jedną ze stałych (liczbowych) SIG_IGN, SIG_DFL, SIG_ERR */
struct sigaction * sa = sig + p->sig->action - 1;
/*
* Optimize away the signal, if it's a signal that can
* be handled immediately (ie non-blocked and
untraced)
* and that is ignored (either explicitly or by default)
*//* Jeżeli sygnał jest ignorowany i nie jest blokowany, to proces go nie
dostanie. Wyjątkiem jest tutaj SIGCHLD. SIGCHLD zachowuje się trochę dziwacznie
- mimo, że ma sa_handler ustawione
na SIG_IGN, to jest on obsługiwany (p. do_signal). Procesy śledzone (tzn.
mające ustawiony bit PF_TRACED w polu p->flags) zawsze dostają sygnały*/
if (!(mask & p->blocked) && !(p->flags & PF_PTRACED)) { /* don't bother with ignored signals (but SIGCHLD is special) */ if (sa->sa_handler == SIG_IGN && sig != SIGCHLD) return;
/* some signals are ignored by default.. (but SIGCONT already did its deed) *//*To dotyczy sygnałów, dla których zachowaniem domyślnym (SIG_DFL) jest ignorowanie (czyli zgodnie wyłuszczonymi powyżej racjami powinniśmy je ignorować) */
if ((sa->sa_handler == SIG_DFL) && (sig == SIGCONT || sig == SIGCHLD || sig == SIGWINCH || sig == SIGURG)) return; }
/*Tutaj odbywa się "prawdziwe" wysłanie sygnału - zmiana pola p->signal. UWAGA: sygnały blokowane są rejestrowane! Gdy zostaną odblokowane będą obsłużone. Procesy uśpione TASK_INTERRUPTIBLE są budzone (o ile istnieją sygnału odebrane, a nie blokowane) */
p->signal |= mask; if (p->state == TASK_INTERRUPTIBLE && (p->signal & ~p->blocked)) wake_up_process(p); }
/* * Force a signal that the process can't ignore: if necessary * we unblock the signal and change any SIG_IGN to SIG_DFL.*//*Procedura wymuszająca sygnał - robi to co powyższa, z następującymi różnicami:
void force_sig(unsigned long sig, struct task_struct * p) { sig--; if (p->sig) { unsigned long mask = 1UL << sig; struct sigaction *sa = p->sig->action + sig; p->signal |= mask; p->blocked &= ~mask; if (sa->sa_handler == SIG_IGN) sa->sa_handler = SIG_DFL; if (p->state == TASK_INTERRUPTIBLE) wake_up_process(p); } }
/* To jest "prawdziwa" procedura wysyłająca sygnał. Z niej korzystają np. notify_parent, oraz - przez pośrednictwo kill_proc i sys_kill - funkcja systemowa kill. Parametry to:
int send_sig(unsigned long sig,struct task_struct * p,int priv) { if (!p || sig > 32) return -EINVAL;
/* Sygnał można wysłać (tzn. mamy uprawnienie), jeżeli:
if (!priv && ((sig != SIGCONT) || (current->session != p->session)) && (current->euid ^ p->suid) && (current->euid ^ p->uid) && (current->uid ^ p->suid) && (current->uid ^ p->uid) && !suser()) return -EPERM;
/* Wysyłanie sygnału zerowego (a takiego nie ma) nie kończy wywołania send_sig. Być może sygnał ten służy do sprawdzenia, czy proces ma uprawnienia do wysyłania sygnałów: wywołanie send_sig(0,p,0) zwróci -EPERM, jeżeli nie można wysłać sygnału, 0 w.p.p.*/
if (!sig) return 0;
/* Jeżeli proces nie ma wskaźnika do signal_struct (zasadniczo tablicy z rekordem sigaction na każdy sygnał), to nie warto go wysyłać. Dzieje się tak, gdy odbiorca jest w stanie ZOMBIE*/
if (!p->sig) return 0;
/*Jeżeli proces dostaje sygnał SIGCONT, automatycznie kasowane jest odebranie sygnałów, które mogły go zatrzymać w oczekiwaniu na wznowienie, a nie zostały jeszcze obsłużone. To samo dotyczy SIGKILL. */
if ((sig == SIGKILL) || (sig == SIGCONT)) { if (p->state == TASK_STOPPED) wake_up_process(p); p->exit_code = 0; p->signal &= ~( (1<<(SIGSTOP-1)) | (1<<(SIGTSTP-1)) | (1<<(SIGTTIN-1)) | (1<<(SIGTTOU-1)) ); }
/* Sytuacja odwrotna - jeżeli SIGCONT (wznowienie) nie zostało jeszcze obsłużone, a proces dostał sygnał zatrzymujący, jest ono kasowane */
if (sig == SIGSTOP || sig == SIGTSTP || sig == SIGTTIN || sig == SIGTTOU) p->signal &= ~(1<<(SIGCONT-1));
/* Actually generate the signal *//* Wywołanie samego sygnału, za pomocą wyżej opisanej procedury */
generate(sig,p); return 0; }
/* Procedura ta informuje rodzica o zakończeniu działania potomka. Rodzic dostanie sygnał niezależnie od uprawnień potomka (trzeci argument funkcji send_sig, czyli priv, jest różny od 0). Następnie budzone są procesy, które wykonały wait.*/
void notify_parent(struct task_struct * tsk, int signal)
{
send_sig(signal, tsk->p_pptr, 1);
wake_up_interruptible(&tsk->p_pptr->wait_chldexit);
}
/*Komentował Mikołaj Bojańczyk */
int kill_pg(int pgrp, int sig, int priv) { struct task_struct *p; int err,retval = -ESRCH; int found = 0; if (sig<0 || sig>32 || pgrp<=0) return -EINVAL;
/* for_each_task(p){BLOK(p)} jest makrem, które wywołuje BLOK(p) dla każdego działającego procesu. W tym przypadku po prostu wykonujemy dla odpowiednich procesów send_sig. */
for_each_task(p) { if (p->pgrp == pgrp) { if ((err = send_sig(sig,p,priv)) != 0) retval = err; else found++; } }
/* Jeżeli przy wysyłaniu sygnału do wszystkich procesów powstał błąd ( -EPERM), błąd ten zostanie zwrócony. Jeżeli w ogóle nie ma procesów w tej grupie, zwracamy -ESRCH. W przeciwnym przypadku zwracane jest 0. */
return(found ? 0 : retval); }
/* * kill_sl() sends a signal to the session leader: this is used * to send SIGHUP to the controlling process of a terminal when * the connection is lost. *//* Procedura poniższa zachowuje się podobnie jak kill_pg, tyle że wysyła sygnał do 'lidera' sesji (np. proces kontrolujący terminal dostaje w ten sposób SIGHUP kiedy utracone jest połączenie z terminalem) */
int kill_sl(int sess, int sig, int priv) { struct task_struct *p; int err,retval = -ESRCH; int found = 0; if (sig<0 || sig>32 || sess<=0) return -EINVAL; for_each_task(p) { if (p->session == sess && p->leader) { if ((err = send_sig(sig,p,priv)) != 0) retval = err; else found++; } } return(found ? 0 : retval); }
/* Funkcja wysyła sygnał do procesu o danym pid. Zupełnie analogiczna do powyższych*/
int kill_proc(int pid, int sig, int priv) { struct task_struct *p; if (sig<0 || sig>32) return -EINVAL; for_each_task(p) { if (p && p->pid == pid) return send_sig(sig,p,priv); } return(-ESRCH); }
/* * POSIX specifies that kill(-1,sig) is unspecified, but what we have * is probably wrong. Should make it like BSD or SYSV. */ /* Funkcja systemowa. Wysyła sygnał, przy czym pid jest rozumiany nastepująco:
Jest to ta sama konwencja co przy argumentach dla funkcji wait. Wszystkie sygnały są wysłane z argumentem priv=0, czyli wysyłanie nieuprzywilejowane (tzn. można nie mieć uprawnien do wysłania sygnału)*/
asmlinkage int sys_kill(int pid,int sig) { int err, retval = 0, count = 0; if (!pid) return(kill_pg(current->pgrp,sig,0));
/* Błąd to:
if (pid == -1) { struct task_struct * p; for_each_task(p) { if (p->pid > 1 && p != current) { ++count; if ((err = send_sig(sig,p,0)) != -EPERM) retval = err; } } return(count ? retval : -ESRCH); } if (pid < 0) return(kill_pg(-pid,sig,0));
/* Normal kill *//*Normalne wysłanie sygnału do procesy */
return(kill_proc(pid,sig,0)); } /* * Determine if a process group is "orphaned", according to the POSIX * definition in 2.2.2.52. Orphaned process groups are not to be affected * by terminal-generated stop signals. Newly orphaned process groups are * to receive a SIGHUP and a SIGCONT. * * "I ask yoU, have you ever known what it is to be an orphan?" */ static int will_become_orphaned_pgrp(int pgrp, struct task_struct * ignored_task) /* Funkcja ta sprawdza czy grupa procesów podana parametrem pgrp (bez uwzględnienia * procesu, na którego task struct wskazuje parametr ignored_task) jest 'osierocona'. * Przez grupę 'osieroconą' uważać będziemy taką grupę, w której wszystkie procesy są * ZOMBI lub gdy jakiś proces nie jest ZOMBI, to jego ojciec należy do tej grupy. * Gdy grupa z wyłączeniem procesu, na którego strukturę task_struct wskazuje parametr * ignored_task, jest 'osierocona' wówczas zwracana jest wartość 1, w p.p. 0. * Działąnie pętli for_each_task(p) omówione jest przy funkcji forget_original_parent(). */ { struct task_struct *p; for_each_task(p) { if ((p == ignored_task) || (p->pgrp != pgrp) || (p->state == TASK_ZOMBIE) || (p->p_pptr->pid == 1)) continue; if ((p->p_pptr->pgrp != pgrp) && (p->p_pptr->session == p->session)) return 0; } return(1); /* (sighing) "Often!" */ } int is_orphaned_pgrp(int pgrp) /* Działa identycznie jak funkcja will_become_orphaned_pgrp z tą różnicą, że główna * pętla dotyczy wszystkich procesów (tzn. nie ma procesu, którego można pominąć). */ { return will_become_orphaned_pgrp(pgrp, 0); } static inline int has_stopped_jobs(int pgrp) /* Funkcja ta sprawdza, czy w grupie podanej parametrem pgrp są jakieś zatrzymane * zadania (tzn. pole state w strukturze task_struct jest równe TASK_STOPPED). * Zwraca 1 jeżeli taki jest oraz 0 gdy takiego procesu nie ma. * Pętla for_each_task(p) omówiona została przy funkcji forget_original_parent(). */ { struct task_struct * p; for_each_task(p) { if (p->pgrp != pgrp) continue; if (p->state == TASK_STOPPED) return(1); } return(0); } static inline void forget_original_parent(struct task_struct * father) /* Funkcja forget_original_parent() dla każdego procesu, którego pole p_opptr (original * parent) w strukturze task_struct wskazuje na strukturę wskazywaną przez parametr * funkcji - father - zmienia wskaźnik ten tak, aby wskazywał na strukturę * task_struct procesu INIT. * W procesach, którym zmieniany jest ten wskaźnik, ustawiany jest również sygnał końca * (przesyłany ojcu po śmierci dziecka) na SIGCHLD. Procesy te będą zaadoptowane * przez init, a procesy któych ojcem jest init, o śmierci informują go * zawsze sygnałem SIGCHLD. * Pętla for_each_task(p) użyta w tej funkcji, jest makrem zdefiniowanym w pliku sched.h. * Jest to pętla for() przechodząca całą listę procesów (określoną polami next_task i * prev_taskw strukturze task_struct procesów) poczynając od procesu INIT. */ { struct task_struct * p; for_each_task(p) { if (p->p_opptr == father) p->exit_signal = SIGCHLD; p->p_opptr = task[smp_num_cpus] ? : task[0]; /* init */ } } static inline void close_files(struct files_struct * files) /* Funkcja ta zamyka otwarte deskryptory wskazywane przez parametr files. */ { int i, j; j = 0; for (;;) { unsigned long set = files->open_fds.fds_bits[j]; i = j * __NFDBITS; j++; if (i = NR_OPEN) break; while (set) { if (set & 1) close_fp(files->fd[i]); /* Funkcja close_fp() zdefiniowana jest w pliku open.c * Zamyka ona deskryptor będący w tablicy otwartych * plików ze struktury files_struct. */ i++; set >>= 1; } } } static inline void __exit_files(struct task_struct *tsk) /* Funkcja ta zamyka otwarte przez proces (na którego strukturę task_struct wskazuje * parametr tsk) pliki. */ { struct files_struct * files = tsk->files; if (files) { /* Jeżeli pole files ze struktury task_struct procesu * jest NULL'em wtedy funkcja kończy działanie. W przeciwnym * przypadku wykonywane są następujące operacje: */ tsk->files = NULL; /* Ustawienie pola files ze struktury task_struct procesu * na NULL. Wartość tego pola zachowana została na * lokalnej zmiennej files. */ if (!--files->count) { /* Zmniejszony zostaje licznik odwołań do struktury * zapamiętanej na zmiennej files. Jeżeli po zmniejszeniu ma * on wartość 0 (tzn. struktura ta nie jest dzielona z innymi * procesami), wtedy wykonywane są następujące funkcje: */ close_files(files); /* Funkcja close_files() zaimplementowana jest w bieżącym * pliku. Zamyka otwarte deskryptory. */ kfree(files); /* Funkcja kfree() zaimplementowana jest w pliku kmalloc.c. * Zwalnia pamięć przedzieloną na strukturę danych * wskazywaną przez parametr. */ } } } void exit_files(struct task_struct *tsk) { __exit_files(tsk); } static inline void __exit_fs(struct task_struct *tsk) /* Funkcja ta zwalnia i-węzły katalogu bieżącego i katalogu głównego * wskazane przez pole fs ze struktury task_struct procesu. */ { struct fs_struct * fs = tsk->fs; if (fs) { /* Jeżeli pole fs ze struktury task_struct procesu * nie jest NULL'em wtedy wykonywane są następujące operacje: */ tsk->fs = NULL; /* Na to pole przypisany zostaje NULL, poprzednia wartość * tego pola została zapamiętana na lokalnej zmiennej fs. */ if (!--fs->count) { /* Zmniejszony zostaje licznik odwołań do struktury, na którą * wskazuje zmienna fs. Jeżeli jest on zerem (tzn. struktura ta * nie jest dzielona z innymi procesami) wtedy: * - zwolnione zostają i-węzły wskazywane przez pola root * i pwd struktury wskazywanej przez fs funkcją iput() * zdefiniowaną w pliku inode.c; * - funkcja kfree() (zdefiniowana w pliku kmalloc.c) zwalnia * pamięć przydzieloną na strukturę wskazywaną przez zmienną fs. */ iput(fs->root); iput(fs->pwd); kfree(fs); } } } void exit_fs(struct task_struct *tsk) { __exit_fs(tsk); } static inline void __exit_sighand(struct task_struct *tsk) /* Zwalnianie pamięci przydzielonej procesowi na tablicę obsługi sygnałów. */ { struct signal_struct * sig = tsk->sig; if (sig) { /* Jeżeli pole sig ze struktury task_struct procesu nie wskazuje * na NULL, wtedy przypisany zostaje mu NULL. Poprzednia wartość tego * pola zachowana jest na lokalnej zmiennej sig. */ tsk->sig = NULL; if (!--sig->count) { /* Zmniejszony zostaje licznik odwołań do struktury, na którą * wskazuje zmienna sig. Jeżeli jest on równy 0 (tzn. struktura * ta nie jest dzielona z innymi procesami), wtedy funkcją * kfree() (zaimplementowaną w pliku kmalloc.c) * zwolniona zostaje pamięć przydzielona na tą strukturę. */ kfree(sig); } } } void exit_sighand(struct task_struct *tsk) { __exit_sighand(tsk); } static inline void __exit_mm(struct task_struct * tsk) /* Zwalnianie pamięci przydzielonej procesowi na tablicę stron, segment kodu * i segment danych. */ { struct mm_struct * mm = tsk->mm; /* Set us up to use the kernel mm state */ if (mm != &init_mm) { /* Jeżeli pole mm ze stuktury task_struct procesu nie wskazuje na * strukturę mm_struct procesu INIT (która jest zdefiniowana w pliku * sched.h), wtedy wykonywane są następujące czynności: */ flush_cache_mm(mm); flush_tlb_mm(mm); /* Funkcje flush_cache_mm i flush_tlb_mmm zdefiniowane są * w pliku init.c i szerzej omówione powinny być w rozdziale * dotyczącym zarządzaniem pamięcią. */ tsk->mm = &init_mm; tsk->swappable = 0; /* Na pole mm w strukturze task_struct procesu przypisany * zostaje adres zmiennej init_mm zdefiniowanej w pliku * sched.h. Jest on tym samym adresem, który przypisany jest * polu mm w strukturze task_struct procesu INIT. Poprzednia * wartość pola mm dla procesu wykonującego 'exit' pamiętana jest * na lokalnej zmiennej mm. Następnie polu swappable ze struktury * task_struct bieżącego procesu (domyślnie ustawionym na 1) * przypisane zostaje 0. */ SET_PAGE_DIR(tsk, swapper_pg_dir); /* free the old state - not used any more */ if (!--mm->count) { /* Licznik odwołań do struktury mm_struct (zapamietanej na * zmiennej mm) zostaje zmniejszony. Jeżeli jest on równy 0 * ( tzn. struktura ta nie jest dzielona z innymi procesami ) * zwolniona zostaje pamięć przydzielona na pola struktury * mm_struct oraz na samą strukturę. */ exit_mmap(mm); free_page_tables(mm); kfree(mm); } } } void exit_mm(struct task_struct *tsk) { __exit_mm(tsk); } /* * Send signals to all our closest relatives so that they know * to properly mourn us.. */ static void exit_notify(void) /* Funkcja exit_notify służy do usunięcia procesu wykonującego 'exit' * z drzewa procesów, oraz wysłania sygnałów do odpowiednich procesów. */ { struct task_struct * p; forget_original_parent(current); /* Funkcja forget_original_parent służy do zmiany pola p_opptr w strukturze * task_struct tak aby wskazywało na strukturę task_struct procesu INIT. * Zmiana ta dotyczy tych procesów, dla których pole to wskazywało * na strukturę task_struct procesu wykonującego 'exit'. */ /* * Check to see if any process groups have become orphaned * as a result of our exiting, and if they have any stopped * jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2) * * Case i: Our father is in a different pgrp than we are * and we were the only connection outside, so our pgrp * is about to become orphaned. */ if ((current->p_pptr->pgrp != current->pgrp) && (current->p_pptr->session == current->session) && will_become_orphaned_pgrp(current->pgrp, current) && has_stopped_jobs(current->pgrp)) { /* Jeżeli proces wykonujący 'exit' jest w innej grupie niż jego rodzic, * oraz grupa ta nie zostanie 'osierocona' po śmierci bieżącego procesu, * oraz istnieje w tej grupie proces zatrzymany, wtedy grupie tej * wysyłane są sygnały SIGHUP oraz SIGCONT, opisane w temacie dotyczącym * sygnałów. * (za grupę 'osieroconą' uważamy taką, w której wszystkie procesy są * ZOMBI lub jeżeli jakiś proces nie jest ZOBMI to jego * ojciec należy do tej grupy) */ kill_pg(current->pgrp,SIGHUP,1); kill_pg(current->pgrp,SIGCONT,1); } /* Let father know we died */ notify_parent(current,current->exit_signal); /* Funkcja notify_parent odpowiada za wysłanie 'ojcu' (procesowi, którego * struktura task_struct wskazywana jest przez pole p_pptr struktury * task_struct procesu wykonującego 'exit') sygnału o śmierci dziecka. */ /* * This loop does two things: * * A. Make init inherit all the child processes * B. Check to see if any process groups have become orphaned * as a result of our exiting, and if they have any stopped * jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2) */ while ((p = current->p_cptr) != NULL) { /* Dopoki wskaźnik na strukturę task_struct dziecka nie jest NULL'em, * w pętli wykonywane są następujące czynności: * - na zmiennej p zachowywany jest wskaźnik do struktury task_struct * najmłodszego dziecka; * - wskaźnik do struktury task_struct najmłodszego dziecka przekierowany * zostaje na strukturę task_struct starszego dziecka; * - jako ojciec procesu P, na którego strukturę task_struct wskazuje * p, przypisany zostaje proces wskazywany przez pole o_pptr * (jeżeli pole to wskazywało na proces wykonujący exit, to po wywołaniu funkcji * forget_original_parent, wskazuje ono na proces INIT); * - proces P zostaje odpowiednio umieszczony na liście dzieci nowego * ojca (tzn. dowiązanie do najmłodszego dziecka nowego ojca zostaje * przekierowane na strukturę task_struct procesu P, wskaźnik do * młodszego rodzeństwa procesu P staje się NULL'em, a starszego * rodzeństwa wskaźnikiem do struktury task_struct dotychczasowego * najmłodszego dziecka nowego ojca, itd.); * - jeżeli proces P jest ZOMBI, wtedy funkcją notify_parent() powiadomiony * zostaje jego nowy rodzic o śmierci dziecka; * - wysłane zostają odpowiednie sygnały do grupy procesu P jeżeli * ojciec jest w innej grupie oraz funkcje is_orphaned_pgrp() * i has_stopped_jobs() zwrócą 1. */ current->p_cptr = p->p_osptr; p->p_ysptr = NULL; p->flags &= ~(PF_PTRACED|PF_TRACESYS); p->p_pptr = p->p_opptr; p->p_osptr = p->p_pptr->p_cptr; p->p_osptr->p_ysptr = p; if (p->p_osptr) p->p_pptr->p_cptr = p; if (p->state == TASK_ZOMBIE) notify_parent(p); /* * process group orphan check * Case ii: Our child is in a different pgrp * than we are, and it was the only connection * outside, so the child pgrp is now orphaned. */ if ((p->pgrp != current->pgrp) && (p->session == current->session) && is_orphaned_pgrp(p->pgrp)&& has_stopped_jobs(p->pgrp)) { kill_pg(p->pgrp,SIGHUP,1); kill_pg(p->pgrp,SIGCONT,1); } } if (current->leader) /* Jeżeli proces wykonujący 'exit' jest leaderem grupy związanej z * pewnym terminalem, wtedy wywoływana jest funkcja disassociate_ctty() * zdefiniowana w pliku tty_io.c, która wykonuje następujące operacje: * - wysyła sygnały: SIGCONT, SIGHUP procesom z grupy; * - 'uwalnia' terminal od 'wpływów' procesów wykonujących się na nim * (tzn. wstawia w polu session struktury tty bieżącego trminala * wartość 0, zaś w polu pgrp tej struktury wartość -1). */ disassociate_ctty(1); } NORET_TYPE void do_exit(long code) /* Jest to główna funkcja wykonująca algorytm 'exit'. * Wywoływana jest w funkcji sys_exit. */ { if (intr_count) { printk("Aiee, killing interrupt handler\n"); intr_count = 0; } /* Zmienna intr_count zadeklarowana jest w interrupt.h. Jest ona flagą * wskazującą na to, czy proces jest w trakcie obsługi przerwania czy nie. * Wykonanie algorytmu różni się dla obu przypadków jedynie pojawieniem * się komunikatu na konsoli. Wyświetlanie komunikatów na konsoli obsługuje * funkcja printk() zdefiniowana w pliku printk.c. */ fake_volatile: /* Etykieta fake_volatile wykorzystana jest do skoku z końca programu * w przypadku niespodziewanego powrotu z funkcji schedule(). */ acct_process(code); /* Funkcja acct_process() zdefiniowana jest w pliku sys.c. * Służy do zapisania danych o pracy procesu do pliku rozliczeniowego. * Flaga acct_active, zdefinoiowana w plklu sys.c odpowiada za to, * czy włączona jest opcja rozliczania procesów. Jeżeli flaga ta jest * niezerowa, wtedy na lokalną zmienną typu struct acct zapisywane * są następujące dane o procesie pobrane lub wyliczone z pól struktury * task_struct procesu: - nazwa programu pobrana z pola com; - czas pracy procesu w trybach jądra i użytkownika pobrane odpowidednio z pól stime i utame; - czas pracy i rozpoczęcia pracy procesu; - identyfikatory użytkownika, grupy, terminala procesu pobrane z pól uid, gid i tty; - flaga opisująca cechy pracy procesu (takie jak: czy wokonany został zrzut pamięci, czy wywołał funkcję fork "bez funkcji exec", itp) pobrane z pola flags. * Następnie dopisane zostają te dane do pliku rozliczeniowego, którego parametry * określone są na zmiennej acct_file. */ current->flags |= PF_EXITING; /* W strukturze task_struct procesu pole flags * jest ustawiane na PF_EXITING. */ del_timer(¤t->real_timer); /* Funkcja del_timer() zdefiniowana jest w pliku shed.c. Służy do usunięcia * prywatnego zegara procesu. Zegar ten znajduje się w stukturze timer_list * zadeklarowanej w pliku time.h. Wskaźnik do zegara procesu wykonującego 'exit' * przechowywany jest w polu real_timer w strukturze task_struct procesu. */ sem_exit(); /* Funkcja sem_exit() zdefiniowana jest w pliku sem.c. Najpierw usuwa ona proces * wykonujący 'exit' z listy procesów czekających na semaforze. Wskaźnik do * elementu tej listy przechowywany jest w polu semsleeping w strukturze * task_struct procesu. Następnie cofane są operacje semaforowe wykonywane ze * znacznikiem SEM_UNDO. Lista tych operacji przechowywana jest w polu * semundo w strukturze task_struct procesu. */ kerneld_exit(); /* Funkcja kerneled_exit() zdefiniowana jest w pliku msg.c. Obsługuje ona * odłączenie procasu od kolejki komunikatów jądra. Jeżeli istnieje ta kolejka * (tzn. kerneld_msqid!=0) oraz proces korzysta z niej (tzn. istnieje i: * 0 <= i <= MAX_KERNELDS takie, że kerneld_arr[i] = current->pid), wówczas * odłączany jest proces od kolejki komunikatow jądra (tzn. kerneld_arr[i]=0) * oraz zminiejszany jest licznik odwołań (tzn. --n_kernelds). W przypadku, * gdy nie ma procesu korzystającego z kolejki komunikatow jądra (tzn. * n_kernelds == 0), kolejka ta zostaje usunięta. Zmienne kerneld_msqid, * kerneld_arr[], n_kernelds i MAX_KERNELDS zdefiniowane są w pliku msg.c. */ __exit_mm(current); /* Funkcja __exit_mm() zdefiniowana jest w bieżącym pliku. Jej celem jest * zwolnienie pamieci przydzielonej procesowi na segmenty danych, kodu * i tablice stron. */ __exit_files(current); /* Funkcja __exit_files() zdefiniowana jest w bieżącym pliku. Zadaniem jej jest * zamknięcie (zwolnienie) otwartych przez proces wykonujący 'exit' plików. */ __exit_fs(current); /* Funkcja __exit_fs() zdefiniowana jest w bieżącym pliku. * Zamyka (zwalnia) ona i-wezly katalogu głównego oraz katalogu bieżącego. */ __exit_sighand(current); /* Funkcja __exit_sighand() zdefiniowana jest w bieżącym pliku. Odpowiada ona * za zwalnianie pamięci przydzielonej na tablicę obsługi sygnałów. */ exit_thread(); /* Funkcja exit_thread() zdefiniowana jest w pliku process.c. Odpowiada ona * między inymi za zwolnienie pamięci przydzielonej procesowi na pole ldt * struktury task_struct. */ current->state = TASK_ZOMBIE; /* Ustawienie pola state w strukturze task_struct procesu na TASK_ZOMBIE. */ current->exit_code = code; /* Ustawienie pola exit_code (kodu wyjścia) w stukturze task_struct * procesu na parametr wywołania funkcji exit. */ exit_notify(); /* Funkcja exit_notify (zdefiniowana w bieżącym pliku) modyfikuje * drzewo procesów, wysyła sygnał do procesu macierzystego o swojej śmierci. * W przypadku gdy proces jest liderem grupy procesów to wysyła grupie * odpowiedni sygnał. */ #ifdef DEBUG_PROC_TREE audit_ptree(); /* Funkcja audit_ptree (zdefiniowana w bieżącym pliku) jest wywoływana * jedynie wtedy, gdy zdefiniowana jest flaga DEBUG_PROC_TREE. Funkcja ta * sprawdza poprawność drzewa procesów. W razie odkrycia błędu wyświetlane * są odpowiednie komunikaty na konsoli. */ #endif if (current->exec_domain && current->exec_domain->use_count) (*current->exec_domain->use_count)--; /* W przypadku gdy proces korzysta ze struktury exec_domain (tzn. pole * exec_domain ze sruktury task_struct procesu - wskaźnik do struktury * exex_domain - nie jest NULL), oraz licznik odwołań do tej struktury nie * jest równy 0, wtedy zmniejszany jest ten licznik. */ if (current->binfmt && current->binfmt->use_count) (*current->binfmt->use_count)--; /* W przypadku, gdy proces korzysta ze struktury linux_binfmt (tzn. pole * binfmt ze struktury task_struct procesu - wskaznik do struktury * linux_binfmt - nie jest NULL), oraz licznik odwołań do tej struktury * nie jest równy 0, wtedy zmniejszany jest ten licznik. */ schedule(); /* Funkcja schedule() zdefiniowana jest w pliku shed.c. Odpowiada ona za * szeregowanie procesow. Szerzej omowiona jest w rozdziale dotyczącym * szeregowania. */ /* * In order to get rid of the "volatile function does return" message * I did this little loop that confuses gcc to think do_exit really * is volatile. In fact it's schedule() that is volatile in some * circumstances: when current-state = ZOMBIE, schedule() never * returns. * * In fact the natural way to do all this is to have the label and the * goto right after each other, but I put the fake_volatile label at * the start of the function just in case something /really/ bad * happens, and the schedule returns. This way we can try again. I'm * not paranoid: it's just that everybody is out to get me. */ goto fake_volatile; /* Z funkcji schedule() proces nie powinien wrócić. Jeżeli zdażył się * jakiś nieprzewidziany wypadek i proces znalazł sie w tym miejscu programu, * powinien powtórzyć wykonanie powyższych funkcji. */ }
Komentował Jacek Drzycimski
asmlinkage int sys_exit(int error_code) { do_exit((error_code&0xff)<<8); } asmlinkage int sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru) { int flag, retval; struct wait_queue wait = { current, NULL }; struct task_struct *p; /* Argument funkcji stat_addr określa adres, pod który ma zostać wpisana przyczyna zakończenia procesu. Wyjątkiem jest stat_addr równe 0 i oznacza, że nie interesuje nas kod zakończenia procesu potomnego.*/ if (stat_addr) { /* sprawdź, czy adres podany jako argument stat_addr jest poprawny, tzn. czy należy do przestrzeni, w której proces może pisać */ flag = verify_area(VERIFY_WRITE, stat_addr, sizeof(*stat_addr)); /* jeżeli argument nie jest poprawny, to zakończ funkcję i zwróć błąd EFAULT */ if (flag) return flag; } /* Argument ru oznacza wskaźnik do struktury rusage, w której pamiętane są informacje o korzystaniu z zasobów takie jak np. czas w trybie użytkownika i czas w trybie jądra,liczba błędów braku strony wymagających korzystania z urządzeń wymiany i i nie wymagających, liczba wymian stron i inne.Podanie jako tego argumentu wartości NULL oznacza, że nie interesują nas takie informacje*/ if (ru) { /* sprawdź czy argument podany jako argument ru jest poprawny, tzn. czy należy do przestrzeni, w której proces może pisać */ flag = verify_area(VERIFY_WRITE, ru, sizeof(*ru)); /* jeśli argument niepoprawny zwróć błąd EFAULT */ if (flag) return flag; } /* Argument options określa różne opcje wywołania sys_wait4.Możemy podać dowolną alternatywę bitową flag:WNOHANG, WUNTRACED, _WCLONE lub 0. Jeżeli argument options jest inny to zwróć błąd EINVAL */ if (options & ~(WNOHANG|WUNTRACED|__WCLONE)) return -EINVAL; /* Proces wkłada siebie do kolejki wait_chldexit.Mógłby to zrobić dopiero przed zaśnięciem, czyli zmianą swojego stanu na TASK_INTERRUPTIBLE, ale wymagałoby to wkładania się do tej kolejki po każdym obudzeniu. Efektywniejsze wydaje się włożenie siebie do kolejki tylko raz, zwłaszcza, że procesy przebywające w kolejce typu wait_queue mające stan inny niż TASK_INTERRUPTIBLE lub TASK_UNINTERRUPTIBLE są ignorawane przez takie operacje na kolejce jak wake_up czy wake_up_ineterruptible.Jeśli jakiś proces potomny zakończy się w chwili gdy proces macierzysty śpi w kolejce wait_chlexit, zbudzi śpiący proces wywołując funkcję notify_parent.*/ add_wait_queue(¤t->wait_chldexit,&wait); repeat: /* zmienna flag określa, czy znaleźliśmy jakiś proces potomny określony przez argument pid */ flag=0; /* przeglądamy wszystkie swoje dzieci w poszukiwaniu procesu spełniajacego ograniczenie narzucone przez pid oraz będącego w stanie TASK_STOPPED lub TASK_ZOMBIE */ for (p = current->p_cptr ; p ; p = p->p_osptr) { if (pid>0) { /* jeżeli argument pid jest większy od zera i identyfikator aktualnie rozpatrywanego potomka jest różny od argumentu pid to przejdź do następnego potomka */ if (p->pid != pid) continue; } else if (!pid) { /* jeżeli argument pid jest równy 0 i aktualnie rozpatrywany potomek ma identyfikator grupy różny od identyfikatora grupy procesu macierzystego to przejdź do następnego potomka */ if (p->pgrp != current->pgrp) continue; } else if (pid != -1) { /* jeżeli argument pid jest mniejszy od -1 i identyfikator grupy rozpatrywanego potomka jest różny od wartości bezwzględnej pid to przejdź do następnego potomka */ if (p->pgrp != -pid) continue; } /* wait for cloned processes iff the __WCLONE flag is set */ /* opcja _WCLONE wywołania sys_wait4 jest używana do czekanie na wątki jądra ( zobacz funkcje pthread.c i manager.c w katalogu linuxthread-0.7 ).Przy tworzeniu takiego wątku jako sygnał wysyłany po śmierci do procesu macierzystego ustawiana jest stała PTHREAD_SIG_RESTART, której wartość równa jest numerowi sygnału SIGUSR1. */ /* jeżeli ustawiona jest opcja _WCLONE i exit_signal procesu wynosi SIGCHLD przejdź do następnego potomka.Analogicznie jeśli opcja ta nie jest ustawiona i exit_signal procesu jest różny od SIGCHLD omiń tego potomka */ if ((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0)) continue; /* zaznacz, że znaleziono jakiś proces potomny spełniający ograniczenia narzucone przez pid */ flag = 1; switch (p->state) { case TASK_STOPPED: /* proces przechodzi w stan TASK_STOPPED po otrzymaniu sygnału: SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU. Wówczas w polu exit_code procesu jest wpisany numer sygnału, który zatrzymał proces.Ojciec sprawdza, czy w polu tym jest O. Jeśli tak oznacza to, że ojciec dowiedział się już o zatrzymaniu procesu potomka i szuka następnego potomka. */ if (!p->exit_code) continue; /* w polu exit_code znajduje się numer sygnału, który zatrzymał proces */ /* argument options wynoszący WUNTRACED powoduje branie pod uwagę wszystkich procesów potomnych w stanie TASK_STOPPED.Jeżeli opcja ta nie jest ustawiona brane pod uwagę są tylko te procesy potomne w stanie TASK_STOPPED, które ustawiły flagę śledzenia PF_PTRACE, wywołując funkcję ptrace(0) */ if (!(options & WUNTRACED) && !(p->flags & PF_PTRACED)) continue; if (ru != NULL) /* wywołanie funkcji getrusage z parametrem RUSAGE_BOTH powoduje zapisanie do struktury wskazywanej przez wskaźnik ru, dla każdego zasobu łączne wykorzystanie zasobów przez proces i jego procesy potomne */ getrusage(p, RUSAGE_BOTH, ru); if (stat_addr) /* do zmiennej wskazywanej przez wskaźnik stat_addr wpisz numer sygnału, który spowodował zatrzymanie procesu potomnego przesunięty o osiem bitów w lewo */ put_user((p->exit_code << 8) | 0x7f, stat_addr); /* ustal wartość pola exit_code w zatrzymanym procesi na 0, aby zaznaczyć, że proces macierzysty dowiedział się już o zatrzymaniu procesu potomnego */ p->exit_code = 0; /* do zmiennej oznaczającej wartość zwracaną przez funkcję wpisz identyfikator zatrzymanego procesu potomnego */ retval = p->pid; goto end_wait4; case TASK_ZOMBIE: /* proces może osiągnać ten stan na dwa sposoby: 1) wywołując funkcję exit i wówczas do pola exit_code procesu wpisywany jest argument dla exit przesunięty o 8 bitów w lewo; 2) otrzymując sygnał, dla którego czynnością domyślną jest zakończenie procesu,o ile proces nie zmienił obsługi takiego sygnału.Wówczas pole exit_code procesu zawiera numer sygnału, który spowodował śmierć procesu (ewentualnie ze znacznikiem zrzutu pamięci ). */ /* uaktualnij czas w trybie jądra i w trybie użytkownika o czasy zużyte przez procesy potomne*/ current->cutime += p->utime + p->cutime; current->cstime += p->stime + p->cstime; if (ru != NULL) /* uaktualnij strukturę wskazywaną przez ru */ getrusage(p, RUSAGE_BOTH, ru); if (stat_addr) /* do zmiennej wskazywanej przez wskaźnik stat_addr wpisz wartość exit_code zakończonego procesu bądź numer sygnału, gdy proces zakończył się z powodu nadejścia sygnału */ put_user(p->exit_code, stat_addr); /* jako wartość zwracaną przez funkcję ustaw identyfikator zakończonego procesu potomnego */ retval = p->pid; if (p->p_opptr != p->p_pptr) { /* Zazwyczaj wskaźnik do ojca i orginalnego ojca procesu wskazują na ten sam proces. Jedynie w funkcji sys_ptrace (zobacz plik ptrace.c w katalogu /arch/i386 ) z parametrem request wynoszącym PTRACE_ATTACH zmieniany jest wskaźnik do ojca procesu.Wskaźnik ten ustawiany jest z powrotem na pierwotną wartość przy wywołaniu funkcji sys_ptrace z argumentem request równym PTRACE_DETACH. Zatem jeżeli proces taki zrobił exit, to wywołując notify_parent powiadomił nie tego ojca co powinien. Musi zatem z powrotem powiadomić o swojej śmierci prawdziwego ojca */ REMOVE_LINKS(p); p->p_pptr = p->p_opptr; SET_LINKS(p); notify_parent(p, p->exit_signal); } else /* zwolnij pozycję w tablicy procesów po zakończonym procesie */ release(p); #ifdef DEBUG_PROC_TREE audit_ptree(); #endif goto end_wait4; default: continue; } } if (flag) { /* nie ma żadnego procesu potomnego, spełniającego ograniczenia wynikające z argumentów funkcji, który przestał działać */ /* jeżeli ustawiona jest opcja WNOHANG to zwróć 0 */ retval = 0; if (options & WNOHANG) goto end_wait4; /* jeżeli w trakcie wykonywania funkcji nadszedł jakiś nieoczekiwany sygnał i nie zablokowany to zwróć bład ERASTARTSYS */ retval = -ERESTARTSYS; if (current->signal & ~current->blocked) goto end_wait4; /* zaśnij dopuszczając przerwania */ current->state=TAST_INTERRUPTIBLE; schedule(); /* po obudzeniu na skutek nadejścia sygnału przejdź do ponownego przeglądania listy dzieci */ goto repeat; } retval = -ECHILD; end_wait4: /* usuń się z kolejki wait_chldexit i zwróć jako wynik funkcji wartość zapamiętaną w zmiennaj retval */ remove_wait_queue(¤t->wait_chldexit,&wait); return retval; }
Komentowała Małgorzata Górbiel
#ifndef __alpha__ /* * sys_waitpid() remains for compatibility. waitpid() should be * implemented by calling sys_wait4() from libc.a. */ asmlinkage int sys_waitpid(pid_t pid,unsigned int * stat_addr, int options) { return sys_wait4(pid, stat_addr, options, NULL); } #endif