news 2026/6/22 0:35:06

Linux rwlock读写锁arch_read_lock与ticket锁对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux rwlock读写锁arch_read_lock与ticket锁对比

Linux rwlock读写锁arch_read_lock与ticket锁对比

Linux内核中的rwlock提供了读写分离的同步语义:读者之间不互斥,写者独占.其实现同样经过多层抽象,rwlock_t -> arch_rwlock_t.底层最关键的对比在于:传统rwlock使用ticket锁来保证公平性,而arch_read_lock/core读者实现则面临写者饥饿问题.

一、rwlock的核心数据结构

```c
typedef struct {
arch_rwlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map dep_map;
#endif
} rwlock_t;
```

ARM64平台的arch_rwlock_t基于排队锁(queue rwlock):

```c
typedef struct {
union {
u32 __lock;
struct {
u8 wrpend; /* 写者挂起标志: 0表示无写者等待, 1表示有写者等待 */
u8 waiters; /* 等待者计数 (包括读者和写者) */
u16 readers; /* 活跃读者计数, 0xFFFF表示有写者持有 */
};
};
} arch_rwlock_t;
```

x86平台的传统arch_rwlock_t:

```c
typedef struct {
u32 rwlock; /* 高16位: 读者计数; 低16位: 写者标志 */
} arch_rwlock_t;
```

二、arch_read_lock实现分析

ARM64的读者锁获取:

```c
static inline void arch_read_lock(arch_rwlock_t *lock)
{
unsigned int tmp;
asm volatile(
"1: ldaxr %w0, %1\n"
" tbnz %w0, #24, 2f\n" /* 检查wrpend位,有写者挂起则跳转 */
" add %w0, %w0, #1\n" /* 读者计数+1 */
" stxr %w0, %w0, %1\n"
" cbnz %w0, 1b\n" /* 存储失败则重试 */
" dmb ishld\n" /* 数据内存屏障 */
" ret\n"
"2: /* 写者挂起,等待 */\n"
" ldar %w0, %1\n"
" tbz %w0, #24, 1b\n" /* wrpend清零则重试读获取 */
" wfe\n"
" b 2b\n"
: "=&r" (tmp), "+Q" (lock->__lock)
:
: "memory");
}
```

x86平台的arch_read_lock:

```c
static inline void arch_read_lock(arch_rwlock_t *lock)
{
/* 尝试原子减1 (rwlock初始值0x01000000, 减1后为0x00FFFFFF表示读锁) */
asm volatile(
"1: lock; decl %0\n"
" jns 3f\n"
"2: rep; nop\n"
" cmpl $0, %0\n"
" js 2b\n"
" jmp 1b\n"
"3:"
: "+m" (lock->rwlock)
:
: "memory", "cc");
}
```

三、arch_write_lock实现分析

ARM64的写者锁获取:

```c
static inline void arch_write_lock(arch_rwlock_t *lock)
{
unsigned int tmp;
asm volatile(
"1: mov %w0, #0x00010000\n" /* wrpend=1, waiters递增 */
" ldaxr %w1, %2\n"
" add %w0, %w0, %w1, lsr #16\n" /* waiters域递增 */
" stxr %w0, %w0, %2\n"
" cbnz %w0, 1b\n"
/* 等待所有读者释放 */
"2: dmb ishld\n"
" ldr %w0, %2\n"
" and %w0, %w0, #0xFFFF\n" /* 取readers域 */
" cbnz %w0, 2b\n" /* 仍有读者则等待 */
" ret\n"
: "=&r" (tmp), "=&r" (lock->wrpend), "+Q" (lock->__lock)
:
: "memory");
}
```

四、ticket锁形式的rwlock

某些架构(如旧版x86)使用了ticket锁思想来实现读写锁.每个锁维护两个计数器:当前服务号和服务号上限.

```c
struct ticket_rwlock {
union {
u32 headtail;
struct {
u16 head; /* 当前服务的票号 */
u16 tail; /* 下一个可用的票号 */
};
};
u16 readers; /* 读者计数, 分开存储以避免与写者票号冲突 */
};
```

ticket锁形式的读写锁操作:

```c
static inline void ticket_write_lock(struct ticket_rwlock *lock)
{
u16 my_ticket = xadd(&lock->tail, 2); /* 获取一个偶数的写者票号 */

/* 等待轮到自己的票号 */
while (lock->head != my_ticket)
cpu_relax();

/* 等待所有读者释放 */
while (lock->readers)
cpu_relax();

smp_mb();
}

static inline void ticket_read_lock(struct ticket_rwlock *lock)
{
/* 读者不需要票号队列 */
atomic_inc(&lock->readers);
smp_mb();
}

static inline void ticket_read_unlock(struct ticket_rwlock *lock)
{
smp_mb();
atomic_dec(&lock->readers);
}
```

五、公平性差异分析

传统rwlock(非ticket版)存在严重的写者饥饿问题:如果读者持续到来,写者可能永远无法获取锁.

```c
/* 写者饥饿场景 */
CPU0: read_lock(&rwlock); /* 持有读锁 */
CPU1: read_lock(&rwlock); /* 同时获得读锁 */
CPU2: write_lock(&rwlock); /* 等待所有读者释放 */
CPU3: read_lock(&rwlock); /* CPU0释放后, CPU3进入, 写者依然等待 */
/* 写者CPU2永远等下去... */
```

解决写者饥饿的两个方案:

1. Queue rwlock: 当前ARM64使用的方案,通过wrpend位标记等待的写者,新读者看到wrpend后不再获取锁.

2. ticket rwlock: 写者获取一个票号,所有操作按票号FIFO执行,新读者即使没有写者等待也必须排队.

六、性能对比关键点

- 读者冲突场景: 传统rwlock读者之间通过原子增/减操作,缓存行 bouncing 开销大
- queue rwlock读者路径: 需要检查wrpend位,多一次分支预测
- ticket rwlock读者路径: 完全无需排队,但写者饥饿问题严重
- ARM64 LSE指令集: 支持LDAPR等宽松语义指令,可降低读者路径延迟

七、qrwlock的改进 - 公平读写锁

内核引入了queued_read_lock()来替代旧的arch_read_lock:

```c
static inline void queued_read_lock(struct qrwlock *lock)
{
/* 快速路径: 无写者竞争时直接自增读者计数 */
if (likely(atomic_inc_not_zero(&lock->cnts)))
return;
/* 慢路径: 有写者竞争, 需要排队 */
queued_read_lock_slowpath(lock);
}

static inline void queued_write_lock(struct qrwlock *lock)
{
/* 尝试获取写锁: 从0xFFFFFFFE变为(0xFFFFFFFE | _QW_LOCKED) */
if (likely(atomic_cmpxchg(&lock->cnts, 0, _QW_LOCKED) == 0))
return;
queued_write_lock_slowpath(lock);
}
```

这种设计的核心优势:读者快速路径在无竞争时仅需一条原子操作,慢路径使用MCS锁队列避免缓存行bouncing.

八、使用建议

- 读远多于写的场景(如路由表查找): 使用rwlock,但需注意写者饥饿
- 读写均衡场景: 优先使用带公平性的qrwlock
- 对实时性有要求: 使用RT内核中的rwlock(基于rt_mutex实现),完全避免饥饿
- 读者性能极致要求: 考虑RCU或percpu-rw-semaphore替代rwlock

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/22 0:32:59

AI视频时序取证:Flow of Truth框架解析与实战

1. 项目概述:当AI学会“剪辑”时间,我们如何鉴别真伪?最近圈子里聊得最多的,除了各种大模型的迭代,就是AIGC(人工智能生成内容)在视频领域的狂飙突进了。从静态的“文生图”到动态的“图生视频”…

作者头像 李华
网站建设 2026/6/22 0:28:25

3个关键步骤解决Sunshine游戏串流兼容性问题

3个关键步骤解决Sunshine游戏串流兼容性问题 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 想要在不同设备上享受流畅的游戏串流体验,却总是遇到兼容性问题&#xff1…

作者头像 李华
网站建设 2026/6/22 0:22:55

嵌入式矢量图形开发实战:基于i.MX RT700 VGLite硬件加速

1. 项目概述:为什么嵌入式系统需要矢量图形?如果你正在开发下一代智能手表、工业控制面板或者车载仪表盘,大概率会遇到一个头疼的问题:UI界面既要精美流畅,又要能在资源有限的微控制器上高效运行。传统的位图&#xff…

作者头像 李华
网站建设 2026/6/22 0:14:49

Ubuntu 20.04 TigerVNC远程桌面部署全指南:X11+GNOME Classic稳定方案

1. 项目概述:为什么在 Ubuntu 20.04 上亲手部署 VNC 远程桌面,比“一键安装”更值得你花这 30 分钟?VNC(Virtual Network Computing)不是个新词,但每次在 Ubuntu 20.04 上配置它,总有人卡在“连…

作者头像 李华
网站建设 2026/6/22 0:10:20

Steam游戏自动破解终极指南:3分钟实现正版游戏离线自由

Steam游戏自动破解终极指南:3分钟实现正版游戏离线自由 【免费下载链接】Steam-auto-crack Steam Game Automatic Cracker 项目地址: https://gitcode.com/gh_mirrors/st/Steam-auto-crack 在数字游戏时代,你是否曾经因为网络问题而无法畅玩自己合…

作者头像 李华