绵阳住房和城市建设局网站官网,wordpress 主题 模板 区别,做网站需要什么服务器配置,做网站月薪10万Linux#xff1a;进程等待究竟是什么#xff1f;如何解决子进程僵尸所带来的内存泄漏问题#xff1f; 一、进程等待的概念二、进程等待存在的意义三、如何进行进程等待3.1 wait()是实现进程等待1、wait()原型2. 验证wait()能回收僵尸子进程的空间 3.2 waitpid()实现进程等待… Linux进程等待究竟是什么如何解决子进程僵尸所带来的内存泄漏问题 一、进程等待的概念二、进程等待存在的意义三、如何进行进程等待3.1 wait()是实现进程等待1、wait()原型2. 验证wait()能回收僵尸子进程的空间 3.2 waitpid()实现进程等待1、系统调用接口waitpid()原型 四、获取子进程status实现机制五、阻塞等待和非阻塞等待5.1 阻塞等待5.2 非阻塞等待非阻塞 轮询方案 六、非阻塞轮询方案示例演示 一、进程等待的概念 进程等待通常是指父进程通过wait()/waitpid()的方式让父进程对子进程进行资源回收的等待过程
二、进程等待存在的意义 进程等待通常是为了解决以下两种情况
解决子进程僵尸所带来的内存泄漏问题对僵尸子进程进行资源回收 原因在于当子进程僵尸后便“刀枪不入”了。即使是操作系统也没法对僵尸进程进行资源回收进而导致内存泄漏问题。让父进程获得子进程运行结果代码运行正常结果正确、代码运行正常结果错误、代码异常。父进程创建子进程通常是希望子进程帮父进程执行某些任务。但子进程任务执行的如何父进程需要得到反馈。此时父进程可以通过进程等待的方式来获取子进程的退出信息退出码和退出信号。
三、如何进行进程等待
下面依次介绍进程等待所需调用的接口wait()/waitpid()。
3.1 wait()是实现进程等待
1、wait()原型
#include sys/types.h
#include sys/wait.hpid_t wait(int *status);返回值如果成功返回被等待进程的pid否则返回-1.参数ststus为输出型参数获取子进程的退出信息由操作系统自动填充。后续会单独详细介绍这里我们暂且不关心该参数设为NULLwait()用于等待任意进程而waitpid则可以等待任意进程
2. 验证wait()能回收僵尸子进程的空间 下面这样一段代码fork()创建子进程让子进程运行约5秒后退出但父进程不退出。此时子进程变为僵尸进程进程状态为Z。此时调用父进程调用wait()接口回收僵尸子进程的资源空间。
【源代码】
#include stdio.h
#include unistd.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h
#include unistd.hvoid 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){//childworker();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。对于多个进程来说谁先被调度是未知的由内核调度算法决定。但可以肯定的是父进程一定是最后退出的
3.2 waitpid()实现进程等待
1、系统调用接口waitpid()原型
#include sys/types.h
#include sys/wait.hpid_t waitpid(pid_t pid, int *status, int options);参数pid: 如果pid-1等待任意进程和wait效果一样。如果pid0等待进程ID和pid值相等的子进程参数status子进程的退出信息。status为NULL表示不关心子进程的退出状态信息否则操作系统会将子进程的退出码和错误码相关信息写入该参数中。后续具体介绍其实现机制参数options: 为0表示阻塞等待。options除了0外还可以被设置为WNOHANG此时表示父进程以非阻塞方式进行等待非阻塞 轮询方案。返回值当正常退出时waitpid返回收集到的子进程ID如果进程异常返回-1此时errno会被设置为对于的错误码如果进程采用非阻塞轮询方案即将options设置为WNOHANG如果子进程waitpid收集到的子进程没有退出此时返回0
四、获取子进程status实现机制 在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, (status8)0xFF, status0x7F); } return 0;
} 【运行结果】 我们发现status变量中确实保存着子进程的退出码和退出信号等相关信息。
在系统中提供了一些宏函数用于直接获取进程的相关退出信息,具体如下
WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码
父进程如何得知子进程的退出信息底层执行流程 在子进程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表示父进程以(非阻塞方式)即非阻塞轮询方式进行进程等待。 那两种等待方式究竟是什么有什么区别呢
5.1 阻塞等待 父进程以阻塞方式进行等待和普通阻塞进程一样。 当父进程调用waitpid接口等待子进程时如果此时子进程没有退出操作系统会将父进程设置为阻塞进程然后将父进程的PCB链入到子进程的等待队列中。一旦子进程退出操作系统会将父进程PCB重新加载到运行队列中等待调度
5.2 非阻塞等待非阻塞 轮询方案 非阻塞等待是指父进程在等待子进程时发现子进程还未退出此时父进程和阻塞等待一样一直在原地等地子进程运行结束。父进程会执行一些其他任务并每隔一段时间查看子进程是否退出。一旦子进程退出后父进程才会开始执行后续程序。 非阻塞等待的好处就是让父进程在等待时可以做一些自己占据时间不多的任务
六、非阻塞轮询方案示例演示 下面我们通过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){//childint cnt 5;while(cnt){worker(cnt--);sleep(1);}exit(3);}//parentwhile(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, (status8)0xFF, status0x7F);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]();}
}
运行结果