本节摘要:指令集里有一个特殊族群——读控制寄存器、加载描述符表、刷页表缓存、停机——它们只允许在最高特权级执行,用户程序触碰即触发异常。本节讲清特权级的分层模型、特权指令守卫的系统资源,以及用户态程序与这些指令的正确"借力"方式(系统调用)。读完你能解释"为什么用户程序碰控制寄存器会崩而不是被静默拒绝",并理解 6.3 节系统调用机制的地基。
把操作系统所有代码和用户程序放在同一权限平面上,会发生什么?一个越界的指针写入就能改掉内核的页表,任何程序都能关中断、停机、伪造中断向量——多任务系统的根基(隔离)荡然无存。特权级的思路是按"可信程度"分层:最可信的内核代码放最高层,拥有全部指令权限;普通程序放最低层,指令集里最危险的那部分对它物理性关闭。
x86 用 ring 0 到 ring 3 四层命名这个模型,当代主流操作系统只用两端:内核在 ring 0,用户程序在 ring 3,中间两层基本闲置。硬件的执行机制朴素而彻底:每个指令取指时都带着当前特权级(CPL),遇到特权指令先核对级别,不够格就触发保护异常(general protection fault)——注意是"异常"不是"拒绝返回值",程序会直接崩,这也是为什么用户态碰特权指令的实验要小心做。
特权指令的清单可以按"它守卫的资源"分成四组,每组对应系统的一个命门:
| 守卫组 | 代表指令 | 资源与用途 | 越权的后果 |
|---|---|---|---|
| 控制寄存器 | mov cr0, rax 一类、rdmsr 与 wrmsr | 开关分页、缓存配置、模式位 | 保护异常 |
| 描述符表 | lgdt、lidt、lldt | 段描述符与中断向量表的基址 | 保护异常 |
| 页表管理 | invlpg、mov cr3 | 切换地址空间、刷页表缓存 | 保护异常 |
| 系统状态 | hlt、cli、sti、rdtsc(部分受限) | 停机、关开中断、读时钟 | 保护异常或被限速 |
清单里有个反直觉的成员:rdtsc(读时间戳计数器)本身不要求特权,但允许被内核通过 CR4 的 TSD 位关进围栏——因为高精度时钟是计时侧信道攻击的原料,安全敏感场景内核会收紧它。这个例子说明了特权管理的现代演化:从"指令级一刀切"走向"按安全策略细粒度配额"。
还有一类"半特权"成员值得单独认识:syscall/sysret(以及更老的 sysenter)是专门的"快速穿越指令"——它们是特权指令,却是给 ring 3 用的合法通道,一次执行完成特权级提升、跳到内核入口、保存返回点三件事。用户程序与内核的全部通信,物理上都汇入这类通道,6.3 节会把它拆开看。
特权模型不该停留在示意图,做一个一秒即崩的实验。下面这段用户态程序直接执行特权指令 wrmsr(写模式寄存器),预期结果不是"被忽略"而是立刻收到段错误:
; privileged.asm —— 在 ring 3 执行特权指令 section .text global _start _start: mov ecx, 0x10 ; 想写的 MSR 编号 xor eax, eax xor edx, edx wrmsr ; 特权指令:ring 0 才合法 mov edi, 0 ; 永远走不到这里 mov eax, 60 syscall
$ nasm -f elf64 privileged.asm -o privileged.o && ld -o priv privileged.o $ ./priv Segmentation fault $ dmesg | tail -1 priv[1234]: segfault at ... error 15 in libc... # GPF 被内核转译成 SIGSEGV
程序的第四条指令就送了命,退出信息显示非法指令类错误。这个实验的推论值得多说一句:保护不是"善意提醒"而是硬件级拒绝——译码与执行流水线在权限核对失败时直接注入异常,不存在"越权但继续跑"的灰色状态。安全设计里常说的"纵深防御",最底层这道墙就是它。
特权指令对用户态关闭,但用户态确实需要那些能力(读时钟、刷缓存、管内存)怎么办?正统答案是走系统调用通道,由内核代为执行。这条规矩之外,x86 还给用户态留了一份有限的自助清单,认识它们有助于理解现代性能代码的写法:
这份清单透露一个设计哲学:凡是被证明"用户态自己管也能安全"的资源,硬件逐步下放;凡是可能破坏隔离或成为攻击面的,收紧或改造。rdtsc 从无条件可用到可被 TSD 关闭的演化,就是哲学落地的活标本。
⚠️ 常见坑:把"ring 3 执行特权指令会崩"等同于"保护到位可以随便试"。实验环境中崩的是你自己程序当然无妨,但若在驱动、内核模块或虚拟机监控器的开发里写错特权级上下文,panic 的范围会大得多——特权边界的实验永远先想清楚"崩了算谁的"。
💡 关键直觉:特权级模型与 1.2 节的 ISA 契约是同一枚硬币的两面——契约规定"指令做什么",特权模型规定"谁有资格让它做"。读系统级代码(内核、驱动)时,判断一条指令是否合法的第一问永远是"此时 CPL 是几"。
指令系统三章清点完毕。下一章进入汇编最独特的疆域:内存——寻址方式、栈帧,以及真实世界的错位排错。