Linux内核同步笔记

Linux内核同步实现
1 抢占管理
- preempt_enable
- preempt_disable
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule(); \
} while (0)
// 继续分析
#define preempt_count_dec_and_test() \
({ preempt_count_sub(1); should_resched(0); })
static __always_inline bool should_resched(int preempt_offset)
{
return unlikely(raw_cpu_read_4(pcpu_hot.preempt_count) == preempt_offset);
}
可以看到preempt_count()
获取计数
static __always_inline int preempt_count(void)
{
return raw_cpu_read_4(pcpu_hot.preempt_count) & ~PREEMPT_NEED_RESCHED;
}
- barrier() 防止指令乱序
- preempt_count_inc 增加计数
- preempt_count_dec_and_test 减少计数
- 获取抢占计数
增加/减少计数
static __always_inline void __preempt_count_add(int val)
{
raw_cpu_add_4(pcpu_hot.preempt_count, val);
}
static __always_inline void __preempt_count_sub(int val)
{
raw_cpu_add_4(pcpu_hot.preempt_count, -val);
}
为什么这个变量可以实现抢占呢?
需要检查schedule
实现分析
在Linux内核中,preempt_disable()
和 preempt_enable()
是用于控制内核抢占(Preemption)的关键函数。
它们通过管理抢占计数器(preempt_count
)来确保临界区代码的原子性,避免任务在内核态执行期间被其他高优先级任务抢占。以下是详细分析:
1.1 3. 使用场景
1.1.1 典型用例
访问每CPU变量(Per-CPU Data):
- 每CPU变量是每个CPU独有的数据,禁用抢占可防止任务被迁移到其他CPU。
- 例如:
int *ptr = this_cpu_ptr(&my_percpu_var); preempt_disable(); *ptr += 1; /* 操作每CPU变量 */ preempt_enable();
保护短临界区:
- 当需要保证一段代码不被其他任务打断,但无需处理中断或软中断时。
- 例如:
preempt_disable(); modify_global_non_sleepable_data(); /* 修改非睡眠安全的数据 */ preempt_enable();
与自旋锁配合使用:
- 自旋锁(如
spin_lock()
)内部会自动禁用抢占,但在某些手动管理场景中可能需要显式调用。 - 例如,持有锁期间访问共享资源:
spin_lock(&lock); preempt_disable(); critical_section(); preempt_enable(); spin_unlock(&lock);
- 自旋锁(如
1.1.2 与自旋锁的区别
机制 | 抢占控制 | 中断控制 | 适用场景 |
---|---|---|---|
preempt_disable() | 禁用任务调度 | 不影响中断 | 短临界区、每CPU变量 |
spin_lock() | 隐式禁用抢占 | 可能禁用中断/软中断 | 共享资源在多核/中断上下文访问 |
1.2 4. 注意事项
1.2.1 (1) 嵌套调用
preempt_disable()
和preempt_enable()
必须严格配对。- 每次
preempt_disable()
增加计数器,preempt_enable()
减少计数器。 - 错误示例:
preempt_disable(); preempt_disable(); /* 嵌套禁用抢占 */ critical_section(); preempt_enable(); /* 仅减少一次计数器,抢占仍被禁用! */
1.2.2 (2) 禁止在禁用抢占时睡眠
- 若在
preempt_disable()
后调用可能睡眠的函数(如kmalloc(GFP_KERNEL)
),会导致死锁或内核崩溃。 - 原因:调度器无法切换任务,当前任务无法释放CPU。
1.2.3 (3) 单核与多核行为
- 单核系统(非抢占内核):
preempt_disable()
可能无实际效果(因内核不可抢占),但仍需使用以保证代码可移植性。 - 多核系统:禁用抢占仅对当前CPU有效,其他CPU仍可并发执行。
1.2.4 (4) 与中断的关系
- 不禁止中断:中断仍可发生,但中断处理程序执行完毕后,调度器不会抢占当前任务(因
preempt_count > 0
)。 - 若需同时禁用中断,需配合
local_irq_disable()
:local_irq_disable(); preempt_disable(); /* 临界区:不受中断和任务抢占影响 */ preempt_enable(); local_irq_enable();
1.3 5. 性能影响
- 优点:轻量级,仅操作计数器,适合高频短临界区。
- 缺点:
- 长时间禁用抢占会导致调度延迟,影响系统实时性。
- 不适用于需要睡眠的操作(如I/O等待)。
1.4 6. 代码示例
#include <linux/preempt.h>
void example_function(void) {
preempt_disable(); // 禁用抢占
// 临界区代码:不会被其他任务抢占
access_shared_data();
preempt_enable(); // 启用抢占,可能触发调度
}
1.5 总结
关键点 | 说明 |
---|---|
作用 | 禁用/启用内核抢占,保护短临界区代码的原子性。 |
实现机制 | 通过preempt_count 计数器控制抢占。 |
适用场景 | 每CPU变量、非睡眠安全的短临界区、与锁配合使用。 |
注意事项 | 严格嵌套配对、禁止睡眠、区分单核/多核行为、不保护中断。 |
替代方案 | 自旋锁(处理多核并发+中断)、RCU(读多写少场景)。 |
合理使用preempt_disable()
/preempt_enable()
可在不引入锁开销的情况下,高效实现内核数据保护,但需严格遵守使用规则以避免系统稳定性问题。
2 原子变量
2.1 原子变量数据结构
https://elixir.bootlin.com/linux/v4.0/source/include/linux/types.h#L175
typedef struct {
int counter;
} atomic_t;
typedef struct {
long long counter;
} atomic64_t;
2.2 提供的操作
类别 | 函数原型 | 功能 |
---|---|---|
初始化 | ATOMIC_INIT(int val) | 静态初始化原子变量为val 。 |
atomic_set(atomic_t *v, int i) | 动态设置原子变量值为i 。 | |
基础操作 | atomic_read(const atomic_t *v) | 读取原子变量的值。 |
atomic_add(int i, atomic_t *v) | 原子地将v 的值增加i 。 | |
atomic_sub(int i, atomic_t *v) | 原子地将v 的值减少i 。 | |
atomic_inc(atomic_t *v) | 原子地递增v (等价于v += 1 )。 | |
atomic_dec(atomic_t *v) | 原子地递减v (等价于v -= 1 )。 | |
条件操作 | atomic_inc_and_test(atomic_t *v) | 递增v ,若结果为0返回true 。 |
atomic_dec_and_test(atomic_t *v) | 递减v ,若结果为0返回true 。 | |
atomic_sub_and_test(int i, atomic_t *v) | 减去i ,若结果为0返回true 。 | |
返回值操作 | atomic_add_return(int i, atomic_t *v) | 增加i 并返回新值。 |
atomic_sub_return(int i, atomic_t *v) | 减少i 并返回新值。 |
函数原型 | 功能 |
---|---|
set_bit(int nr, volatile unsigned long *addr) | 原子地设置地址addr 的第nr 位为1。 |
clear_bit(int nr, volatile unsigned long *addr) | 原子地清除地址addr 的第nr 位为0。 |
test_and_set_bit(int nr, volatile unsigned long *addr) | 设置第nr 位为1,返回旧值。 |
test_and_clear_bit(int nr, volatile unsigned long *addr) | 清除第nr 位为0,返回旧值。 |
通用的模式
static inline void set_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
unsigned long flags;
_atomic_spin_lock_irqsave(p, flags);
*p |= mask;
_atomic_spin_unlock_irqrestore(p, flags);
}
static inline void clear_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
unsigned long flags;
_atomic_spin_lock_irqsave(p, flags);
*p &= ~mask;
_atomic_spin_unlock_irqrestore(p, flags);
}
如果使用高效率的方式,使用汇编
2.3 读取/写入原子变量
https://elixir.bootlin.com/linux/v4.0/source/arch/arm/include/asm/atomic.h#L30
#define atomic_read(v) ACCESS_ONCE((v)->counter)
#define atomic_set(v,i) (((v)->counter) = (i))
#define __ACCESS_ONCE(x) ({ \
__maybe_unused typeof(x) __var = (__force typeof(x)) 0; \
(volatile typeof(x) *)&(x); })
#define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))
3 自旋锁
3.1 概览
在Linux内核中,自旋锁(spinlock)是一种用于多处理器(SMP)环境的同步机制,确保共享资源的互斥访问。以下是不同自旋锁变种的详细解释及使用场景:
- spin_lock/spin_unlock
- spin_lock_bh/spin_unlock_bh
- spin_lock_irq/spin_unlock_irq
- spin_lock_irqsave/spin_unlock_irqrestore
- queued_spin_lock/queued_spin_unlock
3.1.1 1. spin_lock()
/ spin_unlock()
- 功能:最基本的自旋锁操作。
- 行为:
spin_lock()
:尝试获取锁,若锁被占用,则自旋等待。spin_unlock()
:释放锁。
- 适用场景:
- 非中断上下文(如线程、进程上下文)。
- 共享资源不会被中断处理程序或软中断(Bottom Half)访问。
- 注意:若在中断上下文中可能访问同一锁,需使用其他变种(如
spin_lock_irq()
)。
3.1.2 2. spin_lock_bh()
/ spin_unlock_bh()
- 功能:在获取锁的同时禁用软中断(Bottom Half)。
- 行为:
spin_lock_bh()
:禁用本地CPU的软中断,然后获取锁。spin_unlock_bh()
:释放锁,并重新启用软中断。
- 适用场景:
- 保护共享资源在**进程上下文与软中断(如tasklet、定时器)**之间的并发访问。
- 例如:进程上下文与网络协议栈的软中断共享数据时。
3.1.3 3. spin_lock_irq()
/ spin_unlock_irq()
- 功能:在获取锁的同时禁用硬中断。
- 行为:
spin_lock_irq()
:禁用本地CPU的硬中断,然后获取锁。spin_unlock_irq()
:释放锁,并重新启用硬中断。
- 适用场景:
- 保护共享资源在进程上下文与硬中断处理程序之间的并发访问。
- 注意:若在调用前中断已禁用,解锁后可能错误启用中断。此时应使用
spin_lock_irqsave()
。
3.1.4 4. spin_lock_irqsave()
/ spin_unlock_irqrestore()
- 功能:安全处理中断状态的锁操作。
- 行为:
spin_lock_irqsave()
:保存当前中断状态到变量,禁用本地CPU中断,然后获取锁。spin_unlock_irqrestore()
:释放锁,并根据保存的状态恢复中断。
- 适用场景:
- 共享资源在不确定中断是否已禁用的上下文中使用(如可重入函数)。
- 确保中断状态的正确保存与恢复,避免破坏原有状态。
3.1.5 5. queued_spin_lock()
/ queued_spin_unlock()
- 功能:排队自旋锁的实现,解决传统自旋锁的高竞争问题。
- 行为:
- 通过队列机制让等待锁的CPU按顺序获取,减少缓存行争用。
- 提升多核系统下的扩展性和公平性。
- 内核使用:
- 从Linux 3.10开始,x86架构默认使用排队自旋锁。
- 用户通过通用接口(如
spin_lock()
)调用,无需直接操作queued_spin_*
。
- 优点:在高竞争场景下性能更优,避免“惊群效应”。
3.1.6 总结与选择指南
锁类型 | 禁用中断类型 | 适用场景 |
---|---|---|
spin_lock() | 无 | 仅进程上下文,无中断/软中断竞争 |
spin_lock_bh() | 软中断(Bottom Half) | 进程上下文与软中断共享资源 |
spin_lock_irq() | 硬中断 | 进程上下文与硬中断共享资源,且已知中断状态 |
spin_lock_irqsave() | 硬中断(带状态保存) | 进程上下文与硬中断共享资源,且需安全处理中断状态 |
queued_spin_lock() | 无(内核内部优化) | 高竞争场景,由内核自动选择 |
3.1.7 注意事项
- 禁止在持有自旋锁时睡眠:可能导致死锁或内核崩溃。
- 锁持有时间应极短:自旋锁通过忙等待消耗CPU,长时间持有会降低性能。
- 区分单核与多核行为:在单核非抢占内核中,自旋锁可能退化为仅禁用抢占。
通过合理选择自旋锁变种,可确保内核数据的安全访问,同时兼顾性能与正确性。
3.2 数据结构
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock_types.h#L32
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} raw_spinlock_t;
进入arch的实现
https://elixir.bootlin.com/linux/v4.0/source/arch/arm/include/asm/spinlock_types.h#L23
typedef struct {
union {
u32 slock;
struct __raw_tickets {
#ifdef __ARMEB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} tickets;
};
} arch_spinlock_t;
https://elixir.bootlin.com/linux/v4.0/source/include/linux/spinlock.h#L188
3.3 加锁
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock)
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable(); // 核心实现
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); // lockdep检测
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
static __always_inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock->rlock);
}
函数调用路线:
graph LR spin_lock_bh ==> __raw_spin_lock_bh ==> __local_bh_disable_ip ==> preempt_count_add
最终,执行到了pcpu_hot.preempt_count
计数功能实现
static inline void spin_lock_bh(spinlock_t *lock)
{
raw_spin_lock_bh(&lock->rlock);
}
static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
preempt_count_add(cnt);
barrier();
}
static __always_inline void __preempt_count_add(int val)
{
raw_cpu_add_4(pcpu_hot.preempt_count, val);
}
static inline int spin_trylock(spinlock_t *lock)
{
return raw_spin_trylock(&lock->rlock);
}
3.4 解锁
static inline void spin_unlock(spinlock_t *lock)
{
raw_spin_unlock(&lock->rlock);
}
static inline void spin_unlock_bh(spinlock_t *lock)
{
raw_spin_unlock_bh(&lock->rlock);
}
static inline void spin_unlock_irq(spinlock_t *lock)
{
raw_spin_unlock_irq(&lock->rlock);
}
3.5 qspinlock实现
进入qspin实现
qspin锁数据
typedef struct qspinlock {
union {
atomic_t val;
/*
* By using the whole 2nd least significant byte for the
* pending bit, we can allow better optimization of the lock
* acquisition for the pending bit holder.
*/
#ifdef __LITTLE_ENDIAN
struct {
u8 locked;
u8 pending;
};
struct {
u16 locked_pending;
u16 tail;
};
#else
struct {
u16 tail;
u16 locked_pending;
};
struct {
u8 reserved[2];
u8 pending;
u8 locked;
};
#endif
};
} arch_spinlock_t;
- 如果lock可以直接获取,直接返回;
- 进入
slowpath
static __always_inline void queued_spin_lock(struct qspinlock *lock)
{
int val = 0;
if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL)))
return;
queued_spin_lock_slowpath(lock, val);
}
slowpath实现
void queued_spin_lock_slowpath(struct qspinlock *lock)
{
/*
* This looks funny, but it induces the compiler to inline both
* sides of the branch rather than share code as when the condition
* is passed as the paravirt argument to the functions.
*/
if (IS_ENABLED(CONFIG_PARAVIRT_SPINLOCKS) && is_shared_processor()) {
if (try_to_steal_lock(lock, true)) {
spec_barrier();
return;
}
queued_spin_lock_mcs_queue(lock, true);
} else {
if (try_to_steal_lock(lock, false)) {
spec_barrier();
return;
}
queued_spin_lock_mcs_queue(lock, false);
}
}
static __always_inline void queued_spin_unlock(struct qspinlock *lock)
{
/*
* unlock() needs release semantics:
*/
smp_store_release(&lock->locked, 0);
}
// 直接进行smp释放
#define smp_store_release(p, v) do { kcsan_release(); __smp_store_release(p, v); } while (0)
// 对locked直接释放
do { \
compiletime_assert_atomic_type(*p); \
__smp_mb(); \
WRITE_ONCE(*p, v); \
} while (0)
4 互斥锁
4.1 数据结构
https://elixir.bootlin.com/linux/v4.0/source/include/linux/mutex.h#L50
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
struct task_struct *owner;
#endif
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
如何判断mutex加锁
/**
* mutex_is_locked - is the mutex locked
* @lock: the mutex to be queried
*
* Returns 1 if the mutex is locked, 0 if unlocked.
*/
static inline int mutex_is_locked(struct mutex *lock)
{
return atomic_read(&lock->count) != 1;
}
4.2 加锁操作
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
/*
* The locking fastpath is the 1->0 transition from
* 'unlocked' into 'locked' state.
*/
__mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
mutex_set_owner(lock);
}
/* 直接设置owner */
static inline void mutex_set_owner(struct mutex *lock)
{
lock->owner = current;
}
下面的函数分析:
- 如果可以直接设置原子变量
- 进入慢速的实现
static inline void
__mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
if (unlikely(atomic_xchg(count, 0) != 1))
if (likely(atomic_xchg(count, -1) != 1))
fail_fn(count)
}
分析慢速实现
__visible void __sched
__mutex_lock_slowpath(atomic_t *lock_count)
{
struct mutex *lock = container_of(lock_count, struct mutex, count);
__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0,
NULL, _RET_IP_, NULL, 0);
}
https://elixir.bootlin.com/linux/v4.0/source/kernel/locking/mutex.c#L517
4.3 解锁实现
https://elixir.bootlin.com/linux/v4.0/source/kernel/locking/mutex.c#L421
void __sched mutex_unlock(struct mutex *lock)
{
#ifndef CONFIG_DEBUG_MUTEXES
mutex_clear_owner(lock);
#endif
__mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);
}
进入快速释放函数
static inline void
__mutex_fastpath_unlock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
if (unlikely(atomic_xchg(count, 1) != 0))
fail_fn(count);
}
进入慢速函数实现
https://elixir.bootlin.com/linux/v4.0/source/kernel/locking/mutex.c#L724
5 信号量
5.1 数据结构
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
5.2 down操作
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
进入内核实现 https://elixir.bootlin.com/linux/v4.0/source/kernel/locking/semaphore.c#L204
5.3 up操作
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
进入up的具体实现
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = true;
wake_up_process(waiter->task);
}
6 读写锁
Linux内核中的读写锁(Reader-Writer Locks)主要用于优化对共享资源的高并发访问,尤其是在读多写少的场景下。其设计目标是在保证数据一致性的前提下,最大化读取操作的并行性,同时确保写入操作的独占性。以下是Linux内核中两种主要读写锁的实现分析:
6.1 1. rwlock_t
(自旋读写锁)
适用场景:不可休眠的上下文(如中断处理程序、原子上下文),基于自旋锁实现。
6.1.1 数据结构
// include/linux/rwlock_types.h
typedef struct {
arch_rwlock_t raw_lock; // 架构相关的底层锁表示
} rwlock_t;
- 底层实现:依赖于架构(如x86使用原子计数和状态标志),通常通过原子指令或自旋锁机制实现。
6.1.2 核心操作
读者加锁:
read_lock(rwlock_t *lock);
- 原子操作增加读者计数,若当前无写者持有锁则立即获得访问权;否则自旋等待。
写者加锁:
write_lock(rwlock_t *lock);
- 等待所有读者释放锁后,独占获取锁,期间通过自旋避免上下文切换。
解锁:
read_unlock(rwlock_t *lock); write_unlock(rwlock_t *lock);
- 减少读者计数或释放写锁,唤醒等待的读者/写者。
6.1.3 实现特点
- 无休眠:读者和写者均通过自旋等待,适用于非抢占式上下文。
- 低开销:无上下文切换成本,适合极短临界区。
- 潜在问题:长时间持有锁会导致CPU资源浪费,可能引发写者饥饿(若读者持续进入)。
6.2 2. struct rw_semaphore
(读写信号量)
适用场景:可休眠的进程上下文,基于信号量实现,支持复杂的同步策略。
6.2.1 数据结构
// include/linux/rwsem.h
struct rw_semaphore {
atomic_long_t count; // 组合计数器:高32位为等待计数,低32位为活跃读者/写者标志
struct list_head wait_list; // 等待队列(读者或写者)
// 其他调试和优化字段...
};
- 计数器设计:
- 写者标记:若最低位(
0x1
)为1,表示写者持有锁。 - 读者计数:低32位中除最低位的其余位表示活跃读者数量。
- 等待计数:高32位记录等待的写者或读者数量。
- 写者标记:若最低位(
6.2.2 核心操作
读者加锁:
down_read(struct rw_semaphore *sem);
- 若当前无写者持有锁且无写者等待,立即增加读者计数;否则加入等待队列并休眠。
写者加锁:
down_write(struct rw_semaphore *sem);
- 等待所有活跃读者/写者释放锁后独占访问,期间休眠让出CPU。
解锁:
up_read(struct rw_semaphore *sem); up_write(struct rw_semaphore *sem);
- 减少读者计数或清除写者标记,唤醒队列中的等待者(优先唤醒写者以避免饥饿)。
6.2.3 实现特点
- 公平性策略:
- 写者优先:当写者等待时,新读者会被阻塞,防止写者饥饿。
- 等待队列:通过FIFO或优先级调度管理等待者,平衡读者与写者的公平性。
- 休眠支持:在无法立即获取锁时,任务进入休眠状态,避免CPU空转。
- 优化机制:
- 无竞争快速路径:无竞争时直接通过原子操作完成加锁/解锁,无需操作等待队列。
- 乐观自旋:在某些实现中,写者可能会短暂自旋以尝试快速获取锁,减少休眠开销。
6.3 3. 性能与设计权衡
特性 | rwlock_t | rw_semaphore |
---|---|---|
上下文 | 仅限非休眠上下文(中断、原子操作) | 进程上下文(可休眠) |
等待机制 | 自旋等待(忙等待) | 休眠等待 |
公平性 | 无明确策略,可能写者饥饿 | 写者优先,减少饥饿风险 |
开销 | 低(无上下文切换) | 较高(休眠唤醒、队列管理) |
适用场景 | 极短临界区、高频读操作 | 长临界区、需公平性保障 |
6.4 4. 实现细节分析
原子操作与内存屏障:
rwlock_t
使用原子指令(如atomic_add
)管理读者计数,结合内存屏障(barrier()
)确保操作顺序。rw_semaphore
通过atomic_long_t
的原子操作更新组合计数器,配合显式屏障(如smp_mb__before_atomic
)保证多核一致性。
调试与检测:
- 锁依赖检测:内核配置
CONFIG_DEBUG_LOCK_ALLOC
时,会跟踪锁的获取顺序以防止死锁。 - 死锁预警:通过
CONFIG_DEBUG_ATOMIC_SLEEP
检查在原子上下文中误用rw_semaphore
。
- 锁依赖检测:内核配置
6.5 5. 示例场景
场景1:网络包统计(高频读,低频写)
- 使用
rwlock_t
保护全局统计计数器:rwlock_t stats_lock; unsigned int packet_count; // 读者(中断上下文) read_lock(&stats_lock); unsigned int count = packet_count; read_unlock(&stats_lock); // 写者(定时器上下文) write_lock(&stats_lock); packet_count = 0; write_unlock(&stats_lock);
场景2:文件系统元数据更新(需公平性)
- 使用
rw_semaphore
保护目录结构:struct rw_semaphore dir_sem; // 读者(遍历目录) down_read(&dir_sem); // 读取目录项 up_read(&dir_sem); // 写者(创建文件) down_write(&dir_sem); // 修改目录结构 up_write(&dir_sem);