目录
1.为什么进程等待
2.进程等待函数
3.子进程status
4.非阻塞等待
1. 为什么进程等待
1.子进程退出,父进程不管,就会形成僵尸进程,造成内存泄露
2.僵尸进程kill也无法杀死
3.父进程给子进程的任务完成情况需要知道,结果是否正常退出
4.父进程通过进程等待方式,回收子进程资源,获取退出信息
2. 进程等待函数
运行下面代码监测,会发现僵尸进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if (id < 0)
{
perror("fork");
exit(1); //标识进程完毕,结果不正确
}
else if (id == 0)
{
//子进程
int cnt = 5;
while (cnt--)
{
printf("我是子进程: %d, %d\n", getpid(), getppid());
sleep(1);
}
exit(0);
}
else
{
//父进程
while (1)
{
printf("我是父进程: %d %d\n",getpid(), getppid() );
sleep(1);
}
}
return 0;
}
父进程需要通过等待的方式来回收子进程的资源和结果,避免出现僵尸进程
查看等待函数的说明
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
将上面代码加上等待子进程,就会发现僵尸进程被回收了
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:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
第一个参数pid>0时等待指定进程,-1等待任一子进程
option默认为0,表示阻塞等待
status输出型参数,会输出子进程的退出信息
- 如果子进程已经退出,调用wait/waitpid时,会立即返回,并且释放资源,获得退出信息
- 如果子进程正在运行,可能会阻塞,子进程退出后再继续执行下面的程序
- 不存在子进程,立即出错返回
3. 子进程status
3.1 解析
这个输出型参数,由操作系统填充,传递为NULL,表示不关心退出信息。否则要提取需要的信息
3.2 手动获取
status的次8为退出状态,最后8位是终止信号
验证一下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if (id < 0)
{
printf("创建失败\n");
exit(0);
}
else if (id == 0)
{
//子进程
int cnt = 5;
while (cnt--)
{
printf("我是子进程,%d %d\n", getpid(),getppid());
sleep(0.5);
}
exit(105);
}
else
{
//父进程
int status = 0;
pid_t ret = waitpid(id, &status, 0);
printf("等待成功,返回值: %d ,退出信号: %d, 退出码: %d\n", ret, status & 0x7f, (status >> 8) & 0xff);
}
return 0;
}
通过提取最低7位可以得到子进程的退出码,表示程序结果正常。次8位是子进程的退出信号
退出信号是0,表示正常跑完。如果是明显有错误的会是什么信号,用一个野指针程序来观察
用kill -l 查询11信号
段错误信号,说明程序运行不正常。系统杀死程序也是通过发送信号的方式,9号信号
那么可以不可以用全局变量拿到子进程的退出结果。不可以,进程具有独立性,子进程会发生写时拷贝,无法拿到
进程的退出结果信息保存在task_struct结构中,拿到进程信息本质就是读取PCB信息里的内容,因为wait的函数本身就是系统调用,有这个权利
3.3 宏获取
系统提供了获取退出状态和退出码的宏,可以直接使用
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
int main()
{
pid_t id = fork();
if (id < 0)
{
printf("创建失败\n");
exit(0);
}
else if (id == 0)
{
//子进程
int cnt = 5;
while (cnt--)
{
printf("我是子进程,%d %d\n", getpid(),getppid());
sleep(0.5);
}
exit(105);
}
else
{
//父进程
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
if (WIFEXITED(status))
{
printf("正常退出,退出码: %d\n",WEXITSTATUS(status));
}else
{
printf("异常退出,退出码: %d\n",WEXITSTATUS(status));
}
}
}
return 0;
}
4. 非阻塞等待
前面的父进程等待时,不能做其他事情,只能子进程返回了才继续执行。这就是阻塞等待,系统上处于阻塞队列中,没有被cpu调度。父进程怎么在等待的同时执行其他的任务,这就是非阻塞等待
waitpid的第三个参数默认为0,表示阻塞等待,如果为1,就是非阻塞等待。不过系统定义了一个宏,WNOHANG,用这个表示非阻塞等待
阻塞和非阻塞:
父进程通过调用waitpid等待,如果子进程没有退出,waitpid这个系统调用,立马返回,这样就可以非阻塞
上面的是伪代码,非阻塞时传入的参数在系统调用中会判断,非阻塞就会直接返回,阻塞就会将父进程挂起,等子进程退出再唤起,开始返回
将父进程的代码修改一下,不断获取子进程的等待信息,输入代码测试:
while (1)
{
//父进程
int status = 0;
pid_t ret = waitpid(id, &status, WNOHANG);
if (ret > 0)
{
// if (WIFEXITED(status))
// {
// printf("正常退出,退出码: %d\n",WEXITSTATUS(status));
// }else
// {
// printf("异常退出,退出码: %d\n",WEXITSTATUS(status));
// }
printf("等待成功,退出\n");
return 0;
}
else if (ret == 0)
{
printf("等待成功,子进程未退出\n");
}
else
{
printf("等待失败\n");
}
sleep(1);
}
}
非阻塞等待的情况下,可以让父进程同时执行一些其他事情,用一个函数指针数组保存两个函数,在等待的同时调用vector保存的这几个函数
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <unistd.h>
typedef void (*handler_t)(); //函数指针类型
std::vector<handler_t> handlers; //函数指针数组
void fun_one()
{
printf("这是一个临时任务1\n");
}
void fun_two()
{
printf("这是一个临时任务2\n");
}
// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!
void Load()
{
handlers.push_back(fun_one);
handlers.push_back(fun_two);
}
int main()
{
pid_t id = fork();
if(id == 0)
{
// 子进程
int cnt = 5;
while(cnt)
{
printf("我是子进程: %d\n", cnt--);
sleep(1);
}
exit(11); // 11 仅仅用来测试
}
else
{
int quit = 0;
while(!quit)
{
int status = 0;
pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
if(res > 0)
{
//等待成功 && 子进程退出
printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));
quit = 1;
}
else if( res == 0 )
{
//等待成功 && 但子进程并未退出
printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");
if(handlers.empty()) Load();
std:: vector<handler_t>::iterator it = handlers.begin();
while (it != handlers.end())
{
(*it)();
it++;
}
}
else
{
//等待失败
printf("wait失败!\n");
quit = 1;
}
sleep(1);
}
}
}