进程退出1.exit函数(1) 头文件#includestdlib.h(2) 函数定义voidexit(intstatus);参数status是进程退出状态码分两层含义给操作系统 / 父进程看父进程通过wait()/waitpid()可以拿到这个状态值判断子进程是正常退出还是异常崩溃。有效范围系统只会保留status的低 8 位0~255传大于 255 的数字会自动截断。exit(0)正常、成功退出exit(非0)异常、出错退出约定俗成返回值无返回值。作用安全、完整地终止当前进程自动完成 IO 刷新、自定义回调、文件关闭并向操作系统传递退出状态码供父进程回收判断运行结果。(3)exit函数的执行流程调用exit()不会立刻销毁进程会按顺序完成清理工作执行所有注册的终止钩子函数用atexit()/on_exit()提前注册的回调函数会按逆序依次执行。刷新标准 IO 缓冲区自动fflush(stdout/stderr/stdin)把缓冲区里未打印的文字输出到屏幕 / 文件对比_exit()_exit直接退出不刷缓冲区容易丢失输出。关闭进程打开的所有文件描述符所有open()、fopen()打开的文件、管道、套接字全部自动关闭。将退出状态 status 交给内核进程进入僵尸进程状态PCB 保留存放退出码等待父进程 wait 回收。释放用户态内存、销毁进程进入僵尸态。2._exit函数(1) 头文件#include unistd.h(2) 函数定义void _exit(int status);参数作用和exit完全一致仅低 8 位有效父进程wait可获取退出码。返回值无调用后进程直接终止不会返回原代码。(3)_exit执行步骤直接进内核跳过所有用户态清理直接跳过atexit回调、IO 缓冲区刷新、FILE 流清理。内核层操作关闭所有文件描述符fd、释放用户内存。将status存入 PCB进程变为僵尸进程等待父进程回收。直接终止进程。3. 样例(1)exit函数创建一个 exit.c 文件#includestdio.h#includeunistd.h#includestdlib.hintmain(){printf(hello\n);printf(linux);exit(0);return0;}编译并运行 exit.cgcc exit.c-oexit./exit这里可以看到两个printf的内容都被正常输出。(2)_exit函数创建 _exit.c 文件#includestdio.h#includeunistd.h#includestdlib.hintmain(){printf(hello\n);printf(linux);_exit(0);return0;}编译并运行 _exit.cgcc _exit.c-o_exit ./_exit程序输出只打印 hello、不打印 linux 原因Linux 终端下标准输出 stdout 默认是行缓冲策略printf(hello\n)包含换行符\n触发行缓冲自动刷新hello 立刻输出到终端printf(linux)没有换行数据仅暂存在stdout用户缓冲区没有刷到内核_exit()是底层系统调用作用是直接终止进程不会执行标准 IO 库的缓冲区刷新、流关闭等清理操作进程销毁后缓冲区残留的linux数据直接丢失因此无法打印。孤儿进程1. 孤儿进程定义当父进程先于子进程退出而子进程仍在正常运行时这个失去原生父进程的子进程就被称作孤儿进程Orphan Process。2. 内核处理机制一旦父进程提前消亡Linux 内核会自动完成一步操作将该孤儿进程的父进程 PID 重新设置为1 号进程系统初始化进程。CentOS 6 / 传统 SysV 系统1 号进程是initUbuntu、CentOS7、Debian 等现代发行版1 号进程是systemd它兼容传统init的回收逻辑。1 号系统进程会持续循环调用wait()/waitpid()去监听它的所有子进程状态。当这个孤儿进程后续运行结束、正常退出时1 号进程会自动回收它的 PCB 资源、释放僵尸进程残留全权完成进程退出后的资源善后清理工作。3. 孤儿进程的特性与影响无资源泄漏风险因为有 1 号进程兜底回收退出状态孤儿进程结束后不会变成僵尸进程不会长期占用进程表资源对系统无危害。典型使用场景很多后台守护程序、服务进程会主动制造孤儿进程父进程创建子进程后立刻退出让子进程由 systemd/init 接管脱离终端独立后台运行。4. 样例创建一个 orphan.c 文件#includestdio.h#includeunistd.h#includesys/types.hintmain(){pid_tpidfork();if(pid0){printf(I am parent progress, pid: %d, ppid: %d\n,getpid(),getppid());}elseif(pid0){sleep(1);printf(I am child progress, pid: %d, ppid: %d\n,getpid(),getppid());}else{perror(fork);return-1;}for(inti0;i3;i){printf(i %d\n,i);}return0;}编译并执行 orphan.c由于代码中使用了sleep(1)让子进程延迟执行保证父进程先结束父进程消亡后子进程的 ppid 自动变为 1systemd/init 一号进程此时子进程成为孤儿进程由 1 号进程接管回收资源。终端输出现象分析即便父进程提前退出孤儿子进程依旧可以向原终端打印内容原理如下1调用fork()创建子进程时基于读时共享写时复制机制复制父进程地址空间同时完整继承父进程全部打开的文件描述符其中包含绑定当前终端的标准输出 fdstdout2内核为每个打开的设备文件维护引用计数父进程退出仅释放自身持有的文件引用子进程保留有效的终端文件描述符因此只要子进程不主动关闭标准输出就能持续向终端输出数据不受父进程生命周期影响。僵尸进程1. 僵尸进程标准定义在 Linux 进程模型中子进程先于父进程终止父进程没有调用wait()/waitpid()系统调用去获取子进程退出状态、释放内核进程控制块 PCB此时该子进程仅保留内核 PCB 残留、用户空间资源已全部释放这种已死亡但资源未回收的进程称为僵尸进程Zombie Process。2. 内核处理机制进程退出时内核资源分离释放规则任何进程调用exit()终止时内核会立刻回收该进程用户地址空间、文件缓冲区、栈、堆等用户层资源但存放进程 PID、退出码、运行统计信息的PCB进程控制块不会自动销毁内核会将该 PCB 挂在父进程的子进程链表上保留。内核通知机制SIGCHLD 信号子进程变为僵尸瞬间内核会向父进程发送SIGCHLD信号告知父进程有子进程已退出提示父进程执行wait回收 PCB。若父进程默认忽略SIGCHLD僵尸进程会持续驻留内核若父进程捕获SIGCHLD并调用waitpid内核立刻销毁 PCB清除僵尸。kill 信号对僵尸进程的内核处理逻辑kill发送的信号只能作用于处于运行 / 睡眠状态的活跃进程僵尸进程已经完全终止无代码执行上下文内核不会对僵尸进程响应任何信号kill -9也无法删除内核中的 PCB 结构。3. 僵尸进程特性资源占用两极分化不占用 CPU、不占用用户内存用户层资源全部释放持续占用内核资源独占一块 PCB 内存、永久占用一个唯一 PID。无法被任何信号杀死僵尸进程已经死亡不存在可调度的执行流kill -9、kill -15等所有信号都无法清除它只能靠父进程回收或父进程退出。进程状态标识使用ps aux查看进程时僵尸进程的状态标记为Zzombie命令列会显示[defunct]。仅能被亲生父进程回收只有创建它的父进程调用wait/waitpid内核才会释放该僵尸进程的 PCB其他进程无权限回收别人的僵尸子进程。4. 僵尸进程的影响若仅存在几个僵尸进程占用的内核内存极小不会对系统运行产生感知影响但属于代码漏洞隐患。但 Linux 系统全局可用 PID 存在上限默认 32768大量持续生成僵尸进程会不断占用 PID当 PID 全部被占满后fork()创建新进程会直接失败系统无法启动任何新程序、服务、命令直接瘫痪。5. 样例创建一个 zombie.c 文件#includestdio.h#includeunistd.h#includesys/types.hintmain(){pid_tpidfork();if(pid0){while(1){sleep(3);printf(I am parent progress, pid: %d, ppid: %d\n,getpid(),getppid());}}elseif(pid0){printf(I am child progress, pid: %d, ppid: %d\n,getpid(),getppid());}else{perror(fork);return-1;}return0;}编译并运行 zombie.cgcc zombie.c-ozombie ./zombie复制当前会话终端在复制的第二个终端内查看进程状态psaux通过终端输出可以看到子进程已经变成了僵尸进程。尝试杀死僵尸进程并查看进程状态kill4933psaux会发现僵尸进程并没有被成功杀死。子进程控制所有这些系统调用都用于等待调用进程的子进程状态发生变化并获取状态已改变的子进程的相关信息。状态变化被认为是子进程终止子进程被信号停止或者子进程被信号恢复。对于已终止的子进程执行等待操作可使系统释放与该子进程相关的资源如果不执行等待操作那么已终止的子进程将保持僵尸状态。如果子进程的状态已经发生变化那么这些调用会立即返回。否则它们会阻塞直到子进程状态发生变化或者信号处理程序中断调用。1.wait函数(1) 头文件#includesys/types.h#includesys/wait.h(2) 函数原型pid_twait(int*wstatus);参数存储进程退出时的状态信息可以作为传出参数函数会修改其指向的内存。状态信息获取:通过指针操作获取⼦进程退出状态。可使⽤宏如WIFEXITED等解析状态值。NULL处理若不需要状态信息可传⼊NULL。返回值成功返回终止的子进程的进程 id 。失败返回 -1 。作用⽤于等待进程状态改变包括⼦进程终⽌、被信号停⽌或恢复并获取状态已改变的子进程的相关信息。(3) 退出信息相关的宏函数WIFEXITED(status)判断进程是否正常退出⾮0表示正常退出WEXITSTATUS(status)若WIFEXITED为真获取进程退出状态即exit的参数值WIFSIGNALED(status)判断进程是否异常终⽌⾮0表示被信号杀死WTERMSIG(status)若WIFSIGNALED为真获取导致终⽌的信号编号WIFSTOPPED(status)判断进程是否处于暂停状态WSTOPSIG(status)若WIFSTOPPED为真获取导致暂停的信号编号WIFCONTINUED(status)判断进程是否从暂停状态恢复运⾏2.wait函数样例(1) 子进程被信号杀死创建一个 wait.c 文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){printf(我是父进程, pid: %d\n,getpid());intst;intretwait(st);if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}if(ret-1){printf(进程全部结束, 返回值: %d\n,ret);break;}}}elseif(pid0)// 子进程{while(1){printf(我是子进程, pid: %d\n,getpid());sleep(3);}exit(0);}else{perror(fork);exit(-1);}return0;}编译并运行 wait.cgcc wait.c-owait./wait可以看到终端显示了子进程被 9 号信号杀死。(2) 子进程正常退出略微修改上面的文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){printf(我是父进程, pid: %d\n,getpid());intst;intretwait(st);if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}if(ret-1){printf(进程全部结束, 返回值: %d\n,ret);break;}}}elseif(pid0)// 子进程{//while(1)//{printf(我是子进程, pid: %d\n,getpid());//sleep(3);//}exit(-1);}else{perror(fork);exit(-1);}return0;}重新编译并运行 wait.cgcc wait.c-owait./wait解释一下这里的状态码 255 :c 语言的int类型是 32 位代码中对于子进程正常退出写的是exit(-1)那么 -1 的二进制原码为10000000 00000000 00000000 00000001-1 的二进制补码为11111111 11111111 11111111 11111111但是WEXITSTATUS(wstatus)只会取状态码的最低八位且为无符号整数退出码标准约定范围是 0~255只有非负错误码不存在负退出码也就是11111111255。3.waitpid函数相对于wait函数两者都用于回收子进程资源。主要区别wait只能回收任意子进程waitpid可指定特定子进程waitpid提供更多控制选项如WNOHANG非阻塞模式waitpid可以回收指定进程组的子进程(1) 头文件#includesys/types.h#includesys/wait.h(2) 函数原型pid_twaitpid(pid_tpid,int*wstatus,intoptions);参数pid指定要等待的子进程。pid 0等待回收进程 ID 等于该pid值的子进程。pid 0等待回收当前进程组的所有子进程。pid -1等待回收所有子进程 (wait(wstatus)等于waitpid(-1, wstatus, 0))。pid -1等待回收进程组 ID 等于pid值的所有子进程。wstatus子进程的退出状态。存储进程退出时的状态信息可以作为传出参数函数会修改其指向的内存。状态信息获取:通过指针操作获取⼦进程退出状态。可使⽤宏如WIFEXITED等解析状态值。NULL处理若不需要状态信息可传⼊NULL。options控制函数行为选项该参数可由以下一个或多个常量通过按位或运算组合而成也可直接传 0不开启任何特殊选项WNOHANG若无任何子进程退出函数立刻返回不会阻塞等待。WUNTRACED若子进程被暂停未通过 ptrace() 调试追踪也会触发函数返回。对于被 ptrace() 追踪、而后暂停的子进程即便不设置该选项也会返回其状态。WCONTINUEDLinux 2.6.10 版本起支持若一个先前被暂停的子进程收到 SIGCONT 信号恢复运行函数也会返回。返回值返回值 0返回被回收的子进程 PID。返回值 0 (仅当optionsWNOHANG非阻塞模式时生效)当前检查时刻没有子进程结束退出但还有存活子进程立刻返回 0、不阻塞父进程。返回值 -1出错pid 非法、权限不足等。当前没有任何子进程可以回收。当所有子进程都已结束时也会返回 -1 。作用与wait函数相比可以回收指定进程号的子进程还可以设置是否阻塞。4.waitpid函数样例(1) 子进程被信号杀死创建一个 waitpid.c 文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){sleep(1);printf(我是父进程, pid: %d\n,getpid());intst;intretwaitpid(-1,st,WNOHANG);// 非阻塞等待回收所有子进程if(ret0){if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}}elseif(ret0){printf(当前还有子进程未终止\n);}elseif(ret-1){perror(waitpid);break;}}}elseif(pid0)// 子进程{while(1){printf(我是子进程, pid: %d\n,getpid());sleep(3);}exit(-1);}else{perror(fork);exit(-1);}return0;}编译并运行 waitpid.cgcc waitpid.c-owaitpid ./waitpid(2) 子进程正常退出稍微修改一下上面的 waitpid.c 文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){sleep(1);printf(我是父进程, pid: %d\n,getpid());intst;intretwaitpid(-1,st,WNOHANG);// 非阻塞等待回收所有子进程if(ret0){if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}}elseif(ret0){printf(当前还有子进程未终止\n);}elseif(ret-1){perror(waitpid);break;}}}elseif(pid0)// 子进程{//while(1)//{printf(我是子进程, pid: %d\n,getpid());//sleep(3);//}exit(-1);}else{perror(fork);exit(-1);}return0;}再次编译并运行 waitpid.cgcc waitpid.c-owaitpid ./waitpid
五、进程控制
进程退出1.exit函数(1) 头文件#includestdlib.h(2) 函数定义voidexit(intstatus);参数status是进程退出状态码分两层含义给操作系统 / 父进程看父进程通过wait()/waitpid()可以拿到这个状态值判断子进程是正常退出还是异常崩溃。有效范围系统只会保留status的低 8 位0~255传大于 255 的数字会自动截断。exit(0)正常、成功退出exit(非0)异常、出错退出约定俗成返回值无返回值。作用安全、完整地终止当前进程自动完成 IO 刷新、自定义回调、文件关闭并向操作系统传递退出状态码供父进程回收判断运行结果。(3)exit函数的执行流程调用exit()不会立刻销毁进程会按顺序完成清理工作执行所有注册的终止钩子函数用atexit()/on_exit()提前注册的回调函数会按逆序依次执行。刷新标准 IO 缓冲区自动fflush(stdout/stderr/stdin)把缓冲区里未打印的文字输出到屏幕 / 文件对比_exit()_exit直接退出不刷缓冲区容易丢失输出。关闭进程打开的所有文件描述符所有open()、fopen()打开的文件、管道、套接字全部自动关闭。将退出状态 status 交给内核进程进入僵尸进程状态PCB 保留存放退出码等待父进程 wait 回收。释放用户态内存、销毁进程进入僵尸态。2._exit函数(1) 头文件#include unistd.h(2) 函数定义void _exit(int status);参数作用和exit完全一致仅低 8 位有效父进程wait可获取退出码。返回值无调用后进程直接终止不会返回原代码。(3)_exit执行步骤直接进内核跳过所有用户态清理直接跳过atexit回调、IO 缓冲区刷新、FILE 流清理。内核层操作关闭所有文件描述符fd、释放用户内存。将status存入 PCB进程变为僵尸进程等待父进程回收。直接终止进程。3. 样例(1)exit函数创建一个 exit.c 文件#includestdio.h#includeunistd.h#includestdlib.hintmain(){printf(hello\n);printf(linux);exit(0);return0;}编译并运行 exit.cgcc exit.c-oexit./exit这里可以看到两个printf的内容都被正常输出。(2)_exit函数创建 _exit.c 文件#includestdio.h#includeunistd.h#includestdlib.hintmain(){printf(hello\n);printf(linux);_exit(0);return0;}编译并运行 _exit.cgcc _exit.c-o_exit ./_exit程序输出只打印 hello、不打印 linux 原因Linux 终端下标准输出 stdout 默认是行缓冲策略printf(hello\n)包含换行符\n触发行缓冲自动刷新hello 立刻输出到终端printf(linux)没有换行数据仅暂存在stdout用户缓冲区没有刷到内核_exit()是底层系统调用作用是直接终止进程不会执行标准 IO 库的缓冲区刷新、流关闭等清理操作进程销毁后缓冲区残留的linux数据直接丢失因此无法打印。孤儿进程1. 孤儿进程定义当父进程先于子进程退出而子进程仍在正常运行时这个失去原生父进程的子进程就被称作孤儿进程Orphan Process。2. 内核处理机制一旦父进程提前消亡Linux 内核会自动完成一步操作将该孤儿进程的父进程 PID 重新设置为1 号进程系统初始化进程。CentOS 6 / 传统 SysV 系统1 号进程是initUbuntu、CentOS7、Debian 等现代发行版1 号进程是systemd它兼容传统init的回收逻辑。1 号系统进程会持续循环调用wait()/waitpid()去监听它的所有子进程状态。当这个孤儿进程后续运行结束、正常退出时1 号进程会自动回收它的 PCB 资源、释放僵尸进程残留全权完成进程退出后的资源善后清理工作。3. 孤儿进程的特性与影响无资源泄漏风险因为有 1 号进程兜底回收退出状态孤儿进程结束后不会变成僵尸进程不会长期占用进程表资源对系统无危害。典型使用场景很多后台守护程序、服务进程会主动制造孤儿进程父进程创建子进程后立刻退出让子进程由 systemd/init 接管脱离终端独立后台运行。4. 样例创建一个 orphan.c 文件#includestdio.h#includeunistd.h#includesys/types.hintmain(){pid_tpidfork();if(pid0){printf(I am parent progress, pid: %d, ppid: %d\n,getpid(),getppid());}elseif(pid0){sleep(1);printf(I am child progress, pid: %d, ppid: %d\n,getpid(),getppid());}else{perror(fork);return-1;}for(inti0;i3;i){printf(i %d\n,i);}return0;}编译并执行 orphan.c由于代码中使用了sleep(1)让子进程延迟执行保证父进程先结束父进程消亡后子进程的 ppid 自动变为 1systemd/init 一号进程此时子进程成为孤儿进程由 1 号进程接管回收资源。终端输出现象分析即便父进程提前退出孤儿子进程依旧可以向原终端打印内容原理如下1调用fork()创建子进程时基于读时共享写时复制机制复制父进程地址空间同时完整继承父进程全部打开的文件描述符其中包含绑定当前终端的标准输出 fdstdout2内核为每个打开的设备文件维护引用计数父进程退出仅释放自身持有的文件引用子进程保留有效的终端文件描述符因此只要子进程不主动关闭标准输出就能持续向终端输出数据不受父进程生命周期影响。僵尸进程1. 僵尸进程标准定义在 Linux 进程模型中子进程先于父进程终止父进程没有调用wait()/waitpid()系统调用去获取子进程退出状态、释放内核进程控制块 PCB此时该子进程仅保留内核 PCB 残留、用户空间资源已全部释放这种已死亡但资源未回收的进程称为僵尸进程Zombie Process。2. 内核处理机制进程退出时内核资源分离释放规则任何进程调用exit()终止时内核会立刻回收该进程用户地址空间、文件缓冲区、栈、堆等用户层资源但存放进程 PID、退出码、运行统计信息的PCB进程控制块不会自动销毁内核会将该 PCB 挂在父进程的子进程链表上保留。内核通知机制SIGCHLD 信号子进程变为僵尸瞬间内核会向父进程发送SIGCHLD信号告知父进程有子进程已退出提示父进程执行wait回收 PCB。若父进程默认忽略SIGCHLD僵尸进程会持续驻留内核若父进程捕获SIGCHLD并调用waitpid内核立刻销毁 PCB清除僵尸。kill 信号对僵尸进程的内核处理逻辑kill发送的信号只能作用于处于运行 / 睡眠状态的活跃进程僵尸进程已经完全终止无代码执行上下文内核不会对僵尸进程响应任何信号kill -9也无法删除内核中的 PCB 结构。3. 僵尸进程特性资源占用两极分化不占用 CPU、不占用用户内存用户层资源全部释放持续占用内核资源独占一块 PCB 内存、永久占用一个唯一 PID。无法被任何信号杀死僵尸进程已经死亡不存在可调度的执行流kill -9、kill -15等所有信号都无法清除它只能靠父进程回收或父进程退出。进程状态标识使用ps aux查看进程时僵尸进程的状态标记为Zzombie命令列会显示[defunct]。仅能被亲生父进程回收只有创建它的父进程调用wait/waitpid内核才会释放该僵尸进程的 PCB其他进程无权限回收别人的僵尸子进程。4. 僵尸进程的影响若仅存在几个僵尸进程占用的内核内存极小不会对系统运行产生感知影响但属于代码漏洞隐患。但 Linux 系统全局可用 PID 存在上限默认 32768大量持续生成僵尸进程会不断占用 PID当 PID 全部被占满后fork()创建新进程会直接失败系统无法启动任何新程序、服务、命令直接瘫痪。5. 样例创建一个 zombie.c 文件#includestdio.h#includeunistd.h#includesys/types.hintmain(){pid_tpidfork();if(pid0){while(1){sleep(3);printf(I am parent progress, pid: %d, ppid: %d\n,getpid(),getppid());}}elseif(pid0){printf(I am child progress, pid: %d, ppid: %d\n,getpid(),getppid());}else{perror(fork);return-1;}return0;}编译并运行 zombie.cgcc zombie.c-ozombie ./zombie复制当前会话终端在复制的第二个终端内查看进程状态psaux通过终端输出可以看到子进程已经变成了僵尸进程。尝试杀死僵尸进程并查看进程状态kill4933psaux会发现僵尸进程并没有被成功杀死。子进程控制所有这些系统调用都用于等待调用进程的子进程状态发生变化并获取状态已改变的子进程的相关信息。状态变化被认为是子进程终止子进程被信号停止或者子进程被信号恢复。对于已终止的子进程执行等待操作可使系统释放与该子进程相关的资源如果不执行等待操作那么已终止的子进程将保持僵尸状态。如果子进程的状态已经发生变化那么这些调用会立即返回。否则它们会阻塞直到子进程状态发生变化或者信号处理程序中断调用。1.wait函数(1) 头文件#includesys/types.h#includesys/wait.h(2) 函数原型pid_twait(int*wstatus);参数存储进程退出时的状态信息可以作为传出参数函数会修改其指向的内存。状态信息获取:通过指针操作获取⼦进程退出状态。可使⽤宏如WIFEXITED等解析状态值。NULL处理若不需要状态信息可传⼊NULL。返回值成功返回终止的子进程的进程 id 。失败返回 -1 。作用⽤于等待进程状态改变包括⼦进程终⽌、被信号停⽌或恢复并获取状态已改变的子进程的相关信息。(3) 退出信息相关的宏函数WIFEXITED(status)判断进程是否正常退出⾮0表示正常退出WEXITSTATUS(status)若WIFEXITED为真获取进程退出状态即exit的参数值WIFSIGNALED(status)判断进程是否异常终⽌⾮0表示被信号杀死WTERMSIG(status)若WIFSIGNALED为真获取导致终⽌的信号编号WIFSTOPPED(status)判断进程是否处于暂停状态WSTOPSIG(status)若WIFSTOPPED为真获取导致暂停的信号编号WIFCONTINUED(status)判断进程是否从暂停状态恢复运⾏2.wait函数样例(1) 子进程被信号杀死创建一个 wait.c 文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){printf(我是父进程, pid: %d\n,getpid());intst;intretwait(st);if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}if(ret-1){printf(进程全部结束, 返回值: %d\n,ret);break;}}}elseif(pid0)// 子进程{while(1){printf(我是子进程, pid: %d\n,getpid());sleep(3);}exit(0);}else{perror(fork);exit(-1);}return0;}编译并运行 wait.cgcc wait.c-owait./wait可以看到终端显示了子进程被 9 号信号杀死。(2) 子进程正常退出略微修改上面的文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){printf(我是父进程, pid: %d\n,getpid());intst;intretwait(st);if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}if(ret-1){printf(进程全部结束, 返回值: %d\n,ret);break;}}}elseif(pid0)// 子进程{//while(1)//{printf(我是子进程, pid: %d\n,getpid());//sleep(3);//}exit(-1);}else{perror(fork);exit(-1);}return0;}重新编译并运行 wait.cgcc wait.c-owait./wait解释一下这里的状态码 255 :c 语言的int类型是 32 位代码中对于子进程正常退出写的是exit(-1)那么 -1 的二进制原码为10000000 00000000 00000000 00000001-1 的二进制补码为11111111 11111111 11111111 11111111但是WEXITSTATUS(wstatus)只会取状态码的最低八位且为无符号整数退出码标准约定范围是 0~255只有非负错误码不存在负退出码也就是11111111255。3.waitpid函数相对于wait函数两者都用于回收子进程资源。主要区别wait只能回收任意子进程waitpid可指定特定子进程waitpid提供更多控制选项如WNOHANG非阻塞模式waitpid可以回收指定进程组的子进程(1) 头文件#includesys/types.h#includesys/wait.h(2) 函数原型pid_twaitpid(pid_tpid,int*wstatus,intoptions);参数pid指定要等待的子进程。pid 0等待回收进程 ID 等于该pid值的子进程。pid 0等待回收当前进程组的所有子进程。pid -1等待回收所有子进程 (wait(wstatus)等于waitpid(-1, wstatus, 0))。pid -1等待回收进程组 ID 等于pid值的所有子进程。wstatus子进程的退出状态。存储进程退出时的状态信息可以作为传出参数函数会修改其指向的内存。状态信息获取:通过指针操作获取⼦进程退出状态。可使⽤宏如WIFEXITED等解析状态值。NULL处理若不需要状态信息可传⼊NULL。options控制函数行为选项该参数可由以下一个或多个常量通过按位或运算组合而成也可直接传 0不开启任何特殊选项WNOHANG若无任何子进程退出函数立刻返回不会阻塞等待。WUNTRACED若子进程被暂停未通过 ptrace() 调试追踪也会触发函数返回。对于被 ptrace() 追踪、而后暂停的子进程即便不设置该选项也会返回其状态。WCONTINUEDLinux 2.6.10 版本起支持若一个先前被暂停的子进程收到 SIGCONT 信号恢复运行函数也会返回。返回值返回值 0返回被回收的子进程 PID。返回值 0 (仅当optionsWNOHANG非阻塞模式时生效)当前检查时刻没有子进程结束退出但还有存活子进程立刻返回 0、不阻塞父进程。返回值 -1出错pid 非法、权限不足等。当前没有任何子进程可以回收。当所有子进程都已结束时也会返回 -1 。作用与wait函数相比可以回收指定进程号的子进程还可以设置是否阻塞。4.waitpid函数样例(1) 子进程被信号杀死创建一个 waitpid.c 文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){sleep(1);printf(我是父进程, pid: %d\n,getpid());intst;intretwaitpid(-1,st,WNOHANG);// 非阻塞等待回收所有子进程if(ret0){if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}}elseif(ret0){printf(当前还有子进程未终止\n);}elseif(ret-1){perror(waitpid);break;}}}elseif(pid0)// 子进程{while(1){printf(我是子进程, pid: %d\n,getpid());sleep(3);}exit(-1);}else{perror(fork);exit(-1);}return0;}编译并运行 waitpid.cgcc waitpid.c-owaitpid ./waitpid(2) 子进程正常退出稍微修改一下上面的 waitpid.c 文件#includestdio.h#includesys/types.h#includesys/wait.h#includestdlib.h#includeunistd.hintmain(){pid_tpid;// 创建三个子进程for(inti0;i3;i){pidfork();if(pid0){break;}}if(pid0)// 父进程{while(1){sleep(1);printf(我是父进程, pid: %d\n,getpid());intst;intretwaitpid(-1,st,WNOHANG);// 非阻塞等待回收所有子进程if(ret0){if(WIFEXITED(st))// 子进程正常退出{printf(子进程正常退出, pid: %d, 状态码: %d\n,ret,WEXITSTATUS(st));}if(WIFSIGNALED(st))// 子进程异常终止{printf(子进程被信号杀死, pid: %d, 信号编号: %d\n,ret,WTERMSIG(st));}}elseif(ret0){printf(当前还有子进程未终止\n);}elseif(ret-1){perror(waitpid);break;}}}elseif(pid0)// 子进程{//while(1)//{printf(我是子进程, pid: %d\n,getpid());//sleep(3);//}exit(-1);}else{perror(fork);exit(-1);}return0;}再次编译并运行 waitpid.cgcc waitpid.c-owaitpid ./waitpid