1. 进程创建
1.1 fork 系统调用
通过 fork() 系统调用可以创建子进程。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int ret = fork();
printf("hello proc : %d!, ret: %d\n", getpid(), ret);
sleep(1);
return 0;
}
成功创建时,父进程返回子进程的 PID,子进程返回 0。若失败则返回 -1。由于父进程可能拥有多个子进程,需要通过返回值区分执行逻辑,通常配合 if 语句让父子进程运行不同代码。
值得注意的是,子进程不会执行 fork() 之前的代码,而是从 fork() 返回处继续。当父子进程尝试修改数据时,内核会触发写时拷贝(Copy-On-Write),避免立即复制内存,从而提升效率。这使得父子进程在逻辑上独立运行。
1.2 常规用法
- 父进程创建子进程后,两者各自执行不同的业务逻辑。
- 子进程常通过
exec系列函数完全替换为另一个程序。
1.3 fork 失败原因
主要受限于系统资源,例如进程总数超过内核限制,或用户进程数超出配额。
2. 进程退出
2.1 基本概念
进程退出意味着释放代码和数据段,但 PCB(进程控制块)对象不会立即释放。
2.2 退出场景
包括代码运行完毕且结果正确、运行完毕但结果不正确,以及因收到信号导致的异常终止。
2.3 退出码
异常终止时退出码无意义。正常退出时,0 代表成功,非 0 代表失败(不同值可表示不同错误原因)。
Shell 中可用 $? 查看最近一个进程的退出码。系统调用或库函数出错时,errno 会被设置为对应错误码,需包含 <errno.h> 并使用 strerror() 获取错误信息。
2.4 常见退出方式
main函数的return退出码,表示进程退出。_exit(退出码),系统调用,直接退出。exit(退出码),C 标准库函数,先刷新 I/O 缓冲区再退出。
3. 进程等待
3.1 必要性
子进程退出后,其代码和数据已释放,但 PCB 仍保留。若父进程未回收,该进程变为'僵尸进程',造成资源泄漏。父进程需通过等待机制回收子进程 PCB。


