在本篇博客中进行孤儿进程和僵尸进程的代码示例中都涉及到了pause()
函数和signal
函数,signal
函数主要功能是将信号与函数指针绑定在一起,当进程执行到pause
时,如果接受到该信号就会执行相对应的signal
函数中绑定的相关代码,如果没有接受到该信号,就不会执行。如果想要了解更多关于pause
的知识,可以查看这篇博文:linux系统编程:暂停函数pause
接下来让我们学习孤儿进程和僵尸进程的相关知识。
孤儿进程
当父进程退出后,子进程还没有结束时,这时的子进程我们称之为孤儿进程。系统会将孤儿进程挂在最早的进程或它的祖父进程上,这种进程没有危害,不会造成资源泄漏。
下文中我们给出的代码就生成了一个孤儿进程,其中涉及到关于fork
函数的知识,我们可以直接简单理解为该函数可以创建一个子进程,该函数在父进程中的返回值为1,在子进程中的返回值为0。所以父进程在获取pid
之后执行最后一个cout
语句就会退出,但子进程会进入if
执行pause
暂停运行。所以该子进程就会成为一个孤儿进程。
关于fork
的具体知识,可以看这篇博文:linux系统编程:分叉函数fork
具体代码如下:
#include <iostream>
#include <signal.h> // 关于信号的头文件
#include <unistd.h>
#include <wait.h>
using namespace std;
int main(){
signal(SIGINT,[](int sig){cout << "over pause" << endl;});
pid_t id = fork();
if(id == 0){
cout << getpid() << ":before pause" << endl;
pause();
cout << getpid() << ":after pause" << endl;
}
cout << getpid() << ":exit" << endl;
}
执行结果如下:
使用ps -ef
查询进程具体信息,找到子进程16532的相关信息如下:
该子进程的父进程已经不是16531,而是2308。我们可以使用kill 16532
杀死该进程。
僵尸进程
在子进程运行结束退出侯,由于父进程无法获取子进程状态信息,从而难以回收子进程资源,这种子进程成为僵尸进程。这种僵尸进程占用系统资源,会造成资源泄漏,而且没有办法杀死,只能在杀死父进程时才能杀死僵尸进程。
所以子进程在获取pid
之后会直接执行最后一个cout
语句就会退出,但父进程会进入if
执行pause
暂停运行。所以该子进程就会成为一个僵尸进程。
具体代码如下:
#include <iostream>
#include <signal.h> // 关于信号的头文件
#include <unistd.h>
#include <wait.h>
using namespace std;
int main(){
signal(SIGINT,[](int sig){cout << "over pause" << endl;});
pid_t id = fork();
if(id != 0){
cout << getpid() << ":before pause" << endl;
pause();
cout << getpid() << ":after pause" << endl;
}
cout << getpid() << ":exit" << endl;
}
执行结果如下:
如何避免僵尸进程的出现
wait()
函数作用:
- 子进程没有退出,父进程等待
- 父进程回收子进程资源
#include <iostream>
#include <signal.h> // 关于信号的头文件
#include <unistd.h>
#include <wait.h>
using namespace std;
int main(){
signal(SIGINT,[](int sig){cout << "over pause" << endl;});
pid_t id = fork();
if(id != 0){
cout << getpid() << ":before pause" << endl;
wait(NULL); // 等待子进程退出
cout << getpid() << ":after pause" << endl;
}else{
cout << getpid() << ":pause" << endl;
pause();
}
cout << getpid() << ":exit" << endl;
}
执行结果如下:
避免出现僵尸进程的优化处理
但是上述这种用法使得父进程必须要等待子进程退出才能继续进行处理,父进程在等待的时间内无法进行任何操作。我们可以让子进程注册信号,让子进程退出的时候通知父进程,父进程获取子进程状态直接进行销毁回收资源。
我们在子进程处理的过程中给出一个中断,让父进程处理事务,父进程处理结束后,子进程如果还没有收到绑定的信号,就会变成孤儿进程。如果子进程等待到绑定的信号,父进程将也会受到该信号,将会回收子进程资源。
相关代码如下:
#include <iostream>
#include <signal.h> // 关于信号的头文件
#include <unistd.h>
#include <wait.h>
using namespace std;
int main(){
signal(SIGINT,[](int sig){cout << getpid() << "over pause" << endl;});
signal(SIGCHLD,[](int sig){
pid_t id = wait(NULL);
cout << "wait" << id << endl;
});
pid_t id = fork();
if(id != 0){
cout << getpid() << ":before sleep" << endl;
sleep(10); // 在这个时间里面父进程可以处理一些其他的事情
cout << getpid() << ":after sleep" << endl;
}else{
cout << getpid() << ":pause" << endl;
pause();
}
cout << getpid() << ":exit" << endl;
}
运行结果有三种:
-
Ctrl+Z信号如下:
父进程子进程都收到该信号后,都会运行至wait
函数处,这时使用fg
命令将打入后台的程序调回到前台继续运行,由于两者都还在wait
函数处等待,我们Ctrl+C
,使得父子进程都收到该信号,执行该信号绑定的函数指针中的代码,子进程将会继续执行后续代码指导退出。父进程收到子进程退出的信号之后,完成wait函数,继续执行后续操作。 -
直接收到
Ctrl+C
信号:
父子进程接受到该信号后,执行绑定操作,子进程继续执行,父进程执行sleep
操作,直至父进程退出,子进程 -
父进程将自己的操作全部执行结束
父进程已经退出,子进程还会处于pause
,这时的子进程成为了孤儿进程,不会造成资源泄漏。