Linux内核学习——5. 互斥量的实现

互斥量的实现


5、互斥量mutex的实现

5.1 互斥量mutex的内核数据结构

数据结构为struct mutex结构体,定义在include\linux\mutex.h文件中:

  • 一次只能有一个进程能持有互斥锁
  • 只有锁的持有者能进行解锁操作;
  • 禁止多次解锁操作;
  • 禁止递归加锁操作;
  • mutex结构只能通过API进行初始化
  • mutex结构禁止通过memset或者拷贝来进行初始化
  • 已经被持有的mutex锁禁止被再次初始化
  • mutex不允许在硬件或软件上下文中使用;
struct mutex {
	atomic_long_t		owner;		/* 原子计数,用于指向锁持有者的task struct结构 */
	spinlock_t		wait_lock;		/* 自旋锁,用于wait_list链表的保护操作 */
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
	struct list_head	wait_list;	/* 链表,用于管理所有在该互斥锁上睡眠的进程 */
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};

对于这个optimistic_spin_queue osq,是一种优化手段:

  1. mutex 的目的一般是用来保护一小段代码,这段代码运行的时间很快。这意味着一个获得 mutex
    的进程,可能很快就会释放掉 mutex;
  2. 针对这点可以进行优化:特别是当前获得 mutex 的进程是在别的 CPU 上运行、并且**“ 我”是唯一等待这**
    个 mutex 的进程
  3. 在这种情况下,那**“ 我” 就原地 spin 等待**吧:懒得去休眠了,休眠又唤醒就太慢了;
  4. 所以, mutex 是做了特殊的优化,比 semaphore 效率更高

5.2 mutex_lock()函数的实现

其函数实现在kernel\locking\mutex.c文件中,可以看到其有两个实现道路:

  1. fastpath;
  2. slowpath;
void __sched mutex_lock(struct mutex *lock)
{
	might_sleep();

	if (!__mutex_trylock_fast(lock))	/* 1、先尝试fastpath */
		__mutex_lock_slowpath(lock);	/* 2、fast失败后,尝试slowpath */
}

以下摘抄自:【原创】Linux Mutex机制分析 - LoyenWang - 博客园 (cnblogs.com)

3.2 加锁流程分析

mutex_lock加锁来看一下大概的流程:

img

  • mutex_lock为了提高性能,分为三种路径处理,优先使用快速和中速路径来处理,如果条件不满足则会跳转到慢速路径来处理,慢速路径中会进行睡眠和调度,因此开销也是最大的。
3.2.1 fast-path
  • 快速路径是在__mutex_trylock_fast中实现的,该函数的实现也很简单,直接调用atomic_long_cmpxchg_release(&lock->owner, 0UL, curr)函数来进行判断,如果lock->owner == 0表明锁未被持有,将curr赋值给lock->owner标识curr进程持有该锁,并直接返回;
  • lock->owner不等于0,表明锁被持有,需要进入下一个路径来处理了;
3.2.2 mid-path
  • 中速路径和慢速路径的处理都是在__mutex_lock_common中实现的;
  • __mutex_lock_common的传入参数为(lock, TASK_INTERRUPTIBLE, 0, NULL, _RET_IP_, false),该函数中很多路径覆盖不到,接下来的分析也会剔除掉无效代码;

中速路径的核心代码如下:

img

  • 当发现mutex锁的持有者正在运行(另一个CPU)时,可以不进行睡眠调度,而可以选择自选等待,当锁持有者正在运行时,它很有可能很快会释放锁,这个就是乐观自旋的原因;
  • 自旋等待的条件是持有锁者正在临界区运行,自旋等待才有价值;
  • __mutex_trylock_or_owner函数用于尝试获取锁,如果获取失败则返回锁的持有者。互斥锁的结构体中owner字段,分为两个部分:1)锁持有者进程的task_struct(由于L1_CACHE_BYTES对齐,低位比特没有使用);2)MUTEX_FLAGS部分,也就是对应低三位,如下:
    1. MUTEX_FLAG_WAITERS:比特0,标识存在非空等待者链表,在解锁的时候需要执行唤醒操作;
    2. MUTEX_FLAG_HANDOFF:比特1,表明解锁的时候需要将锁传递给顶部的等待者;
    3. MUTEX_FLAG_PICKUP:比特2,表明锁的交接准备已经做完了,可以等待被取走了;
  • mutex_optimistic_spin用于执行乐观自旋,理想的情况下锁持有者执行完释放,当前进程就能很快的获取到锁。实际需要考虑,如果锁的持有者如果在临界区被调度出去了,task_struct->on_cpu == 0,那么需要结束自旋等待了,否则岂不是傻傻等待了。
    1. mutex_can_spin_on_owner:进入自旋前检查一下,如果当前进程需要调度,或者锁的持有者已经被调度出去了,那么直接就返回了,不需要做接下来的osq_lock/oqs_unlock工作了,节省一些额外的overhead;
    2. osq_lock用于确保只有一个等待者参与进来自旋,防止大量的等待者蜂拥而至来获取互斥锁;
    3. for(;;)自旋过程中调用__mutex_trylock_or_owner来尝试获取锁,获取到后皆大欢喜,直接返回即可;
    4. mutex_spin_on_owner,判断不满足自旋等待的条件,那么返回,让我们进入慢速路径吧,毕竟不能强求;
3.2.3 slow-path

慢速路径的主要代码流程如下:

img

  • for(;;)部分的流程可以看到,当没有获取到锁时,会调用schedule_preempt_disabled将本身的任务进行切换出去,睡眠等待,这也是它慢的原因了;
3.3 释放锁流程分析

img

  • 释放锁的流程相对来说比较简单,也分为快速路径与慢速路径,快速路径只有在调试的时候打开;
  • 慢速路径释放锁,针对三种不同的MUTEX_FLAG来进行判断处理,并最终唤醒等待在该锁上的任务;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值