4. 自旋锁实现

任务目标

在RISC-V多核环境中,基于lr.w/sc.w指令实现自旋锁,保护临界区数据,并通过多核原子递增操作验证互斥效果。

核心实现

// spinlock.h
#ifndef SPINLOCK_H
#define SPINLOCK_H

#include <stdint.h>

typedef struct {
    volatile uint32_t locked;
} spinlock_t;

static inline void spinlock_init(spinlock_t *lock) {
    lock->locked = 0;
}

static inline void spinlock_acquire(spinlock_t *lock) {
    uint32_t tmp;
    __asm__ volatile (
        "1: lr.w   %0, (%1)\n"      // 读取锁状态
        "   bnez   %0, 1b\n"        // 若已锁定,重试
        "   li     %0, 1\n"         // 准备写入1
        "   sc.w   %0, %0, (%1)\n"  // 条件存储
        "   bnez   %0, 1b\n"        // 存储失败则重试
        : "=&r"(tmp)
        : "r"(&lock->locked)
        : "memory"
    );
}

static inline void spinlock_release(spinlock_t *lock) {
    __asm__ volatile (
        "   amoswap.w.rl  x0, x0, (%0)\n"  // 原子写0释放
        : 
        : "r"(&lock->locked)
        : "memory"
    );
}

#endif // SPINLOCK_H
// test_spinlock.c
#include <stdio.h>
#include <pthread.h>
#include "spinlock.h"

#define THREADS 4
#define ITERATIONS 100000

static spinlock_t lock;
static volatile long counter = 0;

void *worker(void *arg) {
    (void)arg;
    for (int i = 0; i < ITERATIONS; i++) {
        spinlock_acquire(&lock);
        counter++;          // 临界区
        spinlock_release(&lock);
    }
    return NULL;
}

int main() {
    pthread_t threads[THREADS];
    spinlock_init(&lock);

    for (int i = 0; i < THREADS; i++)
        pthread_create(&threads[i], NULL, worker, NULL);
    for (int i = 0; i < THREADS; i++)
        pthread_join(threads[i], NULL);

    printf("counter = %ld (expected %d)\n", counter, THREADS * ITERATIONS);
    return (counter == THREADS * ITERATIONS) ? 0 : 1;
}

编译与运行

# 使用RISC-V交叉编译器(假设已配置工具链)
riscv64-unknown-linux-gnu-gcc -O2 -pthread -o test_spinlock test_spinlock.c

# 在RISC-V平台(或QEMU模拟器)上运行
./test_spinlock

验证方法

  1. 正确性判定:程序输出counter = 400000且退出码为0,说明4个线程各完成100000次递增,无数据竞争。
  2. 互斥验证:若注释掉spinlock_acquire/spinlock_release,counter结果将小于400000(因原子性丢失),对比可确认锁生效。
  3. 内存序说明:lr.w隐含acquire语义,amoswap.w.rl带release语义,确保临界区操作不会逃逸出锁边界。

关键机制

  • LR/SC循环:lr.w读取锁值,若为0则尝试用sc.w写入1;若期间发生上下文切换或缓存行被其他核修改,sc.w失败并重试。
  • 公平性说明:该实现为无公平性保证的自旋锁,高竞争下可能出现饥饿,但满足当前互斥目标。
  • 适用场景:临界区极短(仅数条指令)时性能最佳;若临界区较长,建议改用操作系统提供的futex或信号量。
线程1 线程2 lr.w → sc.w lr.w → sc.w locked = 0/1 sc.w失败则重试

上图展示两个线程竞争同一把锁的流程:每个线程通过lr.w读取锁状态,若为0则尝试sc.w写入1;只有一个线程能成功,失败的线程立即重试直到成功。