3. 原子操作与缓存一致性

任务目标

本案例使用RISC-V原子内存操作(AMO)指令实现一个多核共享计数器的原子自增操作,验证AMO指令在缓存一致性协议下能够保证读-改-写序列的原子性,避免多核竞争导致的数据丢失。

核心原理

RISC-V的AMO指令(如amoadd.w)在硬件层面完成“读-改-写”原子序列:指令执行期间,处理器通过缓存一致性协议(如MOESI)锁定对应缓存行,阻止其他核并发修改,从而保证操作不可分割。与普通load+add+store序列不同,AMO不需要软件加锁,由硬件直接保证原子性。

实现代码

以下代码在RISC-V多核环境中,使用amoadd.w指令对共享计数器执行原子自增。每个核执行N次自增,最终计数器值应等于核数 × N。


#include <stdint.h>
#include <stdio.h>

#define NUM_CORES  4
#define N          1000

volatile uint32_t counter = 0;

/* 使用AMO指令原子自增 */
static inline void atomic_inc(uint32_t *addr) {
    uint32_t old;
    __asm__ volatile (
        "amoadd.w %0, %1, (%2)\n"
        : "=r"(old)          /* 输出:返回旧值 */
        : "r"(1), "r"(addr)  /* 输入:增量1,目标地址 */
        : "memory"
    );
}

/* 每个核执行的入口 */
void core_entry(int core_id) {
    for (int i = 0; i < N; i++) {
        atomic_inc((uint32_t *)&counter);
    }
    /* 通知主核本核完成(简化:用原子flag标记) */
    __asm__ volatile ("fence rw, rw" ::: "memory");
}

int main() {
    /* 在多核系统上,由启动代码为每个核调用core_entry */
    /* 以下为单核验证路径:模拟4核并发效果 */
    
    /* 方法1:直接调用4次(串行,仅验证指令正确性) */
    for (int c = 0; c < NUM_CORES; c++) {
        core_entry(c);
    }
    printf("串行验证: counter = %u (预期 %u)\n", 
           counter, NUM_CORES * N);
    
    /* 方法2:真实多核验证(需平台支持) */
    /* 在支持多核启动的系统中,每个硬件核执行core_entry(core_id) */
    /* 最终检查counter == NUM_CORES * N */
    
    return 0;
}

运行与验证

环境要求:RISC-V 64位(RV64)或32位(RV32)多核处理器,支持A扩展(原子指令)。若使用QEMU模拟,需启用-smp 4参数。

编译命令:


riscv64-unknown-elf-gcc -O2 -march=rv64ima -mabi=lp64 -o atomic_test atomic_test.c

验证路径:

  1. 串行验证:上述代码中main()直接循环调用4次core_entry,验证AMO指令本身功能正确。预期输出counter = 4000。
  2. 真实多核验证:将core_entry分发到4个硬件核并行执行。若系统支持多核启动(如通过SBI或平台引导),每个核执行core_entry(core_id),主核等待所有核完成后打印counter值。

通过判定标准:

  • 串行验证:程序输出counter = 4000,说明AMO指令正确完成原子自增。
  • 真实多核验证:并行执行后counter == 4000。若使用普通load/store实现自增,由于读-改-写序列被中断,结果将小于4000(存在丢失更新)。AMO指令保证结果精确等于4000,即验证了原子性与缓存一致性。

关键说明

  • amoadd.w的返回值是操作前的旧值,本例未使用但保留以展示指令语义。
  • "memory"clobber通知编译器内存可能被修改,防止优化导致指令重排。
  • 真实多核场景需配合fence指令确保内存序,本例在core_entry末尾添加了fence rw, rw保证自增操作对其他核可见后再通知完成。
  • 若平台为RV32,将uint32_t改为unsigned int即可,指令不变。

常见问题

  • 编译报错“illegal instruction”:确认编译选项包含march=rv64ima中的a(原子扩展),且目标硬件支持A扩展。
  • 多核结果小于预期:检查是否所有核都实际执行了N次循环;若使用普通内存操作而非AMO,则必然出现丢失更新,这正是本案例要验证的差异。
  • QEMU验证:使用qemu-system-riscv64 -smp 4配合sifive_u或virt机器,通过OpenSBI引导多核启动。