news 2026/4/23 13:41:36

【Linux】从 fork 到进程终止:写时拷贝细节与常见退出方式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux】从 fork 到进程终止:写时拷贝细节与常见退出方式

【Linux】从 fork 到进程终止:写时拷贝细节与常见退出方式

Linux 进程创建(fork)与终止(exit/kill)是操作系统中最核心、最常被考察的机制之一。
本文重点讲解fork 的写时拷贝(Copy-On-Write, COW)实现细节,以及进程终止的各种方式(正常/异常/强制),结合内核视角、常见陷阱与生产实践。

1. fork() 的本质与写时拷贝(COW)机制

1.1 fork() 做了什么?

fork()是 POSIX 标准中创建新进程的系统调用,返回值:

  • 子进程:返回 0
  • 父进程:返回子进程 PID(>0)
  • 失败:返回 -1(errno)

最关键的一点:子进程是父进程的几乎完整副本(包括代码段、数据段、堆、栈、打开的文件描述符、信号处理、环境变量等)。

传统实现(很早的 Unix)会直接复制整个地址空间 →极其昂贵(内存拷贝 + 时间)。

1.2 现代 Linux 如何优化?→ 写时拷贝(Copy-On-Write)

Linux(从很早版本开始)采用COW机制:

  1. fork 时

    • 不复制物理页面,而是复制页表(page table)
    • 子进程的页表项指向父进程相同的物理页面
    • 所有用户态可写页面(大多数数据/堆/栈)被标记为只读(read-only)(内核在页表中设置写保护位)
  2. 读操作:直接访问共享的物理页面 →零拷贝,速度极快

  3. 第一次写操作(任一进程):

    • 触发页面故障(page fault)
    • 内核检测到写保护 → 分配一个全新物理页面
    • 把原页面内容复制到新页面
    • 更新当前进程的页表 → 指向新页面(可写)
    • 父/子进程各自拥有独立的副本

一句话总结
fork 后父子共享物理内存,直到其中一方写时才真正复制该页

1.3 COW 的典型表现(代码演示)
#include<stdio.h>#include<unistd.h>#include<sys/wait.h>intglobal_var=100;// 数据段intmain(){intstack_var=200;// 栈pid_tpid=fork();if(pid==0){// 子进程printf("子进程: global=%d, stack=%d\n",global_var,stack_var);global_var=999;// 写 → 触发 COW(该页被复制)stack_var=888;printf("子进程修改后: global=%d, stack=%d\n",global_var,stack_var);}else{// 父进程wait(NULL);printf("父进程: global=%d, stack=%d\n",global_var,stack_var);// 父进程看到的仍是 100 和 200(COW 后子进程修改的是自己的副本)}return0;}

输出

子进程: global=100, stack=200 子进程修改后: global=999, stack=888 父进程: global=100, stack=200

结论:父子进程修改的是各自独立的副本,不互相影响

1.4 COW 的优点与代价

优点

  • fork 极快(只需复制页表 + 标记写保护)
  • 内存利用率高(大量 fork 后 exec 的场景,如 shell、Apache prefork 模式,几乎不额外耗内存)

代价 / 副作用

  • 写操作会触发页面复制 →写放大(尤其是大进程 fork 后频繁写内存时)
  • 多进程同时写同一页 → 每个进程都复制一份
  • 内存碎片可能增加

生产建议

  • 尽量 fork 后尽快 exec(经典用法:避免 COW 带来的写放大)
  • 高并发服务器避免 fork 模型 → 改用线程池 / 事件驱动 / 多进程预 fork + accept(2) 复用

2. 进程终止的常见方式

Linux 进程退出有多种路径,影响退出码资源释放信号处理atexit/on_exit等清理函数是否执行。

方式系统调用/信号是否调用 atexit()是否发 SIGCHLD是否可捕获典型退出码备注 / 适用场景
main 返回exit()返回值最正常退出
exit() / _exit()exit_group() / exit是 / 否参数exit 调用 atexit,_exit 不调用
pthread_exit()否(仅线程)仅退出当前线程,主线程仍存活
return from mainexit()返回值等价于 exit()
SIGTERM (kill -15)是(若 handler 调用 exit)可捕获通常 143优雅终止(默认行为)
SIGKILL (kill -9)不可捕获通常 137强制杀死(不可拦截)
SIGABRT (abort())否(核心转储)可捕获通常 134断言失败等
段错误等异常SIGSEGV 等可捕获通常 139核心转储

关键区别

  • exit():调用 atexit/on_exit 注册的清理函数 → 刷新 stdio 缓冲区 → 关闭打开的文件(fclose)→ 然后调用 _exit()
  • _exit():直接系统调用 exit,不做任何用户态清理(缓冲区可能丢失)
  • SIGTERM:默认行为是终止进程,但可以捕获并做清理(调用 exit())
  • SIGKILL:内核强制杀死,无法捕获、无法清理(脏数据可能残留)

退出码约定(Shell 中 $?):

  • 正常退出:0 ~ 255(用户定义)
  • 被信号杀死:128 + 信号编号
    • SIGTERM (15) → 143
    • SIGKILL (9) → 137
    • SIGSEGV (11) → 139

3. 生产中最常见的终止流程(推荐)

#include<stdio.h>#include<stdlib.h>#include<signal.h>#include<unistd.h>staticvoidcleanup(void){printf("atexit 清理:关闭文件、释放资源...\n");}staticvoidsigterm_handler(intsig){printf("收到 SIGTERM,进行优雅退出...\n");// 可以做:保存状态、通知其他进程、关闭连接等exit(EXIT_SUCCESS);// 或 _exit(0) 看需求}intmain(){atexit(cleanup);signal(SIGTERM,sigterm_handler);// signal(SIGINT, sigterm_handler); // Ctrl+Cprintf("进程运行中... PID=%d\n",getpid());while(1){sleep(1);}return0;}

测试

# 终端1./a.out# 终端2kill-TERM<pid># 优雅退出,调用 atexitkill-9<pid># 强制杀死,不调用 atexit

小结 & 面试/运维高频问题

  1. fork 后父子进程共享哪些资源?(文件描述符、mmap 共享内存等)
  2. COW 触发条件是什么?(第一次写)
  3. SIGTERM 和 SIGKILL 区别?(可捕获 vs 不可捕获)
  4. 为什么生产中常用 SIGTERM + 优雅退出?(避免数据丢失、脏关闭)
  5. 如何防止子进程变成僵尸(zombie)?(父进程 wait/waitpid)
  6. 多线程程序中主线程 exit() 会怎样?(整个进程退出)

想继续深入哪个方向?
A. vfork() vs fork() vs clone() 区别
B. 僵尸进程、孤儿进程的产生与处理
C. 多线程信号分发与 pthread_kill
D. 内核视角:task_struct → do_exit 流程
E. 生产事故案例(SIGKILL 导致数据损坏)

告诉我字母,我们继续深挖!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 15:14:29

为什么C语言执行效率高,运行快?

C 语言之所以执行效率高、运行速度快&#xff0c;主要源于它的设计哲学、语言特性以及编译执行流程与硬件的贴合度远高于大多数现代高级语言。下面从最核心的几个维度系统解释原因&#xff08;基于 2026 年视角&#xff0c;这些结论在性能敏感领域如操作系统、嵌入式、游戏引擎…

作者头像 李华
网站建设 2026/4/18 4:22:22

学信网学历认证照片大小太大怎么办?照片格式怎么弄与压缩技巧

做学信网学历认证时&#xff0c;最让人头疼的莫过于照片上传环节。要么显示“照片太大无法上传”&#xff0c;要么提示“格式不符合要求”&#xff0c;反复调整却始终通不过审核&#xff0c;耽误认证进度。学信网学历认证照片有明确规范&#xff1a;蓝白背景&#xff0c;像素48…

作者头像 李华
网站建设 2026/4/15 19:00:12

【dz-1042】车内滞留儿童远程报警系统设计

车内滞留儿童远程报警系统设计 摘要 在汽车广泛普及的当下&#xff0c;车内滞留儿童引发的安全事故频发&#xff0c;密闭环境中温度骤变与 CO₂浓度升高对儿童生命安全构成严重威胁。由于儿童自救能力薄弱&#xff0c;传统依赖人工检查的方式易因疏忽导致悲剧&#xff0c;因此研…

作者头像 李华
网站建设 2026/4/16 17:54:14

每天一个网络知识:什么是交换式端口分析器 SPAN?

在学习交换机和局域网时&#xff0c;同学们可能会产生一个疑问&#xff1a; 交换机内部的数据是如何流动的&#xff1f;网络故障时&#xff0c;管理员又是如何“看到”网络中的数据包的&#xff1f; 要回答这些问题&#xff0c;就离不开我们今天要介绍的网络知识——SPAN&…

作者头像 李华
网站建设 2026/4/21 9:11:43

户外探险新利器:用照片to谷歌地球记录我的荒野足迹

探险者的困境 作为一名狂热的户外探险爱好者&#xff0c;我每年都会花大量时间在山林、沙漠、海岸线等荒野地带探索。从云南的高黎贡山到新疆的喀纳斯&#xff0c;从四川的稻城亚丁到青海的可可西里&#xff0c;我的足迹遍布祖国的大好河山。 然而&#xff0c;每次探险归来&a…

作者头像 李华
网站建设 2026/4/18 10:28:34

掌握这25条小贴士,快速提升数据可视化能力!

可视化不是单纯的数据展示&#xff0c;其真正价值是&#xff0c;设计出可以被读者轻松理解的数据展示。设计过程中的每一个选择&#xff0c;最终都应落地于读者的体验&#xff0c;而非设计者个人。笔者为大家整理了25条小贴士&#xff0c;能够快速帮助大家提升和巩固你的数据可…

作者头像 李华