目录
3. 子进程没退出时,父进程在wait上进行阻塞等待,直到子进程僵尸,wait自动回收(wait)
一、进程等待的原因
- 解决子进程僵尸问题带来的内存泄露的问题
- 父进程为什么要创建子进程?要让子进程来完成任务。子进程任务完成的如何,父进程要不要知道?要知道(需要通过进程等待的方式),获取子进程退出的信息----两个数字(退出信号和退出码)----不是必须,但是系统需要提供这样的基础功能
二、进程等待的方法
(一)wait方法
1. 定义
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
2. 进程等待能回收子进程的僵尸状态(wait方法验证)
#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件
void Worker()
{
int cnt = 3;
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)//子进程
{
Worker();
exit(0);
}
else
{//父进程
sleep(5);
pid_t rid = wait(NULL);
if(rid == id)
{
printf("wait a process,pid: %d\n",getpid());
}
sleep(5);
}
return 0;
}
3. 子进程没退出时,父进程在wait上进行阻塞等待,直到子进程僵尸,wait自动回收(wait)
#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件
void Worker()
{
int cnt = 3;
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)//子进程
{
Worker();
exit(0);
}
else
{//父进程
printf("wait before!\n");//回收之前
pid_t rid = wait(NULL);
printf("wait after!\n");//回收之后
if(rid == id)
{
printf("wait a process,pid: %d\n",getpid());
}
sleep(5);
}
return 0;
}
(二)waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
- 返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
pid
参数指定了要等待的子进程的进程ID。它可以取以下几个值:-1
:等待任意一个子进程,类似于wait
函数。- 大于
0
:等待指定进程ID的子进程。
status
是一个指向整型的指针,用于接收子进程的退出状态信息。如果子进程正常结束,会将退出状态信息存放在这个指针指向的位置。
status
的内容会被填充为一个整数,其中包含了子进程的退出状态信息。可以通过宏来解析这个整数,比如:WIFEXITED(status)
:若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status)
:若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options
是一个整型参数,可以用来指定一些选项,比如WNOHANG
可以使waitpid
变为非阻塞调用。
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
(三)获取子进程status
1. status结构
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图
2. 我们不能对status整体使用
#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件
void Worker()
{
int cnt = 3;
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)//子进程
{
Worker();
exit(10);
}
else
{//父进程
printf("wait before!\n");//回收之前
int status;
pid_t rid = waitpid(id, &status,0);
printf("wait after!\n");//回收之后
if(rid == id)
{
printf("wait a process,pid: %d, status: %d\n",getpid(),status);
}
sleep(5);
}
return 0;
}
3. 正确使用
#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件
void Worker()
{
int cnt = 3;
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)//子进程
{
Worker();
exit(10);
}
else
{//父进程
printf("wait before!\n");//回收之前
int status;
pid_t rid = waitpid(id, &status,0);
printf("wait after!\n");//回收之后
if(rid == id)
{//我们不能对status整体使用
printf("wait a process,pid: %d, rpid: %d, exit sig: %d, exit code: %d\n",getpid(),rid, status&0x7F, (status>>8)&0xFF);
}
sleep(5);
}
return 0;
}
(四)图示说明父进程等待子进程的方式
(五)阻塞等待和非阻塞等待
-
阻塞式等待:当父进程调用
wait
或者waitpid
(第三个参数为0)等待子进程时,如果子进程还未退出,父进程会被阻塞,暂停执行,直到子进程退出并返回其状态信息。 -
非阻塞式等待:当父进程调用
waitpid
(第三个参数为WNOHANG
)等待子进程时,如果父进程检测到子进程未退出,父进程并不会在原地等待,而是继续执行自己的代码。父进程可以通过检查返回值来判断子进程的状态,如果返回0表示子进程还在运行,非0表示子进程已退出。
非阻塞式等待代码举例:
#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件
#define TASK_NUM 5
typedef void (*task_t)();//函数指针
void InitTasks(task_t tasks[], int num)//初始化任务
{
for(int i =0;i<num;i++)
{
tasks[i] = NULL;
}
}
int AddTask(task_t tasks[], task_t t)//添加任务
{
int i= 0;
for(;i<TASK_NUM;i++)
{
if(tasks[i] == NULL)
{
tasks[i] = t;
return 1;
}
}
return 0;
}
void executeTask(task_t tasks[], int num)//执行任务
{
for(int i =0;i<num;i++)
{
if(tasks[i])
{
tasks[i]();
}
}
}
void Worker(int cnt)//子进程要做的工作
{
printf("I am a child, pid: %d, cnt: %d\n",getpid(),cnt);
}
void download()
{
printf("this is a download task is running\n");
}
void printlog()
{
printf("this is a write log task is running\n");
}
void show()
{
printf("this is a show info tsak is running\n");
}
int main()
{
task_t tasks[TASK_NUM];
InitTasks(tasks, TASK_NUM);//初始化任务
AddTask(tasks, download);
AddTask(tasks, printlog);
AddTask(tasks, show);
pid_t id = fork();
if(id == 0)//子进程
{
int cnt = 5;
while(cnt)
{
Worker(cnt);
sleep(2);
cnt--;
}
exit(0);
}
else
{//父进程
while(1)//父进程对子进程的状态进程轮询
{
int status=0;
pid_t rid = waitpid(id,&status,WNOHANG);//由于第3个参数是WNOHANG,所以子进程如果没有结束将直接返回0
if(rid>0)
{
//子进程退出,返回子进程的pid
printf("child quit success, exit code: %d, exit signal: %d\n",(status>>8)&0xFF,status&0x7F);
break;
}
else if (rid == 0)
{
printf("##################################\n");
//等待的进程暂时没有退出
printf("child is alive, wait again, father do other thing....\n");
executeTask(tasks, TASK_NUM);//执行任务
}
else{
//等待失败,waitpid中的第一个参数传错了
printf("wait failed!\n");
break;
}
sleep(1);
}
}
return 0;
}
文章评论