跳到主要内容位置

Linux驱动-内核互斥量mutex的实现

“一图胜千言”

mutex内核结构体#

struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
struct task_struct *owner;
#endif
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};

初始化 mutex 之后,就可以使用 mutex_lock 函数或其他衍生版本来获取信号量,使用 mutex_unlock函数释放信号量。

owner 有 2 个用途:debug(CONFIG_DEBUG_MUTEXES)spin_on_owner(CONFIG_MUTEX_SPIN_ON_OWNER),调试和优化。

spin on owner:一个获得mutex的进程,可能很快就会释放掉 mutex;此时,当前获得 mutex 的进程是在别的 CPU 上运行、并且“我”是唯一等待这个 mutex 的进程。在这种情况下,那“我”就原地 spin 等待吧:懒得去休眠了,休眠又唤醒就太慢了。

mutex_lock函数的实现#

1.fastpath#

kernel/locking/mutex.c

void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
/*
* The locking fastpath is the 1->0 transition from
* 'unlocked' into 'locked' state.
*/
__mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
mutex_set_owner(lock);
}
static inline void
__mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
if (unlikely(atomic_dec_return_acquire(count) < 0))
fail_fn(count);
}

这里,如果count=1,dec后为0,成功获得mutex,if判断不成立,直接返回;如果dec后count<0,则使用slowpath,休眠等待mutex。

2.slowpath#

如果mutex当前值是0或负数,则需要调用__mutex_lock_slowpath慢慢处理:可能会休眠等待。

__visible void __sched
__mutex_lock_slowpath(atomic_t *lock_count)
{
struct mutex *lock = container_of(lock_count, struct mutex, count);
__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0,
NULL, _RET_IP_, NULL, 0);
}

__mutex_lock_common函数代码比较长,这里看关键代码:

spin_lock_mutex(&lock->wait_lock, flags); //获得spinlock
/*
* Once more, try to acquire the lock. Only try-lock the mutex if
* it is unlocked to reduce unnecessary xchg() operations.
*/
//如果count==1, 就不要需要进入等待了
if (!mutex_is_locked(lock) &&
(atomic_xchg_acquire(&lock->count, 0) == 1))
goto skip_wait;
debug_mutex_lock_common(lock, &waiter);
debug_mutex_add_waiter(lock, &waiter, task);
/* add waiting tasks to the end of the waitqueue (FIFO): */
list_add_tail(&waiter.list, &lock->wait_list);
waiter.task = task;
lock_contended(&lock->dep_map, ip);
for (;;) {
/*
* Lets try to take the lock again - this is needed even if
* we get here for the first time (shortly after failing to
* acquire the lock), to make sure that we get a wakeup once
* it's unlocked. Later on, if we sleep, this is the
* operation that gives us the lock. We xchg it to -1, so
* that when we release the lock, we properly wake up the
* other waiters. We only attempt the xchg if the count is
* non-negative in order to avoid unnecessary xchg operations:
*/
if (atomic_read(&lock->count) >= 0 &&
(atomic_xchg_acquire(&lock->count, -1) == 1))
break;
/*
* got a signal? (This code gets eliminated in the
* TASK_UNINTERRUPTIBLE case.)
*/
if (unlikely(signal_pending_state(state, task))) {
ret = -EINTR;
goto err;
}
if (use_ww_ctx && ww_ctx->acquired > 0) {
ret = __ww_mutex_lock_check_stamp(lock, ww_ctx);
if (ret)
goto err;
}
__set_task_state(task, state);
/* didn't get the lock, go to sleep: */
spin_unlock_mutex(&lock->wait_lock, flags);
schedule_preempt_disabled();
spin_lock_mutex(&lock->wait_lock, flags);
}

mutex_unlock函数的实现#

mutex_unlock函数中也有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。

代码如下:

void __sched mutex_unlock(struct mutex *lock)
{
#ifndef CONFIG_DEBUG_LOCK_ALLOC
if (__mutex_unlock_fast(lock))
return;
#endif
__mutex_unlock_slowpath(lock, _RET_IP_);
}
EXPORT_SYMBOL(mutex_unlock);

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就可以了。

2.slowpath#

如果mutex的值加1后还是小于等于0,就表示有人在等待mutex,需要去wait_list把它取出唤醒,这需要用到slowpath的函数:__mutex_unlock_slowpath。

__mutex_unlock_common_slowpath函数代码如下,主要工作就是从wait_list中取出并唤醒第1个进程: