HyperEnclave启动和初始化流程

概述

项目源码:https://github.com/asterinas/hyperenclave

最近在看蚂蚁的HyperEnclave,很好奇它是怎么实现的。其实HyperEnclave源码量并不多,非常方便我们去分析、学习。

本文将结合源码,分析HyperEnclave的启动和初始化流程。

启动整体流程图

HyperEnclave的入口是 main.rs文件中的entry函数。

核心函数分析

Entry函数 - 系统入口点

1
2
3
4
5
6
7
8
9
10
11
extern "sysv64" fn entry(cpu_id: usize, linux_sp: usize) -> i32 {
let mut code = 0;
if let Err(e) = main(cpu_id, linux_sp) {
error!("{:?}", e);
ERROR_NUM.store(e.code(), Ordering::Release);
code = e.code();
}
restore_states(cpu_id);
println!("CPU {} return back to driver with code {}.", cpu_id, code);
code
}

关键点:

  • 这是hypervisor的入口点,由内核驱动调用

  • 参数:cpu_id(CPU ID)和linux_sp(Linux栈指针)

  • 错误处理:如果main失败,记录错误代码

  • 状态恢复:如果main失败,还会调用restore_states,恢复Linux状态(关闭IOMMU等硬件资源)

参数说明

cpu_id - CPU标识符

含义:物理CPU核心的标识符,表示当前正在启动hypervisor的CPU核心编号。通常从0开始,到系统CPU核心数-1

在hypervisor中用途:

  • 用于标识每个CPU的PerCpu结构

  • 在多CPU系统中进行协调和同步

  • 确定哪个CPU是Primary CPU

linux_sp - Linux栈指针

含义:

  • Linux内核栈指针:保存Linux内核在调用hypervisor时的栈指针位置

  • 上下文保存:用于保存Linux的执行上下文,以便后续恢复

  • 栈切换:hypervisor需要切换到自己的栈,但需要记住Linux的栈位置

用途:保存Linux的执行上下文,确保hypervisor退出时能正确恢复Linux状态。

entry函数,确保了hypervisor能够安全地接管CPU控制权,同时也是hypervisor安全退出机制的关键组成部分,确保了即使在启动失败的情况下,系统也能安全地恢复到Linux状态。

Main函数 - 核心初始化逻辑

1
fn main(cpu_id: usize, linux_sp: usize) -> HvResult

参数说明:

  • cpu_id: CPU核心标识符

  • linux_sp: Linux栈指针,用于保存Linux执行上下文

  • 返回值:HvResult(成功为Ok(()),失败为Err(HvError))

第一步:CPU数据获取和同步

1
2
3
4
5
// 第1步:获取CPU数据和系统信息
let cpu_data = PerCpu::from_id_mut(cpu_id);
let online_cpus = HvHeader::get().online_cpus as usize;
let is_primary = ENTERED_CPUS.fetch_add(1, Ordering::SeqCst) == 0;
wait_for_other_completed(&ENTERED_CPUS, online_cpus)?;

详细分析:

  1. PerCpu::from_id_mut(cpu_id)
  • 获取当前CPU的PerCpu数据结构

  • 每个CPU都有独立的PerCpu实例

  1. HvHeader::get().online_cpus
  • 获取系统中在线CPU的总数

  • 用于后续的同步等待

  1. ENTERED_CPUS.fetch_add(1, Ordering::SeqCst) == 0
  • 原子操作,递增已进入的CPU计数

  • 第一个进入的CPU(返回0)被标记为Primary CPU

  • 其他CPU被标记为Secondary CPU

  1. wait_for_other_completed(&ENTERED_CPUS, online_cpus)
  • 等待所有CPU都进入hypervisor

  • 确保多CPU同步启动

第二步:早期初始化阶段

1
2
3
4
5
6
// 第2步:早期初始化阶段
if is_primary {
primary_init_early()?;
} else {
wait_for_other_completed(&INIT_EARLY_OK, 1)?;
}

Primary CPU执行primary_init_early()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn primary_init_early() -> HvResult {
logging::init(); // 1. 初始化日志系统
info!("Primary CPU init early...");
cpumask::check_max_cpus()?; // 2. 检查CPU数量限制

// 3. 打印系统配置信息
let system_config = HvSystemConfig::get();
println!("Initializing hypervisor...\n\
build_mode = {}\n\
log_level = {}\n\
arch = {}\n\
vendor = {}\n\
stats = {}\n\
sme = {}\n\
epc = {}\n",
// ... 配置信息
);

info!("Hypervisor header: {:#x?}", HvHeader::get());
debug!("System config: {:#x?}", system_config);

reclaim::init(); // 4. 初始化内存回收模块
memory::init()?; // 5. 初始化内存管理
cell::init()?; // 6. 初始化内存单元管理

INIT_EARLY_OK.store(1, Ordering::Release); // 7. 标记早期初始化完成
Ok(())
}

Secondary CPU等待早期初始化完成

1
wait_for_other_completed(&INIT_EARLY_OK, 1)?

第三步:CPU初始化

1
2
3
4
5
// 第3步:CPU初始化
cpu_data.init(cpu_id, linux_sp, &cell::ROOT_CELL)?;
println!("CPU {} init OK.", cpu_id);
INITED_CPUS.fetch_add(1, Ordering::SeqCst);
wait_for_other_completed(&INITED_CPUS, online_cpus)?;

详细分析:

  1. cpu_data.init(cpu_id, linux_sp, &cell::ROOT_CELL)
  • 初始化当前CPU的PerCpu结构

  • 设置CPU状态、保存Linux上下文

  • 初始化内存映射和VCPU

  1. INITED_CPUS.fetch_add(1, Ordering::SeqCst)
  • 原子递增已初始化的CPU计数
  1. wait_for_other_completed(&INITED_CPUS, online_cpus)
  • 等待所有CPU都完成初始化

第四步:晚期初始化阶段

1
2
3
4
5
// 第4步:晚期初始化阶段
if is_primary {
primary_init_late()?;
} else {
wa

primary_init_late()函数分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn primary_init_late() -> HvResult {
info!("Primary CPU init late...");

logging::hhbox_init()?; // 1. 初始化hypervisor日志系统

let (ra, rb, rc) = to_subversion(RUST_HYPERVISOR_VERSION);

// 2. 版本检查
unsafe {
if LIBTPM_VERSION != RUST_HYPERVISOR_VERSION {
let (ta, tb, tc) = to_subversion(LIBTPM_VERSION);
error!("Version mismatch. libtpm v{}.{}.{} v rust-hypervisor v{}.{}.{} ",
ta, tb, tc, ra, rb, rc);
return hv_result_err!(EINVAL);
}
}

println!("Rust-hypervisor (libtpm) version v{}.{}.{}", ra, rb, rc);

iommu::init()?; // 3. 初始化IOMMU
if !tc::tc_init() { // 4. 初始化TPM和加密模块
println!("HyperEnclave: tpm or cyrpto module initialization failed");
return hv_result_err!(EIO);
}

INIT_LATE_OK.store(1, Ordering::Release); // 5. 标记晚期初始化完成
Ok(())
}

第五步:激活VMM

1
2
// 第5步:激活VMM
cpu_data.activate_vmm()

activate_vmm()的作用:

  • 激活虚拟化硬件(Intel VMX或AMD SVM)

  • 切换到hypervisor模式

  • 开始处理虚拟化事件

底层实现机制

为什么看起来那么简单

我刚看的时候,感觉还是挺疑惑的,rust代码就这样就可以操作CPU了?其实刚刚我们看的Rust代码只是”胶水层”。实际上是有Rust代码,帮我们封装了 汇编指令与一些硬件虚拟化指令。

下面详细说下我们main函数入口,涉及的到的各种CPU操作底层是如何实现的。

CPU状态获取和同步操作

原子操作 - CPU计数

1
2
3
// src/main.rs:171-172
let is_primary = ENTERED_CPUS.fetch_add(1, Ordering::SeqCst) == 0;
wait_for_other_completed(&ENTERED_CPUS, online_cpus)?;

底层实现:

1
2
3
// 原子操作底层是CPU的LOCK前缀指令
// fetch_add 展开为类似:
asm!("lock add {}, {}", in(reg) value, in(reg) addr);

CPU ID获取

1
2
// src/main.rs:169
let cpu_data = PerCpu::from_id_mut(cpu_id);

底层实现:

1
2
3
4
5
6
7
8
9
// src/percpu.rs:65-72
pub fn from_id_mut<'a>(cpu_id: usize) -> &'a mut Self {
unsafe {
&mut core::slice::from_raw_parts_mut(
PER_CPU_ARRAY_PTR, // 全局PerCpu数组指针
HvHeader::get().max_cpus as usize,
)[cpu_id]
}
}

内存映射和页表操作

内存映射设置

1
2
3
4
5
6
7
8
9
// src/percpu.rs:100-115
let vaddr = self as *const _ as usize;
let paddr = virt_to_phys(vaddr);
hvm.insert(MemoryRegion::new_with_offset_mapper(
vaddr,
paddr,
PER_CPU_SIZE,
MemFlags::READ | MemFlags::WRITE | MemFlags::ENCRYPTED,
))?;

底层实现:

1
2
3
4
5
6
// 涉及页表操作,最终会执行:
// 1. 读取CR3寄存器获取页表基址
// 2. 修改页表项
// 3. 刷新TLB
asm!("mov rax, cr3"); // 读取页表基址
asm!("invlpg [{}]", in(reg) addr); // 刷新TLB

栈切换操作

1
2
// src/percpu.rs:152
unsafe { asm!("add rsp, {}", in(reg) LOCAL_PER_CPU_BASE - old_percpu_vaddr) };

底层实现:

1
2
// 直接操作栈指针寄存器
asm!("add rsp, {}", in(reg) offset);

寄存器保存和恢复操作

Linux上下文保存

1
2
3
4
5
6
7
8
// src/arch/x86_64/context.rs:128-135
pub fn load_from(linux_sp: usize) -> Self {
let regs = unsafe { core::slice::from_raw_parts(linux_sp as *const u64, SAVED_LINUX_REGS) };
let gdt = GDTStruct::sgdt(); // 获取GDT
let mut fs = Segment::from_selector(segmentation::fs(), &gdt);
let mut gs = Segment::from_selector(segmentation::gs(), &gdt);
fs.base = Msr::IA32_FS_BASE.read(); // 读取MSR
gs.base = Msr::IA32_GS_BASE.read();

底层实现:

1
2
3
4
5
6
7
8
9
10
// 读取MSR寄存器
asm!("rdmsr", in("ecx") 0xc0000100, out("eax") low, out("edx") high); // IA32_FS_BASE
asm!("rdmsr", in("ecx") 0xc0000101, out("eax") low, out("edx") high); // IA32_GS_BASE

// 读取段寄存器
asm!("mov rax, fs"); // 读取FS段寄存器
asm!("mov rax, gs"); // 读取GS段寄存器

// 读取GDT
asm!("sgdt [{}]", in(reg) &gdt_ptr);

控制寄存器操作

1
2
3
4
// src/arch/x86_64/context.rs:152-154
cr0: Cr0::read(),
cr3: Cr3::read().0.start_address().as_u64(),
cr4: Cr4::read(),

底层实现:

1
2
3
4
// 读取控制寄存器
asm!("mov rax, cr0"); // 读取CR0
asm!("mov rax, cr3"); // 读取CR3
asm!("mov rax, cr4"); // 读取CR4

VMCS(Virtual Machine Control Structure)设置

VMCS初始化

1
2
3
4
5
6
7
8
9
10
// src/arch/x86_64/intel/vcpu.rs:198-205
fn vmcs_setup(&mut self, linux: &LinuxContext, cell: &Cell) -> HvResult {
let paddr = self.vmcs_region.paddr();
Vmcs::clear(paddr)?; // 清除VMCS
Vmcs::load(paddr)?; // 加载VMCS
self.setup_vmcs_host()?; // 设置Host状态
self.setup_vmcs_guest(linux)?; // 设置Guest状态
self.setup_vmcs_control(cell)?; // 设置控制字段
Ok(())
}

底层实现:

1
2
3
// Intel VMX指令
asm!("vmclear [{}]", in(reg) paddr); // 清除VMCS
asm!("vmptrld [{}]", in(reg) paddr); // 加载VMCS

Host状态设置

1
2
3
4
5
6
7
8
9
// src/arch/x86_64/intel/vcpu.rs:207-235
fn setup_vmcs_host(&mut self) -> HvResult {
VmcsField64Host::IA32_PAT.write(Msr::IA32_PAT.read())?;
VmcsField64Host::IA32_EFER.write(Msr::IA32_EFER.read())?;
VmcsField64Host::CR0.write(Cr0::read_raw())?;
VmcsField64Host::CR3.write(Cr3::read().0.start_address().as_u64())?;
VmcsField64Host::CR4.write(Cr4::read_raw())?;
// ... 其他寄存器设置
}

底层实现:

1
2
3
4
5
6
7
8
9
10
11
// 读取MSR寄存器
asm!("rdmsr", in("ecx") 0x277, out("eax") low, out("edx") high); // IA32_PAT
asm!("rdmsr", in("ecx") 0xc0000080, out("eax") low, out("edx") high); // IA32_EFER

// 读取控制寄存器
asm!("mov rax, cr0");
asm!("mov rax, cr3");
asm!("mov rax, cr4");

// 写入VMCS字段
asm!("vmwrite {}, {}", in(reg) field_id, in(reg) value);

Guest状态设置

1
2
3
4
5
6
7
8
9
10
11
12
// src/arch/x86_64/intel/vcpu.rs:247-289
fn setup_vmcs_guest(&mut self, linux: &LinuxContext) -> HvResult {
VmcsField64Guest::IA32_PAT.write(linux.pat)?;
VmcsField64Guest::IA32_EFER.write(linux.efer)?;
self.set_cr(0, linux.cr0.bits());
self.set_cr(4, linux.cr4.bits());
self.set_cr(3, linux.cr3);
// ... 段寄存器设置
VmcsField64Guest::RSP.write(linux.rsp)?;
VmcsField64Guest::RIP.write(linux.rip)?;
VmcsField64Guest::RFLAGS.write(0x2)?;
}

底层实现:

1
2
3
4
5
// 写入VMCS Guest字段
asm!("vmwrite {}, {}", in(reg) field_id, in(reg) value);

// 设置段寄存器
set_guest_segment!(linux.cs, CS); // 展开为VMCS字段写入

虚拟化启动操作

VCPU激活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/arch/x86_64/intel/vcpu.rs:136-155
pub fn activate_vmm(&mut self, linux: &LinuxContext) -> HvResult {
let regs = self.regs_mut();
regs.rax = 0;
regs.rbx = linux.rbx;
regs.rbp = linux.rbp;
regs.r12 = linux.r12;
regs.r13 = linux.r13;
regs.r14 = linux.r14;
regs.r15 = linux.r15;
unsafe {
asm!(
"mov rsp, {0}",
restore_regs_from_stack!(),
"vmlaunch", // 关键:Intel VMX指令
in(reg) &self.guest_regs as *const _ as usize,
);
}
}

底层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 设置寄存器
asm!("mov rax, {}", in(reg) 0);
asm!("mov rbx, {}", in(reg) linux.rbx);
asm!("mov rbp, {}", in(reg) linux.rbp);
// ... 其他寄存器

// 设置栈指针
asm!("mov rsp, {}", in(reg) stack_ptr);

// 恢复寄存器(从栈上)
asm!("pop rax");
asm!("pop rcx");
asm!("pop rdx");
// ... 其他寄存器

// 启动虚拟化
asm!("vmlaunch"); // Intel VMX指令,启动虚拟化

中断和异常处理设置

中断描述符表设置

1
2
3
4
5
6
7
8
9
// src/arch/x86_64/context.rs:168-171
GDT.lock().load();
unsafe {
segmentation::load_cs(GDTStruct::KCODE_SELECTOR);
segmentation::load_ds(SegmentSelector::from_raw(0));
segmentation::load_es(SegmentSelector::from_raw(0));
segmentation::load_ss(SegmentSelector::from_raw(0));
}
IDT.lock().load();

底层实现:

1
2
3
4
5
6
7
8
9
10
11
// 加载GDT
asm!("lgdt [{}]", in(reg) &gdt_ptr);

// 加载段寄存器
asm!("mov cs, {}", in(reg) selector);
asm!("mov ds, {}", in(reg) selector);
asm!("mov es, {}", in(reg) selector);
asm!("mov ss, {}", in(reg) selector);

// 加载IDT
asm!("lidt [{}]", in(reg) &idt_ptr);

状态恢复操作

寄存器恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/arch/x86_64/context.rs:175-225
pub fn restore(&self) {
unsafe {
// 恢复MSR寄存器
Msr::IA32_PAT.write(self.pat);
Msr::IA32_EFER.write(self.efer);
Msr::IA32_KERNEL_GSBASE.write(self.kernel_gsbase);
// ... 其他MSR

// 恢复控制寄存器
Cr0::write(self.cr0);
Cr4::write(self.cr4);
Cr3::write(PhysFrame::containing_address(PhysAddr::new(self.cr3)), Cr3Flags::empty());

// 恢复段寄存器
segmentation::load_cs(self.cs.selector);
segmentation::load_ds(self.ds.selector);
segmentation::load_es(self.es.selector);
segmentation::load_fs(self.fs.selector);
segmentation::load_gs(self.gs.selector);

// 恢复段基址
Msr::IA32_FS_BASE.write(self.fs.base);
Msr::IA32_GS_BASE.write(self.gs.base);
}
}

底层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 写入MSR寄存器
asm!("wrmsr", in("ecx") 0x277, in("eax") low, in("edx") high); // IA32_PAT
asm!("wrmsr", in("ecx") 0xc0000080, in("eax") low, in("edx") high); // IA32_EFER

// 写入控制寄存器
asm!("mov cr0, rax");
asm!("mov cr4, rax");
asm!("mov cr3, rax");

// 加载段寄存器
asm!("mov cs, {}", in(reg) selector);
asm!("mov ds, {}", in(reg) selector);
asm!("mov es, {}", in(reg) selector);
asm!("mov fs, {}", in(reg) selector);
asm!("mov gs, {}", in(reg) selector);

底层实现依赖总结

x86架构指令集

  • MSR操作rdmsr/wrmsr 指令
  • 控制寄存器mov rax, cr0/mov cr0, rax
  • 段寄存器mov cs, selector
  • 页表操作mov rax, cr3invlpg

Intel VMX虚拟化指令

  • VMCS操作vmclearvmptrldvmwritevmread
  • 虚拟化启动vmlaunchvmresume
  • 虚拟化退出vmxoff

原子操作指令

  • LOCK前缀lock addlock xchg
  • 内存屏障mfencesfencelfence

内存管理指令

  • 页表操作invlpg(刷新TLB)
  • 内存访问:直接内存读写操作

中断和异常处理

  • 描述符表lgdtlidt 指令
  • 中断控制clisti 指令

这些操作都依赖于Intel x86架构的硬件支持,通过Rust的asm!宏直接执行汇编指令来实现对CPU的底层控制。