一、孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程变成
init
进程,负责子进程的回收。
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(void)
{
pid_t pid = fork();
if(pid > 0){ // 父进程输出pid并睡眠2s退出
printf("parent pid is %d\n", getpid());
sleep(2);
}
else if(pid == 0){ // 子进程循环打印pid和ppid
for(int i=0;i<10;i++){
printf("my pid is %d, my ppid is %d\n", getpid(), getppid());
sleep(1);
}
}
else {
perror("fork");
exit(1);
}
return 0;
}
运行结果:
二、僵尸进程
僵尸进程:进程终止,父进程没有回收,子进程残留的
PCB
存放在内核中,变成僵尸(zombie)进程。
僵尸进程无法用kill杀掉,因为kill只是用来终止进程的,僵尸进程已经终止。正确方法是kill掉它的父进程,如此僵尸进程就会被init
进程管理回收。
示例:
/*
我们让父进程一直循环,子进程打印出pid和ppid后就退出
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
pid_t pid = fork();
if(pid > 0){
while(1){
printf("parent pid is %d\n", getpid());
sleep(2);
}
}
else if(pid == 0){
printf("my pid is %d, my parent pid is %d\n", getpid(), getppid());
sleep(1);
}
else {
perror("fork");
exit(1);
}
return 0;
}
结果:
三、wait
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的
PCB
还保留着。内核在其中保存了一些信息:
如果是正常终止则保存着退出状态;
如果是异常终止则保存着导致该进程终止的信号是哪个。
这个进程的父进程可以调用wait
或waitpid
获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?
查看,因为Shell是它的父进程,当它终止时Shell调用wait
或waitpid
得到它的退出状态,同时彻底删除掉这个进程。
wait()的作用:
1、阻塞等待子进程结束。
2、回收子进程
pcb
资源。3、获取子进程结束状态:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
// return
// on success, returns the process ID of the terminated child;
// on error, -1 is returned.
// status 子进程退出时返回的状态,通过下面的宏函数检查状态信息。
// WIFEXITED(status) 为真 -> 子进程正常退出。
// WEXITSTATUS(status) 当子进程正常退出时,通过此函数获取退出值。
// WIFSIGNALED(status) 为真 -> 子进程异常退出,被信号终止。
// WTERMSIG(status) 当子进程异常退出时,通过此函数获取退出时收到的信号。
// 其它的宏函数:
// WCOREDUMP(status)
// WIFSTOPPED(status)
// WSTOPSIG(status)
// WIFCONTINUED(status)
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if (pid == 0)
{
printf("I am the child process, pid=%u, ppid=%u\n", getpid(), getppid());
sleep(2);
execl("./test", "test", NULL);
}
else if (pid > 0)
{
int status;
pid_t wpid;
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 exit with signal:%d \n", WTERMSIG(status));
}
printf("I am the parent process, pid=%u\n", getpid());
}
else
{
perror("fork error");
exit(-1);
}
return 0;
}
test.c
:
#include <stdio.h>
int main()
{
int a = 5;
int b = a/0;
return 25;
}
输出结果:
I am the child process, pid=19092, ppid=19091
child exit with signal:8
I am the parent process, pid=19091
test.c
:正常结束
#include <stdio.h>
int main()
{
int a = 5;
int b = a/1;
return 25;
}
输出结果:
I am the child process, pid=19903, ppid=19902
child exit with 25.
I am the parent process, pid=19902
Note:
一次wait()、waitpid()调用,只回收一个子进程。
四、waitpid
作用与wait相同,但可指定pid进程清理,可以不阻塞等待子进程结束(采用轮询方式检查子进程是否结束)。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
// 参数1: pid > 0 指定进程id回收
/*
pid > 0 指定进程id回收
pid = -1 回收任意子进程 (==wait)
pid = 0 回收本组任意子进程
pid < -1 回收该进程组的任意子进程,如-2,就回收所有进程组为2的任意子进程。
*/
// 参数2:status 同 wait()
// 参数3:options
// 0: 阻塞回收,同wait()
// WNOHANG: 非阻塞回收(轮询回收)
// 返回值:
// 成功: pid
// 失败: -1
// 参3传WNOHANG,并且子进程尚未结束: 0
示例:创建5个子进程,并回收
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid, wpid;
int n = 5;
int i;
for (i=0; i < n; i++)
{
// 创建n个子进程
pid = fork();
if (pid == 0)
{
break;
}
}
if (n == i)
{
// 父进程
do {
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid > 0)
{
n--;
printf("recycle child process:%u\n", wpid);
}
sleep(1);
} while(n > 0);
while(1)
{
printf("I am the parent process, pid:%u\n", getpid());
sleep(1);
}
}
else
{
// 子进程等待一段时间
printf("I am the %dth child process, pid=%u, ppid=%u\n", getpid(), getppid());
sleep(i);
}
return 0;
}