1.进程等待的必要性
之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。
另外,一旦一个进程变成僵尸状态,那就刀枪不入,“杀人不咋眼”的kill -9也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道,如,子进程运行完成,结果对还是不对或者是否正常退出。
父进程通过进程等待的方式,回收子进程的资源,获取子进程退出信息。
2.进程等待的方法
wait方法
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
返回值:成功,返回被等待进程pid,失败返回-1;
参数:输出型参数,获取子进程退出状态,
waitpid方法
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时,errno会被设置成相应的值以指示错误所在;
参数:pid:
pid=-1,等待任一个子进程,与wait等效
pid>0,等待其进程ID与pid相等的子进程
status:
WIFEXITED(status);若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
WEXITSTATUS(status);若WIFEXITED非零,提取子进程的退出码。(查看进程的退出码)
options:
默认为0,表示阻塞等待
WNOHANG(非阻塞式等待):若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则放回该子进程的ID。
waitpid(-1,NULL,0) = wait(NULL);
35 pid_t ret = waitpid(id,&status,0);//非阻塞式等待
36 if(ret>0)
37 {
38 // printf("等待子进程成功,ret:%d,子进程收到的信号编号:%d,status:%d\n",ret,status&0x7F,(status>>8)&0xFF);
39 if(WIFEXITED(status))//值:子进程正常退出为1,异常退出为0
40 {
41 //子进程是正常退出的
42 printf("子进程执行完毕,子进程的退出码:%d\n",WEXITSTATUS(status));
43 }
44 else
45 {
46 printf("子进程异常退出:%d\n",WIFEXITED(status));
47 }
48 }
2.1具体代码(重要)
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 int main()
7 {
8 pid_t id = fork();
9 if(id<0)
10 {
11 perror("fork");
12 exit(1);//标识进程运行完毕,结果不正确
13 }
14 else if(id == 0)
15 {
16 //子进程
17 int cnt = 5;
18 while(cnt)
19 {
20 printf("cnt:%d,我是子进程,pid:%d,ppid:%d\n",cnt,getpid(),getppid());
21 cnt--;
22 }
23 exit(54);
24 }
25 else
26 {
27 //父进程
28 printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
29 // pid_t ret = wait(NULL);//阻塞式等待
30 int status = 0;
31 pid_t ret = waitpid(id,&status,0);//阻塞式等待 等待5s
//pid_t ret = waitpid(id,&status,WNOHANG);//非阻塞式等待
32 if(ret>0)
33 {
34 printf("等待子进程成功,ret:%d,status:%d\n",ret,status);
35 }
36 //基本没有意义的,只是为了对比演示
37 while(1)
38 {
39 printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
40 sleep(1);
41 }
42 }
43 return 0;
44 }
运行结果:
[jyf@VM-12-14-centos 进程]$ ./mytest10
我是父进程,pid:6469,ppid:31362
cnt:5,我是子进程,pid:6470,ppid:6469
cnt:4,我是子进程,pid:6470,ppid:6469
cnt:3,我是子进程,pid:6470,ppid:6469
cnt:2,我是子进程,pid:6470,ppid:6469
cnt:1,我是子进程,pid:6470,ppid:6469
// printf("等待子进程成功,ret:%d,status:%d\n",ret,status);
等待子进程成功,ret:6470,status:13824
// printf("等待子进程成功,ret:%d,status:%d\n",ret,(status>>8)&0xFF);
//等待子进程成功,ret:10928,status:54
我是父进程,pid:6469,ppid:31362
我是父进程,pid:6469,ppid:31362
我是父进程,pid:6469,ppid:31362
我是父进程,pid:6469,ppid:31362
^C
一般都是在内核中阻塞,等待被唤醒,可见伴随着切换,scanf(),cin这些必定封装了系统调用。
通过waitpid参数options的值来控制阻塞式等待还是非阻塞式等待,options的值为0表示阻塞式等待,WNOHANG表示非阻塞式等待;
阻塞式等待时,父进程要等子进程结束后,才开始运行自己的程序;而非阻塞式等待父进程和子进程都在运行,仅仅判断当时那一次子进程的状态就返回了。
[jyf@VM-12-14-centos 进程]$ grep -ER 'WNOHANG' /usr/include/ //查找某个语句在目标文件夹下的内容
/usr/include/sys/wait.h: If the WNOHANG bit is set in OPTIONS, and that child
/usr/include/sys/wait.h: If the WNOHANG bit is set in OPTIONS, and that child
/usr/include/bits/waitflags.h:#define WNOHANG 1 /* Don't block waiting. */
/usr/include/valgrind/vki/vki-linux.h:#define VKI_WNOHANG 0x00000001
/usr/include/linux/wait.h:#define WNOHANG 0x0000000
可见WNOHANG是一个宏,默认为1;(Wait No Hang)夯住了,不就是这个进程没有被CPU调度吗。要么是在阻塞队列里,要么是等待被调度。
如果子进程已经返回,调用wait/waitpid后,wait/waitpid会立即返回,并且释放资源,获得子进程的退出信息
如果在任意时刻调用wait/waitpid,子进程还没退出,父进程可能阻塞
如果不存在该子进程,立即出错返回
1.父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid函数呢?
进程具有独立性,那么数据会发送写时拷贝,父进程无法拿到,况且,信号呢?
2.既然进程具有独立性,进程退出码不也是子进程的的数据吗?父进程凭什么拿到?wait/waitpid究竟干了什么?
僵尸进程,至少要保留进程的PCB结构体,task_struct里面保留了任何进程退出时的结果信息;wait/waitpid究竟干了什么?本质其实就是读取子进程的task_struct结构体的信息。
3.wait/waitpid是系统调用,归系统管,task_struct是内核数据结构,所以有权限进行读取。
3.status的构成
pid_t waitpid(pid_t pid,int* status,int options);
status并不是按照整数来整体使用的。
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整型来看待,可以当作位图来看待,具体细节如下图,status是32个比特位的,我们现在只学习其低16位。
程序异常,不光光是内部代码有问题,也有可能是外力直接杀掉(子进程代码跑完了吗?不确定)
.cpp .cc .cxx都是C++文件的后缀名