Linux系统编程——进程(四)进程的退出,子进程退出的信息收集,以及僵尸进程和孤儿进程
发布日期:2021-05-07 23:27:14 浏览次数:22 分类:精选文章

本文共 5093 字,大约阅读时间需要 16 分钟。

1、进程退出

退出方式有两种:正常退出,异常退出

正常退出有5种:

1、Main函数调用return

2、进程调用exit(),标准C库
3、进程调用_exit()或者_Exit(),属于系统调用
和线程有关的
4、进程最后一个线程返回
5、最后一个线程调用pthread_exit

异常退出有三种:

1、调用abort
2、当进程收到某些信号时,如Ctrl + C
3、最后一个线程对取消(cancellation),请求作出响应

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit._exit和_Exit),实现这一点的方法是,将其退出状态((exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数,取得其终止状态。

二、子进程退出的信息收集

为什么要等待子进程退出

1、防止僵尸进程,造成内存泄漏
2、父进程要管理子进程,所以父进程交代给子进程的任务完成的如何,都需要知道,如,子进程运行完成,运行结果对还是不对,或者是否正常退出
3、父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

如果不收集子进程的退出信息会怎么样?

如果子进程的退出状态不被收集,子进程会变成僵死进程(僵尸进程)。

例如

#include 
#include
#include
#include
int main(){ int data = 3; pid_t pid; pid = vfork(); if(pid > 0){ while(1){ printf("Father pid = %d\n",getpid()); sleep(1); } }else if(pid == 0){ while(1){ data--; printf("Child pid = %d\n",getpid()); sleep(3); if(data == 0){ exit(0); } } } return 0;}

在这里插入图片描述

子进程的PID号为20024
在这里插入图片描述
Z是单词(zombie)的缩写,意思是僵尸

收集子进程退出信息的函数wait和waitpid

头文件

#include 
#include

函数原型

pid_t wait(int *status);

status:是一个整型数指针

指针为空:不关心退出状态
指针非空:子进程退出状态放在它所指向的地址中

函数作用:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

wait()要与fork()配套出现,如果在使用fork()之前调用wait(),程序出错时wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

wait函数的两种参数情况

1、status为NULL时

int main(){       int data = 3;    pid_t pid;    pid = vfork();    if(pid > 0){          wait(NULL);       while(1){              printf("Father pid = %d\n",getpid());           sleep(1);       }    }else if(pid == 0){       	while(1){           	data--;            printf("Child pid = %d\n",getpid());            sleep(3);            if(data == 0){               	exit(0);            }        }     }     return 0;}

2、status非空时

#include 
#include
#include
#include
int main(){ int data = 3; pid_t pid; pid = vfork(); int status = 10; if(pid > 0){ wait(&status); printf("child quit status = %d\n",WEXITSTATUS(status)); while(1){ printf("Father pid = %d\n",getpid()); sleep(1); } }else if(pid == 0){ while(1){ data--; printf("Child pid = %d\n",getpid()); sleep(3); if(data == 0){ exit(3); } } } return 0;}

在这里插入图片描述

在这里插入图片描述
可以看到用wait将子进程的返回信息收集了起来,并且将返回的信息放到了status指向的内存中,并且没有出现进程号为20319的僵尸进程,进程被关闭

一些宏

在这里插入图片描述
waitpid

头文件

#include 
#include

函数原型

pid_t waitpid(pid_t pid, int *status, int options);int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,wait使调用者阻塞,waitpid 可以使调用者不阻塞

PID:

pid > 0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid = -1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid == 0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

status:是一个整型数指针,和wait一样

options:options提供了一些额外的选项来控制waitpid,目前 在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

ret = waitpid(-1,  NULL,  0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

返回值:

1、当正常返回的时候,waitpid返回收集到的子进程的进程ID
2、如果设置了选项WNOHANG,在调用中waitpid发现没有已经退出的子进程可收集时,返回0
3、如果调用中出错,则返回-1

例:

#include 
#include
#include
#include
int main(){ int data = 3; pid_t pid; pid = fork(); int status = 10; if(pid > 0){ waitpid(pid,&status,WNOHANG); printf("child quit status = %d\n",WEXITSTATUS(status)); while(1){ printf("Father pid = %d\n",getpid()); sleep(1); } }else if(pid == 0){ while(1){ data--; printf("Child pid = %d\n",getpid()); sleep(3); if(data == 0){ exit(3); } } } return 0;}

在这里插入图片描述

在这里插入图片描述
在这里可以看到进程号为20830的子进程变成了僵尸进程,这是因为用的是fork()函数创建进程,它不会先执行子进程,,再执行父进程,所以在执行父进程的时候waitpid这里没有返回子进程的pid号,又因为设置了选项WNOHANG,所以返回0,当真正的子进程信息返回来的时候,就没有收集,所以变成了僵尸进程。

当把fork改为vfork时,就可以解决

pid = vfork();

在这里插入图片描述

在这里插入图片描述
在这里并没有出现进程号为20917的僵尸进程

三、孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程

Linux为了避免孤儿进程过多,init进程收留孤儿进程,变成孤儿进程的父进程(init进程为系统初始化进程,进程ID为1)

例如

#include 
#include
#include
#include
int main(){ int data = 3; pid_t pid; pid = fork(); if(pid > 0){ printf("Father pid = %d\n",getpid()); } else if(pid == 0){ while(1){ data--; printf("my Father pid = %d Child pid = %d\n",getppid(),getpid()); sleep(3); if(data == 0){ exit(3); } } } return 0;}

在这里插入图片描述

此时进程号为21311的子进程的父进程就为init了。

上一篇:IEEE期刊缩写(常见的电机控制类期刊)
下一篇:Linux系统编程——进程(三)fork函数和vfork函数的区别

发表评论

最新留言

逛到本站,mark一下
[***.202.152.39]2025年04月02日 22时33分34秒