linux mutex
常规的理解,mutex,就是获取锁,没获取到就挂起。其实 read the fxxk source code之后,发现了其实内里还有很多细节,针对一些场景做优化。
总结起来,就是尽量站住cpu,不休眠下去,等别人释放锁的第一时间就接住。
先上数据结构:
struct mutex {atomic_long_t owner;raw_spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNERstruct optimistic_spin_queue osq; /* Spinner MCS lock */
#endifstruct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXESvoid *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct 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_MUTEXESvoid *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 flagunsigned long flags = __owner_flags(owner);// 从owner变量内 提取当前持有者的 task_structunsigned 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才是1if (flags & MUTEX_FLAG_HANDOFF)//已经置位了,不用管了break;flags |= MUTEX_FLAG_HANDOFF;// 没有置位,置位} else {break;//啥都没有,还是走吧}} else { //如果当前没人持有锁,就是没锁//这里告警,flag有置位,但是还走到这里了,不应该啊,有bugMUTEX_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 NULLreturn NULL;break;}}//跟我没关系,return 现在的taskreturn __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 flaglist_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;
}