/* * 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