《深入理解计算机系统(原书第3版)》第8章:异常控制流
!note 阅读范围与适用边界
本笔记依据本地附件中的第 8 章整理,用自己的话讲解异常、进程、系统调用、信号与非本地跳转。书中以 Linux/x86-64 和 POSIX 风格接口为背景;不同操作系统、CPU 与 C 运行库的细节可能不同,遇到实际代码时应以目标平台文档为准。
!tip 一句话抓住本章**异常控制流(ECF)**就是程序原本按顺序走着,因硬件事件、操作系统调度、其他进程通知或错误处理需要,突然转去执行另一段代码;它是异常、进程、信号和并发的共同底座。
!warning 这一章最容易误会的地方"异常"不等于"程序出错","并发"也不等于"多核同时跑"。本章大量问题都来自:你以为控制流一定按某个顺序发生,但系统并没有承诺这个顺序。
1 本章学习导航
| 阶段 | 建议用时(估计) | 学什么 | 通过标准 |
|---|---|---|---|
| 0. 预热 | 20 分钟 | 回顾 PC、函数调用、栈、寄存器与指令 | 能说出 PC 决定"下一条执行什么" |
| 1. 建立总图 | 30 分钟 | 控制流、事件、ECF 的层次 | 能解释为什么定时器、缺页、Ctrl+C 都会改变控制流 |
| 2. 认识硬件异常 | 75 分钟 | 异常表、处理程序、中断/陷阱/故障/终止 | 能根据"返回到哪里"区分四类异常 |
| 3. 认识进程 | 90 分钟 | 逻辑控制流、地址空间、用户/内核模式、上下文切换 | 能说明一个进程为何"看起来独占 CPU 和内存" |
| 4. 操作进程 | 100 分钟 | fork、waitpid、execve、僵死进程 | 能画出父子进程并解释 fork 与 execve 的分工 |
| 5. 攻克信号 | 150 分钟 | pending、blocked、处理程序、竞争、sigsuspend | 能说明信号为何不能拿来计数,以及怎样避免竞态 |
| 6. 收尾与迁移 | 45 分钟 | 非本地跳转、诊断工具、速查与练习 | 能把本章连接到虚拟内存、I/O 和并发编程 |
!tip 推荐学习顺序
不要一开始就背信号编号或函数参数。先按 事件 → 异常 → 进程 → fork/wait/execve → 信号 → 同步 这条主线走;每一层都在回答同一个问题:控制权为什么会突然换地方,之后又怎么安全地回来?
2 本章学习目标
学完后,你应该能做到:
- 用大白话解释什么是控制流、什么是异常控制流,以及为什么它贯穿硬件、内核和应用程序。
- 说清异常表和异常处理程序分别做什么,并按发生原因、同步性和返回位置区分四类异常。
- 区分"程序""进程""逻辑控制流""并发"和"并行",不再把它们混成一件事。
- 解释进程为什么能给程序造成"独占 CPU、独占内存"的假象,以及上下文切换实际保存、恢复了什么。
- 正确说出 fork、waitpid、execve、exit 的基本分工,理解僵死进程和回收的必要性。
- 描述信号从发送、待处理、阻塞到接收的过程,知道普通信号不能可靠计数。
- 写出信号处理程序的基本安全规则,并能指出"先 fork、后 addjob"为什么会有竞争。
- 理解 sigsuspend 为什么能避免"检查条件后才睡眠"的竞态。
- 用 setjmp/longjmp 解释"调用一次、返回多次"与"调用一次、从不返回"的非本地跳转。
3 整章知识地图
#mermaid-svg-SgLRrdPwk43Uc33q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SgLRrdPwk43Uc33q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SgLRrdPwk43Uc33q .error-icon{fill:#552222;}#mermaid-svg-SgLRrdPwk43Uc33q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SgLRrdPwk43Uc33q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SgLRrdPwk43Uc33q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SgLRrdPwk43Uc33q .marker.cross{stroke:#333333;}#mermaid-svg-SgLRrdPwk43Uc33q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SgLRrdPwk43Uc33q p{margin:0;}#mermaid-svg-SgLRrdPwk43Uc33q .edge{stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .section--1 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section--1 path,#mermaid-svg-SgLRrdPwk43Uc33q .section--1 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section--1 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section--1 text{fill:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth--1{stroke-width:17;}#mermaid-svg-SgLRrdPwk43Uc33q .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-0 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-0 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-0 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-0 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-0 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-0{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-0{stroke-width:14;}#mermaid-svg-SgLRrdPwk43Uc33q .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-1 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-1 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-1 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-1 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-1 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-1{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-1{stroke-width:11;}#mermaid-svg-SgLRrdPwk43Uc33q .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-2 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-2 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-2 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-2 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-2 text{fill:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-2{stroke-width:8;}#mermaid-svg-SgLRrdPwk43Uc33q .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-3 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-3 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-3 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-3 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-3 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-3{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-3{stroke-width:5;}#mermaid-svg-SgLRrdPwk43Uc33q .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-4 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-4 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-4 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-4 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-4 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-4{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-4{stroke-width:2;}#mermaid-svg-SgLRrdPwk43Uc33q .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-5 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-5 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-5 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-5 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-5 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-5{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-5{stroke-width:-1;}#mermaid-svg-SgLRrdPwk43Uc33q .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-6 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-6 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-6 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-6 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-6 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-6{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-6{stroke-width:-4;}#mermaid-svg-SgLRrdPwk43Uc33q .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-7 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-7 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-7 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-7 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-7 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-7{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-7{stroke-width:-7;}#mermaid-svg-SgLRrdPwk43Uc33q .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-8 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-8 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-8 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-8 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-8 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-8{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-8{stroke-width:-10;}#mermaid-svg-SgLRrdPwk43Uc33q .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-9 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-9 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-9 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-9 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-9 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-9{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-9{stroke-width:-13;}#mermaid-svg-SgLRrdPwk43Uc33q .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-10 rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-10 path,#mermaid-svg-SgLRrdPwk43Uc33q .section-10 circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-10 polygon,#mermaid-svg-SgLRrdPwk43Uc33q .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-10 text{fill:black;}#mermaid-svg-SgLRrdPwk43Uc33q .node-icon-10{font-size:40px;color:black;}#mermaid-svg-SgLRrdPwk43Uc33q .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .edge-depth-10{stroke-width:-16;}#mermaid-svg-SgLRrdPwk43Uc33q .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled,#mermaid-svg-SgLRrdPwk43Uc33q .disabled circle,#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:lightgray;}#mermaid-svg-SgLRrdPwk43Uc33q .disabled text{fill:#efefef;}#mermaid-svg-SgLRrdPwk43Uc33q .section-root rect,#mermaid-svg-SgLRrdPwk43Uc33q .section-root path,#mermaid-svg-SgLRrdPwk43Uc33q .section-root circle,#mermaid-svg-SgLRrdPwk43Uc33q .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-SgLRrdPwk43Uc33q .section-root text{fill:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .section-root span{color:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .section-2 span{color:#ffffff;}#mermaid-svg-SgLRrdPwk43Uc33q .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-SgLRrdPwk43Uc33q .edge{fill:none;}#mermaid-svg-SgLRrdPwk43Uc33q .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-SgLRrdPwk43Uc33q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第8章 异常控制流
中心问题
正常控制流为何改道
改道后如何安全返回
硬件与内核边界
异常表
中断
陷阱和系统调用
故障和终止
操作系统抽象
进程
逻辑控制流
私有地址空间
上下文切换
应用程序接口
fork
waitpid
execve
信号
并发正确性
待处理信号不排队
竞争
阻塞信号
sigsuspend
应用层跳转
setjmp
longjmp
怎么看这张图: 从上往下读。最上层的问题是"控制为什么突然换地方";异常负责硬件到内核的改道,进程把这种能力变成多任务,fork/wait/execve 和信号把能力交给应用程序,最后的同步规则防止这些改道在错误时机相撞。
3.1 前置知识与后续关系
#mermaid-svg-9mTR7lEfsypErceU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9mTR7lEfsypErceU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9mTR7lEfsypErceU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9mTR7lEfsypErceU .error-icon{fill:#552222;}#mermaid-svg-9mTR7lEfsypErceU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9mTR7lEfsypErceU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9mTR7lEfsypErceU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9mTR7lEfsypErceU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9mTR7lEfsypErceU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9mTR7lEfsypErceU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9mTR7lEfsypErceU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9mTR7lEfsypErceU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9mTR7lEfsypErceU .marker.cross{stroke:#333333;}#mermaid-svg-9mTR7lEfsypErceU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9mTR7lEfsypErceU p{margin:0;}#mermaid-svg-9mTR7lEfsypErceU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9mTR7lEfsypErceU .cluster-label text{fill:#333;}#mermaid-svg-9mTR7lEfsypErceU .cluster-label span{color:#333;}#mermaid-svg-9mTR7lEfsypErceU .cluster-label span p{background-color:transparent;}#mermaid-svg-9mTR7lEfsypErceU .label text,#mermaid-svg-9mTR7lEfsypErceU span{fill:#333;color:#333;}#mermaid-svg-9mTR7lEfsypErceU .node rect,#mermaid-svg-9mTR7lEfsypErceU .node circle,#mermaid-svg-9mTR7lEfsypErceU .node ellipse,#mermaid-svg-9mTR7lEfsypErceU .node polygon,#mermaid-svg-9mTR7lEfsypErceU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9mTR7lEfsypErceU .rough-node .label text,#mermaid-svg-9mTR7lEfsypErceU .node .label text,#mermaid-svg-9mTR7lEfsypErceU .image-shape .label,#mermaid-svg-9mTR7lEfsypErceU .icon-shape .label{text-anchor:middle;}#mermaid-svg-9mTR7lEfsypErceU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9mTR7lEfsypErceU .rough-node .label,#mermaid-svg-9mTR7lEfsypErceU .node .label,#mermaid-svg-9mTR7lEfsypErceU .image-shape .label,#mermaid-svg-9mTR7lEfsypErceU .icon-shape .label{text-align:center;}#mermaid-svg-9mTR7lEfsypErceU .node.clickable{cursor:pointer;}#mermaid-svg-9mTR7lEfsypErceU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9mTR7lEfsypErceU .arrowheadPath{fill:#333333;}#mermaid-svg-9mTR7lEfsypErceU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9mTR7lEfsypErceU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9mTR7lEfsypErceU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9mTR7lEfsypErceU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9mTR7lEfsypErceU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9mTR7lEfsypErceU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9mTR7lEfsypErceU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9mTR7lEfsypErceU .cluster text{fill:#333;}#mermaid-svg-9mTR7lEfsypErceU .cluster span{color:#333;}#mermaid-svg-9mTR7lEfsypErceU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9mTR7lEfsypErceU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9mTR7lEfsypErceU rect.text{fill:none;stroke-width:0;}#mermaid-svg-9mTR7lEfsypErceU .icon-shape,#mermaid-svg-9mTR7lEfsypErceU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9mTR7lEfsypErceU .icon-shape p,#mermaid-svg-9mTR7lEfsypErceU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9mTR7lEfsypErceU .icon-shape .label rect,#mermaid-svg-9mTR7lEfsypErceU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9mTR7lEfsypErceU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9mTR7lEfsypErceU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9mTR7lEfsypErceU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} \[第1章_计算机系统漫游]
程序、内核、上下文切换
\[第3章_程序的机器级表示\]
PC、调用、栈、寄存器
\[第4章_处理器体系结构\]
异常、状态码、处理器执行
第8章
异常控制流
候选:第9章 虚拟内存
候选:第10章 系统级 I/O
候选:第12章 并发编程
| 已有基础 | 在本章哪里使用 | 不熟时先补什么 |
|---|---|---|
| PC、顺序执行、跳转、调用和返回 | 理解"控制流突然改变" | \[第3章_程序的机器级表示] 中的控制转移与栈 |
| 指令、特权、异常状态 | 理解硬件为何进入异常处理程序 | \[第4章_处理器体系结构] 中的异常状态码 |
| 程序、内核、上下文切换的直觉 | 理解进程与调度 | \[第1章_计算机系统漫游] 的进程示例 |
| C 的函数、指针和基础流程控制 | 阅读 fork、waitpid 与信号示例 | 能读懂 if、while、函数返回即可 |
!note 候选关联
本库中尚未确认第 9、10、12 章对应笔记,因此上图只把它们作为候选学习方向,而不是已确认的双向链接。
3.2 零基础词汇表
| 词 | 先用一句话记住 | 不要误解成 |
|---|---|---|
| 控制流 | 程序计数器 PC 依次取到哪些指令地址 | 只有 if 和 while 才算控制流 |
| 控制转移 | PC 从当前位置去到一个不相邻的位置 | 一定是程序员主动写的跳转 |
| ECF | 由系统事件触发或参与的"突然改道"控制流 | 只有错误才会发生的机制 |
| 事件 | 让处理器状态发生重要变化的事情 | 必然来自当前那条指令 |
| 异常 | 处理器检测到事件后转去执行处理程序 | C++ 或 Java 的 try/catch 本身 |
| 异常处理程序 | 内核中专门处理某类异常的代码 | 普通用户态回调函数 |
| 进程 | 一个正在执行的程序实例及其运行上下文 | 磁盘上的可执行文件本身 |
| 上下文 | 让一个被暂停的进程能从原处继续的状态集合 | 只有几个通用寄存器 |
| 信号 | 内核或进程发给进程的一条小通知 | 可无限累积的消息队列 |
| 待处理信号 | 已发送、尚未被接收的信号 | 一定马上会执行处理程序 |
| 阻塞信号 | 暂时不允许接收的一类信号 | 信号已经被删除 |
| 竞争 | 正确性取决于多个并发动作谁先发生 | 程序性能比较慢 |
| 非本地跳转 | 跨过正常调用/返回链,直接回到较早保存的位置 | 普通的函数 return |
4 核心知识点逐个讲
4.1 知识点 1:ECF 是系统处理"计划外事件"的通用改道机制(⭐⭐⭐⭐⭐)
为什么重要: 后面的异常、进程、信号和非本地跳转看似互不相同,实际上都在做"让控制权临时离开原来的指令序列"。先抓住这个共同模型,整章才不会变成零散 API 清单。
4.1.1 一句话
**异常控制流(Exceptional Control Flow,ECF)**是指程序原本按某条控制流执行,但因为系统或程序中的事件,控制突然转到另一段处理代码,之后再继续、重试或终止。
!abstract 📌 一句话卡片|ECF
ECF 不是某一条专门的指令,而是一类"控制权因事件而改道"的机制;硬件异常、进程切换、信号和非本地跳转都是它在不同层次的表现。
4.1.2 大白话解释(含三个类比)
正常控制流像你沿着导航一路直行:PC 通常会从一条指令自然走向内存中相邻的下一条。函数调用、返回和条件跳转已经会让路线拐弯,但它们主要响应的是程序变量和代码中写明的逻辑。
系统还有另一类事情:网卡收到了数据、磁盘读完了、定时器到点、访问的内存页还没在内存、子进程结束、用户按下 Ctrl+C。它们不一定写在当前函数的 if 里,却必须被及时处理。ECF 就是系统为这些事情准备的"改道规则"。
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 城市道路的应急车道 | 原路线 → 正常控制流;突发事故 → 事件;应急车道 → ECF | 为什么控制要暂时离开原路线 | 程序改道必须保存精确状态,不能只靠"让一让" |
| 医院分诊 | 正常候诊 → 顺序执行;急症 → 事件;分诊台 → 处理程序 | 不是所有改道都是错误,重点是按类型处理 | 医院会排队;本章的普通信号并不保证逐个排队 |
| 火车调度中心 | 列车运行计划 → 逻辑控制流;道岔/信号变化 → 事件;调度切换 → 上下文切换 | 同一条轨道能在时间上服务不同列车 | 真实 CPU 的状态远复杂于一列火车的位置 |
4.1.3 为什么会有它
- 系统必须响应外部设备和时间,而这些事情不能等程序恰好执行到某个 if。
- 操作系统需要把一个 CPU 在多个程序之间切换,才能提供多任务。
- 应用程序需要请求文件、进程和网络等内核服务;系统调用提供受控入口。
- 错误处理有时不能层层 return,非本地跳转可直接回到统一的恢复点。
4.1.4 ECF 出现在哪些层次
#mermaid-svg-wTpExbPCu3BSmL0J{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wTpExbPCu3BSmL0J .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wTpExbPCu3BSmL0J .error-icon{fill:#552222;}#mermaid-svg-wTpExbPCu3BSmL0J .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wTpExbPCu3BSmL0J .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wTpExbPCu3BSmL0J .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wTpExbPCu3BSmL0J .marker.cross{stroke:#333333;}#mermaid-svg-wTpExbPCu3BSmL0J svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wTpExbPCu3BSmL0J p{margin:0;}#mermaid-svg-wTpExbPCu3BSmL0J .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wTpExbPCu3BSmL0J .cluster-label text{fill:#333;}#mermaid-svg-wTpExbPCu3BSmL0J .cluster-label span{color:#333;}#mermaid-svg-wTpExbPCu3BSmL0J .cluster-label span p{background-color:transparent;}#mermaid-svg-wTpExbPCu3BSmL0J .label text,#mermaid-svg-wTpExbPCu3BSmL0J span{fill:#333;color:#333;}#mermaid-svg-wTpExbPCu3BSmL0J .node rect,#mermaid-svg-wTpExbPCu3BSmL0J .node circle,#mermaid-svg-wTpExbPCu3BSmL0J .node ellipse,#mermaid-svg-wTpExbPCu3BSmL0J .node polygon,#mermaid-svg-wTpExbPCu3BSmL0J .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wTpExbPCu3BSmL0J .rough-node .label text,#mermaid-svg-wTpExbPCu3BSmL0J .node .label text,#mermaid-svg-wTpExbPCu3BSmL0J .image-shape .label,#mermaid-svg-wTpExbPCu3BSmL0J .icon-shape .label{text-anchor:middle;}#mermaid-svg-wTpExbPCu3BSmL0J .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wTpExbPCu3BSmL0J .rough-node .label,#mermaid-svg-wTpExbPCu3BSmL0J .node .label,#mermaid-svg-wTpExbPCu3BSmL0J .image-shape .label,#mermaid-svg-wTpExbPCu3BSmL0J .icon-shape .label{text-align:center;}#mermaid-svg-wTpExbPCu3BSmL0J .node.clickable{cursor:pointer;}#mermaid-svg-wTpExbPCu3BSmL0J .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wTpExbPCu3BSmL0J .arrowheadPath{fill:#333333;}#mermaid-svg-wTpExbPCu3BSmL0J .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wTpExbPCu3BSmL0J .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wTpExbPCu3BSmL0J .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wTpExbPCu3BSmL0J .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wTpExbPCu3BSmL0J .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wTpExbPCu3BSmL0J .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wTpExbPCu3BSmL0J .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wTpExbPCu3BSmL0J .cluster text{fill:#333;}#mermaid-svg-wTpExbPCu3BSmL0J .cluster span{color:#333;}#mermaid-svg-wTpExbPCu3BSmL0J div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wTpExbPCu3BSmL0J .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wTpExbPCu3BSmL0J rect.text{fill:none;stroke-width:0;}#mermaid-svg-wTpExbPCu3BSmL0J .icon-shape,#mermaid-svg-wTpExbPCu3BSmL0J .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wTpExbPCu3BSmL0J .icon-shape p,#mermaid-svg-wTpExbPCu3BSmL0J .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wTpExbPCu3BSmL0J .icon-shape .label rect,#mermaid-svg-wTpExbPCu3BSmL0J .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wTpExbPCu3BSmL0J .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wTpExbPCu3BSmL0J .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wTpExbPCu3BSmL0J :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 处理器与设备
操作系统调度
进程间或内核通知
应用自己的错误恢复
发生事件
事件所在层次
异常
CPU 转入内核处理程序
上下文切换
进程 A 改为进程 B
信号
转入用户信号处理程序
非本地跳转
直接回到保存点
继续、重试或终止
怎么看这张图: 不要把四条分支当成互斥的"替代技术"。它们发生在不同层次,甚至可以串起来:磁盘中断触发内核工作,内核切换进程;子进程退出后,父进程再收到 SIGCHLD。
!example 🎬 动画脑补
程序正在执行一条普通指令。屏幕外一个事件出现,系统先判断它属于哪一层;随后控制转入对应的处理代码。处理完后,系统根据事件性质决定:回到原处、走下一条、重试,还是彻底结束。
| 镜头 | 画面 | 旁白 | 对应关系 |
|---|---|---|---|
| 1 | PC 沿着一串指令向前移动 | "正常情况下,PC 按既定路线前进。" | 正常控制流 |
| 2 | 出现定时器、缺页、子进程结束等图标 | "系统状态也会变化,而且不能被忽略。" | 发生事件 |
| 3 | 分流到四种处理机制 | "事件在哪层,就由哪层的 ECF 接住。" | 事件 → 分支 |
| 4 | 箭头回到继续/重试/终止 | "改道的结果不只有继续,也可能是重试或结束。" | 处理结果 |
4.1.5 容易混淆
| 容易混淆 | 正确理解 | 为什么容易错 | 判断方法 |
|---|---|---|---|
| 普通跳转 vs ECF | 普通跳转通常由程序逻辑直接安排;ECF 强调由事件触发或参与的突变 | 两者最后都会改 PC | 问:改变控制权的直接原因是代码分支,还是系统事件? |
| ECF vs "报错" | ECF 可以服务正常功能,如定时器中断、系统调用和调度 | "异常"这个词容易让人只想到失败 | 问:处理完是否还能正常继续?能,则未必是错误 |
| 并发 vs ECF | ECF 是实现并发的重要基础;并发是多个逻辑流时间重叠的现象 | 两者常一起出现 | 先问"控制怎么改道",再问"多个流是否时间重叠" |
4.1.6 面试 / 表达题
题:为什么说 ECF 是理解操作系统的钥匙?
答题骨架:先说结论------ECF 让系统能在正常程序执行之外处理设备、内核服务、调度和进程间事件。再说理由------异常提供硬件到内核的入口,上下文切换实现多任务,信号让应用能响应异步事件。最后举例------一次磁盘读取会让进程阻塞、内核切换到其他进程,数据到达后再通过中断恢复原进程。
4.1.7 一句话总结
把 ECF 看成"系统级控制流改道",就能把异常、进程、信号和错误恢复放进同一张地图。
4.2 知识点 2:异常是硬件和内核协作完成的一次精确转场(⭐⭐⭐⭐⭐)
为什么重要: 异常是本章最底层的 ECF。理解它,才能明白系统调用为什么能进入内核、缺页为何能"修好后重试"、以及定时器为什么能打断当前程序。
4.2.1 一句话
**异常(exception)**是处理器检测到事件后,通过异常表跳转到内核异常处理程序的控制转移;处理完成后可能返回当前指令、下一条指令,或者不再返回。
!abstract 📌 一句话卡片|异常
异常不是"CPU 随便跳走",而是 CPU 按异常号查异常表、进入对应内核处理程序,再依据异常类别决定返回位置。
4.2.2 大白话解释(含三个类比)
把 CPU 想成严格按工单执行的机器。正常时它一张张处理工单;一旦检测到"缺少材料""外部设备说数据到了""用户要申请特权操作"等事件,它不会猜该找谁,而是用事件编号查一张预先登记的通讯录------异常表------然后转给正确的内核处理程序。
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 医院急诊分诊表 | 症状编号 → 异常号;专科医生 → 异常处理程序 | 同类事件应进入固定处理入口 | 异常表是地址表,CPU 不会靠语义"猜科室" |
| 公司值班电话簿 | 电话编号 → 表项;值班人 → 处理程序 | 为什么异常号能迅速定位处理代码 | 电话可无人接;已正确初始化的异常表必须有对应处理策略 |
| 考场突发情况流程卡 | 停电/火警/身体不适 → 不同处理流程 | 不同事件返回方式不同 | CPU 的保存与恢复状态是硬件精确完成的,不是人工记录 |
4.2.3 异常处理的分工
系统启动时,内核建立异常表:每个异常号对应一个处理程序地址。运行时,CPU 检测到事件、确定异常号、通过表项转入处理程序。CPU 负责保存必要现场和切换特权级;内核代码负责真正处理事件。
内核处理程序 异常表 处理器 用户程序 内核处理程序 异常表 处理器 用户程序 #mermaid-svg-WgvXg4u0pfMmBEpX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WgvXg4u0pfMmBEpX .error-icon{fill:#552222;}#mermaid-svg-WgvXg4u0pfMmBEpX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WgvXg4u0pfMmBEpX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WgvXg4u0pfMmBEpX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WgvXg4u0pfMmBEpX .marker.cross{stroke:#333333;}#mermaid-svg-WgvXg4u0pfMmBEpX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WgvXg4u0pfMmBEpX p{margin:0;}#mermaid-svg-WgvXg4u0pfMmBEpX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WgvXg4u0pfMmBEpX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-WgvXg4u0pfMmBEpX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-WgvXg4u0pfMmBEpX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-WgvXg4u0pfMmBEpX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-WgvXg4u0pfMmBEpX .sequenceNumber{fill:white;}#mermaid-svg-WgvXg4u0pfMmBEpX #sequencenumber{fill:#333;}#mermaid-svg-WgvXg4u0pfMmBEpX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-WgvXg4u0pfMmBEpX .messageText{fill:#333;stroke:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WgvXg4u0pfMmBEpX .labelText,#mermaid-svg-WgvXg4u0pfMmBEpX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .loopText,#mermaid-svg-WgvXg4u0pfMmBEpX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-WgvXg4u0pfMmBEpX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-WgvXg4u0pfMmBEpX .noteText,#mermaid-svg-WgvXg4u0pfMmBEpX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-WgvXg4u0pfMmBEpX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WgvXg4u0pfMmBEpX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WgvXg4u0pfMmBEpX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WgvXg4u0pfMmBEpX .actorPopupMenu{position:absolute;}#mermaid-svg-WgvXg4u0pfMmBEpX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-WgvXg4u0pfMmBEpX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WgvXg4u0pfMmBEpX .actor-man circle,#mermaid-svg-WgvXg4u0pfMmBEpX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-WgvXg4u0pfMmBEpX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 执行当前指令或外部事件到达检测事件并确定异常号保存返回位置与必要状态用异常号查表对应处理程序地址转入内核模式并执行处理程序继续、重试或终止的结果必要时恢复状态并返回
怎么看这张图: 先看 CPU 的两次关键动作:它不是直接"运行一段神秘代码",而是先保存状态、再查表。最后一箭头写着"必要时",因为终止类异常不会回到用户程序。
!note 异常与普通函数调用的关键差异
两者都像"转去执行另一段代码",但异常的返回地址由异常类型决定;CPU 还会保存额外状态;若从用户态进入内核态,保存现场使用的是内核栈;处理程序拥有内核模式的特权。
4.2.4 四类异常:最该背的是"原因、同步性、回到哪里"
| 类别 | 常见原因 | 同步 / 异步 | 处理后控制流 | 初学者记忆法 |
|---|---|---|---|---|
| 中断 interrupt | 外部 I/O 设备、定时器 | 异步 | 下一条指令 | 外面有人按门铃,当前指令先做完 |
| 陷阱 trap | 程序有意执行特殊指令 | 同步 | 下一条指令 | 程序主动走正规窗口申请服务 |
| 故障 fault | 可能修复的错误,如缺页 | 同步 | 修好后重执行当前指令;修不好则终止 | 这一题材料没拿到,补齐后重做 |
| 终止 abort | 不可恢复的致命硬件错误 | 同步 | 不返回被中断流 | 考场被永久关闭,不能继续答题 |
#mermaid-svg-RnSzxN0hGv2cGgp7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RnSzxN0hGv2cGgp7 .error-icon{fill:#552222;}#mermaid-svg-RnSzxN0hGv2cGgp7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RnSzxN0hGv2cGgp7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .marker.cross{stroke:#333333;}#mermaid-svg-RnSzxN0hGv2cGgp7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RnSzxN0hGv2cGgp7 p{margin:0;}#mermaid-svg-RnSzxN0hGv2cGgp7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .cluster-label text{fill:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .cluster-label span{color:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .cluster-label span p{background-color:transparent;}#mermaid-svg-RnSzxN0hGv2cGgp7 .label text,#mermaid-svg-RnSzxN0hGv2cGgp7 span{fill:#333;color:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .node rect,#mermaid-svg-RnSzxN0hGv2cGgp7 .node circle,#mermaid-svg-RnSzxN0hGv2cGgp7 .node ellipse,#mermaid-svg-RnSzxN0hGv2cGgp7 .node polygon,#mermaid-svg-RnSzxN0hGv2cGgp7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .rough-node .label text,#mermaid-svg-RnSzxN0hGv2cGgp7 .node .label text,#mermaid-svg-RnSzxN0hGv2cGgp7 .image-shape .label,#mermaid-svg-RnSzxN0hGv2cGgp7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-RnSzxN0hGv2cGgp7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .rough-node .label,#mermaid-svg-RnSzxN0hGv2cGgp7 .node .label,#mermaid-svg-RnSzxN0hGv2cGgp7 .image-shape .label,#mermaid-svg-RnSzxN0hGv2cGgp7 .icon-shape .label{text-align:center;}#mermaid-svg-RnSzxN0hGv2cGgp7 .node.clickable{cursor:pointer;}#mermaid-svg-RnSzxN0hGv2cGgp7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .arrowheadPath{fill:#333333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RnSzxN0hGv2cGgp7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RnSzxN0hGv2cGgp7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RnSzxN0hGv2cGgp7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RnSzxN0hGv2cGgp7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .cluster text{fill:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 .cluster span{color:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RnSzxN0hGv2cGgp7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RnSzxN0hGv2cGgp7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-RnSzxN0hGv2cGgp7 .icon-shape,#mermaid-svg-RnSzxN0hGv2cGgp7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RnSzxN0hGv2cGgp7 .icon-shape p,#mermaid-svg-RnSzxN0hGv2cGgp7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RnSzxN0hGv2cGgp7 .icon-shape .label rect,#mermaid-svg-RnSzxN0hGv2cGgp7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RnSzxN0hGv2cGgp7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RnSzxN0hGv2cGgp7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RnSzxN0hGv2cGgp7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是,主动请求
是,可修复
是,不可修复
是
否
事件发生
来自当前指令?
中断
外部设备事件
陷阱
系统调用
故障
例如缺页
终止
回到下一条
修好了吗?
重执行当前指令
终止程序
怎么看这张图: 先判断事件是不是由当前指令直接造成,再看它是否可恢复。不要死背名词,始终用"发生原因"和"控制回到哪里"做判断。
4.2.5 系统调用:一种有意的陷阱
用户程序不能直接执行特权指令或随意读写内核内存,却又需要读文件、创建进程、退出程序等服务。系统调用就是受控入口:程序执行专门的陷阱指令,CPU 转入内核,内核检查参数并执行服务,随后通常回到系统调用后的下一条指令。
| 概念 | 看起来像什么 | 实际上有什么不同 |
|---|---|---|
| 普通函数调用 | 调用另一段代码再返回 | 通常仍在用户态,使用调用者可见的地址空间与栈规则 |
| 系统调用 | "调用一个系统函数" | 通过陷阱进入内核态,可执行特权操作并使用内核资源 |
| C 库包装函数 | 如同普通 C 接口 | 常负责整理参数、触发系统调用、把返回状态交给应用 |
!warning 不要把库函数和系统调用强行画等号
对使用者来说,它们常配合出现;但"库函数包装了系统调用"和"每个库函数都是一次系统调用"是两回事。本章重点是理解受控地从用户态进入内核态。
4.2.6 Linux/x86-64 例子怎样理解
本章举了除法错误、一般保护故障、缺页和机器检查等例子。对初学者而言,不必背异常号,先抓住三种结果:
- 可恢复的缺页:所需页面暂不在物理内存,处理程序把页面准备好,再重做那条指令。
- 通常不可恢复的用户程序错误:例如非法内存访问,系统可能报告段故障并终止进程。
- 严重硬件错误:终止处理程序不把控制还给应用。
4.2.7 容易混淆
| 容易混淆 | 正确理解 | 为什么容易错 | 判断方法 |
|---|---|---|---|
| 中断 vs 陷阱 | 中断来自处理器外部且异步;陷阱由当前指令主动触发且同步 | 二者都会转入内核 | 看触发源:设备/定时器还是当前指令 |
| 故障 vs 终止 | 故障可能修复并重试;终止不可恢复且不返回 | 都可能由"错误"引起 | 看是否可能再次执行故障指令 |
| 缺页 vs 非法地址访问 | 缺页不代表地址非法;它可能只表示所需页还未在内存 | 两者都涉及内存访问 | 看内核是否能准备页面后重试 |
| 系统调用 vs 函数调用 | 系统调用用陷阱进入内核;普通调用一般不改变特权级 | C 代码写法都像函数 | 看是否在请求内核服务 |
4.2.8 面试 / 表达题
题:中断、陷阱、故障、终止怎样快速区分?
答题骨架:先给分类维度------触发来源、同步性和返回位置。中断来自外部设备,异步且回到下一条;陷阱是程序主动请求服务,同步且回到下一条;故障是可能修复的同步问题,修复后重试当前指令;终止是不可恢复问题,不回到应用程序。
4.2.9 一句话总结
异常表解决"该找谁",异常类别决定"处理后回哪里";这两件事合起来,才让系统能精确地打断并恢复控制流。
4.3 知识点 3:进程用两个"独占假象"让多任务变得可用(⭐⭐⭐⭐⭐)
为什么重要: 进程是操作系统把底层异常机制提升成可编程抽象的结果。理解进程,才能解释为什么你的程序看似独占 CPU 和内存,却又会被调度、被信号打断。
4.3.1 一句话
**进程(process)**是一个正在执行的程序实例及其上下文;它给应用程序两个关键假象:独立的逻辑控制流,以及私有的地址空间。
!abstract 📌 一句话卡片|进程
程序是静态的代码和数据,进程是它正在运行时的完整现场;多个进程交替使用 CPU,每个进程却感觉自己在连续执行。
4.3.2 大白话解释(含三个类比)
同一个可执行文件可以被运行多次,每次运行得到的都是不同的进程。进程不只是一段代码,还带着"此刻执行到哪、寄存器里是什么、栈里有什么、打开了哪些文件"等现场。内核在不同现场之间切换,才让一台机器同时服务多个程序。
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 舞台上的多场排练 | 每场排练 → 一个进程;舞台 → CPU;暂停后保留的布景和台词进度 → 上下文 | 为什么一个 CPU 能轮流服务多个任务 | 真实 CPU 的切换远快于人工换场,且状态保存更精细 |
| 酒店的独立房间 | 房间 → 私有地址空间;客人 → 进程 | 一个进程的普通内存不应被另一个进程随意改写 | 进程间仍可通过内核机制共享或通信,不是绝对隔离 |
| 多人轮流使用同一台打印机 | 每人一次使用时间 → 时间片;排队规则 → 调度器 | "同时在运行"常是快速轮换的视觉效果 | 多核时确实可能物理同时执行,不只是轮换 |
4.3.3 逻辑控制流、并发与并行
如果单步观察一个进程,你会看到它自己的 PC 值序列,这就是它的逻辑控制流。即使 CPU 在中间去运行了别的进程,回来后它仍从原来位置接着走,于是该进程感觉自己独占 CPU。
| 词 | 准确定义 | 关键判断 |
|---|---|---|
| 逻辑控制流 | 一个进程自己的 PC 值序列 | 从该进程视角看,指令按它自己的顺序推进 |
| 并发 | 两个逻辑流在时间区间上重叠 | 即使只有一个 CPU 核也可以并发 |
| 并行 | 并发流在不同 CPU 核或不同机器上同时执行 | 并行一定并发,并发不一定并行 |
| 多任务 | 多个进程轮流使用处理器的现象 | 一个进程一次占用的一段时间叫时间片 |
#mermaid-svg-II5wrH70WSeCETCO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-II5wrH70WSeCETCO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-II5wrH70WSeCETCO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-II5wrH70WSeCETCO .error-icon{fill:#552222;}#mermaid-svg-II5wrH70WSeCETCO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-II5wrH70WSeCETCO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-II5wrH70WSeCETCO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-II5wrH70WSeCETCO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-II5wrH70WSeCETCO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-II5wrH70WSeCETCO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-II5wrH70WSeCETCO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-II5wrH70WSeCETCO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-II5wrH70WSeCETCO .marker.cross{stroke:#333333;}#mermaid-svg-II5wrH70WSeCETCO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-II5wrH70WSeCETCO p{margin:0;}#mermaid-svg-II5wrH70WSeCETCO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-II5wrH70WSeCETCO .cluster-label text{fill:#333;}#mermaid-svg-II5wrH70WSeCETCO .cluster-label span{color:#333;}#mermaid-svg-II5wrH70WSeCETCO .cluster-label span p{background-color:transparent;}#mermaid-svg-II5wrH70WSeCETCO .label text,#mermaid-svg-II5wrH70WSeCETCO span{fill:#333;color:#333;}#mermaid-svg-II5wrH70WSeCETCO .node rect,#mermaid-svg-II5wrH70WSeCETCO .node circle,#mermaid-svg-II5wrH70WSeCETCO .node ellipse,#mermaid-svg-II5wrH70WSeCETCO .node polygon,#mermaid-svg-II5wrH70WSeCETCO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-II5wrH70WSeCETCO .rough-node .label text,#mermaid-svg-II5wrH70WSeCETCO .node .label text,#mermaid-svg-II5wrH70WSeCETCO .image-shape .label,#mermaid-svg-II5wrH70WSeCETCO .icon-shape .label{text-anchor:middle;}#mermaid-svg-II5wrH70WSeCETCO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-II5wrH70WSeCETCO .rough-node .label,#mermaid-svg-II5wrH70WSeCETCO .node .label,#mermaid-svg-II5wrH70WSeCETCO .image-shape .label,#mermaid-svg-II5wrH70WSeCETCO .icon-shape .label{text-align:center;}#mermaid-svg-II5wrH70WSeCETCO .node.clickable{cursor:pointer;}#mermaid-svg-II5wrH70WSeCETCO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-II5wrH70WSeCETCO .arrowheadPath{fill:#333333;}#mermaid-svg-II5wrH70WSeCETCO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-II5wrH70WSeCETCO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-II5wrH70WSeCETCO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-II5wrH70WSeCETCO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-II5wrH70WSeCETCO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-II5wrH70WSeCETCO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-II5wrH70WSeCETCO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-II5wrH70WSeCETCO .cluster text{fill:#333;}#mermaid-svg-II5wrH70WSeCETCO .cluster span{color:#333;}#mermaid-svg-II5wrH70WSeCETCO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-II5wrH70WSeCETCO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-II5wrH70WSeCETCO rect.text{fill:none;stroke-width:0;}#mermaid-svg-II5wrH70WSeCETCO .icon-shape,#mermaid-svg-II5wrH70WSeCETCO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-II5wrH70WSeCETCO .icon-shape p,#mermaid-svg-II5wrH70WSeCETCO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-II5wrH70WSeCETCO .icon-shape .label rect,#mermaid-svg-II5wrH70WSeCETCO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-II5wrH70WSeCETCO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-II5wrH70WSeCETCO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-II5wrH70WSeCETCO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 同一条时间轴
A 的逻辑流
B 的逻辑流
C 的逻辑流
进程 A 运行
进程 B 运行
进程 A 继续
进程 C 运行
怎么看这张图: 实际 CPU 时间轴被切成片段;A 的两段在物理时间上被 B 隔开,但对于 A 而言,第二段仍接着第一段的寄存器、PC 和栈继续执行。这正是"逻辑连续、物理交替"。
4.3.4 私有地址空间与用户/内核模式
每个进程都有自己的虚拟地址空间。对一个用户程序来说,通常能直接使用的是用户区中的代码、数据、堆、共享库映射和用户栈;内核区对用户程序不可直接访问。
处理器还通过模式位区分权限:
| 模式 | 能做什么 | 什么时候进入 |
|---|---|---|
| 用户模式 | 运行普通应用代码;不能执行特权指令,也不能直接访问内核区 | 应用程序正常执行时 |
| 内核模式 | 可执行特权操作并访问系统资源 | 处理中断、故障、陷阱/系统调用等异常时 |
!important 内核不是"另一个普通进程"
内核是管理所有进程的一组代码和数据结构。当它代表某个进程处理系统调用时,仍是在处理该进程的请求;不要把"进入内核模式"误读为"用户程序变成了一个独立内核进程"。
4.3.5 上下文切换:多任务真正发生了什么
内核决定换一个进程运行,称为调度 ;真正保存旧现场、恢复新现场并转交控制权,称为上下文切换。需要保存/恢复的上下文不止寄存器,还包括 PC、用户栈、状态寄存器、内核栈、页表、进程表信息和打开文件信息等。
进程 B 内核与调度器 进程 A 进程 B 内核与调度器 进程 A #mermaid-svg-ha3mbmd3ZpeGuok0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ha3mbmd3ZpeGuok0 .error-icon{fill:#552222;}#mermaid-svg-ha3mbmd3ZpeGuok0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ha3mbmd3ZpeGuok0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ha3mbmd3ZpeGuok0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ha3mbmd3ZpeGuok0 .marker.cross{stroke:#333333;}#mermaid-svg-ha3mbmd3ZpeGuok0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ha3mbmd3ZpeGuok0 p{margin:0;}#mermaid-svg-ha3mbmd3ZpeGuok0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ha3mbmd3ZpeGuok0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ha3mbmd3ZpeGuok0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ha3mbmd3ZpeGuok0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ha3mbmd3ZpeGuok0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ha3mbmd3ZpeGuok0 .sequenceNumber{fill:white;}#mermaid-svg-ha3mbmd3ZpeGuok0 #sequencenumber{fill:#333;}#mermaid-svg-ha3mbmd3ZpeGuok0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ha3mbmd3ZpeGuok0 .messageText{fill:#333;stroke:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ha3mbmd3ZpeGuok0 .labelText,#mermaid-svg-ha3mbmd3ZpeGuok0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .loopText,#mermaid-svg-ha3mbmd3ZpeGuok0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ha3mbmd3ZpeGuok0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ha3mbmd3ZpeGuok0 .noteText,#mermaid-svg-ha3mbmd3ZpeGuok0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ha3mbmd3ZpeGuok0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ha3mbmd3ZpeGuok0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ha3mbmd3ZpeGuok0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ha3mbmd3ZpeGuok0 .actorPopupMenu{position:absolute;}#mermaid-svg-ha3mbmd3ZpeGuok0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ha3mbmd3ZpeGuok0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ha3mbmd3ZpeGuok0 .actor-man circle,#mermaid-svg-ha3mbmd3ZpeGuok0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ha3mbmd3ZpeGuok0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 系统调用阻塞,或定时器中断到达保存 A 的上下文选择可运行的 B恢复 B 的上下文从上次暂停处继续之后发生中断或阻塞保存 B,恢复 AA 从原位置继续
怎么看这张图: 上下文切换不是简单把 PC 改成另一个程序的入口。若不保存 A 的寄存器、栈和地址空间信息,A 回来后就无法保持"自己从未被打断"的假象。
!example 🎬 动画脑补
进程 A 发起磁盘读取,数据还没来。内核不让 CPU 空等,先把 A 的现场放进"存档柜",取出 B 的存档继续运行。磁盘完成后触发中断,内核再把 A 的存档恢复,A 看起来像只是一次 read 之后继续走。
| 镜头 | 画面 | 旁白 | 对应关系 |
|---|---|---|---|
| 1 | A 读磁盘后停住 | "A 需要等慢设备。" | A → 内核 |
| 2 | 内核保存 A 的现场 | "先保存,才能原样回来。" | 保存 A |
| 3 | B 开始运行 | "CPU 转去做别的有用工作。" | 恢复 B |
| 4 | 磁盘中断到达,A 恢复 | "数据到了,A 从 read 之后继续。" | B → 内核 → A |
4.3.6 容易混淆
| 容易混淆 | 正确理解 | 为什么容易错 | 判断方法 |
|---|---|---|---|
| 程序 vs 进程 | 程序是静态文件;进程是运行实例和上下文 | 日常语言常把"运行程序"统称为程序 | 问:它有 PID、寄存器和地址空间吗?有则是进程 |
| 并发 vs 并行 | 并发看时间重叠;并行还要求不同核/机器同时执行 | "同时"一词太模糊 | 先问是否时间重叠,再问是否物理同时 |
| 上下文切换 vs 函数调用 | 切换可换地址空间、进程和内核状态;函数调用通常在同一进程内 | 两者都有保存/恢复 | 看是否从一个进程切到另一个进程 |
| 用户模式 vs 内核模式 | 是处理器权限状态,不等价于用户/管理员这个人 | 名称很像账户权限 | 问:当前代码能否执行特权指令、访问内核内存? |
4.3.7 面试 / 表达题
题:单核机器为什么也能让多个程序"同时运行"?
答题骨架:单核通常不是真正的物理同时,而是内核把 CPU 时间切成时间片。每次切换时保存当前进程上下文,恢复另一个进程上下文;切换足够快,每个进程的逻辑控制流都显得连续,于是形成并发和多任务的效果。
4.3.8 一句话总结
进程用上下文切换复用 CPU,用私有地址空间隔离内存;"独占"是给程序的抽象,不是机器此刻只运行它的事实。
4.4 知识点 4:系统调用错误检查是进程控制代码的安全带(⭐⭐⭐)
为什么重要: fork、waitpid、execve 和信号相关函数都可能失败。忽略返回值会把真正的原因埋掉,后续错误反而更难定位;本节的包装思路能让示例代码既简洁又不假装"系统调用从不失败"。
4.4.1 一句话
许多 Unix 系统级函数失败时返回约定的错误值,并通过全局变量 errno 说明错误类别;正常程序应立即检查返回值,并在需要时报告 errno 对应的信息。
4.4.2 大白话解释(含三个类比)
把系统调用看成向服务窗口提交申请:拿到"失败回执"时,不能假装申请成功再继续办下一步;errno 像失败原因编号,必须在失败的现场马上读取。
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 快递签收回执 | 返回值 → 是否送达;原因码 → 未送达原因 | 为什么先看成败再做后续动作 | 系统调用的错误值和 errno 由接口约定,不是自然语言回执 |
| 医院检验单 | 检验失败标记 → 返回值;异常代码 → errno | 错误不是"没有信息",而是有分类信息 | errno 不是诊断报告,具体含义需查文档 |
| 安全带 | 错误检查 → 防护;业务代码 → 行驶 | 它不让程序更快,却避免小问题演变成事故 | 错误处理仍要按业务选择恢复、重试或退出 |
4.4.3 一个常见的包装模式
教材用"首字母大写的包装函数"演示一种写法:包装函数调用底层函数,若失败就统一报告错误;正常路径只拿到已经检查过的结果。
c
/* 示例:普通流程中的包装思路,不适用于信号处理程序 */
pid_t Fork(void)
{
pid_t pid = fork();
if (pid < 0) {
unix_error("fork failed");
}
return pid;
}
!warning errno 的使用边界
先检查函数是否失败,再读取 errno。不要在一个成功调用之后把 errno 当作"本次调用状态";也不要在信号处理程序里随意调用会改写 errno 的函数而不保存、恢复它。
#mermaid-svg-GV4YCTzElouCeStd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GV4YCTzElouCeStd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GV4YCTzElouCeStd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GV4YCTzElouCeStd .error-icon{fill:#552222;}#mermaid-svg-GV4YCTzElouCeStd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GV4YCTzElouCeStd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GV4YCTzElouCeStd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GV4YCTzElouCeStd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GV4YCTzElouCeStd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GV4YCTzElouCeStd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GV4YCTzElouCeStd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GV4YCTzElouCeStd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GV4YCTzElouCeStd .marker.cross{stroke:#333333;}#mermaid-svg-GV4YCTzElouCeStd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GV4YCTzElouCeStd p{margin:0;}#mermaid-svg-GV4YCTzElouCeStd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GV4YCTzElouCeStd .cluster-label text{fill:#333;}#mermaid-svg-GV4YCTzElouCeStd .cluster-label span{color:#333;}#mermaid-svg-GV4YCTzElouCeStd .cluster-label span p{background-color:transparent;}#mermaid-svg-GV4YCTzElouCeStd .label text,#mermaid-svg-GV4YCTzElouCeStd span{fill:#333;color:#333;}#mermaid-svg-GV4YCTzElouCeStd .node rect,#mermaid-svg-GV4YCTzElouCeStd .node circle,#mermaid-svg-GV4YCTzElouCeStd .node ellipse,#mermaid-svg-GV4YCTzElouCeStd .node polygon,#mermaid-svg-GV4YCTzElouCeStd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GV4YCTzElouCeStd .rough-node .label text,#mermaid-svg-GV4YCTzElouCeStd .node .label text,#mermaid-svg-GV4YCTzElouCeStd .image-shape .label,#mermaid-svg-GV4YCTzElouCeStd .icon-shape .label{text-anchor:middle;}#mermaid-svg-GV4YCTzElouCeStd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GV4YCTzElouCeStd .rough-node .label,#mermaid-svg-GV4YCTzElouCeStd .node .label,#mermaid-svg-GV4YCTzElouCeStd .image-shape .label,#mermaid-svg-GV4YCTzElouCeStd .icon-shape .label{text-align:center;}#mermaid-svg-GV4YCTzElouCeStd .node.clickable{cursor:pointer;}#mermaid-svg-GV4YCTzElouCeStd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GV4YCTzElouCeStd .arrowheadPath{fill:#333333;}#mermaid-svg-GV4YCTzElouCeStd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GV4YCTzElouCeStd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GV4YCTzElouCeStd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GV4YCTzElouCeStd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GV4YCTzElouCeStd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GV4YCTzElouCeStd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GV4YCTzElouCeStd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GV4YCTzElouCeStd .cluster text{fill:#333;}#mermaid-svg-GV4YCTzElouCeStd .cluster span{color:#333;}#mermaid-svg-GV4YCTzElouCeStd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GV4YCTzElouCeStd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GV4YCTzElouCeStd rect.text{fill:none;stroke-width:0;}#mermaid-svg-GV4YCTzElouCeStd .icon-shape,#mermaid-svg-GV4YCTzElouCeStd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GV4YCTzElouCeStd .icon-shape p,#mermaid-svg-GV4YCTzElouCeStd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GV4YCTzElouCeStd .icon-shape .label rect,#mermaid-svg-GV4YCTzElouCeStd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GV4YCTzElouCeStd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GV4YCTzElouCeStd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GV4YCTzElouCeStd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
调用系统级函数
返回值表示成功?
继续使用结果
立即读取 errno
报告、恢复、重试或安全退出
怎么看这张图: 关键不是"所有错误都退出",而是不要跳过 B。后续策略由业务决定,但必须建立在已确认成功或已理解失败原因之上。
4.4.4 容易混淆
| 容易混淆 | 正确理解 |
|---|---|
| 返回值 vs errno | 返回值先告诉你是否失败;失败后 errno 再说明失败类别 |
| 包装函数 vs 忽略错误 | 包装是把检查集中起来,不是把错误检查删掉 |
| 普通错误路径 vs 信号处理程序 | 普通路径可使用很多诊断函数;信号处理程序受异步安全规则限制 |
4.4.5 面试 / 表达题
题:为什么系统编程里不能只判断指针是否为空或只看打印结果?
答题骨架:系统调用会用各自的返回约定报告失败,失败原因通常在 errno。只看表面现象会把真正出错点延后,尤其在 fork、waitpid、文件和信号相关代码里,后续行为可能已经没有意义。
4.4.6 一句话总结
返回值决定"有没有成功",errno 帮你解释"为什么没成功";先检查,才谈后续逻辑。
4.5 知识点 5:fork、waitpid 与 execve 组成 Unix 进程控制的基本三件套(⭐⭐⭐⭐⭐)
为什么重要: 这是 shell、服务器和大量系统程序的基本骨架。真正理解它们,不是记住函数签名,而是知道谁创建进程、谁替换程序、谁回收退出状态,以及哪些执行顺序不能假设。
4.5.1 一句话
fork 创建一个子进程,waitpid 等待并回收子进程,execve 在当前进程中装载新程序;三者分工不同,常按"父进程 fork → 子进程 execve → 父进程 waitpid"的方式配合。
!abstract 📌 一句话卡片|fork / waitpid / execve
fork 负责"多一个进程",execve 负责"这个进程换成另一个程序",waitpid 负责"父进程收尾并取得子进程状态"。
4.5.2 大白话解释(含三个类比)
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 复印一份正在填写的表格 | fork 后父子都有相同初始内容 | 为什么子进程一开始像父进程的副本 | 实际系统有写时复制等实现细节;不能把它理解为永远共享普通变量 |
| 招人、换岗、离职登记 | fork → 新员工;execve → 换到新岗位;waitpid → 办离职/回收档案 | 三个函数的职责边界 | 进程不是人,PID、文件描述符和地址空间有严格语义 |
| 厨房派单 | 父进程 → 主厨;子进程 → 新厨师;execve → 按新菜谱工作;waitpid → 主厨等结果 | shell 为什么要创建子进程再运行命令 | 不同进程的执行顺序不能由"谁先接单"猜出 |
4.5.3 先记住进程的三种状态
| 状态 | 含义 | 常见触发 |
|---|---|---|
| 运行 | 正在 CPU 上执行,或已就绪等待调度 | 正常执行、收到 SIGCONT 后恢复 |
| 停止 | 被挂起,不会被调度 | 收到 SIGSTOP、SIGTSTP 等停止信号 |
| 终止 | 永久停止 | 从 main 返回、调用 exit、收到默认行为为终止的信号 |
终止不等于立刻从系统里消失。父进程尚未回收其退出状态前,子进程处于**僵死进程(zombie)**状态;长时间运行的 shell 或服务器若不回收,会浪费系统资源。
4.5.4 fork:调用一次,返回两次
fork 的返回值是区分父子进程的钥匙:
| 执行者 | fork 返回值 | 含义 |
|---|---|---|
| 父进程 | 新子进程的 PID(正数) | 创建成功,父进程知道孩子是谁 |
| 子进程 | 0 | 这是新创建的子进程 |
| 调用失败 | -1 | 没有创建子进程,应处理错误 |
fork 之后最重要的四件事:
- 父子是并发的独立进程:不能假设哪一个先打印、先结束或先执行某条语句。
- 初始地址空间内容相同,但随后修改相互独立:父子各自拥有私有地址空间。
- 打开文件描述符会被子进程继承:父子可以都向同一终端或文件输出,因此输出顺序会交错。
- 同一处 fork 只在源码中写一次,却会在父、子两个进程中分别返回:多次或嵌套 fork 时,画进程图比凭感觉数更可靠。
4.5.5 waitpid:等待与回收不是可选的"清理细节"
waitpid 让父进程等待符合条件的子进程终止或状态变化,并取得该子进程的 PID 与状态。最常见的两种等待集合:
| 调用意图 | 典型 pid 参数 | 含义 |
|---|---|---|
| 等一个指定子进程 | 某个正 PID | 只等这个子进程 |
| 等任意一个子进程 | -1 | 等待调用者的任意子进程 |
常见选项与状态宏:
| 项目 | 作用 | 先记住什么 |
|---|---|---|
| WNOHANG | 若没有符合条件的已结束子进程,立即返回 | 不想阻塞时使用 |
| WUNTRACED | 子进程停止时也可返回 | 作业控制需要关注停止状态 |
| WCONTINUED | 被停止子进程收到 SIGCONT 恢复时也可返回 | 需要追踪继续运行时使用 |
| WIFEXITED | 判断子进程是否正常退出 | 为真时才能取普通退出码 |
| WEXITSTATUS | 取正常退出状态 | 先确认 WIFEXITED 为真 |
| WIFSIGNALED | 判断是否因未捕获信号终止 | 为真时可查看终止信号 |
!warning 僵死进程与孤儿进程不是同一个概念
僵死进程是"已终止但还没被父进程回收";孤儿进程是"父进程先终止了"。教材说明,孤儿会由 init 进程接管并回收;但长时间运行的父进程仍应主动回收自己的子进程。
4.5.6 execve:不新建进程,而是把当前进程"换脑子"
execve 在当前进程中加载并运行一个新的可执行文件。成功时它不返回到原来的调用点,因为原来的用户地址空间已经被新程序替换。
| 问题 | fork | execve |
|---|---|---|
| 是否创建新进程 | 是 | 否 |
| PID 是否是新 PID | 子进程有新 PID | 仍是当前进程的 PID |
| 地址空间 | 子进程获得父进程初始内容的独立副本 | 当前进程的用户地址空间被新程序替换 |
| 打开的文件描述符 | 子进程继承 | 当前进程原有已打开描述符会继承到新程序 |
| 正常返回 | 父、子都会返回 | 成功时不返回;失败才返回错误 |
!important 程序与进程再区分一次
程序是磁盘上的代码和数据;进程是执行中的实例。fork 产生新的进程实例,execve 让已有进程实例去执行另一份程序。
4.5.7 shell 的经典流程
#mermaid-svg-8uqYIsAXHPjcR5Ct{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8uqYIsAXHPjcR5Ct .error-icon{fill:#552222;}#mermaid-svg-8uqYIsAXHPjcR5Ct .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8uqYIsAXHPjcR5Ct .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .marker.cross{stroke:#333333;}#mermaid-svg-8uqYIsAXHPjcR5Ct svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8uqYIsAXHPjcR5Ct p{margin:0;}#mermaid-svg-8uqYIsAXHPjcR5Ct .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .cluster-label text{fill:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .cluster-label span{color:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .cluster-label span p{background-color:transparent;}#mermaid-svg-8uqYIsAXHPjcR5Ct .label text,#mermaid-svg-8uqYIsAXHPjcR5Ct span{fill:#333;color:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .node rect,#mermaid-svg-8uqYIsAXHPjcR5Ct .node circle,#mermaid-svg-8uqYIsAXHPjcR5Ct .node ellipse,#mermaid-svg-8uqYIsAXHPjcR5Ct .node polygon,#mermaid-svg-8uqYIsAXHPjcR5Ct .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .rough-node .label text,#mermaid-svg-8uqYIsAXHPjcR5Ct .node .label text,#mermaid-svg-8uqYIsAXHPjcR5Ct .image-shape .label,#mermaid-svg-8uqYIsAXHPjcR5Ct .icon-shape .label{text-anchor:middle;}#mermaid-svg-8uqYIsAXHPjcR5Ct .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .rough-node .label,#mermaid-svg-8uqYIsAXHPjcR5Ct .node .label,#mermaid-svg-8uqYIsAXHPjcR5Ct .image-shape .label,#mermaid-svg-8uqYIsAXHPjcR5Ct .icon-shape .label{text-align:center;}#mermaid-svg-8uqYIsAXHPjcR5Ct .node.clickable{cursor:pointer;}#mermaid-svg-8uqYIsAXHPjcR5Ct .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .arrowheadPath{fill:#333333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8uqYIsAXHPjcR5Ct .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8uqYIsAXHPjcR5Ct .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8uqYIsAXHPjcR5Ct .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8uqYIsAXHPjcR5Ct .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .cluster text{fill:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct .cluster span{color:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8uqYIsAXHPjcR5Ct .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8uqYIsAXHPjcR5Ct rect.text{fill:none;stroke-width:0;}#mermaid-svg-8uqYIsAXHPjcR5Ct .icon-shape,#mermaid-svg-8uqYIsAXHPjcR5Ct .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8uqYIsAXHPjcR5Ct .icon-shape p,#mermaid-svg-8uqYIsAXHPjcR5Ct .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8uqYIsAXHPjcR5Ct .icon-shape .label rect,#mermaid-svg-8uqYIsAXHPjcR5Ct .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8uqYIsAXHPjcR5Ct .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8uqYIsAXHPjcR5Ct .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8uqYIsAXHPjcR5Ct :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
shell 读入命令行
解析参数 argv
内置命令?
shell 自己处理
fork 创建子进程
当前是子进程?
execve 装载目标程序
前台作业?
waitpid 等待并回收
回到提示符
目标程序终止
等待下一条命令
怎么看这张图: shell 不直接把自己替换成用户命令,否则就无法继续接受下一条命令。它先 fork,让子进程 execve;父 shell 对前台作业等待,对后台作业先返回提示符,后续再用信号处理回收。
!example 🎬 动画脑补
shell 收到一条外部命令,先复制出一个子进程。子进程马上把自己的程序内容换成目标命令;父 shell 没有变,若是前台命令就等待孩子结束,若是后台命令就先继续接收输入。
| 镜头 | 画面 | 旁白 | 对应关系 |
|---|---|---|---|
| 1 | shell 读到命令行 | "先判断是不是 shell 自己能处理的内置命令。" | 解析与判断 |
| 2 | fork 分出父、子两条线 | "外部命令需要一个新进程。" | fork |
| 3 | 子线换成目标程序 | "execve 替换子进程的程序,不额外创建第三个进程。" | 子进程 → execve |
| 4 | 父线等待或回提示符 | "前台要等,后台先继续。" | waitpid / 返回 |
4.5.8 让进程休眠
sleep 请求进程休眠一段时间;若被信号提前打断,返回值可表示还没睡完的秒数。pause 则会一直等待,直到收到一个信号;它常用于教学,但和条件检查组合时很容易产生竞态,后面会用 sigsuspend 解释更稳妥的做法。
4.5.9 容易混淆
| 容易混淆 | 正确理解 | 判断方法 |
|---|---|---|
| fork vs execve | fork 新建子进程;execve 替换当前进程运行的程序 | 问:PID 增加了吗?增加是 fork;没增加但程序换了是 execve |
| 子进程复制 vs 共享变量 | 初始内容相同,但普通用户内存后续修改相互独立 | 在父子各自修改同一变量,彼此不会自动看见 |
| 终止 vs 回收 | 终止停止运行;回收由父进程取得状态并释放残留记录 | 子进程结束后父进程还需要 wait/waitpid |
| 输出顺序 vs 源码顺序 | 父子并发,输出顺序不确定 | 只要没有同步,不要假设谁先 printf |
| 休眠 vs 等待子进程 | sleep/pause 等时间或信号;waitpid 等子进程状态 | 问等待条件是时间、信号还是特定子进程 |
4.5.10 面试 / 表达题
题:为什么 shell 通常是 fork 后由子进程 execve,而不是 shell 自己直接 execve?
答题骨架:execve 会替换当前进程的用户地址空间且成功不返回。如果 shell 自己执行 execve,它会失去自己的命令循环。fork 先保留父 shell,子进程再 execve 目标程序;父 shell 可以等待前台作业或继续处理后台作业。
4.5.11 一句话总结
fork 负责生出执行者,execve 负责换成目标程序,waitpid 负责把结束的孩子收回来;把三者混淆,进程控制就会处处出错。
4.6 知识点 6:信号是"小通知",不是可靠的消息队列(⭐⭐⭐⭐⭐)
为什么重要: 信号让内核或一个进程可以异步通知另一个进程,所以它既强大又危险。它会在主程序意想不到的时刻打断控制流;一旦把"信号会逐个排队"或"处理程序和普通函数一样安全"当真,就很容易写出偶发且难复现的错误。
4.6.1 一句话
**信号(signal)**是内核或进程发给目标进程的一条小通知;目标进程接收后可以执行默认动作、忽略它,或转去运行用户注册的信号处理程序。
!abstract 📌 一句话卡片|信号
信号的重点不是携带多少数据,而是异步提醒"某类事件至少发生过";本章的普通信号模型中,同一类型的待处理信号最多保留一个。
4.6.2 大白话解释(含三个类比)
信号像一张非常短的便条:"Ctrl+C 被按下了""你的子进程结束了""定时器到点了"。它不会替你完成工作,只会让你有机会按预先约定的方式响应。问题是便条可能在你处理另一张便条时到来,同类便条也不会无限堆在桌上。
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 门铃 | 按门铃 → 发送信号;开门处理 → 处理程序 | 处理程序可打断当前活动 | 门铃通常每按一次都能听见;普通信号可能合并/丢失重复通知 |
| 手机的未读红点 | 红点 → pending 位;静音模式 → blocked 集合 | "有通知"不代表有精确数量 | 手机可显示计数;本章普通信号每类至多一个待处理标记 |
| 楼宇火警广播 | 广播事件 → 信号;默认疏散/自定义流程 → 默认动作/处理程序 | 为什么某些信号会直接终止或停止进程 | 真实信号有严格内核语义,不能用人为流程随意替换 |
4.6.3 发送、待处理、阻塞、接收:四个词必须分开
| 词 | 含义 | 容易误解成 |
|---|---|---|
| 发送(发送/递送) | 内核更新目标进程的信号状态 | 目标进程已经执行处理程序 |
| 待处理 pending | 已发送但还没接收 | 有一个可精确计数的消息队列 |
| 阻塞 blocked | 某类信号暂时不允许被接收 | 该信号已经被删除 |
| 接收 | 内核强制进程按该信号的行为响应 | 只能运行用户自定义函数 |
内核可把每个进程的信号状态想成两个集合:
- pending:已经到达、尚未被接收的信号类型。
- blocked:当前暂不允许接收的信号类型。
进程从内核态返回用户态时,内核会查看 pending 中且不在 blocked 中 的信号。如果集合非空,就选择其中一个并让进程接收它。
#mermaid-svg-ZUOtwBjJlEvSZ6Vt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .error-icon{fill:#552222;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .marker.cross{stroke:#333333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt p{margin:0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .edgeLabel .label text{fill:#333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .label div .edgeLabel{color:#333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt #statediagram-barbEnd{fill:#333333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .cluster-label,#mermaid-svg-ZUOtwBjJlEvSZ6Vt .nodeLabel{color:#131300;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .note-edge{stroke-dasharray:5;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-note text{fill:black;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram-note .nodeLabel{color:black;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagram .edgeLabel{color:red;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt #dependencyStart,#mermaid-svg-ZUOtwBjJlEvSZ6Vt #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZUOtwBjJlEvSZ6Vt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 该类型被阻塞
解除阻塞
未阻塞且内核检查到
返回原控制流或终止
已发送
待处理
保持待处理
已接收
默认动作
忽略
用户处理程序
怎么看这张图: "已发送"和"已接收"之间可能隔很久。阻塞只让信号暂时停在待处理状态;解除阻塞后,它才有机会被接收。对本章讨论的普通信号,同类型重复发送不会形成可靠的多条待处理记录。
4.6.4 常见信号先记用途,不必先背编号
| 信号 | 常见来源 | 默认效果(概念级) | 初学者要记住 |
|---|---|---|---|
| SIGINT | 前台作业收到 Ctrl+C | 终止 | 用户请求中断前台程序 |
| SIGTSTP | 前台作业收到 Ctrl+Z | 停止 | 用户请求暂时挂起前台作业 |
| SIGCONT | 进程被要求继续 | 继续运行 | 用来恢复已停止进程 |
| SIGCHLD | 子进程停止或终止 | 通常忽略 | 父进程可据此回收孩子 |
| SIGALRM | alarm 到点 | 终止 | 进程给自己设定时提醒 |
| SIGKILL | 其他进程/系统请求强制终止 | 终止 | 不能被捕获或忽略 |
| SIGSTOP | 停止请求 | 停止 | 不能被捕获或忽略 |
| SIGSEGV | 非法内存引用等 | 终止 | 是程序内存访问异常的常见表面结果 |
!warning 两个例外
SIGKILL 和 SIGSTOP 的默认行为不能被修改:不能忽略,也不能安装用户处理程序。它们存在的目的就是让系统仍能强制终止或停止进程。
4.6.5 信号可以怎样发送
本章从进程组出发解释发送方式。一个进程属于一个进程组;shell 为一个作业组织相应进程组,因此 Ctrl+C、Ctrl+Z 可以作用到前台作业中的多个进程。
| 方式 | 谁发 | 典型用途 | 关键点 |
|---|---|---|---|
| 内核检测事件 | 内核 | 除零、非法访问、子进程结束 | 应用通常只看到随后收到的信号 |
| 键盘 | 终端/内核 | Ctrl+C、Ctrl+Z | 作用到前台进程组,而不是只作用 shell |
| kill 命令或 kill 函数 | 一个进程 | 向指定 PID 或进程组发送信号 | PID 的正负和零有不同含义,应查接口规则 |
| alarm 函数 | 调用进程自己 | 在若干秒后收到 SIGALRM | 新 alarm 会影响已有闹钟安排 |
4.6.6 接收信号后的三种选择
进程可以为多数信号选择:
| 设定 | 含义 |
|---|---|
| 默认行为 | 采用系统规定的终止、停止、忽略等动作 |
| 忽略 | 直接忽略这一类型的信号 |
| 捕获 | 转去运行用户提供的信号处理程序,完成后通常回到被打断的位置 |
#mermaid-svg-rY5yAeM81UzfD2AA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rY5yAeM81UzfD2AA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rY5yAeM81UzfD2AA .error-icon{fill:#552222;}#mermaid-svg-rY5yAeM81UzfD2AA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rY5yAeM81UzfD2AA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rY5yAeM81UzfD2AA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rY5yAeM81UzfD2AA .marker.cross{stroke:#333333;}#mermaid-svg-rY5yAeM81UzfD2AA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rY5yAeM81UzfD2AA p{margin:0;}#mermaid-svg-rY5yAeM81UzfD2AA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rY5yAeM81UzfD2AA .cluster-label text{fill:#333;}#mermaid-svg-rY5yAeM81UzfD2AA .cluster-label span{color:#333;}#mermaid-svg-rY5yAeM81UzfD2AA .cluster-label span p{background-color:transparent;}#mermaid-svg-rY5yAeM81UzfD2AA .label text,#mermaid-svg-rY5yAeM81UzfD2AA span{fill:#333;color:#333;}#mermaid-svg-rY5yAeM81UzfD2AA .node rect,#mermaid-svg-rY5yAeM81UzfD2AA .node circle,#mermaid-svg-rY5yAeM81UzfD2AA .node ellipse,#mermaid-svg-rY5yAeM81UzfD2AA .node polygon,#mermaid-svg-rY5yAeM81UzfD2AA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rY5yAeM81UzfD2AA .rough-node .label text,#mermaid-svg-rY5yAeM81UzfD2AA .node .label text,#mermaid-svg-rY5yAeM81UzfD2AA .image-shape .label,#mermaid-svg-rY5yAeM81UzfD2AA .icon-shape .label{text-anchor:middle;}#mermaid-svg-rY5yAeM81UzfD2AA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rY5yAeM81UzfD2AA .rough-node .label,#mermaid-svg-rY5yAeM81UzfD2AA .node .label,#mermaid-svg-rY5yAeM81UzfD2AA .image-shape .label,#mermaid-svg-rY5yAeM81UzfD2AA .icon-shape .label{text-align:center;}#mermaid-svg-rY5yAeM81UzfD2AA .node.clickable{cursor:pointer;}#mermaid-svg-rY5yAeM81UzfD2AA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rY5yAeM81UzfD2AA .arrowheadPath{fill:#333333;}#mermaid-svg-rY5yAeM81UzfD2AA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rY5yAeM81UzfD2AA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rY5yAeM81UzfD2AA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rY5yAeM81UzfD2AA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rY5yAeM81UzfD2AA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rY5yAeM81UzfD2AA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rY5yAeM81UzfD2AA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rY5yAeM81UzfD2AA .cluster text{fill:#333;}#mermaid-svg-rY5yAeM81UzfD2AA .cluster span{color:#333;}#mermaid-svg-rY5yAeM81UzfD2AA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rY5yAeM81UzfD2AA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rY5yAeM81UzfD2AA rect.text{fill:none;stroke-width:0;}#mermaid-svg-rY5yAeM81UzfD2AA .icon-shape,#mermaid-svg-rY5yAeM81UzfD2AA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rY5yAeM81UzfD2AA .icon-shape p,#mermaid-svg-rY5yAeM81UzfD2AA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rY5yAeM81UzfD2AA .icon-shape .label rect,#mermaid-svg-rY5yAeM81UzfD2AA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rY5yAeM81UzfD2AA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rY5yAeM81UzfD2AA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rY5yAeM81UzfD2AA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
默认
忽略
捕获
信号 K 已发送
K 在 blocked 中?
保持 pending
内核让进程接收 K
K 的处置方式
终止、停止或忽略
继续原控制流
运行信号处理程序
通常回到被打断的位置
怎么看这张图: 信号处理程序不是"另起一个普通线程"。它会在当前进程的控制流中插进来;处理完毕后,通常回到原位置,所以它可能与主程序共享全局状态。
4.6.7 sigprocmask:显式阻塞与解除阻塞
应用可用 sigprocmask 操作自己的 blocked 集合;sigset_t 及其辅助函数用来构造要操作的信号集合。
| how 参数的概念 | 对 blocked 集合做什么 |
|---|---|
| SIG_BLOCK | 把 set 中信号加入 blocked |
| SIG_UNBLOCK | 从 blocked 中移除 set 中信号 |
| SIG_SETMASK | 直接把 blocked 改为 set |
常见辅助函数的作用:
| 函数 | 用途 |
|---|---|
| sigemptyset | 构造空集合 |
| sigfillset | 构造包含所有信号的集合 |
| sigaddset / sigdelset | 向集合加入 / 移出某一信号 |
| sigismember | 判断一个信号是否属于集合 |
一个"临界区"式的思路是:先保存旧掩码,再阻塞某类信号,安全访问共享数据,最后恢复旧掩码。恢复旧掩码比"随手解除某一个信号"更稳妥,因为它不会破坏调用前已有的阻塞状态。
4.6.8 写信号处理程序的五条保守规则
信号处理程序与主程序、其他处理程序并发运行,而且会在不可预测时刻打断它们。教材建议采取保守写法:
| 规则 | 为什么 | 初学者做法 |
|---|---|---|
| G0:尽可能简单 | 处理程序越复杂,和主程序交错的机会越多 | 常见方案是只置一个标志后立即返回 |
| G1:只调用异步信号安全函数 | printf、malloc、exit 等常用函数不保证安全 | 输出优先用 write 或教材的安全 I/O 包;退出可用 _exit |
| G2:保存并恢复 errno | 处理程序内调用函数可能改写 errno | 入口先保存,返回前还原;若直接 _exit 则无需还原 |
| G3:保护共享全局数据 | 多条指令组成的更新可能被打断 | 访问复杂共享结构时暂时阻塞相关信号 |
| G4/G5:用 volatile 与 sig_atomic_t | 编译器缓存和非原子更新会让主程序看不到正确状态 | 信号标志常写成 volatile sig_atomic_t;只把它做简单读写 |
!warning 两个看起来无害、实际危险的写法
- 在处理程序中直接 printf:它不属于本章列出的异步信号安全函数。
- 对 volatile sig_atomic_t 做 counter++:单次读/写可原子,不代表"读出、加一、写回"这个复合动作也原子。
4.6.9 为什么不能用普通信号给事件精确计数
普通信号的 pending 状态本质是"某类信号至少来了一个"的位标志。若同类信号在处理程序运行期间连续到达,可能只有一个保留下来,其余不会逐个排队。因此 SIGCHLD 处理程序不能假设"一次调用只对应一个结束的子进程"。
正确思路是:每次进入 SIGCHLD 处理程序,都循环调用 waitpid,尽可能回收所有已终止子进程,直到没有可回收的孩子。
| 错误假设 | 可能后果 | 改正思路 |
|---|---|---|
| "来了 3 个 SIGCHLD,就一定会运行 3 次处理程序" | 留下未回收的僵死子进程 | 每次处理程序循环回收全部已结束子进程 |
| "信号到达即刻运行处理程序" | 在阻塞期间误判状态 | 区分发送、待处理和接收 |
| "处理程序一次只会被主程序打断" | 忽略处理程序被其他信号打断的可能 | 保持处理程序简单并保护共享状态 |
4.6.10 经典竞态:fork 与 addjob 的顺序漏洞
假设 shell 的父进程 fork 一个子进程,准备把子进程加到作业表;同时 SIGCHLD 处理程序会在子进程结束时从作业表删除它。若子进程在父进程 addjob 之前就结束,SIGCHLD 处理程序可能先执行 deletejob,此时表里还没有这个条目;随后父进程又把一个已不存在的作业加进表,形成错误残留。
#mermaid-svg-6nwPLjK26JGRrRDp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6nwPLjK26JGRrRDp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6nwPLjK26JGRrRDp .error-icon{fill:#552222;}#mermaid-svg-6nwPLjK26JGRrRDp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6nwPLjK26JGRrRDp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6nwPLjK26JGRrRDp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6nwPLjK26JGRrRDp .marker.cross{stroke:#333333;}#mermaid-svg-6nwPLjK26JGRrRDp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6nwPLjK26JGRrRDp p{margin:0;}#mermaid-svg-6nwPLjK26JGRrRDp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6nwPLjK26JGRrRDp .cluster-label text{fill:#333;}#mermaid-svg-6nwPLjK26JGRrRDp .cluster-label span{color:#333;}#mermaid-svg-6nwPLjK26JGRrRDp .cluster-label span p{background-color:transparent;}#mermaid-svg-6nwPLjK26JGRrRDp .label text,#mermaid-svg-6nwPLjK26JGRrRDp span{fill:#333;color:#333;}#mermaid-svg-6nwPLjK26JGRrRDp .node rect,#mermaid-svg-6nwPLjK26JGRrRDp .node circle,#mermaid-svg-6nwPLjK26JGRrRDp .node ellipse,#mermaid-svg-6nwPLjK26JGRrRDp .node polygon,#mermaid-svg-6nwPLjK26JGRrRDp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6nwPLjK26JGRrRDp .rough-node .label text,#mermaid-svg-6nwPLjK26JGRrRDp .node .label text,#mermaid-svg-6nwPLjK26JGRrRDp .image-shape .label,#mermaid-svg-6nwPLjK26JGRrRDp .icon-shape .label{text-anchor:middle;}#mermaid-svg-6nwPLjK26JGRrRDp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6nwPLjK26JGRrRDp .rough-node .label,#mermaid-svg-6nwPLjK26JGRrRDp .node .label,#mermaid-svg-6nwPLjK26JGRrRDp .image-shape .label,#mermaid-svg-6nwPLjK26JGRrRDp .icon-shape .label{text-align:center;}#mermaid-svg-6nwPLjK26JGRrRDp .node.clickable{cursor:pointer;}#mermaid-svg-6nwPLjK26JGRrRDp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6nwPLjK26JGRrRDp .arrowheadPath{fill:#333333;}#mermaid-svg-6nwPLjK26JGRrRDp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6nwPLjK26JGRrRDp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6nwPLjK26JGRrRDp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6nwPLjK26JGRrRDp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6nwPLjK26JGRrRDp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6nwPLjK26JGRrRDp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6nwPLjK26JGRrRDp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6nwPLjK26JGRrRDp .cluster text{fill:#333;}#mermaid-svg-6nwPLjK26JGRrRDp .cluster span{color:#333;}#mermaid-svg-6nwPLjK26JGRrRDp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6nwPLjK26JGRrRDp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6nwPLjK26JGRrRDp rect.text{fill:none;stroke-width:0;}#mermaid-svg-6nwPLjK26JGRrRDp .icon-shape,#mermaid-svg-6nwPLjK26JGRrRDp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6nwPLjK26JGRrRDp .icon-shape p,#mermaid-svg-6nwPLjK26JGRrRDp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6nwPLjK26JGRrRDp .icon-shape .label rect,#mermaid-svg-6nwPLjK26JGRrRDp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6nwPLjK26JGRrRDp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6nwPLjK26JGRrRDp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6nwPLjK26JGRrRDp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 父进程 fork
子进程先被调度
子进程迅速终止
SIGCHLD 待处理 / 被接收
处理程序 deletejob
作业尚未 addjob,删除无效
父进程恢复后 addjob
表中留下不存在的作业
怎么看这张图: 错误不在 addjob 或 deletejob 单独看是否正确,而在两者之间没有建立必须满足的先后关系。这就是竞争:正确性取决于不受你控制的调度交错。
修复顺序:
- 父进程在 fork 前阻塞 SIGCHLD。
- fork 后,子进程先恢复原来的信号掩码,再 execve。
- 父进程在 SIGCHLD 仍被阻塞时执行 addjob。
- 父进程恢复旧掩码;此时即便 SIGCHLD 已待处理,也只会在作业已入表后被接收。
#mermaid-svg-AXD4iV2M53i7EI50{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AXD4iV2M53i7EI50 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AXD4iV2M53i7EI50 .error-icon{fill:#552222;}#mermaid-svg-AXD4iV2M53i7EI50 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AXD4iV2M53i7EI50 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AXD4iV2M53i7EI50 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AXD4iV2M53i7EI50 .marker.cross{stroke:#333333;}#mermaid-svg-AXD4iV2M53i7EI50 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AXD4iV2M53i7EI50 p{margin:0;}#mermaid-svg-AXD4iV2M53i7EI50 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AXD4iV2M53i7EI50 .cluster-label text{fill:#333;}#mermaid-svg-AXD4iV2M53i7EI50 .cluster-label span{color:#333;}#mermaid-svg-AXD4iV2M53i7EI50 .cluster-label span p{background-color:transparent;}#mermaid-svg-AXD4iV2M53i7EI50 .label text,#mermaid-svg-AXD4iV2M53i7EI50 span{fill:#333;color:#333;}#mermaid-svg-AXD4iV2M53i7EI50 .node rect,#mermaid-svg-AXD4iV2M53i7EI50 .node circle,#mermaid-svg-AXD4iV2M53i7EI50 .node ellipse,#mermaid-svg-AXD4iV2M53i7EI50 .node polygon,#mermaid-svg-AXD4iV2M53i7EI50 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AXD4iV2M53i7EI50 .rough-node .label text,#mermaid-svg-AXD4iV2M53i7EI50 .node .label text,#mermaid-svg-AXD4iV2M53i7EI50 .image-shape .label,#mermaid-svg-AXD4iV2M53i7EI50 .icon-shape .label{text-anchor:middle;}#mermaid-svg-AXD4iV2M53i7EI50 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AXD4iV2M53i7EI50 .rough-node .label,#mermaid-svg-AXD4iV2M53i7EI50 .node .label,#mermaid-svg-AXD4iV2M53i7EI50 .image-shape .label,#mermaid-svg-AXD4iV2M53i7EI50 .icon-shape .label{text-align:center;}#mermaid-svg-AXD4iV2M53i7EI50 .node.clickable{cursor:pointer;}#mermaid-svg-AXD4iV2M53i7EI50 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AXD4iV2M53i7EI50 .arrowheadPath{fill:#333333;}#mermaid-svg-AXD4iV2M53i7EI50 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AXD4iV2M53i7EI50 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AXD4iV2M53i7EI50 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AXD4iV2M53i7EI50 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AXD4iV2M53i7EI50 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AXD4iV2M53i7EI50 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AXD4iV2M53i7EI50 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AXD4iV2M53i7EI50 .cluster text{fill:#333;}#mermaid-svg-AXD4iV2M53i7EI50 .cluster span{color:#333;}#mermaid-svg-AXD4iV2M53i7EI50 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AXD4iV2M53i7EI50 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AXD4iV2M53i7EI50 rect.text{fill:none;stroke-width:0;}#mermaid-svg-AXD4iV2M53i7EI50 .icon-shape,#mermaid-svg-AXD4iV2M53i7EI50 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AXD4iV2M53i7EI50 .icon-shape p,#mermaid-svg-AXD4iV2M53i7EI50 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AXD4iV2M53i7EI50 .icon-shape .label rect,#mermaid-svg-AXD4iV2M53i7EI50 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AXD4iV2M53i7EI50 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AXD4iV2M53i7EI50 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AXD4iV2M53i7EI50 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 子进程
父进程
父进程阻塞 SIGCHLD
fork
父进程还是子进程?
恢复旧掩码
execve 运行目标程序
在保护区内 addjob
恢复旧掩码
随后可安全接收 SIGCHLD 并 deletejob
怎么看这张图: 修复的核心不是"让子进程慢一点",而是让 SIGCHLD 无法在 addjob 之前被父进程接收。阻塞信号相当于把这段必须连续完成的更新变成受保护的临界区。
4.6.11 显式等待信号:为什么 sigsuspend 比 pause 更可靠
主程序有时要等 SIGCHLD 处理程序把某个标志改好。下面三种想法的差别很关键:
| 做法 | 问题 |
|---|---|
| while 不断检查标志 | 正确时会空转,浪费 CPU |
| 检查后调用 pause | 检查和 pause 之间若信号已到,就可能永久睡眠 |
| 循环 sleep | 可能太慢,也仍难选合适间隔 |
| sigsuspend | 原子地替换临时掩码并休眠,避免检查与睡眠之间的窗口 |
sigsuspend 临时用给定掩码替换当前 blocked 集合并挂起进程;收到可处理信号后,处理程序返回时,它会恢复调用前的掩码。它可以理解为"设置临时掩码 + pause + 恢复掩码"的不可中断版本。
#mermaid-svg-YS8dMxTyEaeyEsRW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YS8dMxTyEaeyEsRW .error-icon{fill:#552222;}#mermaid-svg-YS8dMxTyEaeyEsRW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YS8dMxTyEaeyEsRW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YS8dMxTyEaeyEsRW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YS8dMxTyEaeyEsRW .marker.cross{stroke:#333333;}#mermaid-svg-YS8dMxTyEaeyEsRW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YS8dMxTyEaeyEsRW p{margin:0;}#mermaid-svg-YS8dMxTyEaeyEsRW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW .cluster-label text{fill:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW .cluster-label span{color:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW .cluster-label span p{background-color:transparent;}#mermaid-svg-YS8dMxTyEaeyEsRW .label text,#mermaid-svg-YS8dMxTyEaeyEsRW span{fill:#333;color:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW .node rect,#mermaid-svg-YS8dMxTyEaeyEsRW .node circle,#mermaid-svg-YS8dMxTyEaeyEsRW .node ellipse,#mermaid-svg-YS8dMxTyEaeyEsRW .node polygon,#mermaid-svg-YS8dMxTyEaeyEsRW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YS8dMxTyEaeyEsRW .rough-node .label text,#mermaid-svg-YS8dMxTyEaeyEsRW .node .label text,#mermaid-svg-YS8dMxTyEaeyEsRW .image-shape .label,#mermaid-svg-YS8dMxTyEaeyEsRW .icon-shape .label{text-anchor:middle;}#mermaid-svg-YS8dMxTyEaeyEsRW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YS8dMxTyEaeyEsRW .rough-node .label,#mermaid-svg-YS8dMxTyEaeyEsRW .node .label,#mermaid-svg-YS8dMxTyEaeyEsRW .image-shape .label,#mermaid-svg-YS8dMxTyEaeyEsRW .icon-shape .label{text-align:center;}#mermaid-svg-YS8dMxTyEaeyEsRW .node.clickable{cursor:pointer;}#mermaid-svg-YS8dMxTyEaeyEsRW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YS8dMxTyEaeyEsRW .arrowheadPath{fill:#333333;}#mermaid-svg-YS8dMxTyEaeyEsRW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YS8dMxTyEaeyEsRW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YS8dMxTyEaeyEsRW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YS8dMxTyEaeyEsRW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YS8dMxTyEaeyEsRW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YS8dMxTyEaeyEsRW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YS8dMxTyEaeyEsRW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YS8dMxTyEaeyEsRW .cluster text{fill:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW .cluster span{color:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YS8dMxTyEaeyEsRW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YS8dMxTyEaeyEsRW rect.text{fill:none;stroke-width:0;}#mermaid-svg-YS8dMxTyEaeyEsRW .icon-shape,#mermaid-svg-YS8dMxTyEaeyEsRW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YS8dMxTyEaeyEsRW .icon-shape p,#mermaid-svg-YS8dMxTyEaeyEsRW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YS8dMxTyEaeyEsRW .icon-shape .label rect,#mermaid-svg-YS8dMxTyEaeyEsRW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YS8dMxTyEaeyEsRW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YS8dMxTyEaeyEsRW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YS8dMxTyEaeyEsRW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
先阻塞 SIGCHLD
创建子进程并设置等待标志
标志已被处理程序修改?
sigsuspend:临时解除阻塞并睡眠
收到信号,运行处理程序
恢复原掩码并继续
怎么看这张图: 循环仍然保留,因为可能被别的信号唤醒;关键是每次睡眠前,SIGCHLD 已被正确阻塞,而 sigsuspend 会原子地完成"暂时解除阻塞并睡眠",因此没有 pause 的竞态空档。
!example 🎬 动画脑补
父进程先把 SIGCHLD 放到"暂不接收"盒子里,创建子进程并把等待标志清零。它准备睡觉时,sigsuspend 一次性打开盒子并进入睡眠。若孩子已经结束,信号马上被处理;若还没结束,父进程会安全地睡到信号到来。
| 镜头 | 画面 | 旁白 | 对应关系 |
|---|---|---|---|
| 1 | 父进程先关闭 SIGCHLD 入口 | "先消除创建和登记期间的竞争。" | 阻塞信号 |
| 2 | 子进程运行,父进程检查标志 | "还没收到完成通知。" | 等待条件 |
| 3 | sigsuspend 同时开门并睡眠 | "不会出现刚开门就错过通知的缝隙。" | 原子等待 |
| 4 | 处理程序修改标志,循环结束 | "父进程醒来后确认条件再继续。" | 信号处理 → 继续 |
4.6.12 容易混淆
| 容易混淆 | 正确理解 | 判断方法 |
|---|---|---|
| 发送 vs 接收 | 发送只是标记信号到达;接收才触发行为 | 问:处理程序真的开始运行了吗? |
| pending vs queued | 本章普通信号同类型至多一个待处理标记,不是精确消息队列 | 问:连续 3 次同类事件能否保证记录 3 次?不能 |
| 阻塞 vs 忽略 | 阻塞是延后接收;忽略是接收后不做动作 | 解除阻塞后还会处理;忽略则不会 |
| signal handler vs 普通函数 | 可在任意时机插入、受异步安全限制 | 问:它是否可能打断正在执行的库函数?会 |
| pause vs sigsuspend | pause 不能原子地配合掩码设置;sigsuspend 可以 | 检查与睡眠之间是否有信号窗口?有则危险 |
4.6.13 面试 / 表达题
题:为什么 SIGCHLD 处理程序通常要循环 waitpid,而不是一次 waitpid?
答题骨架:普通信号并不保证同类事件逐个排队;多个子进程可能在一次处理期间结束,SIGCHLD 只表明"至少有一个孩子状态变了"。因此每次进入处理程序都应循环回收所有已终止孩子,避免僵死进程残留。
4.6.14 一句话总结
信号是异步提醒而非可靠计数器:处理程序要短、共享数据要保护、SIGCHLD 要循环回收、等待信号要用原子方式消除竞态。
4.7 知识点 7:非本地跳转让 C 程序可以跳出深层调用链(⭐⭐⭐⭐)
为什么重要: 普通 return 必须一层层回退;遇到深层函数发现的错误时,逐层传递错误代码既繁琐又容易漏掉。setjmp/longjmp 提供另一种应用层 ECF,但它会跳过正常清理路径,因此必须谨慎使用。
4.7.1 一句话
非本地跳转用 setjmp 保存一个调用环境,用 longjmp 恢复它,从而把控制直接转回某个仍在执行的较早函数,而不遵循通常的调用---返回顺序。
!abstract 📌 一句话卡片|非本地跳转
setjmp 第一次返回 0,longjmp 不返回却会让之前的 setjmp 以非零值"再次返回";它像一条跨过中间栈帧的紧急通道。
4.7.2 大白话解释(含三个类比)
普通错误返回像从办公楼逐层下楼:深层函数发现错误后,要把结果交给调用者,再由调用者继续往上交。非本地跳转像确认过安全出口后直接回到大厅的应急集合点,中间楼层的"收尾动作"不会自动执行。
| 生活类比 | 对应关系 | 帮助理解什么 | 类比边界 |
|---|---|---|---|
| 大楼紧急出口 | setjmp 保存集合点;longjmp 走紧急出口 | 为什么可以跨过中间调用层 | C 不会替你清理资源或执行析构,风险比真实疏散更隐蔽 |
| 游戏存档点 | 保存环境 → 存档;跳回存档 → 恢复环境 | "调用一次、返回多次"的直觉 | 程序只能跳到仍有效的保存环境,不能任意回到过去 |
| 客服升级通道 | 深层问题直接回到统一处理台 | 集中处理错误而非层层转交 | 实际栈帧被跳过,不能假设中间逻辑仍完成 |
4.7.3 基本流程
#mermaid-svg-8XA5WBdR4RGdLcGQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8XA5WBdR4RGdLcGQ .error-icon{fill:#552222;}#mermaid-svg-8XA5WBdR4RGdLcGQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8XA5WBdR4RGdLcGQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .marker.cross{stroke:#333333;}#mermaid-svg-8XA5WBdR4RGdLcGQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8XA5WBdR4RGdLcGQ p{margin:0;}#mermaid-svg-8XA5WBdR4RGdLcGQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .cluster-label text{fill:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .cluster-label span{color:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .cluster-label span p{background-color:transparent;}#mermaid-svg-8XA5WBdR4RGdLcGQ .label text,#mermaid-svg-8XA5WBdR4RGdLcGQ span{fill:#333;color:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .node rect,#mermaid-svg-8XA5WBdR4RGdLcGQ .node circle,#mermaid-svg-8XA5WBdR4RGdLcGQ .node ellipse,#mermaid-svg-8XA5WBdR4RGdLcGQ .node polygon,#mermaid-svg-8XA5WBdR4RGdLcGQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .rough-node .label text,#mermaid-svg-8XA5WBdR4RGdLcGQ .node .label text,#mermaid-svg-8XA5WBdR4RGdLcGQ .image-shape .label,#mermaid-svg-8XA5WBdR4RGdLcGQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-8XA5WBdR4RGdLcGQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .rough-node .label,#mermaid-svg-8XA5WBdR4RGdLcGQ .node .label,#mermaid-svg-8XA5WBdR4RGdLcGQ .image-shape .label,#mermaid-svg-8XA5WBdR4RGdLcGQ .icon-shape .label{text-align:center;}#mermaid-svg-8XA5WBdR4RGdLcGQ .node.clickable{cursor:pointer;}#mermaid-svg-8XA5WBdR4RGdLcGQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .arrowheadPath{fill:#333333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8XA5WBdR4RGdLcGQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8XA5WBdR4RGdLcGQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8XA5WBdR4RGdLcGQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8XA5WBdR4RGdLcGQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .cluster text{fill:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ .cluster span{color:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8XA5WBdR4RGdLcGQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8XA5WBdR4RGdLcGQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-8XA5WBdR4RGdLcGQ .icon-shape,#mermaid-svg-8XA5WBdR4RGdLcGQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8XA5WBdR4RGdLcGQ .icon-shape p,#mermaid-svg-8XA5WBdR4RGdLcGQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8XA5WBdR4RGdLcGQ .icon-shape .label rect,#mermaid-svg-8XA5WBdR4RGdLcGQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8XA5WBdR4RGdLcGQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8XA5WBdR4RGdLcGQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8XA5WBdR4RGdLcGQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是,返回 0
否
是
main 调用 setjmp 保存环境
第一次到达?
进入深层调用链
深层函数发现错误?
正常 return 回来
longjmp 恢复保存环境
原 setjmp 以非零值返回
统一错误处理
怎么看这张图: setjmp 在源码中只调用一次,却有两条返回路径:初次保存时返回 0;以后某次 longjmp 恢复环境时,控制回到同一位置并返回非零。longjmp 自己不会沿着下一行继续执行。
4.7.4 该记住的接口语义
| 函数 | 做什么 | 返回特点 |
|---|---|---|
| setjmp | 把当前调用环境保存到缓冲区 | 初次返回 0;被 longjmp 跳回时返回非零 |
| longjmp | 恢复缓冲区保存的环境并跳回 | 从不返回到调用 longjmp 的下一行 |
| sigsetjmp / siglongjmp | 信号场景可用的变体 | 可保存、恢复信号相关上下文,具体取决于保存参数 |
保存的调用环境包括本章强调的程序计数器、栈指针和通用寄存器等。教材也特别提醒:setjmp 的返回值不应像普通函数返回那样随意赋给变量;更安全的用法是直接作为条件或 switch 的判定依据。
4.7.5 为什么它危险
| 风险 | 原因 | 应对思路 |
|---|---|---|
| 跳过资源释放 | 中间函数原本在结尾释放的内存/锁/文件不会自动执行 | 设计清晰的资源所有权;优先考虑结构化错误处理 |
| 控制流难读 | 调用与返回不再一一对应 | 把保存点和所有跳转点放在容易审查的位置 |
| 信号场景更复杂 | 跳入任意代码可能碰到非异步安全函数 | 仅在确认可达路径安全时使用 siglongjmp |
| 保存点尚未建立就收到信号 | 处理程序可能跳向未初始化环境 | 先建立 sigsetjmp 环境,再安装相应处理程序 |
!note 与 C++ / Java 异常的关系
教材把 C++/Java 的 try/catch/throw 看作更高层、更结构化的相似机制:catch 类似保存的恢复点,throw 类似跳转。这个类比有助于理解,但语言级异常通常还带有类型匹配和资源清理等额外语义,不能当作一模一样的实现。
4.7.6 容易混淆
| 容易混淆 | 正确理解 |
|---|---|
| longjmp vs return | return 只回到直接调用者;longjmp 可跨过多个中间调用层 |
| setjmp "返回两次" vs 调用了两次 | 源码调用一次;第二次返回由 longjmp 触发 |
| 非本地跳转 vs goto | goto 通常只在同一函数内跳;longjmp 恢复已保存调用环境,可跨函数 |
| 能跳出错误 vs 自动清理资源 | 跳出控制流不等于正确释放中间资源 |
4.7.7 面试 / 表达题
题:longjmp 为什么容易引入资源泄漏?
答题骨架:longjmp 不按正常调用/返回链逐层退出,而是直接恢复较早保存的环境。中间函数结尾的 free、解锁或关闭文件等清理代码会被跳过,因此必须显式设计资源清理,不能把它当作普通 return。
4.7.8 一句话总结
非本地跳转能快速回到统一恢复点,但它跳过的是整段正常收尾路径;越方便,越需要明确资源与控制流边界。
4.8 知识点 8:用系统工具把"看不见的进程状态"变成证据(⭐⭐⭐)
为什么重要: 本章的大量对象------进程、系统调用、僵死状态、内存映射------平时看不见。工具能帮助你把"我猜是这样"变成可观察证据。
| 工具 | 本章中的用途 | 适用环境与观察重点 |
|---|---|---|
| strace | 追踪程序及子进程的系统调用 | Linux;观察 read、write、fork、execve、wait 等系统调用序列 |
| ps | 列出当前进程,包括僵死进程 | Linux/Unix;关注 PID、PPID、STAT、命令行 |
| top | 观察进程资源使用 | Linux/Unix;关注 CPU、内存与进程状态 |
| pmap | 显示进程内存映射 | Linux;把"私有地址空间"落到实际区域 |
| /proc | 由内核导出的虚拟文件系统 | Linux;可读进程状态、内存映射和系统信息 |
!tip 用工具时的提问方式
不要只问"命令输出是什么"。先写下假设,例如"子进程退出但未回收会显示为僵死状态",再选择 ps 验证;这样工具输出才会反过来加深概念,而不是变成一串陌生字段。
5 本章完整控制链:一次磁盘读取如何串起异常、进程与切换
下面用一个概念级场景把本章的对象连起来:进程 A 发起读取,数据暂未就绪;内核安排磁盘操作并切换去运行进程 B;磁盘完成后发出中断,内核再让 A 从读取之后继续。
进程 B(用户态) 磁盘控制器 内核 进程 A(用户态) 进程 B(用户态) 磁盘控制器 内核 进程 A(用户态) #mermaid-svg-7ZN2OeCOu9uL6icP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7ZN2OeCOu9uL6icP .error-icon{fill:#552222;}#mermaid-svg-7ZN2OeCOu9uL6icP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7ZN2OeCOu9uL6icP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7ZN2OeCOu9uL6icP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7ZN2OeCOu9uL6icP .marker.cross{stroke:#333333;}#mermaid-svg-7ZN2OeCOu9uL6icP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7ZN2OeCOu9uL6icP p{margin:0;}#mermaid-svg-7ZN2OeCOu9uL6icP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7ZN2OeCOu9uL6icP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7ZN2OeCOu9uL6icP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-7ZN2OeCOu9uL6icP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-7ZN2OeCOu9uL6icP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-7ZN2OeCOu9uL6icP .sequenceNumber{fill:white;}#mermaid-svg-7ZN2OeCOu9uL6icP #sequencenumber{fill:#333;}#mermaid-svg-7ZN2OeCOu9uL6icP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-7ZN2OeCOu9uL6icP .messageText{fill:#333;stroke:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7ZN2OeCOu9uL6icP .labelText,#mermaid-svg-7ZN2OeCOu9uL6icP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .loopText,#mermaid-svg-7ZN2OeCOu9uL6icP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7ZN2OeCOu9uL6icP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-7ZN2OeCOu9uL6icP .noteText,#mermaid-svg-7ZN2OeCOu9uL6icP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-7ZN2OeCOu9uL6icP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7ZN2OeCOu9uL6icP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7ZN2OeCOu9uL6icP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7ZN2OeCOu9uL6icP .actorPopupMenu{position:absolute;}#mermaid-svg-7ZN2OeCOu9uL6icP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-7ZN2OeCOu9uL6icP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7ZN2OeCOu9uL6icP .actor-man circle,#mermaid-svg-7ZN2OeCOu9uL6icP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-7ZN2OeCOu9uL6icP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} read 请求(系统调用陷阱)请求 DMA 读取A 需等待,保存 A 上下文恢复 B 上下文并运行数据完成(硬件中断)保存 B,上下文切换回 A从 read 之后继续执行
怎么看这张图:
- read 是陷阱:A 主动请求内核服务,所以先从用户态进入内核态。
- 磁盘完成是中断:它来自 CPU 外部设备,不是 A 当前指令直接造成的。
- A 与 B 的切换是上下文切换:内核保存、恢复不同进程的状态,避免 CPU 干等。
- A 的体验仍是连续的:A 最后从 read 之后继续,看起来像读操作"花了一些时间",而不是它自己必须管理磁盘和调度。
!important 一条主线,四个层次
系统调用解决"应用怎样进内核";中断解决"设备怎样通知内核";上下文切换解决"等待时 CPU 去做什么";进程抽象解决"应用为什么仍觉得自己连续运行"。
6 重点概念关系图与速记框架
#mermaid-svg-n0wxzU4AEavxddMP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-n0wxzU4AEavxddMP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-n0wxzU4AEavxddMP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-n0wxzU4AEavxddMP .error-icon{fill:#552222;}#mermaid-svg-n0wxzU4AEavxddMP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n0wxzU4AEavxddMP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-n0wxzU4AEavxddMP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n0wxzU4AEavxddMP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n0wxzU4AEavxddMP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-n0wxzU4AEavxddMP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n0wxzU4AEavxddMP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n0wxzU4AEavxddMP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n0wxzU4AEavxddMP .marker.cross{stroke:#333333;}#mermaid-svg-n0wxzU4AEavxddMP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n0wxzU4AEavxddMP p{margin:0;}#mermaid-svg-n0wxzU4AEavxddMP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-n0wxzU4AEavxddMP .cluster-label text{fill:#333;}#mermaid-svg-n0wxzU4AEavxddMP .cluster-label span{color:#333;}#mermaid-svg-n0wxzU4AEavxddMP .cluster-label span p{background-color:transparent;}#mermaid-svg-n0wxzU4AEavxddMP .label text,#mermaid-svg-n0wxzU4AEavxddMP span{fill:#333;color:#333;}#mermaid-svg-n0wxzU4AEavxddMP .node rect,#mermaid-svg-n0wxzU4AEavxddMP .node circle,#mermaid-svg-n0wxzU4AEavxddMP .node ellipse,#mermaid-svg-n0wxzU4AEavxddMP .node polygon,#mermaid-svg-n0wxzU4AEavxddMP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-n0wxzU4AEavxddMP .rough-node .label text,#mermaid-svg-n0wxzU4AEavxddMP .node .label text,#mermaid-svg-n0wxzU4AEavxddMP .image-shape .label,#mermaid-svg-n0wxzU4AEavxddMP .icon-shape .label{text-anchor:middle;}#mermaid-svg-n0wxzU4AEavxddMP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-n0wxzU4AEavxddMP .rough-node .label,#mermaid-svg-n0wxzU4AEavxddMP .node .label,#mermaid-svg-n0wxzU4AEavxddMP .image-shape .label,#mermaid-svg-n0wxzU4AEavxddMP .icon-shape .label{text-align:center;}#mermaid-svg-n0wxzU4AEavxddMP .node.clickable{cursor:pointer;}#mermaid-svg-n0wxzU4AEavxddMP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-n0wxzU4AEavxddMP .arrowheadPath{fill:#333333;}#mermaid-svg-n0wxzU4AEavxddMP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-n0wxzU4AEavxddMP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-n0wxzU4AEavxddMP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n0wxzU4AEavxddMP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-n0wxzU4AEavxddMP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n0wxzU4AEavxddMP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-n0wxzU4AEavxddMP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-n0wxzU4AEavxddMP .cluster text{fill:#333;}#mermaid-svg-n0wxzU4AEavxddMP .cluster span{color:#333;}#mermaid-svg-n0wxzU4AEavxddMP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-n0wxzU4AEavxddMP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-n0wxzU4AEavxddMP rect.text{fill:none;stroke-width:0;}#mermaid-svg-n0wxzU4AEavxddMP .icon-shape,#mermaid-svg-n0wxzU4AEavxddMP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n0wxzU4AEavxddMP .icon-shape p,#mermaid-svg-n0wxzU4AEavxddMP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-n0wxzU4AEavxddMP .icon-shape .label rect,#mermaid-svg-n0wxzU4AEavxddMP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n0wxzU4AEavxddMP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-n0wxzU4AEavxddMP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-n0wxzU4AEavxddMP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件
异常:硬件转入内核
内核服务与调度
进程:逻辑流与地址空间
应用接口:fork / waitpid / execve
信号:异步通知应用
同步:阻塞掩码与 sigsuspend
正确处理并发控制流
怎么看这张图: 从左到右是抽象层次逐步升高的路线。异常是底座,进程是内核提供的抽象,进程控制和信号是应用可直接调用的机制;一旦出现多个流,就必须回到同步问题,保护正确的事件顺序。
| 如果你遇到...... | 先想到的本章对象 | 第一问 |
|---|---|---|
| 硬件定时器、网卡、磁盘完成 | 中断 | 外部事件何时让 CPU 进入内核? |
| 缺页、除零、非法访问 | 故障或终止 | 能否修复并重试当前指令? |
| 要创建或运行新程序 | fork 与 execve | 是要新进程,还是替换当前进程? |
| 子进程退出后资源残留 | waitpid 与 SIGCHLD | 父进程有没有回收所有孩子? |
| Ctrl+C / Ctrl+Z | 前台进程组与信号 | 信号实际送给了哪个进程组? |
| 偶发错误、顺序不稳定 | 竞争 | 是否有两个并发流访问同一状态且缺乏同步? |
| 深层错误快速返回 | setjmp / longjmp | 跳过的栈帧是否有资源需要清理? |
7 易混淆概念与常见误区
| 错误理解 | 正确理解 | 为什么易错 | 自检问题 |
|---|---|---|---|
| "异常一定是程序 bug" | 中断、系统调用、缺页恢复等都属于异常控制流的一部分 | 日常语言把异常等同于失败 | 处理后能否继续正常工作? |
| "并发就是多核同时跑" | 时间重叠即并发;不同核同时执行才是并行 | "同时"在口语中不精确 | 单核时间片能否形成并发?能 |
| "fork 后父子共享普通变量" | 初始值相同,后续普通用户空间修改相互独立 | 初始状态看起来像复制粘贴 | 父改 x 后子是否一定看见?否 |
| "execve 会新建一个进程" | execve 替换当前进程的程序,成功时不返回 | 常和 fork 连用而被混淆 | PID 是否产生新的?否 |
| "子进程 exit 后就完全消失" | 未被父进程回收前仍是僵死进程 | "结束"与"回收"是两步 | 父进程是否 wait/waitpid? |
| "信号来了就会立即运行处理程序" | 信号可先 pending,也可能被 blocked | 发送与接收容易混成同一动作 | 此时该信号是否被阻塞? |
| "同类信号来 N 次,处理程序会跑 N 次" | 本章普通信号不提供精确排队保证 | 把信号当消息队列 | 是否用信号给事件计数?若是,应重新设计 |
| "volatile 让复合更新线程安全" | 它约束编译器缓存,不使 counter++ 自动原子 | 关键字名称像"安全开关" | 操作是否只是单次读或写? |
| "pause 等信号一定没问题" | 若信号在检查条件后、pause 前到达,会错过唤醒 | 窗口极短,测试不易触发 | 设置掩码与睡眠是否原子? |
| "longjmp 类似 return,所以会自动清理" | 它直接跨过中间栈帧,清理代码可能被跳过 | 都会回到较早代码位置 | 中间函数的释放/解锁还会执行吗? |
!warning 遇到偶发并发 bug 时的定位顺序
先分清是初始化问题、链路/设备问题、异常进入问题、进程状态问题,还是共享状态的竞争问题。对于本章的 fork/信号代码,优先画事件顺序图,不要先凭一次运行结果断言"系统总是这样调度"。
8 自测与实践
8.1 基础题(5 题)
- 什么是异常控制流?它和普通顺序控制流的差别是什么?
- 中断、陷阱、故障、终止四类异常中,哪一类来自外部设备?
- fork 成功后,在父进程和子进程中分别返回什么?
- 什么是僵死进程?为什么父进程需要回收它?
- 什么叫待处理信号?"阻塞"某信号又是什么意思?
8.2 理解题(5 题)
- 为什么缺页通常属于故障,而不是终止?
- 为什么单核机器也能有并发进程?
- 为什么 execve 成功时不会返回到调用它的下一行?
- 为什么不能用普通 SIGCHLD 的次数精确统计结束的子进程数量?
- 在 fork 和 addjob 之间,为什么要先阻塞 SIGCHLD?
8.3 思考题(3 题)
- 一个父进程 fork 后父、子都 printf。若没有 wait、管道或其他同步,哪些输出顺序可以被你安全假设?为什么?
- 你要在信号处理程序中记录"发生过一次事件"。请设计一个尽量小的共享状态,并说明为什么不直接在处理程序里做复杂打印和链表操作。
- 有人把 while 条件检查后紧跟 pause 说成"等待信号的最省 CPU 方法"。请画出一个能让它永久睡眠的事件顺序。
8.4 面试 / 表达题(5 题)
- 系统调用与普通函数调用最关键的差别是什么?
- 上下文切换通常需要保存和恢复哪些类型的状态?为什么不能只保存 PC?
- fork、execve、waitpid 在 shell 中各做什么?
- 写信号处理程序时,为什么 printf 和 malloc 往往不安全?
- sigsuspend 为什么能避免 pause 方案的竞态?
点击查看答案与评分点
- 基础 1: ECF 是因系统或程序事件而发生的控制流突变;普通顺序流通常按相邻指令推进,ECF 会转去处理事件。评分点: 事件、突然改道、继续/重试/终止。
- 基础 2: 中断。评分点: 外部 I/O 设备、异步、通常回到下一条指令。
- 基础 3: 父进程得到子进程 PID,子进程得到 0;失败返回 -1。评分点: "调用一次、返回两次"。
- 基础 4: 已终止但尚未被父进程回收的子进程;它仍占用内核记录和资源。评分点: 终止不等于回收,wait/waitpid。
- 基础 5: pending 是已发送未接收;blocked 是该类信号暂不允许接收。评分点: 阻塞不是删除。
- 理解 1: 缺页常可由内核把所需页面准备到内存后重试故障指令,所以属于可恢复故障。评分点: 修复、重执行当前指令。
- 理解 2: 单核通过时间片和上下文切换交替运行多个逻辑流;时间重叠即并发。评分点: 不要求物理同时。
- 理解 3: execve 用新程序替换当前进程的用户地址空间;成功后原调用点已不在当前程序路径中。评分点: 不创建新 PID、成功不返回。
- 理解 4: 本章普通信号同类待处理最多一个,不保证逐个排队;多个 SIGCHLD 可被合并。评分点: 循环 waitpid 回收。
- 理解 5: 防止子进程在父进程 addjob 前结束并触发处理程序 deletejob,造成作业表残留。评分点: 阻塞→fork→addjob→恢复掩码。
- 思考 1: 只能假设父、子各自在自己的控制流内保持程序顺序;两者之间的 printf 先后不能假设。评分点: 并发调度不确定。
- 思考 2: 使用 volatile sig_atomic_t 标志,处理程序只做一次简单写,主程序检查并清除/处理。评分点: 简单、原子单读写、复杂工作移到主程序。
- 思考 3: 主程序检查标志为假;SIGCHLD 在 pause 前到达并使标志变真;主程序随后 pause,却再无信号唤醒。评分点: 检查---睡眠之间的窗口。
- 表达 1: 系统调用通过陷阱受控进入内核态以请求特权服务;普通函数调用通常在用户态同一进程内执行。评分点: 特权级、内核服务、陷阱。
- 表达 2: 需要寄存器、PC、栈、状态、地址空间相关和内核管理信息等;只保存 PC 无法还原计算现场。评分点: "能从原处继续"的完整上下文。
- 表达 3: fork 创建子进程,子进程 execve 装载命令,父 shell waitpid 回收前台作业或管理后台作业。评分点: 三者分工。
- 表达 4: 处理程序可在库函数内部任意打断主程序;printf/malloc 等不保证异步信号安全,可能破坏内部状态。评分点: 异步安全、处理程序尽量简单。
- 表达 5: sigsuspend 原子地完成临时掩码替换与睡眠,避免 signal 在"检查条件后、pause 前"到达而丢失唤醒。评分点: 原子性、恢复原掩码、循环检查条件。
8.5 实践 1|纸面追踪一次"read 等磁盘"的控制转移
- 目标: 把陷阱、中断、上下文切换和进程抽象连成一条因果链。
- 前提/环境: 纸笔或 Obsidian 白板;不需要 Linux。
- 预计时间: 15 分钟(估计)。
- 风险与提醒: 这是概念练习,不要把图中的时序当成所有系统的唯一实现细节。
步骤
- 画四个角色:进程 A、内核、磁盘控制器、进程 B。
- 依次标出:A 的 read 系统调用、A 阻塞、A→B 上下文切换、磁盘完成中断、B→A 上下文切换、A 从 read 后继续。
- 在每个箭头旁写明它是陷阱、中断还是上下文切换。
预期观察: 你会看到"同一次读操作"并非只发生在 A 内部,而是跨越用户态、内核、设备和另一进程。
复盘问题
- 哪一步是 A 主动请求的?哪一步来自外部设备?
- 为什么 A 可以感觉自己只是"read 花了时间",而不是"被 B 抢走了 CPU"?
8.6 实践 2|在 Linux/WSL 中观察 fork 与 waitpid
- 目标: 用一个最小程序观察父、子 PID 与回收状态。
- 前提/环境: Linux、WSL 或容器;具备 gcc。Windows 原生 PowerShell 不适用。
- 预计时间: 20 分钟(估计)。
- 风险与提醒: 只运行你自己创建的示例程序;不要对不属于你的 PID 发送信号,不需要管理员权限。
示例程序
c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
pid_t pid = fork();
int status;
if (pid < 0) {
perror("fork");
return 1;
}
if (pid == 0) {
printf("child: pid=%ld, ppid=%ld\n",
(long)getpid(), (long)getppid());
_exit(42);
}
printf("parent: pid=%ld, child=%ld\n",
(long)getpid(), (long)pid);
if (waitpid(pid, &status, 0) < 0) {
perror("waitpid");
return 1;
}
if (WIFEXITED(status)) {
printf("parent: child exit status=%d\n", WEXITSTATUS(status));
}
return 0;
}
步骤
- 保存为 fork_wait_demo.c,并执行:gcc -Wall -Wextra -O0 fork_wait_demo.c -o fork_wait_demo
- 执行:./fork_wait_demo
- 多运行几次,记录父、子两行输出的相对顺序,再确认父进程总会在 waitpid 后打印退出状态。
预期观察: 父、子两条输出的先后可能不同;但父进程在 waitpid 成功返回后,能够取得子进程的退出状态 42。
复盘问题
- 为什么输出的先后不应当被当作固定规则?
- 如果删掉 waitpid,程序的"子进程退出"与"子进程被回收"还会是同一步吗?
8.7 实践 3|安全地查看自己的进程信息
- 目标: 用 ps 和 /proc 把 PID、PPID、状态和地址空间概念落到实际观察。
- 前提/环境: Linux、WSL 或容器。
- 预计时间: 10 分钟(估计)。
- 风险与提醒: 命令只读取信息;不要用 kill 操作陌生 PID。
步骤
- 在一个终端运行:sleep 60 &
- 立刻运行:ps -o pid,ppid,stat,cmd -p $!
- 若系统提供 /proc,运行:cat /proc/$!/maps | head
- 等待 sleep 自然结束,或只在确认是自己启动的进程时执行 wait $!。
预期观察: ps 会显示该进程的 PID、父 PID 和状态;/proc//maps 会展示进程的内存映射片段。
复盘问题
- 哪个字段能帮助你确认"谁是它的父进程"?
- maps 中的多个区域如何帮助你理解"私有地址空间不是一整块单一内存"?
9 本章速查表
| 概念 | 一句话解释 | 为什么重要 | 容易混淆什么 |
|---|---|---|---|
| ECF | 因事件而发生的控制流改道 | 统一理解异常、进程、信号和非本地跳转 | 普通跳转、普通错误 |
| 异常表 | 异常号到处理程序地址的跳转表 | 让 CPU 找到正确的内核处理代码 | 普通函数表 |
| 中断 | 外部设备导致的异步异常 | 设备完成和定时器依赖它 | 陷阱 |
| 陷阱 | 程序有意触发的同步异常 | 系统调用的受控入口 | 普通函数调用 |
| 故障 | 可能修复并重试的同步异常 | 缺页等机制依赖它 | 终止 |
| 进程 | 执行中程序及其上下文 | 提供逻辑流和地址空间抽象 | 程序文件 |
| 上下文切换 | 保存一个进程、恢复另一个进程 | 实现多任务 | 函数调用 |
| fork | 创建子进程 | shell/服务器基本构造 | execve |
| waitpid | 等待并回收子进程 | 避免僵死进程 | sleep/pause |
| execve | 替换当前进程所运行的程序 | 让子进程运行目标命令 | 创建进程 |
| 信号 | 异步事件的小通知 | 响应 Ctrl+C、子进程结束等 | 可计数消息队列 |
| pending / blocked | 已到未处理 / 暂不接收 | 正确推理信号时序 | 忽略信号 |
| sigsuspend | 原子地临时改掩码并等待信号 | 避免 pause 竞态 | sleep 或忙等 |
| 非本地跳转 | 跨过正常调用链回到保存点 | 深层错误恢复 | return / goto |
10 本章总结与下一步
今天真正应该带走的结论:
- ECF 是贯穿系统层次的统一主题。 异常、调度、信号和非本地跳转都在处理"控制权为何突然改道"。
- 异常必须按"触发原因 + 返回位置"理解。 中断/陷阱回到下一条,故障可能重试当前指令,终止不回到应用。
- 进程的独占感是一种抽象。 内核靠上下文切换让多个逻辑流交替运行,并靠地址空间隔离保护它们。
- fork、execve、waitpid 的职责不能混。 创建、替换、回收是三件不同的事。
- 信号最难的不是函数名,而是时序。 它会异步打断、普通信号不可靠排队、共享状态会竞争,因此要阻塞、保护并用 sigsuspend 等原子机制等待。
- 非本地跳转是强力但有代价的逃生通道。 它能跨过调用链,也会跨过正常清理逻辑。
下一步建议:
- 继续学习候选主题:第 9 章虚拟内存。缺页故障会在其中从"一个例子"变成完整机制。
- 学习系统级 I/O 时,回头复习"read 为什么可能让进程阻塞、磁盘完成为什么是中断"。
- 学习并发编程时,把本章的竞争、共享状态、信号处理规则当作前置经验。
!tip 30 秒复述挑战
不看笔记,用自己的话说明:什么事件会让正常控制流改道;四类异常怎样按"返回到哪里"区分;为什么 fork 之后不能假设父子输出顺序;以及为什么等待 SIGCHLD 时 sigsuspend 比 pause 更可靠。