sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(¤t->wait_chldexit,&wait);
repeat:
flag = 0;
current->state = TASK_INTERRUPTIBLE;
tsk = current;
do {
struct task_struct *p;
for (p = tsk->p_cptr ; p ; p = p->p_osptr) {
if (pid>0) { //pid>0,说明寻找进程号等于pid的子进程
if (p->pid != pid)
continue;
} else if (!pid) { //pid=0,说明寻找与当前进程同一个组的子进程
if (p->pgrp != current->pgrp)
continue;
} else if (pid != -1) { //pid<-1,说明寻找进程组编号等于-pid的子进程
if (p->pgrp != -pid)
continue;
} //pid=-1,说明寻找任何一个子进程
flag = 1;
switch (p->state) {
case TASK_STOPPED: ... goto end_wait4;
case TASK_ZOMBIE: ...release_task(p);goto end_wait4; //子进程已结束,释放其描述符
default: continue;
}
if (option & __WNOTHREAD) break;
tsk = next_thread(tsk);
}while(tsk != current) //这是我不太习惯的地方。它的解释是,当前任务可能是一个线程,而等待的进程是由同一个进程克隆出来的另一个线程的子进程
if (flag) {
...
schedule(); //子进程没有结束,继续睡眠,下次仍然跳到repeat处扫描子进程列表
goto repeat;
}
end_wait4:
current->state = TASK_RUNNING;
remove_wait_queue(¤t->wait_chldexit,&wait);
return retval;
}
首先实现的功能是:根据给定的参数去守候子进程,收集子进程使用资源的记录信息,比回收子进程没有回收的资源,系统堆栈空间。
流程:某个进程调用了wait4函数,假设调用如:wait(要等待的子进程的pid,WUNTRACED,status,reus)。
wait第一个动作就是current->state = TASK_INTERRUPTIBLE;将当前进程的状态设置成TASK_INTERRUPTIBLE表示挂起,休眠,阻碍等等。接下来是一大堆的检查判断,判断通过的话就执行current->state = TASK_RUNNING;,将进程状态设置成可以运行,然后执行remove_wait_queue(¤t->wait_chldexit,&wait);再返回,至于是什么判断这里先不管这些,最后执行schedule();意思就是进程从新调度。下面我们看看这么做的意义:
首先我们把进程状态改成了阻塞,如果判断条件没通过,意思就是说在调用schedule()后当前进程就被阻塞不会运行了。那什么时候会唤醒呢,前面我们讨论过exit函数,一个进程在exit时候就会唤醒他的父进程,这里就是他该唤醒的地方,唤醒后,父进程会循环继续回到current->state = TASK_INTERRUPTIBLE;继续运行,如此循环,那么退出循环就是以下几个条件之一被满足:
a.所等待的子进程状态变成了TASK_STOPPED或者TASK_ZOMBIE;
b.所等待的子进程存在但不是上面2种状态,而调用参数为WNOHANG,或者当前进程收到了其它的信号;
c.所等待的进程PID不存在,或者不是当前进程的子进程。
这几种条件判断正好适应以下几种情况:
1.等待子进程被跟踪,属于TASK_STOPPED,
2.等待子进程僵死,属于TASK_ZOMBIE,
3.参数为WNOHANG,正好符合我们wait4调用参数WNOHANG的意义,就是不等待子进程直接执行完毕。
4.进程收到其它信号,属于其它情况。
5.等待进程的PID不存在或者不是当前进程的子进程,代码写错了。
我想我们的代码wait(要等待的子进程的pid,WUNTRACED,status,reus)应该是在第2种情况,此时当子进程exit后,便会唤醒父进程然后,父进程将会回到current->state = TASK_INTERRUPTIBLE,会再次检查条件,发现第a个条件满足到达代码current->state = TASK_RUNNING;处(在到达这里之前还有其它动作,读者可以自己参看代码)。接下来就是回收资源,拷贝子进程的资源使用信息。
下面是个小实验,我们知道当我们新建一个进程后,如果我在父进程里面没有调用wait4是不是表示这个子进程系统堆栈空间不会被回收了呢?我们看下面的程序
[root@liumengli wait]# cat print.c
#include "stdio.h"
int main() {
int i;
//for (i = 0; i < 50; i++)
while(1)
printf("Hello, This is print!!!/n");
exit(1);
}
[root@liumengli wait]# cat test_wait.c
#include "stdio.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
int main() {
int child;
int * status;
struct rusage * rus;
char * argu[] = {"/program/wait/print", NULL};
if(!(child = vfork())) {
execve("/program/wait/print", argu, NULL);
} else {
//wait4(child,status,WUNTRACED,rus);
//printf("The son's pid:%d, status:%d, The memory is:%d/n", child, WIFEXITED(status),rus->ru_nswap);
while(1)
printf("This is test_wait/n");
}
exit(0);
}
我将print.c编译完毕,名字为print, test_wait.c编译成了test_wait,我在虚拟机里面启动中断,然后执行命令./test_wait,这个大家大概都知道了,就是2个死循环在一直打印,我用SSH链接我的虚拟机,执行PS命令看下系统进程的信息,如下:
0 S 0 2901 2898 0 76 0 - 1438 wait pts/2 00:00:00 bash
0 R 0 6241 2901 3 77 0 - 353 - pts/2 00:00:00 test_wait
0 R 0 6242 6241 0 79 0 - 353 - pts/2 00:00:00 print
4 R 0 6245 2854 0 76 0 - 1379 - pts/1 00:00:00 ps
这里我们看到,明显这2个进程都在运行,接下来我执行kill 6242杀死print进程
[root@liumengli wait]# kill 6242
[root@liumengli wait]# ps -le
下面是杀死print进程后的进程信息:
0 S 0 2901 2898 0 76 0 - 1438 wait pts/2 00:00:00 bash
0 S 0 6241 2901 5 75 0 - 353 write_ pts/2 00:00:04 test_wait
0 Z 0 6242 6241 3 75 0 - 0 exit pts/2 00:00:03 print <defunct>
4 R 0 6257 2854 0 76 0 - 1379 - pts/1 00:00:00 ps
这里我们看到print依然存在,而且被ps检索到,这就是僵死状态,我们没有执行wait所以到目前它的系统堆栈没有被回收,如果我们杀死test_wait呢
[root@liumengli wait]# kill 6241
0 S 0 2901 2898 0 76 0 - 1438 - pts/2 00:00:00 bash
4 R 0 6274 2854 0 76 0 - 1379 - pts/1 00:00:00 ps
这下我们看到2个进程都没有了,那是谁回收了print的资源。我想应该是init进程,我们在exit提过,所有无主的孤儿进程都会被送进托儿所,虽然是僵死下的print,在其死亡后尸体也是被交给init来处理的,我们查看了wait4代码就清楚了,我猜init的 wait等待进程的ID应该是小于-1,这样他就等待所有的子进程,当test_wait在exit以后,他不但会把孤儿托付给init还会向init发送信号,接受到信号的init从沉睡中醒来,完成检测所有子进程,然后回收资源的任务。我猜的,我想这些应该在读完系统初始化后可以揭晓吧。