Linux(9)——进程(控制篇——下)
三、进程等待
1)进程等待的必要性
- 之前提过子进程退出,父进程如果不读取子进程的退出信息,就可能造成“僵尸进程”的问题,从而造成内存泄漏的问题。
- 再者,一旦子进程进入了僵尸状态,那就连kill -9都杀不亖他,因为没有谁能够杀亖一个死去的进程。
- 最后,父进程创建子进程是要获取子进程的完成任务的情况的。
- 父进程需要通过等待的方式来回收子进程的资源,获取子进程的退出信息。
2)获取子进程的status
下面进程等待使用的两个方法wait方法和waitpid方法都有一个status参数,这是一个输出型参数(输出型参数是函数中用于返回结果或修改调用者变量的参数,通常通过引用或指针实现。如void func(int *output)。),由操作系统进行填写。
如果向status中传递的是NULL,那就表示用户不关心子进程的退出状态。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
我们从图中可见,status的低16比特位中,高8位表示进程的退出状态,即退出码。当进程被信息杀亖时,则低7位表示终止信息,第8位时core dump标志。
我们可以通一系列的位操作来得出进程的退出码和退出信号。
exitcCode = (status >> 8) & 0xFF; //退出码exitSignal = status & 0x7F;
对于这两个操作,系统提供了两宏来获取退出码以及退出信号。分别是:
- WIFEXITED(status):用于查看是否是正常退出,本质是检查是否收到信号。
- WEXITSTATUS(status):用于获取进程的退出码。
exitNormal = WIFEXITED(status); //是否正常退出exitCode = WEXITSTATUS(status); //获取退出码
敲黑板:
当一个进程是非正常退出的时候,那么该进程的退出码将毫无意义。
3)进程的等待方法
wait方法
函数类型:pid_t wait(int* status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
作用:等待任意子进程
创建子进程后,父进程使用wait方法等待子进程,直到子进程的退出信息被读取,我们可以写个代码验证一下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h> int main()
{ pid_t id = fork(); if(id == 0){ //子进程 int count = 10; while(count--) { printf("我是子进程,PID:%d, PPID:%d\n", getpid(), getppid()); sleep(1); } exit(0); } //父进程 int status = 0; pid_t ret = wait(&status); if(ret > 0){ printf("等待成功...\n"); if(WIFEXITED(status)){ printf("退出码:%d\n", WEXITSTATUS(status)); } } sleep(3); return 0;
}
然后我们可以在开一个会话用来监控进程的状态:
while :; do ps axj | head -1 && ps axj | grep test | grep -v grep;echo "============================================================";sleep 1;done
在下面这图中我们可以看到,当子进程退出,父进程读取到了子进程的退出信息时,子进程就不会变成僵尸状态了。
waitpid方法
函数原型:pid_t waitpid(pid_t pid, int *status, int options);
返回值:
- 当正常返回的时候waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG(option),而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
- pid:当pid=-1,等待任意一个子进程,与wait等效。当pid>0.等待其进程ID与pid相等的子进程。
- status:输出型参数,用来获取子进程的退出状态,不关心可以设置成NULL。
- options:默认为0,表示阻塞等待;当设置为WNOHANG时,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
返回值:等待任意子进程(可以指定)退出
我们可以写个代码来验证一下,创建子进程后,父进程可以使用waitpid函数一直等待子进程,直到子进程退出后读取子进程的退出信息。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h> int main()
{ pid_t id = fork(); if(id == 0){ //子进程 int count = 10; while(count--) { printf("我是子进程,PID:%d, PPID:%d\n", getpid(), getppid()); sleep(1); } exit(0); } //父进程 int status = 0; //pid_t ret = wait(&status); pid_t ret = waitpid(id, &status, 0); if(ret >= 0){ printf("等待成功...\n"); if(WIFEXITED(status)){ printf("退出码:%d\n", WEXITSTATUS(status)); }else{ printf("被信号杀?:%d\n",status & 0x7F); } } sleep(3); return 0;
}
在父进程运行过程中,我们可以使用kill -9命令来将子进程杀亖,这个时候父进程也能成功等待子进程。
敲黑板:
被信号杀亖的进程的退出码是没有意义的。
多进程创建以及等待的代码模型
上面演示的都是父进程的创建以及等待一个子进程,那么接下来我们可以同时创建多个子进程,然后让父进程进程依次等待子进程退出。
下面我们可以同时创建10个子进程,同时将子进程的pid放到一个id数组中,并将这10个子进程的退出时的退出码设置为该子进程pid对应数组中的下标,之后父进程使用waitpid等待这10个子进程。