常规的理解,mutex,就是获取锁,没获取到就挂起。其实 read the fxxk source code之后,发现了其实内里还有很多细节,针对一些场景做优化。
总结起来,就是尽量站住cpu,不休眠下去,等别人释放锁的第一时间就接住。
先上数据结构:
struct mutex {
atomic_long_t owner;
raw_spinlock_t wait_lock;
#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
};
owner 原子结构,用法for() {cmpxchg},看似是一个变量,实则内里乾坤。owner分为两部分,高位部分是持有锁的task_struct指针,由于其字节对齐,低位没用,所以这里被抠出来三个bit。
* @owner: contains: 'struct task_struct *' to the current lock owner, * NULL means not owned. Since task_struct pointers are aligned at * at least L1_CACHE_BYTES, we have low bits to store extra state. * Bit0 indicates a non-empty waiter list; unlock must issue a wakeup. * Bit1 indicates unlock needs to hand the lock to the top-waiter * Bit2 indicates handoff has been done and we're waiting for pickup. */ #define MUTEX_FLAG_WAITERS 0x01 #define MUTEX_FLAG_HANDOFF 0x02 #define MUTEX_FLAG_PICKUP 0x04
要是光看这个注释,给你看蒙蔽。
bit0 是一个状态位,置位代表现在这个锁有人在等,也就是wait_list上挂着waiter。
bit1 和 bit2 其实是用来握手的通讯位。比如A持有锁,B等着锁,且B是wait_list最top的waiter,则B置位bit1 handoff,然后A释放锁的时候,发现handoff置位,则把owner设置成top waiter,并且把 bit 2置位pickup,bit1清掉。实现一个简单握手。当B检查到pickup位,且owner与自己current相等,则自己获取到锁咯。
这里引出一个逻辑,除了锁没人等,则owner是由持有锁的线程释放锁的时候,将owner设置成top waiter的task_struct, 而不是由申请锁的线程来修改owner。击鼓传花。
然后就是链表wait_list,用来放置waiter,既然是链表,就少不了wait_lock锁来锁住.wait_list上挂的数据结构如下,没啥好说的:
struct mutex_waiter { struct list_head list; struct task_struct *task; struct ww_acquire_ctx *ww_ctx; #ifdef CONFIG_DEBUG_MUTEXES void *magic; #endif };
几个关键函数注解:
static inline struct task_struct *__mutex_trylock_common(struct mutex *lock, bool handoff)
{
unsigned long owner, curr = (unsigned long)current;
owner = atomic_long_read(&lock->owner);
for (;;) { /* must loop, can race against a flag */
// 从owner变量内 提取那三个bit flag
unsigned long flags = __owner_flags(owner);
// 从owner变量内 提取当前持有者的 task_struct
unsigned long task = owner & ~MUTEX_FLAGS;
if (task) {// 如果当前有人持有锁
if (flags & MUTEX_FLAG_PICKUP) {//如果pick_up置位,则持有者释放锁,并交权咯
if (task != curr)//如果当前 task_struct 与本线程的不相等,则现在这个锁不是给我的
break;
flags &= ~MUTEX_FLAG_PICKUP;// 走到这里说明这个锁现在是给我的,把pickup清掉
} else if (handoff) { //如果 handoff 传参为1,则置位 handoff 位
//这里再说一下,只有当前task是wait_list的第一个,handoff才是1
if (flags & MUTEX_FLAG_HANDOFF)//已经置位了,不用管了
break;
flags |= MUTEX_FLAG_HANDOFF;// 没有置位,置位
} else {
break;//啥都没有,还是走吧
}
} else { //如果当前没人持有锁,就是没锁
//这里告警,flag有置位,但是还走到这里了,不应该啊,有bug
MUTEX_WARN_ON(flags & (MUTEX_FLAG_HANDOFF | MUTEX_FLAG_PICKUP));
task = curr;//直接把 task设置成本大爷的。
}
// 原子操作cmpxchg,可能会失败,所以要for(;;)
if (atomic_long_try_cmpxchg_acquire(&lock->owner, &owner, task | flags)) {
if (task == curr)//如果 task是自己,则获取成功咯,直接return NULL
return NULL;
break;
}
}
//跟我没关系,return 现在的task
return __owner_task(owner);
}
__mutex_add_waiter(struct mutex *lock, struct mutex_waiter *waiter,
struct list_head *list)
{
hung_task_set_blocker(lock, BLOCKER_TYPE_MUTEX);
debug_mutex_add_waiter(lock, waiter, current);
//无脑往队列tail一塞,如果是第一个则,置位waiter flag
list_add_tail(&waiter->list, list);
if (__mutex_waiter_is_first(lock, waiter))
__mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
}
static noinline
bool mutex_spin_on_owner(struct mutex *lock, struct task_struct *owner,
struct ww_acquire_ctx *ww_ctx, struct mutex_waiter *waiter)
{
bool ret = true;
lockdep_assert_preemption_disabled();
while (__mutex_owner(lock) == owner) {//保证这个锁控制权一直在这一个家伙手里
/*
* Ensure we emit the owner->on_cpu, dereference _after_
* checking lock->owner still matches owner. And we already
* disabled preemption which is equal to the RCU read-side
* crital section in optimistic spinning code. Thus the
* task_strcut structure won't go away during the spinning
* period
*/
barrier();
/*
* Use vcpu_is_preempted to detect lock holder preemption issue.
*/
//这个注释还是老版本的注释,代码已经更新了,差评啊差评
//这里判断锁的持有者 是否还在cpu上面跑,如果不在cpu上面跑了,
//也没必要等下去了,应为他也释放不了锁了
//然后判断本cpu有人没有线程要跑,如果有其他线程想跑,咱先让一让。
if (!owner_on_cpu(owner) || need_resched()) {
ret = false;
break;
}
if (ww_ctx && !ww_mutex_spin_on_owner(lock, ww_ctx, waiter)) {
ret = false;
break;
}
cpu_relax();
}
return ret;
}
1700

被折叠的 条评论
为什么被折叠?



