You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派3B多核内核初始化调试:仅主核运行问题排查

树莓派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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 18:45:03