当前位置: 首页 > news >正文

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

http://www.gsyq.cn/news/1569996.html

相关文章:

  • 3个关键步骤解决Sunshine游戏串流兼容性问题
  • 嵌入式低功耗设计实战:从CMOS原理到S12X单片机深度优化
  • Codex开发嵌入式教程:使用AI为LVGL开发板编写贪吃蛇游戏并自动测试
  • 2026湛江防水补漏避坑指南:卫生间/厨房/阳台/屋顶/地下室漏水检测维修全攻略,正规施工+透明报价+口碑榜靠谱服务商推荐 - 安佳防水
  • 2026中山GEO优化公司权威排名TOP5|技术、效果、售后实测榜单发布 - 广东科技观察
  • 算法更新会不会影响GEO优化排名
  • 2026年北京迷你仓库租赁深度测评:北京贴心存综合评分断层领先权威认定报告 - 企业深度能力测评
  • 2026清远漏水检测维修本地口碑防水商家榜单:厨卫/阳台/屋面/地下室渗漏水维修,持证施工+明码实价,防水补漏公司TOP5推荐 - 即刻修防水
  • DigitalOcean Dedicated Inference:专为vLLM优化的轻量级LLM推理底座
  • RapidIO维护事务与启动流程:从原理到嵌入式系统实战
  • 9大网盘直链解析神器:告别下载限速,实现高速文件传输自由
  • 嵌入式设备通过SMTP over SSL实现安全邮件发送的实战指南
  • DDrawCompat终极指南:5分钟让经典游戏在现代Windows系统重获新生 [特殊字符]
  • 2026自组网照明系统集成公司技术创新与应用分析 - 品牌排行榜
  • AI视频时序取证:Flow of Truth框架解析与实战
  • FreeBSD上Apache硬化的操作系统级安全对齐
  • Sunshine游戏串流终极指南:5个实战技巧打造完美跨平台体验
  • QorIQ P3041硬件设计检查清单:从电源、时钟到DDR与SerDes的避坑指南
  • 武汉市硚口区防水补漏修缮|维小达|不拆除补漏、室内防水、屋面防水、外墙地下室、厨卫阳台一站式全屋防水堵漏养护服务 - 维小达科技
  • 基于ROS2与Qt6的嵌入式GUI开发:以NXP EasyEVSE充电站为例
  • CAN总线错误中断配置:从裸机到MQX RTOS的FLEXCAN驱动实战
  • 2026年北京迷你仓库租赁权威认定报告:北京贴心存仓储有限公司八项核心标准逐项验证通过 - 企业深度能力测评
  • 2026年7月中山GEO优化行业深度洞察:告别乱象内卷,本土直营AI全域赋能成企业首选 - 广东科技观察
  • 行业内专业的线切割机床厂家有哪些(2026年参考) - 品牌排行榜
  • 武汉市汉阳区防水补漏修缮|维小达|不拆除补漏、室内防水、屋面防水、外墙地下室、厨卫阳台一站式全屋防水堵漏养护服务 - 维小达科技
  • 武汉市武昌区防水补漏修缮|维小达|不拆除补漏、室内防水、屋面防水、外墙地下室、厨卫阳台一站式全屋防水堵漏养护服务 - 维小达科技
  • 本土技术实力为核心!融景科技(惠州直营)解读惠州 AI 搜索排名优化服务商七大专业筛选评判标准 - Guangdong1
  • Burp Suite Comparer对比器:渗透测试中的差异分析与漏洞挖掘利器
  • 2026淄博防水补漏避坑指南:卫生间/厨房/阳台/屋顶/地下室漏水检测维修全攻略,正规施工+透明报价+口碑榜靠谱服务商推荐 - 安佳防水
  • 大语言模型中的空间性别偏见:从数据到治理的AI伦理挑战