【Linux】进程退出

发布时间:2026/10/18 7:14:40
【Linux】进程退出
标签Linux 进程控制 exit _exit 退出码 task_struct 进程等待重点回答三个问题父进程为什么要关心子进程怎么退出的进程有哪几种退出方式区别是什么异常终止时退出信息存在哪里一、为什么要关心进程的退出情况子进程也是进程它由父进程创建替父进程执行任务。任务执行得怎么样父进程自然需要知道。进程退出可以分为三种场景代码运行完毕结果正确代码运行完毕结果不正确代码异常终止前两种是代码跑完了结果好坏由退出码决定第三种是代码没跑完要看终止信号。二、类比函数的返回值与进程的退出码函数调用中被调函数通过返回值x86 下放在eax寄存器告诉调用者执行情况比如a()调用b() :b() 中 mov eax, 1 ret a() 中 call b mov 0x123[], eax ; 从 eax 取到返回值进程之间是同样的模式函数世界进程世界callfork execreturnexit / return返回值退出码调用者读取返回值父进程 wait 读取退出码三、进程的三种正常退出方式正常终止可以通过echo $?查看退出码从main返回调用exit调用_exit异常退出CtrlC 等信号终止。3.1 从 main 返回int main() { return 0; // 0 表示成功 }main的返回值通常用来表明程序的执行情况return 0代码跑完结果正确return 非01, 2, 3 ...代码跑完结果不正确不同的值代表不同的出错原因具体含义由程序员约定return n等价于exit(n)因为调用 main 的运行时启动代码会把 main 的返回值当作 exit 的参数。所以第 1 种本质上就是第 2 种。3.2 exit(n)#include stdlib.h void exit(int status);exit是C 库函数依次做三件事执行用户通过atexit或on_exit注册的清理函数 此函数是用户自己写的关闭所有打开的流刷新缓冲区缓冲数据全部写出调用_exit3.3 _exit(n)#include unistd.h void _exit(int status);_exit是系统调用直接进入内核终止进程不执行清理函数不刷新 C 库的缓冲区。status定义了进程的终止状态父进程通过wait获取。说明status 虽然是 int但只有低 8 位能被父进程使用。所以_exit(-1)时终端echo $?看到的是 255exit(257)得到 1257 % 256。3.4 exit 与 _exit 的关系exit 用户态收尾工作 _exit四、核心实验为什么 exit 有输出而 _exit 没有// 实验一 int main() { printf(hello); // 注意没有 \n exit(0); } // 运行结果hello[rootlocalhost linux]#// 实验二 int main() { printf(hello); // 注意没有 \n _exit(0); } // 运行结果[rootlocalhost linux]# 什么都没有原因数据有两个仓库printf并不是直接把字符写到屏幕而是先写进C 库维护的用户态缓冲区之后才刷新到内核、再到屏幕printf ── [C库缓冲区在进程自己的内存里] 刷新 ── 内核 ── 屏幕终端的刷新策略是行缓冲遇到\n、缓冲区满、主动fflush、正常退出时才会刷新。exit退出前先刷新缓冲区字符送出去了所以能看到hello_exit直接结束进程缓冲区属于进程的内存进程一没里面的字符就丢了这也说明这个缓冲区是 C 库的不是操作系统内核的。如果缓冲区在内核里_exit也不会丢数据。原因printf(hello); _exit(0);的结果是hello 丢了。什么情况下数据会随着进程结束而丢失数据放在进程自己的内存里进程一结束这块内存被回收数据就没了。数据放在内核的内存里内核不会因为你的进程结束而消失它会继续把数据送到屏幕或写到文件数据不会丢。所以缓冲区在进程自己的内存里。一个实际坑fork 之后的子进程fork 会把父进程用户态缓冲区里未刷新的内容也复制给子进程。因为子进程没缺页的时候mm_struct也会指向父进程的内存。如果父子进程退出时都调用exit各自都会刷新一次导致同一份内容被打印两遍。因此子进程在 fork 后需要提前退出如 exec 失败后时通常用_exit而不是exit。五、谁在读取退出码echo $?$ ./proc $ echo $? 1./proc这个进程的父进程是 bashbash 通过等待子进程拿到退出码并保存下来echo $?打印的就是最近一个程序进程退出时的退出码注意$?每次都是上一条命令的结果所以连续执行两次echo $?第二次得到的是第一次 echo 本身的退出码 0。六、退出码存在哪里task_struct进程退出时退出信息被记录在进程的 PCB 中Linux 下是task_struct。进程退出后会先变成僵尸状态PCB 保留等待父进程通过wait/waitpid来读取。这也是进程等待必要性的来源之一。七、异常终止时 task_struct 里还有东西吗既然异常终止退出码无意义是不是 task_struct 里什么都没有不是。进程终止时内核会把终止信息统一填入task_struct的exit_code字段只是两种终止方式填的内容不同正常终止: | 15 ...... 8 | 7 | 6 ...... 0 | | 退出码 | 0 | 0 | 被信号杀死: | 15 ...... 8 | 7 | 6 ...... 0 | | 未用 | core dump | 终止信号 |正常退出return / exit / _exit退出码放在第 8~15 位低 7 位为 0被信号杀死终止信号编号放在低 7 位第 7 位是 core dump 标志高 8 位无参考价值所以异常时退出码无意义的准确含义是进程没机会执行return/exit/_exit没有程序员设定的退出码此时应该看的是终止信号。补充task_struct 里还有一个exit_signal字段它表示子进程退出时发给父进程的信号通常是 SIGCHLD和子进程被什么信号杀死不是一回事不要混淆。父进程如何区分两种终止方式wait/waitpid的status参数取出的就是上面这个位图不能当普通整数看int status 0; waitpid(pid, status, 0); if ((status 0x7F) 0) { // 低 7 位为 0正常退出 printf(exit code: %d\n, (status 8) 0xFF); } else { // 低 7 位非 0被信号终止 printf(signal: %d\n, status 0x7F); }标准库提供了等价的宏实际开发中更推荐使用if (WIFEXITED(status)) { printf(exit code: %d\n, WEXITSTATUS(status)); } else { printf(killed by signal: %d\n, WTERMSIG(status)); }八、总结属于执行清理函数刷新缓冲区最终调用returnmain语言层面是是exitexitC 库函数是是_exit_exit系统调用否否内核子进程是父进程派出去干活的父进程需要知道结果三种正常退出方式main 返回 → 本质是 exit → 最终都落到_exitexit 比 _exit 多做用户态收尾清理函数、刷新缓冲区缓冲区在 C 库而非内核退出码 0 表示成功非 0 表示失败只有低 8 位有效数值含义由程序约定bash 是命令的父进程echo $?读取的是上一个进程的退出信息退出信息保存在task_struct中进程退出后以僵尸状态等待父进程读取正常退出看退出码第 8~15 位异常退出看终止信号低 7 位退出码$?不是 errno信号终止在 shell 中显示为128 信号编号