分类: LINUX
2014-08-09 23:21:49
法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友()发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。
1.1 读写自旋锁
读写自旋锁是一种特殊的自旋锁。它与自旋锁的区别:自旋锁仅仅同时允许一个读者或者写者进入临界保护区。而读写自旋锁允许多个读者同时进入临界保护区,但是不允许多个写者同时进入临界保护区,也不允许读者和写者同时进入临界保护区。
读写自旋锁的数据结构与自旋锁完全一致。但是对lock字段有了不同的解释:
/**
* 体系结构相关的读写自旋锁。
* 对ARM来说,就是一个int。其中第31位,即符号位表示是否存在写者。其余位表示读者的数量。
*/
typedef struct {
volatile unsigned int lock;
} arch_rwlock_t;
1.1.1 获取和释放读锁
申请读锁的函数是read_lock,它的流程与spin_lock几乎完全一致,唯一不同的是最后调用体系结构相关的函数是arch_read_lock而不是arch_spin_lock:
/**
* 获取读自旋锁。
*/
static inline void arch_read_lock(arch_rwlock_t *rw)
{
unsigned long tmp, tmp2;
__asm__ __volatile__(
"1: ldrex %0, [%2]\n"/* 从lock字段中排它性的装载到寄存器 */
" adds %0, %0, #1\n"/* 将lock字段的值加1,并更新cpsr标志 */
" strexpl %1, %0, [%2]\n"/* 如果加1后是正值,表示没有写者。则排它性将值存回内存 */
WFE("mi")
" rsbpls %0, %1, #0\n"/* 如果加1后是正值,%1中保存的是strexpl的结果。将指令结果取反并更新cpsr标志 */
" bmi 1b"/* 不论是adds后,寄存器中的值变为负,还是在回写内存时不成功,此时的cpsr标志都是小于0,这两种情况都表示获取读锁不成功,继续死循环等待读锁 */
: "=&r" (tmp), "=&r" (tmp2)
: "r" (&rw->lock)
: "cc");
/**
* 运行到这里,已经获得了读锁。
* 这个屏障即可以防止读锁后面的内存操作与锁前面的操作乱序,也防止编译乱序。
*/
smp_mb();
}
释放读自旋锁的过程与spin_unlock也是类似的,最终调用的体系结构相关的函数是而不是arch_spin_unlock:
/**
* 释放读自旋锁。
*/
static inline void arch_read_unlock(arch_rwlock_t *rw)
{
unsigned long tmp, tmp2;
/**
* 这个内存屏障是为了防止锁前面的内存操作与锁内存操作乱序。使得读取的内存是错误的值。
*/
smp_mb();
__asm__ __volatile__(
"1: ldrex %0, [%2]\n"/* 从lock字段排它性装载数据到寄存器 */
" sub %0, %0, #1\n"/* 将lock字段的值减1 */
" strex %1, %0, [%2]\n"/* 将lock字段的值排它性写入内存 */
" teq %1, #0\n"/* 排它性写入内存的结果保存在%1中,将它与0比较 */
" bne 1b"/* 如果%1中的值不为0,说明在写入过程中,与其他核产生了冲突,大家都试图保存lock字段的值。返回到标号1处,重试 */
: "=&r" (tmp), "=&r" (tmp2)
: "r" (&rw->lock)
: "cc");
if (tmp == 0)
dsb_sev();
}
1.1.2 获取和释放写锁
申请写锁的函数是write_lock,它的流程与spin_lock几乎完全一致,唯一不同的是最后调用体系结构相关的函数是arch_write_lock而不是arch_spin_lock:
/**
* 申请写自旋锁。
*/
static inline void arch_write_lock(arch_rwlock_t *rw)
{
unsigned long tmp;
__asm__ __volatile__(
"1: ldrex %0, [%1]\n"/* 从内存中装载lock字段的值到寄存器 */
" teq %0, #0\n"/* 只有当lock字段为0时,才表示没有读者和写者,此时才能成功获取写锁 */
WFE("ne")
" strexeq %0, %2, [%1]\n"/* 当lock字段为0时,将0x80000000写入内存,0x80000000表示存在一个写者,没有读者 */
" teq %0, #0\n"
" bne 1b"/* 如果排它性写入失败,或者lock字段不为0,则申请写锁失败,返回到标号1处重试 */
: "=&r" (tmp)
: "r" (&rw->lock), "r" (0x80000000)
: "cc");
smp_mb();
}
释放写锁的函数是write_unlock,它的流程与spin_unlock几乎完全一致,唯一不同的是最后调用体系结构相关的函数是arch_write_unlock而不是arch_spin_unlock:
/**
* 释放写锁
*/
static inline void arch_write_unlock(arch_rwlock_t *rw)
{
smp_mb();
__asm__ __volatile__(
"str %1, [%0]\n"/* 由于只会存在一个写者,因此释放写锁的过程不可能与其他锁原语冲突,因此这里不需要进行排它性装载和存储,直接向lock字段写入0即可 */
:
: "r" (&rw->lock), "r" (0)
: "cc");
dsb_sev();
}