树莓派3B多核内核初始化调试:仅主核运行问题排查
kernel_init 我正在为树莓派3B开发一款可在四核上运行基础任务的简单内核,但目前仅主核在运行kernel_init,其余核心始终未启动。UART输出仅显示processor ID: 0,次级核心专属代码似乎从未运行。预期所有核心正常运行时,输出应包含所有核心的ID(顺序随机)。
相关代码片段
启动汇编代码
.globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, el1_entry // Hang for all non-primary CPU b proc_hang proc_hang: wfe b proc_hang el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY // Initialize and start secondary cores on Raspberry Pi 3 ldr x1, =start_core_1 // Address to jump to for secondary cores ldr x2, =0xE0 // Mailbox register for Core 1 str x1, [x2] ldr x1, =start_core_2 // Address to jump to for secondary cores ldr x2, =0xE8 // Mailbox register for Core 2 str x1, [x2] ldr x1, =start_core_3 // Address to jump to for secondary cores ldr x2, =0xF0 // Mailbox register for Core 3 str x1, [x2] sev // Send event to wake up all cores bl kernel_init b proc_hang // should never come here start_core_1: ldr x1, =LOW_MEMORY ldr x2, =PAGE_SIZE add sp, x1, x2 // sp = LOW_MEMORY + PAGE_SIZE bl kernel_init b start_core_1 start_core_2: ldr x1, =LOW_MEMORY ldr x2, =PAGE_SIZE add sp, x1, x2, lsl #1 // sp = LOW_MEMORY + 2*PAGE_SIZE bl kernel_init b start_core_2 start_core_3: ldr x1, =LOW_MEMORY ldr x2, =PAGE_SIZE mov x3, #3 mul x2, x2, x3 // x2 = 3 * PAGE_SIZE add sp, x1, x2 // sp = LOW_MEMORY + 3*PAGE_SIZE bl kernel_init b start_core_3
kernel_init C代码
extern "C" void kernel_init(void) { uart_init(); init_printf(0, putc); printf("processor ID: %d\n", get_core_number()); while (1) { uart_send(uart_recv()); } }
get_core_number 汇编代码
.global get_core_number get_core_number: mrs x0, MPIDR_EL1 // Read MPIDR_EL1 into x0 and x0, x0, #0xff // Check processor id ret // Return, with the result in x0
已尝试的操作
- 为每个核心设置不同的栈指针,但仍只有主核活跃。
- 使用无核区分逻辑的简化启动代码(如下),输出仍仅显示
processor ID: 0:
_start: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_init
邮箱寄存器地址错误:树莓派3B的次级核心唤醒邮箱寄存器是物理地址,你当前使用的
0xE0、0xE8、0xF0是虚拟地址偏移,需要映射到物理地址(正确地址为0x400000E0、0x400000E8、0x400000F0)。次级核心启动时处于物理地址空间,无法直接访问虚拟地址,必须写入物理地址的邮箱寄存器。MPIDR_EL1核ID提取错误:ARMv8的MPIDR_EL1中,核心ID仅位于低4位(
[3:0]),你当前用and x0, x0, #0xff提取的是包含集群ID的低8位,会导致核ID判断错误。正确提取方式应为and x0, x0, #0xF。内存屏障缺失:主核写入邮箱后,需要添加
dsb sy(数据同步屏障)确保内存操作完成,再执行sev发送唤醒事件,同时添加isb(指令同步屏障)保证指令顺序执行,否则次级核心可能无法看到邮箱中的启动地址。UART初始化竞态:所有核心重复调用
uart_init()会导致硬件初始化冲突,应让主核单独完成UART初始化,次级核心直接复用已初始化的硬件,避免异常。次级核心启动状态校验:次级核心上电后默认处于WFE状态,主核发送
sev前,需确保次级核心已经进入WFE循环,可通过延迟或重复触发sev保证唤醒效果。
修改后的关键代码示例
启动汇编核心部分
el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY // 写入物理地址的邮箱寄存器 ldr x1, =start_core_1 ldr x2, =0x400000E0 // Core 1 物理邮箱地址 str x1, [x2] dsb sy // 确保内存写入完成 ldr x1, =start_core_2 ldr x2, =0x400000E8 // Core 2 物理邮箱地址 str x1, [x2] dsb sy ldr x1, =start_core_3 ldr x2, =0x400000F0 // Core 3 物理邮箱地址 str x1, [x2] dsb sy sev // 唤醒所有核心 isb // 确保指令同步 bl kernel_init b proc_hang
修改后的get_core_number
.global get_core_number get_core_number: mrs x0, MPIDR_EL1 and x0, x0, #0xF // 仅提取低4位的核心ID ret
调整后的kernel_init
extern "C" void kernel_init(void) { int core_id = get_core_number(); if (core_id == 0) { uart_init(); init_printf(0, putc); } // 内存屏障确保UART初始化完成后次级核心再输出 dsb sy; isb; printf("processor ID: %d\n", core_id); while (1) { uart_send(uart_recv()); } }
内容的提问来源于stack exchange,提问作者Diego A Wearden

