Jailhouse BSP和AP Entry设置机制详解

基于我对代码库的深入分析,现在我可以详细解释BSP和AP的entry设置机制以及代码加载过程:

BSP和AP Entry设置机制详解

1. BSP (Bootstrap Processor) Entry设置

A. 配置文件中的重置地址

// configs/x86/linux-x86-demo.c
struct {
    struct jailhouse_cell_desc cell;
    // ...
} config = {
    .cell = {
        .cpu_reset_address = 0x0,  // x86平台默认为0
        // ...
    },
};

B. Hypervisor设置BSP的Entry Point

// hypervisor/arch/x86/vmx.c - vcpu_vendor_reset函数
void vcpu_vendor_reset(unsigned int sipi_vector)
{
    unsigned long reset_addr, val;
    bool ok = true;

    /* ... 其他重置代码 ... */

    if (sipi_vector == APIC_BSP_PSEUDO_SIPI) {
        /* BSP重置:使用配置文件中的cpu_reset_address */
        reset_addr = this_cell()->config->cpu_reset_address;

        /* 设置Guest RIP为重置地址的低16位 */
        ok &= vmcs_write64(GUEST_RIP, reset_addr & 0xffff);

        /* 设置CS段选择子和基址(实模式格式) */
        ok &= vmcs_write16(GUEST_CS_SELECTOR,
                           (reset_addr >> 4) & 0xf000);
        ok &= vmcs_write64(GUEST_CS_BASE, reset_addr & ~0xffffL);
    } else {
        /* AP重置:从SIPI向量开始执行 */
        ok &= vmcs_write64(GUEST_RIP, 0);
        ok &= vmcs_write16(GUEST_CS_SELECTOR, sipi_vector << 8);
        ok &= vmcs_write64(GUEST_CS_BASE, sipi_vector << 12);
    }

    /* 设置实模式环境 */
    ok &= vmcs_write32(GUEST_CS_LIMIT, 0xffff);
    ok &= vmcs_write32(GUEST_CS_AR_BYTES, 0x0009b);
    /* ... */
}

C. BSP启动流程

BSP启动地址: 0x0 (cpu_reset_address)
    
inmates/lib/x86/header-common.S:__reset_entry (地址0x0)
    
实模式  保护模式切换
    
inmates/lib/x86/header-64.S:start32 (32位代码)
    
启用分页和长模式
    
inmates/lib/x86/header-64.S:start64 (64位代码)
    
inmates/lib/setup.c:c_entry()
    
inmates/tools/x86/linux-loader.c:inmate_main()

2. 代码加载机制

A. Inmate镜像的内存布局

// inmates/lib/x86/inmate.lds - 链接器脚本
SECTIONS
{
    . = 0;                    // 从地址0开始
    .boot : {
        *(.boot.entry)        // __reset_entry放在最开始
        *(.boot)              // 其他启动代码
    }

    . = 0x1000;              // 0x1000处放置命令行
    .cmdline : {
        *(.cmdline)
    }

    .text : {                // 主要代码段
        *(.text)
    }
    // ...
}

ENTRY(__reset_entry)         // 入口点是__reset_entry

B. 用户空间加载过程

// tools/jailhouse.c - cell_shutdown_load函数
static int cell_shutdown_load(int argc, char *argv[], enum shutdown_load_mode mode)
{
    // 读取inmate镜像文件
    image->source_address = (unsigned long)read_file(argv[arg_num++], &size);
    image->size = size;
    image->target_address = 0;  // 默认加载到地址0

    // 通过ioctl加载到cell内存
    err = ioctl(fd, JAILHOUSE_CELL_LOAD, cell_load);
}

C. 实际的启动代码分析

# inmates/lib/x86/header-common.S
.code16
.section ".boot.entry", "ax"

.globl __reset_entry
__reset_entry:
    lgdtl %cs:gdt_ptr        # 加载GDT
    mov %cr0, %eax
    or $X86_CR0_PE, %al      # 启用保护模式
    mov %eax, %cr0
    ljmpl $INMATE_CS32, $start32  # 跳转到32位代码

# inmates/lib/x86/header-64.S  
.code32
.section ".boot", "ax"
.globl start32
start32:
    # 设置PAE和长模式
    mov %cr4,%eax
    or $X86_CR4_PAE,%eax
    mov %eax,%cr4

    mov $pml4,%eax           # 设置页表
    mov %eax,%cr3

    # 启用长模式
    movl $MSR_EFER,%ecx
    rdmsr
    or $EFER_LME,%eax
    wrmsr

    # 启用分页
    mov $(X86_CR0_PG | X86_CR0_WP | X86_CR0_PE),%eax
    mov %eax,%cr0

    ljmpl $INMATE_CS64,$start64  # 跳转到64位代码

.code64
start64:
    # 检查是否为AP启动
    xor %rbx,%rbx
    xchg ap_entry,%rbx       # 检查ap_entry变量
    or %rbx,%rbx
    jnz call_entry           # 如果是AP,直接调用entry

    # BSP初始化
    mov $1,%edi
    lock xadd %edi,cpu_number  # 原子递增CPU计数

    # 获取APIC ID并记录
    mov $X86_CPUID_FEATURES, %eax
    cpuid
    shr $24,%ebx
    mov %bl,smp_cpu_ids(%edi)  # 记录CPU ID

    # 只有CPU 0执行主初始化
    cmp $0,%edi
    jne stop

    # 清零BSS段
    xor %rax,%rax
    mov $bss_start,%rdi
    mov $bss_qwords,%rcx
    rep stosq

    mov $c_entry,%rbx        # 设置C入口点

call_entry:
    xor %rsp, %rsp
    xchg stack, %rsp         # 设置栈指针
    call arch_init_features
    callq *%rbx             # 调用C代码入口

3. AP (Application Processor) Entry设置

A. AP启动机制

// inmates/lib/x86/smp.c
void smp_start_cpu(unsigned int cpu_id, void (*entry)(void))
{
    u64 base_val = ((u64)cpu_id << 32) | APIC_LVL_ASSERT;

    ap_entry = entry;        // 设置AP的入口函数
    stack = zalloc(PAGE_SIZE, PAGE_SIZE) + PAGE_SIZE;  // 分配栈

    // 发送INIT信号
    write_msr(X2APIC_ICR, base_val | APIC_DM_INIT);
    delay_us(10000);

    // 发送SIPI信号(两次,符合Intel规范)
    write_msr(X2APIC_ICR, base_val | APIC_DM_SIPI);
    delay_us(200);
    write_msr(X2APIC_ICR, base_val | APIC_DM_SIPI);

    // 等待AP启动完成
    while (ap_entry && stack)
        cpu_relax();
}

B. AP的启动流程

AP收到SIPI信号
    ↓
从SIPI向量地址开始执行 (通常是0x0)
    ↓
inmates/lib/x86/header-common.S:__reset_entry
    ↓
实模式 → 保护模式 → 长模式切换
    ↓
inmates/lib/x86/header-64.S:start64
    ↓
检查ap_entry变量 (非空)
    ↓
直接跳转到call_entry
    ↓
执行ap_entry指向的函数

C. AP和BSP的区别

// inmates/lib/x86/header-64.S中的关键区别
start64:
    xor %rbx,%rbx
    xchg ap_entry,%rbx       // 原子交换ap_entry
    or %rbx,%rbx
    jnz call_entry           // 如果ap_entry非空,说明是AP

    // 以下代码只有BSP执行
    mov $1,%edi
    lock xadd %edi,cpu_number

    cmp $0,%edi              // 只有第一个CPU (BSP)
    jne stop                 // 其他CPU停止

    // BSS清零等初始化工作
    mov $c_entry,%rbx        // BSP使用c_entry

call_entry:
    // AP使用ap_entry,BSP使用c_entry
    callq *%rbx

4. 关键数据结构和变量

A. 全局变量

// inmates/lib/x86/header-64.S
.globl ap_entry
ap_entry:
    .quad   0                // AP入口函数指针

.globl smp_num_cpus
smp_num_cpus:
    .long   0                // 当前CPU数量

.globl smp_cpu_ids
smp_cpu_ids:
    .fill   SMP_MAX_CPUS, 1, 0  // CPU ID数组

B. 内存映射

物理地址0x0:     __reset_entry (所有CPU的启动点)
物理地址0x1000:  cmdline区域
物理地址0x2000+: 主要代码和数据
栈顶:           动态分配的栈空间

5. 完整的启动时序

sequenceDiagram
    participant HV as Hypervisor
    participant BSP as Bootstrap CPU
    participant AP as Application CPU
    participant Code as Inmate Code

    Note over HV: 1. 加载inmate镜像到cell内存地址0x0
    HV->>BSP: arch_reset_cpu() - 设置RIP=0
    BSP->>Code: 0x0开始执行__reset_entry
    Code->>BSP: 实模式→保护模式→长模式切换
    Code->>BSP: 检查ap_entry=NULL,执行BSP初始化
    Code->>BSP: 调用c_entry()  inmate_main()

    BSP->>AP: smp_start_cpu() - 设置ap_entry
    BSP->>AP: 发送INIT/SIPI信号
    AP->>Code: 0x0开始执行__reset_entry  
    Code->>AP: 实模式→保护模式→长模式切换
    Code->>AP: 检查ap_entry!=NULL,跳过BSP初始化
    Code->>AP: 调用ap_entry指向的函数

补充:vcpu_vendor_reset函数的完整调用链

基于对实际代码的分析,vcpu_vendor_reset函数的调用链如下:

A. BSP启动时的调用链

用户命令: jailhouse cell start cell_id
    
tools/jailhouse.c:cell_simple_cmd()
    
ioctl(fd, JAILHOUSE_CELL_START, &cell_id)
    
driver/main.c:jailhouse_ioctl()
    
driver/cell.c:jailhouse_cmd_cell_start()
    
hypercall(JAILHOUSE_HC_CELL_START, cell_id, 0)
    
hypervisor/control.c:hypercall()
    
hypervisor/control.c:cell_start()
    
hypervisor/arch/x86/control.c:arch_reset_cpu()
    
hypervisor/control.c:resume_cpu()
    
[CPU开始执行,触发VM Exit处理]
    
hypervisor/arch/x86/control.c:x86_check_events()
    
hypervisor/arch/x86/vcpu.c:vcpu_reset()
    
hypervisor/arch/x86/vmx.c:vcpu_vendor_reset()

B. AP启动时的调用链

BSP执行: inmates/lib/x86/smp.c:smp_start_cpu()
    
write_msr(X2APIC_ICR, INIT信号)
    
write_msr(X2APIC_ICR, SIPI信号)
    
[目标CPU收到SIPI,触发VM Exit]
    
hypervisor/arch/x86/control.c:x86_check_events()
    
hypervisor/arch/x86/vcpu.c:vcpu_reset()
    
hypervisor/arch/x86/vmx.c:vcpu_vendor_reset()

C. 关键函数实现分析

1. arch_reset_cpu函数

// hypervisor/arch/x86/control.c
void arch_reset_cpu(unsigned int cpu_id)
{
    // 设置SIPI向量为BSP伪SIPI
    public_per_cpu(cpu_id)->sipi_vector = APIC_BSP_PSEUDO_SIPI;

    // 恢复CPU执行,触发事件检查
    resume_cpu(cpu_id);
}

2. resume_cpu函数

// hypervisor/control.c
void resume_cpu(unsigned int cpu_id)
{
    struct public_per_cpu *target_data = public_per_cpu(cpu_id);

    if (target_data->suspend_cpu) {
        target_data->suspend_cpu = false;
        // 发送NMI唤醒目标CPU
        apic_send_nmi_ipi(target_data);
    }
}

3. x86_check_events函数

// hypervisor/arch/x86/control.c
void x86_check_events(void)
{
    struct public_per_cpu *cpu_public = this_cpu_public();
    int sipi_vector = -1;

    spin_lock(&cpu_public->control_lock);

    // 检查SIPI向量
    if (cpu_public->sipi_vector >= 0) {
        if (!cpu_public->failed) {
            cpu_public->wait_for_sipi = false;
            sipi_vector = cpu_public->sipi_vector;
        }
        cpu_public->sipi_vector = -1;
    }

    spin_unlock(&cpu_public->control_lock);

    // 如果收到SIPI信号,重置CPU
    if (sipi_vector >= 0) {
        printk("CPU %d received SIPI, vector %x\n", this_cpu_id(), sipi_vector);
        apic_clear();
        vcpu_reset(sipi_vector);  // 调用vcpu_reset
    }
}

4. vcpu_reset函数

// hypervisor/arch/x86/vcpu.c
void vcpu_reset(unsigned int sipi_vector)
{
    struct per_cpu *cpu_data = this_cpu_data();

    // 调用厂商特定的重置函数
    vcpu_vendor_reset(sipi_vector);

    // 清零guest寄存器
    memset(&cpu_data->guest_regs, 0, sizeof(cpu_data->guest_regs));

    // 如果是BSP,初始化PAT和MTRR
    if (sipi_vector == APIC_BSP_PSEUDO_SIPI) {
        cpu_data->pat = PAT_RESET_VALUE;
        cpu_data->mtrr_def_type &= ~MTRR_ENABLE;
        vcpu_vendor_set_guest_pat(0);
    }
}

D. BSP vs AP的区别

BSP启动流程:

  1. arch_reset_cpu()设置sipi_vector = APIC_BSP_PSEUDO_SIPI
  2. vcpu_vendor_reset(APIC_BSP_PSEUDO_SIPI)被调用
  3. 在VMX中,BSP使用cpu_reset_address作为起始地址
  4. 设置实模式环境,从地址0开始执行

AP启动流程:

  1. BSP发送真实的SIPI信号
  2. x86_check_events()检测到SIPI向量
  3. vcpu_vendor_reset(sipi_vector)被调用,其中sipi_vector是实际的SIPI向量
  4. 在VMX中,AP使用SIPI向量计算起始地址
  5. 同样设置实模式环境,但从SIPI向量地址开始执行

E. 时序对比

BSP启动时序:

用户命令 → cell_start → arch_reset_cpu → resume_cpu → 
x86_check_events → vcpu_reset → vcpu_vendor_reset(APIC_BSP_PSEUDO_SIPI)

AP启动时序:

BSP发送SIPI → 目标CPU收到中断 → x86_check_events → 
vcpu_reset → vcpu_vendor_reset(实际SIPI向量)

总结

  1. BSP Entry设置:通过cpu_reset_address配置(x86默认为0),hypervisor设置GUEST_RIP为该地址
  2. 代码预加载:用户空间通过JAILHOUSE_CELL_LOAD ioctl将inmate镜像加载到cell内存的地址0
  3. AP Entry设置:AP通过SIPI信号启动,同样从地址0开始,但通过ap_entry变量区分执行路径
  4. 统一入口:所有CPU都从__reset_entry开始,但通过运行时检查决定不同的执行路径
  5. 调用链验证vcpu_vendor_reset通过完整的事件处理机制被调用,确保CPU状态正确设置

这种设计实现了代码复用,同时保证了BSP和AP的正确初始化顺序。