目录
编辑编辑
小实验 子进程的退出码
第二个实验 手动退出
小结
WIFEXITED WEXITSTAUS
非阻塞轮巡/非阻塞等待
非阻塞等待案例
等待失败的情况
非阻塞等待实验
在2号手册里查wait()。wait()等待任意一个子进程的状态。
wait()等待成功会返回该子进程的id,返回失败会返回-1:
子进程执行work(),父进程wait子进程。
子进程跑完5秒之后就exit(0)退出。
子进程跑的时候是run状态
当子进程跑完,父进程接收到子进程的状态,即僵尸状态,然后父进程会跑10秒,10秒过后父进程也结束进程。
代码如下:
#include
#include
#include
#include
#include
void work()
{
int cnt=5;
while(cnt--)
{
printf("子进程开始启动:pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
sleep(1);
}
}
int main()
{
sleep(10);
pid_t id=fork();
if(id==0) //子进程
{
work();
exit(0);
}
else //父进程
{
pid_t ret=wait(NULL);
if(ret==id)
{
printf("wait success,pid:%d\n",getpid());
}
}
sleep(10);
return 0;
}
~
结论:子进程在跑,父进程就要进行阻塞等待。等子进程跑完,父进程才wait sucess,总结如下:
waitpid()
用法:
三个参数,重点讲一下 stauts
status是输出型参数,所谓输出型参数需要用户自己定义变量,然后传参,操作系统接收这个参数经过操作之后再返回给用户级变量
如下,定义一个int型变量,变量名为status,取地址传参,然后等如果父进程等待子进程之后就把status的值打印出来看看:
结果如下:
如果进程正常退出,就返回低八位的0,高八位的退出状态。
如果异常退出,就返回低七位的终止信号,为什么不是低八位呢? 低八位的第一位是core dump标志,所以只返回低七位。
如下,我们把子进程的退出码改为exit(10),再让父进程去等待:
运行结果:status为2560。
因为退出码10的二进制为1010,又因为异常所以在高八位,如下:
exit sign为stautus的信号退出码,exit code为status的进程退出码:
printf("wait success,pid:%d,status返回码:%d,
exit sign:%d ,exit code:%d\n",getpid(),ret,
status&0x7F,(status>>8)&0xFF);
为什么exit sign是 status&0x7F?
因为信号退出码在低七位 7是3个1,F是4个1,合起来就是7个1,&7个1就把低七位保留,其他位全变0
同理,exit code为高八位
运行结果:
如果我们把子进程的退出码改为正常退出码,即0,再跑,就会退出信号为0,退出码为0:
退出信号会显示8:
我们让子进程出现空指针错误:
退出信号为11:11就是段错误:
此时运行之后子进程就会一直跑,然后我们输入kill -9 pid终止进程,进程退出信号会显示9:
父进程得到子进程的退出结果实际上是调用stautus这个整型变量,可以用一个指针解引用即可得到status。
用这两个宏就我们就可以不用关注返回值,宏会自己获取子进程的返回值。
调用WIFEXITED获取是否正常退出,如果为假,直接输出else里面的异常。
如果为真,就调用WEXITSTAUS获取子进程的退出码并打印出来。
运行:
与阻塞式等待相对。
阻塞式等待父进程什么事情也不干,就在那等着子进程返回值。
阻塞式等待可以干自己的事情,等子进程返回的时候接收一下就可以了。
fork()
函数创建一个子进程,子进程在循环中调用work()
函数并逐秒递减计数器。当计数器达到0时,子进程通过exit(0)
结束。父进程则使用waitpid()
函数等待子进程的结束,并打印相关信息。子进程会运行10次work()
函数然后退出。
#include
#include
#include
#include
#include
void work(int cnt)
{
printf("i am child process ,pid:%d,cnt:%d\n",getpid(),cnt);
}
int main()
{
// int status=0;
//子进程
pid_t id=fork();
if(id==0)//说明子进程创建成功
{
int cnt=10;
while(cnt )
{
// printf("child process creat sucess\n");
work(cnt);
sleep(1);
cnt--;
}
exit(0);
}
//父进程
int status=0;
pid_t rid=waitpid(id,&status,WNOHANG);
if(rid>0)
{
//走到这里,说明父进程等待子进程成功
printf("child quit sucess,exit code:%d,exit sigin:%d\n ",(status<<8)&0xFF,status&0x7F);
}
else if(rid==0)
{
//走到这里,父进程等子进程成功,但是子进程还未退出。
printf("father to do other things\n");
}
else
{
//走到这里就只有最后一种情况:rid<0,说明有异常,一般都是pid写错了
printf("wait fail\n");
}
return 0;
}
pid写错就会等待失败:
这里故意写错。
那么父亲节就检测到子进程不是它的,父进程就退了,子进程还在继续
因为是非阻塞式等待,所以父进程在等待期间可以做其他事情。
我们让父进程进程三个任务,下载任务,打印日志任务,展示信息任务,任务时长为5秒:
然后我们定义一个函数指针数组,然后把它tyepdef为一个函数指针类型:
也就是说 task_t就是我们的任务类型。
然后我们开始执行任务,
tasks[TASK_NUM]也就是有五个任务。
然后对五个任务进行初始化,最后添加任务。
调用:
task_t tasks[TASK_NUM];
InitTasks(tasks,TASK_NUM);
AddTask(tasks);
定义:
对五个任务进行初始化,把每个任务初始化为NULL;
对任务数组添加任务,如果任务数组为NULL,那就把 t 这个任务添加到任务数组中;
用return 1表示添加任务成功,return 0表示任务数组内全为有效内容,不再添加,退出。
void IninTasks(task_t tasks[],int num)
{
for(int i=0;i
接下来我们就开始添加执行任务了,把download,PrinfLog,show三个任务添加:
AddTask(tasks,download);
AddTask(tasks,PrintLog);
AddTask(tasks,show);
执行任务:
void executeTask(task_t task[],int num)
{
//安全检查
for(int i=0;i
然后把executeTask()添加到父进程等待那里,让父进程可以在等待期间移除,添加,执行任务:
else if(rid==0)
{
//走到这里,父进程等子进程成功,但是子进程还未退出。
printf("father to do other things\n");
executeTask(tasks,TASK_NUM);//父进程在等待期间也可以移除,添加等任务
}
我们可以看见子进程在执行work(),父进程在等待期间做其他事情,下载,打印日志,展示信息:
#include
#include
#include
#include
#include
#define TASK_NUM 5
typedef void (*task_t)();
//
void download()
{
printf("this is a dowm;load task is running\n");
}
void PrintLog()
{
printf("this is a wirte log task\n");
}
void show()
{
printf("this is ashow info task is running\n");
}
void InitTasks(task_t tasks[],int num)
{
for(int i=0;i0)
{
//走到这里,说明父进程等待子进程成功
printf("child quit sucess,exit code:%d,exit sigin:%d\n ",(status<<8)&0xFF,status&0x7F);
}
else if(rid==0)
{
printf("#########################################################");
//走到这里,父进程等子进程成功,但是子进程还未退出。
printf("father to do other things\n");
executeTask(tasks,TASK_NUM);//父进程在等待期间也可以移除,添加等任务
sleep(1);
}
else
{
//走到这里就只有最后一种情况:rid<0,说明有异常,一般都是pid写错了
printf("wait fail\n");
break;
}
}
return 0;
}
总结:非阻塞等待可以让父进程在等待期间做其他任务