Linux系统编程——僵尸进程及wait/waitpid函数
来源:互联网 发布:如何增加福报 知乎 编辑:程序博客网 时间:2024/05/17 02:37
僵尸进程
一般来说,当父进程fork()了一个子进程之后,它有义务把子进程回收。但如果子进程运行完毕后,父进程尚未回收,此时的子进程残留信息(PCB)存放于内核中,变成僵尸进程。(若父进程先于子进程结束,则子进程成为孤儿进程。)
我们知道,子进程的结束和父进程的运行是异步的,那么,父进程会不会不知道子进程已经结束了呢?UNIX中存在一种机制,一个进程调用exit()来结束自己的生命,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但还保留了PCB存放于进程列表中,PCB主要记录了进程的退出状态和进程号。当父进程想要知道子进程的退出状态时,就可以随时查看,进而释放PCB。其实,每个进程都会变成僵尸进程。只是,当父进程快速的回收子进程的时候,我们察觉不到,而父进程还没回收的子进程,则残留在内核中成为了真正的僵尸进程。
通过top指令我们可以看到,僵尸进程并不占用内存资源,但这并不意味着它就没有危害。系统的进程号是有限的,如果产生大量的僵尸进程,则会占用大量的进程号,这就会导致新创建的进程无进程号可用。
僵尸进程代码如下:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){ pid_t pid; pid = fork(); if (pid == 0) { printf("-----child, my parent = %d, going to sleep 10s\n", getppid()); sleep(10); printf("-------------child die---------\n"); } else if (pid > 0) { while (1) { printf("I am parent, pid = %d, myson = %d\n", getpid(), pid); sleep(1); } } else { perror("fork"); return -1; } return 0;}
运行结果:
另外开一个shell,使用ps aux
命令查看进程:
红框圈起来的就是僵尸进程,<defunct>
的意思是死亡。
使用top -p 404 -p 405
命令查看僵尸进程的内存占用情况:
S是状态栏,Z(zombie)代表僵尸进程,S(sleeping)代表父进程。在虚拟内存(VIRT)和物理内存(RES)一栏,父进程均有占用,而僵尸进程则占用为0。
既然僵尸进程对系统有一定的危害,那么有没有什么办法回收呢?这就要用到我们下面要讲到的wait和waitpid函数。
wait()函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
父进程调用wait函数来回收子进程,该函数的作用主要表现在:
- 阻塞等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
wait函数的原型:pid_t wait(int *status)
成功:清理掉的子进程ID; 失败:-1(没有子进程)
注:参数 status 是一个整形指针传出参数,用来保存进程的退出(终止)状态。如果status不是一个空指针,则终止进程的终止状态将存储在该指针所指向的内存单元中。如果不关心终止状态,可以将 status参数设置为NULL。而子进程的进程ID将作为函数的返回值返回。
对僵尸进程的代码进行稍微修改如下:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){ pid_t pid, wpid; pid = fork(); if (pid == 0) { printf("-----child, my parent = %d, going to sleep 10s\n", getppid()); sleep(10); printf("-------------child die---------\n"); } else if (pid > 0) { wpid = wait(NULL); if (wpid == -1) { perror("wait error:"); exit(1); } while (1) { printf("I am parent, pid = %d, myson = %d\n", getpid(), pid); sleep(1); } } else { perror("fork"); return -1; } return 0;}
运行结果:
使用ps aux
命令查看进程列表:
可见,此时僵尸进程已被回收。
当参数status带着子进程的终止信息传出来时,因该指针指向的是整形,所以需要借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下两组:
- WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数) - WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号
注:在Linux中,所有的进程异常退出都是由信号所导致的。
使用第一组宏函数代码如下(正常结束):
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){ pid_t pid, wpid; int status; pid = fork(); if (pid == 0) { printf("-----child, my parent = %d, going to sleep 3s\n", getppid()); sleep(3); printf("-------------child die---------\n"); exit(76); //return 100; } else if (pid > 0) { wpid = wait(&status); if (wpid == -1) { perror("wait error:"); exit(1); } if (WIFEXITED(status)) { printf("child exit with %d\n", WEXITSTATUS(status)); } while (1) { printf("I am parent, pid = %d, myson = %d\n", getpid(), pid); sleep(1); } } else { perror("fork"); return -1; } return 0;}
运行结果:
使用第二组宏函数代码如下(异常结束):
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){ pid_t pid, wpid; int status; pid = fork(); if (pid == 0) { printf("-----child, my parent = %d, going to sleep 30s\n", getppid()); sleep(30); printf("-------------child die---------\n"); exit(76); //return 100; } else if (pid > 0) { wpid = wait(&status); if (wpid == -1) { perror("wait error:"); exit(1); } if (WIFEXITED(status)) { printf("child exit with %d\n", WEXITSTATUS(status)); } if (WIFSIGNALED(status)) { printf("child killed by %d\n", WTERMSIG(status)); } while (1) { printf("I am parent, pid = %d, myson = %d\n", getpid(), pid); sleep(1); } } else { perror("fork"); return -1; } return 0;}
运行结果:
打开两个终端,其中一个终端运行该程序:
另外一个终端执行kill pid
或kill -9 pid
命令。
程序返回:
从这些结果中我们可以看到,当kill pid
时,返回的信号是15,而当kill -9 pid
时返回的信号是9。这是什么原因?这就要从kill命令说起,从下图中我们可以看到,
kill pid
对应的信号编号是15,而kill -9 pid
对应的信号编号是9。
waitpid()函数
wait函数一次只能回收一个子进程,假设现在有5个子进程都死亡了,那么wait函数回收的子进程到底是哪一个呢?如果父进程独宠第5个子进程,并不想管其它子进程的死活呢?这就需要使用waitpid函数,它能回收指定的子进程,并可以不阻塞。
函数原型: pid_t waitpid(pid_t pid, int *status, int options);
成功:返回回收的子进程ID; 失败:-1(无子进程)
参数options可以设置阻塞(0)或非阻塞(WNOHANG),如果设置成阻塞,则等同于wait;若设置成非阻塞,则父进程不会阻塞等待子进程结束,而是直接返回。若参数options为WNOHANG(非阻塞),且当前子进程正在运行,则函数返回0.
对于参数pid:
- >0 回收指定ID的子进程
- -1 回收任意子进程(相当于wait)
- 0 回收和当前调用waitpid一个组的所有子进程
- <-1回收指定进程组内的任意子进程
注:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
以下程序主要实现的功能是:创建5个子进程,并用waitpid函数来回收它们。
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>int main(void){ int n = 5, i; //默认创建5个子进程 pid_t pid, wpid; for(i = 0; i < n; i++) //出口1,父进程专用出口 { pid = fork(); if (pid == 0) { break; //出口2,子进程出口 } } if (n == i) //父进程执行 { sleep(n); printf("I am parent, pid = %d, gpid = %d\n", getpid(), getgid()); do { wpid = waitpid(-1, NULL, WNOHANG); if (wpid > 0) { n--; } sleep(1); }while(n > 0); printf("wait finish\n"); } else //子进程执行 { sleep(i); printf("I am %dth child, pid = %d, gpid = %d\n", i+1, getpid(), getgid()); } return 0;}
输出结果:
用ps aux
命令查看进程,没有出现僵尸进程,回收成功。
- Linux系统编程——僵尸进程及wait/waitpid函数
- linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times
- linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times
- wait、waitpid及僵尸进程
- 网络编程(12)—— 利用wait和waitpid函数消毁僵尸进程
- Linux系统进程控制编程(六)——wait和waitpid函数
- Linux系统进程控制编程——wait和waitpid函数
- 进程编程中的孤儿和僵尸进程--wait/waitpid函数
- 僵尸进程以及wait和waitpid函数
- 僵尸进程 wait() waitpid()
- 僵尸进程 wait() waitpid()
- 僵尸进程 wait() waitpid()
- 僵尸进程 wait waitpid
- Linux进程 -- wait/waitpid函数
- 十二、Linux系统编程-进程(五)SIGCHLD、wait、waitpid、system
- wait waitpid WNOHANG 僵尸进程
- UNIX多进程 - 销毁僵尸进程 - wait()和waitpid()函数
- UNIX多进程 - 销毁僵尸进程 - wait()和waitpid()函数
- 垂直居中
- 错误:module 'scipy.misc' has no attribute 'imread'
- python学习五-使用字符串
- 如何使用Disruptor(一)Ringbuffer的特别之处
- opencv1.0学习笔记(一)之OpenCV1.0的安装及配置
- Linux系统编程——僵尸进程及wait/waitpid函数
- 【知识碎片】1:2017.12.17前(建立索引,JSE基础细节)
- 使用Spring框架下的完成对事务的操作
- 如何用fiidler抓安卓手机的包
- Codeforces Round #451 (Div. 2) a
- Java基础-类-接口interface-package-import.
- day01-学习elasticsearch
- 实验三 、四 继承、派生和多态程序设计
- 中国绅士笔记