Linux 进程退出和进程控制
1. 进程退出
进程退出大致可以分为三种情况
exit _exit 和 return的区别


1.return 对比 exit和_exit的区别
return 在函数内 return后程序继续进行
但是exit和_exit在程序结束后就停止进行了
2.exit和_exit的区别
exit():是标准库函数(属于 C 标准库),声明在 <stdlib.h> 中。
_exit():是系统调用(直接与操作系统交互),声明在 <unistd.h> 中。
exit():终止进程前会执行一系列清理操作:
调用通过 atexit() 或 on_exit() 注册的清理函数(用户自定义的收尾逻辑)。
关闭所有已打开的标准 I/O 流(如 FILE* 指针),并刷新缓冲区(将缓冲区中的数据写入实际文件或设备)。
最终调用 _exit() 完成进程终止。
_exit():直接终止进程,不执行任何清理操作:
不会调用 atexit() 注册的函数。 不会刷新标准 I/O 缓冲区(缓冲区中的数据会丢失)。
直接释放进程占用的资源(如内存、文件描述符等)并返回内核。
进程异常终止情况
进程一般异常终止 不一定有返回值 比如我下面的这个代码
由于对空指针进行简引用 所以程序会异常终止
这个时候没有返回值 于是进程会返回一个信号 这个信号可以通过kill -l
进行查看
从输出可知,可执行文件运行时出现 Segmentation fault(段错误),对应的信号是 SIGSEGV。在 kill -l 列出的信号列表中,SIGSEGV 的编号是 11。
2.打印返回值
我们的返回值可以用echo 打印 返回值表示?
但是和打印PSTH一样 要前面加一个$


为什么第二次echo打印的是0
因为echo $? 打印的是最近的一次进程的返回值
第二次echo $? 打印的是上一次echo $?的返回值
上一次echo $?是正常返回所以是0
3.进程等待
我们前面学习过了进程有一个状态是僵亡状态
但是我们没有解决怎么让父进程回收
让子进程从僵亡态(zombile)到死亡态(dead)


我们发现我们的将亡的子进程直接被父进程回收了
因此wait是可以将僵亡的进程直接回收的
1.wait函数
pid_t wait(int *status);头文件:需要包含 <sys/wait.h> 和 <sys/types.h>
父进程调用 wait 后,会阻塞等待任意一个子进程终止,并回收该子进程的资源
如果父进程没有子进程,或所有子进程都已被回收,wait 会立即返回错误。
1.status
参数 status 作用:用于存储子进程的退出状态信息(如退出码、是否被信号终止等)。
特殊值:若传 NULL,表示父进程 “不关心子进程的退出状态”,仅需回收资源(如之前测试代码中的 wait(NULL))。
2. 返回值
成功:返回被回收的子进程的 PID(唯一标识哪个子进程被回收)。
失败:返回 -1,并设置 errno(如 errno=ECHILD 表示没有子进程可回收)。
当 status 不为 NULL 时,它存储的是子进程的 “原始退出状态”(一个整数,需通过系统提供的宏解析)。常用宏如下:
// 父进程代码片段
int status;
pid_t ret = wait(&status); // 等待子进程并获取状态
if (ret != -1) {if (WIFEXITED(status)) {// 子进程正常退出,打印退出码printf("子进程%d正常退出,退出码:%d\n", ret, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {// 子进程被信号终止,打印信号值printf("子进程%d被信号%d终止\n", ret, WTERMSIG(status));}
}wait 是 waitpid 的简化版,等价于 waitpid(-1, status, 0)(-1 表示等待任意子进程,0 表示阻塞模式)。
阻塞性: 父进程调用 wait 后会 “卡住”(阻塞),直到有子进程终止才会继续执行。如果已有子进程终止但未被回收(即已成为僵尸进程),wait 会立即返回并回收它。
回收范围: wait 只能回收当前进程的子进程(不能回收孙进程或其他无关进程)。如果有多个子进程,wait 会按 “子进程终止的先后顺序” 回收其中一个(不确定具体是哪一个,除非配合信号机制)。
2.pid_wait函数
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);1.pid
pid:指定等待的子进程 pid 的值决定了 waitpid 等待哪些子进程,具体规则如下:
pid > 0:等待进程 ID 为 pid 的特定子进程。
pid = 0:等待与调用进程(父进程)同进程组的任意子进程(即组内所有子进程)。
pid = -1:等待任意子进程(与 wait 函数功能一致)。
pid < -1:等待进程组 ID 为 |pid|(pid 的绝对值)的任意子进程(即指定进程组内的子进程)。
2.status
status:存储子进程的状态信息 wstatus 是一个指向整数的指针,
用于接收子进程的状态信息(如退出原因、信号等)。
若不需要状态信息,可传入 NULL。
通过 <sys/wait.h> 提供的宏可以解析 wstatus 的值,常用宏如下:

3. options
控制 waitpid 的行为 options 是位掩码,用于设置等待的模式,常用选项如下(可通过 | 组合): WNOHANG:非阻塞模式。若指定的子进程未发生状态变化,waitpid 立即返回 0,而非阻塞等待。
WUNTRACED:除了等待子进程终止,还返回被信号暂停的子进程状态。
WCONTINUED:返回从暂停状态恢复(收到 SIGCONT)的子进程状态。
4.返回值
成功:返回状态变化的子进程的 PID(若子进程终止,会清理其僵尸进程状态)。
若设置 WNOHANG 且无符合条件的子进程状态变化:返回 0。
失败:返回 -1(如无符合条件的子进程、被信号中断等),并设置 errno 表示错误原因(如 ECHILD 表示无待等待的子进程)。
5.与 wait 函数的区别
wait 函数可视为 waitpid 的简化版,等价于 waitpid(-1, wstatus, 0),
即: wait 只能等待任意子进程,而 waitpid 可通过 pid 指定特定子进程或进程组。
wait 是阻塞的,而 waitpid 可通过 WNOHANG 实现非阻塞等待。
wait 不支持获取子进程的暂停 / 继续状态,而 waitpid 可通过 WUNTRACED/WCONTINUED 实现。
4.status结构
我们举个例子来试试
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == -1) {perror("fork 失败");exit(EXIT_FAILURE);}if (pid == 0) {printf("子进程 (PID: %d) 即将以退出码 1 结束\n", getpid());exit(1);} else {int status;pid_t waited_pid = wait(&status);if (waited_pid == -1) {perror("wait 失败");exit(EXIT_FAILURE);}printf("\n父进程捕获到子进程 (PID: %d) 的结束状态\n", waited_pid);printf("wait 返回的原始 status 值: %d\n", status);if (WIFEXITED(status)) {int exit_code = WEXITSTATUS(status);printf("子进程正常退出,退出码: %d\n", exit_code);printf("验证:退出码 << 8 = %d(与原始 status 一致)\n", exit_code << 8);} else {printf("子进程未正常退出(可能被信号终止)\n");}}return 0;
}
为什么status的值256呢???


在类 Unix 系统中,进程的终止状态通过一个整数(通常是 8 位或 16 位结构)来表示,这张图将其拆分为两种场景:
正常终止:进程通过 exit() 等正常逻辑结束。
此时 ** 高 8 位(位 8-15)** 存储 “退出状态码”,** 低 8 位(位 0-7)** 为 0(或对应正常退出的结构)。
被信号所杀:进程因收到某个系统信号(如 SIGSEGV、SIGKILL 等)而终止。
此时高 8 位未用,低 8 位中包含两部分: core dump 标志位:标识进程终止时是否生成了核心转储文件(用于调试);
终止信号编号:标识导致进程终止的具体信号。


我们直接看代码1和代码2
//代码1
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == 0) { // 子进程exit(123); // 正常退出,状态码设为123} else { // 父进程int status;wait(&status); // 等待子进程if (WIFEXITED(status)) { // 判断正常退出printf("子进程正常退出,状态码:%d\n", WEXITSTATUS(status));}}return 0;
}代码1 正常退出 因为exit(123)所以高八位存123
所以WEXITSTATUS提取到的是123 所以打印的是123
//代码2
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>int main() {pid_t pid = fork();if (pid == 0) { // 子进程sleep(2); // 让子进程先休眠,方便父进程发信号} else { // 父进程sleep(1); // 等1秒后给子进程发终止信号kill(pid, SIGKILL); // 发SIGKILL信号杀死子进程int status;wait(&status); // 等待子进程if (WIFSIGNALED(status)) { // 判断信号终止printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));// SIGKILL的编号是9,可验证}}return 0;
}代码2 由于是被信号所杀 所以低七位存信号编号 高位无意义 由于这个地方被SIGKILL杀死 其编号是9 所以 WTERMSIG提取到的是 打印出来的是9
