持续创作,加速成长!这是我参与「掘金日新计划 · 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"
);