news 2026/3/28 10:58:08

【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/3/21 7:35:17

SOLIDWORKS采购避坑指南:4个核心维度锁定优质渠道

对于制造企业而言&#xff0c;SOLIDWORKS不仅是设计工具&#xff0c;更是数字化研发的核心支撑。选对购买渠道&#xff0c;才能让工具价值最大化&#xff1b;选错渠道&#xff0c;轻则影响研发效率&#xff0c;重则面临版权风险。结合行业实践经验&#xff0c;以下4个核心维度&…

作者头像 李华
网站建设 2026/3/27 11:01:40

对线面试官系列:MySQL 常见面试题,你能答对几道?

1. 什么是存储过程&#xff1f;有哪些优缺点&#xff1f; 存储过程就像我们编程语言中的函数一样&#xff0c;封装了我们的代码(PLSQL、T-SQL)。 存储过程的优点&#xff1a;1.能够将代码封装起来2.保存在数据库之中3.让编程语言进行调用4.存储过程是一个预编译的代码块&#…

作者头像 李华
网站建设 2026/3/16 21:07:48

安捷伦 Keysight 16195B 是德 16195B 校准套件

是德科技 16195B校准套件‌主要用于高频阻抗测量仪的校准&#xff0c;特别是在7 mm接头平面上进行校准。该套件包含以下组件&#xff1a; E4991-60021 &#xff08;SHORT&#xff09;、 E4991-60022 &#xff08;OPEN&#xff09;以及一个50欧姆的负载&#xff0c;频率范围为DC…

作者头像 李华
网站建设 2026/3/27 3:34:07

力科Teledyne LeCroy PP024 示波器探头 PP024-2

力科PP024探头 是一款无源衰减探头&#xff0c;适用于示波器&#xff0c;具有10:1的衰减值和500MHz的带宽。‌ 该探头的输入电阻为10MΩ&#xff0c;最大电压值为CAT I 500V和CAT II 400V&#xff0c;输入电容为10pF&#xff0c;电缆长度为51.181英寸&#xff08;约1300毫米&am…

作者头像 李华
网站建设 2026/3/19 12:37:29

springboot旅游旅行攻略网站的设计与实现vue

目录系统架构设计核心功能模块技术实现要点扩展功能设计部署方案开发技术源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;系统架构设计 SpringBoot旅游旅行攻略网站采用前后端分离架构&#xff0c;后端使用SpringBoot框架提供RESTful A…

作者头像 李华
网站建设 2026/3/7 14:52:00

【dz-670】基于单片机的智能化光伏发电监控与管理系统设计

基于单片机的智能化光伏发电监控与管理系统 摘要&#xff1a;当前全球能源需求持续增长&#xff0c;同时对环境保护的重视程度也日益提高&#xff0c;光伏发电作为一种有清洁、可再生特性的能源技术&#xff0c;已获得广泛应用&#xff0c;本文设计了一种以单片机为基础的智能化…

作者头像 李华