卸载hello程序
现在再来看看我们的Hello World程序:
#include <stdio.h>
int main ()
{
printf ("hello world\n");
return 0;
}
在进程运行结束后,我们会显示的调用exit()或者return退出正在运行的进程,如果调用return的话,编译器会自己加上exit().此时,保存子进程的一
部份信息是很有必要的,因为父进程可以读取这些消息而取得子进程的退出状态.如果子进程退出.但父进程没有用wait(),这就成为了我们常说的僵
尸进程,exit()系统调用在内核中的相应接口为sys_exit(),我们来跟踪一下,看下内核是如何处理这个过程的.
fastcall NORET_TYPE void do_exit(long code)
{
...
//不可以在中断上下文或者是0号进程使用该函数
if (unlikely(in_interrupt()))
panic("Aiee, killing interrupt handler!");
if (unlikely(!tsk->pid))
panic("Attempted to kill the idle task!");
...
//设置PF_EXITING:表示进程正在退出
tsk->flags |= PF_EXITING;
...
//退出状态码
tsk->exit_code = code;
taskstats_exit(tsk, group_dead);
//退出进程所占用的空间
exit_mm(tsk);
if (group_dead)
acct_process();
//从进程的信号从IPC队列中删除
exit_sem(tsk);
//
__exit_files(tsk);
//关闭打开的文件
__exit_fs(tsk);
check_stack_usage();
exit_thread();
cgroup_exit(tsk, 1);
exit_keys(tsk);
//进程组全部退出且当前进程是进程组的组长
if (group_dead && tsk->signal->leader)
//脱离当前的tty 并向进程显示终端的组发送SIGHUP 和SIGCONT
disassociate_ctty(1);
//减少模块的引用计数
module_put(task_thread_info(tsk)->exec_domain->module);
if (tsk->binfmt)
module_put(tsk->binfmt->module);
proc_exit_connector(tsk);
//更新进程的亲属关系.并给父进程发送相应的信号
exit_notify(tsk);
...
//进程退出已经完成了,设置PF_EXITPIDONE
tsk->flags |= PF_EXITPIDONE;
...
//设置进程的状态为TASK_DEAD
tsk->state = TASK_DEAD;
//调度另一个进程运行
schedule();
...
}
显然上面的函数首先做的是检查进程退出条件是否满足,然后结束和进程相关的资源,最后标记当前进程为TASK_DEAD,注意此时还没有将
task_struct删除。那么进程描述符什么时候被删除?
由于在linux中允许进程查询其父进程的状态,或者是任何子进程的状态,所以在某个进程执行完毕之后,不能马上将task_struct删除。只有当
父进程发出了wait系统调用之后才允许将task_struct结构从内存中删除。但是如果子进程在父进程之前结束那该怎么办?在linux中,将这样的子进程的父进程自动的设置成init进程。这时,在init进程中使用wait系统调用来结束这样的子进程。
下面参见《linux内核编程》
当子进程终止时,父进程收到内核发出的信号“SIGCHLD”,父进程可以在任何的事件地点来调用wait系统调用。可以采用:
pid_t wait(int* status)
pid_t waitpid (pid_t pid, int* status, int options)
pid_t wait3(int* status, int options, struct rusage* rusage)
pid_t wait4(pid_t pid, int* status, int options, struct rusage* rusage)
这些函数又调用sys_wait4。
asmlinkage int sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru) {
...
/* 参数有效? */
if (options & ~(WNOHANG|WUNTRACED|__WCLONE)) return -EINVAL; /* 将进程加入到等待进程中 */ add_wait_queue(¤t->wait_chldexit,&wait); repeat: flag=0; /* 首先初始化flag为0,在下面如果查找到参数pid和某个子进程的pid相同,更改pid */
/* p_cptr是进程的子进程,p_osptr是进程的子进程兄弟进程 */ for (p = current->p_cptr ; p ; p = p->p_osptr) { ... flag = 1; switch (p->state) { /* 找到这样的进程pid等于函数参数pid */ case TASK_STOPPED: /* TASK_STOPPED进程进入睡眠状态,此时进程是可以被唤醒的 */ if (!p->exit_code) continue; if (!(options & WUNTRACED) && !(p->flags & PF_PTRACED)) continue; if (ru != NULL) getrusage(p, RUSAGE_BOTH, ru); if (stat_addr) put_user((p->exit_code << 8) | 0x7f, stat_addr); p->exit_code = 0; retval = p->pid; goto end_wait4; case TASK_ZOMBIE: /* 进程处于TASK_ZOMBIE,此时需要收回task_struct */ current->cutime += p->utime + p->cutime; current->cstime += p->stime + p->cstime; if (ru != NULL) getrusage(p, RUSAGE_BOTH, ru); if (stat_addr) put_user(p->exit_code, stat_addr); retval = p->pid; if (p->p_opptr != p->p_pptr) { REMOVE_LINKS(p); p->p_pptr = p->p_opptr; SET_LINKS(p); notify_parent(p); } else release(p); /* 释放进程的内存中的结构 */ ... } } ... end_wait4: /* 结束函数sys_wait4 */ remove_wait_queue(¤t->wait_chldexit,&wait); return retval; }
显然在sys_wait4函数中实现了将进程的内存结构消除。
到现在为止,hello world程序最终完成了一个简单的printf ("hello world\\n");的使命,挂掉了。那么linux中程序(或者说是进程)的完整生命周期就是这样的。
终于hello world完了。。。