wait及waitpid函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gc348342215/article/details/70225139
转载前注明出处 欢迎转载分享
一个进程调用了exit之后,该进程并非马上就消失掉,而是留下一个称为 僵尸进程(Zombie)的数据结构。在Linux进程的5种状态中, 僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。从这点来看,僵尸进程对系统毫无作用。
当一个进程已退出,但其父进程还没有调用系统调用wait(稍后介绍)对其进行收集之前的这段时间里,它会一直保持僵尸状态,利用这个特点,我们来写一个简单的小程序:
 C Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#include < sys/types.h >
#include < unistd.h >
int main()
{
    pid_t pid;
    pid = fork();
    
if(pid < 0)
        printf(
"error occurred!\n");
    
else if(pid == 0)
        exit(
0);
    
else
    {
        sleep(
60);
        wait(
NULL);
    }
    
return 0;
}
sleep的作用是让进程休眠制定秒数,在这60秒内子进程已经退出,而父进程还忙着睡觉,不能对它进行收集,这样,我们就能保持子进程60秒的僵尸状态。

1
2
./zombie & //后台运行
ps -ax //查看进程状态
显示如下:  
1
1578 pts/0 Z 0:00 [zombie ]
其中 Z即是僵尸进程的状态,它表示1578号进程现在就是一个僵尸进程。

wait函数:
#include < sys/types.h >
#include < wait.h >
int wait(int *status);

函数功能: 父进程一旦调用wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

当父进程忘了用wait()函数等待已经终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程。
wait()与fork()配套出现,如果 在使用fork()之前调用wait(),wait()返回值为-1,正常情况下wait()返回值为子进程的PID。
如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID=1)继承,当子进程终止时,init进程捕获这个状态。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意, 只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,如下:
1
pid = wait(NULL);
如果成功,wait会返回收集到的子进程PID,如果调用进程没有子进程,调用就会失败,此时wait返回-1。
看如下代码:
 C Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include < sys/types.h >
#include < sys/wait.h >
#include < unistd.h >
#include < stdlib.h >
int main()
{
    pid_t pid, tmp;
    pid = fork();
    
if(pid < 0)         
        printf(
"error ocurred!\n");
    
else if(pid == 0)       
    {
        printf(
"This is child process with pid of %d\n", getpid());
        sleep(
10);  
    }
    
else            
    {
        tmp = wait(
NULL);   
        printf(
"I catched a child process with pid of %d\n", tmp);
    }
    exit(
0);
}
父进程会在wait阻塞,直到子进程睡眠10秒正常退出后,父进程才能将其捕捉到,其实这里不管设定子进程睡眠的时间有多长,父进程都会一直等待下去。

参数status
如果参数status值不为NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套 专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
1.WIFEXITED(status)这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
注意:虽然WIFEXITED中也有一个status,但这里的参数status并不同于wait唯一的参数(指向整数的指针status),而是那个指针所指向的整数,切记不要搞混了。
2.WEXITSTATUS(status)当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。
下面通过下面例子实战一下我们刚学到的内容:
 C Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#include < sys/types.h >
#include < sys/wait.h >
#include < unistd.h >
int main(){
    
int status;
    pid_t pid, tmp;
    pid = fork();
    
if(pid < 0)                         //如果出错
        printf("error ocurred!\n");
    
else if(pid == 0){                   //子进程
        printf("This is child process with pid of %d\n", getpid());
        exit(
3);
    }
    
else{
        tmp = wait(&status);
        
if(WIFEXITED(status)){
            printf(
"the child process %d exit normally\n", tmp);
            printf(
"the return code is %d\n", WEXITSTATUS(status));
    }
else
        printf(
"the child process %d exit abnormally\n", tmp);
}
运行结果:
This is child process with pid of 1538
the child process 1538 exit normally
the return code is 3

父进程准确捕捉到了子进程的返回值3,并把它打印了出来。当然,处理进程退出状态的宏并不止这两个,但它们当中的绝大部分在平时的编程中很少用到。
------------------------------------------------------------------------------------------------------------------
waitpid函数
#include < sys/types.h >
#include < sys/wait.h >
pid_t waitpid(pid_t pid, int *status, int options);
从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:
pid
从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
  • pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
  • pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
  • pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
  • pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options
options提供了一些额外的选项来控制waitpid,目前在Linux中只支持 WNOHANGWUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
1
ret = waitpid(-1NULL, WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:
1
ret = waitpid(-1NULL0);
如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。而WUNTRACED参数,如果有处于停止状态的进程将导致调用返回。

可以把wait看作waitpid实现的特例,wait实际上就是经过包装的waitpid,我的github上的内核源码 https://github.com/chensguo8099/linux-/blob/master/linux-2.6.11.12/include/asm-frv/unistd.h的第459-462行有以下程序段:  
1
2
3
4
static inline pid_t wait(int *wait_stat)
{
    
return waitpid(-1, wait_stat, 0);
}

返回值和错误
waitpid的返回值比wait稍微复杂一些,一共有三种情况:
  • 正常返回的时候,waitpid返回收集到的子进程的进程ID
  • 如果设置了选项WNOHANG(非阻塞调用),而调用中waitpid发现没有已退出的子进程可收集,则返回0
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
 C Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include < sys/types.h >
#include < sys/wait.h >
#include < unistd.h >
int main()
{
    pid_t pid, tmp;
    pid = fork();
    
if(pid < 0)
        printf(
"error occured on forking\n");
    
else if(pid == 0)
    {
        sleep(
10);
        exit(
0);
    }
    
do
    {
        tmp = waitpid(pid, 
NULL, );
        
if(pid == 0)
        {
            printf(
"no child exited\n");
            sleep(
1);
        }
    }
    
while(pid == 0);
    
if(pid == tmp)
        printf(
"successfully get child %\n", tmp);
    
else
        printf(
"some error occured\n");
    
return 0;
}
编译运行结果:
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
successfully get child 1526

父进程经过10次失败的尝试之后,终于收集到了退出的子进程。

参考资料:

猜你喜欢

转载自blog.csdn.net/gc348342215/article/details/70225139
今日推荐