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
验证方法
- 正确性判定:程序输出
counter = 400000且退出码为0,说明4个线程各完成100000次递增,无数据竞争。 - 互斥验证:若注释掉
spinlock_acquire/spinlock_release,counter结果将小于400000(因原子性丢失),对比可确认锁生效。 - 内存序说明:
lr.w隐含acquire语义,amoswap.w.rl带release语义,确保临界区操作不会逃逸出锁边界。
关键机制
- LR/SC循环:
lr.w读取锁值,若为0则尝试用sc.w写入1;若期间发生上下文切换或缓存行被其他核修改,sc.w失败并重试。 - 公平性说明:该实现为无公平性保证的自旋锁,高竞争下可能出现饥饿,但满足当前互斥目标。
- 适用场景:临界区极短(仅数条指令)时性能最佳;若临界区较长,建议改用操作系统提供的futex或信号量。
上图展示两个线程竞争同一把锁的流程:每个线程通过lr.w读取锁状态,若为0则尝试sc.w写入1;只有一个线程能成功,失败的线程立即重试直到成功。