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启动流程:
arch_reset_cpu()
设置sipi_vector = APIC_BSP_PSEUDO_SIPI
vcpu_vendor_reset(APIC_BSP_PSEUDO_SIPI)
被调用- 在VMX中,BSP使用
cpu_reset_address
作为起始地址 - 设置实模式环境,从地址0开始执行
AP启动流程:
- BSP发送真实的SIPI信号
x86_check_events()
检测到SIPI向量vcpu_vendor_reset(sipi_vector)
被调用,其中sipi_vector
是实际的SIPI向量- 在VMX中,AP使用SIPI向量计算起始地址
- 同样设置实模式环境,但从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向量)
总结
- BSP Entry设置:通过
cpu_reset_address
配置(x86默认为0),hypervisor设置GUEST_RIP为该地址 - 代码预加载:用户空间通过
JAILHOUSE_CELL_LOAD
ioctl将inmate镜像加载到cell内存的地址0 - AP Entry设置:AP通过SIPI信号启动,同样从地址0开始,但通过
ap_entry
变量区分执行路径 - 统一入口:所有CPU都从
__reset_entry
开始,但通过运行时检查决定不同的执行路径 - 调用链验证:
vcpu_vendor_reset
通过完整的事件处理机制被调用,确保CPU状态正确设置
这种设计实现了代码复用,同时保证了BSP和AP的正确初始化顺序。
评论