本文共 2557 字,大约阅读时间需要 8 分钟。
互斥锁,顾名思义,就是互斥的,独占的,只能有一个进程占有锁,其他进程都得等待锁占有者释放互斥锁,才有可能占有锁。
以下的内容是基于linux-3.11.1(arm)内核代码来讲解内核中mutex的实现。
mutex的定义:
linux-3.11.1/include/linux/mutex.h
其中, count表示空闲资源的数目,初始值为1,表示只有一个资源可用,即只能一个进程可以用。
count: 1 表示可以lockcount: 0 表示锁被某个进程lock了,且没有其他进程来lock
count: -1表示锁被某个进程lock了,且至少有一个进程来lock。
初始化API:mutex_init()
linux-3.11.1/include/linux/mutex.h
linux-3.11.1/kernel/mutex.c
__mutex_init()初始化lock->count为1,初始化lock->ower为NULL。以及初始化等待spin_lock和等待队列wait_list.
上锁API:mutex_lock()
linux-3.11.1/kernel/mutex.c
我们不考虑CONFIG_PREEMPT_VOLUNTARY=y,那么might_sleep()为空。
linux-3.11.1/include/asm-generic/mutex-xchg.h
atomic_xchg(a,b)是将原子变量a设置为b,并将设置之前a的旧值返回。
我们看以下arm下这个的实现:
linux-3.11.1/arch/arm/include/asm/atomic.h
linux-3.11.1/arch/arm/include/asm/cmpxchg.h
好了,我们回到__mutex_fastpath_lock()。
28行,如果本次lock之前没有其他进程占用锁,则,atomic_xchg()返回1,count被设置为0,表示锁被占用(第一个进程成功lock);否则,atomic_xchg()返回0或负数。28行如果失败,说明之前有其他进程占用锁了,那个占用锁的进程会将count设置为0,然后34行被调用,将count设置为-1,表明有其他进程等待这个mutex锁释放。
注意,在28行到34行之间,有可能那个占有锁的进程释放了锁,则34行有可能锁成功,即atomic_xchg()在设置count为-1的时候,返回1(锁占用成功)。
如果34行返回值不是1,则,count被设置为-1了,然后调用fail_fn(),即调用__mutex_lock_slowpath()
linux-3.11.1/kernel/mutex.c
__mutex_lock_slowpath()调用了__mutex_lock_common()。
linux-3.11.1/kernel/mutex.c
418行: 禁内核抢占
514行: spin lock锁上(多核在这个地方是互斥的,并且排队)
520-521行: 将当前进程加到锁的等待队列lock->wait_list,这个入等待队列是排队的
528-550行: 循环检查锁计数是否为1,为1表明锁可占用,跳出循环,否则将当前进程设置为uninterrupt状态(只能被锁可用唤醒,不能被信号唤醒,不能被Kill),然后释放spin lock,调用schedule_preemt_disabled()去使能内核抢占,并主动调用schedule()让出cpu。当锁占有者释放锁的时候,会唤醒等待队列lock->wait_list中的第一个task. 这个task被唤醒后,从schedule_preemt_disable() -> schedule()返回,然后禁内核抢占。
549行,spin lock锁上。
当task占有锁从循环中break出来后,检查等待队列是否还有等待的task,如果没有,则将锁的值设置为0,然后释放spin lock,使能内核抢占。这样就完成了锁的占有操作。
锁的释放API: mutex_unlock()
linux-3.11.1/kernel/mutex.c
linux-3.11.1/arch/arm/include/asm/cmp-xchg.h
mutex_unlock() -> __mutex_fastpath_unlock()将count设置为1,然后判断设置之前count是否是0,不是0,说明至少有一个task在等待锁。然后调用fail_fn(),即调用__mutex_unlock_slowpath()。
这个时候(70行之后),可能有其他task会来上锁,因为70行将count设置为1了。
我们继续看__mutex_unlock_slowpath()。
linux-3.11.1/kernel/mutex.c
__mutex_unlock_slowpath() -> __mutex_unlock_common_slowpath在723行spin lock锁上。
725-734行:判断等待队列是否有task在等待该锁,有则唤醒第一个task。这里只是唤醒,被唤醒的task还没run,这样它就需要和这个点之后来的task竞争锁了。
736行: spin lock释放。
所以,函数并没有处理锁计数刚恢复1时被其他刚来的task占用的”插队问题”,使用mutex_lock尤其要注意这一点,即mutex()不保证lock的先后次序。
由于mutex_lock()获取锁失败后,进入uninterrupt状态,只能被锁可用唤醒,不能被信号唤醒,也不能被kill,故还存在下面两个api:
1. mutex_lock_interruptible() //获取锁失败,进入interrupt状态,可以被锁可用唤醒,也可以被信号唤醒。
2. mutex_lock_killable() //获取锁失败,进入wakekill | uninterrupt状态,只能被kill信号或锁可用唤醒。
最后,由于mutex()获取锁失败时,会调用schedule()让出cpu,故mutex不能用于中断程序中。
转载地址:http://gtlci.baihongyu.com/