回收子进程
孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init 进程,称为 init进程(进程ID为1)领养孤儿进程
【验证孤儿进程】
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid > 0)
{
sleep(1);
printf("I am parent, pid = %u, ParentID = %u\n", getpid(), getppid());
}
else
{
printf("I am child, parentID = %u\n", getppid());
sleep(5);
printf("I am child, parentID = %u\n", getppid());
}
return 0;
}
【运行结果】
查看一下1487是什么进程?
【结果分析】
孤儿进程被 1号进程init 收养,但是这里为什么孤儿进程被1487进程收养了呢?重复执行几次都是这个结果,每次都是2197,并不是书上说的 1 。看来2197也是个特殊的进程。 我们用 ps 查看一下这个进程: 它对应的/sbin/upstart ,这是个什么鬼? 于是求助度娘得到答案:原来 upstart 是init 演进来的,可以说是一种新型的 init系统,传统的 sysvinit 已经淡出历史舞台,新系统 UpStart 和 systemd 各有特点,而越来越多的 Linux 发行版采纳了 systemd。 ubuntu 较新的发行版都用 upstart 代替 init 来收养孤儿进程。
僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程
【代码】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
{
printf("---child, parentID = %u, going to sleep 10s\n", getppid());
sleep(10);
printf("---child, I am die\n");
}
else
{
while(1)
{
printf("I am parent, ID = %u", getpid());
sleep(1);
}
}
return 0;
}
【运行结果】
【注意】僵尸进程是不能用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止
【思考】有什么办法可以清除掉僵尸进程呢?
【答案】父进程在子进程结束后将其回收; 或者将其父亲杀死,让其变成孤儿进程,然后由init进程领养,init进程发现它是一个僵尸进程,自然会将它回收
wait函数
- 一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一写信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量 $? 查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程
- 父进程调用wait函数可以回收子进程终止信息。该函数有三个功能
- 阻塞等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
pid_t wait(int *status);
【返回值】成功:清理掉的子进程ID;失败:-1(没有子进程)
- 当进程终止时,os的隐式回收机制会:
- 关闭所有的文件描述符
- 释放用户空间分配的内存。内核的PCB仍存在。其中保存着该进程的退出状态。(正常终止->退出值;异常终止->终止信号)
- 可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可以分为如下三组:
1. WIFEXITED(status) 为非0 -> 进程正常结束
W IF EXITED
WEXITSTATUS(status) 如上宏为真,使用此宏 -> 获取进程退出状态(exit参数)
2. WIFSIGNALED(status) 为非0 -> 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 -> 取得使进程终止的那个信号的编号
*3. WIFSTOPPED(status) 为非0 -> 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 -> 取得使进程暂停的那个信号的编号
WIFCONTINUED(status) 为真 -> 进程暂停后已经继续运行
【代码】
//子进程正常终止
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
pid_t pid, wpid;
pid = fork();
int status;
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
{
printf("---child, parentID = %u, going to sleep 1s\n", getppid());
sleep(1);
printf("---child, I am die\n");
exit(2);
}
else
{
wpid = wait(&status);
if(wpid == -1) //判断wait返回值是不是子进程ID,是则回收成功
{
perror("wait error\n");
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, ID = %u\n", getpid());
sleep(1);
}
}
return 0;
}
【执行结果】没有僵尸进程存在
【练习】使用kill将子进程杀死,查看status状态
//子进程异常终止
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
pid_t pid, wpid;
pid = fork();
int status;
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
{
printf("---child, parentID = %u, going to sleep 1s\n", getppid());
sleep(20);
printf("---child, I am die\n");
}
else
{
wpid = wait(&status);
if(wpid == -1) //判断wait返回值是不是子进程ID,是则回收成功
{
perror("wait error\n");
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, ID = %u\n", getpid());
sleep(1);
}
}
return 0;
}
【运行结果】
waitpid 函数
作用同wait,但可指定pid进程清理,可以不阻塞
pid_t waitpid(pid_t pid, int *status, int options);
【返回值】成功:返回清理掉的子进程ID;失败:-1(无子进程)
【特殊参数和返回情况】
参数 pid:
> 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参数三为 WNOHANG(非阻塞),且子进程正在运行
【注意】一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环
【练习一】创建3个子进程,调用一次waitpid函数,发现只回收一个子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int i;
int status;
for(i = 0; i < 3; i++)
{
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
break;
}
if(i == 3)
{
sleep(i);
printf("I am parent\n");
waitpid(pid, &status, WNOHANG);
//while();
if(WIFEXITED(status))
{
printf("child exit with %d\n", WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("child exit with %d\n", WTERMSIG(status));
}
while(1);
}
else
{
sleep(i);
printf("I am %dth child, pid = %d\n", i + 1, getpid());
}
}
【运行结果】父进程只回收了进程号为29869的子进程,而且父进程回收哪个子进程是不确定的
【练习2】使用waitpid阻塞回收全部子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int i;
int status;
for(i = 0; i < 3; i++)
{
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
break;
}
if(i == 3)
{
sleep(i);
printf("I am parent\n");
while(waitpid(-1, &status, 0)); //== wait(NULL)
while(1);
}
else
{
sleep(i);
printf("I am %dth child, pid = %d\n", i + 1, getpid());
}
}
【运行结果】没有存在僵尸进程
【练习3】使用waitpid非阻塞回收全部子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
pid_t pid, wpid;
int i;
int n = 3;
int status;
for(i = 0; i < 3; i++)
{
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
break;
}
if(i == 3)
{
sleep(i);
printf("I am parent\n");
do{
//非阻塞回收全部子进程
wpid = waitpid(-1, &status, WNOHANG);
if(wpid > 0) //回收一个子进程成功
{
n--;
}
// if wpid = 0 说明子进程正在运行
sleep(1);
}while(n > 0);
printf("wait finish\n");
}
else
{
sleep(i);
printf("I am %dth child, pid = %d\n", i + 1, getpid());
}
}
【运行结果】
【练习】使用waitpid回收指定子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main()
{
pid_t pid, wpid;
int i;
int n = 3;
int status;
for(i = 0; i < 3; i++)
{
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
break;
else
{
if(i == 1)
{
wpid = pid;
}
}
}
if(i == 3)
{
sleep(i);
printf("I am parent\n");
waitpid(wpid, &status, 0);
while(1);
}
else
{
sleep(i);
printf("I am %dth child, pid = %d\n", i + 1, getpid());
}
}
【运行结果】