linux内核之sys_wait4

sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)

     DECLARE_WAITQUEUE(wait, current);
     add_wait_queue(&current->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(&current->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(&current->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_waitexit以后,他不但会把孤儿托付给init还会向init发送信号,接受到信号的init从沉睡中醒来,完成检测所有子进程,然后回收资源的任务。我猜的,我想这些应该在读完系统初始化后可以揭晓吧。

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值