写点什么

【精通内核】Linux 内核读锁实现原理与源码解析

  • 2022 年 9 月 13 日
    上海
  • 本文字数:2821 字

    阅读完需:约 9 分钟

前言

📫作者简介小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫 

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

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


本文导读

Linux 内核读锁实现原理,描述自旋锁时,已经顺带描述了读写自旋锁,所以本节将不再描述自旋锁的读写锁实现。读者是否能想到,既然自旋锁有相关的读写锁实现,信号量也应该有呢?答案是一定的。所以可以到,读写锁实际上是在原有锁上进行优化读写的操作。下面讨论源码实现。

一、Linux 内核读写锁核心结构源码解读

定义一个结构体 rw_semaphore 代表读写信号量,然后义一宏定义表明读写信号量的偏移值。具体源码如下。

struct rw_semaphore{    // 符号长整型,看到long类型,读者就知道,这又是将一个long类型长度大小切割成不同部分来使用的    // 由于使用i38632位来作为例子,因此这里long为32位,同样我们分割为高16位和低16位来使用     signed long count;        #define RWSEM UNLOCKED VALUE 0x0000 0000   // 无锁状态值为0	       #define RWSEM_ACTIVE_BIAS	 0x0000 0001   // 锁活动偏移值1	        // 锁活动位数为4(4个16进制)*4(一个16进制等于4个二进制)=16,即2^16次方个锁位    #define RWSEM ACTIVE MASK	Ox0000 ffff	        #define RWSEM_WAITING BIAS	(-0x00010000)	  // 锁等待偏移量,即 0xffff 0000	
#define RWSEM ACTIVE READ_BIAS RWSEM_ACTIVE_BIAS // 读锁偏移量
// 写锁偏移量0xffff0001 为负数 #define RWSEM ACTIVE WRITE BIAS (RWSEM_WAITING_BIAS+RWSEM_ACTIVE_BIAS) spinlock t wait_lock; // 保护等待链表的自旋锁 struct list_head wait_list; // 等待链表 };
//等待读写信号量的任务结构体 struct rwsem_waiter{ struct list_head list; struct task_struct *task; unsigned int flags; // 标志位声明为等待读锁还是写锁
#define RWSEM_WAITING_FOR_READ 0x00000001 #define RWSEM_WAITING_FOR_WRITE 0x00000002};
复制代码

二、Linux 内核读锁实现原理读写信号量初始化源码解读

读写信号量初始化时,将 count 初始化为 0,自旋锁也同时进行初始化,等待链表也相应地进行了初始化。

static inline void init_rwsem(struct rw_semaphore *sem) {    sem->count =RWSEM_UNLOCKED_VALUE;	// 初始值为0	    spin_lock_init(&sem->wait_lock);	// 初始化自旋锁	    INIT_LIST_HEAD(&sem->wait_list);	//初始化等待链表	}
复制代码

三、Linux 内核上读锁流程源码解读

我们先对 count 值原子性自增,如果成功则退出;否则应保存 ecx、edx,然后调用 rwsem_down_ read_failed 函数。

static inline void_down_read(struct rw_semaphore*sem) {    _asm_volatile_(        LOCK_PREFIX"incl (%%eax)"	// 原子性自增	        " js 2f"	// 如果自增后的值小于0,即写锁小于0,则上锁失败,并跳到标号2处	        "1:"        LOCK_SECTION_START("")        // 保存ecx、edx,然后调用rwsem_down_read_failed函数,最后恢复ecx、edx的值结束方法"2:"        " pushl %%ecx"         " pushl %%edx"        " call rwsem_down_read failed"	// 调用rwsem_down_read_failed 函数	        " popl %%edx"         " popl %%ecx"         " jmp 1b"        LOCK_SECTION END         : "=m"(sem->count)        : "a"(sem),"m"(sem->count)        : "memory","cc");}
// 执行上读锁失败的逻辑struct rw semaphore *rwsem_down_read failed(struct rw_semaphore*sem) { // 创建等待节点 struct rwsem_waiter waiter; waiter.flags=RWSEM_WAITING FOR_READ; //RWSEM WAITING BIAS-RWSEM ACTIVE BIAS=> 0xffff0000-0x0000 0001=0x fffefff rwsem_down_failed_common(sem, &waiter, RWSEM WAITING BIAS-RWSEM ACTIVE BIAS); return sem;}
// 读写信号量共用逻辑,等待锁释放static inline struct rw semaphore*rwsem_down_failed_common(struct rw_semaphore *sem, struct rwsem waiter*waiter, signed long adjustment) { truct task_struct *tsk= current; //获取当前任务PCB signed long count; set_task_state(tsk,TASK_UNINTERRUPTIBLE); //设置任务为不可中断阻塞状态 // 上自旋锁 spin_lock(&sem->wait_lock); // 将PCB和等待节点关联 waiter->task=tsk; list_add_tail(&waiter->list,&sem->wait_list); // 将等待节点插入等待队列末尾处 count = rwsem_atomic_update(adjustment, sem); // 原子性更新sem中的count值
// 如果不再有活动的锁,那唤醒之前等待的任务,因为这里可能有其他任务已经释放了锁
if (!(count &RWSEM_ACTIVE_MASK)) sem=__rwsem_do_wake(sem,1); // 传入1,表明可以唤醒写者 // 释放自旋锁 spin_unlock(&sem->wait_lock); // 到这一步开始等待锁释放,如果等待者的等待标志位为0,则直接退出 for (;;){ if(!waiter->flags) break; //否则调用调度器调度其他任务执行 schedule(); set_task_state(tsk, TASK_UNINTERRUPTIBLE);//设置任务状态为不可中断等待 } //到这一步任务获得了锁,可直接修改任务状态为TASK_RUNNING tsk->state=TASK RUNNING; return sem;}
复制代码

四、Linux 内核释放读锁流程

这里先对 count 进行原子性减 1,如果有写任务正在等待锁释放,那么看看是否还有其他读线程执行操作,如果有,则退出;否则唤醒等待的写任务。

static inline void__up_read(struct rw_semaphore*sem){s32 tmp =-RWSEM ACTIVE READ BIAS; asm volatile// 原子性减1, 返回旧值LOCK_PREFIX" xadd %%edx,(%%eax)"// 如果小于0,那么有写任务在等待锁释放,将跳到标号为2处执行" js	2f"	"1:"LOCK_SECTION_START("")"2:"// 对edx也就是上一步替换的lock值的低16位即 dx自减decw	%%dx"	// 如果不为0,则表明有其他任务在操作,可什么都不做,退出即可inz	1b"	// 否则保存ecx,调用rwsem_wake唤醒等待的任务pushl	%%eсx"	call	rwsem_wake"	popl	%%есx"	jmp	1b"	LOCK_SECTION_END"=m"(sem->count), "=d"(tmp)"a"(sem), "1"(tmp), "m"(sem->count)"memory",	"cc");}
复制代码

总结

Linux 内核读锁实现原理,描述自旋锁时,已经顺带描述了读写自旋锁,所以本节将不再描述自旋锁的读写锁实现。读者是否能想到,既然自旋锁有相关的读写锁实现,信号量也应该有呢?答案是一定的。所以可以到,读写锁实际上是在原有锁上进行优化读写的操作。

发布于: 刚刚阅读数: 5
用户头像

InfoQ签约作者/技术专家/博客专家 2020.03.20 加入

🏆InfoQ签约作者、CSDN专家博主/Java领域优质创作者、阿里云专家/签约博主、华为云专家、51CTO专家/TOP红人 📫就职某大型金融互联网公司高级工程师 👍专注于研究Liunx内核、Java、源码、架构、设计模式、算法

评论

发布
暂无评论
【精通内核】Linux内核读锁实现原理与源码解析_源码_小明Java问道之路_InfoQ写作社区