【精通内核】Linux内核自旋锁实现原理与源码解析_linux自旋锁底层实现原理

// 宏定义创建一个值为1的自旋锁
#define SPIN_LOCK_UNLOCKED(spinlock_()(1}

// 宏定义自旋锁初始化
#define spin_lock_init(x)
do {
*(x) = SPIN_LOCK_UNLOCKED;
} while(0)

// 宏定义判断自旋锁是否被锁定,通过将 lock 变量转为 volatile signed char 看看是否小于或等于0
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->lock) <= 0)

// 宏定义等待自旋锁释放,这里的 barrier()为前述内容曾讨论过的编译器屏障
#define spin_unlock_wait(x)
do{
barrier();
} while(spin_is_locked(x))


## **二、Linux内核源码实现**


自旋锁就是一个无符号整型值的 lock 变量,通过 volatile 的修饰,可以让它避免被编译器优化。接下来,查看获取自旋锁的代码实现。



// 宏定义获取自旋锁内联汇编代码
#define spin_lock_string
“1:”
“lock ; decb %0” // 对lock 变量自减
“js 2f” // 如果小于O,则跳转到前面标号2处
LOCK_SECTION_START(“”)
“2:”
“rep;nop” // 执行空操作
“cmpb $0, %0” // 比较lock的值是否为0
“jle 2b” // 如果非 0,则继续跳到标号2处循环判断
“jmp 1b” // 如果等于0,则跳到标号1处重新开始尝试获取锁
LOCK_SECTION_END

// 直接上锁,如果上锁失败,则进入前面的 spin_lock_string中,自旋等待
static inline void_raw_spin_lock(spinlock_t *lock) {
asm___volatile(
spin_lock_string
:“=m” (lock->lock): : “memory”);
}

// 下面为自旋锁释放锁的代码实现。
#define spin_unlock_string
“movb $1,%0” // 将立即数 1 设置为 lock的值
:“=m” (lock->lock)::“memory”

static inline void_raw_spin_unlock(spinlock_t *lock) {
asm___volatile(
spin_unlock_string);
}


## **三、Linux内核自旋读写锁源码解析**


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



// 上述的自旋锁都是互斥自旋锁,这里定义了一个读写自旋锁,即支持多个读共享,写者互斥
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)


### **1、Linux内核自旋锁读锁**



// 获取读锁
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”
);


### **2、Linux内核自旋锁写锁**



// 获取写锁
static inline void_raw_write_lock(rwlock_t*rw) {
__build_write_lock(rw, “__write_lock_failed”);
}

#define__build_write_lock(rw, helper)
do {
__build_write_lock_ptr(rw, helper);
} while (0)

#define_build_write lock_ptr(rwhelper)
//原子性对lock变量执行宏定义RW_LOCK_BIAS=0x01000000相减运算
asm volatile(LOCK"subl $“RWLOCKBIAS_STR”,(%O)"
//如果不为0,则跳到标号2处,执行__write_lock_failed 代码
“jnz 2r”
“1:”
LOCK SECTION_START()
"2: call " helper “”
“jmp 1b”
LOCK SECTION_END
::“a”(rw) : “memory”)

// 获取写锁失败,回调_write_lock_failed

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值