简 述: 这篇继承上一篇,先要自己梳理清楚一下进程相关的知识, 上上一篇的虚拟地址空间和进程控制块 PCB
,以及上一篇的进程相关知识,带着思考来学习 孤儿进程、僵尸进程、 以及进程回收 wait()
, waitpid()
相关的概念。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
编程环境:
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
孤儿进程:
父进程创建子进程
父进程死了,子进程还活着,该进程叫孤儿进程(Orphan Process)
在传统的 Linux 系统中,孤儿进程被 init 进程领养,init 进程成为孤儿进程的父亲
- init 进程这样做,主要是为了释放子进程占用的系统资源。
- 因为进程结束之后,能够释放用户控空间;但是释放不了系统空间的 PCB,这块资源必须由父进程释放。
写一个代码例子:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid = fork(); //创建子进程 if (pid == 0) { //存活时间大于 1s(此时父进程已经执行完成销毁), 为孤儿子进程 sleep(1); printf("this is a child process, pid = %d ppid = %d\n", getpid(), getppid()); } else if (pid > 0) { //父进程 printf("this is a parent process, pid = %d ppid = %d\n", getpid(), getppid()); //执行完后,父进程就结束了 } return 0; }
代码分析:
当父进程创建子进程之后,很快的就执行完后就结束销毁自己(小于 1s 内);因为子进程的生存时间大于 1s,故此时它为孤儿进程(下图可证明:此时其父进程号是 1 而非 17441),然后被系统的 init 进程领养,帮助销毁子进程的 PCB;
运行结果:
僵尸进程:
父进程创建子进程
子进程死了,父进程还活着,且不去释放子进程的 PCB,该子进程就变成了僵尸进程(Zombie Process)。
僵尸进程是一个已经死掉的进程。
写一个代码例子:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid = fork(); //创建子进程 if (pid == 0) { //(此时子进程已经执行完成销毁), 而父进程没有时间去销毁这个子进程的 pcb,故为僵尸进程 printf("this is a child process, pid = %d ppid = %d\n", getpid(), getppid()); } else if (pid > 0) { //父进程 while (true) { sleep(1); printf("this is a parent process, pid = %d ppid = %d\n", getpid(), getppid()); //执行完后,父进程就结束了 } } return 0; }
代码分析:
父进程创建子进程之后,子进程在极短的时间内执行完所有代码后,其用户区的资源被释放,其系统区的 pcb 等待其父进程来释放;但是父进程一直在忙其它的时间,没有做处理,释放自己成的 PCB,在下图左侧还没有运行
ctrl c
的时候,使用ps
在管道里面查询该子进程的号 5563,可以发现其已经被标注为死亡的进程(其 Z+ 中的是单词僵尸 Zombie 的首字母)。运行结果:
进程回收:
wait():
pid_t wait(int *stat_loc);
作用:
- 阻塞并且等待子进程退出
- 回收子进程残留的资源
- 获取子进程结束的状态(退出原因)
- 每次只能够回收一个进程,调用一次 wait() 也只能回收一个进程
参数:
WIFEXITED(status)
: 为非 0 –> 进程正常退出WEXITSTATUS(status)
: 如果这个宏为真,使用此宏 –> 获取进程退出状态(exit / return) 的参数
WIFSIGNALED(status)
: 为非 0 –> 进程异常终止WTERMSIG(status)
: 如果这个宏为真,使用此宏 –> 获取使进程退出的那个信号的编号。
返回值:
- 成功:清理掉的子进程 ID
- 失败:-1(没有子进程)
写一个代码例子验证:
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char *argv[]) { printf("-----开始-----\n"); pid_t pid = fork(); //创建子进程 if (pid == 0) { //(此时子进程已经执行完成销毁), 而父进程没有时间去销毁这个子进程的 pcb,故为僵尸进程 while (true) { sleep(2); printf("this is a child process, pid = %d ppid = %d\n", getpid(), getppid()); } } else if (pid > 0) { //父进程 //sleep(1); int status = 0; pid_t wpid = wait(&status); //回收子进程 if (WIFEXITED(status)) //判断是否正常退出 printf("退出值是 val = %d\n", WEXITSTATUS(status)); if (WIFSIGNALED(status)) //判断是被信号杀死 printf("通过信号退出 val = %d\n", WTERMSIG(status)); printf("this is a parent process, pid = %d ppid = %d\n", getpid(), getppid()); //执行完后,父进程就结束了 } printf("+++++结束+++++\n"); return 9; //若是注释掉 12 和 15 行代码,会显示父父进程的 退出值是 9 }
代码分析:
代码是在 MacOS(Unix)系统上面运行的;
若是注释掉 12 和 15 行代码,则会运行第一个宏,可以看到进程结束的返回值是 9 (人为随机指定的一个数值,return 9);运行结果见下面第一个图。
而不注释掉 12 和 15 行代码;运行程序后,执行
kill 进程号
杀死子进程, 则会执行第二个宏,显示是被信号(15)所杀「若果是 Linux 运行,这里会显示信号是 9」;运行结果见下面第二个图。运行截图:
waitpid():
pid_t waitpid(pid_t pid, int *stat_loc, int options);
作用:
同 wait(),但是可以指定要回收的进程 pid 号,且能够指定是阻塞还是非阻塞模式。
参数:
pid:
pid == -1;回收所有的子进程。与 wait 等效
pid > 0; 回收指定 pid 的进程
pid == 0; 回收当前组的所有子进程
pid < -1; 回收进程的 pid 取反(加减号)
status:
子进程的退出状态,用法同 wait()
options:
设置为 WNOHANG,函数非阻塞,设置为 0,函数阻塞
返回值:
> 0 //返回清理掉的子进程 ID -1 //无子进程 =0 //参数 3 为 WHOHANG,且子进程正在运行
下载地址:
欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。