尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

功能安全架构中Hypervisor选型与分区隔离实操指南

功能安全架构中Hypervisor选型与分区隔离实操指南 1. 为什么要在功能安全架构里引入Hypervisor第一次接触“在安全架构里塞一个Hypervisor”这个思路是在一个域控制器项目上。当时系统里跑着两个操作系统一个负责仪表显示和娱乐交互基于Linux另一个负责车身控制逻辑基于RTOS。两个系统共享一颗SoC但安全等级要求完全不同——仪表那边偶尔卡一下用户能忍车身控制那边如果响应超时后果就严重了。最初的方案是双芯片各跑各的互不干扰但成本高、板子面积大、功耗也压不下来。后来团队讨论能不能用一颗芯片通过Hypervisor把两个系统隔离开让高安全等级的任务独占一个虚拟机低安全等级的任务跑在另一个虚拟机里即使娱乐系统崩了也不会影响到车身控制。这个思路的核心就是分区隔离。Hypervisor虚拟机监控器本质上是一层很薄的软件它直接运行在硬件之上负责把物理资源——CPU核心、内存区域、外设——划分成若干个互相隔离的执行环境。每个执行环境里可以跑一个独立的操作系统操作系统以为自己独占硬件实际上所有敏感操作都要经过Hypervisor的仲裁。这种架构在服务器和桌面领域已经非常成熟但把它搬到功能安全场景下考量的重点就完全不一样了。功能安全关注的是什么是确定性和可验证性。一个系统通过了ASIL-D等级认证意味着它的失效概率被控制在极低水平而且整个开发过程有完整的追溯链。Hypervisor引入之后隔离机制本身成了安全链条上的一环它必须足够可靠不能成为新的单点故障。所以不是随便拿一个开源Hypervisor就能往安全架构里塞的选型、配置、验证每一步都有讲究。这篇文章主要面向做嵌入式系统架构、域控制器开发、以及功能安全相关工作的工程师。如果你正在考虑用一颗SoC跑多个操作系统或者想了解Hypervisor在安全场景下到底怎么落地下面这些内容应该能帮你少走一些弯路。我会从架构设计、Hypervisor选型、资源分配、实操配置、问题排查几个维度展开尽量把原理和实操揉在一起讲。2. Hypervisor选型与功能安全架构设计2.1 Type 1和Type 2的本质区别Hypervisor分两大类Type 1和Type 2。Type 1叫裸机型bare-metal直接跑在硬件上虚拟机在它之上Type 2叫宿主型hosted跑在一个操作系统之上虚拟机是宿主系统里的进程。桌面上的VirtualBox、VMware Workstation属于Type 2服务器上的ESXi、Xen属于Type 1。功能安全场景下几乎只能用Type 1。原因很简单Type 2依赖一个宿主操作系统宿主系统本身的安全等级、实时性、可靠性都会影响整个架构。你没法在一个通用Linux上跑ASIL-D的任务因为Linux内核的代码量和复杂度决定了它很难通过高等级安全认证。Type 1 Hypervisor的代码量通常只有几万行甚至更少形式化验证和认证的可行性高得多。但Type 1也不是铁板一块。有些Type 1 Hypervisor是静态分区的配置在编译时固定运行时不能动态创建或销毁虚拟机有些是动态分区的支持运行时调度和资源热插拔。安全场景下静态分区更受青睐因为行为可预测认证时更容易做最坏情况执行时间分析。动态调度虽然灵活但调度器的行为复杂WCET分析难度大认证成本高。还有一个维度是是否支持实时调度。通用Hypervisor的调度器追求吞吐量和公平性而安全场景要求的是确定性的响应时间。所以安全Hypervisor通常会提供时间分区调度time-partitioned scheduling给每个虚拟机分配固定的时间窗口按轮转方式执行。这样每个虚拟机的执行时间是可计算的不会因为某个虚拟机的负载突增而影响其他虚拟机。2.2 安全架构中的隔离层级在功能安全架构里Hypervisor提供的隔离不是单一维度的而是分层的。我习惯把它分成三层来看第一层是空间隔离。每个虚拟机有独立的内存地址空间Hypervisor通过MMU内存管理单元或MPU内存保护单元来强制隔离。一个虚拟机不能读写另一个虚拟机的内存这是最基本的要求。实现方式通常是给每个虚拟机分配独立的页表Hypervisor在上下文切换时切换页表基址寄存器。第二层是时间隔离。每个虚拟机有独立的执行时间窗口Hypervisor的调度器保证一个虚拟机不会无限期占用CPU。时间隔离的实现依赖于硬件定时器和调度算法。安全场景下调度算法要足够简单最好能用数学方法证明其最坏情况行为。第三层是设备隔离。外设的访问权限要严格控制。比如CAN控制器只能被车身控制虚拟机访问GPU只能被娱乐虚拟机访问。设备隔离通常通过IOMMU输入输出内存管理单元或SMMU系统内存管理单元来实现把设备DMA访问也纳入地址翻译和权限检查的范围。这三层隔离缺一不可。我见过一些项目只做了空间隔离没做设备隔离结果娱乐虚拟机通过DMA直接改写了车身控制虚拟机的内存隔离形同虚设。IOMMU的配置在安全架构里不是可选项是必选项。2.3 安全等级与虚拟机划分策略虚拟机怎么划分直接关系到安全认证的难度。常见的策略有两种按安全等级划分一个虚拟机跑ASIL-D的任务另一个跑QM质量管理级别的任务。高安全等级的虚拟机独占关键资源低安全等级的虚拟机跑在剩余资源上。这种划分方式认证路径清晰高安全虚拟机可以独立认证低安全虚拟机即使出问题也不影响安全目标。按功能域划分一个虚拟机跑座舱功能一个跑智驾功能一个跑车身功能。这种划分方式更贴近实际业务但认证时需要考虑虚拟机之间的交互。如果座舱虚拟机需要向车身虚拟机发送信号这个通信通道本身也要做安全分析。我个人的经验是优先按安全等级划分然后在同一安全等级内部再按功能域细分。这样认证的边界最清晰工作量最小。如果业务上确实需要跨安全等级通信那就把通信通道做成一个受控的、可验证的接口比如基于共享内存的环形缓冲区加上完整性校验和超时监控。2.4 硬件支持从ARM到RISC-VHypervisor能不能跑起来硬件支持是关键。ARMv8-A架构提供了EL2异常等级专门给Hypervisor用。EL0是用户态EL1是内核态EL2是Hypervisor态EL3是安全监控态。有了EL2Hypervisor可以拦截虚拟机的敏感指令做指令模拟和资源仲裁。ARM的虚拟化扩展还包括Stage-2地址翻译让Hypervisor可以给每个虚拟机维护一套独立的地址映射。RISC-V这边H扩展Hypervisor扩展提供了类似的机制包括HS模式Hypervisor Supervisor和VS模式Virtual Supervisor。RISC-V的虚拟化生态还在发展中但已经有了一些安全Hypervisor的实现。x86这边Intel VT-x和AMD-V提供了硬件虚拟化支持但在功能安全场景下x86的生态和认证基础不如ARM和RISC-V。汽车和工业领域的主流选择还是ARM。选硬件的时候除了看虚拟化扩展还要看中断控制器是否支持虚拟化。ARM的GICv3/v4支持虚拟中断注入Hypervisor可以把物理中断路由到指定的虚拟机而不需要软件模拟。这对实时性很关键。如果中断控制器不支持虚拟化Hypervisor就得自己处理所有中断然后以软件方式注入给虚拟机延迟会大很多。3. 核心细节解析与实操要点3.1 内存分区与MPU配置内存隔离是Hypervisor最基础的功能。在ARMv8上Hypervisor通过Stage-2翻译表来控制每个虚拟机的物理地址空间。Stage-2翻译表把虚拟机的“物理地址”IPAIntermediate Physical Address翻译成真实的物理地址PA。Hypervisor可以在Stage-2表里设置权限位决定某个内存区域是只读、读写还是不可访问。配置Stage-2翻译表的时候有几个坑要注意第一个坑是页表粒度。ARMv8支持4KB、16KB、64KB三种页粒度。4KB粒度最灵活但页表层级多TLB miss率高64KB粒度TLB效率高但内存碎片多。安全场景下我通常建议用4KB粒度因为内存分区往往需要精细控制大页容易造成权限溢出。第二个坑是内存属性的配置。ARMv8的内存属性包括Normal、Device、Strongly-ordered等。虚拟机的代码和数据区应该配成Normal内存带缓存外设寄存器应该配成Device内存不带缓存或写合并。如果配错了轻则性能下降重则功能异常。我见过一个项目把CAN控制器的寄存器配成了带缓存的内存结果写寄存器的操作被缓存合并了CAN报文发送时序完全乱掉。第三个坑是共享内存的权限。如果两个虚拟机需要共享一块内存做通信这块内存在两个虚拟机的Stage-2表里都要映射但权限要仔细设置。通常是一个虚拟机只读另一个只写或者双方都读写但加上软件层的锁。如果两个虚拟机都能写同一块内存又没有同步机制数据竞争几乎必然发生。下面是一个Stage-2翻译表的配置示例用伪代码表示// 定义内存区域 #define VM1_BASE 0x80000000 #define VM1_SIZE 0x10000000 // 256MB #define VM2_BASE 0xC0000000 #define VM2_SIZE 0x10000000 // 256MB #define SHARED_BASE 0x90000000 #define SHARED_SIZE 0x00100000 // 1MB // 配置VM1的Stage-2表 void configure_vm1_stage2(void) { // VM1的代码和数据区读写权限 stage2_map(VM1_BASE, VM1_BASE, VM1_SIZE, NORMAL_MEMORY, RW_EL1); // 共享内存区只读权限 stage2_map(SHARED_BASE, SHARED_BASE, SHARED_SIZE, NORMAL_MEMORY, RO_EL1); // 其他区域不可访问 stage2_map(0x00000000, 0x00000000, VM1_BASE, NORMAL_MEMORY, NO_ACCESS); } // 配置VM2的Stage-2表 void configure_vm2_stage2(void) { // VM2的代码和数据区读写权限 stage2_map(VM2_BASE, VM2_BASE, VM2_SIZE, NORMAL_MEMORY, RW_EL1); // 共享内存区读写权限 stage2_map(SHARED_BASE, SHARED_BASE, SHARED_SIZE, NORMAL_MEMORY, RW_EL1); // 其他区域不可访问 stage2_map(0x00000000, 0x00000000, VM2_BASE, NORMAL_MEMORY, NO_ACCESS); }注意Stage-2翻译表的配置必须在Hypervisor初始化阶段完成运行时不应该动态修改。动态修改会引入不可预测的行为认证时很难说服审核员。3.2 中断路由与虚拟中断注入中断处理是Hypervisor里最复杂的部分之一。在非虚拟化系统里中断直接送到CPU由操作系统处理。在虚拟化系统里中断先到HypervisorHypervisor判断这个中断应该给哪个虚拟机然后注入给那个虚拟机。ARM的GICv3/v4提供了硬件级的虚拟中断支持。Hypervisor可以配置GIC把某个物理中断直接路由到某个虚拟机的虚拟CPU接口。虚拟机收到中断后读GIC的寄存器看到的是虚拟中断号而不是物理中断号。Hypervisor负责维护物理中断号和虚拟中断号的映射关系。配置中断路由的时候有几个关键点中断优先级。安全相关的中断应该配成高优先级确保及时响应。比如看门狗中断、安全监控中断优先级要高于普通外设中断。GIC支持优先级分组可以把中断分成安全组和非安全组安全组的中断不能被非安全组的代码屏蔽。中断亲和性。在多核系统里中断可以绑定到特定的CPU核心。安全虚拟机的vCPU应该绑定到专用的物理核心中断也绑定到这些核心避免跨核迁移带来的延迟抖动。虚拟中断的注入时机。Hypervisor在注入虚拟中断之前要确保虚拟机的vCPU处于可接收中断的状态。如果vCPU正在处理更高优先级的中断或者处于关中断状态虚拟中断要挂起等vCPU开中断后再注入。这个逻辑要仔细实现否则会丢中断或重复注入。下面是一个GIC虚拟中断配置的示例// 配置物理中断30路由到VM1 void configure_irq_routing(void) { // 设置中断30的亲和性绑定到CPU0 gic_set_affinity(30, 0x01); // 设置中断30的优先级 gic_set_priority(30, 0x20); // 设置中断30的目标虚拟机为VM1 gic_set_vm_target(30, VM1_ID); // 使能中断30 gic_enable_irq(30); } // Hypervisor的中断处理入口 void hypervisor_irq_handler(void) { uint32_t irq gic_acknowledge_irq(); if (irq 30) { // 注入虚拟中断到VM1 vgic_inject_irq(VM1_ID, 30); } gic_deactivate_irq(irq); }实操心得GIC的虚拟化配置寄存器很多建议对照芯片手册逐位确认。我踩过一次坑LIST寄存器的一个位配错了导致虚拟中断永远注入不到VM1排查了两天才发现是寄存器位定义看漏了。3.3 CPU核心分配与调度策略CPU核心怎么分配给虚拟机直接影响系统的实时性和吞吐量。常见的分配方式有三种独占分配每个虚拟机独占一个或多个物理核心。这种方式隔离性最好一个虚拟机的负载不会影响另一个虚拟机。缺点是核心利用率低如果某个虚拟机空闲它的核心也不能给别的虚拟机用。共享分配多个虚拟机共享物理核心Hypervisor的调度器负责切换。这种方式利用率高但隔离性差调度延迟不确定。混合分配关键虚拟机独占核心非关键虚拟机共享剩余核心。这种方式兼顾隔离性和利用率是我最推荐的方案。在安全场景下关键虚拟机必须独占核心。比如ASIL-D的虚拟机应该独占一个核心并且这个核心不能被Hypervisor用于其他目的。Hypervisor自身的代码可以跑在另一个核心上或者跑在关键虚拟机的核心上但只在特定时间窗口执行。调度策略方面安全Hypervisor通常采用时间分区调度。把时间切成固定长度的周期每个周期内给每个虚拟机分配固定的时间片。比如周期1msVM1分500usVM2分300usHypervisor分200us。调度器按固定顺序轮转不动态调整。这种调度器的行为完全可预测WCET分析很简单。时间分区调度的配置参数包括参数说明典型值调度周期一轮调度总时长1msVM1时间片安全虚拟机执行时间500usVM2时间片非安全虚拟机执行时间300usHypervisor时间片Hypervisor自身执行时间200us上下文切换开销虚拟机切换耗时5-10us上下文切换开销要算在时间片里。如果切换开销是10usVM1的实际执行时间只有490us。这个开销在认证时要明确说明并且要测量最坏情况值。3.4 设备直通与IOMMU配置设备直通device passthrough是指把某个物理设备直接分配给一个虚拟机虚拟机直接操作设备寄存器不需要Hypervisor介入。这种方式性能好但隔离性依赖IOMMU。IOMMU的作用是翻译设备DMA访问的地址并检查权限。没有IOMMU设备可以DMA到任意物理地址虚拟机可以通过编程设备来读写其他虚拟机的内存。有了IOMMU设备只能DMA到Hypervisor给它配置的地址范围越界访问会被阻止。配置IOMMU的步骤在Hypervisor初始化时扫描所有PCIe设备记录每个设备的BDFBus/Device/Function号。为每个需要直通的设备创建IOMMU页表映射虚拟机看到的地址到物理地址。把设备的BDF号和IOMMU页表关联起来。使能IOMMU的全局翻译和权限检查。// 配置IOMMU把设备0000:01:00.0直通给VM1 void configure_iommu_passthrough(void) { // 创建IOMMU页表 iommu_page_table_t *pt iommu_create_page_table(); // 映射VM1的地址空间 iommu_map(pt, VM1_BASE, VM1_BASE, VM1_SIZE, IOMMU_RW); // 关联设备和页表 iommu_attach_device(0x0000, 0x01, 0x00, 0, pt); // 使能IOMMU iommu_enable(); }注意IOMMU的页表要和Stage-2翻译表保持一致。如果Stage-2表里VM1不能访问某块内存IOMMU页表里也不应该映射那块内存。两边不一致会导致安全漏洞。4. 实操过程与核心环节实现4.1 环境搭建与工具链准备动手之前先把环境搭好。我以ARMv8平台为例说一下需要的工具和步骤。硬件平台需要一块支持ARMv8虚拟化扩展的开发板。常见的选项有Raspberry Pi 4Cortex-A72支持EL2、NXP i.MX 8系列Cortex-A53/A72、Xilinx Zynq UltraScaleCortex-A53。如果做功能安全项目建议选带安全手册和认证包的芯片比如NXP的S32系列或TI的TDA4系列。软件工具交叉编译工具链aarch64-linux-gnu-gcc或aarch64-none-elf-gcc调试器OpenOCD GDB或者芯片厂商的调试工具串口终端minicom或picocom用于查看启动日志镜像工具mkimage、dd、fdisk用于制作启动镜像Hypervisor选择开源的有Xen、KVMARM上叫KVM/ARM、Jailhouse、ACRN。安全场景下Jailhouse和ACRN比较合适代码量小静态分区认证基础好。Xen功能全但代码量大认证成本高。KVM依赖Linux不适合高安全等级场景。我以Jailhouse为例说一下编译和部署流程# 克隆Jailhouse源码 git clone https://github.com/siemens/jailhouse.git cd jailhouse # 配置交叉编译 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- clean make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- config # 编译Hypervisor和 inmate make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc) # 编译 inmate 示例 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- \ KDIR/path/to/linux-kernel \ -C inmates编译完成后会生成hypervisor.bin和各个inmate的.bin文件。hypervisor.bin是Hypervisor本体inmate是跑在虚拟机里的裸机程序或操作系统镜像。4.2 系统配置与虚拟机创建Jailhouse的配置通过.cell文件描述。一个cell代表一个虚拟机里面定义了内存区域、CPU核心、中断、PCI设备等资源。下面是一个简化的cell配置文件示例// vm1.cell - 安全虚拟机配置 struct jailhouse_cell_desc { .name vm1, .cpu_set { 0x01 }, // 使用CPU0 .num_memory_regions 2, .memory_regions { { .phys_start 0x80000000, .virt_start 0x80000000, .size 0x10000000, .flags JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE | JAILHOUSE_MEM_EXECUTE, }, { .phys_start 0x90000000, .virt_start 0x90000000, .size 0x00100000, .flags JAILHOUSE_MEM_READ, }, }, .num_irqchips 1, .irqchips { { .address 0x08000000, .id 0, .type JAILHOUSE_IRQCHIP_GICV3, }, }, .num_pci_devices 1, .pci_devices { { .type JAILHOUSE_PCI_TYPE_DEVICE, .domain 0x0000, .bdf 0x0100, .caps 0x0, }, }, };加载cell的命令# 加载Hypervisor jailhouse enable /path/to/hypervisor.bin # 创建VM1 jailhouse cell create /path/to/vm1.cell # 加载VM1的镜像 jailhouse cell load vm1 /path/to/vm1_image.bin # 启动VM1 jailhouse cell start vm1实操心得cell配置文件里的内存区域要仔细核对确保不重叠。我见过一个项目两个cell的内存区域有重叠结果VM1写数据把VM2的代码段覆盖了VM2直接跑飞。排查的时候用jailhouse cell stat命令查看内存映射才发现问题。4.3 启动流程与初始化顺序Hypervisor的启动流程很关键顺序错了系统起不来。典型的启动顺序是Bootloader阶段U-Boot加载Hypervisor镜像和初始虚拟机镜像到内存。Hypervisor初始化设置EL2异常向量初始化Stage-2翻译表初始化GIC虚拟化初始化IOMMU。创建根虚拟机Hypervisor创建一个根虚拟机root cell通常跑一个精简的Linux用于管理其他虚拟机。根虚拟机加载其他虚拟机根虚拟机里的管理工具如jailhouse工具加载其他cell的配置和镜像。启动其他虚拟机根虚拟机发送启动命令Hypervisor调度其他虚拟机的vCPU开始执行。这个流程里Hypervisor初始化阶段最容易出问题。Stage-2翻译表如果配错了根虚拟机可能都起不来。GIC虚拟化如果没使能中断收不到。IOMMU如果没配好设备直通会失败。调试的时候建议在Hypervisor初始化代码里加串口打印每一步都输出状态。虽然最终产品里不会保留这些打印但开发阶段能省很多时间。4.4 通信机制与共享内存实现虚拟机之间需要通信的时候共享内存是最常用的方式。实现一个可靠的共享内存通信机制需要注意以下几点内存布局共享内存区域要固定两个虚拟机的Stage-2表里都要映射。通常放在物理内存的一个保留区域不参与操作系统的内存管理。同步机制两个虚拟机同时读写共享内存时需要同步。简单的方案是用自旋锁但自旋锁在虚拟化环境里有个问题如果一个虚拟机持有锁的时候被Hypervisor切走了另一个虚拟机会一直自旋等待浪费CPU时间。更好的方案是用无锁环形缓冲区生产者写、消费者读通过内存屏障保证顺序。完整性校验安全相关的通信数据要加校验。可以用CRC32或更简单的校验和。如果安全等级要求高可以用HMAC但HMAC需要密钥管理复杂度高。下面是一个无锁环形缓冲区的实现示例// 共享内存中的环形缓冲区结构 struct ring_buffer { volatile uint32_t head; // 生产者写位置 volatile uint32_t tail; // 消费者读位置 uint32_t size; // 缓冲区大小 uint8_t data[]; // 数据区 }; // 生产者写入数据 int ring_buffer_write(struct ring_buffer *rb, const void *data, uint32_t len) { uint32_t head rb-head; uint32_t tail rb-tail; uint32_t available (tail - head - 1 rb-size) % rb-size; if (len available) { return -1; // 缓冲区满 } // 写入数据 for (uint32_t i 0; i len; i) { rb-data[(head i) % rb-size] ((uint8_t *)data)[i]; } // 内存屏障确保数据写入后再更新head __sync_synchronize(); rb-head (head len) % rb-size; return 0; } // 消费者读取数据 int ring_buffer_read(struct ring_buffer *rb, void *data, uint32_t len) { uint32_t head rb-head; uint32_t tail rb-tail; uint32_t available (head - tail rb-size) % rb-size; if (len available) { return -1; // 数据不足 } // 读取数据 for (uint32_t i 0; i len; i) { ((uint8_t *)data)[i] rb-data[(tail i) % rb-size]; } // 内存屏障确保数据读取后再更新tail __sync_synchronize(); rb-tail (tail len) % rb-size; return 0; }注意__sync_synchronize()是GCC的内存屏障内建函数。在ARMv8上它生成dmb ish指令。如果编译器不支持可以用内联汇编写dmb ish。5. 常见问题与排查技巧实录5.1 虚拟机启动失败排查虚拟机启动失败是最常见的问题表现是串口没有任何输出或者输出到某一步就卡住。排查思路如下第一步确认Hypervisor是否启动成功。如果Hypervisor都没起来虚拟机肯定起不来。检查Bootloader是否正确加载了Hypervisor镜像Hypervisor的入口地址是否正确。第二步确认根虚拟机是否启动。根虚拟机是管理其他虚拟机的如果根虚拟机没起来其他虚拟机也起不来。检查根虚拟机的cell配置特别是内存区域和CPU核心。第三步确认目标虚拟机的镜像是否正确加载。用jailhouse cell stat命令查看cell状态确认镜像加载地址和入口地址。第四步检查Stage-2翻译表。如果虚拟机的代码段没有正确映射CPU取指就会失败。用调试器查看Stage-2表确认虚拟机的入口地址有映射权限是可执行。常见错误和解决方法错误现象可能原因解决方法串口无输出Hypervisor未启动检查Bootloader加载地址输出到某一步卡住内存映射错误检查Stage-2表配置虚拟机跑飞代码段权限错误确认代码段有执行权限中断收不到GIC虚拟化未使能检查GIC配置寄存器设备访问失败IOMMU未配置检查IOMMU页表和设备关联5.2 中断丢失与延迟问题中断丢失在虚拟化环境里很常见原因通常有几个虚拟中断注入时机不对。Hypervisor在vCPU关中断的时候注入虚拟中断中断会被挂起等vCPU开中断后再注入。如果Hypervisor没有正确处理挂起状态中断就丢了。中断优先级配置错误。如果虚拟中断的优先级低于vCPU当前的中断屏蔽级别中断会被屏蔽。检查GIC的优先级配置和vCPU的PSTATE寄存器。中断亲和性配置错误。如果中断绑定到了错误的CPU核心而那个核心上没有对应的vCPU中断就没人处理。延迟问题通常和调度有关。如果虚拟机的执行时间片太短中断处理被推迟到下一个时间片延迟就会增大。解决方法是给安全虚拟机分配足够长的时间片或者让安全虚拟机独占核心。5.3 性能优化与资源调优Hypervisor引入后性能下降是必然的。下降幅度取决于几个因素上下文切换开销。每次虚拟机切换都要保存和恢复寄存器、切换页表、刷新TLB。减少切换次数可以降低开销。方法是增大时间片或者让关键虚拟机独占核心。Stage-2翻译开销。每次内存访问都要经过Stage-1和Stage-2两级翻译TLB miss率增加。优化方法是使用大页映射减少页表层级。但大页会影响隔离粒度需要权衡。中断注入开销。虚拟中断注入需要Hypervisor介入比物理中断直接处理慢。优化方法是使用硬件虚拟中断支持减少软件模拟。性能调优的参数和效果优化措施效果代价增大时间片减少切换开销调度延迟增大使用大页映射减少TLB miss隔离粒度变粗硬件虚拟中断降低注入延迟需要硬件支持独占CPU核心消除调度干扰核心利用率降低5.4 安全认证中的常见坑功能安全认证是Hypervisor项目里最耗时的环节。常见的坑有隔离机制没有形式化验证。认证员会要求提供隔离机制的证明如果只是测试验证不够。需要用形式化方法证明Stage-2翻译表的隔离性。WCET分析不完整。Hypervisor的调度器、中断处理、上下文切换都要做WCET分析。如果漏了某个路径认证会被打回。故障注入测试不充分。认证要求做故障注入测试验证隔离机制在故障情况下仍然有效。比如注入内存位翻转看Hypervisor是否能检测并处理。文档追溯链不完整。从需求到设计到代码到测试每一环都要有追溯。如果某个需求没有对应的测试用例认证员会开问题。实操心得认证前期就要把认证员拉进来定期评审。不要等所有开发做完了再送审那时候发现问题改起来成本极高。我经历过一个项目送审后发现隔离机制的形式化证明方法不被认可重新做花了三个月。6. 一些个人体会Hypervisor在功能安全架构里的应用本质上是在灵活性和确定性之间找平衡。虚拟化带来了资源整合的好处一颗SoC跑多个系统成本低、功耗小、板子简洁。但虚拟化也引入了新的复杂度和不确定性Hypervisor本身成了安全链条上的一环它的可靠性直接决定了整个系统的安全等级。我的经验是能用静态分区就不用动态调度能用硬件支持就不用软件模拟能独占资源就不共享。安全场景下确定性比性能重要可验证性比灵活性重要。每一个设计决策都要问自己这个决策能不能被证明能不能被测试能不能被认证还有一个体会是Hypervisor不是万能的。它解决的是隔离问题不解决实时性问题。如果两个虚拟机都需要硬实时那它们之间的资源竞争仍然存在Hypervisor只能保证时间隔离不能保证每个虚拟机都能满足自己的实时要求。这种情况下可能需要考虑多芯片方案或者把实时要求最高的任务放到MCU上SoC只跑非实时任务。最后工具链和生态很重要。选一个社区活跃、文档齐全、有认证案例的Hypervisor能省很多事。不要为了省授权费选一个冷门的Hypervisor后期遇到问题没人帮你认证时也没有先例可循省下的钱远远不够填坑的。
返回列表