﻿# x86-64 关键寄存器 —— Linux 性能/底层机制的前置知识

> **核心问题**：看 `context-switch.md` 时遇到 `CR3`、`CR0.TS`、`XMM`/`YMM` 是什么？看 `syscall-details.md` 时 `MSR_LSTAR`、`CS=0x33`、`RFLAGS` 又是什么？看 `interrupts.md` 时 `IDT`、`IF` 标志、`APIC` 又是怎么工作的？


> 本文是一份**体系化寄存器速查手册**——按功能分组列出 x86-64 长模式下 Linux 性能分析和底层调试最关键的寄存器，每个都说明"它是什么、在哪个场景出现、对性能意味着什么"。它是阅读 [syscall-details.md](/concepts/process/syscall-details.md)、[context-switch.md](/concepts/process/context-switch.md)、[segment-management.md](/concepts/process/segment-management.md)、[interrupts.md](/concepts/process/interrupts.md) 等底层文档的前置知识。

## 零、总览：x86-64 寄存器分类全景

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<gpr>> #E3F2FD
  BorderColor<<gpr>> #1976D2
  BackgroundColor<<seg>> #C8E6C9
  BorderColor<<seg>> #388E3C
  BackgroundColor<<ctrl>> #FFE0B2
  BorderColor<<ctrl>> #EF6C00
  BackgroundColor<<msr>> #F3E5F5
  BorderColor<<msr>> #7B1FA2
  BackgroundColor<<simd>> #FFCDD2
  BorderColor<<simd>> #C62828
  BackgroundColor<<other>> #ECEFF1
  BorderColor<<other>> #607D8B
}
rectangle "通用寄存器\n(GPR × 16)\nrax rbx rcx rdx\nrsp rbp rsi rdi\nr8 ~ r15" <<gpr>> as GPR
rectangle "段寄存器\n(Segment × 6 + GDTR/IDTR/TSS)\nCS DS SS ES FS GS\nGDTR IDTR TR" <<seg>> as SEG
rectangle "控制寄存器\n(Control × 5)\nCR0 CR2 CR3 CR4\nEFER (MSR 0xC0000080)" <<ctrl>> as CTRL
rectangle "MSR 寄存器\n(Model-Specific)\nLSTAR STAR FMASK\nFS/GS BASE\nAPIC Base ..." <<msr>> as MSR
rectangle "SIMD/浮点寄存器\nXMM(×16) YMM(×16) ZMM(×32)\nx87 FPU ST(0-7)\nMXCSR XCR0" <<simd>> as SIMD
rectangle "其他\nRFLAGS RIP\n调试寄存器 DR0-DR7\nAPIC 寄存器\n时间戳计数器 TSC" <<other>> as OTHER
GPR -[hidden]-> SEG
SEG -[hidden]-> CTRL
CTRL -[hidden]-> MSR
MSR -[hidden]-> SIMD
SIMD -[hidden]-> OTHER
note bottom of GPR : 共约 40 类寄存器\n本文重点覆盖与 Linux 性能/底层相关的部分
@enduml
```

## 一、通用寄存器（GPR —— General Purpose Registers）

**16 个 64 位通用寄存器**，是 CPU 做运算、传参、存地址的"工作台"。

### 1.1 寄存器表

| 寄存器 | 低 32 位 | 低 16 位 | 低 8 位 | 调用约定角色 | 在 Linux 底层中的关键场景 |
|:---|:---|:---|:---|:---|:---|
| **RAX** | EAX | AX | AL | 返回值 / 累加器 | **syscall 调用号**（`mov $N, %rax`）、**函数返回值** |
| **RBX** | EBX | BX | BL | callee-saved | `switch_to()` 中压栈保存、内核栈上的固定"锚点" |
| **RCX** | ECX | CX | CL | 第 4 参数（用户态） | **被 `syscall` 硬件征用**——存返回 RIP，这就是为什么 syscall 第 4 参数用 `r10` 而非 `rcx` |
| **RDX** | EDX | DX | DL | 第 3 参数 | syscall 的第 3 个参数、IO 端口操作（`in`/`out`） |
| **RSP** | ESP | SP | SPL | **栈指针** | **上下文切换的核心**——`switch_to()` 一换 RSP 执行流就变了；syscall 时从用户栈切到内核栈 |
| **RBP** | EBP | BP | BPL | callee-saved / 帧指针 | 栈回溯（frame pointer chain）、GDB 中 `bt` 命令追溯调用链 |
| **RSI** | ESI | SI | SIL | 第 2 参数 | syscall 的第 2 个参数、字符串操作源地址 |
| **RDI** | EDI | DI | DIL | 第 1 参数 | syscall 的第 1 个参数（如 fd、buf 指针） |
| **R8** | R8D | R8W | R8B | 第 5 参数 | syscall 的第 5 个参数 |
| **R9** | R9D | R9W | R9B | 第 6 参数 | syscall 的第 6 个参数 |
| **R10** | R10D | R10W | R10B | 临时（caller-saved） | **syscall 的第 4 参数**——因为 `rcx` 被征用 |
| **R11** | R11D | R11W | R11B | 临时（caller-saved） | **被 `syscall` 硬件征用**——存 RFLAGS，`sysret` 时恢复 |
| **R12~R15** | R12D~R15D | R12W~R15W | R12B~R15B | callee-saved | `switch_to()` 中压栈/弹栈保存恢复 |

### 1.2 调用约定：为什么有些寄存器由调用者保存、有些由被调用者保存？

```bash
System V AMD64 ABI 的寄存器分工：
caller-saved（调用者保存）：
  rax rcx rdx rsi rdi r8 r9 r10 r11
  → 如果你调一个函数后还需要这些值，调用前自己 push
  → 被调函数可以随意覆盖它们，不用恢复
callee-saved（被调用者保存）：
  rbx rbp r12 r13 r14 r15
  → 被调函数如果用这些寄存器，必须先 push，返回前 pop 回去
  → 调用者可以放心——这些寄存器的值不会被破坏
栈指针（特殊）：
  rsp → 必须 16 字节对齐（call 指令前），被调函数通过 sub/add 管理自己的栈帧
```

### 1.3 在上下文切换中的作用

```asm
; context_switch() → switch_to(prev, next) 简化版
; 这不是真实内核代码，是逻辑等价版本
switch_to:
    ; 1. 保存 callee-saved 寄存器到 prev 的内核栈上
    pushq   %rbp
    pushq   %rbx
    pushq   %r12
    pushq   %r13
    pushq   %r14
    pushq   %r15
    ; 2. ★ 关键：保存 prev 的 RSP 到 prev->thread.sp
    movq    %rsp, %rax              ; rax = prev 当前的 RSP
    movq    %rax, prev(%rip)        ; prev->thread.sp = rax
    ; 3. ★ 关键：把 RSP 切换到 next 的内核栈
    movq    next(%rip), %rsp        ; rsp = next->thread.sp
    ; ↑ 从这行开始，执行流已经是 next 了！
    ;   push/pop/call/ret 都操作 next 的内核栈
    ; 4. 恢复 next 的 callee-saved 寄存器
    popq    %r15
    popq    %r14
    popq    %r13
    popq    %r12
    popq    %rbx
    popq    %rbp
    ret                             ; 返回到 next 的调用者
```

> **为什么只保存/恢复 callee-saved 寄存器？**


> caller-saved 寄存器（rax/rcx/rdx/rsi/rdi/r8-r11）在调用 `switch_to()` 时已经被调用者（即 `context_switch()`）保存过了——这是 C 调用约定的自然结果。`switch_to()` 本身只需要负责 callee-saved 的那几个。详见 [context-switch.md](/concepts/process/context-switch.md) §5.4。

### 1.4 RCX 和 R11 被 syscall 征用的完整故事

这是理解"为什么系统调用第 4 个参数用 r10 而不是 rcx"的关键：

```bash
syscall 指令的硬件微操作（一条指令做了三件事）：
1. 保存返回地址
   RCX ← RIP          (下一条用户态指令的地址)
2. 保存标志寄存器
   R11 ← RFLAGS        (当前的标志位状态)
3. 跳转到内核入口
   RIP ← MSR_LSTAR     (内核系统调用入口地址，如 entry_SYSCALL_64)
所以：
- rcx 的值被破坏了 → 不能用它传第 4 个参数
- r11 的值被破坏了 → 不能用它传参数
- 因此约定：syscall 第 4 参数用 r10（而非普通函数约定的 rcx）
sysret 指令反向操作：
   RIP ← RCX           (恢复用户态返回地址)
   RFLAGS ← R11         (恢复标志)
```

> 详见 [syscall-details.md](/concepts/process/syscall-details.md) §二、§四。

## 二、RIP 和 RFLAGS

### 2.1 RIP —— 指令指针

| 属性 | 说明 |
|:---|:---|
| **全称** | Instruction Pointer（64 位模式下） |
| **32 位名** | EIP |
| **可读否** | 不可直接 `mov` 读取，但 `lea (%rip), %rax` 可间接获取 |
| **可写否** | 不可直接 `mov`，只能通过跳转指令（`jmp`/`call`/`ret`/`syscall`/`sysret`）间接修改 |

在性能分析中的关键使用场景：

```bash
场景 1: crash 分析
  → GDB 中 info registers 看 rip
  → rip 指向崩溃的那条指令地址
  → 通过 /proc/pid/maps 判断落在哪个段（代码段/库/堆/栈）
场景 2: perf 采样
  → perf record 采样时记录的就是 RIP
  → perf report 把 RIP 映射回函数名 → 火焰图
场景 3: syscall/sysret
  → syscall:  RCX ← RIP（保存返回地址）
  → sysret:   RIP ← RCX（恢复返回地址）
场景 4: 中断/异常
  → CPU 硬件自动把 RIP 压入内核栈
  → iret 时从栈上弹出恢复
```

### 2.2 RFLAGS —— 标志寄存器

64 位的标志寄存器，每一位代表一个状态或控制标志。关键位：

| 位 | 名称 | 含义 | 在 Linux 底层的关键场景 |
|:---|:---|:---|:---|
| **0** | CF | 进位标志 | 无符号运算溢出 |
| **6** | ZF | 零标志 | 结果为 0 |
| **7** | SF | 符号标志 | 结果为负 |
| **9** | **IF** | **中断使能标志** | `cli` 清除（关中断）、`sti` 设置（开中断）。**关中断 = 禁止本 CPU 响应可屏蔽中断**，是 spin_lock 等临界区保护的硬件基础 |
| **10** | DF | 方向标志 | 字符串操作方向（`cld`/`std`） |
| **11** | OF | 溢出标志 | 有符号运算溢出 |
| **12-13** | IOPL | IO 特权级 | 控制哪些 ring 可执行 IO 指令（`in`/`out`） |
| **14** | NT | 嵌套任务 | 与任务切换相关（Linux 几乎不用硬件任务切换） |
| **16** | RF | 恢复标志 | 调试相关，屏蔽指令断点 |
| **17** | VM | 虚拟 8086 模式 | 兼容 16 位实模式程序 |
| **18** | AC | 对齐检查 | 启用后可检测未对齐的内存访问 |
| **21** | ID | CPUID 可用 | 标识 CPU 支持 CPUID 指令 |

**在 Linux 中的两个关键点：**

1. **syscall 时 RFLAGS 的处理**：

```bash

   syscall:   R11 ← RFLAGS           (硬件保存)

               RFLAGS ← RFLAGS & ~MSR_FMASK  (清除 FMASK 中指定的位，通常包括 IF)

   sysret:    RFLAGS ← R11           (硬件恢复)

   ```

   `MSR_FMASK` 在 Linux 中默认清除 IF——进入内核时自动关中断（但不是完全关，NMI 仍可到达）。

2. **中断上下文中 IF 的意义**：

   - 关中断区域（`local_irq_disable()`）→ `cli` 清除 IF → 可屏蔽中断不响应
   - 中断 handler 中默认 IF=0（关），下半部（softirq）中 IF=1（开）
   - **中断处理中 IF 的状态决定了能否嵌套**

## 三、段寄存器 —— 特权级的门禁

> 本文只做速查。完整的段选择子结构、GDT 描述符逐位拆解、权限检查公式见 [segment-management.md](/concepts/process/segment-management.md)。

### 3.1 六个段寄存器

| 寄存器 | 全称 | 用途 | x86-64 Linux 典型值（用户态） | x86-64 Linux 典型值（内核态） |
|:---|:---|:---|:---|:---|
| **CS** | Code Segment | 代码段选择子 + CPL | `0x33`（ring 3, 用户代码段） | `0x10`（ring 0, 内核代码段） |
| **DS** | Data Segment | 默认数据段 | `0x2B`（用户数据段） | `0x18`（内核数据段） |
| **SS** | Stack Segment | 栈段 | `0x2B`（同数据段） | `0x18`（同数据段） |
| **ES** | Extra Segment | 附加数据段 | `0x2B` | `0x18` |
| **FS** | F Segment | 通用，常用作 TLS 基址 | 指向线程局部存储（TLS） | 切换为内核态的 per-CPU 数据 |
| **GS** | G Segment | 通用，常用作 per-CPU 基址 | 可能指向 TLS | 切换为内核态的 per-CPU 数据（或保持） |

### 3.2 CS 的段选择子结构

```bash
CS = 0x33 的位分解（16 位段选择子）：
bit 15       3  2  1  0
    ┌──────────┬──┬─────┐
    │  Index   │TI│ RPL │
    └──────────┴──┴─────┘
     13 bits    1   2 bits
0x33 = 0b 0000000 0 11 0011
       Index = 6    TI=0(查GDT)  RPL=3(用户态)
0x10 = 0b 0000000 0 00 0010
       Index = 2    TI=0(查GDT)  RPL=0(内核态)
```

**关键理解**：RPL（Requested Privilege Level）嵌在段选择子低 2 位。`syscall` 指令硬件将 CS 从 `0x33`（RPL=3）改为 `0x10`（RPL=0）——**CPL 就这样跟着 CS 变了**。

### 3.3 FS 和 GS：per-CPU 和 TLS 的秘密

```bash
FS 和 GS 是 x86-64 长模式下唯一仍然使用"基址"的段寄存器：
用户态：
  FS 基址 → 线程局部存储（TLS）的起始地址
  → 访问方式：mov %fs:0xNN, %rax  读取 TLS 中偏移 0xNN 的值
  → C/C++ 中的 thread_local 变量、errno 都通过 FS 访问
内核态（通过 swapgs 指令切换）：
  GS 基址 → 指向 per-CPU 数据结构（struct per_cpu_data）
  → 访问方式：mov %gs:0xNN, %rax  读取本 CPU 的内核数据
  → 如 current（当前 task_struct）通常通过 GS 获取
swapgs 指令：
  → 交换 MSR_KERNEL_GS_BASE 和 MSR_GS_BASE
  → syscall 入口执行 swapgs，出口执行 swapgs
  → 实现用户 GS（TLS）↔ 内核 GS（per-CPU）的原子切换
```

### 3.4 GDTR 和 IDTR

| 寄存器 | 全称 | 指向什么 | 在 Linux 中的特点 |
|:---|:---|:---|:---|
| **GDTR** | Global Descriptor Table Register | GDT（全局描述符表） | 每个 CPU 有自己的 GDT（per-CPU），`lgdt` 加载 |
| **IDTR** | Interrupt Descriptor Table Register | IDT（中断描述符表） | 全局一张表，`lidt` 加载；将中断向量号映射到 handler 入口 |
| **TR** | Task Register | 当前 TSS（任务状态段） | `ltr` 加载；TSS 存内核栈指针（RSP0），中断/特权级切换时用 |

## 四、控制寄存器 —— CPU 模式的开关

五个控制寄存器（CR0/CR2/CR3/CR4）+ 一个扩展特性寄存器（EFER），是决定 CPU 行为模式的关键。

### 4.1 速查表

| 寄存器 | 关键位 | 在 Linux 底层的关键场景 |
|:---|:---|:---|
| **CR0** | **PG** (bit 31) | 分页使能——**打开后才能用虚拟地址**。开机在实模式→保护模式切换时设置 |
| | **PE** (bit 0) | 保护模式使能——脱离实模式 |
| | **TS** (bit 3) | Task Switched——FPU 惰性切换的核心。`switch_to()` 后设置 TS，新 task 首次用浮点触发 `#NM` 异常，异常 handler 中才真正保存/恢复 FPU 状态 |
| | **WP** (bit 16) | Write Protect——置 1 后，即使 ring 0 也不能写只读页。Linux 依赖此位实现 CoW（写时复制） |
| | **MP/EM/NE** (bit 1/2/5) | FPU 相关控制 |
| **CR2** | 全部 64 位 | **缺页异常时，CPU 自动把引发缺页的线性地址写入 CR2**。`do_page_fault()` handler 从 CR2 读出这个地址做判断 |
| **CR3** | PML4 表物理地址 (bits 12-51) | **页表根指针**——存当前进程的 PML4 表物理基址。切进程时 `mov cr3, next->pgd`。**KPTI 下每次 syscall 进出要切两次 CR3** |
| | PCID (bits 0-11, 需 CR4.PCIDE=1) | Process-Context Identifier——给 TLB 条目打标签，换 CR3 时不刷 TLB |
| **CR4** | **PAE** (bit 5) | 物理地址扩展——64 位模式的先决条件 |
| | **PGE** (bit 7) | 全局页使能——标记为 Global 的页不随 CR3 切换刷 TLB |
| | **OSFXSR** (bit 9) | 支持 SSE 指令（FXSAVE/FXRSTOR） |
| | **OSXMMEXCPT** (bit 10) | 支持 SSE 非掩码异常 |
| | **FSGSBASE** (bit 16) | 允许用户态直接读写 FS/GS 基址（`rdfsbase`/`wrfsbase`） |
| | **PCIDE** (bit 17) | 启用 PCID（与 CR3 的 PCID 字段配合） |
| | **SMEP** (bit 20) | Supervisor Mode Execution Prevention——禁止 ring 0 执行用户态页面的代码 |
| | **SMAP** (bit 21) | Supervisor Mode Access Prevention——禁止 ring 0 访问用户态页面（除非设置 RFLAGS.AC） |
| **EFER** | **SCE** (bit 0) | System Call Extension——**启用 syscall/sysret 指令**。不开此位，syscall = #UD 异常 |
| (MSR 0xC0000080) | **LME** (bit 8) | Long Mode Enable——启用 64 位长模式 |
| | **LMA** (bit 10) | Long Mode Active——只读，表示 CPU 当前在长模式 |
| | **NXE** (bit 11) | No-Execute Enable——启用页表中的 NX 位，支持 DEP/W^X |

### 4.2 CR3 与 KPTI —— 为什么每次 syscall 要切 CR3？

```bash
普通情况（无 KPTI）：
  用户态和内核态共用一套页表
  → 内核空间映射在所有进程的页表中（0xFFFF8000... 区域）
  → syscall 不换 CR3
KPTI（Kernel Page Table Isolation，Meltdown 漏洞缓解）：
  每个进程有两套页表：
  - 用户页表：只有用户空间映射 + 极小的内核跳板
  - 内核页表：只有内核空间映射
  → syscall 进入：CR3 切到内核页表
  → syscall 返回：CR3 切回用户页表
  → 每次切 CR3 导致 TLB 刷新（PCID 可部分缓解）
开销量化：
  - 无 KPTI + 无 PCID：~0 cycles（不切）
  - 有 KPTI + 无 PCID：~300-500 cycles（刷 TLB）
  - 有 KPTI + 有 PCID：~100 cycles（保留打了标签的 TLB 条目）
```

> 详见 [syscall-details.md](/concepts/process/syscall-details.md) §五 和 [../cache/tlb.md](/concepts/cache/tlb.md)。

### 4.3 CR0.TS —— FPU 惰性切换的核心

```bash
惰性 FPU 切换的工作流程：
步骤 1: switch_to(A→B) 时
  ① 不立即保存 A 的 XMM/YMM/ZMM 寄存器（它们可能很大，最多 2KB）
  ② 只设置 CR0.TS = 1
  → 开销极小
步骤 2: B 首次使用浮点指令（如 movaps %xmm0, ...）
  ① CPU 检查 CR0.TS = 1
  ② 触发 #NM（Device Not Available）异常
  ③ 异常 handler：
     - 如果 A 用过 FPU，先保存 A 的 FPU/SIMD 状态到 A->thread.fpu
     - 如果 B 之前用过 FPU，恢复 B 的 FPU/SIMD 状态
     - 清除 CR0.TS = 0
     - 返回 B 继续执行
结果：
  - B 不用浮点 → 零开销
  - B 用浮点 → 只在这时付出保存/恢复开销
```

> 详见 [context-switch.md](/concepts/process/context-switch.md) §5.5。

## 五、MSR 寄存器 —— 模型专用寄存器

MSR（Model-Specific Register）不是一种"类型"，而是一种**访问方式**——通过 `rdmsr`/`wrmsr` 指令以索引方式读写。每个 MSR 有一个 32 位地址。

### 5.1 与系统调用相关的 MSR（Linux 最关键的 MSR）

| MSR | 地址 | 作用 | 初始化内容 |
|:---|:---|:---|:---|
| **MSR_STAR** | `0xC0000081` | 设置 syscall/sysret 的 CS/SS 段选择子 | 高 32 位：syscall 目标 CS/SS；低 32 位：sysret 目标 CS/SS |
| **MSR_LSTAR** | `0xC0000082` | **syscall 入口地址（64 位 RIP）** | `entry_SYSCALL_64` |
| **MSR_CSTAR** | `0xC0000083` | 兼容模式 syscall 入口地址（32 位进程在 64 位内核上） | `entry_SYSCALL_compat` |
| **MSR_SFMASK** | `0xC0000084` | syscall 时清除 RFLAGS 的哪些位 | 通常清除 IF（关中断）、TF（调试陷阱）、DF 等 |

**MSR_STAR 的位域分解（理解 CS/SS 切换的关键）：**

```bash
MSR_STAR 是一个 64 位寄存器：
位 [63:48] — sysret 到用户态后的 CS 基址
  → CS = (SYSCALL_CS + 16) | 3  → 即 0x33（ring 3 用户代码段）
位 [47:32] — syscall 进入内核态后的 CS 基址
  → CS = (SYSCALL_CS + 0)  → 即 0x10（ring 0 内核代码段）
位 [31:0]  — 保留（不用）
Linux 初始化：
  wrmsr(MSR_STAR, 0, (GDT_ENTRY_KERNEL32_CS << 16) | GDT_ENTRY_KERNEL_CS)
  → 高 32 位 = 0x001B_0008（实际上 SYSCALL_CS=8，所以：
    syscall  CS = 8+0 = 8  → 加上 DPL 等 → 0x10
    sysret   CS = 8+16 = 24 → 加上 RPL=3  → 0x33
```

### 5.2 与 FS/GS 基址相关的 MSR

| MSR | 地址 | 作用 |
|:---|:---|:---|
| **MSR_FS_BASE** | `0xC0000100` | FS 段基址——用户态指向 TLS |
| **MSR_GS_BASE** | `0xC0000101` | GS 段基址——内核态指向 per-CPU 数据 |
| **MSR_KERNEL_GS_BASE** | `0xC0000102` | **swapgs 的交换目标**——存内核 per-CPU 基址（或用户 GS 基址） |

```bash
swapgs 指令的原子交换：
  tmp ← MSR_GS_BASE
  MSR_GS_BASE ← MSR_KERNEL_GS_BASE
  MSR_KERNEL_GS_BASE ← tmp
用户态：GS_BASE=TLS基址,  KERNEL_GS_BASE=per-CPU基址
  → 用 GS 访问 TLS
  ↓ swapgs
内核态：GS_BASE=per-CPU基址, KERNEL_GS_BASE=TLS基址
  → 用 GS 访问 current、preempt_count 等 per-CPU 变量
```

### 5.3 其他关键 MSR

| MSR | 地址 | 作用 |
|:---|:---|:---|
| **IA32_EFER** | `0xC0000080` | 即 EFER，见 §四控制寄存器 |
| **IA32_APIC_BASE** | `0x1B` | Local APIC 基址——xAPIC 模式下存 MMIO 地址 |
| **IA32_TSC** | `0x10` | 时间戳计数器——`rdtsc` 指令读取，高精度计时 |
| **IA32_PAT** | `0x277` | Page Attribute Table——控制内存类型（WC/WB/UC/WP/WT） |
| **IA32_MTRR** 系列 | `0x200` 起 | Memory Type Range Registers——物理地址范围的内存类型控制 |
| **IA32_MISC_ENABLE** | `0x1A0` | 杂项使能——Fast String、EIST、Turbo 等 |

### 5.4 如何观测 MSR

```bash
# 读取 MSR（需要 root 权限 + msr 内核模块）
sudo modprobe msr
sudo rdmsr -a 0xC0000082        # 所有 CPU 的 LSTAR
sudo rdmsr -p 0 0xC0000082      # CPU 0 的 LSTAR
sudo rdmsr 0xC0000080           # 当前 CPU 的 EFER
# 观测 syscall 入口地址
sudo rdmsr 0xC0000082
# 输出: ffffffffabcd1234 → 这就是 entry_SYSCALL_64 的虚拟地址
# 确认 KPTI 是否开启（看 PTI 启动参数）
dmesg | grep -i "pti\|kaiser\|meltdown"
cat /sys/devices/system/cpu/vulnerabilities/meltdown
```

## 六、SIMD / 浮点寄存器 —— 被上下文切换遗忘的代价

### 6.1 寄存器组一览

| 寄存器组 | 数量 × 宽度 | 总大小 | 指令集 | 上下文切换时是否保存 |
|:---|:---|:---|:---|:---|
| **x87 FPU** | 8 × 80 bits | 80 bytes | x87 (FADD/FMUL) | 是（惰性） |
| **MMX** | 8 × 64 bits | 64 bytes | MMX（复用 x87 ST 寄存器） | 是（复用 x87 保存） |
| **XMM** | 16 × 128 bits | **256 bytes** | SSE/SSE2/SSE3/SSE4 | 是（惰性） |
| **YMM** | 16 × 256 bits | **512 bytes** | AVX/AVX2 | 是（惰性，**如果不用则为零开销**） |
| **ZMM** | 32 × 512 bits | **2048 bytes** | AVX-512 | 是（惰性，**最大开销**） |
| **MXCSR** | 1 × 32 bits | 4 bytes | SSE 状态/控制寄存器 | 是 |

**关键事实**：虽然惰性切换让"不用浮点就零开销"，但一旦新 task 使用了浮点/SIMD，`#NM` 异常 handler 中就要执行完整的 `xsave`/`xrstor`，最多搬动 **2KB+** 的数据。对于 AVX-512 频繁切换的混合负载，这是一个不可忽视的隐性开销。

### 6.2 XCR0 —— 控制哪些状态组件参与 xsave/xrstor

`XCR0`（通过 `xgetbv`/`xsetbv` 指令访问）控制 `xsave`/`xrstor` 指令保存/恢复哪些状态组件：

| 位 | 名称 | 控制的组件 |
|:---|:---|:---|
| 0 | X87 | x87 FPU 状态 |
| 1 | SSE | XMM 寄存器 + MXCSR |
| 2 | AVX | YMM 高 128 位 |
| 5 | OPMASK | AVX-512 掩码寄存器 (k0-k7) |
| 6 | ZMM_Hi256 | ZMM 高 256 位 |
| 7 | Hi16_ZMM | ZMM16-ZMM31 |

**内核在 `fpu__init_system_xstate()` 中设置 XCR0，启用它支持的所有状态组件。** 如果用户态代码使用了内核未启用的组件（如 AVX-512 而内核未在 XCR0 中置位），将触发 `#UD` 异常。

## 七、APIC 寄存器 —— 中断的硬件调度器

> 完整的中断投递机制和亲和性实践见 [interrupts.md](/concepts/process/interrupts.md) 和 [irq-affinity.md](/concepts/process/irq-affinity.md)。

### 7.1 Local APIC 关键寄存器

Local APIC 通过 MMIO 地址空间访问（基址从 IA32_APIC_BASE MSR 读取）：

| 寄存器（偏移） | 作用 | 关键场景 |
|:---|:---|:---|
| **APIC ID** (`0x020`) | 本 CPU 的唯一标识 | MSI/MSI-X 目标地址中指定的目标 APIC ID |
| **EOI** (`0x0B0`) | End of Interrupt | 中断 handler 结束时写入，通知 APIC"我处理完了，可以投递下一个" |
| **TPR** (`0x080`) | Task Priority Register | 屏蔽低优先级中断 |
| **LVT Timer** (`0x320`) | 本地定时器 | 调度器时钟中断的硬件来源 |
| **LVT LINT0/1** (`0x350`/`0x360`) | 本地中断引脚 | 连接 NMI、SMI 等 |
| **LVT Error** (`0x370`) | 错误中断 | APIC 内部错误 |
| **LVT PMU** (`0x340`) | 性能计数器中断 | perf 采样依赖的 PMU 溢出中断 |
| **ICR** (`0x300`/`0x310`) | Interrupt Command Register | **发送 IPI**——SMP 调度中的 reschedule IPI、TLB flush IPI 都通过 ICR |

### 7.2 IO-APIC 重定向表

IO-APIC 有 24 个重定向表条目（RTE），每个 64 位，控制一个 IRQ 线的投递策略：

```bash
RTE 64 位结构（简化）：
位 [63:56] — 目标 APIC ID（物理模式）或目标 CPU 集合（逻辑模式）
位 [16]    — 中断屏蔽位（1=屏蔽）
位 [15]    — 触发模式（0=边沿, 1=电平）
位 [13]    — 极性（0=高有效, 1=低有效）
位 [11]    — 目标模式（0=物理, 1=逻辑）
位 [10:8]  — 投递模式（000=固定, 111=ExtINT, ...）
位 [7:0]   — 中断向量号（0-255）
/proc/irq/N/smp_affinity 本质上就是修改 RTE 的目标字段
```

## 八、调试寄存器（DR0-DR7）和 TSC

### 8.1 DR0-DR7 —— 硬件断点

| 寄存器 | 作用 |
|:---|:---|
| **DR0-DR3** | 线性断点地址（最多 4 个硬件断点） |
| **DR6** | 调试状态——哪个断点命中了 |
| **DR7** | 调试控制——断点类型（执行/读/写/IO）、长度、使能 |

**GDB 的 `watch` 命令使用这些寄存器**——所以最多只能设 4 个硬件 watchpoint。更多就降级为软件单步（极慢）。

### 8.2 TSC —— 时间戳计数器

```bash
TSC（Time Stamp Counter）：
  → 从 CPU 上电开始以固定频率递增的 64 位计数器
  → 通过 rdtsc 指令读取（返回 EDX:EAX，64 位模式用 RAX:RDX）
  → 恒速 TSC（invariant TSC）：频率不随 CPU 频率变化而变化
Linux 中的使用：
  → ktime_get() 底层
  → perf 的时间基准
  → ftrace 的时间戳
  → 用户态通过 __rdtsc() 获取高精度时间
注意事项：
  → 不同 CPU 核的 TSC 可能不同步（老 CPU）
  → 新 CPU（Nehalem+）有 invariant TSC，跨核同步
  → 读取开销约 20-30 cycles（比 gettimeofday 快一个数量级）
```

## 九、寄存器使用场景速查表

**当你阅读某个主题的文档时，可能遇到以下寄存器：**

| 主题 | 涉及的关键寄存器 |
|:---|:---|
| **系统调用** | RAX(调用号), RDI/RSI/RDX/R10/R8/R9(参数), RCX(被征用存 RIP), R11(被征用存 RFLAGS), MSR_LSTAR/MSR_STAR/MSR_FMASK, CS/SS, CR3(KPTI) |
| **上下文切换** | RSP(SP 切换), RBX/RBP/R12-R15(callee-saved), CR3(换页表), CR0.TS(FPU 惰性), XMM/YMM/ZMM(FPU 状态) |
| **中断处理** | RFLAGS.IF(关中断), IDT(IDTR 指向), APIC ID/EOI/ICR, CS/SS(硬件压栈) |
| **段管理** | CS/DS/SS/ES/FS/GS(段选择子), GDTR, TR(TSS), MSR_FS_BASE/MSR_GS_BASE/MSR_KERNEL_GS_BASE |
| **页表/TLB** | CR3(页表根), CR4.PGE/PCIDE/SMEP/SMAP, EFER.NXE, CR2(缺页地址) |
| **FPU/SIMD** | XMM/YMM/ZMM, MXCSR, XCR0, CR0.TS/MP/EM, CR4.OSFXSR/OSXMMEXCPT |
| **中断亲和性** | IO-APIC RTE, Local APIC ID/ICR, MSI-X Message Address |
| **原子操作** | RFLAGS(lock 前缀影响), 通用寄存器(CAS 的 cmpxchg 操作数) |
| **缺页异常** | CR2(缺页地址), CR3(页表根) |
| **perf/火焰图** | RIP(采样点), TSC(时间戳) |
| **core dump 分析** | RIP(崩溃指令), RSP/RBP(栈回溯), 通用寄存器(现场) |

## 十、交叉引用

- **系统调用详细过程**：[syscall-details.md](/concepts/process/syscall-details.md) —— RCX/R11 被征用、段寄存器切换、KPTI CR3 切换的完整时序
- **上下文切换**：[context-switch.md](/concepts/process/context-switch.md) —— RSP 切换魔法、callee-saved 寄存器保存、FPU 惰性切换
- **段管理**：[segment-management.md](/concepts/process/segment-management.md) —— 段选择子结构、GDT 描述符逐位拆解、CPL/RPL/DPL 权限检查
- **中断处理**：[interrupts.md](/concepts/process/interrupts.md) —— IDT 映射、RFLAGS.IF 控制、硬件压栈
- **中断亲和性**：[irq-affinity.md](/concepts/process/irq-affinity.md) —— APIC 寄存器与中断投递目标
- **TLB**：[../cache/tlb.md](/concepts/cache/tlb.md) —— CR3 切换与 TLB 刷新、PCID 优化
- **IO 全链路**：[../io/read-write-process.md](/concepts/io/read-write-process.md) —— read/write 中模式切换的寄存器变化

## 十一、总结

```bash
x86-64 寄存器 = CPU 的"工作台" + "控制面板"
工作台（通用寄存器 × 16）：
  → 存数据、传参数、指向内存
  → syscall 时 rax=调用号，rdi/rsi/rdx/r10/r8/r9=参数
  → 上下文切换时 rsp 一换，执行流就变了
控制面板（系统寄存器）：
  → CR3 = 页表根指针（切进程必换，KPTI 下 syscall 也换）
  → CR0.TS = FPU 惰性切换的开关
  → EFER.SCE = syscall 指令的总开关
  → RFLAGS.IF = 中断的总开关
  → MSR_LSTAR = syscall 入口地址
  → GDTR/IDTR = 段表/中断表的指针
这些寄存器构成了 CPU 硬件和 Linux 内核之间的"接口协议"——
理解它们，才能真正看懂 syscall、上下文切换、中断处理的每一行代码。
```

