x86 体系结构基础——特权级、中断异常、内存与总线
x86 体系结构基础——特权级、中断异常、内存与总线
本文目标:给机器学习系统工程师/虚拟化初学者打底——把 x86 上"软件如何与硬件协作"的全链路讲清。从特权级 ring0~ring3 出发,串到用户态/内核态切换、中断与异常、MMU 与页表、DMA、PCIe 总线、最后到 IOMMU。读完你能解释"为什么虚拟化需要 VT-x/EPT、为什么容器隔离要靠内核、为什么 GPU 能直接读写内存"。
一、全景:一次系统调用穿越了多少层
先给一张全景,后面逐层拆:
1 | 应用(用户态, ring3) |
这五件事——特权级、用户/内核态、中断异常、MMU 页表、DMA/PCIe/IOMMU——就是 x86 软硬件协作的全部地基。下面逐个讲。
二、特权级 ring0~ring3
x86 提供 4 个特权级(Protection Rings),用 CPL(Current Privilege Level,当前特权级)表示 CPU 当前在哪个环:
1 | ring0 ← 内核 / hypervisor:可执行所有指令、访问所有寄存器与硬件 |
现代操作系统只用了 ring0 和 ring3 两级(Linux/Windows 都是),ring1/ring2 历史上设计给微内核用,实际基本闲置。所以日常说的"内核态=ring0、用户态=ring3"。
2.1 为什么需要特权级
核心矛盾:应用不可信,但应用又必须能用硬件资源(网络、磁盘、内存)。解决方式是把指令分成两类:
- 普通指令:任何环都能执行(加法、访存、逻辑运算)。
- 特权指令 / 敏感指令:只有 ring0 能执行,ring3 执行会触发 General Protection Fault(#GP,异常 13) 陷入内核。例如:
hlt(停 CPU)、cli/sti(关/开中断)- 修改 CR0/CR3(控制寄存器、页表基址)
lgdt/lidt(加载 GDT/IDT)IN/OUT(端口 IO,操作设备寄存器)WRMSR/RDMSR(读写 model-specific register)
2.2 DPL 与访问控制
每个段描述符(在 GDT/LDT 里)有 DPL(Descriptor Privilege Level,描述符特权级),表示"允许哪个最低级别的 CPL 访问它"。访问规则简化为:
RPL(Requested Privilege Level)是段选择子里可由软件设定的字段,用来"自降特权"防止越权。
面试要点:ring 机制的本质 = 给指令分级 + 给段描述符标 DPL,硬件在执行时做 CPL vs DPL 校验,不满足就 #GP 陷入内核。这是"应用不能直接碰硬件"的硬件级保障。
2.3 64 位下的"扁平模型"
x86-64 进入长模式后,段机制大幅弱化——代码段/数据段基址都设为 0、界限都设为全空间,叫"扁平内存模型"。所有段覆盖整个 64 位地址空间,CS/DS 只是装个 DPL 标签做权限,寻址完全交给分页(MMU 页表)。所以现代 x86 的隔离主要靠页表,不是靠分段。
三、用户态与内核态、系统调用
"用户态/内核态"是 ring0/ring3 在操作系统语义层的叫法。
3.1 两种执行上下文
- 用户态(ring3):跑应用代码,权限受限,不能直接执行特权指令、不能直接访问物理硬件、不能访问内核内存。
- 内核态(ring0):跑内核代码,全权。
3.2 特权级切换:系统调用
应用要读文件、发包,必须进内核。过程:
- 应用把参数放进寄存器(Linux:
rax放系统调用号,rdi/rsi/rdx...放参数),执行syscall指令(64 位;老式用int 0x80软中断)。 syscall由硬件完成特权级切换:- 保存用户态
RIP/RFLAGS到内核栈(或 MSRs)。 - 从
IA32_LSTARMSR 加载内核入口地址,跳过去。 - CPL 从 3 变 0,切到内核栈。
- 保存用户态
- 内核执行系统调用处理函数,校验参数(关键安全点:内核绝不信任用户指针,要
copy_from_user校验)。 - 返回时执行
sysret/iret,恢复用户态现场。
3.3 切换的代价
每次系统调用都有:上下文切换 + 特权级切换 + 栈切换 + TLB 可能失效 的开销,约几百纳秒到微秒级。这就是为什么:
- 高吞吐网络要
io_uring/DPDK/zero-copy 绕开系统调用。 - 内存数据库要把数据常驻用户态避免反复
read。 - RDMA 要 kernel bypass(参见 RDMA 那篇)。
面试金句:“ring0/ring3 是硬件特权级,用户态/内核态是 OS 语义;系统调用是两者唯一的合法桥梁,桥梁的代价就是上下文切换——所有绕内核技术(RDMA、DPDK、io_uring)本质都是少过这座桥。”
四、中断与异常
CPU 在执行时会被两类事件打断:中断(异步,外部来)和异常(同步,当前指令引发)。
4.1 区别
| 中断(Interrupt) | 异常(Exception) | |
|---|---|---|
| 来源 | 外部硬件(网卡、定时器、键盘) | CPU 执行当前指令引发 |
| 时机 | 异步,指令边界 | 同步,当前指令 |
| 举例 | 网卡收包 IRQ、时钟 tick | 缺页 #PF、除零 #DE、非法指令 #UD、#GP |
4.2 中断向量与 IDT
x86 用 0~255 号向量标识每个中断/异常。关键向量:
- 0
#DE除零、6#UD非法指令、13#GP通用保护、14#PF缺页、 - 32~255 是 可屏蔽硬件中断(由 IO-APIC/Local APIC 投递)。
IDT(Interrupt Descriptor Table,中断描述符表):一张 256 项的表,每项是一个门描述符(中断门/陷阱门),记录该向量的处理函数地址 + DPL。IDTR 寄存器存 IDT 基址。
4.3 处理流程(中断/异常通用)
- CPU 确定向量号 。
- 查 IDT[] 拿到处理函数地址和目标 CS(决定目标特权级)。
- 若需要特权级提升(如用户态中断进内核),切换栈到内核栈。
- 压入
RFLAGS/RIP/CS(和可能的错误码)。 - 跳到处理函数执行。
iret返回,恢复现场和特权级。
4.4 缺页异常 #PF(重点,连虚拟化)
访问一个未映射或权限不对的虚拟页时触发 #PF,CR2 寄存器存引发缺页的虚拟地址。内核 page fault 处理:
- 地址合法但页不在内存 → 从磁盘换入(demand paging)。
- 地址非法 →
SIGSEGV段错误杀进程。 - 写只读页但 COW(copy-on-write)→ 复制页改可写。
虚拟化关联:在 **EPT(扩展页表)**下,Guest 的地址翻译有两级——GVA→GPA(Guest 页表)和 GPA→HPA(EPT)。Guest 页表缺页走 Guest 自己的 #PF;EPT 缺页(GPA 没映射到物理页)触发 EPT Violation,陷入 hypervisor 处理。两者截然不同,是 KVM 内存虚拟化的核心。
4.5 中断控制器:PIC → APIC
- 老 PIC(8259):最多 15 个中断。
- APIC:Local APIC(每 CPU 一个,处理本地中断、IPI 核间中断)+ IO-APIC(收设备中断转发给 CPU)。多核必备。
- MSI/MSI-X:设备直接写 Local APIC 寄存器发中断,绕过 IO-APIC 和电平信号, PCIe 设备主流方式。中断数更多、更快、更省中断引脚。
五、MMU 与页表(虚拟内存的地基)
现代系统里"内存隔离"主要靠分页:每个进程有自己的虚拟地址空间,靠页表翻译到物理地址,页表项带权限位,越权访问 → #PF。
5.1 虚拟地址 → 物理地址翻译
x86-64 用 4 级页表(48 位虚拟地址,普通 4KB 页;5 级页表支持 57 位):
1 | 虚拟地址(48bit): [ 签名(高位) | PML4(9) | PDPT(9) | PD(9) | PT(9) | 页内偏移(12) ] |
每级 9 位索引(512 项),页内偏移 12 位(4KB)。**每张表 4KB,一个进程完整映射 48 位空间要 4 级表全展开——实际靠 lazy 分配,按需建表。
5.2 页表项 PTE 的权限位(隔离核心)
PTE 关键位:
- R/W:读写位。写只读页 → #PF。
- U/S:User/Supervisor 位。U=1 用户态可访问;U=0 只有 ring0 能访问。这就是"内核内存对用户态不可见"的硬件机制——内核页 PTE 的 U=0。
- NX:No-eXecute,不可执行位。数据页设 NX 防代码注入(DEP/W^X)。
- P:Present,页是否在内存。P=0 触发缺页。
- A/D:Accessed/Dirty,访问/写过位。
5.3 TLB 与 PCID
页表是 4 级内存查表,每次访存要 4 次内存读 → 太慢。TLB(Translation Lookaside Buffer) 是 CPU 内缓存最近翻译的 PTE,命中就一步拿到物理地址。TLB 失效(miss)才走 4 级表。
- 进程切换换页表(CR3)→ TLB 大量失效。PCID(Process Context ID) 给每个进程一个 ID,TLB 项带 PCID 标记,切换时不刷别的进程的 TLB,加速上下文切换。
- 大页(2MB/1GB HugePages):减少页表层级、减少 TLB miss,适合大内存工作负载(数据库、ML 显存映射)。
5.4 虚拟内存的意义
- 隔离:进程地址空间独立,互不踩踏。
- 按需分配:
mmap只建 VMA,真访问才分配物理页(demand paging)。 - 共享:共享库、COW fork 都靠映射同一物理页。
- 交换:内存压力下换出到磁盘,突破物理内存上限(但慢)。
六、DMA:设备直接读写内存
6.1 没有 DMA 的世界
CPU 用 IN/OUT 指令逐字节/逐字从设备寄存器读数据到寄存器再存内存——PIO(Port IO)。吞吐极低、CPU 全程搬运。1Gbps 网卡能把 CPU 跑满。
6.2 DMA 的解法
DMA(Direct Memory Access,直接内存访问):让设备里的 DMA 引擎自己读写内存,CPU 只下命令"把这块内存发出去"或"收到数据放到这块内存",然后 CPU 去干别的,DMA 完成后用中断通知 CPU。
数据流:
1 | CPU 下发 DMA 描述符(含内存地址、长度、方向) |
6.3 DMA 用的地址是物理地址
关键点:DMA 引擎直接操作物理地址,不走 CPU MMU 页表。也就是说设备看到的是物理地址(或经 IOMMU 翻译后的 IOVA,见下文)。所以驱动要给设备物理地址或 IOVA,不能给虚拟地址。这就是为什么驱动要先把用户态虚拟地址 pin 住并翻译(参见 RDMA MR 注册)。
6.4 一致性与缓存问题
- DMA coherence:DMA 写内存时,CPU cache 里可能有旧数据。x86 默认 cache coherent(硬件保证 DMA 与 cache 一致),软件省心;某些架构(ARM、嵌入式)需要显式 cache flush。
- 流式映射 vs 一致性映射:Linux DMA API 区分
dma_alloc_coherent(一致性,自动同步)和dma_map_single(流式,需手动 sync)。
七、PCIe 总线:设备挂在哪里
DMA 设备通过 PCIe(Peripheral Component Interconnect Express) 连到 CPU。
7.1 拓扑
1 | CPU ── Root Complex(RC) ──┬── Switch ──┬── Endpoint(网卡/GPU/SSD) |
7.2 配置空间与 BAR
每个 PCIe 设备有 256B/4KB 配置空间,含厂商/设备 ID、BAR(Base Address Register)。BAR 是设备向系统申请一段地址空间(MMIO 空间)的寄存器——CPU 往这段 MMIO 地址写数据,就等于写设备寄存器。这就是 CPU 访问设备的两种方式:
- MMIO:把设备寄存器映射到一段物理地址,CPU 用普通访存指令访问(主流,现代设备)。
- Port IO:用
IN/OUT指令访问独立端口地址(老式设备,如 8259 PIC、IDE)。
7.3 PCIe 的两种事务
- TLP(Transaction Layer Packet):PCIe 上传的报文,分 Memory Read/Write、Configuration、Message 等。
- P2P(Peer-to-Peer):两个 endpoint 之间直接传数据,不经过主内存(CPU 不参与)。GPU Direct RDMA 就是 PCIe P2P——网卡直接 DMA GPU 显存。要求两个设备在 PCIe 拓扑上能 P2P(最好同 Switch 下)。
7.4 MSI/MSI-X
PCIe 设备用 MSI/MSI-X 发中断:设备写一个特定内存地址(Local APIC 的 MMIO 区域),APIC 自动给 CPU 发中断。比传统 INTx 引脚快、中断数多(MSI-X 可达 2048 个向量),网卡每收发队列一个中断、GPU Direct 一个完成中断都用它。
八、IOMMU:给 DMA 上权限(虚拟化的安全基石)
8.1 问题:DMA 不受 ring 限制
DMA 绕开 CPU,也绕开 ring0/ring3——设备一旦拿到物理地址,能读写整块物理内存,包括内核。如果一台机器上多个 VM 共享设备,或一个设备分给某 VM,怎么办?需要给 DMA 也加一层"页表 + 权限",这就是 IOMMU(Intel 称 VT-d,AMD 称 AMD-Vi,ARM 称 SMMU)。
8.2 IOMMU 做两件事
- 地址翻译:设备用的是 IOVA(I/O Virtual Address),IOMMU 有一张类似页表的表,把 IOVA 翻译成 HPA。驱动给设备的是 IOVA,IOMMU 翻译后真去访问物理内存。
- 隔离与权限:只允许设备访问它被授权的 IOVA 范围,越权 → IOMMU 阻断并报错(fault)。这样一个 VM 的设备只能访问这个 VM 的内存。
8.3 IOMMU 的关键能力
- VFIO(设备直通):把一块物理设备独占地分配给一个 VM,VM 里跑原生驱动直接操作真硬件,性能近裸机。靠 IOMMU 保证这个 VM 的设备 DMA 只能碰到这个 VM 的内存,不会越界到 host 或别的 VM。GPU 直通就是 VFIO + IOMMU。
- SR-IOV:把一块物理网卡切成多个 VF(Virtual Function),每个 VF 是一个轻量 PCIe 设备,各有独立配置空间和 DMA 队列,分给不同 VM/容器,硬件级隔离。云网络高频。
- 共享 IOMMU 组:同 IOMMU 组的设备互相可见、可 P2P,跨组不可见——GPU Direct RDMA 要求网卡和 GPU 在同一 IOMMU 组(同一 PCIe Switch 下的 P2P 才安全)。
8.4 DMA Remapping 与中断重映射
- DMA Remapping:IOMMU 的核心,按设备 BDF(Bus/Device/Function)查对应翻译表,重写 DMA 地址。
- Interrupt Remapping:设备 MSI/MSI-X 的中断也经 IOMMU 校验和重映射后再投递给 CPU,防止恶意设备伪造中断。虚拟化中断隔离靠它。
8.5 串起虚拟化全图
把今天所有概念连起来看"一个 VM 怎么发出一个网络包":
1 | VM 里 Guest 应用调用 send() |
每一步都用到了今天讲的:特权级、用户/内核态、中断、MMU 页表、DMA、PCIe、IOMMU。这就是为什么这些是虚拟化和系统开发的地基。
九、面试速答清单
Q1:ring0/ring3 是什么,为什么需要?
x86 的 4 级特权(实际用 2 级),ring0 内核全权、ring3 应用受限。硬件在执行特权/敏感指令时校验 CPL vs 段描述符 DPL,不满足触发 #GP 陷入内核。作用是防止不可信应用直接碰硬件和内核内存。
Q2:系统调用是怎么从用户态进内核态的?
应用执行
syscall指令,硬件完成特权级切换:保存 RIP/RFLAGS、从 LSTAR MSR 取内核入口、CPL 3→0、切内核栈;内核处理完用sysret/iret返回。代价是上下文+特权+栈切换,所以高吞吐场景要绕开系统调用(RDMA/DPDK/io_uring)。
Q3:中断和异常的区别?缺页是哪个?
中断异步来自外部设备,异常同步由当前指令引发。缺页 #PF 是异常——访问未映射/越权虚拟页时由 MMU 触发,CR2 存出错地址。内核按合法/非法/COW 分流处理。
Q4:页表怎么实现内存隔离?
每进程独立页表,PTE 有 U/S 位:内核页 U=0,用户态访问就 #PF。R/W 位管读写、NX 位管执行。进程切换换 CR3 → TLB 失效(PCID 可缓解)。虚拟内存靠这套做隔离、按需分页、共享、交换。
Q5:DMA 为什么快?它用的什么地址?
DMA 让设备 DMA 引擎直接搬数据进/出内存,CPU 不参与逐字搬运,只在开始下命令、结束收中断时参与。DMA 用物理地址(或经 IOMMU 的 IOVA),不走 CPU MMU 页表,所以驱动要给设备物理地址/IOVA。
Q6:IOMMU 解决什么问题?和 MMU 什么关系?
MMU 管 CPU 访存的翻译隔离,但 DMA 绕开 MMU 也能读写物理内存。IOMMU 是"给设备用的 MMU"——按设备 BDF 查表把 IOVA 翻译成物理地址,并校验权限,越权报 fault。这样设备直通给 VM 时,VM 的 DMA 只能碰自己的内存。GPU 直通、SR-IOV、GPU Direct RDMA 的安全地基都是 IOMMU。
Q7:GPU Direct RDMA 为什么要求网卡和 GPU 在同一 PCIe Switch 下?
GDR 走 PCIe P2P(网卡直接 DMA GPU 显存,不经主内存)。P2P 要求两端在 PCIe 拓扑上可达且在同一 IOMMU 组(同 Switch 下最安全、性能最好);跨 PCIe root/跨 NUMA 会引入桥接和地址翻译开销,性能下降明显。
十、一张图收口
1 | 特权: ring3(用户) ──syscall──► ring0(内核) ──驱动──► 设备 |
记住主线:特权级把指令分了层,中断异常是软硬件的打断通道,MMU 页表管 CPU 访存隔离,DMA 让设备直连内存,PCIe 是设备挂载的血管,IOMMU 是给 DMA 上的锁。 这五块是 x86 软硬协作的地基,也是虚拟化、容器、RDMA、GPU 直通等所有上层技术的根。
参考资料
- Intel ® 64 and IA-32 Architectures Software Developer’s Manual, Vol.3(特权级/中断/分页/VMX/EPT 权威来源)
- Intel ® Virtualization Technology for Directed I/O (VT-d) Specification(IOMMU)
- PCI Express Base Specification
- 《深入理解 Linux 内核》《Linux 设备驱动程序》
- kernel.org 文档:
Documentation/x86/,Documentation/driver-api/