Theory · OS · Overview

操作系统总览

内核态与用户态 · 系统调用 · 中断与异常 · 内核架构 —— 一切上层八股(进程 / 内存 / 文件 / I/O)的地基

隔离

CPU 用特权级把世界劈成两半:用户态跑应用,内核态独占危险指令——保护与共享并存

陷入

用户态进内核只有三条路:系统调用(主动)、中断(异步)、异常(同步)——所有切换都走这条窄门

取舍

宏内核要性能,微内核要隔离,混合内核两头都要——Linux 的选择决定了服务端开发的所有直觉

定位:OS 系列的第一篇,建立"受限直接执行"的心智模型。后面 11 篇(进程/内存/文件/IO)全都建立在内核态/用户态与系统调用这两个概念上。

Why an OS at All

先看一个动作:printf 一行字,程序却做不了主

// 你写的代码只有一行
printf("hi");

// 但"让这两个字符出现在屏幕上"这件事,
// 你的程序一步都做不了:
// ① 它不知道终端/显卡在哪、怎么驱动
// ② 它不知道现在该不该轮到它用这块硬件
// ③ 它也无权直接去碰 —— 试了会被 CPU 拦下
//
// 于是只能:写(1, "hi", 2) —— 请内核代劳
// 一次函数调用  ~1 纳秒
// 一次系统调用  ~100 纳秒(贵 100 倍)
关键观察:硬件是全局共享且危险的。如果每个程序都能直接写磁盘扇区、直接刷显存,两个程序同时跑就会互相踩,一个野指针就能毁掉整台机器。所以必须有人来统一仲裁——这个人就是内核。

没有操作系统会怎样

每个程序都得自带全部硬件驱动,换一块网卡、换一台机器就要重写;而且必须独占整机(没法同时跑两个程序),因为没人替它们划地盘、排时间。早期计算机正是这么工作的:一次只跑一个作业。

它换掉了什么(两件礼物)

抽象:把"磁盘扇区"包装成文件、把"物理内存"包装成虚拟内存、把"CPU 时间片"包装成进程——程序只跟抽象打交道,可移植性由此而来。② 仲裁:谁先用、用多久、能不能用,由内核统一裁决,程序之间彼此隔离。

代价:多了一道门

凡是涉及共享资源的操作,都不能直接做,只能请求内核代做。进这道门要换特权级、保存现场、查表分发——大约一百纳秒,比一次普通函数调用贵两个数量级。本 deck 讲的就是这道门:它为什么存在、怎么进、有多贵、还有哪些别的入口。

本 deck 的路线

先看清 OS 的两个角色与分层地图 → 再讲内核态/用户态这道隔离墙 → 然后完整走一遍一次系统调用(最重要的门)→ 补上中断与异常两扇侧门 → 算清切换成本 → 最后看内核架构之争与 Go 视角。

动机页:用"printf 一行字程序却做不了主"这个所有人都有体感的动作开场,讲清硬件共享带来的仲裁需求,再给出抽象与仲裁两件礼物,以及"多了一道门"的代价。

Prerequisites & Glossary

先把词认全:下面每一页都会用到它们

术语一句话理解(先记住这个,细节后面展开)
内核 kernel常驻内存、拥有全部权限的那段程序,负责替所有应用管理硬件
用户态 / 内核态CPU 的两种运行状态:用户态受限,内核态无所不能
特权级 ringx86 硬件提供的权限分级(0–3),实际只用 ring 0(内核)与 ring 3(应用)
系统调用 syscall应用主动请求内核做事的正式接口,进内核最主要的门
陷入 trapCPU 从用户态切进内核态这个动作本身(不论因何而起)
中断 interrupt异步事件:CPU 之外的设备(时钟、网卡)随时打断 CPU
异常 exception同步事件:由当前正在执行的那条指令引发(缺页、除零、断点)
上下文切换把 CPU 从 A 交给 B:保存 A 的寄存器现场、装入 B 的
系统调用号 / 调用表整数编号指代要调用哪个服务;内核按号在一张表里查出对应函数
宏内核 / 微内核宏内核把子系统全放进内核态(快);微内核只留最小核心,其余做用户态服务(隔离好)

如果你还不确定下面两件事

先读这两篇再回来,本 deck 默认你已经知道它们:
进程 · 线程 · 协程 → 被调度的"程序"到底是什么
虚拟内存 → "地址空间"是怎么把进程隔开的

最小心智模型(OSTEP 称之为"受限直接执行")

平时你的代码在 CPU 上全速、直接地跑;只有当它要碰共享资源(读写文件、收发网络、申请内存)时,才通过唯一的一道门进内核,由内核代为执行并接受检查。——"直接执行"保证了快,"受限"保证了安全,本 deck 全部内容都是这两个词的实现细节。

一个提前建立的直觉

进内核的路只有三条:你主动请求(系统调用)、设备打断你(中断)、你执行出错(异常)。除此之外没有任何第四条路。记住这三条,后面每一页的分类都能对号入座。

阅读提示:术语不用背,忘了回来查这一页;真正要背的是"受限直接执行"这五个字,以及最后那张速查页。
前置页:十个术语先定义再使用。最小心智模型"受限直接执行"统一了后面的特权级、系统调用、中断异常三页;提前点出"进内核只有三条路"。

Two Roles of an OS · 回指开场:printf 做不了主

操作系统 = 资源管理者 + 抽象服务者

角色一:管理底层资源

CPU、内存、磁盘、网卡在进程之间是稀缺且共享的。OS 负责分配、复用、隔离、回收——谁用多久 CPU(调度)、谁能用哪段内存(MMU + 页表)、文件落在磁盘哪里(文件系统)。

角色二:向上提供抽象

应用不直接碰硬件,而是使用 OS 给的"假象":进程抽象 CPU虚拟内存抽象物理内存文件抽象磁盘块socket 抽象网卡。抽象换来了可移植性与保护。

四大管理功能(教科书口径)

进程管理、内存管理、文件管理、设备管理——本系列 12 个 deck 就是按这四块 + 前置概念展开的。

从硬件到应用的分层结构 分层图:最底层是硬件,其上是操作系统内核,内核通过系统调用接口向上提供服务,再往上是 C 库与 Shell,最顶层是应用程序;用户态与内核态以系统调用接口为分界。 应用程序(Nginx / MySQL / 你的服务) 用户态 · ring 3 C 库 glibc / Shell / Runtime 系统调用接口(约 350 个调用号) 操作系统内核 · ring 0 进程调度内存管理文件系统 设备驱动网络协议栈IPC 硬件:CPU · 内存 · 磁盘 · 网卡 物理资源 特权级 分界线
面试定位:"操作系统通过受保护的间接层同时做到两件矛盾的事——让所有进程高效共享硬件,又让它们彼此隔离、互不伤害。"
两个角色一句话:向下管资源、向上给抽象。右侧分层图是后续所有 deck 的"地图":调度/内存/文件/IPC 各对应本系列一篇。系统调用接口是用户态与内核态唯一的合法通道。

Kernel Mode vs User Mode

特权级:为什么应用不能为所欲为

为什么需要两个态

如果任何程序都能执行特权指令(关中断、写页表基址、直接 I/O),一个 bug 就能毁掉整机、恶意程序可以读任意内存。CPU 硬件提供特权级(x86 的 ring 0–3,实际只用 0 和 3),配合 MMU 让危险指令只在内核态生效——用户态执行会触发异常。

内核态(ring 0)

执行全部指令集,访问全部内存。内核代码与共享的内核地址空间(高半区)驻留在这里。

用户态(ring 3)

受限指令集 + 独立虚拟地址空间。进程之间互不可见,碰不到其他进程与内核的内存。

用户态 → 内核态的三个入口

系统调用:进程主动请求服务;② 中断:外部设备异步打断(时钟、I/O 完成);③ 异常:指令执行出错或特殊事件(缺页、除零、断点)。

对比用户态内核态
特权级x86 ring 3x86 ring 0
特权指令禁用(触发异常)全部可用
地址空间各进程独立虚拟空间内核空间 + 当前进程用户空间
崩溃影响仅该进程挂掉整个系统 panic
典型代码应用逻辑、glibc、Go runtime调度器、驱动、VFS、协议栈
为什么不只用一个态 + 软件检查?软件检查每个操作都有代价且必有遗漏;硬件特权级把检查成本摊薄到"切换瞬间一次",违反即触发异常——性能与安全的正解。
经典理论出处:OSTEP 称这套机制为受限直接执行(Limited Direct Execution):平时直接在 CPU 上全速跑(快),需要时被内核"拦住"(受限)。
核心是"三条入口":系统调用(trap,主动)、中断(异步)、异常(同步)。ring 1/2 设计给驱动用但从未流行。受限直接执行=直接执行的效率+受限操作的仲裁,是整章的理论骨架。

Anatomy of a System Call

一次 write(1, "hi", 2) 的完整旅程

系统调用的完整流程:从用户程序到内核再返回 以 write 系统调用为例:应用调用 glibc 封装,把系统调用号放入 rax、参数放入寄存器,执行 syscall 指令陷入内核;CPU 切到 ring 0 并跳到固定入口,保存用户态现场到内核栈,按调用号查系统调用表执行 sys_write,最后把返回值放入 rax,用 sysret 恢复现场回到用户态。 USER SPACE · ring 3 应用代码 printf("hi") 用户态函数调用,仍在 ring 3 glibc write() 封装函数 mov rax, 1(write 的调用号) mov rdi, 1 · rsi, buf · rdx, 2 特权级边界 syscall CPU 换 ring 0 sysret KERNEL SPACE · ring 0 固定入口 entry_SYSCALL_64 入口地址由 MSR 寄存器 LSTAR 预先告知 CPU 保存用户态现场(寄存器存入内核栈 ptregs) 换用当前进程的内核栈 查系统调用表 sys_call_table[rax] rax = 1 → sys_write VFS 层 → 具体 fd 的实现 返回值写入 rax,恢复用户态现场 失败时返回 -1 并设置 errno(glibc 负责) 关键点:内核入口是"唯一且固定"的 跳转目标不是随便一个地址,而是 MSR 里的 约定入口——用户态无法伪造内核任意代码执行
逐步讲:glibc 把调用号放 rax(write=1)、参数放 rdi/rsi/rdx;syscall 指令硬件级完成换栈换特权级并跳到 LSTAR 固定入口;查表执行 sys_write;返回走 sysret。强调"固定入口"是安全设计的点睛之笔。

Calling Conventions · man 2 syscall

各架构怎么发系统调用:寄存器就是接口

架构陷入指令调用号参数寄存器
x86-64syscallraxrdi rsi rdx r10 r8 r9
i386(legacy)int $0x80eaxebx ecx edx esi edi ebp
arm64svc #0w8x0–x5

为什么旧 int 0x80 被淘汰

走软件中断:查中断向量表(IDT)+ 完整权限检查,路径长。x86-64 的 syscall/sysret 是专门的快速切换指令,入口地址存在 MSR 寄存器,硬件直跳——这是 x86-64 上系统调用比 32 位快的原因之一。

数量与形态

x86-64 调用号约 350 个(Linux 6.x,含少量占位未实现)。高频四件套:open / read / write / close;新趋势:io_uring 用共享环形缓冲把系统调用批量化、甚至零系统调用提交 I/O。

观测手段

strace ./a.out 看每次系统调用的参数与耗时;perf top 看 sys 占比。Go 的 runtime 不走 glibc,直接汇编发 syscall,strace 照样能抓到。

// x86-64 上手动发起 write(1, "hi", 2)
// nasm 语法:调用号入 rax,参数依次入 rdi/rsi/rdx
mov rax, 1          ; sys_write 的调用号
mov rdi, 1          ; fd = stdout
mov rsi, msg        ; buf 地址
mov rdx, 2          ; count
syscall             ; 陷入内核,返回值在 rax

// C 里等价的底层写法(man 2 syscall)
#include <unistd.h>
long r = syscall(SYS_write, 1, "hi", 2);
// 失败返回 -1,errno 由库封装设置
注意第 4 个参数寄存器:系统调用约定用 r10 而不是普通函数调用约定的 rcx——因为 syscall 指令会用 rcx 保存返回地址、r11 保存 rflags,这两个寄存器被硬件征用了。
面试金句:"系统调用本质是一次约定号的软陷入:寄存器传参 → 陷入指令 → 固定入口 → 查表分发。它比函数调用贵,贵在特权级切换与现场保存,而不是参数传递。"
约定表(man 2 syscall 核实)+ 汇编示例。两个加分点:r10 vs rcx 的硬件原因;io_uring 是系统调用的下一代演进(批量、零拷贝提交)。strace 是面试演示的常用抓手。

Interrupts & Exceptions

中断(异步)与异常(同步):内核的另外两扇门

陷入内核的三类事件分类图 分类树:进入内核的事件分为中断和异常两大类。中断是异步的外部事件,分为可屏蔽中断和不可屏蔽中断;异常是同步的指令内事件,分为故障、陷阱和终止。另有一类软中断属于下半部机制。 陷入内核的事件 CPU 暂停当前指令流 → 保存现场 → 查向量表 中断 Interrupt 异步 · 来自 CPU 之外 异常 Exception 同步 · 指令执行的产物 可屏蔽 INTR 时钟 tick · 磁盘 I/O 完成 网卡收包 · 键盘 不可屏蔽 NMI 硬件故障 · 掉电 必须立即响应 Fault 故障 缺页 · 可修复 修好重执行该指令 Trap 陷阱 断点 · into 调试器原理 Abort 严重硬件错误 终止进程/系统 软中断 SOFTIRQ(Linux 的下半部机制,不是新的陷入类型) 硬中断只做最紧急的事(拷数据进内存、应答硬件)→ 剩余处理延后给 softirq / tasklet / workqueue,由 ksoftirqd 内核线程兜底
判别口诀:中断异步(和当前指令无关,随时来),异常同步(由当前指令引发)。缺页是最重要的 fault——虚拟内存 deck 的主角。下半部机制放在网络/I/O deck 还会展开。

Cost of Mode & Context Switch

切换有多贵:直接成本小,间接成本大

系统调用(用户态 ↔ 内核态)

syscall/sysret 硬件快速路径,只保存/恢复少量现场、不换地址空间——往返在百纳秒量级。2018 年 Meltdown 漏洞后内核开启 KPTI(内核页表隔离),系统调用路径变长,代价可见地上升。

进程上下文切换

除寄存器现场外还要切换页表(重装 CR3)→ TLB 大面积失效(PCID/ASID 可缓解)→ cache 预热作废。直接成本 微秒量级,间接成本更高且不可直接观测。

线程上下文切换

同进程内的线程共享地址空间,不切页表,只切寄存器与栈——比进程切换便宜一个量级。这也是"多线程模型"长期流行的原因。

观测

vmstat 1 看 cs(每秒上下文切换数);pidstat -wt 按线程看;perf 拆 sys / irq 占比。

切换类型要做什么量级
函数调用压栈、跳转~ns
系统调用特权级切换 + 少量现场保存百 ns
线程切换寄存器 + 内核栈,同页表~μs
进程切换线程切换 + 换页表 + TLB 失效μs + 间接成本
间接成本 = 真正的大头:切回来之后,cache 与 TLB 里都是别人的热数据,要靠后续执行慢慢"重新暖机"——这部分花的是 CPU 时间,却从不出现在任何计数器里。
工程推论:高频小任务用线程池 / 协程而不是疯狂起进程;批量化 I/O(缓冲、io_uring)的动机之一就是摊薄系统调用的固定成本。
三级成本阶梯:syscall(百 ns)→ 线程切换(μs)→ 进程切换(μs+)。间接成本(cache/TLB 污染)是面试的差异化答案。KPTI 是有版本感的加分细节:2018 年 Meltdown 之后开启。

Monolithic · Micro · Hybrid

宏内核 vs 微内核:性能与隔离的百年之争

宏内核(Monolithic)—— Linux

调度、内存、文件系统、驱动全部跑在内核态,子系统间是普通函数调用。快,但任何一处 bug 都可能 panic 整机。Linux 用可加载模块 LKM(驱动、文件系统按需加载)弥补臃肿——注意模块仍在内核态,只是"可插拔"。

微内核(Micro)—— MINIX 3 / QNX / seL4 / Fuchsia

内核只保留最小集:IPC、调度、基础内存管理;驱动、文件系统、协议栈都是用户态服务进程,靠消息传递协作。崩溃一个驱动,重启那个服务即可——隔离好,但每次跨服务通信都是两次上下文切换 + 消息拷贝,IPC 开销是性能瓶颈

混合内核 —— Windows NT / macOS XNU

宏观是宏内核(服务都在内核态拿性能),架构上借鉴微内核思想(如 XNU = Mach 微内核消息机制 + BSD 内核)。

维度宏内核微内核
内核态里有什么全部子系统 + 驱动仅 IPC / 调度 / 基础内存
子系统间通信函数调用(纳秒级)消息传递 IPC(微秒级)
可靠性一损俱损故障隔离,可重启服务
代表Linux、传统 UNIXMINIX 3、QNX、seL4、Fuchsia
历史彩蛋:1992 年 Tanenbaum–Torvalds 论战——MINIX 作者批评 Linux 宏内核"过时",Linus 回应"实用压倒理论"。三十年后服务器世界用性能投了宏内核一票,而车载(QNX)与安全关键领域用可靠性投了微内核一票。
面试标准答案:"宏内核赢在子系统间零开销调用,微内核赢在故障隔离与可验证性(seL4 有形式化证明)。选择本质是调用开销 vs 隔离收益的权衡。"
三种架构各给一段立场+一句代表系统。LKM 容易被误答成微内核特征——模块仍跑在内核态,这是常见追问点。论战彩蛋增加记忆锚点,QNX/车载是微内核存活的现实证据。

Kernel Subsystems Map

Linux 内核五大子系统:本系列的地图

Linux 内核子系统全景图与本系列对应关系 分层图:顶层是用户空间应用与 glibc,向下经系统调用接口进入内核;内核包含五大子系统——进程调度、内存管理、虚拟文件系统、网络协议栈、进程间通信;再往下是驱动与架构相关代码,最底层是硬件。每个子系统旁标注了本知识库对应的 deck。 用户空间:应用进程 · Shell · glibc / Go runtime ring 3 · 每个进程独立虚拟地址空间 系统调用接口 SCI KERNEL · ring 0 · 五大子系统 进程调度 SCHED CFS / EEVDF · 实时类 → 本系列 2 篇 内存管理 MM 页表 · TLB · 缺页 · swap → 本系列 3 篇 虚拟文件系统 VFS inode · dentry · ext4 → 本系列 1 篇 网络协议栈 NET socket · TCP/IP · netfilter → network 系列 2 篇 IPC 信号 · 管道 · 共享内存 → 本系列 2 篇 设备驱动(字符 / 块 / 网络设备)+ 架构相关代码 arch/* 硬件:CPU · 内存 · 磁盘 · 网卡
这张图就是本系列目录:调度 2 篇(process-thread / process-scheduling)、IPC+同步+死锁 3 篇、内存 3 篇、文件 1 篇、I/O 2 篇,加本篇总览共 12 篇。VFS 与网络协议栈的细节在 network 系列展开。

Go Runtime × Kernel

Go 程序怎么用内核:能不进内核就不进

goroutine 调度 = 用户态把切换成本消化掉

Go runtime 自带调度器(GMP),goroutine 之间的切换发生在用户态(几十 ns,只换少量寄存器),完全绕开内核线程切换的微秒级成本——这就是把本 deck"切换成本"推论工程化的范例。

绕不开内核的三类时刻

网络 I/O:netpoller 底层用 epoll_create/ctl/wait(io-model deck 联动);② 锁竞争:sync.Mutex 饥饿路径用 futex 让线程睡眠;③ 内存:向 OS 批量申请新 arena 时 mmap/madvise

阻塞系统调用的处理

goroutine 发起的长阻塞 syscall(如文件 I/O)会占住 M(内核线程),runtime 检测到 P 本地队列还有活,就唤醒/新建一个 M 接管 P——阻塞不拖累其他 goroutine,代价是线程数可能上涨。

runtime 行为背后的系统调用对应 deck
netpoller 就绪等待epoll_wait(Linux)io-model
锁竞争线程挂起futex_wait / futex_wakethread-sync
堆扩容 / 归还内存mmap / madvisememory-layout
文件读写openat / read / writefile-system
定时器到期前休眠不 syscall:netpoll 超时复用process-scheduling
观测:GODEBUG=schedtrace=1000 每秒打印 G/M/P 状态;strace -c ./main 统计系统调用分布——Go 服务 sys 占比高时先想 netpoller 与文件 I/O。
面试金句:"Go 的性能故事,一半是把内核的工作搬到用户态自己做:调度自己做(少进程切换)、锁唤醒精细化(少 futex)、内存池化(少 mmap)。"
Go 岗差异化页:GMP(用户态调度)对本 deck 概念的三处消费——切换成本、futex、mmap。表里五条对应关系是后续 deck 的"预告片",每条都有可讲的深度。

Cheat Sheet

一页带走:两态、三门、一次调用、成本阶梯

① 两态对照

特权级用户态 = x86 ring 3;内核态 = ring 0
能做什么用户态只能执行受限指令集;内核态可执行全部指令、访问全部内存
地址空间用户态各进程独立;内核态共享同一份内核空间
崩溃后果用户态只死自己;内核态整机 panic

② 进内核只有三条路

· 系统调用:应用主动请求服务(trap,同步)
· 中断:外部设备异步打断(时钟、网卡收包)
· 异常:当前指令同步引发(缺页 fault、断点 trap、致命 abort)

③ 一次 write 的六步(x86-64)

glibc 封装 → 调用号入 rax、参数入 rdi/rsi/rdx → 执行 syscall 指令 → 换 ring 0 + 跳固定入口 → 保存现场到内核栈、查 sys_call_table[rax] → 返回值入 rax、sysret 回到用户态

④ 成本阶梯(量级比数字重要)

函数调用压栈 + 跳转 —— ~ns
系统调用特权级切换 + 少量现场保存,不换页表 —— 百 ns
线程切换寄存器 + 内核栈,同页表 —— ~μs
进程切换再加换页表(CR3)+ TLB 失效 —— μs + 间接成本
真正的大头是间接成本:切回来后 cache 与 TLB 里全是别人的数据,要慢慢重新暖机——这部分花 CPU 却不出现在任何计数器里

⑤ 宏内核 vs 微内核

宏内核(Linux)子系统间是函数调用(ns);一损俱损;LKM 只解决可插拔,模块仍在内核态
微内核(QNX/seL4)子系统间是消息传递 IPC(μs);故障隔离可重启服务
本质权衡调用开销 vs 隔离收益

⑥ 排障抓手

· strace -c ./main 看系统调用分布与耗时
· vmstat 1cs 列 = 每秒上下文切换数
· topsy 高 = 系统调用风暴;si 高 = 软中断风暴(看 /proc/softirqs 分摊)
一句话背下来:受限直接执行——平时全速直接跑,要碰共享资源就走唯一那道门进内核。
速查页:六块按使用顺序排——两态对照、三条入口、一次 write 六步、成本阶梯、架构对照、排障抓手。回看时只看这一页。

Interview QA · Part 1

高频追问:内核态与系统调用

先盖住答案自己答一遍,再展开对照——想不起来比看得顺眼记得牢;答不出的直接翻回速查页。

1 · 为什么要区分内核态和用户态?

保护硬件特权级

共享资源需要仲裁与隔离:CPU 用特权级(ring 0/3)+ MMU 把危险指令与关键数据锁在内核态。用户程序崩溃只死自己,内核 bug 才会拖垮系统——这是稳定性的基石。

2 · 系统调用和普通函数调用的区别?

特权级切换调用号

普通调用是同特权级内的跳转(压栈 + jmp);系统调用要陷入内核:调用号放 rax、参数进寄存器、执行 syscall、切栈、查表分发——多出特权级切换与现场保存,开销高 1–2 个数量级。

3 · 用户态切到内核态时发生了什么?

现场保存换栈

CPU 完成特权级切换并跳到固定入口(如 entry_SYSCALL_64);内核把用户态寄存器现场保存到当前进程的内核栈(ptregs),然后执行内核代码;返回时恢复现场、回到触发点继续执行。

4 · 中断和异常有什么区别?

异步 vs 同步

中断由 CPU 外部事件触发(时钟、I/O 完成),与当前指令无关,随时可能发生;异常由当前指令执行引发(缺页、除零、断点),同步且可复现。系统调用按实现归为 trap 类异常。

5 · 键盘敲入字母 A,到屏幕显示,经历了什么?

中断全链路

键盘控制器发中断 → APIC 路由到 CPU → 保存现场、查中断向量表 → 键盘驱动读扫描码(硬中断上半部)→ 软中断下半部交给 TTY 子层解析成字符 → 窗口系统/终端把字符写显存或经显示驱动刷新——全程大量"上半部快、下半部延后"。

6 · 一直关中断会怎样?为什么要区分上半部/下半部?

关中断时间延迟

关中断期间时钟中断也进不来,调度器失灵、系统像死机。所以硬中断处理必须短平快:只做最紧急的(应答硬件、拷数据),剩余工作丢给下半部(softirq/tasklet/workqueue)延后执行——吞吐与响应性的平衡。

前六题覆盖概念主线:为什么分层、系统调用 vs 函数调用、陷入时的动作、中断/异常判别、键盘链路(经典综合题)、上半部下半部的存在理由。第 5 题是串联中断+驱动+子层的加分题。

Interview QA · Part 2

高频追问:架构与工程实践

同样建议先自答。这一页的题都需要"结论 + 一句代价/边界"才完整——只答结论会被追问。

7 · Linux 为什么选宏内核?什么时候微内核更合适?

调用开销生态

服务器场景追求吞吐:子系统间函数调用零开销,宏内核占优;LKM 提供可扩展性。要求强隔离/可验证的场景选微内核:车载(QNX)、安全关键(seL4 形式化验证)、Fuchsia 等新系统。

8 · 进程切换为什么比线程切换贵?

页表TLB

多一步换页表(重装 CR3),TLB 与各级 cache 的映射大面积失效,切换后要重新预热——直接成本差一个量级,间接成本差距更大。同进程线程共享地址空间,无需这一步。

9 · sys 占比高说明什么?怎么优化?

系统调用风暴

说明 CPU 在内核态打转:典型原因是高频系统调用(大量小 read/write、频繁 gettimeofday 类调用)或软中断风暴(网络包)。优化方向:用户态缓冲合并 I/O、批量化(io_uring)、网卡多队列/RSS 分摊软中断。

10 · 什么是内核线程?和普通线程的区别?

无用户空间

只在内核态运行、没有用户地址空间的线程,由内核自己创建管理,如 ksoftirqd(软中断)、kswapd(内存回收)、kworker(工作队列)。用户看不到它们的可执行文件,ps 里名字带方括号。

11 · 什么是软中断?为什么 top 里 si 很高?

softirqksoftirqd

softirq 是内核的下半部延迟处理机制,硬中断处理完后延后执行。si 高通常是网络包量大且分摊不均,单核被打满(旧内核没开 RSS)——调优看 /proc/softirqs 的各核分布。

12 · 为什么说系统调用是"安全"的入口?

固定入口参数校验

内核入口地址预置在 MSR 中,用户态只能"跳进去",不能指定任意地址;进入后内核校验参数(如 copy_from_user 处理用户指针),调用号表是白名单——未实现的号返回 ENOSYS。

后六题偏架构与排障:宏/微内核取舍、进程切换成本、sys 高排查、内核线程、软中断 si 高(运维高频)、系统调用入口安全性。第 11 题对 Go 后端是加分项(网络服务常见)。

Related & References

相关知识点与参考

OS 系列(本分类)

进程、线程与协程 →(调度的对象是谁)
CPU 调度 →(内核怎么分配 CPU)
虚拟内存 →(缺页异常的主场)
I/O 模型与 epoll →(系统调用密集区)

跨领域联动

GMP 调度模型 →(用户态消化切换成本)
TCP 连接管理 →(内核协议栈侧视角)

参考来源(本 deck 结论可溯源至下列一手材料)

OSTEP ch.6 Limited Direct Execution内核态/用户态 + 受限直接执行的理论框架
man7.org man 2 syscall各架构陷入指令与寄存器约定(x86-64 syscall / i386 int 0x80 / arm64 svc)
CSAPP ch.8 Exceptional Control Flow异常控制流:中断 / 陷阱 / 故障 / 终止四分类
filippo.io/linux-syscall-table(Linux 6.16-rc1)x86-64 调用号分布与数量
xiaolincoding.com《图解系统》soft_interrupt / device / linux_vs_windows软中断、键盘中断链路、宏内核 vs 微内核的中文系统讲解
kernel.org Documentation/admin-guide(KPTI)Meltdown 缓解与系统调用开销变化
收尾:OSTEP 的受限直接执行是本篇理论骨架,man 2 syscall 是约定表的一手出处。总页数 13。下一篇进入进程管理主线。