赞
踩
进程等待通常是指:父进程通过wait()/waitpid()的方式,让父进程对子进程进行资源回收的等待过程!!
进程等待通常是为了解决以下两种情况:
下面依次介绍进程等待所需调用的接口:wait()/waitpid()。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
wait()
用于等待任意进程,而waitpid
则可以等待任意进程!!下面这样一段代码:fork()创建子进程,让子进程运行约5秒后退出但父进程不退出。此时子进程变为僵尸进程,进程状态为Z。此时调用父进程调用wait()接口回收僵尸子进程的资源空间。
【源代码】:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> void worker() { int cnt = 5; while(cnt) { printf("I am child process, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--); sleep(1); } } int main() { pid_t id = fork(); if(id == 0) { //child worker(); exit(0); //子进程执行完worker()后直接退出,变成僵尸状态 } else{ //parent sleep(10); pid_t rid = wait(NULL);//对子进程进行回收 if(rid == id) { printf("child process being recyceled sucess!, pid:%d, rid:%d\n", getpid(), rid); } sleep(3); } return 0; }
【运行结果】:
我们观察左边监视脚本发现,子进程在执行5次代码后退出,进程状态变为Z
。一段时间后,父进程调用wait()
函数对子进程进行回收,子进程消失。即父进程通过wait()实现了对子进程的回收!!
tips:
wait()
后,如果子进程没有退出,父进程会在wait上发生进程阻塞。直到子进程僵尸,wait自动回收后,返回被回收的子进程pid。#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
WNOHANG
,此时表示父进程以非阻塞方式进行等待(非阻塞 + 轮询方案)。waitpid
返回收集到的子进程ID;如果进程异常,返回-1,此时errno会被设置为对于的错误码;如果进程采用非阻塞轮询方案,即将options
设置为WNOHANG
,如果子进程waitpid
收集到的子进程没有退出,此时返回0!! 在wait()/waitpid()中,均存在参数status
,该参数是一个输出型参数,由操作系统自动填充。如果该参数被设为NULL,表示不关心子进程的退出信息;否则OS会通过status的值,来将子进程相关退出信息返回给父进程!!
status如何保存相关信息?
status是int类型,32bit。这里我们仅研究低16位!!
其中status的最低7位保存子进程的退出信号(exit signal);第8位表示的是core dump标志
;9~16位表示的是进程的退出码(exit code)
status中保存信息验证
下面我们来做实验:我们通过fork()创建出子进程,然后让子进程运行约3秒;此时父进程通过waitpid
以阻塞方式对子进程进行等待回收。然后通过位运算对status
进行处理,获取status
中的子进程退出码和退出信号。
【源代码】:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main() { int status = 0; pid_t id = fork(); if(id == 0) { int cnt = 3; while(cnt) { printf("I am child, cnt:%d\n", cnt--); sleep(1); } exit(3); } else if(id > 0) { pid_t rid = waitpid(id, &status, 0);//以阻塞方式等待 printf("I am parent, pid:%d, rid:%d, status:%d, exit code:%d, signal:%d\n", getpid(), rid, status, (status>>8)&0xFF, status&0x7F); } return 0; }
【运行结果】:
我们发现status变量中确实保存着子进程的退出码和退出信号等相关信息。
在系统中提供了一些宏函数,用于直接获取进程的相关退出信息,具体如下:
父进程如何得知子进程的退出信息(底层执行流程)
在子进程pcb中存在如下几个变量,分别用于保存进程的状态、退出码、退出信号:(Linux为例)
strucr task_struct{
int exit_state; //退出状态
int exit_code; //退出码
int exit_signal;//退出信号
}
当子进程退出时,操作系统会将子进程的退出码和退出信号保存到子进程PCB的exit_code
变量和exit_signal
变量中。
而父进程通过waitpid/wait
等待子进程时,OS会将子进程PCB中的退出码和退出信号通过组合放入status变量中,并将子进程的状态从Z
改成S
!!
此时,父进程便可通过status来获取子进程退出信息,子进程可以被操作系统回收。
前面我们介绍waitpis
接口时提到过,options参数设为0,表示父进程进行的时阻塞等待;设为WNOHANG
表示父进程以==(非阻塞方式),即非阻塞轮询方式==进行进程等待。
那两种等待方式究竟是什么?有什么区别呢?
父进程以阻塞方式进行等待和普通阻塞进程一样。
当父进程调用waitpid
接口等待子进程时,如果此时子进程没有退出,操作系统会将父进程设置为阻塞进程,然后将父进程的PCB链入到子进程的等待队列中。一旦子进程退出,操作系统会将父进程PCB重新加载到运行队列中等待调度!
非阻塞等待是指父进程在等待子进程时发现子进程还未退出,此时父进程和阻塞等待一样一直在"原地等地子进程运行结束"。父进程会执行一些其他任务,并每隔一段时间查看子进程是否退出。一旦子进程退出后,父进程才会开始执行后续程序。
非阻塞等待的好处就是让父进程在等待时,可以做一些自己占据时间不多的任务!!
下面我们通过fork
创建子进程。然后让子进程做一些工作(打印输出一些信息,整个过程约10s),此时父进程通过waitpid
接口进行非阻塞轮询方案进行等待。在等待过程中,我们让父进程做一些自己的“小任务”(这些小任务,博主同样采用输出信息代替,各位可根据实际情况修改)
【源代码】:
轮询时,父进程执行任务:
博主将任务简化为输出一些信息,各位可自行更改任务
void download()
{
printf("This download task is running!\n");
}
void writelog()
{
printf("This write log task is running!\n");
}
void printinfo()
{
printf("This print info task is running!\n");
}
大致框架,父进程和子进程执行任务:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #define TASK_NUM 5 void worker(int cnt) { printf("I am child, pid:%d, cnt:%d\n", getpid(), cnt); } int main() { //下面5行模拟父进程的任务被加载好了,方便父进程等待子进程时被执行 task tasks[TASK_NUM]; Init(tasks, TASK_NUM);//初始化 taskadd(tasks, download); //加载任务 taskadd(tasks, writelog); taskadd(tasks, printinfo); pid_t id = fork(); if(id == 0) {//child int cnt = 5; while(cnt) { worker(cnt--); sleep(1); } exit(3); } //parent while(1) { int status = 0; pid_t rid = waitpid(id, &status, WNOHANG); if(rid > 0) {//子进程正常退出 printf("wait sucess!\n, pid:%d, rid:%d, exit code:%d, signal:%d\n",getpid(), rid, (status>>8)&0xFF, status&0x7F); break; } else if(rid == 0) {//父进程等待成功,但子进程没有退出。父进程开始做自己的小任务,一段时间后在查询子进程是否退出 printf("------------------------------------------------\n"); printf("wait sucess, but chils alive, wait again!\n"); executeTask(tasks, 3); printf("------------------------------------------------\n"); } else {//子进程退出异常 printf("wait failed!\n"); break; } sleep(1); } return 0; }
父进程加载任务代码:
void Init(task tasks[], int num) { for(int i = 0; i < num; i++) { tasks[i] = NULL; } } int taskadd(task tasks[], task t) { for(int i = 0; i < TASK_NUM; i++) { if(tasks[i] == NULL) { tasks[i] = t; return 1;//增加任务成功 } } return 0;//增加任务失败 } void executeTask(task tasks[], int num) { for(int i = 0; i < num; i++) { tasks[i](); } }
运行结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。