ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

功能安全架构中Type 1 Hypervisor在SoC上的隔离机制与工程实践

功能安全架构中Type 1 Hypervisor在SoC上的隔离机制与工程实践 1. 从一颗SoC说起为什么功能安全架构突然需要Hypervisor如果你最近两年在做车载域控制器、工业伺服或者医疗设备的底层软件大概率会遇到一个绕不开的架构难题一颗SoC上要同时跑Linux、RTOS甚至还有裸机程序而且这些系统里有一部分代码是要过功能安全认证的。以前的做法简单粗暴——安全相关的逻辑单独放一颗MCU非安全的应用跑在另一颗高性能芯片上两颗芯片之间用SPI或者CAN通信。这套方案稳是稳但BOM成本、板子面积、通信延迟都摆在那里域集中化的趋势一来谁都扛不住。Hypervisor就是在这个背景下被推到台前的。它本质上是一层薄薄的软件直接运行在硬件之上把一颗物理SoC的CPU核心、内存、外设切分成若干个互相隔离的虚拟机。每个虚拟机里可以跑独立的操作系统互不干扰。听起来像是服务器虚拟化那一套搬过来了但功能安全场景对它的要求和数据中心完全不同——数据中心追求的是资源利用率和迁移灵活性功能安全追求的是确定性、隔离性和可证明性。我最初接触这个方向的时候脑子里有个很自然的疑问既然Linux内核本身就有cgroups和namespace做隔离为什么还要在下面再垫一层Hypervisor这个问题想清楚了整个技术选型的逻辑就通了。cgroups和namespace是操作系统层面的隔离前提是Linux内核本身是可信的。但在功能安全架构里恰恰不能假设Linux内核永远不出问题——一个驱动越界写内存就可能把旁边安全关键任务的代码段覆盖掉。Hypervisor提供的隔离是硬件级的通过MMU和IOMMU在页表层面做地址空间划分一个虚拟机的内存访问根本到不了另一个虚拟机的物理页。这个区别是功能安全架构选择Hypervisor的根本原因。这篇文章面向的是正在做域控制器架构设计、或者准备把功能安全逻辑和非安全应用往一颗SoC上合并的工程师。我会从Type 1 Hypervisor的工作机制讲起拆解它在功能安全架构里的具体落位方式然后给出实际配置和验证过程中踩过的坑。涉及到的关键词包括Hypervisor、功能安全架构、Type 1、虚拟化和SoC这些概念我会在具体场景里解释不单独做名词定义。2. Type 1 Hypervisor在SoC上的真实运行机制2.1 为什么功能安全场景几乎只用Type 1Hypervisor分两类Type 1是裸机运行直接管理硬件Type 2是跑在宿主操作系统之上比如你在Windows里装个VMware Workstation那种。功能安全场景基本不会考虑Type 2原因很直接Type 2的下面还压着一个通用操作系统那个操作系统的调度延迟、内存管理行为、驱动质量都是不可控的。你没法向认证机构证明一个安全关键任务的响应时间因为中间隔着的宿主系统本身就没有确定性保证。Type 1 Hypervisor直接跑在EL2异常级别以ARM架构为例它自己就是一个极简的实时内核。它负责的事情只有几件CPU核心的分配和调度、内存的二级页表管理、中断的路由和注入、外设的访问控制。代码量通常控制在几万行以内相比Linux内核的几千万行可审计性和可验证性完全不是一个量级。这也是它能过功能安全认证的前提——你不可能对一个几千万行的系统做完整的形式化验证但几万行的微内核是有可能的。2.2 ARM架构下的异常级别与隔离边界ARMv8-A架构定义了四个异常级别理解这个分层是理解Hypervisor隔离能力的关键。EL0是用户态EL1是内核态EL2是Hypervisor层EL3是安全监控层TrustZone相关。功能安全架构里安全关键任务通常跑在EL1的RTOS里非安全应用跑在另一个EL1的Linux里Hypervisor在EL2负责切换和隔离。这里有个容易混淆的点EL2的隔离和TrustZone的隔离是两回事。TrustZone把整个SoC分成Secure World和Normal World是安全域的概念主要防的是恶意攻击。Hypervisor的隔离是同一个World内部的资源分区防的是故障传播。功能安全架构里两者经常同时使用但目的不同——TrustZone保证密钥和敏感数据不被非安全侧读取Hypervisor保证非安全侧的崩溃不会拖垮安全侧。2.3 内存隔离的实现细节Stage 2页表Hypervisor做内存隔离靠的是Stage 2地址转换。ARM架构下内存访问要经过两级转换Stage 1是虚拟机内部OS自己的页表把虚拟地址转成中间物理地址Stage 2是Hypervisor管理的页表把中间物理地址转成真正的物理地址。关键就在于Stage 2的页表只有Hypervisor能改虚拟机里的OS无论怎么折腾自己的Stage 1页表最终能访问到的物理内存范围是被Stage 2锁死的。这个机制在功能安全里的价值在于即使Linux内核被一个野指针写崩了它也只能在自己的物理内存范围内崩碰不到RTOS那边的内存。我在实际项目里验证过这个边界——在Linux侧故意写一个越界访问的驱动让它去读写RTOS虚拟机占用的物理地址段结果是Linux侧直接触发Stage 2的权限异常Hypervisor捕获后只终止了Linux虚拟机RTOS侧的任务周期抖动没有任何变化。2.4 中断路由安全关键任务的响应确定性从哪来中断处理是功能安全架构里最敏感的部分。一个安全关键任务需要在确定的时限内响应传感器信号如果中断先被Hypervisor截获、再注入到目标虚拟机这个注入过程本身会引入延迟。Type 1 Hypervisor通常支持中断直通IRQ Passthrough把某个物理中断直接路由到指定虚拟机不经过Hypervisor的软件转发。配置中断直通需要在Hypervisor的设备树里声明中断号和目标虚拟机的对应关系。以常见的开源Type 1方案为例配置片段大致是这样的/* 将SPI 45号中断直通给安全RTOS虚拟机 */ interrupt-router { compatible vendor,irq-router; safe-vm-irqs 45 46 47; linux-vm-irqs 100 101 102; };这个配置的含义是45、46、47号中断直接送到安全RTOS虚拟机Hypervisor不介入100到102号中断送给Linux虚拟机。实测下来直通中断的响应延迟在百纳秒级别和裸机跑RTOS几乎没有区别。但如果中断需要Hypervisor转发延迟会增加到微秒级别对于控制周期在100微秒以内的场景这个差距是致命的。3. 功能安全架构中Hypervisor的落位方式3.1 安全岛与非安全域的划分逻辑把Hypervisor放进功能安全架构第一步是划分安全岛。安全岛指的是那些必须满足特定安全完整性等级比如ISO 26262的ASIL D或者IEC 61508的SIL 3的功能模块它们运行在独立的虚拟机里拥有专属的CPU核心、内存区域和外设。非安全域则是那些失效后不会直接导致人身伤害的功能比如车载信息娱乐、数据记录、OTA升级代理。划分的依据不是功能的重要性而是失效后果的严重程度。我见过一个项目把倒车影像划到了安全岛里理由是“驾驶员看不到后面会撞人”。这个逻辑有问题——倒车影像失效后驾驶员仍然可以通过后视镜观察失效后果可控不应该占用安全岛的算力资源。正确的做法是把倒车影像放在非安全域但在安全域里保留一个轻量的监控逻辑检测到影像链路失效时触发降级提示。3.2 安全域与非安全域的通信通道设计两个虚拟机之间需要交换数据比如非安全域计算出目标轨迹安全域执行控制。这个通信通道本身不能成为故障传播的路径。常见的做法是共享内存加中断通知在Hypervisor层面划出一块物理内存作为共享缓冲区两个虚拟机都能读写但各自只能写自己那一半读对方那一半。写入完成后通过Hypervisor注入一个虚拟中断通知对方。这里有个细节容易出问题共享内存的访问需要做原子性保护。如果安全域正在读轨迹数据非安全域同时更新了缓冲区安全域可能读到半新半旧的数据。解决方案是双缓冲加序列号非安全域写完缓冲区A后更新序列号安全域读取时先读序列号、再读数据、再读一次序列号两次序列号一致才认为数据有效。这个模式在锁-free编程里很常见但在功能安全场景里序列号本身也需要做CRC校验防止位翻转导致误判。3.3 资源分配表把架构决策变成可验证的配置Hypervisor的资源分配是通过配置文件定义的这份配置在功能安全认证里是一份关键工件。它需要明确列出每个虚拟机分到哪些CPU核心、物理内存的起始地址和大小、可以访问哪些外设、哪些中断直通给它。这份配置在系统启动时由Hypervisor解析并强制执行运行期间不可更改。我建议在项目早期就把这份配置表固化下来作为架构设计和安全分析的输入。下面是一个简化的资源分配表示例资源类型安全虚拟机非安全虚拟机说明CPU核心Core 0, Core 1Core 2, Core 3物理隔离不共享内存范围0x8000_0000 - 0xBFFF_FFFF0xC000_0000 - 0xFFFF_FFFFStage 2页表锁定直通中断SPI 45-47SPI 100-102中断不经过Hypervisor转发外设CAN控制器、PWM以太网、USBIOMMU做访问控制共享内存0xB000_0000 - 0xB000_FFFF同左双缓冲序列号这份表格在安全分析里会直接映射到FFIFreedom From Interference的论证材料中。认证机构会检查安全域和非安全域之间是否存在共享资源如果存在是否有机制证明非安全域的故障不会通过这个共享资源影响安全域共享内存和中断是重点审查对象。3.4 启动时序谁先跑谁等谁Hypervisor的启动时序直接影响系统的安全状态。正确的顺序是Hypervisor先初始化自身然后启动安全虚拟机等安全虚拟机进入稳定运行状态后再启动非安全虚拟机。这个顺序保证了即使非安全虚拟机的启动过程出了问题安全域已经处于可控状态。反过来如果非安全虚拟机先启动它可能在安全域还没就绪的时候就往共享内存里写数据或者触发中断导致安全域启动异常。我在一个项目里遇到过这个问题非安全侧的Linux启动太快在安全RTOS还没初始化完中断控制器的时候就发了IPI处理器间中断结果安全侧的中断向量表还没建立直接跑飞了。后来在Hypervisor配置里加了一个启动屏障强制非安全虚拟机等待安全虚拟机发出“就绪”信号后才开始引导。4. 实际配置与验证从纸面架构到跑起来的系统4.1 硬件选型不是所有SoC都支持Stage 2隔离选SoC的时候第一个要确认的是它是否支持虚拟化扩展。ARMv8-A的虚拟化扩展包括Stage 2地址转换、虚拟中断注入、以及EL2异常级别。大部分Cortex-A系列的核心都支持但Cortex-R系列实时核的虚拟化支持情况参差不齐有些型号只支持部分特性。如果你打算在R核上跑Hypervisor一定要查清楚它的虚拟化扩展具体支持到什么程度。第二个要确认的是IOMMUSMMU。没有IOMMU的话外设的DMA访问可以绕过Stage 2页表直接读写任意物理内存。这意味着一个非安全域的外设驱动如果配置错了DMA地址就能写到安全域的内存里。IOMMU的作用就是给外设的DMA访问也加上一层地址转换和权限检查把外设也纳入隔离范围。第三个是中断控制器的虚拟化支持。GICv3及以上版本支持虚拟中断注入Hypervisor可以把一个物理中断注入到指定虚拟机的虚拟中断控制器里虚拟机内部看到的就像是一个普通中断。GICv2需要Hypervisor做更多的软件模拟延迟和CPU开销都更大。4.2 Hypervisor配置文件的常见字段与含义不同Hypervisor的配置格式不一样但核心字段是相通的。以设备树风格的配置为例一个虚拟机的定义通常包含以下部分vmsafe { compatible vendor,vm; id 0; cpus 0 1; /* 分配Core 0和Core 1 */ memory 0x80000000 0x40000000; /* 起始地址0x80000000大小1GB */ entry 0x80000000; /* 虚拟机入口地址 */ passthrough-devices can0 pwm0; passthrough-irqs 45 46 47; shared-mem 0xB0000000 0x10000; /* 共享内存区域 */ };cpus字段指定了虚拟机可以使用的物理核心。这里有个坑如果两个虚拟机共享同一个物理核心Hypervisor需要在它们之间做时间片调度这会引入调度延迟破坏安全关键任务的确定性。功能安全架构里安全虚拟机应该独占核心不和任何其他虚拟机共享。memory字段定义的是中间物理地址的起始和大小Hypervisor会在Stage 2页表里建立这个范围的映射。注意这个地址是中间物理地址不是最终物理地址最终映射到哪块物理内存由Hypervisor的物理内存分配器决定。passthrough-devices和passthrough-irqs是设备直通配置。直通的外设需要IOMMU支持否则DMA访问不受控。直通的中断不经过Hypervisor转发延迟最低。4.3 用Xen做原型验证一个可复现的最小系统如果你想快速搭一个原型验证Hypervisor的隔离效果Xen on ARM是一个比较成熟的选择。它支持ARMv8的虚拟化扩展配置相对灵活社区文档也比较全。下面是我在一个Cortex-A53开发板上跑通的最小系统步骤。首先准备一个支持虚拟化的开发板确认它的固件通常是U-Boot或者UEFI已经正确初始化了EL2。有些开发板的固件默认把Hypervisor模式关掉了需要在启动参数里显式使能。检查方法是看启动日志里有没有“EL2”相关的输出。然后编译Xen和Dom0内核。Xen的配置文件里需要指定Dom0的内存大小和CPU亲和性。对于功能安全验证我建议把Dom0限制在一个核心上另一个核心留给后续创建的RTOS虚拟机。# Xen编译配置片段 CONFIG_DOM0_MEM512M CONFIG_DOM0_CPU2 CONFIG_SHARED_MEMy启动Xen后用xl工具创建一个RTOS虚拟机。RTOS的镜像需要是位置无关的或者链接到虚拟机配置里指定的入口地址。创建命令大致如下xl create -c rtos.cfgrtos.cfg里定义了虚拟机的CPU、内存、直通设备等参数。创建完成后用xl list可以看到两个虚拟机都在运行。这时候可以在Dom0里写一个越界访问的程序尝试读写RTOS虚拟机的内存区域验证Stage 2页表是否真的拦住了。4.4 验证隔离效果故障注入测试怎么做隔离效果不能只看配置必须做故障注入测试。我通常从三个维度来验证内存隔离、CPU隔离、外设隔离。内存隔离的测试方法是在非安全虚拟机里写一个内核模块让它去读写安全虚拟机占用的物理地址范围。预期结果是触发Stage 2权限异常Hypervisor捕获后终止非安全虚拟机安全虚拟机不受影响。测试的时候要注意有些Hypervisor默认会把权限异常上报给非安全虚拟机自己处理需要在配置里改成由Hypervisor接管。CPU隔离的测试方法是在非安全虚拟机里跑一个死循环把它的CPU核心占满然后测量安全虚拟机上任务的周期抖动。如果安全虚拟机独占核心抖动应该没有明显变化。如果两个虚拟机共享核心抖动会显著增大。外设隔离的测试方法是在非安全虚拟机里配置一个直通外设的DMA让它往安全虚拟机的内存地址写数据。如果IOMMU配置正确DMA访问会被拦截如果IOMMU没配好数据就写进去了。这个测试能直接暴露IOMMU配置的遗漏。5. 踩过的坑与排查链路5.1 中断直通配好了但延迟反而变大有一次在项目里配置了中断直通理论上延迟应该降低但实测发现安全RTOS的任务响应时间反而比之前用Hypervisor转发时更大了。排查过程是这样的先确认中断确实直通了在Hypervisor的日志里看到中断没有被截获然后测量中断到任务唤醒的完整链路发现延迟主要发生在RTOS内部的中断处理程序里。进一步排查发现直通中断的优先级配置有问题。GIC的中断优先级分组在Hypervisor和RTOS之间的理解不一致导致RTOS把直通中断当成了低优先级中断被其他中断抢占。修复方法是在Hypervisor的设备树里显式指定直通中断的优先级并确保RTOS的中断控制器初始化代码读取的是Hypervisor配置后的优先级值而不是自己重新设置。这个坑的教训是中断直通不只是把中断号配过去就完了优先级、触发方式、目标核心这些属性都需要在Hypervisor和虚拟机之间对齐。任何一方擅自修改都可能导致行为异常。5.2 共享内存的缓存一致性导致数据错乱另一个项目里安全域和非安全域通过共享内存交换数据功能测试都通过了但在高温老化测试时出现了偶发的数据错乱。排查时先怀疑是内存位翻转加了ECC后问题依旧然后怀疑是双缓冲的序列号逻辑有race condition用逻辑分析仪抓了时序发现序列号更新和数据写入的顺序在某些情况下会颠倒。最终定位到的是缓存一致性问题。安全域和非安全域运行在不同的CPU核心上各自有本地缓存。共享内存区域如果没有配置成非缓存Non-cacheable或者没有做缓存维护操作一个核心写的数据可能停留在它的本地缓存里另一个核心读到的还是旧值。修复方法是在Hypervisor的Stage 2页表里把共享内存区域标记为Non-cacheable或者在写入后执行缓存清理操作。这个坑的隐蔽性在于常温下缓存行为比较规律问题不容易复现高温下缓存替换策略变化race condition才暴露出来。功能安全架构里共享内存的缓存属性必须在Hypervisor层面统一配置不能依赖虚拟机内部的驱动去维护。5.3 非安全虚拟机崩溃后安全域跟着挂这个问题最吓人。测试的时候故意让非安全虚拟机的内核崩溃结果安全域的任务也停了。排查链路先看Hypervisor日志发现非安全虚拟机崩溃后触发了一个全局异常Hypervisor在处理这个异常时进入了死循环然后看Hypervisor的异常处理代码发现它在终止非安全虚拟机之前先尝试回收该虚拟机占用的所有资源包括直通外设。回收过程中访问了一个已经被非安全虚拟机改坏的寄存器导致Hypervisor自身挂死。修复方案是调整Hypervisor的异常处理策略检测到虚拟机异常后先隔离该虚拟机的CPU核心停止它的所有活动然后再做资源回收。资源回收过程中对寄存器的访问要加超时和容错不能假设寄存器状态是合法的。这个问题的根本原因是Hypervisor的异常处理路径没有做充分的防御性设计。功能安全认证里Hypervisor本身也需要满足一定的安全完整性等级它的异常处理、资源管理、调度器都需要做失效模式分析。不能因为它是“特权层”就假设它不会出错。5.4 启动阶段的安全域就绪信号丢失前面提到过启动屏障的问题这里展开说一个更隐蔽的变种。Hypervisor配置了安全域先启动、非安全域等待就绪信号但偶尔会出现非安全域一直等不到信号的情况。排查发现就绪信号是通过共享内存的一个标志位传递的安全域写标志位后触发一个虚拟中断通知非安全域。问题出在虚拟中断的注入时机上安全域写标志位的时候非安全域的虚拟中断控制器还没初始化完中断被丢弃了。非安全域初始化完后去读标志位但标志位在共享内存里非安全域读的是缓存里的旧值。修复方法是双保险安全域写标志位后既发虚拟中断也确保标志位写入的是Non-cacheable内存非安全域初始化完后先主动读一次标志位再等中断。这样即使中断丢了轮询也能发现标志位已经置位。6. 认证材料里Hypervisor相关部分的准备要点功能安全认证不是只看最终产品过程文档和架构论证同样重要。Hypervisor相关的认证材料通常包括Hypervisor的安全手册、资源分配配置、FFI论证、以及Hypervisor自身的安全完整性等级证明。安全手册需要说明Hypervisor提供了哪些安全机制、这些机制的失效模式是什么、以及如何配置才能达到目标安全等级。比如Stage 2页表隔离手册里要写明隔离的粒度是4KB页配置错误可能导致隔离失效建议在启动时做页表完整性校验。FFI论证是重点。认证机构会要求你证明非安全域的故障不会通过共享资源传播到安全域。共享内存、中断、直通外设是三个主要的共享资源每一个都需要有对应的隔离机制和验证证据。共享内存的FFI论证需要包含双缓冲协议的正确性证明、序列号CRC的覆盖率分析、缓存一致性配置的验证记录。Hypervisor自身的安全完整性等级证明比较麻烦。如果Hypervisor是自研的需要提供它的开发过程符合相应安全等级的证据包括需求追溯、代码审查、单元测试、集成测试等。如果用的是第三方Hypervisor需要供应商提供安全手册和认证证书。选型的时候要确认供应商的认证范围是否覆盖你的目标等级和目标芯片。7. 一些实际项目中的经验判断Hypervisor在功能安全架构里的价值是明确的但它不是万能药。我个人的经验是如果安全域和非安全域之间的交互非常频繁比如每毫秒都要交换大量数据那Hypervisor的隔离开销和通信延迟可能会成为瓶颈。这种情况下更务实的做法可能是用双芯片方案或者在同一颗芯片上用硬件隔离比如TrustZone而不是Hypervisor。另一个判断点是团队的技术储备。Hypervisor的调试和优化需要比较深的底层功底包括ARM架构、中断控制器、内存管理、缓存一致性这些。如果团队里没有人熟悉这些贸然上Hypervisor可能会在集成阶段消耗大量时间。我建议先在开发板上跑通一个最小原型验证隔离效果和实时性再决定是否往产品方案里推。还有一个容易被忽略的点是工具链的支持。Hypervisor的调试通常需要JTAG加特定的感知插件能同时看到Hypervisor层和各个虚拟机的状态。如果工具链不支持排查跨层问题会非常痛苦。选型的时候要把调试工具的支持情况也纳入评估。最后说一个我踩过的坑Hypervisor的版本升级。开源Hypervisor的版本迭代比较快不同版本之间的配置格式和API可能有变化。功能安全项目一旦进入认证阶段Hypervisor的版本就应该冻结任何升级都需要重新做影响分析和回归测试。我见过一个项目在认证后期升级了Hypervisor版本结果中断直通的配置格式变了导致安全域的响应时间超标不得不回退版本重新认证。这个教训是在项目早期就锁定Hypervisor版本并把版本号写进配置管理文档里。
返回列表