x86 如何支持 32bit mode
32bit 模式
可以,x86-64 宿主机可以通过 KVM 运行完整的 32 位 x86 内核,而且主要由 CPU 直接执行。 理解这里的支持,需要区分“运行 32 位保护模式”和“启动时运行实模式”。
- Host 和 guest 可以使用不同的 CPU 模式
硬件虚拟化允许分别保存 host 和 guest 的控制寄存器、段寄存器等状态。以 Intel VMX 为例:
64 位 host:VMX root,64 位模式 │ VM entry:加载 guest 状态 ▼ 32 位 guest:VMX non-root,32 位保护模式,内核运行在 guest ring 0 │ VM exit:加载 host 状态 ▼ 64 位 host:继续执行 KVM
因此,guest 可以拥有自己的 32 位内核、页表和中断处理程序;它与普通 32 位用户进程的兼容执行机制不同。
KVM 中有直接对应的实现:vmx_set_efer() (arch/x86/kvm/vmx/vmx.c:3235) 根据 guest 的 EFER.LMA 设置或清除 VM_ENTRY_IA32E_MODE。Host 是 64 位,并不要求 guest 也进入 long mode。
real mode 有特殊的支持
传统 BIOS 启动路径会经历:
16 位实模式 → 32 位保护模式 → 开启分页
早期 Intel VT-x 对 guest 有限制,要求实际运行的 guest 状态中 CR0.PE=1、CR0.PG=1, 所以实模式和未开启分页的保护模式不能直接按原样运行。
KVM 为此做了适配:
- 用 VM86 模式辅助运行实模式代码;你当前代码树中的 enter_rmode() (arch/x86/kvm/vmx/vmx.c:3188) 就会设置 RFLAGS.VM。
- 在 guest 认为分页关闭时,维护所需的映射;例如 EPT 开启、但没有 unrestricted guest 时,KVM 使用自己的恒等映射页表。
- 对无法直接进入硬件执行的部分状态,用指令模拟器推进执行。相关代码 (arch/x86/kvm/vmx/vmx.c:3428)
后来 Intel 增加了 Unrestricted Guest 硬件能力,允许直接虚拟化实模式和未分页模式。KVM 在硬件及 EPT 支持时默认启用它。内核参数文档 (https://cdn.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html)
所以,Unrestricted Guest 解决的是实模式/未分页模式的执行限制,32 位保护模式本身原本就能通过 VMX 运行。这套启动支持也会用于走传统启动路径的 64 位
efer
控制了什么东西,类似的东西有什么?
#define EFER_SCE (1<<_EFER_SCE)
#define EFER_LME (1<<_EFER_LME)
#define EFER_LMA (1<<_EFER_LMA)
#define EFER_NX (1<<_EFER_NX)
#define EFER_SVME (1<<_EFER_SVME)
#define EFER_LMSLE (1<<_EFER_LMSLE)
#define EFER_FFXSR (1<<_EFER_FFXSR)
#define EFER_TCE (1<<_EFER_TCE)
#define EFER_AUTOIBRS (1<<_EFER_AUTOIBRS)
EFER
EFER(Extended Feature Enable Register,扩展特性使能寄存器)是 x86 用来控制长模式、SYSCALL/SYSRET、页面禁止执行等功能的寄存器。 看 64 位启动流程、页表 和 KVM 时都会遇到它。
它属于 MSR,编号是 0xC0000080,通过 RDMSR/WRMSR 访问。Linux 在 arch/x86/include/asm/msr-index.h 中用 MSR_EFER 表示它。Linux 定义 (https://raw.githubusercontent.com/torvalds/linux/master/arch/x86/include/asm/msr-index.h)
最常用的是下面几个位:
位 名称 含义 ━━━━━ ━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0 SCE 启用 SYSCALL/SYSRET 指令 ───── ────── ──────────────────────────────────────────────── 8 LME Long Mode Enable:允许激活长模式 ───── ────── ──────────────────────────────────────────────── 10 LMA Long Mode Active:表示长模式已经激活 ───── ────── ──────────────────────────────────────────────── 11 NXE 启用页表中的禁止执行功能,Linux 宏名为 EFER_NX ───── ────── ──────────────────────────────────────────────── 12 SVME 启用 AMD SVM 虚拟化扩展,属于 AMD 的扩展位
这些位的定义可以在 AMD 手册的 “Extended Feature Enable Register” (https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf) 中找到。
最容易混淆的是 LME 和 LMA:一个是软件设置的使能位,一个是硬件反映的状态。
启动模式
传统的、使用四级页表的启动路径可以概括为:
处于保护模式,分页关闭:CR0.PE=1,CR0.PG=0 ↓ 设置 CR4.PAE=1,准备好页表并让 CR3 指向 PML4 ↓ 设置 EFER.LME=1 ↓ 设置 CR0.PG=1,开启分页 ↓ CPU 激活长模式,EFER.LMA 变成 1 ↓ 远跳转到 CS.L=1 的代码段,开始执行 64 位代码
因此,LME=1 并不意味着此刻就在执行 64 位代码。还要区分:
状态 当前执行模式 ━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ LMA=0 长模式未激活 ─────────────── ─────────────────────────────────────────── LMA=1,CS.L=0 长模式下的兼容模式,可以执行 16/32 位代码 ─────────────── ─────────────────────────────────────────── LMA=1,CS.L=1 64 位模式,代码段还需满足 CS.D=0
基本属性
这也解释了为什么 64 位 Linux 运行普通 32 位程序时,不需要关闭长模式。 另外,分页开启时不能直接改变 LME;LMA 也不是软件随意写成 1 就能进入长模式的开关。Intel SDM,模式与分页说明 (https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf)
结合你仓库里的 KVM 日志,常见的: EFER=0000000000000d01
按位拆开就是:
0x001 = SCE SYSCALL/SYSRET 已启用 0x100 = LME 已允许长模式 0x400 = LMA 长模式已激活 0x800 = NXE 页表 NX 功能已启用
这是常见的 64 位 Linux EFER 状态。不过,仅凭 0xd01 还不能判断当前指令是不是 64 位代码,要继续看 CS.L。
为什么我总是感觉这个寄存器很奇怪
EFER 是寄存器,访问它的指令是 RDMSR/WRMSR。 如果你想了解类似的“控制 CPU 工作方式”的机制,可以一起看下面这些。
寄存器 主要作用 访问指令 ━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CR0 基础运行控制,例如 PE 开启保护模式、PG 开启分页、WP 控制内核写保护 MOV ─────────────────── ──────────────────────────────────────────────────────────────────── ─────────────────────────────── CR2 保存触发缺页异常 #PF 的线性地址 MOV ─────────────────── ──────────────────────────────────────────────────────────────────── ─────────────────────────────── CR3 指定当前页表根地址;启用 PCID 后还包含地址空间标识 MOV ─────────────────── ──────────────────────────────────────────────────────────────────── ─────────────────────────────── CR4 扩展功能开关,例如 PAE、PGE、SMEP、SMAP、VMXE、OSXSAVE MOV ─────────────────── ──────────────────────────────────────────────────────────────────── ─────────────────────────────── EFER 长模式、NX、SYSCALL/SYSRET,以及 AMD SVM 等 RDMSR/WRMSR ─────────────────── ──────────────────────────────────────────────────────────────────── ─────────────────────────────── XCR0 指定操作系统启用并管理哪些扩展状态,例如 SSE、AVX 状态 XGETBV/XSETBV
因为他和 CR 寄存器是相同的
linux 的支持情况
Linux 支持的入口可以这样区分:
进入位置 进入时的 CPU 状态 谁负责开启分页
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
传统 16 位 setup 入口 实模式,分页关闭 Linux 后续启动代码
───────────────────────────── ───────────────────────── ───────────────────────────────────────
启动解压器 startup_32 32 位保护模式,分页关闭 Linux 启动解压器
───────────────────────────── ───────────────────────── ───────────────────────────────────────
启动解压器 startup_64 64 位模式,分页已开启 前面的 bootloader 或 Linux startup_32
───────────────────────────── ───────────────────────── ───────────────────────────────────────
解压后的内核本体 startup_64 64 位模式,分页已开启 前面的启动阶段
注意:64 位内核也可以从 startup_32 进入,入口名字不代表最终运行的是 32 位内核。
传统路径的代码现在仍然存在。
arch/x86/boot/pmjump.S:37 中,Linux 设置 CR0.PE,随后通过远跳转进入 32 位代码:
movl %cr0, %edx orb $X86_CR0_PE, %dl # Protected mode movl %edx, %cr0
这里开启的是保护模式,还没有开启分页。保护模式和分页是两个不同的开关。
随后进入 arch/x86/boot/compressed/head_64.S 中的 startup_32 (arch/x86/boot/compressed/head_64.S:82),它依次完成:
- 设置 CR4.PAE。
- 建立早期页表,将页表地址写入 CR3。
- 设置 EFER.LME,准备启用 long mode。
- 设置 CR0.PG,开启分页并激活 long mode。
- 通过 lret 切换到 64 位代码段,进入 startup_64。
开启分页的关键指令就在第 268 行 (arch/x86/boot/compressed/head_64.S:268):
/* Enter paged protected Mode, activating Long Mode */ movl $CR0_STATE, %eax movl %eax, %cr0
CR0_STATE 的定义 (arch/x86/include/uapi/asm/processor-flags.h:179)明确包含 X86_CR0_PG。
因此,经传统 setup 入口启动 x86-64 内核时,流程仍然是:
Linux 16 位 setup → 32 位保护模式,分页关闭 → 建页表,开启分页,切换到 64 位执行 → 解压内核 → 进入内核本体
不过,bootloader 可以直接使用 32 位入口,跳过 Linux 自己的实模式 setup;也可以使用 64 位入口,提前准备好分页。
“直接以分页开启的状态进入 Linux”也是正式支持的协议。
官方 x86 启动协议 (https://docs.kernel.org/arch/x86/boot.html#bit-boot-protocol)区分了两种要求:32 位入口要求保护模式、分页关闭;64 位入口则明确要求:
At entry, the CPU must be in 64-bit mode with paging enabled.
本地对应证据是 Documentation/arch/x86/boot.rst:1354 和 Documentation/arch/x86/boot.rst:1391。
普通 x64 UEFI 环境在执行 EFI 程序时,就已经处于 64 位模式且分页开启,所以通过 x64 EFI stub 启动 Linux,并不需要先执行 Linux 那段 16 位 BIOS setup。UEFI 规范 §2.3.4 (https://uefi.org/sites/default/files/resources/UEFI_Spec_2_3_E.pdf)
如果你说的“kernel”特指解压后的内核本体,那么答案就是:进入时分页已经开启。
最直接的证据在 arch/x86/kernel/head_64.S:38,它的 startup_64 开头写着:
/*
* At this point the CPU runs in 64bit mode CS.L = 1 CS.D = 0,
* and someone has loaded an identity mapped page table
* for us.
*/
这里和 boot/compressed/head_64.S 的 startup_64 是两个不同入口。内核本体接下来会调整页表并重新加载 CR3 (arch/x86/kernel/head_64.S:132),这是切换到自己的 页表,不能理解为这时才第一次开启分页。
另外,恒等映射(虚拟地址等于物理地址)仍然是开启了分页;只是页表翻译前后的地址数值相同。
更加混乱了
不是。你列的是一种常见的启动路径,遗漏了“32 位保护模式+分页开启”等组合。
需要分开看:执行模式与分页机制,它们有关联,但不是一个开关。
先看执行模式。下面讨论普通 x86 执行环境,暂不展开 SMM、VMX 等特殊环境;— 表示不参与该行的模式判断。
执行模式 CR0.PE CR0.PG EFER.LMA CS.L CS.D EFLAGS.VM ━━━━━━━━━━━━━━━━ ━━━━━━━━ ━━━━━━━━ ━━━━━━━━━━ ━━━━━━ ━━━━━━ ━━━━━━━━━━━ 实模式 0 0 0 — — — ──────────────── ──────── ──────── ────────── ────── ────── ─────────── 16 位保护模式 1 0 或 1 0 0 0 0 ──────────────── ──────── ──────── ────────── ────── ────── ─────────── 32 位保护模式 1 0 或 1 0 0 1 0 ──────────────── ──────── ──────── ────────── ────── ────── ─────────── 虚拟 8086 模式 1 0 或 1 0 — — 1 ──────────────── ──────── ──────── ────────── ────── ────── ─────────── 16 位兼容模式 1 1 1 0 0 — ──────────────── ──────── ──────── ────────── ────── ────── ─────────── 32 位兼容模式 1 1 1 0 1 — ──────────────── ──────── ──────── ────────── ────── ────── ─────────── 64 位模式 1 1 1 1 0 —
其中,长模式(long mode,Intel 称 IA-32e mode)包含“兼容模式”和“64 位模式”。所以长模式已经激活,也可以执行 32 位代码,由当前代码段决定。Intel SDM,卷 3A (https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf)
各控制字段的作用是:
字段 位号 含义 ━━━━━━━━━━━ ━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CR0.PE 0 开启保护机制;不负责选择 16/32/64 位 ─────────── ─────────────── ─────────────────────────────────────────────────────────── CR0.PG 31 开启分页 ─────────── ─────────────── ─────────────────────────────────────────────────────────── CR4.PAE 5 选择 PAE 相关分页机制;进入长模式必须为 1 ─────────── ─────────────── ─────────────────────────────────────────────────────────── EFER.LME 8 软件设置,允许激活长模式 ─────────── ─────────────── ─────────────────────────────────────────────────────────── EFER.LMA 10 只读状态位,表示长模式已经激活 ─────────── ─────────────── ─────────────────────────────────────────────────────────── CS.L 描述符 bit 53 长模式下,1 选择 64 位模式,0 选择兼容模式 ─────────── ─────────────── ─────────────────────────────────────────────────────────── CS.D 描述符 bit 54 非 64 位代码的默认操作数/地址大小:0 为 16 位,1 为 32 位 ─────────── ─────────────── ─────────────────────────────────────────────────────────── EFLAGS.VM 17 在传统保护模式下选择虚拟 8086 模式
这里的 CS.L、CS.D 是 CS 对应代码段描述符的属性,不是 CS 可见的 16 位选择子中的位。64 位代码要求 L=1、D=0。Intel SDM,代码段描述符 (https://cdrdv2-public.intel.com/868137/325462-089-sdm-vol-1-2abcd-3abcd-4.pdf)
再看分页机制:
CR0.PG CR4.PAE EFER.LME 分页机制 ━━━━━━━━ ━━━━━━━━━ ━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0 — — 分页关闭,长模式未激活 ──────── ───────── ────────── ──────────────────────────────── 1 0 0 传统 32 位分页,通常为两级页表 ──────── ───────── ────────── ──────────────────────────────── 1 1 0 PAE 分页,通常为三级页表 ──────── ───────── ────────── ──────────────────────────────── 1 1 1 IA-32e 分页,四级或五级页表
IA-32e 分页中,CR4.LA57[12] 选择四级(0)或五级(1,需 CPU 支持);CR3 提供页表根地址。大页可以让页表遍历提前结束。兼容模式也使用 IA-32e 页表。Intel SDM,卷 3,第 4 章 (https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-system-programming-manual-325384.pdf)
因此,你原来的启动路径可以细化成:
实模式 PE=0,PG=0 │ 设置 PE,加载 32 位代码段 ▼ 32 位保护模式,分页关闭 PE=1,PG=0,LMA=0 │ 准备页表、加载 CR3 │ 设置 PAE=1、LME=1,最后设置 PG=1 ▼ 长模式已激活,仍执行兼容模式代码 PE=1,PG=1,LMA=1,CS.L=0 │ 远跳转,加载 L=1、D=0 的代码段 ▼ 64 位模式 PE=1,PG=1,LMA=1,CS.L=1
实模式不能开启自身分页;64 位模式不能关闭分页;16/32 位传统保护模式则可以选择开或关。
实模式 保护模式 长模式 64 位模式 都是什么意思?
x86 CPU 的运行模式 │ ├── 实模式(Real Mode) │ ├── 保护模式(Protected Mode) │ ├── 16 位代码 │ └── 32 位代码 │ └── 长模式(Long Mode,Intel 称为 IA-32e Mode) ├── 兼容模式(Compatibility Mode):运行旧的 16/32 位代码 └── 64 位模式(64-bit Mode):运行 64 位代码
Intel 手册明确把兼容模式和 64 位模式列为 IA-32e 的两个子模式。Intel 架构手册,第 1 卷 (https://cdrdv2-public.intel.com/874241/253665-090-sdm-vol-1.pdf)
- 实模式:保留早期 8086 的运行方式
可以理解成一个很原始的执行环境:
• 通常运行 16 位代码。 • 常规地址计算是 段寄存器值 × 16 + 偏移。 • 没有现代操作系统依赖的内存权限隔离,也没有分页。
例如:
段值 = 0x1000,偏移 = 0x0020 地址 = 0x1000 × 16 + 0x0020 = 0x10020
传统 x86 CPU 复位后从实模式开始执行;后面的固件、引导程序再切换模式。
- 保护模式:加入权限和内存保护机制
“保护”主要指 CPU 能限制程序的访问权限,例如用户程序不能随便访问内核内存。
这里段寄存器的含义变了:它保存的是段选择子,需要通过描述符表找到段的基址、界限和权限,不再直接乘以 16。
保护模式支持特权级和内存保护;分页可以开启,也可以关闭。
保护模式最早就支持 16 位,后来才扩展到 32 位。 所以:
保护模式 ≠ 32 位模式
不过日常讨论 32 位 Linux、32 位 Windows 时,说的通常是运行 32 位代码的保护模式。
- 长模式:x86-64 引入的整体运行环境
长模式既要支持新的 64 位程序,也要兼容旧程序,因此分成两个子模式:
长模式的子模式 用途 ━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 64 位模式 执行 64 位代码,例如 64 位内核和应用 ──────────────── ───────────────────────────────────────────────────────── 兼容模式 执行旧的 16/32 位代码,实际能否运行还取决于操作系统支持
长模式要求开启分页,仍然有用户态、内核态和内存保护机制。
- 64 位模式:长模式中的一个子模式
进入这个子模式,代码才使用 x86-64 的执行规则,例如:
• 可以使用 RAX 等 64 位通用寄存器,以及 R8~R15。 • 支持 64 位地址计算,但不代表实际实现了全部 2⁶⁴ 地址空间。 • 分段大幅简化,普通代码和数据主要靠分页管理内存,FS/GS 仍有特殊用途。
注意,“64 位”也不代表所有指令都操作 64 位数据,例如 mov eax, 1 仍然操作 32 位寄存器。Intel 系统编程手册,第 3A 卷 (https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf)
用 Linux 的例子串起来最直观:
64 位 Linux 内核执行 → 长模式下的「64 位模式」
64 位 Linux 执行 64 位应用 → 长模式下的「64 位模式」
64 位 Linux 执行受支持的 32 位应用 → 长模式下的「兼容模式」 → 进入内核时再切到「64 位模式」
因此,运行 32 位应用不需要退出长模式;用户态和内核态也不是这几种模式的划分依据,二者都可以运行在 64 位模式中。
Unrestricted Guest(UG)的作用
• Unrestricted Guest(UG)主要解决两类状态的硬件直接运行问题:实模式,以及分页关闭的保护模式(16 位、32 位都包括)。
早期 Intel VT-x 要求 VMX non-root 中实际运行的 guest 必须满足:
CR0.PE = 1 CR0.PG = 1
开启 UG 后,允许这两个位为 0,所以你前面列的启动路径中,前两个阶段也能直接交给 CPU 执行。Intel SDM:Unrestricted Guests (https://cdrdv2-public.intel.com/819710/252046-sdm-change-document.pdf)
Guest 状态 PE PG 无 UG 时能否按真实状态直接运行 有 UG ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━ ━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━ 实模式 0 0 不能,需要软件补偿 可以 ──────────────────────────── ───── ───── ──────────────────────────────── ────────── 16/32 位保护模式,分页关闭 1 0 不能,需要软件补偿 可以 ──────────────────────────── ───── ───── ──────────────────────────────── ────────── 16/32 位保护模式,分页开启 1 1 可以 可以 ──────────────────────────── ───── ───── ──────────────────────────────── ────────── 兼容模式、64 位模式 1 1 可以 可以 ──────────────────────────── ───── ───── ──────────────────────────────── ────────── “实模式+分页开启” 0 1 非法组合 仍然非法
没有 UG,也并非所有代码都必须逐条解释执行,VMM 可以采用变通方式:
• 实模式:用虚拟 8086 模式执行部分代码,再模拟不兼容的指令、段状态等;也可以采用指令模拟器。 • 未分页保护模式:guest 看到 PG=0,但硬件实际使用 PG=1,由 VMM 配置额外页表模拟“不分页”的效果。使用 EPT 时,可以在第一层放恒等映射页表。
UG 还放宽了部分段寄存器的 VM-entry 检查,以支持模式切换期间的段状态。Intel 对这一扩展的说明 (https://www.intel.com/content/dam/www/public/us/en/documents/research/2010-vol14-iss-3-intel-technology-journal.pdf)
它为什么要求 EPT?因为 guest 关闭分页以后,仍然需要完成 GPA → HPA 的转换与隔离。
Guest 开启分页: 逻辑地址 ─分段→ 线性地址 ─Guest 页表→ GPA ─EPT→ HPA
Guest 关闭分页,使用 UG: 逻辑地址 ─分段→ 线性地址(作为 GPA)──────EPT→ HPA
所以,CR0.PG=0 只关闭 guest 自己的分页,不会关闭 EPT。UG 启用时必须启用 EPT;I/O、EPT violation 等仍可触发 VM exit。Intel SDM:UG 与 EPT (https://cdrdv2-public.intel.com/868136/252046-081-sdm-change-document.pdf)
对应控制字段在 VMCS 中,由 VMM 配置:
VMCS 控制字段 位 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━ Primary processor-based controls:Activate secondary controls 31 ─────────────────────────────────────────────────────────────── ───── Secondary processor-based controls:Enable EPT 1 ─────────────────────────────────────────────────────────────── ───── Secondary processor-based controls:Unrestricted guest 7
你当前源码的 vmx_set_cr0() (arch/x86/kvm/vmx/vmx.c:3425) 就体现了这一区别:无 UG 时需要调整实际 CR0;其后的注释明确解释了 EPT 开启但 UG 关闭时,KVM 为 什么还需要恒等映射页表和拦截 CR3 访问。
32bit 本身需要特殊处理吗?
需要区分处理。上一条我说“不需要特殊处理”范围太宽了,准确说是:不需要为了执行 32 位代码而模拟整个模式,但 KVM 仍要正确配置模式、保存恢复寄存器。
你说的 VMCS 寄存器宽度,需要区分两个概念:字段能存多少位,以及 CPU 当前按多少位使用这个值。
- VMCS 字段不会随着 guest 从 64 位切到 32 位而缩短。
VMCS 的字段有固定的宽度类别:
字段类别 例子 在支持 Intel 64 的 CPU 上 ━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 位 GUEST_CS_SELECTOR 始终 16 位 ─────────────── ─────────────────────────────────── ─────────────────────────────────────── 32 位 GUEST_CS_LIMIT、GUEST_CS_AR_BYTES 始终 32 位 ─────────────── ─────────────────────────────────── ─────────────────────────────────────── 64 位 GUEST_IA32_EFER 始终 64 位 ─────────────── ─────────────────────────────────── ─────────────────────────────────────── Natural width GUEST_RIP、GUEST_RSP、GUEST_CR3 始终 64 位,即使 guest 运行 32 位代码
这里的 natural width 取决于处理器架构能力,不是 guest 当前的位宽。Intel SDM:VMCS 字段编码 (https://cdrdv2-public.intel.com/671294/252046-sdm-change-document.pdf)
例如,32 位 guest 的 EIP = 0x8048123,可以放在同一个字段中:
VMCS.GUEST_RIP = 0x00000000_08048123 └── EIP ──┘
而且硬件会检查合法性:进入传统 32 位模式或兼容模式时,GUEST_RIP[63:32] 必须为零,不能随便填。Intel SDM:Guest RIP 检查 (https://cdrdv2-public.intel.com/825750/326019-sdm-vol-3c.pdf)
- CPU 根据模式控制字段,决定怎么解释和执行。
KVM 要让这些状态相互一致:
目标执行模式 VM-entry 的 IA-32e mode guest Guest CS.L Guest CS.D ━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━ ━━━━━━━━━━━━ 传统 32 位保护模式 0 0 1 ────────────────────────── ─────────────────────────────── ──────────── ──────────── 长模式中的 32 位兼容模式 1 0 1 ────────────────────────── ─────────────────────────────── ──────────── ──────────── 64 位模式 1 1 0
还要配好相应的 CR0、CR4、EFER。KVM 提供正确状态,CPU 在 VM entry 时检查、加载,并按对应模式执行。
host 的模式独立配置。例如,64 位 KVM 运行传统 32 位 guest:
64 位 KVM │ VM entry:按 guest 配置切换 ▼ 32 位 guest │ VM exit:按 host 配置恢复 ▼ 64 位 KVM
VM-exit 的 Host address-space size 控制返回 host 的模式,不要求 host 和 guest 位宽相同。Intel SDM:地址空间大小控制 (https://cdrdv2-public.intel.com/774475/252046-sdm-change-document.pdf)
- 如果你指的是 EAX/RAX 等通用寄存器,它们多数根本不在 VMCS 中。
RAX、RBX、RCX…… 由 KVM 的进出 guest 汇编代码负责保存、恢复;RIP、RSP、RFLAGS 则有对应 VMCS 字段。可以看当前源码的 arch/x86/kvm/vmx/vmenter.S:63。
因此,你指出的差异确实需要被处理,但主要表现为模式配置、状态合法性检查和寄存器保存恢复,而不是把 VMCS 换成一套“32 位布局”。当前 KVM 写入 guest 的 RIP/ RSP 时,也直接使用统一的 vmcs_writel() 路径 (arch/x86/kvm/vmx/vmx.c:7546)。
本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。