1 进程创建
1-1 fork函数初识
在Linux中fork函数十分重要,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程id,出错返回-1
进程调用 fork ,当控制转移到内核中的 fork 代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
fork返回,开始调度器调度

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
1-2 写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自⼀份副本。

因为有写时拷贝技术的存在,所以父子进程得以彻底分离!完成了进程独立性的技术保证!
写时拷贝,是⼀种延时申请技术,可以提高整机内存的使用率
1-3 fork常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从 fork 返回后,调用 exec 函数。
1-4 fork调用失败的原因
- 系统中有太多的进程
- 系统用户的进程数超过了限制
2 进程终止
一、进程终止的三种情况
- 代码跑完,结果正确 进程正常执行完毕,逻辑和结果都符合预期,例如
main函数return 0。 - 代码跑完,结果不正确 进程正常执行完毕,但结果不符合预期,例如
return 1(或其他非 0 退出码),表示程序逻辑错误。 - 代码没跑完,进程异常终止 进程中途被终止,例如收到信号(如段错误、被 kill 命令杀死),此时
return没有被执行,退出码无意义。
二、进程退出状态的表示
进程的执行结果状态,由两个关键值表示:
sig(信号编号):表示进程是否因信号异常终止。sig == 0表示正常结束;sig != 0表示被信号终止。exit_code(退出码):表示进程正常结束时的状态。只有当sig == 0时,退出码才有意义。
| 情况 | sig(信号) |
exit_code(退出码) |
|---|---|---|
| 正常且结果正确 | 0 | 0 |
| 正常但结果错误 | 0 | 非 0 |
| 异常终止 | 非 0 | 无意义 |
操作系统会把这两个信息写入进程的 task_struct(PCB)中,僵尸进程的核心作用,就是保存这个退出状态,供父进程读取。
三、进程如何退出(不考虑异常)
-
main函数的return- 只有
main函数的return会让进程结束;其他函数的return仅表示函数调用结束。 return n本质上等价于调用exit(n)。
- 只有
-
在任意位置调用
exit()- 是 C 标准库函数,调用后进程直接终止。
- 特点:会强制刷新缓冲区 (把缓冲区数据写入文件 / 终端),再调用系统调用
_exit()。

-
在任意位置调用
_exit()- 是直接的系统调用,内核提供的接口。
- 特点:直接终止进程,不会刷新缓冲区,也不做任何用户态的清理工作。
💡 关键对比:
exit()会刷新缓冲区,_exit()不会。因为缓冲区和刷新操作是 C/C++ 库维护的,不是内核的功能。
四、内核视角:进程终止时的操作
- 进程退出时,操作系统会将退出状态(
exit_code和sig)写入进程的task_struct。 - 进程需要以僵尸进程 的形式保留 PCB,直到父进程通过
wait()/waitpid()读取退出状态,才会被彻底回收