文章目录
- 1.进程等待必要性
- 1.1什么是进程等待?**
- 1.2为什么需要进程等待?
- 2.进程等待的方法
- 2.1wait方法
- 2.2waitpid方法
- 2.2.1获取子进程status
- 2.2.2options选项,父进程等待的三种方式
1.进程等待必要性
1.1什么是进程等待?**
通过系统调用
wait/waitpid
,来进行对子进程状态监测与回收的功能!
1.2为什么需要进程等待?
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define N 10
void RunChild()
{
int cnt = 5;
while (cnt)
{
printf("I am Child Process, pid: %d, ppid:%d\n", getpid(), getppid());
sleep(1);
cnt--;
}
}
int main()
{
//wait / waitpid
for (int i = 0; i < N; i++)
{
pid_t id = fork();
if (id == 0)
{
RunChild();
exit(i);
printf("create child process: %d success\n", id); // 这句话只有父进程才会执行
}
}
sleep(10);
return 0;
}
代码运行结果如下:
①僵尸进程无法被信号杀死,需要进程等待杀掉它,解决内存泄漏问题;②创建子进程,父进程(用户)需要知道子进程任务的完成的情况,可以通过进程等待子进程任务的完成的情况!
2.进程等待的方法
父进程通过调用
wait/waitpid
,对子进程资源的回收!
2.1wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
作用:
可以回收任意退出的子进程
eg1:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define N 10
void RunChild()
{
int cnt = 5;
while (cnt)
{
printf("I am Child Process, pid: %d, ppid:%d\n", getpid(), getppid());
sleep(1);
cnt--;
}
}
int main()
{
for (int i = 0; i < N; i++)
{
pid_t id = fork();
if (id == 0)
{
RunChild();
exit(i);
printf("create child process: %d success\n", id); // 这句话只有父进程才会执行
}
}
sleep(10);
// 等待
for(int i = 0; i < N; i++)
{
// wait当任意一个子进程退出的时候,wait回收子进程
pid_t id = wait(NULL);
if(id > 0)
{
printf("wait pid:%d success\n", id);
}
}
return 0;
}
代码运行结果如下:
2.2waitpid方法
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
。
eg2:
//pid_t id = wait(NULL);将上面的eg1的代码语句替换成以下一句,可以达到相同的效果
pid_t id=waitpid(-1,NULL,0);
代码运行结果如下:
2.2.1获取子进程status
wait和waitpid
,都有一个status
参数,该参数是一个输出型参数,由操作系统填充。- 如果传递
NULL
,表示不关心子进程的退出状态信息。- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status
不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位):
eg3:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
int cnt=3;
while(cnt)
{
printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
cnt--;
sleep(1);
}
exit(12);
}
else{
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret==id)
{
printf("wait success, ret:%d, exit sig:%d, exit code:%d\n",ret,status&0x7F,(status>>8)&0xFF);
}
}
return 0;
}
代码运行结果如下:
使用信号杀掉子进程的指令:
kill -l -pid
//其中-l为信号选项,可以选用任意异常信号终止进程
eg4:
//使用指令
kill -9 30773
代码运行结果如下:
eg5:
//在子进程允许的代码中添加以下,让子进程异常退出
int* p=NULL;
*p=100;
代码运行结果如下:
总结:
①进程异常会被操作系统识别成信号杀掉;进程没有出现异常,信号码为0,这时我们只需要观察进程退出即可,子进程结束后代码和数据立即释放掉,PCB资源并没有被立即释放,操作系统需要从子进程PCB中获取信号码和退出码数据封装到status中,让父进程通过wait/waitpid
在这里插入代码片
系统接口函数获取!
②可以使用以下两个宏,一个判断进程是否正常终止,一个获取进程的退出码
WIFEXITED(status):
若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status):
若WIFEXITED
非零,提取子进程退出码。(查看进程的退出码)
2.2.2options选项,父进程等待的三种方式
①options
选项若是为0
,父进程阻塞等待子进程结束,等待方式:阻塞等待
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
int cnt=3;
while(cnt)
{
printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
//printf("I am child, pid:%d, ppid:%d",getpid(),getppid());
cnt--;
sleep(1);
}
sleep(20);
exit(12);
}
else if(id>0){
int status = 0;
printf("I am parent, pid: %d 子进程,你知道我在等你吗?\n",getpid());
pid_t ret = waitpid(id,&status,0);
if(ret>0)
{
if(WIFEXITED(status))
{
printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
}
else{
printf("进程出异常了\n");
}
}
}
return 0;
}
代码运行结果如下:
②optiont
选项若是为WNOHANG
,若pid
指定的子进程没有结束,则waitpid()
函数返回0
,等待方式:进行非阻塞等待,配合循环使用(非阻塞轮询)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
int cnt=3;
while(cnt)
{
//int* p=NULL;
//*p=100;
printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
//printf("I am child, pid:%d, ppid:%d",getpid(),getppid());
cnt--;
sleep(1);
}
//sleep(20);
exit(12);
}
else if(id>0)
{
int status = 0;
printf("I am parent, pid: %d 子进程,你知道我在等你呀!\n",getpid());
while(1)//轮询
{
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret>0)
{
if(WIFEXITED(status))
{
printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
}
else{
printf("进程出异常了\n");
}
break;
}
else if(ret<0)
{
printf("wait failed\n");
break;
}
else if(ret == 0)
{
printf("你好了没?子进程还没有退出,我在等等...\n");
sleep(1);
}
}
}
return 0;
}
代码运行结果如下:
③optiont
选项若是为WNOHANG
,若pid
指定的子进程没有结束,则waitpid()
函数返回0
,等待方式:非阻塞轮询+父进程做自己的事情
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#define TASK_NUM 10
typedef void(*task_t)();//函数指针
task_t tasks[TASK_NUM];//函数指针数组
void task1()
{
printf("这是一个执行打印日志的任务,pid:%d\n",getpid());
}
void task2()
{
printf("这是一个执行检测网络健康状态的一个任务,pid:%d\n",getpid());
}
void task3()
{
printf("这是一个进行绘制图形界面的任务,pid:%d\n",getpid());
}
int AddTask(task_t t);//函数声明
//任务的管理代码
void InitTask()
{
for(int i = 0; i < TASK_NUM; i++) tasks[i]=NULL;
AddTask(task1);
AddTask(task2);
AddTask(task3);
}
int AddTask(task_t t)
{
int pos=0;
for(; pos < TASK_NUM; pos++)
{
if(!tasks[pos])break;//判断函数指针数组是否还有位置
}
if(pos == TASK_NUM)return -1;
tasks[pos]=t;
return 0;
}
void ExecuteTask()
{
//执行任务
for(int i = 0; i < TASK_NUM; i++)
{
if(!tasks[i])continue;
tasks[i]();
}
}
int main()
{
pid_t id=fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
int cnt=3;
while(cnt)
{
printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
cnt--;
sleep(1);
}
exit(12);
}
else if(id>0){
int status = 0;
InitTask();//初始化和加载
printf("I am parent, pid: %d 子进程,你知道我在等你呀!\n",getpid());
while(1)//轮询
{
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret>0)
{
if(WIFEXITED(status))
{
printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
}
else{
printf("进程出异常了\n");
}
break;
}
else if(ret<0)
{
printf("wait failed\n");
break;
}
else if(ret == 0)
{
ExecuteTask();//父进程做自己的事情
usleep(500000);
}
}
}
return 0;
}
代码运行结果如下:
问题1: 父进程以非阻塞轮询+做执行自己的任务的方式等待子进程时,究竟是等待子进程重要,还是父进程执行其他任务重要?
答:当然是等待子进程回收资源重要,只不过子进程执行任务时,父进程会闲置造成资源浪费,为充分利用父进程资源,便设置一些任务让父进程去执行!
问题2: 父进程以非阻塞轮询+做执行自己的任务的方式等待子进程时,如果父进程执行其他任务过重,造成没有在子进程结束的时候立即回收,会不会对有什么影响呢?
答:父进程在等待子进程时,执行的任务都是轻量化的工作,如网络写入、记录日志、错误处理等工作,不会影响子进程的回收;如果父进程执行任务过多,没能在子进程结束的时候立即回收,当父进程执行其他任务结束时便会对子进程资源进行回收,不会有什么影响!这种延迟回收的方式,在父进程创建多个子进程时,等待所有子进程全部结束时再一起回收,反而会更好!