当前位置: 首页 > news >正文

【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 导致数据损坏)

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

http://www.jsqmd.com/news/309298/

相关文章:

  • 【Linux】零基础入门:一篇吃透操作系统核心概念
  • 2026年建筑材料检测公司推荐榜单,助你选择最优服务
  • 2026 年 1 月厂房降温设备厂家推荐排行榜:工业冷风机,环保空调,大型车间通风降温系统,车间降温解决方案公司精选
  • CSS-4:CSS的三大特性 - 详解
  • 基于Gin与GORM的若依后台管理系统设计与实现
  • 基于WebDAV协议的天翼云盘智能分享管理系统设计与实现
  • 2026 年 1 月热缩管厂家推荐排行榜:彩色/黑色/透明/双壁/高压母排/花纹绝缘热缩管,专业防护与耐用品质的电缆绝缘解决方案
  • 2026 年 1 月铝板厂家推荐排行榜:幕墙铝板,阳极氧化铝板,铝单板,冲孔铝板,雕花铝板源头实力厂家精选指南
  • 如何处理Vue中的异常和错误?
  • vue支付流程的前端实现
  • 跨域问题解决方案:Proxy配置与CORS详解
  • 基于AI算法的市场洞察:黄金5100美元新高成因及贵金属板块联动分析
  • SOLIDWORKS采购避坑指南:4个核心维度锁定优质渠道
  • 选择CST代理商的关键五大维度——超越价格,聚焦长期价值
  • 微信小程序制作一个需要多少钱?2026三种开发方式及详细费用解析
  • 2026年1月饮料代加工厂家推荐榜单:液体/植物/茶饮/咖啡/OEM贴牌,无菌冷灌装与网红定制方案深度解析
  • 5kg便携+0.1秒响应:HORIBA MEXA-600SW不透光度计国六柴油车烟度检测实战全解
  • 【dz-1043】基于物联网的水质监测系统设计与实现
  • 一表双显+±1%精度:MTX-D数字油压温度计赛车/改装车发动机监测实战全解
  • 【dz-1044】基于单片机的自动门
  • 【dz-1045】基于STM32的人体生理参数检测系统设计与实现
  • 【dz-1046】基于单片机的全自动洗衣机控制系统设计
  • 【dz-1047】基于单片机的手提文件箱防盗锁设计
  • 【dz-1048】基于单片机的自动避障小车
  • 秒级采集 × 万级点位 × 两万亿条数据管理,「红河复烤」复烤工艺数字化实践
  • 【dz-1040】热水器控制系统
  • 【dz-1041】基于STM32的智能体重秤的设计与实现
  • 【dz-1042】车内滞留儿童远程报警系统设计
  • 如何用CLAUDECODE重塑嵌入式开发
  • springboot企业进销存管理系统演vue论文