做系统的人在工作中会反复听到一些断言,听多了就成了”常识”。但很多常识经不起推敲。本文把 12 条流传度高、被反复复述的说法拆开,给出事实 + 边界条件 + 合理的说法。
在逐条拆解前,先把 12 条错觉里”流传说法 vs 事实”的核心反转画成一张对照图,方便你在本文后面快速对号入座:
flowchart LR
subgraph Myth[常见说法]
direction TB
m1[微内核 = 更安全]
m2[零拷贝 = 零开销]
m3[RTOS = 更快]
m4[容器 = 轻量 VM]
m5[bypass = 永远快]
m6[fork+exec = 精妙]
m7[Linux = 比 Windows 安全]
end
subgraph Fact[实际约束]
direction TB
f1[IPC/复杂度反抵消<br/>写代码水平决定一切]
f2[TLB/pin/cache 仍有成本<br/>小包场景甚至负优化]
f3[worst-case 有界<br/>≠ 平均吞吐高]
f4[共用内核<br/>隔离 ≠ VM]
f5[核独占/运维重<br/>io_uring+XDP 已接近]
f6[页表复制线性成本<br/>posix_spawn 更健康]
f7[取决于配置与场景<br/>开源 ≠ 安全]
end
m1 --> f1
m2 --> f2
m3 --> f3
m4 --> f4
m5 --> f5
m6 --> f6
m7 --> f7
classDef myth fill:#f85149,color:#cdd9e5,stroke:#f85149;
classDef fact fill:#3fb950,color:#2d333b,stroke:#3fb950;
class m1,m2,m3,m4,m5,m6,m7 myth
class f1,f2,f3,f4,f5,f6,f7 fact
红色是流传版本,绿色是实际约束。这张图不是结论,是索引——下面十二节会逐条展开每一对箭头背后的数据和语境。
一、“微内核更安全”
常见说法:微内核把驱动、FS 放到用户态,“TCB 小 = 更安全”。
事实:
- 微内核确实让单一组件的漏洞不能直接拿到 ring 0,但跨组件的攻击面(IPC 协议、能力系统、调度器)并不小
- 实际微内核系统(QNX、seL4、Fuchsia)的漏洞数并没有明显低于同等规模宏内核
- seL4 的 formal verification 是真的降低漏洞,但和”微内核”是两件事——任何经过 formal verification 的代码都会降
- 宏内核加了 KPTI、SMAP、eBPF 验证器、LSM、seccomp、signedness check、KASAN 等等,攻击面并不比微内核大多少
更准确的说:微内核降低的是”单一组件受控范围”,代价是 IPC 多、复杂度高;它对工程可靠性的贡献,在多数实战场景远不如”写代码的人水平 + 自动化工具覆盖率”。
二、“零拷贝 = 零开销”
常见说法:用 sendfile/splice/mmap 就”零拷贝”,性能立刻起飞。
事实:
- 零拷贝节约的是 CPU 拷贝周期,但引入了:
- TLB pressure:mmap 大文件需要大量 TLB 条目
- page pinning 开销:sendfile 要 pin 页
- 缓存一致性:DMA 完成后要 invalidate CPU cache(在某些架构上)
- 用户态无法触碰数据:如果要做压缩/加密/校验,还得搬回用户态
- 在小包场景(<4KB)拷贝开销本来就不大,syscall 本身更贵
- 某些实现(早期 sendfile)的并发模型限制(只支持 file→socket)反而降低吞吐
更准确的说:零拷贝优化在大文件 + 无业务处理 + 高并发时显著;否则提升 10% 以下,甚至有负优化。选优化前先画火焰图。(参考存储百科的零拷贝分析章节)
三、“实时 OS 就是快”
常见说法:RTOS 比 Linux 快,所以延迟低。
事实:
- RTOS 的核心承诺是可预测(worst-case latency bounded),不是快
- 好的 RTOS 的 worst-case 中断延迟可能只有 1–10 µs,但平均延迟可能不如大型 OS(缓存/分支预测/超线程不可开启以保障可预测性)
- PREEMPT_RT(Linux 的实时补丁)也是追求可预测性,启用后平均吞吐量一般会下降 5–15%
- 用户抱怨 “桌面 Linux 卡”,解决方案往往不是 RT
patch,而是调度器参数(
kernel.sched_autogroup_enabled、I/O scheduler 选择)
更准确的说:实时 = worst-case latency bounded;高性能 = throughput 高 + average latency 低。两者不同甚至相反。
四、“Linux 容器 = 轻量虚拟机”
常见说法:Docker 容器是”没有 OS 的虚拟机”。
事实:
- 容器没有自己的内核,用宿主内核。这不是”没有 OS”,是”共用 OS”
- 容器的隔离来自 namespace(视图隔离)+ cgroup(资源限制)+ seccomp + capabilities + SELinux/AppArmor
- 共用内核意味着:kernel 漏洞会穿透容器;内核调度器/内存回收对所有容器共同生效;一个容器跑很多 fork 会影响整机 softirq
- 安全隔离强度:VM > gVisor(用户态内核)> kernelns + seccomp + SELinux > 裸容器(L-100 详述)
更准确的说:容器是”打包 + 进程级隔离”,不是虚拟机;需要严格隔离时用 Kata、gVisor、Firecracker 等跨越内核边界的方案。
五、“内核 bypass(DPDK / SPDK)永远更快”
常见说法:内核协议栈慢,bypass 后 PPS 起飞。
事实:
- 内核 bypass 的好处在于 polling(无中断)+ no syscall + pre-allocated buffers
- 但它要求 CPU 独占(pinning,一核一个 poll);你的低负载场景会在 100% CPU idle 下看到一核永远 100%
- io_uring + XDP 在现代 Linux 上接近 DPDK 性能,而且可以和内核协议栈共存
- bypass 场景的运维复杂度高:CPU affinity、hugepage、NIC offload、同一卡的其他流量等
- 小规模、共享机器、突发负载的业务,bypass 成本高于收益
更准确的说:bypass 是 “latency-sensitive、PPS > 百万级、核独占可接受”场景的解;多数业务用现代 Linux(io_uring、SO_REUSEPORT、多队列)已足够。
六、“NUMA 意味着要手动绑 CPU”
常见说法:多 socket 机器必须手动 numactl。
事实:
- Linux 的 autonuma 和 AutoNUMA balancing 在 3.x 之后逐步完善,大多数工作负载下不需手动干预
- 手动绑核的最大价值在一致性而不是”性能提升”——让压测结果可重复
- 需要手动的典型场景:超低延迟(亚毫秒)、HPC、数据库(特定 shard 绑特定 socket)
- Java 大堆 + G1/ZGC 的 NUMA 支持近年改善但仍不完美
- 盲目
numactl --interleave=all往往不如不绑
更准确的说:现代内核的 NUMA 自动优化对中等 workload 足够;仅在能证明手动绑会更好时再手动。
七、“OOM killer 乱杀”
常见说法:OOM killer 选错进程,总是杀重要的。
事实:
- OOM killer 按
oom_score打分,分数最高的被杀。分数基于:内存占用、oom_score_adj 调节、task_type 等 - 你可以通过
/proc/<pid>/oom_score_adj明确告诉内核哪些进程优先保留(-1000 到 1000) - systemd-oomd、oomd(Facebook 开源)能在 OOM kill 之前按 PSI(压力指标)做预防性 kill,效果好很多
- 生产系统 OOM kill 出错通常是没有调 oom_score_adj、没配 memory limits cgroup、没 swap 空间
- Java/Python runtime 在容器里经常不感知 memory limits(JVM Xmx 配错、Python 无自觉),真正要治的是应用
更准确的说:OOM killer 做的是不得已的 “救整机” 行为;如果你经常被它杀,问题在资源配置和应用,不在 killer 本身。
八、“多线程 = 比多进程快”
常见说法:线程轻、进程重,所以多线程更快。
事实:
- 线程切换比进程切换快的主要原因是不切换地址空间,因此不用 flush TLB、不用切 CR3
- 但 Linux 实现上,线程和进程都是
task_struct,调度和上下文切换开销差别 5–20% - 多线程的真正成本在锁争用、cache bouncing、false sharing——这些在大线程数时主导性能
- Go、Erlang、Java Loom 的协程/虚拟线程用”用户态调度 + 少数内核线程” 试图兼顾
- 多进程 + shm/mmap 的 IPC 在某些场景(Nginx、Postgres)更健壮——一个 worker 崩溃不拖累其它
更准确的说:选择多线程/多进程/协程要看 workload 的通信模式、失败域、调试复杂度,不是”线程更快”就了事。
九、“SSD 上不需要关心 I/O 调度”
常见说法:NVMe SSD IOPS 百万,随便用。
事实:
- NVMe 的随机 4K 读 IOPS 确实很高,但写入、混合、QoS 下表现差异大
- SSD 内部 GC、wear leveling、write amplification 让某些写模式吃亏
- io_uring + SQPOLL + fixed buffer + polling mode 是 NVMe 极限性能的唯一路径
- Linux 的 I/O 调度器对 NVMe 多队列设备默认是
none(不调度),但 cgroup v2 的 blk-io throttle 仍然有效 - 多容器/多租户下,NVMe 的公平性/隔离非常有限——需要 IO throttle 或者独立命名空间
更准确的说:NVMe 改变了 IOPS 绝对值,但没改变”I/O 仍需仔细设计”的本质。存储百科深入分析。
十、“Swap 是过时的,关掉”
常见说法:内存便宜了,swap 只会让系统卡。
事实:
- 无 swap 的系统在内存压力下只能直接 OOM kill,没有缓冲带
vm.swappiness控制anon vs file-backed 回收比例;现代内核默认 60 在 SSD 上通常 OK- zswap / zram(压缩 swap)在延迟敏感场景不明显增加延迟,且能吃一波压力
- Facebook、Google 大规模生产数据显示适量 swap + systemd-oomd 比 “no swap” 延迟更平滑
- “swap 让系统卡”往往是旧时代 HDD swap 的经验残留
更准确的说:关 swap 不是优化,是取消了一个安全带。除非你明确知道不需要。
十一、“fork + exec 是 Unix 的巧妙设计”
常见说法:fork + exec 比 CreateProcess 优雅,是 Unix 精髓。
事实:
- fork 最初在 PDP-7 上实现时只需要几十行汇编,那时候”复制”就是物理复制内存
- 地址空间扩大后 fork 的代价水涨船高,即使 COW 后页表复制仍是线性成本
- fork 的 多线程、大地址空间、复杂状态 三者任一加入都会带来麻烦
- exec-only 接口(posix_spawn) 的生态越来越成熟,Linux 实现已经很快
- Windows NT 选择 CreateProcess 的另一理由是 security context 切换:fork 之后再 drop privilege 有历史安全漏洞窗口
更准确的说:fork 是”简单而 scalable 差”的设计;新代码应优先 posix_spawn,特殊需要时才用 fork。
十二、“Linux 比 Windows 更安全”
常见说法:Linux 开源,bug 少,更安全。
事实:
- Linux 内核平均每年 CVE 数量在几百到上千,比 Windows 内核多
- 但 Linux 的利用难度因多样的编译选项、发行版差异、硬件差异而增加
- Windows 有巨量的”企业防护栈”(Defender、Credential Guard、ASR、HVCI);Linux 原生的防护较少,依赖 AppArmor/SELinux 配置
- Android 基于 Linux,它的安全性主要来自 SELinux strict mode + verified boot + seccomp + namespace,不是 Linux 本身
- 供应链安全方面 Windows 有 Authenticode、DirectSign;Linux 生态在 kernel-modsign 之外依赖发行版
更准确的说:哪个更安全取决于配置、硬件、使用场景、运维水平;“开源 = 安全”和”闭源 = 不安全”都不成立。
十三、一些附带的小错觉
- “大页(Huge Pages)总是好的”:对某些 workload(数据库、JVM)是;对碎片化内存的通用服务可能反而增加启动延迟和内存浪费。透明大页在某些数据库场景明确建议关闭。
- “绑 CPU 永远有利”:同一 NUMA 节点绑核有利;跨节点或与中断同核绑反而伤害
- “用户态 TCP(mTCP、F-Stack)吊打内核”:研究 workload 下吊打;真实生产中的 BBR、Cubic、ECN、fq_codel 等调优让内核 TCP 接近极限
- “内核 rebuild 解决问题”:99% 的情况你可以通过 sysctl、ebpf、新 syscall 解决,不必 rebuild
- “eBPF 是万能钥匙”:eBPF verifier 的限制(指令数、循环、复杂度)让复杂策略仍需要内核模块;eBPF 不是无开销——verifier 和 JIT 本身有复杂成本
十四、给你的启示
不要轻信断言,看数据。本文这些”常识”每一条都能给你省几周的误入歧途时间。写系统软件最危险的不是不懂的领域,而是半懂的”常识”。每当你觉得”大家都知道 X”的时候,停一下,找一组 benchmark 或源码证据再继续。
到此子系列 A 操作系统思想与边界
收尾。下一个子系列 B 进程、线程与任务模型
从 fork 的历史包袱讲起,进入 OS
最核心的抽象。
参考文献
- Klein, G., et al. “seL4: Formal Verification of an OS Kernel.” SOSP, 2009
- Gregg, B. Systems Performance, 2nd ed., 2020 —— 性能错觉的大集合
- McKenney, P. “Is Parallel Programming Hard, And, If So, What Can You Do About It?” —— 并发错觉
- Corbet, J. Multiple LWN articles on fork vs posix_spawn, swap policy, OOM handling
- Facebook Engineering blog: “Improving the memory utilization of our Web servers” (swap + oomd)
- Google SRE Book / Workbook, Chapter on performance
- Brendan Gregg, “Linux Performance”, brendangregg.com
- Intel Developer Zone, “Why Intel RDT matters” —— 多租户 QoS 真实情况
- Microsoft Security Response Center, yearly CVE summary
上一篇:POSIX 与 Linux/BSD/Windows 的偏离 下一篇:进程模型:fork/exec 的历史包袱
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】splice/tee/vmsplice
splice 在内核 pipe buffer 间移动数据——不经过用户态。本文讲 splice/tee/vmsplice 原理、pipe_buffer 与 page 生命周期、sendfile 的前世、CVE-2022-0847 Dirty Pipe 复盘。
【操作系统百科】宏内核 vs 微内核 vs 混合内核:Tanenbaum-Torvalds 三十年后
微内核是理论正义但工程失败?Linux 宏内核赢了只是因为先上车?三十年前的那场论战,在 seL4 形式化正确、L4 家族把 IPC 做到单 syscall、Fuchsia 真正商用的 2020s,我们应该怎么重新看?本文用四个可量化的维度——性能、可维护性、隔离性、可验证性——把四种内核架构(宏、微、混合、Exokernel/Unikernel)摆到同一张尺子上对齐。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。