互斥量mutex的实现-07
1 mutex的内核结构体
mutex的定义及操作函数都在Linux内核文件include\linux\mutex.h中定义,如下:
(count可以取值1(unlocked),0(locked),-1(locked 且有waiter))
初始化mutex之后,就可以使用mutex_lock函数或其他衍生版本来获取信号量,使用mutex_unlock函数释放信号量。我们只分析mutex_lock、mutex_unlock函数的实现。
这里要堪误一下:前面的视频里我们说mutex中的owner是用来记录获得mutex的进程,以后必须由它来释放mutex。这是错的!
从上面的代码可知,owner并不一定存在!
owner有2个用途:debug(CONFIG_DEBUG_MUTEXES)或spin_on_owner(CONFIG_MUTEX_SPIN_ON_OWNER)。
什么叫spin on owner?
我们使用mutex的目的一般是用来保护一小段代码,这段代码运行的时间很快。这意味着一个获得mutex的进程,可能很快就会释放掉mutex。
针对这点可以进行优化,特别是当前获得mutex的进程是在别的CPU上运行、并且“我”是唯一等待这个mutex的进程。在这种情况下,那“我”就原地spin等待吧:懒得去休眠了,休眠又唤醒就太慢了。
所以,mutex是做了特殊的优化,比semaphore效率更高。但是在代码上,并没有要求“谁获得mutex,就必须由谁释放mutex”,只是在使用惯例上是“谁获得mutex,就必须由谁释放mutex”。
只有拥有这个owner,才能识别当前获取mutex的进程是在别的CPU上运行
2 mutex_lock函数的实现
2.1 fastpath
mutex的设计非常精巧,比semaphore复杂,但是更高效。
首先要知道mutex的操作函数中有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。
怎么理解?
这需要把metex中的count值再扩展一下,之前说它只有1、0两个取值,1表示unlocked,0表示locked,还有一类值“负数”表示“locked,并且可能有其他程序在等待”。
代码如下:
先看看fastpath的函数:__mutex_fastpath_lock,这个函数在下面2个文件中都有定义:
include/asm-generic/mutex-xchg.h
include/asm-generic/mutex-dec.h
使用哪一个文件呢?看看arch/arm/include/asm/mutex.h,内容如下:
#if __LINUX_ARM_ARCH__ < 6
#include <asm-generic/mutex-xchg.h>
#else
#include <asm-generic/mutex-dec.h>
#endif
所以,对于ARMv6以下的架构,使用include/asm-generic/mutex-xchg.h中的__mutex_fastpath_lock函数;对于ARMv6及以上的架构,使用include/asm-generic/mutex-dec.h中的__mutex_fastpath_lock函数。这2个文件中的__mutex_fastpath_lock函数是类似的,mutex-dec.h中的代码如下:
大部分情况下,mutex当前值都是1,所以通过fastpath函数可以非常快速地获得mutex。
2.2 slowpath
如果mutex当前值是0或负数,则需要调用__mutex_lock_slowpath慢慢处理:可能会休眠等待。
__mutex_lock_common函数也是在内核文件kernel/locking/mutex.c中实现的,下面分段讲解。
① 分析第一段代码:
② 分析第二段代码:
③ 分析第三段代码:
这个wait_list是FIFO(Firt In Firs Out),谁先排队,谁就可以先得到mutex。
④ 分析第四段代码:for循环,这是重点
(如果count==1,其大于等于0,接着调用atomic_xcha_acquire()把count设为-1,函数返回1,因为结果等于1,执行break;
如果count==0,其大于等于0,接着调用atomic_xcha_acquire()把count设为-1,函数返回0,因为结果不等于1,继续执行;
如果count==-1,它表示本来就被别人lock了,不用再设为-1,继续执行;)
schedule_preempt_disabled()主动发起调度进入休眠
⑤ 分析第五段代码:收尾工作
3 mutex_unlock函数的实现
mutex_unlock函数中也有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。
代码如下:
3.1 fastpath
先看看fastpath的函数:__mutex_fastpath_lock,这个函数在下面2个文件中都有定义:
include/asm-generic/mutex-xchg.h
include/asm-generic/mutex-dec.h
使用哪一个文件呢?看看arch/arm/include/asm/mutex.h,内容如下:
#if __LINUX_ARM_ARCH__ < 6
#include <asm-generic/mutex-xchg.h>
#else
#include <asm-generic/mutex-dec.h>
#endif
所以,对于ARMv6以下的架构,使用include/asm-generic/mutex-xchg.h中的__mutex_fastpath_lock函数;对于ARMv6及以上的架构,使用include/asm-generic/mutex-dec.h中的__mutex_fastpath_lock函数。这2个文件中的__mutex_fastpath_lock函数是类似的,mutex-dec.h中的代码如下:
大部分情况下,加1后mutex的值都是1,表示无人等待mutex,所以通过fastpath函数直接增加mutex的count值为1就可以了。
如果mutex的值加1后还是小于等于0,就表示有人在等待mutex,需要去wait_list把它取出唤醒,这需要用到slowpath的函数:__mutex_unlock_slowpath。
3.2 slowpath
如果mutex当前值是0或负数,则需要调用__mutex_unlock_slowpath慢慢处理:需要唤醒其他进程。
__mutex_unlock_common_slowpath函数代码如下,主要工作就是从wait_list中取出并唤醒第1个进程: