☰
[Linux系统] 进程创建 | 进程终止 | 进程等待
2026/9/30 2:41:23 网站建设 项目流程

一、进程创建:fork 一次调用,两次返回

#include<unistd.h>pid_tfork(void);// 子进程返回 0,父进程返回子进程 pid,出错返回 -1

流程:fork调用→内核给子分配新内存 / 数据结构 → 拷贝:父到子 → 子加入进程列表 →fork返回,开始调度

1.1 为什么一个函数有两个返回值?

fork之后就有两个二进制相同的进程,各自从 fork 返回处继续跑:

printf("Before: pid is %d\n",getpid());pid_tpid=fork();printf("After: pid is %d, fork return %d\n",getpid(),pid);// 输出三行:Before 一行,After 两行

子进程没有打印 Before——fork 之前的代码父进程已执行完,父子共享的代码从fork 之后分流。

返回值:

  • 父进程可能有多个子进程,必须靠返回的 pid 区分
  • 子进程自己getpid()就能拿到,所以返回 0 表示"我是孩子"
  • fork 之后谁先执行完全由调度器决定

1.2 写时拷贝

通常父子代码共享,不写入时数据也共享;任意一方试图写入,就以写时拷贝方式各拿一份副本。它是延时申请技术,既保证了进程独立性,也提高整机内存使用率——不写就不花内存的钱。

1.3 常规用法与失败原因

  • 常规用法:父进程复制自己,让父子执行不同代码段(如父进程等请求、子进程处理请求);或子进程 fork 后调用 exec 执行新程序;
  • 失败原因:系统进程太多,或实际用户的进程数超过限制。

本小节复盘:

二、进程终止

进程终止的本质是释放系统资源——释放申请的内核数据结构和对应的数据、代码。
退出场景只有三种:

  • 代码跑完:结果正确 / 不正确
  • 异常终止

2.1 退出码与 $?

退出码告诉我们最近一次命令的执行状态:0成功,非 0 都算失败。常用查看方式:

echo$?# 最近一次命令的退出码strerror(码)# 代码里取对应描述
  • 1:常见于"不被允许的操作",如除以 0(let a=1/0);
  • 130、143:128+n形式,n 是终止信号编号,如^C(SIGINT)产生 130。

2.2 exit、_exit、return 的区别

三者都能正常终止进程,差别在"善后"程度:

intmain(){printf("hello");// 没有 \n,内容还在缓冲区exit(0);// 输出 hello:先刷新缓冲区再退出}intmain(){printf("hello");_exit(0);// 什么都不输出:直接终止,缓冲区丢弃}

exit最后也会调用_exit,但之前多做两件事:执行atexit/on_exit注册的清理函数,关闭所有打开的流并刷新缓存数据。return n等价于exit(n)——运行时函数会把 main 的返回值当 exit 的参数。另外status虽是 int,但只有低 8 位被父进程使用,所以_exit(-1)时$?看到的是 255。异常退出则走信号,如ctrl+c。

本小节复盘:

三、进程等待:回收僵尸

3.1 为什么要等待

子进程退出后父进程不读取,子进程便进入 Z 状态成僵尸进程,PCB 一直占内存造成泄漏;而且僵尸状态刀枪不入,kill -9也杀不死一个已经死去的进程。父进程通过等待回收子进程资源,同时拿到任务结果:正常退出与否、退出码是多少。

3.2 wait 与 waitpid

pid_twait(int*status);// 成功返回子进程 pidpid_twaitpid(pid_tpid,int*status,intoptions);

waitpid的关键参数:pid=-1等待任意子进程(与 wait 等效),pid>0等指定 pid;options=0阻塞等待,WNOHANG非阻塞(子进程没退出就返回 0)。返回值:正常返回收集到的子进程 pid,出错返回 -1。

3.3 status 是位图,不是普通整数

status 由操作系统填充,只看低 16 比特:低 7 位是终止信号,第 8 位是 core dump 标记,高 8 位是退出码。不想看细节可以直接用宏:

  • WIFEXITED(status):正常终止为真;
  • WEXITSTATUS(status):提取退出码。

手工解读也行:

if((st&0x7F)==0)// 正常退出printf("exit code: %d\n",(st>>8)&0xFF);elseprintf("sig code: %d\n",st&0x7F);// 被信号杀死

3.4 阻塞与非阻塞

阻塞式:waitpid(-1, &status, 0),父进程挂起干等,子进程退出后立即返回并释放资源。

非阻塞式:waitpid(-1, &status, WNOHANG)放进循环,返回 0 说明子进程还在跑,父进程趁机做自己的事(刷新日志、处理任务),这就是基于非阻塞等待的轮询方案。

本小节复盘:

细节问题:

演示代码:

#include<stdio.h>#include<sys/types.h>#include<unistd.h>#include<stdlib.h>#include<sys/wait.h>voidtask(){sleep(1);printf("正在执行其他任务\n");}intmain(){pid_t ret=fork();if(ret<0){printf("创建失败\n");perror("fork");return1;}elseif(ret==0){printf("子进程:%d, 正在运行\n",getpid());sleep(5);intb=0;inta=1/b;(void)a;//printf("子进程结束\n");//exit(5);}else{//阻塞等待//int st = 0;//printf("父进程:%d, 正在运行\n", getpid());////int wret = wait(&st);//printf("阻塞等待结束\n");//////正常退出//if(wret > 0 && (st & 0X7F) == 0)//{// printf("子进程退出码:%d\n", (st >> 8) & 0XFF);//}//else if(wret > 0)//{// printf("子进程异常退出\n");//}//else//{// printf("捕获子进程失败\n");//}//非阻塞等待intst=0;printf("父进程:%d, 正在运行\n",getpid());pid_t wret=0;do{task();wret=waitpid(ret,&st,WNOHANG);if(wret==0)printf("子进程正在运行\n");sleep(1);}while(wret==0);//宏://WIFEXITED判断死法,非0正常退出,0为信号杀死(记图!)//WEXITSTATUS获取退出码(退出状态非0时有效//正常退出if(wret==ret&&WIFEXITED(st)){printf("非阻塞等待结束\n");printf("子进程退出码:%d\n",WEXITSTATUS(st));return0;}elseif(wret>0){printf("子进程异常退出\n");return2;}else{printf("捕获子进程失败\n");return1;}}}

异常退出:

正常退出:

四、总结

  • 进程创建:fork子返 0、父返 pid,两个执行流从 fork 后分流,先后由调度器决定;写时拷贝保证独立;
  • 进程终止:return n≈exit(n),exit刷缓冲、_exit不刷,退出码看$?,status 只有低 8 位有效;
  • 进程等待:waitpid回收僵尸并取退出信息,status当位图读,WNOHANG实现非阻塞轮询。

延伸阅读:man 手册man 2 fork、man 2 waitpid;libc 源码中 exit 的实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询