【精通内核】Linux内核自旋锁读锁

319 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

前言

📫作者简介小明java问道之路,专注于研究 Java/ Liunx内核/ C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。

📫热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长

🏆InfoQ签约作者、CSDN专家博主/后端领域优质创作者/内容合伙人、阿里云专家/签约博主、51CTO专家🏆

🔥如果此文还不错的话,还请👍关注 、点赞 、收藏三连支持👍一下博主~


本文导读

CPU 对于任务切换是通过时钟中断来控制的,只要我们将中断屏蔽,就可以保证在当前CPU中的所有操作都不会被中断,从而保证了原子性。在单核CPU上, 通过操作 EFLAGS 寄存器相当于保存EFLAGS表示中断。

一、Linux内核读写锁源码解析

从上述内容中可以看到,自旋锁在 Linx 中的实现就是不停地对 lock 变量的减 1,然后在循环等待它变为1时,再次尝试获取锁;而释放锁较为简单,只需要将其设置为!即可。我们知道在读存在的情况下,使用同一把锁会导致粒度较大,这时通常将其实现为读写锁,同样,内核中也存在一个读写自旋锁。现在查看它的实现原理。

上述的自旋锁都是互斥自旋锁,这里定义了一个读写自旋锁,即支持多个读共享,写者互斥(和互斥自旋锁一样的结构)。

宏定义获得一个初始化的读写自旋锁,宏定义RW_LOCK_BIAS 为0x0100 0000。我们以int 类型的lock的第7位作为写锁标志位。

宏定义初始化的读写自旋锁。

宏定义判断读写自旋锁是否上锁了,也就是说看看lock是否为RW_LOCK_BIAS。

// 上述的自旋锁都是互斥自旋锁,这里定义了一个读写自旋锁,即支持多个读共享,写者互斥 
typedef struct{
    volatile unsigned int lock;
}rwlock_t;   // 和互斥自旋锁一样的结构

// 宏定义获得一个初始化的读写自旋锁
#define RW_LOCK UNLOCKED(rwlock_t){RWLOCKBIAS}
// 宏定义RW_LOCK_BIAS 为0x0100 0000。我们以int 类型的lock的第7位作为写锁标志位

// 宏定义初始化的读写自旋锁
#define rwlock_init(x)	
do { 
    *(x) = RW_LOCK_UNLOCKED; 
} while(0)	

// 宏定义判断读写自旋锁是否上锁了,也就是说看看lock是否为RW_LOCK_BIAS
#define rwlock_is_locked(x) ((x)->lock != RW LOCK_BIAS)

二、Linux内核自旋锁读锁

首先获取读锁,对rw的指针的lock变量原子性减1。

如果小于 0,则表示有别的线程获取写锁,因为读写锁的初始值为0x0100 0000,只有获取写锁后,才会将其减 0x0100 0000,结果为0,减1后自然变为负数。这时向前跳到标号2处,执行 helper,即执行_read_lock_failed。

上读锁失败后的处理逻辑_read_lock_failed 代码实现,原子性增加 eax 中也就是 lock 变量的值,因为上面的内联汇编已经将 lock 地址放入eax 中,即"a"(rw)操作。

通过cmpl 和js指令看看lock的值是否大于或等于1,如果不是,则跳到标号1处循环比较。

最后原子性减 lock 的值,如果小于 0,则继续执行_read_lock_failed,否则视为获得自旋读锁。

// 获取读锁
static inline void_raw_read_lock(rwlock_t*rw){
    __build_read_lock(rw, "___read_lock_failed");
}

#define_build_read_lock(rw,helper) 
do{
    build_read_lock_ptr(rw,helper);
} while (0)

#define_build_read_lock_ptr(rw, helper)
asm volatile(
    LOCK "subl $1,(%0)"	//对rw的指针的lock变量原子性减1	
    // 如果小于 0,则表示有别的线程获取写锁,因为读写锁的初始值为0x0100 0000,
    // 只有获取写锁后,才会将其减 0x0100 0000,结果为0,减1后自然变为负数。
    // 这时向前跳到标号2处,执行 helper,即执行_read_lock_failed
    "js 2f"
    "1:"
    LOCK SECTION_START("")
    "2: call " helper ""
    "jmp 1b"
    LOCK SECTION END
    ::"a"(rw):"memory")

// 上读锁失败后的处理逻辑_read_lock_failed 代码实现 
asm(
    "_read_lock_failed:"
    // 原子性增加 eax 中也就是 lock 变量的值
    // 因为上面的内联汇编已经将 lock 地址放入eax 中,即"a"(rw)操作
    LOCK "incl (%eax)"
    "1: rep; nop"
    "cmpl	$1,(%eax)" // 通过cmpl 和js指令看看lock的值是否大于或等于1,如果不是,则跳到标号1处循环比较
    "js 1b"
    LOCK "decl	(%eax)” // 最后原子性减 lock 的值,如果小于 0,则继续执行_read_lock_failed,否则视为获得自旋读锁
    "js _read_lock_failed""ret"
);