首先先说一下在linux中凡是syscall_XXX或者do_XXX 都是中断调用的函数。
exit()是一个销毁函数,当中会调用 do_exit() 这个中断函数(系统调用),首先我们先看一下内核销毁进程的流程。
(1)首先该函数会释放进程的代码段和数据段占用的内存
(2)关闭进程打开的所有文件,对当前的目录和i节点进行同步(文件操作)
(3)如果当前要销毁的进程有子进程,那么就让1号进程作为新的父进程(init进程),如 果当前进程是一个会话头进程,则会终止会话中的所有进程
(4)改变当前进程的运行状态,变成TASK_ZOMBIE僵死状态,并且向其父进程发送SIGCHLD信号。
(5)父进程在运行子进程的时候一般都会运行wait waitpid这两个函数(父进程等待某个子进程终止的),
(6)父进程一直在wait_pid,直到子进程发来信号后,父进程才将其task_struct等变量清除,终止进程的僵死状态。
(7)首先父进程会把子进程的运行时间累加到自己的进程变量中,ctime,stime。
(8)把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽(task链表)
首先来看一下exit.c中的release函数
void release(struct task_struct * p) { int i; if (!p) return; for (i=1 ; i<NR_TASKS ; i++) /*在整个任务链表中找到对应的task_struct,将任务槽清空并释放其占用的内存*/ if (task[i]==p) { task[i]=NULL; free_page((long)p); schedule(); /*重新调度进程*/ return; } panic("trying to release non-existent task"); }
该函数完成清空了任务描述表中的对应进程表项,释放对应的内存页(代码段数据段堆栈)
exit.c中的send_sig函数
static inline int send_sig(long sig,struct task_struct * p,int priv) { if (!p || sig<1 || sig>32) /*信号不在范围内则退出*/ return -EINVAL; if (priv || (current->euid==p->euid) || suser()) /*如果权限为1||当前进程的有效用户id等于所指定进程的id||超级用户*/ p->signal |= (1<<(sig-1)); /*向指定进程task_struct的信号中写入发送的信号*/ else return -EPERM; return 0; }
该函数用来向进程发送信号,用于进程间通信或子进程和父进程之间。
(current->euid==p->euid) || suser()
这段代码的意思是如果你要向某个进程发送信号时,要么你是那个目标进程的使用者,要么你是系统的管理者。
exit.c中的kill_session函数
static void kill_session(void) { struct task_struct **p = NR_TASKS + task; while (--p > &FIRST_TASK) { /*关闭对话的扫描不包括0号进程*/ if (*p && (*p)->session == current->session) (*p)->signal |= 1<<(SIGHUP-1); /*发送结束回话的信号*/ } }
终止当前进程的会话(进程与进程之间的通信)
exit.c中的sys_kill(int pid,int sig)函数,可向任何进程或者进程组发送任何信号。但该函数并没有杀死进程的意思。kill -x 就是向进程发送信号的意思。
int sys_kill(int pid,int sig) { struct task_struct **p = NR_TASKS + task;/*将指针指到整个task链表的最后*/ int err, retval = 0; if (!pid) /*pid=0给当前进程的进程组发送sig*/ while (--p > &FIRST_TASK) { /*从后往前找到对应的进程*/ if (*p && (*p)->pgrp == current->pid) if (err=send_sig(sig,*p,1)) retval = err; } else if (pid>0) /*pid>0给对应的pid发送sig*/ while (--p > &FIRST_TASK) { /*从后往前找到对应的进程*/ if (*p && (*p)->pid == pid) if (err=send_sig(sig,*p,0)) /*发送指定信号*/ retval = err; } else if (pid == -1) /*pid=-1给任何进程发送 (相当于kill all)*/ while (--p > &FIRST_TASK) /*从后往前找到对应的进程*/ if (err = send_sig(sig,*p,0)) retval = err; else /*pid<-1 给进程组号为-pid的进程组发送信号*/ while (--p > &FIRST_TASK) /*从后往前找到对应的进程*/ if (*p && (*p)->pgrp == -pid) if (err = send_sig(sig,*p,0)) retval = err; return retval; }
这个函数传入参数中的pid很有讲究
1.pid>0给对应的pid发送sig
2.pid=0给当前进程的进程组发送sig
3.pid=-1给任何进程发送 (相当于kill all)
4.pid<-1 给进程组号为-pid的进程组发送信号,例如pid=-4则向租号为4的进程组发送
下面我们就来看看最重要的do_exit() 函数
int do_exit(long code) { int i; free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); /*释放ldt段*/ free_page_tables(get_base(current->ldt[2]),get_limit(0x17)); for (i=0 ; i<NR_TASKS ; i++) if (task[i] && task[i]->father == current->pid) { task[i]->father = 1; /*如果当前要销毁的进程有子进程,那么就让1号进程作为新的父进程(init进程*/ if (task[i]->state == TASK_ZOMBIE)/*如果进程僵死,则让发送信号给其父进程1号进程*/ /* assumption task[1] is always init */ (void) send_sig(SIGCHLD, task[1], 1); } for (i=0 ; i<NR_OPEN ; i++) /*每个进程能打开的最大文件数是NR_OPEN*/ if (current->filp[i])/*如果当前进程有打开的文件,则关闭*/ sys_close(i); /*对当前的目录和i节点进行同步(文件操作)*/ iput(current->pwd); current->pwd=NULL; iput(current->root); current->root=NULL; iput(current->executable); current->executable=NULL; if (current->leader && current->tty >= 0) /*如果进程打开了控制台*/ tty_table[current->tty].pgrp = 0; /*将控制台清空*/ if (last_task_used_math == current) /*使用了协处理器*/ last_task_used_math = NULL; /*将协处理器清空*/ if (current->leader) /*如果进程时会话头进程,则关闭会话*/ kill_session(); current->state = TASK_ZOMBIE; /*当前进程设置为僵死状态*/ current->exit_code = code; /* 设置任务执行停止的退出码,其父进程会取。*/ tell_father(current->father); schedule(); return (-1); /* just to suppress warnings */ }