kernel源码(十四)exit.c
该源码主要实现进程退出或终止的相关功能
源码
/* * linux/kernel/exit.c * * (C) 1991 Linus Torvalds */ #include <errno.h> #include <signal.h> #include <sys/wait.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/tty.h> #include <asm/segment.h> int sys_pause(void); int sys_close(int fd); void release(struct task_struct * p) { int i; if (!p) return; for (i=1 ; i<NR_TASKS ; i++) if (task[i]==p) { task[i]=NULL; free_page((long)p); schedule(); return; } panic("trying to release non-existent task"); } 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()) p->signal |= (1<<(sig-1)); else return -EPERM; return 0; } static void kill_session(void) { struct task_struct **p = NR_TASKS + task; while (--p > &FIRST_TASK) { if (*p && (*p)->session == current->session) (*p)->signal |= 1<<(SIGHUP-1); } } /* * XXX need to check permissions needed to send signals to process * groups, etc. etc. kill() permissions semantics are tricky! */ int sys_kill(int pid,int sig) { struct task_struct **p = NR_TASKS + task; int err, retval = 0; if (!pid) while (--p > &FIRST_TASK) { if (*p && (*p)->pgrp == current->pid) if (err=send_sig(sig,*p,1)) retval = err; } else if (pid>0) while (--p > &FIRST_TASK) { if (*p && (*p)->pid == pid) if (err=send_sig(sig,*p,0)) retval = err; } else if (pid == -1) while (--p > &FIRST_TASK) if (err = send_sig(sig,*p,0)) retval = err; else while (--p > &FIRST_TASK) if (*p && (*p)->pgrp == -pid) if (err = send_sig(sig,*p,0)) retval = err; return retval; } static void tell_father(int pid) { int i; if (pid) for (i=0;i<NR_TASKS;i++) { if (!task[i]) continue; if (task[i]->pid != pid) continue; task[i]->signal |= (1<<(SIGCHLD-1)); return; } /* if we don't find any fathers, we just release ourselves */ /* This is not really OK. Must change it to make father 1 */ printk("BAD BAD - no father found\n\r"); release(current); } int do_exit(long code) { int i; free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); 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; if (task[i]->state == TASK_ZOMBIE) /* assumption task[1] is always init */ (void) send_sig(SIGCHLD, task[1], 1); } for (i=0 ; i<NR_OPEN ; i++) if (current->filp[i]) sys_close(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 */ } int sys_exit(int error_code) { return do_exit((error_code&0xff)<<8); } int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options) { int flag, code; struct task_struct ** p; verify_area(stat_addr,4); repeat: flag=0; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) { if (!*p || *p == current) continue; if ((*p)->father != current->pid) continue; if (pid>0) { if ((*p)->pid != pid) continue; } else if (!pid) { if ((*p)->pgrp != current->pgrp) continue; } else if (pid != -1) { if ((*p)->pgrp != -pid) continue; } switch ((*p)->state) { case TASK_STOPPED: if (!(options & WUNTRACED)) continue; put_fs_long(0x7f,stat_addr); return (*p)->pid; case TASK_ZOMBIE: current->cutime += (*p)->utime; current->cstime += (*p)->stime; flag = (*p)->pid; code = (*p)->exit_code; release(*p); put_fs_long(code,stat_addr); return flag; default: flag=1; continue; } } if (flag) { if (options & WNOHANG) return 0; current->state=TASK_INTERRUPTIBLE; schedule(); if (!(current->signal &= ~(1<<(SIGCHLD-1)))) goto repeat; else return -EINTR; } return -ECHILD; }
进程释放
void release(struct task_struct * p) { int i; if (!p) return; for (i=1 ; i<NR_TASKS ; i++) //遍历所有进程 if (task[i]==p) { //找到进程p task[i]=NULL; 进程指针置为空 free_page((long)p); //释放内存页 schedule(); //重新调度 return; //返回 } panic("trying to release non-existent task"); //死机了 }
向指定的任务p发送sig信号
static inline int send_sig(long sig,struct task_struct * p,int priv) //priv表示权限 { if (!p || sig<1 || sig>32) return -EINVAL; if (priv || (current->euid==p->euid) || suser()) //如果priv置位,或者是超级用户,或者有效用户id和当前进程的euid相等 p->signal |= (1<<(sig-1)); //则把p进程信号位图的sig信号置位 else return -EPERM; return 0; }
终止会话
static void kill_session(void) { struct task_struct **p = NR_TASKS + task;//任务数组的最末一个任务 while (--p > &FIRST_TASK) { //从最末一个任务开始往前循环,但是不包括任务0 if (*p && (*p)->session == current->session) //如果*p的session和当前进程的session相等 (*p)->signal |= 1<<(SIGHUP-1); //向*p发送SIGHUP(挂断)信号 } }
系统调用kill的实现。
int sys_kill(int pid,int sig) { struct task_struct **p = NR_TASKS + task; int err, retval = 0; if (!pid) while (--p > &FIRST_TASK) { //如果pid为0时 if (*p && (*p)->pgrp == current->pid) if (err=send_sig(sig,*p,1)) retval = err; } else if (pid>0) while (--p > &FIRST_TASK) { //如果pis>0时 if (*p && (*p)->pid == pid) if (err=send_sig(sig,*p,0)) retval = err; } else if (pid == -1) while (--p > &FIRST_TASK) //如果pid==-1时 if (err = send_sig(sig,*p,0)) retval = err; else while (--p > &FIRST_TASK) //如果pid<-1时 if (*p && (*p)->pgrp == -pid) if (err = send_sig(sig,*p,0)) retval = err; return retval; }
通知父进程,向pid发送SIGCHLD信号
static void tell_father(int pid) { int i; if (pid) for (i=0;i<NR_TASKS;i++) { if (!task[i]) continue; if (task[i]->pid != pid) continue; task[i]->signal |= (1<<(SIGCHLD-1)); //向task[i]进程发送SIGCHLD信号 return; } /* if we don't find any fathers, we just release ourselves */ /* This is not really OK. Must change it to make father 1 */ printk("BAD BAD - no father found\n\r"); release(current); //如果没找到父进程,则释放进程 }
int do_exit(long code) { int i; free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));//释放当前代码段和数据段所在的内存页 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) { //如果当前进程有子进程,则设置子进程的father为1 task[i]->father = 1; if (task[i]->state == TASK_ZOMBIE) //如果子进程已处于僵死状态,则向进程1发送SIGCHLD信号 /* assumption task[1] is always init */ (void) send_sig(SIGCHLD, task[1], 1); } for (i=0 ; i<NR_OPEN ; i++) //关闭进程打开的所有的文件 if (current->filp[i]) sys_close(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); //通知父进程,向当前进程的父进程发送SIGCHLD信号 schedule(); //重新调度 return (-1); /* just to suppress warnings */ }
系统调用exit的实现
int sys_exit(int error_code) //error_code是用户程序提供的退出状态信息 { return do_exit((error_code&0xff)<<8); }
等待进程号为pid的子进程结束。
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options) { int flag, code; struct task_struct ** p; verify_area(stat_addr,4); repeat: flag=0; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) { //从最后一个任务开始遍历,不包括任务0 if (!*p || *p == current) //如果是当前进程,则跳过。自己等待自己毫无意义 continue; if ((*p)->father != current->pid) //如果*p不是当前进程的子进程,则跳过。只处理当前进程的子进程 continue; if (pid>0) { //能够运行到这,说明*p是当前进程的子进程。这里根据函数传进来的子进程pid来区分4种不同情况,见下面说明 if ((*p)->pid != pid) //如果*p的pid不等于pid则跳过。言外之意就是:只等待进程id为pid的子进程 continue; } else if (!pid) { //如果pid为0 if ((*p)->pgrp != current->pgrp) //如果*p的进程组不等于当前进程的进程组则跳过,言外之意:等待同一个进程组中的任何子进程 continue; } else if (pid != -1) { //如果pid<-1 if ((*p)->pgrp != -pid) //如果*p的进程组id不等于-pid则继续,言外之意:等待进程组号为-pid的子进程 continue; } //还有一种情况pid=-1,这里没有判断条件,说明对这种情况没有限制条件。言外之意:等待任何一个子进程退出。 switch ((*p)->state) {//程序运行到这里,说明*p是我们要找的需要等待其结束的子进程 case TASK_STOPPED: //判断*p的状态,如果处于TASK_STOPPED状态, if (!(options & WUNTRACED)) continue; put_fs_long(0x7f,stat_addr);//把0x7f放入stat_addr return (*p)->pid; //返回*p的pid case TASK_ZOMBIE: //如果处于僵死状态 current->cutime += (*p)->utime; current->cstime += (*p)->stime; flag = (*p)->pid; //取出pid和进程码 code = (*p)->exit_code; release(*p);//释放这个子进程, put_fs_long(code,stat_addr); //进程码放入stat_addr中 return flag; //返回*p的进程号 default: //其他状态 flag=1; //flag置1,表示找到了符合要求的子进程,但是它处于运行态 continue; } } if (flag) { //如果flag被置位了,说明符合要求的子进程处于运行态,并没有退出 if (options & WNOHANG) //如果options为WNOHANG状态 return 0; //直接返回 current->state=TASK_INTERRUPTIBLE; //当前进程的状态置为可中断状态 schedule(); //重新执行schedule() if (!(current->signal &= ~(1<<(SIGCHLD-1)))) //如果当前进程没有收到SIGCHLD信号,则跳转到repeat重复执行 goto repeat; // else return -EINTR;//如果当前进程收到SIGCHLD信号,则返回错误的中断调用 } return -ECHILD; }
注:上面函数传递的子进程pid 4种条件的说明
pid>0时,只等待进程ID等于pid的子进程,(即指定wait函数等待的到底是具体哪个子进程)
不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
注:我们要充分理解前面章节中讲到的schedule()函数。这个函数是进程调度的核心