本文基于arm linux 5.10来介绍内核中使用的读写信号量rw remphore的实现代码。
内核中信号量结构体struct rw_semaphore的定义在include/linux/rwsem.h
32位architectures下,结构体struct rw_semaphore中的count的使用如下:
先来看信号量的定义和初始化函数和宏:
宏DECLARE_RWSEM()定义了一个读写信号量,并初始化count为0.
如果我们已经定义了信号量变量,要初始化它,可以使用init_rwsem()或直接使用__init_rwsem(), 它们的定义如下:
init_rwsem()和__init_rwsem()他们初始化信号量计数count也是为0.
读写信号量,运行多个读,读与写互斥,写只能有一个。
先看读信号量获取: down_read().
down_read()先调用__down_read_trylock()去上锁, 如果trylock()上锁失败(返回0), 再调用__down_read()去上锁。
先来看__down_read_trylock().
__down_read_trylock() -> atomic_long_try_cmpxchg_acquire() -> atomic_try_cmpxchg_acquire()
这个atomic_try_cmpxchg_acquire()在include/linux/atomic-fallback.h中定义,它有两个定义处,用哪个依赖于对应architecture的atomic.h中定义。
我们来看armv7下的atomic.h中是否定义了atomic_try_cmpxchg_relaxed.
所以,在armv7下,atomic_try_cmpxchg_relaxed没有#define.故,__down_read_trylock() -> atomic_long_try_cmpxchg_acquire() -> atomic_try_cmpxchg_acquire()
这个atomic_try_cmpxchg_acquire()在include/linux/atomic-fallback.h中926行定义,如下:
故,atomic_try_cmpxchg_acquire() -> atomic_cmpxchg_acquire()。
atomic_cmpxchg_acquire() 定义如下:
故, atomic_cmpxchg_acquire() -> atomic_cmpxchg_relaxed().
所以,__down_read_trylock() -> atomic_long_try_cmpxchg_acquire() ->
atomic_try_cmpxchg_acquire() -> atomic_cmpxchg_acquire() -> atomic_cmpxchg_relaxed(),
atomic_cmpxchg_relaxed()定义在arch/arm/include/asm/atomic.h, 如下:
117行:加载信号量计数 sem->count.counter.
118行:清零res.
119行:比较信号量计数值和参数old的大小.
120行:信号量计数值和参数old相等,则将参数new更新到信号量计数。
126行:返回信号量计数更新前的值。
从实现代码看出,__down_read_trylock()是判断信号量计数是否为初值RWSEM_UNLOCKED_VALUE(0), 是的话,则将read计数设置为1(就是1<<8)。
当__down_read_trylock()失败时,则调用__down_read().
__down_read()先调用rwsem_read_trylock().
__down_read() -> rwsem_read_trylock() -> atomic_long_add_return_acquire() ->
atomic_add_return_acquire() -> atomic_add_return_relaxed().
atomic_add_return_relaxed()定义如下:
65行:加载信号量计数sem->count.counter
66行:sem->count.counter + (1 << 8)
67行:新值更新到sem->count.counter
74行:返回sem->count.counter新值。
所以,__down_read()-> rwsem_read_trylock()就是将信号量读计数+1.
rwsem_read_try_lock()拿到更新read计数后的新值时,判断锁是否存在写者或者等待者waiter(279行),存在写者或者等待者waiter时,返回0;否则返回1.
当信号量有写者时,__down_read() –> rwsem_down_read_slowpath().
rwsem_down_read_slowpath()将当前task放入信号量等待队列sem->wait_list中,并设置为TASK_UNINTERRUPTIBLE状态,同时,将信号量计数sem->count减1.
好了,读信号量获取介绍完了,下面介绍读信号量释放: up_read().
up_read() -> __up_read() -> atomic_long_add_return_release() -> atomic_add_return_release() ->
atomic_add_return_relaxed()
atomic_add_return_relaxed() 定义如下:
可见,up_read() -> __up_read()就是将读计数-1。
__up_read()将读计数减1后,判断是等待队列是否有等待者wait_list,有则调用rwsem_wake()将去唤醒。如果等待队列中第一个是写占有请求,则唤醒这个写占有请求者去占有信号量;如果等待队列中第一个不是写等待者,则优先将等待队列中读请求者全部唤醒(最多0x100个)
信号量读操作介绍完了,来看看信号量写。
写信号量获取: down_write().
down_write()先调用__down_write_trylock()获取信号量,如果失败(返回0),则再调用__down_write().
atomic_cmpxchg_acquire() 定义如下:
所以,down_write() -> __down_write_trylock() -> atomic_long_try_cmpxchg_acquire() ->
atomic_try_cmpxchg_acquire() -> atomic_cmpxchg_acquire() -> atomic_cmpxchg_relaxed().
atomic_cmpxchg_relaxed()定义在arch/arm/include/asm/atomic.h,
如下:
117行:加载信号量计数 sem->count.counter.
118行:清零res.
119行:比较信号量计数值和参数old的大小.
120行:信号量计数值和参数old相等,则将参数new更新到信号量计数。
126行:返回信号量计数更新前的值。
down_write() -> __down_write_trylock()就是先判断信号量是否为初值RWSEM_UNLOCKED_VALUE(0)。是的话,则设置信号量count为RWSEM_WRITER_LOCKED (1<<0). 否则__down_write_trylock()就是获取信号量失败,down_write()则调用__down_write()去获取写信号量。
__down_write()先调用atomic_long_try_cmpxchg_acquire(), 这个操作和
__down_write_trylock()一样。
atomic_cmpxchg_acquire() 定义如下:
down_write() -> __down_write() -> atomic_long_try_cmpxchg_acquire() ->
atomic_try_cmpxchg_acquire() -> atomic_cmpxchg_acquire() -> atomic_cmpxchg_relaxed().
atomic_cmpxchg_relaxed()定义在arch/arm/include/asm/atomic.h, 如下:
117行:加载信号量计数 sem->count.counter.
118行:清零res.
119行:比较信号量计数值和参数old的大小.
120行:信号量计数值和参数old相等,则将参数new更新到信号量计数。
126行:返回信号量计数更新前的值。
down_write() -> __down_write()就是判断信号量是否为初值RWSEM_UNLOCKED_VALUE(0)。是的话,则设置信号量count为RWSEM_WRITER_LOCKED (1<<0),否则就是获取信号量失败,则进入rwsem_down_write_slowpath().
1166-1175行:将当前写信号量请求者添加到等待队列sem->wait_list.
1178-1196行:如果当前写信号量请求者不是第一个等待者,则唤醒等待队列前面的task,
读等待者优先。
1207行:设置当前有等待者flag: RWSEM_FLAG_WAITERS
1212行:设置当前进程状态为不可中断休眠状态TASK_UNINTERRUPTILE。
1214行:调用rwsem_try_write_lock(), 尝试去lock信号量。
1238行:重新调度,让出cpu。
好了,写信号量获取down_write()介绍完了,来介绍最后一个写信号量释放: up_write().
up_write() -> __up_write() -> atomic_long_fetch_add_release()
up_write() -> __up_write() -> atomic_long_fetch_add_release() -> atomic_fetch_add_release() ->
atomic_featch_add_relaxed()
atomic_featch_add_relaxed()的定义如下:
86行:加载sem->count.counter
87行:sem->count.counter + i
88行:保存sem->count.counter + i到sem->count.counter。
95行:返回值result为sem->count.counter更新前的值。
故,up_write() -> __up_write()就是删除write_lock, 即写者不再占有信号量。
当写者释放信号量时,如果该信号量有等待者waiters, 则调用rwsem_wake()去唤醒第一个写等待者;如果第一个不是写等待者,则优先唤醒等待队列中所有读等待者(最多0x100个)。
这里的唤醒没有reader优先,和rwsem_down_write_slowpath()不一样。
好了,我们讲解完了读写信号量。现在总结一下:
- 当信号量当前被读者占有时,允许其他task读lock, 每lock一次,读计数+1,读lock次数不限制(当然在计数器不溢出范围内),也就是允许多个读存在。此时写占有请求被阻止,写占有请求者task将被放入等待队列sem->wait_list. 这之后来的读请求,都将被放入等待队列(因为被设置了waiters flag)。当信号量所有读占有者释放信号量时,写占有请求者task将被唤醒,去占有信号量。
- 当信号量当前被写者占有时,任何其他读请求者或写请求者,都将被组织,请求者被放入等待队列sem->wait_list.当信号量写占有者释放信号量时,如果有信号量占有请求,则去做唤醒动作。如果等待队列第一个是写占有请求,则将其唤醒去占有信号量。如果等待队列第一个不是写占有请求,则优先将所有读占有请求者(最多0x100个)唤醒去占有信号量。