2. 多核共享内存初始化

任务目标

本案例要求编写RISC-V多核启动代码,完成共享内存区域的初始化,并确保各核缓存一致性协议在初始状态下处于正确配置。具体包括:定义共享内存区域、实现核间同步屏障、初始化缓存一致性相关CSR,以及验证各核对共享内存的访问一致性。

必要环境与依赖

  • RISC-V 多核处理器(支持Sv39分页与缓存一致性,如 Rocket Chip 或 支持多核的 QEMU 7.2+)
  • GCC 交叉编译器:riscv64-unknown-elf-gcc
  • OpenSBI(提供多核启动入口,或使用自定义 bootloader)

核心实现

以下代码实现多核启动流程:核0初始化共享内存并设置一致性CSR,其他核等待屏障后访问共享内存。关键点在于使用 fence.i 和 fence rw,rw 保证指令与数据同步,并通过原子操作实现自旋锁屏障。

// multi_core_init.c
#include <stdint.h>
#include <stdatomic.h>

#define NUM_CORES 4
#define SHARED_SIZE 1024

// 共享内存区域(按缓存行对齐,避免伪共享)
volatile uint8_t shared_mem[SHARED_SIZE] __attribute__((aligned(64)));
volatile atomic_int barrier_count = 0;
volatile atomic_int spin_lock = 0;

// CSR 地址(RISC-V 标准)
#define CSR_MSTATUS 0x300
#define CSR_MIE     0x304
#define CSR_MIP     0x344

static inline void csr_write(uint64_t csr, uint64_t val) {
    __asm__ volatile("csrw %0, %1" : : "i"(csr), "r"(val));
}

static inline uint64_t csr_read(uint64_t csr) {
    uint64_t val;
    __asm__ volatile("csrr %0, %1" : "=r"(val) : "i"(csr));
    return val;
}

// 获取当前核ID(假设通过 mhartid CSR)
static inline uint32_t get_core_id(void) {
    uint32_t hartid;
    __asm__ volatile("csrr %0, mhartid" : "=r"(hartid));
    return hartid;
}

// 缓存一致性初始化:使能全局中断,设置内存属性
static void cache_coherence_init(void) {
    // 设置 MSTATUS.MIE = 1(全局中断使能)
    uint64_t mstatus = csr_read(CSR_MSTATUS);
    mstatus |= (1 << 3);  // MIE bit
    csr_write(CSR_MSTATUS, mstatus);

    // 确保所有缓存操作完成
    __asm__ volatile("fence rw, rw" ::: "memory");
}

// 屏障:等待所有核到达
static void barrier_wait(void) {
    __atomic_fetch_add(&barrier_count, 1, __ATOMIC_SEQ_CST);
    while (__atomic_load_n(&barrier_count, __ATOMIC_SEQ_CST) < NUM_CORES) {
        // 自旋等待
        __asm__ volatile("pause" ::: "memory");
    }
    // 确保所有核看到一致的 barrier_count
    __atomic_thread_fence(__ATOMIC_SEQ_CST);
}

// 共享内存初始化(仅核0执行)
static void init_shared_memory(void) {
    for (int i = 0; i < SHARED_SIZE; i++) {
        shared_mem[i] = (uint8_t)(i & 0xFF);
    }
    // 写屏障:确保数据写入完成
    __asm__ volatile("fence rw, rw" ::: "memory");
}

// 核主函数
void main(void) {
    uint32_t core_id = get_core_id();

    // 核0:初始化共享内存
    if (core_id == 0) {
        init_shared_memory();
        // 设置 barrier_count 初始值
        __atomic_store_n(&barrier_count, 0, __ATOMIC_SEQ_CST);
    }

    // 所有核执行缓存一致性初始化
    cache_coherence_init();

    // 同步屏障:确保核0完成初始化
    barrier_wait();

    // 各核验证共享内存(读取并检查校验和)
    if (core_id == 0) {
        // 核0:验证数据正确性
        uint32_t checksum = 0;
        for (int i = 0; i < SHARED_SIZE; i++) {
            checksum += shared_mem[i];
        }
        // 预期校验和 = 0xFF * 1024 = 261120(若数据为0..255循环)
        // 实际校验和取决于初始化模式,此处仅作示例
        if (checksum == 0) {  // 占位条件,实际应计算预期值
            // 验证失败处理
        }
    } else {
        // 其他核:读取共享内存并执行简单操作
        volatile uint8_t val = shared_mem[core_id * 64];  // 读取各自缓存行
        // 确保读取完成
        __asm__ volatile("fence r, r" ::: "memory");
    }

    // 最终屏障:确保所有核完成验证
    barrier_wait();

    // 核0输出结果(通过UART或内存映射)
    if (core_id == 0) {
        // 此处可添加输出逻辑,如写状态寄存器
        // 例如:*(volatile uint32_t *)0x10000000 = 0x1; // 成功标志
    }

    while (1) {
        __asm__ volatile("wfi");
    }
}

链接脚本与启动配置

需要将共享内存放置在固定地址,并确保各核入口一致。以下为最小链接脚本片段:

/* link.ld */
OUTPUT_ARCH(riscv)
ENTRY(_start)

SECTIONS
{
    . = 0x80000000;
    .text : { *(.text._start) *(.text*) }
    .rodata : { *(.rodata*) }
    .data : { *(.data*) }
    .bss : { *(.bss*) }
    . = ALIGN(64);
    .shared : { *(.shared*) }  /* 共享内存段 */
}

编译与运行

# 编译
riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64d -nostdlib -T link.ld \
    -o multi_core.elf multi_core_init.c -lgcc

# 使用QEMU运行(4核)
qemu-system-riscv64 -machine virt -smp 4 -kernel multi_core.elf -nographic

验证方法与判定标准

  1. 编译通过:无错误生成 multi_core.elf。
  2. 启动成功:QEMU启动后无异常退出,系统停留在 wfi 循环。
  3. 一致性验证:在核0验证代码中,计算共享内存校验和并与预期值比较。若初始化模式为 shared_mem[i] = i & 0xFF,则校验和应为 sum_{i=0}^{1023} (i mod 256) = 256 * (0+1+...+255) = 256 * 32640 = 8355840。可在核0代码中写入该预期值并比较。
  4. 屏障正确性:若任一核在核0完成初始化前访问共享内存,将读到未初始化数据。通过调试器观察各核执行顺序,确认所有核在 barrier_wait 后同时继续。

常见问题

  • 缓存一致性问题:若未使用 fence rw, rw,核0写入的数据可能仍留在L1缓存中,其他核读取到旧值。本代码在初始化后和屏障前后均插入fence指令。
  • 原子操作支持:需确保目标平台支持A扩展(原子指令),编译时使用 -march=rv64gc 包含原子指令。
  • 多核启动入口:若使用OpenSBI,所有核从 _start 进入,需通过 mhartid 区分角色。本代码中核0执行初始化,其他核等待。