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
验证方法与判定标准
- 编译通过:无错误生成
multi_core.elf。 - 启动成功:QEMU启动后无异常退出,系统停留在
wfi循环。 - 一致性验证:在核0验证代码中,计算共享内存校验和并与预期值比较。若初始化模式为
shared_mem[i] = i & 0xFF,则校验和应为sum_{i=0}^{1023} (i mod 256) = 256 * (0+1+...+255) = 256 * 32640 = 8355840。可在核0代码中写入该预期值并比较。 - 屏障正确性:若任一核在核0完成初始化前访问共享内存,将读到未初始化数据。通过调试器观察各核执行顺序,确认所有核在
barrier_wait后同时继续。
常见问题
- 缓存一致性问题:若未使用
fence rw, rw,核0写入的数据可能仍留在L1缓存中,其他核读取到旧值。本代码在初始化后和屏障前后均插入fence指令。 - 原子操作支持:需确保目标平台支持A扩展(原子指令),编译时使用
-march=rv64gc包含原子指令。 - 多核启动入口:若使用OpenSBI,所有核从
_start进入,需通过mhartid区分角色。本代码中核0执行初始化,其他核等待。