孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程称为init进程,称为init进程领养孤儿进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == 0) {
while (1) {
printf("I am child, my parent pid = %d\n", getppid());
sleep(1);
}
} else if (pid > 0) {
printf("I am parent, my pid is = %d\n", getpid());
sleep(9);
printf("------------parent going to die------------\n");
} else {
perror("fork");
return 1;
}
return 0;
}
僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
特别注意:僵尸进程是不能使用kill命令清除掉得,因为kill命令只是用来中止进程的,而僵尸进程已经终止。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
pid = fork();
if (pid == 0) {
printf("---child, my parent= %d, going to sleep 10s\n", getppid());
sleep(10);
printf("-------------child die--------------\n");
} else if (pid > 0) {
while (1) {
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
} else {
perror("fork");
return 1;
}
return 0;
}
wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是那个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
- 阻塞等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
pid_t wait(int *status)
成功:返回清理掉的子进程ID;
失败:返回-1(没有子进程)
当进程终止时,操作系统的隐式回收机制会:
- 关闭所有文件描述符
- 释放用户空间分配的内存。内核的PCB仍存在,其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可氛围如下三组:
- WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数) - WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。 - WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
int status;
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{ //son
printf("I'm process child, pid = %d\n", getpid());
#if 1
execl("./abnor", "abnor", NULL);
perror("execl error");
exit(1);
#endif
sleep(1);
exit(10);
}
else
{
//wpid = wait(NULL); //传出参数
wpid = wait(&status); //传出参数
if(WIFEXITED(status)){ //正常退出
printf("I'm parent, The child process "
"%d exit normally\n", wpid);
printf("return value:%d\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status))
{ //异常退出
printf("The child process exit abnormally, "
"killed by signal %d\n", WTERMSIG(status));
//获取信号编号
}
else
{
printf("other...\n");
}
}
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调用只能清理一个子进程,清理多个子进程应使用循环。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, pid2, wpid;
int flg = 0;
pid = fork();
pid2 = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{ //son
printf("I'm process child, pid = %d\n", getpid());
sleep(5);
exit(4);
}
else
{ //parent
do
{
wpid = waitpid(pid, NULL, WNOHANG);
//wpid = wait(NULL);
printf("---wpid = %d--------%d\n", wpid, flg++);
if(wpid == 0)
{
printf("NO child exited\n");
sleep(1);
}
} while (wpid == 0); //子进程不可回收
if(wpid == pid)
{ //回收了指定子进程
printf("I'm parent, I catched child process,"
"pid = %d\n", wpid);
}
else
{
printf("other...\n");
}
}
return 0;
}