编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 计算机基础
  • 计算机的原理

  • 网络协议原理

  • 操作系统原理

    • README
    • 01.OS进程与线程原理
    • 02.OS处理器调度策略
    • 03.OS进程间通信机制
    • 04.OS同步与互斥机制
    • 05.OS内存管理的原理
    • 06.OS的虚拟内存机制
    • 07.OS的文件系统原理
    • 08.OS的输入输出模型
    • 09.OS的设备驱动基础
    • 10.OS的容器与虚拟化
    • 11.操作系统原理总结
      • 0.阅读导航
        • 0.1 按场景读
        • 0.2 按层次读
      • 1.第 01 篇·进程与线程原理
        • 1.1 速通卡
        • 1.2 展开:从进程到协程的演进
        • 1.3 诊断信号灯
        • 1.4 一句话拎走
      • 2.第 02 篇·处理器调度策略
        • 2.1 速通卡
        • 2.2 展开:CFS 的数学根基
        • 2.3 诊断信号灯
        • 2.4 一句话拎走
      • 3.第 03 篇·进程间通信机制
        • 3.1 速通卡
        • 3.2 展开:IPC 选型决策树
        • 3.3 一句话拎走
      • 4.第 04 篇·同步与互斥机制
        • 4.1 速通卡
        • 4.2 展开:从原子操作到死锁防线
        • 4.3 诊断信号灯
        • 4.4 一句话拎走
      • 5.第 05 篇·内存管理的原理
        • 5.1 速通卡
        • 5.2 展开:物理内存的五层分配器
        • 5.3 一句话拎走
      • 6.第 06 篇·虚拟内存机制
        • 6.1 速通卡
        • 6.2 展开:缺页中断的完整流程
        • 6.3 诊断信号灯
        • 6.4 一句话拎走
      • 7.第 07 篇·文件系统原理
        • 7.1 速通卡
        • 7.2 展开:从 inode 到 VFS
        • 7.3 诊断信号灯
        • 7.4 一句话拎走
      • 8.第 08 篇·输入输出模型
        • 8.1 速通卡
        • 8.2 展开:epoll 为什么完胜 select/poll
        • 8.3 诊断信号灯
        • 8.4 一句话拎走
      • 9.第 09 篇·设备驱动基础
        • 9.1 速通卡
        • 9.2 展开:驱动的三层模型
        • 9.3 一句话拎走
      • 10.第 10 篇·容器与虚拟化
        • 10.1 速通卡
        • 10.2 展开:容器的五块积木
        • 10.3 一句话拎走
      • 11.全系列·贯通四句话
        • 11.1 虚拟化——操作系统的核心魔法
        • 11.2 调度无处不在
        • 11.3 分层隔离——计算机科学的终极方法论
        • 11.4 隔离与共享的永恒张力
      • 12.全系列数据一页纸
      • 13.全系列症状诊断表
      • 14.学习路径建议
        • 14.1 半天速通
        • 14.2 系统建立操作系统直觉(推荐)
        • 14.3 排查问题时
        • 14.4 自测清单
  • 数据库的原理

  • 计算机
  • 操作系统原理
杨充
2026-07-10
目录

11.操作系统原理总结

# 操作系统原理 · 全系列总结

10 篇操作系统核心原理的提炼。每篇一张速通卡、一段展开、一句话带走。

# 0.阅读导航

# 0.1 按场景读

你遇到了什么 先读哪篇
服务 P99 延迟飙升,但 CPU 不高 → §01 进程线程(上下文切换风暴)→ §02 调度策略(CFS 抢占)
多线程写共享变量结果不对 → §04 同步互斥(锁/条件变量/死锁)→ §03 进程通信(IPC 选型)
进程内存持续增长直到 OOM → §05 内存管理(malloc 碎片)→ §06 虚拟内存(缺页/抖动)
删了大文件磁盘空间没释放 → §07 文件系统(inode + lsof)
高并发连接处理不过来 → §08 IO 模型(epoll vs select)→ §01 进程线程(协程方案)
Docker 容器莫名被 Kill → §10 容器虚拟化(Namespace/Cgroups)→ §06 虚拟内存(Page Cache 误算)
写内核模块/驱动 → §09 设备驱动→ §04 同步互斥(原子/锁在内核中)
面试被问操作系统核心原理 → §01 进程线程 → §05 内存管理 → §06 虚拟内存 → §04 同步互斥 → §08 IO 模型

# 0.2 按层次读

第 1 层·进程调度   → §01 进程线程 → §02 处理器调度
第 2 层·并发同步   → §04 同步互斥 → §03 进程通信
第 3 层·内存管理   → §05 内存管理 → §06 虚拟内存
第 4 层·存储系统   → §07 文件系统 → §08 IO 模型
第 5 层·硬件交互   → §09 设备驱动
第 6 层·系统抽象   → §10 容器与虚拟化

# 1.第 01 篇·进程与线程原理

# 1.1 速通卡

维度 内容
遇到场景 Go 微服务半夜崩溃——P99 延迟从 20ms 飙升到 3s,htop 显示 47 个进程开了 4813 个线程,Load average 96
暴露问题 goroutine 不是"无成本"的——大量阻塞 goroutine 堆积触发上下文切换风暴
核心知识 进程五/七状态模型 → PCB(task_struct ≈ 1.7KB) → 线程(用户级/内核级/混合) → 上下文切换代价(线程 1-5μs, 进程 3-10μs) → 协程(栈初始 2KB vs 线程 8MB)
架构主线 进程是资源分配单位,线程是 CPU 调度单位,协程是用户态轻量执行体——三者的抽象层级越来越轻
加深理解 你的服务是计算密集型还是 IO 密集型?前者用线程池,后者用协程——选反了性能差百倍

# 1.2 展开:从进程到协程的演进

进程五状态:新建→就绪→运行→阻塞→终止。七状态增加"就绪挂起"和"阻塞挂起",表示内存不足时进程被换出到磁盘。

PCB 里存了什么:PID、程序计数器(PC)、寄存器快照、内存管理信息(页表基址)、打开文件表、信号处理表、进程调度参数(优先级/nice 值/时间片)。

线程三种实现:用户级线程(内核不知情,无法利用多核)、内核级线程(创建需系统调用,代价高 20-100 倍)、混合实现(多对多,Go 的 M:N 模型)。

线程 vs 协程:线程栈默认 8MB,协程栈初始 2KB 可动态扩展;线程切换在内核态(≈1-5μs),协程切换在用户态(≈0.1μs);一台机器最多几千线程,但可以有百万协程。

# 1.3 诊断信号灯

信号 行动
Load average 远大于 CPU 核心数 检查线程数 → htop 按线程数排序 → 看是否有线程泄漏
P99 延迟高但 CPU 不高 vmstat 1 看 cs 列(context switch/秒)——超过 10 万可能是切换风暴
短时间创建大量线程 OOM 线程栈 8MB × 1000 = 8GB——改用协程或线程池

# 1.4 一句话拎走

进程扛资源、线程抢 CPU、协程扛并发。选对并发模型,比优化代码有效十倍。


# 2.第 02 篇·处理器调度策略

# 2.1 速通卡

维度 内容
遇到场景 Nginx 被投诉"接口偶尔卡 2-3 秒"——CPU 仅 23%,但 perf top 显示 __schedule 占 15%
暴露问题 CPU 不忙不代表没问题——cron 后台任务( mysqldump + gzip ) 跟 Nginx worker 抢 CPU,CFS 公平调度不分主次
核心知识 FCFS(护航效应)→SJF(需预测)→RR(时间片 10-100ms)→MLFQ(多级反馈)→CFS(完全公平调度,nice 值权重)→实时调度(EDF/RMS)
架构主线 调度器在周转时间、响应时间、公平性三者间永恒博弈——CFS 用 vruntime 把 CPU 时间精确到纳秒级分配
加深理解 Linux CFS 的 sched_latency=6ms、sched_min_granularity=0.75ms 意味着什么?→ 在 8 核上每个进程至少运行 0.75ms,每 6ms 至少被调度一次

# 2.2 展开:CFS 的数学根基

vruntime 的公式:vruntime += 实际运行时间 × (1024 / 进程权重)。nice=0 权重 1024→vruntime=实际时间;nice=5 权重 335→vruntime 涨得快,CPU 分得少。

nice 值的权重差:nice=-20 权重 88761,nice=19 权重 15——极值差 5917 倍。这就是为什么 nice -n -20 能让一个进程吞掉几乎全部 CPU。

MLFQ 五条规则:①高优先级用短时间片 ②低优先级用长时间片 ③用完时间片降级 ④被 IO 唤醒升回高优先级 ⑤定期全部升回最高优先级(防饥饿)。

cgroup CPU 隔离:cpu.shares 按权重分配 CPU 时间。修复方案:给 Nginx 的 cgroup 分配 70% CPU shares,mysqldump 限制到 20%,P99 从 200ms 降到 5ms。

# 2.3 诊断信号灯

信号 行动
P99 延迟冒尖(CV 很大) perf top 看 __schedule 占比 → 超过 5% 说明调度是瓶颈
后台任务拖垮在线服务 cgroup 隔离 → cpu.shares 给在线服务 80%+
RT 进程占满 CPU sched_rt_runtime_us=950ms/秒→ 实时调度最多占用 95% CPU

# 2.4 一句话拎走

CFS 的公平是对进程而言的,不是对用户而言的。在线服务的"公平"是低延迟,批处理的"公平"是高吞吐——cgroup 能在 CFS 之上实现"不公平的公平"。


# 3.第 03 篇·进程间通信机制

# 3.1 速通卡

维度 内容
遇到场景 日志采集 Agent 内存飙升到 7.2GB,触发 OOM Killer——根因是管道通信阻塞 + Agent 开了巨大应用层缓冲
暴露问题 不是说"管道就够了"——管道默认缓冲区 64KB,高吞吐场景会饿死消费者
核心知识 管道(匿名/FIFO)→消息队列(POSIX)→共享内存(shmget/mmap)→信号量(System V/POSIX)→信号(Signal)→Socket(UDS/TCP)→mmap
架构主线 IPC 六种机制的取舍标准就两条:要不要跨网络、对速度的极致程度——共享内存快管道 60 倍,UDS 快 TCP 回环 2-3 倍
加深理解 你用过 Redis 的 UNIX domain socket 吗?——同一台机器上用 UDS 替代 TCP,延迟从 50-100μs 降到 ~30μs

# 3.2 展开:IPC 选型决策树

机制 速度 消息边界 双向 跨网络 持久化 复杂度 适用场景
无名管道 中 流式 半双工 ❌ ❌ 低 ps aux \| grep nginx
命名管道 中 流式 半双工 ❌ ❌ 低 无亲缘进程通信
消息队列 中 ✅ ✅ ❌ ✅ 中 固定格式消息
共享内存 极快 需约定 ✅ ❌ ❌ 高 ≤10MB/s 高吞吐
信号量 快 N/A N/A ❌ ❌ 中 配合共享内存的锁
UDS 快 流式 全双工 ❌ ❌ 中 同机进程间
TCP Socket 慢 流式 全双工 ✅ ❌ 低 跨网络通信

共享内存为什么快 60 倍:管道和消息队列走系统调用→内核缓冲区→拷贝到用户空间(至少 2 次拷贝)。共享内存直接 mmap 同一块物理内存,读写就是普通内存操作,没有拷贝开销。

日志采集的实践方案:< 2MB/s 用管道(简单),< 10MB/s 用 UDS(平衡),≥ 500MB/s 用共享内存环形缓冲(极致)。最终方案用共享内存环形缓冲:内存从 7.2GB 降到 50MB,吞吐反升 10 倍。

# 3.3 一句话拎走

IPC 选型的本质是在速度、复杂度、跨网络三者间做取舍。同机用共享内存,跨机老实走 TCP。


# 4.第 04 篇·同步与互斥机制

# 4.1 速通卡

维度 内容
遇到场景 演唱会售票系统超卖——同一座位被卖两次。代码是简单的"先查后改",单线程 100% 通过,并发下 ~0.1% 复现
暴露问题 SELECT 和 UPDATE 之间不是原子的——竞态条件不靠"测试充分"发现,靠理解并发模型避免
核心知识 硬件原子(TAS/CAS)→内核(futex)→应用(Mutex/CondVar/Semaphore)→死锁四条件+银行家算法→内存序(acquire/release/seq_cst)
架构主线 并发控制三层金字塔:底层硬件保证单条指令原子性,中间 futex 让内核仲裁等待,顶层条件变量让线程"等什么而不是等多久"
加深理解 mutex.lock() 在无竞争时只是一个原子操作(~25ns),有竞争时才陷入内核(futex等待);如果先无竞争后竞争也是 ~25ns,没有历史包袱

# 4.2 展开:从原子操作到死锁防线

Herlihy 共识数:read/write=1(TAS 级别)、CAS/LL/SC=∞(可实现任何并发原语)。CAS 是并发编程的"元操作"——lock-free 队列、无锁栈、乐观锁全部基于 CAS。

自旋锁的代价模型:临界区 < 1μs 选自旋锁(省去内核切换),> 10μs 选互斥锁(不浪费 CPU 空转),1-10μs 是最纠结的灰色地带。实际工程中自旋锁主要用于内核中断上下文(不能睡眠)。

死锁四条件 + 预防四法:互斥(不可剥夺)、持有并等待(一次性申请)、不可抢占(可剥夺)、循环等待(定义锁顺序)。预防死锁只需打破任一条件。

x86 vs ARM 内存一致性:x86 是 TSO(Total Store Order),store-store 有序;ARM 是弱内存序,几乎所有读写都可能乱序。CAS 在 x86 自带全屏障(lock cmpxchg),ARM 需要显式 dmb 屏障。

售票修复方案:UPDATE tickets SET status='sold' WHERE id=? AND status='available'——用受影响行数判断成功。500 QPS 超卖归零,吞吐 4000 QPS。

# 4.3 诊断信号灯

信号 行动
jstack 看到大量 BLOCKED 线程 ThreadMXBean.findDeadlockedThreads() → 找到锁图 → 切大锁为小锁
进程突然 hang 住不出日志 gdb attach → info threads + thread apply all bt 看阻塞点
吞吐量随核数增加反而下降 检查是否有全局热锁(如全局大锁保护小操作)
偶尔超卖/少卖 ~0.1% 检查"先查后改"——改成 CAS 乐观锁

# 4.4 一句话拎走

CAS 乐观锁解决"先查后改"的竞态条件,死锁预防解决"拿着锁等锁"的结构性风险。掌握这两点,并发编程通过一半。


# 5.第 05 篇·内存管理的原理

# 5.1 速通卡

维度 内容
遇到场景 C++ 数据分析服务每 2 小时 RSS 从 200MB 涨到 8GB 被 OOM Kill——new/delete 已配对,但 ptmalloc 缓存了碎片
暴露问题 free() 不意味着内存还给 OS——ptmalloc 的 tcache/fastbins 预缓存机制导致堆顶有长生命周期对象时 brk 无法收缩
核心知识 连续分配(First/Best/Worst Fit)→分页管理(四级页表)→TLB(64-1024 条目)→分段→ptmalloc Bins(tcache/fastbins/smallbins/largebins)→Buddy 系统→Slab 分配器
架构主线 内存管理的本质是解决离散物理内存→连续逻辑空间的映射——分页用 MMU 做翻译,分段给程序员一个视角,ptmalloc/Buddy/Slab 是不同层面的分配器
加深理解 free(p) 后为什么不要用 p?——ptmalloc 可能合并或复用这块内存,此时 p 指向的内容已经不可预测

# 5.2 展开:物理内存的五层分配器

Buddy 系统(伙伴系统):内核管理整个物理页框(4KB)。分配 2^order 个连续页框——找不到就分裂大伙伴,释放时合并。order 0(4KB)到 order 10(4MB)。外部碎片几乎为零但不适合分配小块。

Slab 分配器:在 Buddy 之上,为内核对象(如 task_struct 1.7KB、inode)预缓存。三层结构:Cache→Slab→Object。释放对象不真正释放内存,放入 Slab 空闲链表下次复用。

ptmalloc 的 Bins 体系:

  • tcache(每线程缓存):16-80B,无锁,最快
  • fastbins(LIFO):16-80B,不合并,小块高频分配
  • smallbins(FIFO):<512B,64 个 bin
  • largebins:≥512B,63 个 bin,有序链表
  • unsorted bin:释放后暂存,后续按大小分流

brk() 为什么泄漏:malloc(128KB 以下)→brk() 扩展堆顶,free() 后如果堆顶有长生命周期对象无法收缩。默认 >128KB 走 mmap()→free() 时 munmap() 归还。malloc_trim() 强制回收堆顶碎片。

修复方案:换 jemalloc(每线程 arena 减少锁竞争) + 定期 malloc_trim(),RSS 从 8GB 稳定到 500MB。

# 5.3 一句话拎走

malloc/free 看起来简单,但它背后是 五层分配器(Bins/Buddy/Slab/Brk/mmap)的复杂协作。理解这五层,才能写出不泄漏的程序。


# 6.第 06 篇·虚拟内存机制

# 6.1 速通卡

维度 内容
遇到场景 K8s 中 PG Pod 反复 OOM Kill(一小时 8 次),limit=8GB 但 top 显示仅用 6.2GB——Root Cause 是 CronJob 把 Page Cache 撑爆了
暴露问题 free 命令的 free 列是没意义的——内核把空闲内存全用作 Page Cache,真正的可用内存是 available 列
核心知识 请求调页(按需加载)→缺页中断(Minor/Major/COW)→页替换算法(OPT/LRU/Clock/增强 Clock)→工作集模型→抖动(Thrashing)→Linux 水位线(high/low/min)+LRU 双链表
架构主线 虚拟内存做三件事:①让每个进程以为独占 128TB 空间 ②只加载用到的页(请求调页) ③物理内存满了驱逐冷页(页替换)——一个完美的"骗局"
加深理解 vmstat 1 里 bi(块读入)和 wa(IO等待百分比)——如果 wa > 10,说明磁盘正在被用作"慢速内存",系统已经进入抖动

# 6.2 展开:缺页中断的完整流程

缺页中断各步骤耗时:TLB miss(1-2μs)→查页表(10-200ns)→触发缺页中断→在物理内存分配页框→从磁盘读入数据(5-10ms,占 99.9%)→更新页表→返回到触发指令重执行。

缺页的三种类型:

  • Minor(发生最多):页在物理内存中但未映射(如 COW 复制),无磁盘 IO
  • Major(最慢):页已被换出到磁盘,需要磁盘 IO → 一次 5-10ms
  • COW(写时复制):fork 后父子共享页标记只读,任一写入触发复制

为什么 Belady 异常只发生在 FIFO:Page Fault 随帧数增加反而更多——因为 FIFO 的替换决策"只按年龄不看使用频率"。LRU/Clock 无此问题。

增强 Clock 的二维决策:(访问位, 修改位)四档优先级:(0,0)最优先替换→(0,1)→(1,0)→(1,1)最优先保留。被修改过的页(脏页)替换需要先写回磁盘,代价是干净页的 2 倍。

工作集模型(Denning 1968):进程正常运行时需要的页框数叫"工作集"。当系统总工作集 > 物理页框总数 → 抖动。解决方案:减少并发进程或升级物理内存。

Linux 水位线:内存可用量低于 low→启动 kswapd 后台回收→降至 min→阻塞用户进程强制回收→低于 high 停止。

修复 PG 的抖动:设置 PG 容器 memory.limit=8GB + swappiness=10(减少 swap)、CronJob 容器 memory.limit=1GB。P99 从 500ms 降到 8ms,OOM 归零。

# 6.3 诊断信号灯

信号 行动
wa(IO等待)>10% + bi(块读入)持续很高 系统正在抖动 → 先确认哪个进程缺页最多( pidstat -r -p PID 1 )
free 显示 free 很低但 available 很高 正常——free 被内核用做 Page Cache。看 available 才是有效可用内存
K8s Pod 被驱逐但 limit 没用满 Page Cache 误算——container_memory_working_set_bytes 不包含 Inactive File Cache

# 6.4 一句话拎走

虚拟内存是操作系统最伟大的"骗局"——每个进程以为自己独占 128TB,实际上共享着有限的物理页框。理解缺页中断和 LRU 双链表,你就理解了性能和 OOM 的根源。


# 7.第 07 篇·文件系统原理

# 7.1 速通卡

维度 内容
遇到场景 生产服务器磁盘 99% 满,删了 30GB 旧日志后 df 显示空间没变——lsof \| grep deleted 才发现 Nginx 还持有已删除文件的 fd
暴露问题 rm 只删了目录项(dentry),只要还有进程持有 fd,inode 和数据块就不释放——"删文件"是删引用,不是删数据
核心知识 文件分配(连续/链接/索引)→inode(12 直接块+间接块→extent tree)→目录结构(硬链接/软链接)→VFS(统一接口)→日志(ordered)→EXT4 vs XFS
架构主线 文件系统的职责就两个:名字→数据的映射(目录+inode) 和 空间管理(分配/回收)。VFS 让所有文件系统对上层看起来一样
加深理解 硬链接和软链接到底什么区别?——硬链接共享 inode(同一文件多名字),软链接是独立的快捷方式文件(存的是目标路径,目标没了就断)

# 7.2 展开:从 inode 到 VFS

inode 的寻址能力:EXT4 每个 inode 有 12 个直接块指针(12×4KB=48KB) + 1 个间接块(1024 个 4KB 块=4MB) + 1 个二级间接块(1024²×4KB=4GB) + 1 个三级间接块(1024³×4KB=4TB)。但现代 EXT4 默认用 extent tree(B+树),每个 extent 描述连续块范围(最多 128MB),对大文件效率远超间接块。

硬链接 vs 软链接:硬链接让两个名字指向同一个 inode(ln file hard),删除任一名字 inode 仍在(引用计数未归零)。软链接是独立的 inode 文件,内容是目标路径(ln -s file soft),目标没了 link 就断了(dangling)。

日志三种模式:data=journal(元数据+数据双写,最慢最安全)→data=ordered(先写数据后写元数据,默认,平衡)→data=writeback(仅元数据日志,最快,断电可能损坏)。EXT4 默认 ordered。

VFS 的桥梁作用:open("/home/file") → VFS 根据 /home 找到挂载的 EXT4 超级块 → EXT4 的 ext4_file_operations.open() → 返回 inode → 创建 struct file → 填充 f_op(针对 EXT4 的 read/write 实现) → 分配 fd 返回用户态。上层代码不关心底层是 EXT4/XFS/NFS——这就是 VFS 的魔法。

EXT4 vs XFS 选型:小文件多用 EXT4(更紧凑),大文件/大目录用 XFS(extent B+树 + 分配组并行)。数据库场景普遍选 XFS。

# 7.3 诊断信号灯

信号 行动
df -h 满但 du -sh / 加起来远小于总容量 lsof +L1 \| grep deleted——有进程持有已删除文件的 fd
df -i IUse% 满但空间充足 inode 耗尽——大量小文件(如 session 文件、PHP session),加大 inode 数或清理
touch 报 No space left on device 但空间充足 inode 满或 fs 错误 → df -i 确认

# 7.4 一句话拎走

文件系统的核心抽象就一个:名字→inode→数据块。rm 删名字不删数据,只要 fd 还在进程手里。


# 8.第 08 篇·输入输出模型

# 8.1 速通卡

维度 内容
遇到场景 Python WebSocket 推送服务 1200 个连接时延迟从 50ms 飙升到 3s——strace 显示 select 消耗 78% CPU,max 1024 个 fd
暴露问题 select 三个致命缺陷:1024 上限 + 每次全量 fd 拷贝 + O(n) 遍历——连接数从 100 到 10000 不是线性退化,是指数级崩塌
核心知识 IO 两阶段(等数据→拷贝数据)→五种模型(阻塞/非阻塞/多路复用/信号驱动/异步IO)→select→poll→epoll(LT/ET)→io_uring→零拷贝(sendfile)
架构主线 IO 模型的进化主线只有一条:让 CPU 在"等数据"的阶段不去空等——从阻塞浪费一个线程到 epoll 单线程管万级连接
加深理解 epoll_wait 返回的不是"哪些事件发生了"?——不,它返回的是"哪些 fd 就绪了",事件触发是"就绪"不是"完成"

# 8.2 展开:epoll 为什么完胜 select/poll

IO 两阶段模型:阶段一(等数据准备好,~1-100ms)→阶段二(内核拷贝到用户空间,~几μs)。阻塞 IO 两个阶段都阻塞当前线程,非阻塞 IO 阶段一不阻塞但需轮询,IO 多路复用让一个线程等待多个 fd 的阶段一。

select/poll/epoll 量化对比(10000 连接,每秒 100 次事件循环):

指标 select poll epoll
单次拷贝量 10000 fd × 最大 fd 10000 fd 仅 100 就绪 fd
遍历复杂度 O(max_fd) O(n) O(就绪数)——O(100)
内核维护 每次重建 每次重建 红黑树+就绪链表(事件驱动)
10000 连接耗时 ~100ms(全部拷贝+轮询) ~50ms ~0.1ms(仅处理 100 个就绪)

LT vs ET:LT(水平触发)只要 fd 就绪就通知(默认,安全)。ET(边缘触发)仅在 fd 状态变化时通知一次(高效,必须循环读到 EAGAIN)。ET 少一次内核态切换但容易漏数据——非阻塞 fd + 循环读是 ET 的必要条件。

io_uring——下一代异步 IO:两大共享环形缓冲区(SQ/CQ),完全 mmap,提交和完成都不进内核。BIO 模拟 AIO→内核 AIO(限制多)→io_uring(统一框架,网络+文件)。适合极高 IOPS 场景(> 10 万 IOPS 时比 epoll/spdk 更优)。

零拷贝 sendfile:sendfile(out_fd, in_fd, &offset, count)——数据从磁盘 DMA→内核 Page Cache→DMA→网卡。2 次 DMA、0 次 CPU 拷贝(相比传统 read+write 的 4 次 DMA+2 次 CPU 拷贝)。

改为 epoll 的效果:不到 20 行改动,延迟从 3s 降到 10ms,CPU 从 80% 降到 5%。

# 8.3 诊断信号灯

信号 行动
连接数 > 1000 时 CPU 突然飙高 检查是否用了 select/poll → 换 epoll
epoll + ET 丢数据 检查 fd 是否 NONBLOCK + 循环读到 EAGAIN
磁盘 IO 慢但 CPU 不高 大概率是 HDD 随机读写 → 换 SSD(NVMe 20-30μs vs HDD 7-14ms)

# 8.4 一句话拎走

epoll 的伟大不在于"更快",而在于 O(就绪数)——连接数一万还是十万,只跟活跃连接数有关。


# 9.第 09 篇·设备驱动基础

# 9.1 速通卡

维度 内容
遇到场景 内核旁路驱动上生产 30 秒后服务器直接死机——硬编码的 DMA 物理地址在生产机器上属于 ACPI 区域,写入破坏了固件表触发 NMI
暴露问题 用户态程序崩溃最多 core dump,内核态代码崩溃整台机器陪葬——没有"恢复"选项
核心知识 内核模块(insmod/rmmod)→字符设备(file_operations)→块设备(request_queue)→网络设备(net_device_ops)→Linux 设备模型(bus/driver/device)→内核态专有 API(GFP/Kmalloc/vmalloc/ioremapped)
架构主线 驱动 = 把硬件操作用文件操作接口包装:open = 打开设备、read = 读寄存器、write = 写寄存器、ioctl = 发控制命令、mmap = 映射 DMA 缓冲区
加深理解 为什么驱动不能 printf?——驱动跑在内核态,内核栈只有 8KB(线程栈的 1/1000),不能递归、不能大局部变量、不能用浮点

# 9.2 展开:驱动的三层模型

字符设备:按字节流访问(串口、键盘、/dev/null)。file_operations 结构体定义 open/read/write/ioctl/mmap 等回调——每个回调对应一个系统调用。

块设备:按块访问(磁盘、SSD),有缓存层(page cache)。随机访问、大小固定(典型 512B/4KB)。请求通过 request_queue + IO 调度器(mq-deadline/kyber/bfq)排序合并。

网络设备:无设备文件,通过 ifconfig/ip 管理。net_device_ops 定义 ndo_open/ndo_start_xmit/ndo_change_mtu。收发通过 sk_buff(Socket Buffer)结构。NAPI 机制在中断密集时切到轮询模式减少 CPU 开销。

Linux 设备模型三要素:bus(总线类型,PCI/USB/I2C)、driver(驱动代码)、device(具体设备)。bus 维护两张链表(drivers+devices)→match 函数遍历两张链表配对→probe 成功后初始化设备。插 USB 设备时系统自动走 match→probe,无需手动加载。

内核开发的五项安全准则:

  1. 不信任用户态数据:每次 copy_from_user 后检查返回值
  2. 不硬编码物理地址:用设备树(Device Tree)或 ACPI 获取,不用 remap_pfn_range
  3. 中断处理要快:top half 记下数据,bottom half(tasklet/workqueue)慢处理
  4. 不用浮点数:内核不自动保存 FPU 寄存器,手动保存代价极高
  5. 锁的嵌套顺序固定:死锁在内核比用户态更糟——没有 kill -9 来救

# 9.3 一句话拎走

用户态死了最多弹个报错,内核态死了整台机器重启。写驱动的第一课是"怕"——怕破坏内存、怕死锁、怕硬编码。


# 10.第 10 篇·容器与虚拟化

# 10.1 速通卡

维度 内容
遇到场景 K8s 集群 Node 所有 Pod 被驱逐——top 显示内存仅用 60%,但 mmap 的 Page Cache 占用没被释放且被 kubelet 计为"占用"
暴露问题 容器 limit 是 Cgroups 管,Node 可用内存是内核算——两个"剩余内存"是不同维度的,Cgroups limit 用完 Pod 被 Kill,Node 内存低 Pod 被驱逐
核心知识 VM vs 容器(隔离级别/启动时间 0.5s vs 30s/密度 100:1)→Namespace×7(PID/NET/MNT/UTS/IPC/USER/Cgroup)→Cgroups v1/v2→OverlayFS(Copy-on-Write)→Docker 架构→K8s 编排
架构主线 容器 = chroot + Namespace×7 + Cgroups + OverlayFS + Seccomp/Capability——这五件都不是新技术,Docker 的创新在于把它们打包成一个好用的界面
加深理解 docker run 那条命令背后经历了什么?——docker CLI→dockerd API→containerd→containerd-shim→runc→fork 子进程 → clone(NEWNS

# 10.2 展开:容器的五块积木

Namespace 七把锁:

Namespace 隔离什么 丢了会怎样
PID 进程号(子进程只看到自己的 PID 1) 容器里能看到宿主所有进程
NET 网络栈(独立 IP/路由/iptables) 容器和宿主共用端口空间
MNT 挂载点(独立 /proc) 容器里的 ps 看到宿主进程
UTS 主机名和域名 hostname 改容器会改宿主
IPC 共享内存/信号量/消息队列 容器间 IPC 互相干扰
USER UID/GID(容器 root ≠ 宿主 root) 容器 root 逃逸直接获得宿主 root
Cgroup cgroup 文件系统视图 容器里能看到所有 cgroup 限制

Cgroups v2 统一模型:v1 每个子系统独立目录子树(不统一让 limit 复杂),v2 单一树状层级 + /sys/fs/cgroup/cgroup.controllers 列举启用的子系统。memory.high 软限制(节流)、memory.max 硬限制(OOM Kill)。

VM 的纯开销:每个 VM 约 150MB(QEMU 进程 + Guest 内核 + EPT 二级页表) + 500-2000 CPU 周期/次 VM Exit。EPT(Extended Page Table)把 Guest 物理地址再翻译一次到 Host 物理地址——每个内存访问走两次 MMU(TLB miss ×2)。容器 CPU 损失:0%。

安全三层防线:①Capability(剥离 CAP_SYS_ADMIN/CAP_NET_RAW 等)→②Seccomp(限制系统调用白名单,阻止 reboot/clone/mount)→③AppArmor/SELinux(文件/网络访问控制)。

修复 Pod 驱逐:合理设 requests=limits(Guaranteed QoS)避免超分;驱逐信号改为 evictionHard 留足 Buffer——memory.available < 500Mi 就驱逐而不是等 0。

# 10.3 一句话拎走

容器不是轻量级虚拟机——它是 Linux 五个老功能的精美包装。理解这五块积木,Docker 和 K8s 的每一次错都是可解释的。


# 11.全系列·贯通四句话

# 11.1 虚拟化——操作系统的核心魔法

进程以为自己独占 CPU       → 调度器分时共享
进程以为自己独占 128TB 内存  → MMU + 页表翻译
程序以为自己直接读写文件     → VFS 桥梁 + 具体文件系统驱动
Docker 以为自己是独立机器    → Namespace×7 + Cgroups

诊断第一问:这个"我以为"在哪个层面被操作系统骗了? 答案通常就是瓶颈所在。

# 11.2 调度无处不在

资源 调度器 核心算法 目标
CPU CFS vruntime 红黑树 公平分配 CPU 时间
页框 LRU/Clock 双链表 Active/Inactive 驱逐最不常用的页
IO 块层调度器 mq-deadline/bfq 合并排序减少寻道
物理内存 Buddy + Slab 伙伴分裂/合并 最小化外部碎片

所有调度器都在追求同一个东西:在有限资源下最大化"有效工作"。

# 11.3 分层隔离——计算机科学的终极方法论

层 向上隐藏的复杂性 抽象出的接口
VFS EXT4/XFS/NFS 的实现差异 open/read/write/close
块层调度 HDD/SSD/NVMe 的寻道/块差异 submit_bio
驱动模型(bus/driver/device) PCI/USB/I2C 总线差异 probe/remove
容器 内核 Namespace/Cgroups/seccomp 细节 docker run

每一层独立演化——EXT4 替换 XFS 时上层代码不改,NVMe 替换 HDD 时文件系统不改。

# 11.4 隔离与共享的永恒张力

操作系统所有设计都在这个天平上摇摆:

隔离需求 共享代价
进程独立地址空间(MMU) TLB miss + 上下文切换(3-10μs)
线程共享地址空间 需要锁/条件变量/原子操作保护共享数据
容器 Namespace 隔离 跨容器通信必须走网络(unix socket/TCP)
内核态/用户态分离 系统调用开销(~0.1-0.3μs) + 数据拷贝(pipe)

零隔离(裸机)= 最快但无保护;完全隔离(VM)= 最安全但 150MB+500CPU 周期/VmExit。容器的创新在于找到了两者的甜蜜点。


# 12.全系列数据一页纸

篇 核心数字 含义
01 20-100× + 1.7KB 线程创建快进程 20-100 倍;PCB(task_struct)大小 1.7KB
02 5917× + 6ms nice=-20 和 nice=19 的 CPU 权重差;CFS 调度周期
03 60× + 64KB 共享内存快管道 60 倍;管道默认缓冲区大小
04 0.1%→5% + ∞ 竞态条件从偶现到规律复现;CAS 的 Herlihy 共识数
05 5 层 + 128KB 物理内存五层分配器;brk/mmap 阈值分界
06 5-10ms + 3 种 一次 Major Page Fault 的磁盘代价;缺页三类型(Minor/Major/COW)
07 4TB + 3 种 EXT4 间接块寻址上限;日志三种模式(data=journal/ordered/writeback)
08 1000× + 0→2 次 epoll 比 select 快千倍;sendfile 零拷贝(DMA×2, CPU×0)
09 8KB vs 8MB 内核栈 8KB(线程栈的 1/1000)——驱动不能递归不能大局部变量
10 0% + 7× 容器 CPU 损失 0%(vs VM ~5-20%);Namespace 七种隔离(编号都是 0x02000000 的掩码位)

# 13.全系列症状诊断表

对照症状,直接跳到对应篇章。

症状 第一反应 第二篇
服务 CPU 不高但 P99 延迟很高 §01 进程线程(切换风暴) §02 调度策略(CFS 抢占)
多线程数据偶尔不对 §04 同步互斥(锁/原子) §03 进程通信
进程 RSS 持续增长 §05 内存管理(malloc 碎片) §06 虚拟内存(Page Cache)
删了文件空间没释放 §07 文件系统(lsof + deleted)
连接数上千后 CPU 飙高 §08 IO 模型(epoll) §01 进程线程(协程)
Docker 容器 OOM Kill §10 容器虚拟化(Cgroups) §06 虚拟内存(缺页/抖动)
服务器 CPU 不高但 load 很高 §01 进程状态(不可中断睡眠态 D) §08 IO 模型(阻塞)
htop 显示内存用满但进程列表正常 §06 虚拟内存(Page Cache 误判) §10 容器(Cgroups limit)
线程池开了 500 个线程吞吐反降 §01 进程线程(切换代价) §04 同步互斥(热锁争抢)
用 top 看内存使用率出错 §06 虚拟内存(free vs available) §05 内存管理(RSS vs VSS)
内核模块加载后系统硬死 §09 设备驱动(硬编码/死锁)
面试问操作系统核心原理 §01 → §06 → §04 → §08 §05 → §03 → §07

# 14.学习路径建议

# 14.1 半天速通

读 §0 导航 → 每篇只看 速通卡 + 一句话拎走(每篇 2 分钟,10 篇共 20 分钟)。遇到具体场景再展开。

# 14.2 系统建立操作系统直觉(推荐)

§01 进程线程(并发三层) → §02 调度策略(CFS)
→ §04 同步互斥(原子→锁→死锁→CAS) → §03 进程通信(IPC 选型)
→ §05 内存管理(malloc 真相) → §06 虚拟内存(缺页与抖动)
→ §07 文件系统(inode+VFS) → §08 IO 模型(epoll+零拷贝)
→ §09 设备驱动(内核态危险) → §10 容器虚拟化(Namespace+Cgroups)

# 14.3 排查问题时

看 §13 诊断表 定位篇章 → 看速通卡确认方向 → 看信号灯验证假设 → 看展开加深理解。

# 14.4 自测清单

读完 10 篇后能回答以下问题即算掌握:

  1. 进程和线程的本质区别?协程比线程快在哪?
  2. CFS 的 vruntime 怎么计算?nice=-20 和 nice=19 的 CPU 差距多大?
  3. 共享内存为什么快管道 60 倍?UDS 为什么快 TCP 回环 2-3 倍?
  4. 死锁四条件是什么?怎么预防?CAS 为什么能实现无锁编程?
  5. free() 调用后内存为什么不还给 OS?ptmalloc 的 Bins 体系怎么设计的?
  6. Page Fault 的三种类型分别是什么?系统抖动的本质是什么?
  7. rm 删大文件为什么 df 不降?lsof +L1 | grep deleted 怎么用?
  8. epoll 为什么完胜 select?ET 和 LT 各适合什么场景?
  9. 内核态和用户态的核心区别?为什么写驱动不能用 printf?
  10. Docker 的五块积木(最低要求记住 Namespace×7 + Cgroups)分别是什么?
上次更新: 2026/07/10, 14:57:20
10.OS的容器与虚拟化
README

← 10.OS的容器与虚拟化 README→

最近更新
01
11.数据库的原理总结
07-12
02
资深程序员软能力
07-10
03
做事闭环执行能力
07-10
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式