Skip to the content.

x86 如何支持 32bit mode

32bit 模式

可以,x86-64 宿主机可以通过 KVM 运行完整的 32 位 x86 内核,而且主要由 CPU 直接执行。 理解这里的支持,需要区分“运行 32 位保护模式”和“启动时运行实模式”。

  1. 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 为此做了适配:

后来 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),它依次完成:

  1. 设置 CR4.PAE。
  2. 建立早期页表,将页表地址写入 CR3。
  3. 设置 EFER.LME,准备启用 long mode。
  4. 设置 CR0.PG,开启分页并激活 long mode。
  5. 通过 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)

  1. 实模式:保留早期 8086 的运行方式

可以理解成一个很原始的执行环境:

• 通常运行 16 位代码。 • 常规地址计算是 段寄存器值 × 16 + 偏移。 • 没有现代操作系统依赖的内存权限隔离,也没有分页。

例如:

段值 = 0x1000,偏移 = 0x0020 地址 = 0x1000 × 16 + 0x0020 = 0x10020

传统 x86 CPU 复位后从实模式开始执行;后面的固件、引导程序再切换模式。

  1. 保护模式:加入权限和内存保护机制

“保护”主要指 CPU 能限制程序的访问权限,例如用户程序不能随便访问内核内存。

这里段寄存器的含义变了:它保存的是段选择子,需要通过描述符表找到段的基址、界限和权限,不再直接乘以 16。

保护模式支持特权级和内存保护;分页可以开启,也可以关闭。

保护模式最早就支持 16 位,后来才扩展到 32 位。 所以:

保护模式 ≠ 32 位模式

不过日常讨论 32 位 Linux、32 位 Windows 时,说的通常是运行 32 位代码的保护模式。

  1. 长模式:x86-64 引入的整体运行环境

长模式既要支持新的 64 位程序,也要兼容旧程序,因此分成两个子模式:

长模式的子模式 用途 ━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 64 位模式 执行 64 位代码,例如 64 位内核和应用 ──────────────── ───────────────────────────────────────────────────────── 兼容模式 执行旧的 16/32 位代码,实际能否运行还取决于操作系统支持

长模式要求开启分页,仍然有用户态、内核态和内存保护机制。

  1. 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 当前按多少位使用这个值。

  1. 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)

  1. 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)

  1. 如果你指的是 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 将按侵权追究法律责任,其它情况随意。