
一、引言在嵌入式系统、操作系统和虚拟化系统中硬件与软件并不是互相独立的两个部分而是互相协调互相配合的“同伴”。一个资深的软件工程师需要深知硬件如何与软件交互这样才能最大化的利用硬件资源。如果只会让AI生成代码那么不久的将来确实会被AI替代。在实际开发一块板子时硬件与软件负责不同的部分。硬件负责提供处理器执行能力内存和地址转换总线与 DMA中断控制权限检查虚拟化扩展错误检测与状态记录。软件则负责发现硬件能力配置寄存器建立页表和 DMA 映射分配系统资源管理设备状态处理异常和错误为历史平台提供兼容方案。实际情况下软件使用硬件的都需要依靠硬件提供的寄存器而是用寄存器的方法非常简单*(unsigned long *) 0x20100000这里实际是直接调用硬件提供的寄存器地址这样可以修改寄存器从而操控硬件这里先把数字转换为指针这样就是使用地址然后再修改地址的内容。实际操作系统中常常需要软件使用寄存器完成相应的功能例如x86 处理器不会自动进入操作系统期望的 64 位分页环境启动软件需要配置控制寄存器、页表和 GDTA20 地址线问题源于 8086 时代的地址回绕现代启动代码需要兼容不同平台的 A20 控制方式IOMMU 可以在硬件中完成 DMA 地址转换和权限检查但 DMA 页表和设备隔离域仍然需要软件建立ARMv8-A 提供 EL2、Stage-2 地址转换、虚拟中断和虚拟定时器Hypervisor 仍然需要负责资源管理和异常处理。这些案例背后有一个共同原则硬件实现机制软件制定策略硬件负责高频执行软件负责配置、管理和异常处理。二、什么是软硬件协同设计2.1 硬件与软件不是简单的上下游关系在很多初学者的理解中硬件先设计完成软件再按照手册编写驱动。这种理解只适合非常简单的场景。在实际芯片项目中硬件和软件通常需要共同定义以下内容硬件寄存器布局 DMA 描述符格式 中断状态和清除方式 复位和初始化顺序 地址转换规则 权限检查方式 错误上报机制 版本兼容策略比如一个网络控制器不仅要定义“发送使能位在哪一位”还要定义发送队列如何组织描述符由谁分配哪个字段表示描述符有效设备读取描述符前是否需要软件执行内存屏障设备完成发送后如何通知 CPU中断状态如何清除设备发生 DMA 错误后如何恢复。这些问题如果没有在硬件和软件之间定义清楚即使硬件逻辑和软件代码各自没有语法错误系统仍然无法正常工作。分析一个底层硬件功能时可以按照四个问题展开。第一个问题硬件当前处于什么状态例如CPU 当前处于实地址模式、保护模式还是长模式A20 地址线当前是否有效IOMMU 是否已经启用 DMA 重映射ARMv8-A 当前是否运行在 EL2Guest 是否已经进入运行状态如果软件不了解初始状态后续配置就可能建立在错误假设上。第二个问题软件需要遵守什么契约软件必须了解哪些寄存器可以访问哪些位可以修改哪些字段是只读或保留位地址需要怎样对齐表项使用什么格式哪些功能必须按照顺序开启哪些操作需要内存屏障或 TLB 失效错误发生后由谁负责处理。寄存器手册中的“位定义”只是契约的一部分完整契约通常还包括访问顺序、状态转换和异常处理方式。第三个问题哪个动作真正改变了硬件状态例如向CR0写入分页使能位设置IA32_EFER.LME通过平台控制接口打开 A20启用 IOMMU 的 DMA 重映射设置HCR_EL2.VM通过异常返回指令进入 Guest。硬件不会自动理解软件的最终目标。软件必须通过规定的寄存器、表项或指令明确告诉硬件应当进入什么状态。第四个问题如何验证硬件确实完成了切换不能因为程序“没有立即崩溃”就认为配置一定成功。常见的验证方式包括回读寄存器访问已知地址检查异常入口观察 DMA fault读取ESR_EL2、FAR_EL2等异常寄存器对设备执行受控测试使用调试器或模拟器观察处理器状态。底层初始化可以抽象为读取初始状态 │ ▼ 检查硬件能力 │ ▼ 准备表、栈、地址和权限 │ ▼ 执行寄存器或控制器切换 │ ▼ 读取状态并验证 │ ▼ 进入正常运行因此底层初始化并不是简单的“给一个寄存器写值”而是一个完整的状态转换协议。三、寄存器软硬件之间的交互接口严格来说软件不能修改已经流片完成的寄存器电路。但是在芯片、固件、驱动和操作系统共同开发时软件工程师通常会参与寄存器接口设计包括寄存器地址字段划分复位值读写属性状态转换错误报告初始化顺序版本兼容策略。例如一个 UART 控制寄存器可以设计为31 4 3 0 ---------------------------------- | Reserved | DMA_EN | | | RX_EN | | | TX_EN | | | UART_EN | ----------------------------------软件不仅需要知道每个位的含义还需要知道是否必须先打开时钟是否必须先关闭发送和接收FIFO 是否需要清空中断状态是否需要先清除修改波特率时是否必须停止设备写入配置后是否需要等待硬件确认。所以寄存器接口实际上包含两部分寄存器布局 访问时序只有布局没有时序接口仍然是不完整的。四、x86 启动软件配置处理器执行模式接下来使用x86启动来说明硬件与软件的交互过程。4.1 x86 没有统一的“启动寄存器契约”讨论 x86 启动时必须区分几个不同层次处理器复位状态BIOS 传统启动UEFI 启动Multiboot2 等引导协议Linux x86 Boot ProtocolBootloader 与内核之间的自定义约定。这些协议规定的入口地址、寄存器内容、段寄存器状态和内存布局并不相同。 处理器复位状态由硬件架构规定而操作系统实际接收到的启动状态通常还受到 BIOS、UEFI、Bootloader 和具体启动协议的影响。硬件复位状态、固件交接状态和操作系统启动代码设置完成后的状态是三个不同的时刻不能混为一谈。4.2 典型的启动协议Linux x86 32 位启动协议Linux 32 位保护模式启动协议有自己的入口约定例如ESI 指向 struct boot_params EBP、EDI、EBX 具有协议规定的状态这些约定只适用于 Linux Boot Protocol不能推广到 BIOS、UEFI 或其他 Bootloader。UEFI x64 启动UEFI x64 镜像入口使用 UEFI 规定的调用约定常见参数为RCX ImageHandle RDX EFI_SYSTEM_TABLE*这不是 Linux x86-64 程序的通用入口约定。在调用ExitBootServices()之后Boot Services 已经结束软件不能继续依赖 Boot Services 函数指针。x86 中几个重要的系统寄存器寄存器主要作用CR0保护模式、分页、写保护等控制CR2保存页错误发生时的线性地址CR3指向当前页表根CR4PAE、SMEP、SMAP 等扩展功能控制IA32_EFER长模式、NX 等扩展功能控制GDTR指向全局描述符表IDTR指向中断描述符表这些寄存器不是普通内存变量需要使用处理器规定的指令访问。例如设置页表根mov cr3, eax真实代码必须根据当前执行模式和寄存器宽度选择正确的汇编形式。读取和修改IA32_EFER通常使用 MSR 指令mov ecx, 0xC0000080 ; IA32_EFER rdmsr ; 修改 EDX:EAX 中需要设置的字段 wrmsr真实代码还需要处理寄存器宽度保留位地址对齐物理地址宽度CPU 特性检测序列化和屏障要求。需要注意CR3中保存的是符合架构要求的页表根物理地址。在支持 PCID 等功能时低位还可能包含额外控制信息因此不能无条件地把整个CR3当成一个简单地址。4.3 进入 x86-64 长模式的依赖关系进入长模式不是设置一个位就结束了而是多个条件共同满足的结果。如果当前已经处于保护模式典型准备过程可以表示为检查 CPU 是否支持长模式 │ ▼ 准备 GDT 和 64 位代码段 │ ▼ 准备页表映射代码、栈和必要数据 │ ▼ 确保 CR0.PE 1 │ ▼ 设置 CR4.PAE 1 │ ▼ 把 PML4 或 PML5 根的物理地址写入 CR3 │ ▼ 设置 IA32_EFER.LME 1 │ ▼ 设置 CR0.PG 1 │ ▼ 远跳转到 64 位代码段 │ ▼ 进入 IA-32e 的 64-bit submode下面是一个概念化伪汇编仅用于说明寄存器依赖关系; [概念伪汇编] ; 前提 ; 1. 已通过 CPUID 检查长模式支持 ; 2. GDT 已准备好 ; 3. 页表已映射当前代码、栈和必要数据 ; 4. page_table_root 是满足对齐要求的物理地址 ; 5. 当前环境已经处于或即将进入保护模式 ; 如果当前仍处于实地址模式 ; 需要先按照当前启动协议建立保护模式环境。 ; 具体过程此处省略。 ; 开启 PAE mov eax, cr4 or eax, CR4_PAE mov cr4, eax ; 设置页表根 mov eax, page_table_root mov cr3, eax ; 设置 IA32_EFER.LME mov ecx, IA32_EFER rdmsr or eax, EFER_LME wrmsr ; 开启分页 mov eax, cr0 or eax, CR0_PG mov cr0, eax ; 通过远跳转装载 64 位代码段 jmp CODE64_SELECTOR:long_mode_entry这段代码省略了保护模式建立、GDT 装载、页表构造和异常路径不能直接作为启动代码使用。如果当前仍处于实地址模式则需要先建立保护模式环境。典型过程包括准备 GDT │ ▼ 加载 GDTR │ ▼ 设置 CR0.PE │ ▼ 远跳转到保护模式代码段五、A20 地址线历史兼容问题中的软件兜底5.1 8086 的地址回绕早期 Intel 8086/8088 处理器只有 20 根地址线即 A0 到 A19可以访问2^20 1 MiB的地址空间。但是8086 使用段地址和偏移量计算地址计算结果可能超过 20 位。例如段地址0xFFFF 偏移量0x0010计算结果为0xFFFF × 16 0x0010 0x100000由于处理器只有 A0 到 A19超出的高位无法输出于是发生回绕0x100000 → 0x00000因此早期系统中以下两个地址可能指向同一个位置0x000000 0x100000这是一种由硬件地址线宽度导致的行为。5.2 80286 带来的兼容性问题80286 增加了第 21 个物理地址位也就是 A20可以访问 1 MiB 以上的地址。从新硬件的角度看这是地址能力的提升但部分旧软件依赖 8086 的地址回绕行为。为了兼容旧软件IBM PC/AT 等早期平台通过外部逻辑增加了 A20 GateA20 关闭屏蔽物理地址 bit 20 A20 打开允许物理地址 bit 20 正常参与地址生成这里需要区分三个概念A20 line物理地址的 bit 20A20 gate用于屏蔽或放行 A20 的兼容逻辑A20M#后来部分处理器和平台使用的低有效控制信号。它们不是同一个 CPU 控制寄存器。还需要注意A20 关闭并不意味着“所有超过 1 MiB 的地址都简单地对 1 MiB 取模”。更准确地说是地址的 bit 20 被强制为 0。因此下面两类地址可能发生别名地址 bit 20 不同 其他地址位相同典型例子是0x000000 与 0x100000但不能把所有高地址都简单概括为对 1 MiB 取模。因此在早期开发中软件需要实现队相关硬件的特殊化处理一个优秀的操作系统应该能够适配大部分主流硬件完成这些是软件工程师的责任5.3 常见的 A20 启用路径历史上常见的路径包括BIOS 接口例如INT 15h, AX2401h8042 键盘控制器系统控制端口0x92的 Fast A20芯片组、固件或虚拟机平台提供的兼容接口。通过 8042 控制器时软件通常需要按照类似步骤操作等待控制器输入缓冲区空闲 │ ▼ 向 0x64 发送命令 │ ▼ 从 0x60 读取或写入数据 │ ▼ 修改输出端口中的 A20 控制位 │ ▼ 等待硬件完成传统 8042 路径通常会涉及向 0x64 发送输出端口命令 从 0x60 读取输出端口值 保留其他位 设置输出端口的 A20 位 将新值写回 0x60具体命令、状态位和时序必须按照目标平台实现处理并且必须带有超时机制。使用端口0x92时也不能简单写入一个固定值因为端口的其他位可能具有不同功能。某些平台中 bit 0 可能与快速复位有关错误修改可能导致系统复位。因此A20 处理流程通常是查询当前状态 │ ├── 已经开启直接继续 │ ├── 尝试平台推荐路径 │ ├── 失败后尝试兼容路径 │ └── 通过内存测试确认不同平台不一定支持所有方式也不存在对所有机器都适用的唯一顺序。现代 BIOS、UEFI 和虚拟机固件通常已经打开 A20但启动代码仍然应根据具体启动环境决定是否需要检测而不应盲目操作 8042 或端口0x92。六、IOMMU6.1 IOMMU 的基本工作方式IOMMU即 Input-Output Memory Management Unit输入输出内存管理单元通常位于设备和内存之间。没有 IOMMU 时设备 DMA 地址 │ ▼ 直接访问内存有 IOMMU 时设备 DMA 地址 / IOVA │ ▼ 根据设备身份选择 domain │ ▼ IOMMU 查表 │ ├── 地址和权限合法 │ │ │ ▼ │ 主机物理地址 │ └── 非法访问 │ ▼ 阻断并报告 faultIOMMU 处理的不是 CPU 虚拟地址而是设备发起的 DMA 地址通常称为IOVADMA addressBus addressDevice virtual address。IOMMU 通常根据以下信息进行处理设备身份PCIe Requester ID 或其他平台设备标识所属 translation domainDMA 页表访问权限地址范围。常见实现包括Intel VT-dAMD-ViArm SMMU。CPU MMU 主要处理CPU 虚拟地址 → 物理地址IOMMU 主要处理设备 IOVA / DMA 地址 → 主机物理地址两者设计思想类似但页表格式、失效机制、错误语义和访问者身份并不完全相同。6.2 不同 IOMMU 实现的差异不同厂商的 IOMMU 具有相似的目标但具体硬件结构并不相同。Intel VT-d常见概念包括PCIe Requester ID │ ▼ Root Table │ ▼ Context Table │ ▼ Translation Domain │ ▼ DMA 页表AMD-ViAMD IOMMU 使用自己的设备标识、设备表项、域和页表结构不能直接套用 Intel VT-d 的 Root Table 或 Context Table 格式。Arm SMMUArm SMMU 常见的概念包括StreamID │ ▼ Stream Table Entry │ ▼ 上下文描述符 │ ▼ Stage-1 / Stage-2 转换因此不能把某一种 IOMMU 的寄存器名称和表项格式直接套用到另一种实现上。本文只讨论基本的 DMA remapping 模型设备身份 → 隔离域 → IOVA → 物理地址对于 ATS、PASID、PRI、SVA、SR-IOV 等扩展需要结合具体规范单独分析。6.3 IOMMU 中的软件职责IOMMU 不是打开一个总开关就结束了。软件通常需要完成探测平台是否支持 IOMMU读取 ACPI、设备树和硬件拓扑识别设备身份创建设备对应的 translation domain建立 DMA 页表设置读写权限将设备绑定到对应 domain刷新 IOTLB 或相关翻译缓存处理 DMA fault在设备停止后撤销映射。以 PCIe 设备为例可以抽象为Requester ID │ ▼ 设备上下文 │ ▼ Translation Domain │ ▼ DMA 页表 │ ▼ IOVA → 物理地址核心思想是软件建立映射和权限硬件在每一次 DMA 请求到来时执行检查和转换。6.4 一个 DMA 映射示例假设驱动希望让设备访问一块缓冲区设备可见地址0x40000000 物理地址 0x12300000 长度 64 KiB 权限 设备可读、设备可写软件建立的映射可以表示为IOVA 0x40000000 │ ▼ PA 0x12300000 │ └── 允许读写长度 64 KiB概念性接口如下/* * [概念伪代码] * * 这里假设 buffer 对应一段可用于 DMA 的物理内存。 * 真实系统可能面对非连续页、IOMMU 页粒度、缓存一致性、 * DMA mask、并发控制和设备描述符格式等问题。 */dma_addr_tmap_dma_buffer(device_t*dev,void*buffer,size_tsize,dma_direction_tdirection){phys_addr_tpaget_physical_address(buffer);iommu_domain_t*domainget_device_domain(dev);iommu_map(domain,IO_VA_BASE,pa,size,direction_to_permission(direction));iommu_flush_iotlb(domain,IO_VA_BASE,size);returnIO_VA_BASE;}设备使用 DMA 缓冲区的生命周期通常是停止设备 DMA │ ▼ 分配内存 │ ▼ 建立 IOMMU 映射 │ ▼ 执行 CPU/设备缓存同步 │ ▼ 把 IOVA 写入设备描述符 │ ▼ 允许 bus mastering │ ▼ 启动设备 DMA │ ▼ 等待设备完成 │ ▼ 同步数据回 CPU │ ▼ 停止设备并解除映射关键原则是必须先建立正确映射并完成相关失效与同步再允许设备发起 DMA。撤销映射时则相反停止提交新的 DMA │ ▼ 等待在途 DMA 完成 │ ▼ 禁止 bus mastering │ ▼ 撤销映射 │ ▼ 执行必要的 IOTLB/设备侧缓存失效七、ARMv8-A 硬件虚拟化从软件模拟到硬件辅助这里需要会一点虚拟化知识可以看我之前的帖子学习虚拟化知识。7.1 ARMv8-A 虚拟化简介ARMv7-A 时代已经出现了 Virtualization Extensions并提供了 Hyp mode 等虚拟化能力。Armv8-A 则将虚拟化能力带入 AArch64 执行状态通常使用 EL2 表示 Hypervisor 所处的异常级别。因此更准确的说法是ARMv8-A 进一步完善并扩展了 ARM 平台的硬件虚拟化能力而不是第一次出现硬件虚拟化。不同架构版本和处理器实现支持的功能可能不同例如Armv8.1-A 引入了 VHE 等能力后续架构版本继续增强了嵌套虚拟化和安全虚拟化能力具体芯片是否实现某项扩展需要读取 ID 寄存器或查看产品手册。7.2 为什么需要硬件假设 Guest OS 认为自己运行在最高权限级别它可能执行修改页表访问系统控制寄存器配置中断控制器读取定时器执行异常返回修改内存属性访问设备寄存器。如果完全依靠软件模拟Hypervisor 可能需要捕获 Guest 的特权操作解码相关指令模拟寄存器效果模拟页表变化模拟中断和定时器模拟设备访问。硬件辅助虚拟化的目标是让 Guest 的普通指令直接在处理器上运行只在真正需要 Hypervisor 处理的操作上产生陷入。这与“完全不需要软件”是两回事。硬件主要负责权限检查地址转换异常陷入部分中断状态虚拟定时器虚拟 CPU 状态切换。软件仍然负责创建虚拟机分配内存设置页表管理设备处理陷入调度 vCPU维护 Guest 生命周期。7.3 ARMv8-A 的异常级别在 ARMv8-A 中可以用异常级别描述不同的软件权限EL0普通应用程序 EL1操作系统内核 EL2Hypervisor EL3安全监控程序或固件环境可以抽象为-------------------------- | EL0 用户应用 | -------------------------- | EL1 Guest OS / Host OS | -------------------------- | EL2 Hypervisor | -------------------------- | EL3 Secure Monitor | --------------------------需要注意EL2 是硬件提供的异常级别不是普通函数调用层EL2 不能简单等同于 x86 的某个 Ring当前异常级不一定总是 EL2实际启动时可能从 EL3、EL2 或其他环境交接是否实现 EL2、EL3 以及具体虚拟化扩展要根据处理器实现和启动环境确认Secure EL2 等能力还取决于具体架构扩展。当 Guest 执行某些受限制的操作时硬件可以根据HCR_EL2等控制设置将控制权转移到 EL2Guest 执行敏感操作 │ ▼ 硬件检查虚拟化控制 │ ├── 允许直接执行 │ └── 陷入 EL2 │ ▼ Hypervisor 处理但 EL2 并不会自动完成所有虚拟化工作。Hypervisor 仍然需要配置陷入策略、保存上下文并实现设备模型。7.4 如何地址转换在常见的 Guest 虚拟化模型中Guest OS 负责第一阶段地址转换Guest 虚拟地址 GVA │ ▼ Guest Stage-1 页表 │ ▼ 中间物理地址 IPAHypervisor 负责第二阶段地址转换中间物理地址 IPA │ ▼ Hypervisor Stage-2 页表 │ ▼ 真实物理地址 PA完整路径为GVA ──Stage-1── IPA ──Stage-2── PA其中GVA 是 Guest 内核或应用使用的虚拟地址IPA 是 Guest 认为的物理地址PA 是真实内存系统最终使用的物理地址。例如Guest 认为自己访问IPA0x00000000Hypervisor 可以将它映射到PA0x80000000Guest 不需要知道真实内存位于哪个物理地址。Stage-2 相关寄存器通常包括VTTBR_EL2Stage-2 页表根和 VMID 相关信息VTCR_EL2Stage-2 粒度、输入地址大小和其他转换参数HCR_EL2.VMStage-2 地址转换相关控制HCR_EL2.RW较低异常级执行 AArch64 还是 AArch32。HCR_EL2.VM和HCR_EL2.RW不是同一个功能。如果 Stage-2 没有开启就不能继续声称存在完整的IPA → PA二次转换。Stage-1 和 Stage-2 的权限共同决定最终访问结果最终访问权限 Stage-1 允许的权限 与 Stage-2 允许的权限的交集Stage-2 可以进一步拒绝 Guest 访问但不能把 Stage-1 已经拒绝的访问重新放行。VMID主要用于翻译缓存上下文标识不单独构成完整的安全边界。修改页表、VMID 或转换控制后还需要按照架构要求完成屏障和 TLB 失效。如果 Guest 访问了没有建立 Stage-2 映射的 IPAGuest 访问非法 IPA │ ▼ Stage-2 转换失败 │ ▼ 产生异常并陷入 EL2 │ ▼ Hypervisor 读取故障信息 │ ▼ 决定映射、注入异常或终止 GuestHypervisor 可以根据策略分配新的物理页建立内存映射注入 Guest 页错误模拟设备记录安全事件终止虚拟机。Stage-2 是硬件执行的地址转换机制但页表内容和权限策略由软件建立。修改页表、VMID 或转换控制后还需要按照架构要求完成数据同步屏障指令同步屏障TLB 失效必要的缓存维护。下面是armv8虚拟化的重要寄存器寄存器作用VBAR_EL2EL2 异常向量基地址HCR_EL2虚拟化控制和异常路由VTCR_EL2Stage-2 转换控制VTTBR_EL2当前虚拟机 Stage-2 页表根和 VMIDSCTLR_EL2EL2 自身 MMU 和缓存控制ESR_EL2异常综合信息FAR_EL2故障相关虚拟地址HPFAR_EL2某些场景下的故障 IPA 信息ELR_EL2异常返回地址SPSR_EL2异常返回状态不同 ARMv8-A 版本和处理器实现支持的字段可能不同。不能把某组固定十六进制值当作所有 ARM 处理器的通用配置。还要特别注意VTCR_EL2是 Stage-2 转换控制参数不是页表根地址VTTBR_EL2才包含 Stage-2 页表根相关信息SCTLR_EL2.M控制 EL2 自身翻译环境与 Guest 的 Stage-2 不是同一个开关CurrentEL是状态寄存器不能通过写入它来切换异常级别从 EL3 进入 EL2 或从 EL2 进入 Guest需要准备异常返回状态并执行ERET。下面是一个概念伪代码用于说明初始化依赖不是可直接运行的 Hypervisor/* * [概念伪代码] * * 省略 * - 当前异常级和执行状态检查 * - 寄存器字段计算 * - Stage-2 页表格式 * - 页表项地址宽度和对齐检查 * - TLB 失效 * - 数据和指令屏障 * - 缓存维护 * - 异常向量保存和恢复 * - 多核同步 * - Guest 完整上下文 */voidhypervisor_init(vm_t*vm){/* 1. 先建立 EL2 栈和异常向量 */write_sysreg(VBAR_EL2,el2_exception_vector);setup_el2_stack();/* 2. 准备 Guest 的 Stage-2 页表 */stage2_map(vm-stage2_root,guest_ipa_start,host_pa_start,guest_memory_size,STAGE2_READ|STAGE2_WRITE);/* 3. 配置 Stage-2 转换参数 */write_sysreg(VTCR_EL2,build_vtcr_for_platform());/* 4. 设置当前虚拟机的 Stage-2 页表根 */write_sysreg(VTTBR_EL2,build_vttbr(vm-vmid,vm-stage2_root));/* 5. 配置 Guest 的异常和特权行为 */write_sysreg(HCR_EL2,build_hcr_policy(vm));/* 6. 真实实现还需要 TLBI、DSB、ISB 等架构要求的操作 */translation_sync_barrier();}允许 Guest 运行之前至少应该确保以下资源已经可用EL2 栈 │ ▼ EL2 异常向量 │ ▼ Stage-2 页表 │ ▼ Guest 初始寄存器状态 │ ▼ 虚拟中断和虚拟定时器 │ ▼ 异常处理和 Guest 上下文保存启动Guest时Hypervisor 需要为 Guest 准备入口 PC栈指针SPSR_EL2Guest 的系统寄存器Guest 的通用寄存器Stage-2 页表异常向量虚拟中断和定时器状态。概念上可以表示为/* * [概念伪代码] * ERET 是异常返回不是普通函数返回。 */voidenter_guest(vm_t*vm){write_sysreg(ELR_EL2,vm-entry_pc);write_sysreg(SPSR_EL2,vm-initial_pstate);restore_guest_system_registers(vm);restore_guest_general_registers(vm);instruction_sync_barrier();/* * 真实代码需要使用目标架构规定的异常返回指令。 */eret();}Guest 运行后控制流通常不会像普通函数一样顺序返回而是在发生以下事件时重新陷入 EL2受限系统寄存器访问WFI 或 WFEStage-2 fault虚拟设备访问外部中断未实现或受限制的指令需要 Hypervisor 调度的事件。八、虚拟中断和虚拟定时器8.1 虚拟中断Guest OS 必须能够看到自己的中断否则无法正常调度任务和处理设备事件。GIC 的虚拟化扩展可以帮助 Hypervisor 管理虚拟中断状态虚拟中断优先级虚拟 CPU 的中断接口物理中断到虚拟中断的注入。基本路径为物理设备产生中断 │ ▼ Hypervisor 识别设备归属 │ ▼ 设置虚拟中断状态 │ ▼ 通过 GIC 虚拟接口呈现给 Guest硬件负责快速保存和呈现部分中断状态软件负责决定哪个设备属于哪个 Guest哪个虚拟 CPU 接收中断是否延迟注入设备采用直通还是软件模拟中断完成后如何清理状态。GIC 虚拟化并不意味着 Guest 可以无条件直接使用物理 Distributor 或 Redistributor。Guest 对相关寄存器的访问可能需要陷入并由 Hypervisor 模拟。在 GICv3 等实现中Hypervisor 可能需要管理类似以下寄存器ICH_HCR_EL2 ICH_VMCR_EL2 ICH_LRn_EL2但具体实现和寄存器集合取决于 GIC 版本及处理器能力。还需要注意HCR_EL2.IMO/FMO主要用于中断路由它们不等于“已经打开 GIC 虚拟化”虚拟中断的 pending、active、priority 和 EOI 状态仍需要正确维护GICv4/GICv4.1 的 vPE 和 direct injection 是额外能力不能推广到所有设备和所有平台。8.2 虚拟定时器操作系统依赖定时器进行任务调度时间片轮转超时处理定时唤醒时间统计。ARMv8-A 提供虚拟定时器相关能力例如CNTVCT_EL0虚拟计数器CNTV_CTL_EL0虚拟定时器控制CNTV_CVAL_EL0绝对虚拟比较值CNTV_TVAL_EL0相对虚拟计时值CNTVOFF_EL2虚拟计数器偏移。虚拟计数器可以抽象为Guest 看到的虚拟时间 ≈ 物理计数器时间 - CNTVOFF_EL2在符合具体架构定义的计数器宽度和回绕规则下通常有类似关系CNTVCT_EL0 CNTPCT_EL0 - CNTVOFF_EL2没有硬件虚拟定时器时Guest 读取时间 │ ▼ 陷入 Hypervisor │ ▼ 软件计算虚拟时间 │ ▼ 返回 Guest有硬件虚拟定时器时Guest 可以直接读取虚拟计数器硬件减少了频繁陷入的开销但 Hypervisor 仍然需要管理虚拟时间偏移虚拟定时器中断vCPU 调度Guest 暂停和恢复后的时间语义vCPU 切换时相关寄存器状态。定时器到期也不等于 Guest 一定立刻收到中断。还需要定时器控制状态允许产生事件中断通过 GIC 虚拟接口呈现Hypervisor 或硬件完成虚拟中断注入Guest 侧中断控制器配置正确。因此虚拟定时器是硬件辅助功能而不是完全脱离软件的自动功能。九、软硬件协同设计中的工程原则先定义接口再实现功能寄存器、DMA 描述符、页表和虚拟机上下文本质上都是软硬件之间的接口。接口设计时应明确字段含义默认值对齐要求地址宽度访问权限生命周期并发规则错误状态版本兼容方式。先建立异常路径再开放强能力在开启分页、IOMMU 或虚拟化之前应先准备能够处理故障的路径准备异常向量 │ ▼ 准备异常栈 │ ▼ 准备故障日志 │ ▼ 建立页表和权限 │ ▼ 执行失效和同步 │ ▼ 再开启地址转换或虚拟化否则一旦硬件按预期产生异常而软件没有可用的异常入口系统可能直接死机或产生数据损坏。地址转换和权限必须一起设计不能只设计地址映射还要同时回答谁可以访问 访问哪个地址 访问权限是什么 映射何时生效 映射何时撤销 TLB/IOTLB 是否需要失效 出现错误后如何恢复例如IOMMU 如果只配置了地址却没有正确绑定设备身份就不能形成完整的 DMA 隔离。Stage-2 如果只映射地址却没有设置正确的读写权限也不能形成完整的 Guest 内存保护。能力探测属于功能的一部分底层代码不能假设所有 CPU、芯片组和虚拟机环境都相同。软件应当通过适当机制探测x86 的 CPUIDARM 的 ID 寄存器ACPI 表设备树PCIe 配置空间固件启动协议IOMMU/SMMU 能力。只有确认硬件能力后才能决定是否启用对应功能。为关键功能准备降级路径硬件能力首选方案降级方案A20 控制平台推荐路径8042 或兼容路径IOMMU硬件 DMA 隔离受限内存池或 Bounce Buffer虚拟定时器硬件虚拟定时器Hypervisor 软件管理虚拟中断GIC 硬件虚拟化软件注入或模拟设备虚拟化设备直通软件设备模型CPU 虚拟化硬件陷入和二阶段转换软件模拟或更严格限制降级方案通常性能较低但可以提升系统兼容性和可移植性。把高频机制交给硬件把复杂策略交给软件适合放到硬件中的功能通常具有以下特点执行频率高规则相对稳定对延迟敏感需要在数据通路上完成需要强制执行。例如地址转换权限检查DMA 搬运中断状态保存定时器比较Guest 访问陷入TLB 或 IOTLB 查询。适合放到软件中的功能通常具有以下特点策略变化频繁需要动态分配资源需要兼容多个版本需要复杂的故障恢复需要根据系统负载作出决策。例如内存分配设备归属vCPU 调度Guest 生命周期管理错误恢复设备模型兼容路径。可以概括为硬件实现机制软件实现策略。从 x86 启动阶段配置CR0、CR3、CR4和IA32_EFER到 A20 地址线的历史兼容从 IOMMU 对 DMA 地址进行硬件隔离到 ARMv8-A 通过 EL2、Stage-2 页表、虚拟中断和虚拟定时器支持高效虚拟化这些技术虽然来自不同处理器和不同发展阶段但它们遵循着相同的系统设计思想硬件提供能力 软件建立规则 硬件执行高频路径 软件管理资源和异常因此软硬件协同设计的核心不是简单决定“功能放在硬件还是软件”而是回答以下问题谁生成地址 谁检查权限 谁保存状态 什么时候发生切换 如何确认切换生效 出现错误后谁负责恢复真正优秀的底层初始化不是把硬件简单“打开”而是在打开之前准备好硬件将要遵守的规则并在打开之后证明这些规则确实生效。把高频、低延迟、强隔离的机制交给硬件把灵活、复杂、需要策略判断的部分交给软件。这就是嵌入式系统中软硬件协同设计的核心价值。参考资料Intel 64 and IA-32 Architectures Software Developer’s ManualLinux x86 Boot ProtocolGNU GRUB Multiboot2 SpecificationUEFI SpecificationIntel VT-d Architecture SpecificationAMD IOMMU SpecificationArm Architecture Reference Manual for A-profile architectureArm Learn the ArchitectureArmv8-A VirtualizationArm Generic TimerGIC Architecture SpecificationLinux Kernel DocumentationDMA API、IOMMU、KVMQEMU、Linux KVM 以及相关架构启动代码实现