一、僵尸进程 Zombie Process
1、父子进程模型中,子进程先于父进程结束,父进程必须回收子进程资源(PCB),否则会产生内存泄漏。(僵尸进程)
2、为什么用户空间会被内核回收,而PCB不会,子进程PCB要留给父进程回收,子进程退出的详情保存在PCB中,父进程需要知道子进程的具体因为什么退出(死后验尸QAQ),子进程PCB回收只有父进程能做。(linux强亲缘关系)
3、僵尸进程的危害
1)僵尸进程有比较大的内存泄漏危害。(PCB是庞大的结构体,内部成员众多,许多成员还额外占用内存空间)
2)僵尸进程导致PCB残留,影响新进程的创建。
#include#include #include int main(void) { pid_t pid; pid = fork(); if(pid > 0){ printf("parent runing...n"); while(1) sleep(1); }else if(pid == 0){ printf("child exit..n"); sleep(8); exit(0); //此代码,子进程退出,会产生僵尸进程 }else{ perror("fork call failed"); exit(0); } return 0; }
通过ps aux查看,可观察到状态为Z+的僵尸进程
4、wait函数(父进程通过该函数回收子进程的PCB资源,避免僵进程)
pid_t wpid = wait(int * status);
1)int * status , 当回收成功,会将PCB中子进程的退出状态传出到status变量,父进程可以进行处置与校验,获取子进程结束原因。不想验尸status传空。
2)RETURN VALUE:
1.Wait回收成功,返回僵尸进程pid (wpid > 0)
2.如果返回-1表示回收失败(无子进程调用wait) (wpid == -1)
wait是一个阻塞函数,使用wait回收,父进程则无法执行任何任务,必须等待回收完毕,自由才可以被释放。
#include#include #include #include int main(void) { pid_t pid; pid = fork(); if(pid > 0){ pid_t wpid; printf("parent %d runing...n",getpid()); wpid = wait(NULL); printf("parent wait %d child n",wpid); while(1) sleep(1); }else if(pid == 0){ printf("child %d running ...n",getpid()); sleep(8); printf("child %d exit ...n",getpid()); exit(0); }else{ perror("fork call failed"); exit(0); } return 0; }
5、waitpid
是wait的进阶版,支持多种参数选项,具备更灵活的回收方式,支持非阻塞轮询回收方案。(单进程模式下父进程可在回收过程中穿插执行自身任务)
默认情况下waitpid也是阻塞回收,可以通过opt设置为非阻塞。
pid_t pid = waitpid(pid_t pid , int * status , int opt);
1)argment pid
-1 : 回收任意子进程
大于0 : 通过pid指定回收一个子进程。
等于0 : 同组回收方案,可以帮助调用进程将本进程组所有子进程回收处理掉。
<-1: 跨组回收方案,-PGID,到指定进程组,回收子进程资源。
2)argment status
回收成功传出子进程的结束原因,父进程可以校验, 如果不需要则传NULL。
3)argment opt
WNOHANG 非阻塞关键字
4)RETURN VALUE
-1 , 表示回收失败(无子进程回收)
大于0 , 表示回收成功(返回僵尸进程的pid > 0)
等于0 , 表示非阻塞返回(TRY AGAIN)
使用waitpid编写非阻塞回收模型,在回收过程中让父进程执行N次自定义任务。
#include#include #include #include int main(void) { pid_t pid; pid = fork(); if(pid > 0){ pid_t wpid; printf("parent %d runing...n",getpid()); while((wpid = waitpid(-1, NULL ,WNOHANG)) != -1){ if(wpid > 0){ printf("parent wait child success,zombie pid :%dn",wpid); break; }else{ printf("parent Try to do a job ... n"); sleep(1); } } while(1) sleep(1); }else if(pid == 0){ printf("child %d running ...n",getpid()); sleep(8); printf("child %d exit ...n",getpid()); exit(0); }else{ perror("fork call failed"); exit(0); } return 0; }
但是无论wait还是waitpid 都属于主动回收,无论采取哪种方式,主动回收总会浪费大量资源,前者阻塞,后者不停询问。最理想的是子进程退出了会有消息通知我,然后我去回收,以后会聊到。(通过信号实现)
6、status 子进程的推出原因校验
无论是wait函数还是waitpid函数都可以通过status参数对子进程结束原因进行传出校验。
1) WIFEXITED(status)
校验子进程是否正常退出,如果是返回True。
2)WEXITSTATUS(status)
如果子进程为正常退出,可以通过该函数获取子进程退出码,例如exit(8), 8会被返回。
3)WIFSIGNALED(status)
校验子进程是否被信号杀死,如果是返回True。
4)WTERMSIG(status)
子进程被信号杀死,可以利用该函数返回终止子进程的信号编号。
#include#include #include #include int main(void) {int status; pid_t pid; pid = fork(); if(pid > 0){ pid_t wpid; printf("parent %d runing...n",getpid()); while((wpid = waitpid(-1, &status ,WNOHANG)) != -1){ if(wpid > 0){ printf("parent wait child success,zombie pid :%dn",wpid); if(WIFEXITED(status)) printf("child %d process exit code %dn",wpid,WEXITSTATUS(status) ); if(WIFSIGNALED(status)) printf("kill %d child process signal %dn",wpid,WTERMSIG(status) ); break; }else{ printf("parent Try to do a job ... n"); sleep(1); } } while(1) sleep(1); }else if(pid == 0){ printf("child %d running ...n",getpid()); sleep(8); printf("child %d exit ...n",getpid()); while(1);//signal 杀死 先卡住等待外部信号 方便测试 //exit(8); //正常推迟 }else{ perror("fork call failed"); exit(0); } return 0; }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)