孤儿进程僵尸进程及其回收是进程的经典知识了。
什么是孤儿进程?
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为 init 进程领养孤儿进程。
什么是僵尸进程?
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
特别注意,僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。
孤儿进程有init进程回收,但是僵尸进程要等父进程来回收,如果父进程不回收,僵尸进程会已经死亡得不到回收却占用进程号,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
相关函数:
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。
这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量$?查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。
wait 函数
pid_t wait(int *status);
父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出 ② 回收子进程残留资源 ③ 获取子进程结束状态(退出原因)。
函数返回值:成功:清理掉的子进程 ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
status判断终止原因
可使用 wait 函数传出参数 status 来保存进程的退出状态。借助宏函数来进一步判断进
程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非 0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit 的参数)
2. WIFSIGNALED(status) 为非 0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非 0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<sys/wait.h> 5 6 int main() 7 { 8 pid_t pid,wpid; 9 int status; 10 11 pid=fork(); 12 if (pid==0) { //子进程 13 printf("i am child, pid is %d, sleep 10s ",pid); 14 sleep(20); 15 printf("i am diying"); 16 return 73; 17 } else if (pid>0) { //父进程 18 //wait函数会阻塞,如果没有一个子进程死亡 19 //wpid=wait(NULL); //不关心子进程如何死亡 20 wpid=wait(&status); //关心子进程死亡,写到status里面 21 if (wpid==-1) { 22 perror("wait error"); 23 exit(1); 24 } 25 26 if (WIFEXITED(status)) //为真,子进程正常终止 27 printf("child exit with %d\n",WEXITSTATUS(status)); 28 29 if (WIFSIGNALED(status)) //为假,子进程被信号终止 30 printf("child kill with signal %d\n",WTERMSIG(status)); 31 32 printf("wait successful, %d died",pid); 33 34 } else { 35 perror("fork error"); //fork出错了 36 return 1; 37 } 38 return 0; 39 }
waitpid 函数
pid_t waitpid(pid_t pid, int *status, in options); 作用同 wait,但可指定 pid 进程清理,可以不阻塞。
特殊参数和返回情况:
参数 pid:> 0 回收指定 ID 的子进程 -1 回 回于 收任意子进程(相当于 wait ) 0 回收和当前调用 waitpid 一个组的所有子进程 < -1 回收指定进程组内的任意子进程
参数status:和wait函数的status一样,判断进程终止原因
参options: 0 :(相当于wait)阻塞回收 WBNIOHANG:非阻塞回收(轮询)
返回值:成功:返回清理掉的子进程 ID;失败:-1(无子进程)
特殊的返回 0:参 3 为 WNOHANG,且子进程正在运行。
注意:次 一次 wait 或 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环while。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<sys/wait.h> 6 #include<pthread.h> 7 8 int main(int argc,char *argv[]) 9 { 10 int i; 11 pid_t pid,wpid; 12 13 for (i=0;i<5;i++) { 14 pid=fork(); 15 if (pid==0) break; 16 } 17 18 if (i==5) { //父进程 19 20 //这里不断while循环等待死亡的子进程 21 while ((wpid=waitpid(-1,NULL,WNOHANG))!=-1) { 22 if (wpid>0) { //回收成功 23 printf("wait child %d \n",wpid); 24 } else if (wpid==0) { //回收失败 25 sleep(1); //sleep一秒之后再尝试回收 26 continue; 27 } 28 } 29 } else { //5个子进程 30 sleep(i); //子进程sleep以一下 31 printf("I‘m child %d ,i am dying",i); 32 } 33 return 0; 34 }
更加美妙的回收方式是使用SIGCHLD信号回收子进程,这样父进程不阻塞可以干自己的事情:https://www.cnblogs.com/clno1/p/12941316.html
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<signal.h> 6 #include<sys/wait.h> 7 #include<errno.h> 8 #include<pthread.h> 9 10 void sys_err(const char *str) { 11 perror(str); 12 exit(1); 13 } 14 15 void catch_child(int signo) { 16 pid_t wpid; 17 int status; 18 19 //这里的while非常重要,当接受到SIGCHLD信号时候,有多个子进程同时死亡,这时候就需要while来把这段时间的死亡子全部回收 20 while ((wpid=waitpid(-1,&status,0))!=-1) { 21 if (WIFEXITED(status)) 22 printf("-------------catch child id %d, ret=%d \n",wpid,WEXITSTATUS(status)); 23 } 24 return; 25 } 26 27 int main(int argc,char *argv[]) 28 { 29 pid_t pid; 30 int i; 31 32 for (i=0;i<15;i++) 33 if ((pid=fork())==0) //创建15个子进程 34 break; 35 36 if (i==15) { //父进程 37 38 //信号结构体,三个参数重要 39 struct sigaction act; 40 41 act.sa_handler=catch_child; //注册函数 42 sigemptyset(&act.sa_mask); //在执行期间,sa_mask会替换原mask 43 act.sa_flags=0; //设为0,在该信号处理函数期间,再次收到同样信号就屏蔽 44 45 sigaction(SIGCHLD,&act,NULL); 46 47 printf("I‘m parent, pid=%d \n",getpid()); 48 49 while(1); 50 51 } else { //子进程 52 printf("I‘m child pid= %d\n",getpid()); 53 return i; 54 } 55 return 0; 56 }