Linux内核同步机制mutex详解|环球时快讯
Linux内核同步机制mutex
mutex锁概述
在linux内核中,互斥量mutex是一种保证CPU串行运行的睡眠锁机制。和spinlock类似,都是同一个时刻只有一个线程进入临界资源,不同的是,当无法获取锁的时候,spinlock原地自旋,而mutex则是选择挂起当前线程,进入阻塞状态。所以,mutex无法在中断上下文中使用。
【资料图】
mutex锁使用注意事项
mutex一次只能有一个进程或线程持有该锁mutex只有它的拥有者可以释放该锁不能多次释放同一把锁不可以重复获取同一把锁,否则会造成死锁必须使用mutex提供的专用初始化函数初始化该锁不能重复初始化同一把锁不能使用memset
或memcpy
等内存处理函数初始化mutex锁线程退出时要释放自己持有的所有mutex锁不能用于设备中断或软中断上下文中mutex锁结构体定义
owner:记录mutex的持有者wait_lock:spinlock自旋锁soq:MCS锁队列,用于支持mutex乐观自旋机制wait_list:当无法获取锁的时候挂起在此magic:用于debug调试dep_map:用于debug调试struct mutex { atomic_long_t owner; spinlock_t wait_lock;#ifdef CONFIG_MUTEX_SPIN_ON_OWNER struct optimistic_spin_queue osq; /* Spinner MCS lock */#endif struct list_headwait_list;#ifdef CONFIG_DEBUG_MUTEXES void *magic;#endif#ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map;#endif};
mutex锁主要接口函数
mutex_init | 初始化mutex对象 |
---|---|
__mutex_init | mutex_init会调用此函数 |
DEFINE_MUTEX | 静态定义并初始化一个mutex对象 |
__MUTEX_INITIALIZER | DEFINE_MUTEX会调用此函数 |
mutex_lock | 获取mutex锁,失败进程进入D状态 |
mutex_lock_interruptible | 获取mutex锁,失败进入S状态 |
mutex_trylock | 尝试获取mutex锁,失败直接返回 |
mutex_unlock | 释放mutex锁 |
mutex_is_locked | 判断当前mutex锁的状态 |
获取锁流程分析
mutex_lock()
函数调用might_sleep()
函数判断锁的状态,调用__mutex_trylock_fast()
函数尝试快速获取mutex
锁,如果失败,则调用__mutex_lock_slowpath()
函数获取mutex
锁
void __sched mutex_lock(struct mutex *lock){ might_sleep(); if (!__mutex_trylock_fast(lock)) __mutex_lock_slowpath(lock);}
如果没有定义CONFIG_DEBUG_ATOMIC_SLEEP
宏,might_sleep
函数退化为 might_resched()
函数。
# define might_sleep() \\ do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)# define sched_annotate_sleep() (current- >task_state_change = 0)#else static inline void ___might_sleep(const char *file, int line, int preempt_offset) { } static inline void __might_sleep(const char *file, int line, int preempt_offset) { }# define might_sleep() do { might_resched(); } while (0)# define sched_annotate_sleep() do { } while (0)
在配置了抢占式内核或者非抢占式内核的情况下,might_resched()
函数最终都是空函数。如果配置了主动抢占式内核CONFIG_PREEMPT_VOLUNTARY
,则might_resched()
函数会调用 _cond_resched()
函数来主动触发一次抢占。
#ifdef CONFIG_PREEMPT_VOLUNTARYextern int _cond_resched(void);# define might_resched() _cond_resched()#else# define might_resched() do { } while (0)#endif#ifndef CONFIG_PREEMPTextern int _cond_resched(void);#elsestatic inline int _cond_resched(void) { return 0; }#endif
——cond_resched()
函数调用should_resched()
函数判断抢占计数器是否为0,如果抢占计数器为0并且设置了重新调度标记则调用preempt_schedule_common()
函数进行抢占式调度
#ifndef CONFIG_PREEMPTint __sched _cond_resched(void){ if (should_resched(0)) { preempt_schedule_common(); return 1; } return 0;}EXPORT_SYMBOL(_cond_resched);#endif
__mutex_trylock_fast()
函数调用atomic_long_cmpxchg_acquire()
函数判断lock->owner
的值是否等于0,如果等于0,则直接将当前线程的task struct
的指针赋值给lock->owner
,表示该mutex
锁已经被当前线程持有。如果lock->owner
的值不等于0,则表示该mutex
锁已经被其他线程持有或者锁正在传递给top waiter
线程,当前线程需要阻塞等待。上面描述的操作(比较和赋值)都是原子操作,不会有任何指令插入。
static __always_inline bool __mutex_trylock_fast(struct mutex *lock){ unsigned long curr = (unsigned long)current; if (!atomic_long_cmpxchg_acquire(&lock- >owner, 0UL, curr)) return true; return false;}
慢速获取mutex
锁的路径就是__mutex_lock_common()
函数,所谓慢速其实就是阻塞当前线程,将current task
挂入mutex
的等待队列的尾部。让所有等待mutex
的任务按照时间的先后顺序排列起来,当mutex
被释放的时候,会首先唤醒队首的任务,即最先等待的任务最先被唤醒。此外,在向空队列插入第一个任务的时候,会给mutex flag
设置上MUTEX_FLAG_WAITERS
标记,表示已经有任务在等待这个mutex
锁了。
static noinline void __sched__mutex_lock_slowpath(struct mutex *lock){ __mutex_lock(lock, TASK_UNINTERRUPTIBLE, 0, NULL, _RET_IP_);}static int __sched__mutex_lock(struct mutex *lock, long state, unsigned int subclass, struct lockdep_map *nest_lock, unsigned long ip){ return __mutex_lock_common(lock, state, subclass, nest_lock, ip, NULL, false);}static __always_inline int __sched__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, struct lockdep_map *nest_lock, unsigned long ip, struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx){ struct mutex_waiter waiter; bool first = false; struct ww_mutex *ww; int ret; might_sleep(); ww = container_of(lock, struct ww_mutex, base); if (use_ww_ctx && ww_ctx) { if (unlikely(ww_ctx == READ_ONCE(ww- >ctx))) return -EALREADY; } preempt_disable(); mutex_acquire_nest(&lock- >dep_map, subclass, 0, nest_lock, ip); if (__mutex_trylock(lock) || mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, NULL)) { /* got the lock, yay! */ lock_acquired(&lock- >dep_map, ip); if (use_ww_ctx && ww_ctx) ww_mutex_set_context_fastpath(ww, ww_ctx); preempt_enable(); return 0; } spin_lock(&lock- >wait_lock); /* * After waiting to acquire the wait_lock, try again. */ if (__mutex_trylock(lock)) { if (use_ww_ctx && ww_ctx) __ww_mutex_wakeup_for_backoff(lock, ww_ctx); goto skip_wait; } debug_mutex_lock_common(lock, &waiter); debug_mutex_add_waiter(lock, &waiter, current); lock_contended(&lock- >dep_map, ip); if (!use_ww_ctx) { /* add waiting tasks to the end of the waitqueue (FIFO): */ list_add_tail(&waiter.list, &lock- >wait_list);#ifdef CONFIG_DEBUG_MUTEXES waiter.ww_ctx = MUTEX_POISON_WW_CTX;#endif } else { /* Add in stamp order, waking up waiters that must back off. */ ret = __ww_mutex_add_waiter(&waiter, lock, ww_ctx); if (ret) goto err_early_backoff; waiter.ww_ctx = ww_ctx; } waiter.task = current; if (__mutex_waiter_is_first(lock, &waiter)) __mutex_set_flag(lock, MUTEX_FLAG_WAITERS); set_current_state(state); for (;;) { /* * Once we hold wait_lock, we"re serialized against * mutex_unlock() handing the lock off to us, do a trylock * before testing the error conditions to make sure we pick up * the handoff. */ if (__mutex_trylock(lock)) goto acquired; /* * Check for signals and wound conditions while holding * wait_lock. This ensures the lock cancellation is ordered * against mutex_unlock() and wake-ups do not go missing. */ if (unlikely(signal_pending_state(state, current))) { ret = -EINTR; goto err; } if (use_ww_ctx && ww_ctx && ww_ctx- >acquired > 0) { ret = __ww_mutex_lock_check_stamp(lock, &waiter, ww_ctx); if (ret) goto err; } spin_unlock(&lock- >wait_lock); schedule_preempt_disabled(); /* * ww_mutex needs to always recheck its position since its waiter * list is not FIFO ordered. */ if ((use_ww_ctx && ww_ctx) || !first) { first = __mutex_waiter_is_first(lock, &waiter); if (first) __mutex_set_flag(lock, MUTEX_FLAG_HANDOFF); } set_current_state(state); /* * Here we order against unlock; we must either see it change * state back to RUNNING and fall through the next schedule(), * or we must see its unlock and acquire. */ if (__mutex_trylock(lock) || (first && mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, &waiter))) break; spin_lock(&lock- >wait_lock); } spin_lock(&lock- >wait_lock);acquired: __set_current_state(TASK_RUNNING); mutex_remove_waiter(lock, &waiter, current); if (likely(list_empty(&lock- >wait_list))) __mutex_clear_flag(lock, MUTEX_FLAGS); debug_mutex_free_waiter(&waiter);skip_wait: /* got the lock - cleanup and rejoice! */ lock_acquired(&lock- >dep_map, ip); if (use_ww_ctx && ww_ctx) ww_mutex_set_context_slowpath(ww, ww_ctx); spin_unlock(&lock- >wait_lock); preempt_enable(); return 0;err: __set_current_state(TASK_RUNNING); mutex_remove_waiter(lock, &waiter, current);err_early_backoff: spin_unlock(&lock- >wait_lock); debug_mutex_free_waiter(&waiter); mutex_release(&lock- >dep_map, 1, ip); preempt_enable(); return ret;}
释放锁流程分析
释放锁的流程也分快速释放和慢速释放两种路径
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_);}
如果一个线程获取了mutex
锁之后,没有其他的线程试图获取,此时的mutex
的owner
成员就是该线程的task struct
地址,并且所有的mutex flag
都没有被设置。这时候只需要将mutex
的owner
成员清零即可,不需要其它操作,这就是快速释放锁的路径。
static __always_inline bool __mutex_unlock_fast(struct mutex *lock){ unsigned long curr = (unsigned long)current; if (atomic_long_cmpxchg_release(&lock- >owner, curr, 0UL) == curr) return true; return false;}
如果有其他线程在竞争该mutex
锁,这时候就会进入慢速释放锁路径,慢速释放锁路径的逻辑分成两段:一段是释放mutex
锁,另外一段是唤醒top waiter
线程。
static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip){ struct task_struct *next = NULL; DEFINE_WAKE_Q(wake_q); unsigned long owner; mutex_release(&lock- >dep_map, 1, ip); /* * Release the lock before (potentially) taking the spinlock such that * other contenders can get on with things ASAP. * * Except when HANDOFF, in that case we must not clear the owner field, * but instead set it to the top waiter. */ owner = atomic_long_read(&lock- >owner); for (;;) { unsigned long old;#ifdef CONFIG_DEBUG_MUTEXES DEBUG_LOCKS_WARN_ON(__owner_task(owner) != current); DEBUG_LOCKS_WARN_ON(owner & MUTEX_FLAG_PICKUP);#endif if (owner & MUTEX_FLAG_HANDOFF) break; old = atomic_long_cmpxchg_release(&lock- >owner, owner, __owner_flags(owner)); if (old == owner) { if (owner & MUTEX_FLAG_WAITERS) break; return; } owner = old; } spin_lock(&lock- >wait_lock); debug_mutex_unlock(lock); if (!list_empty(&lock- >wait_list)) { /* get the first entry from the wait-list: */ struct mutex_waiter *waiter = list_first_entry(&lock- >wait_list, struct mutex_waiter, list); next = waiter- >task; debug_mutex_wake_waiter(lock, waiter); wake_q_add(&wake_q, next); } if (owner & MUTEX_FLAG_HANDOFF) __mutex_handoff(lock, next); spin_unlock(&lock- >wait_lock); wake_up_q(&wake_q);}
总结
本篇主要介绍了mutex
互斥锁的使用注意事项,介绍了mutex
的主要函数接口以及获取锁的流程分析和释放锁的流程分析。通过本文的学习,我们基本可以了解了mutex
的实现机制和使用方法。
关键词:
相关阅读
-
Linux内核同步机制mutex详解|环球时快讯
Linux内核同步机制mutex详解-在linux内核中,互斥量mutex是一种保证CPU -
马上评|“高考状元概念股”只是一场尴...
马上评|“高考状元概念股”只是一场尴尬的炒作,股民,概念股,高考状... -
极氪001 再立新功 经过大事故才知极氪...
根据网络信息,端午假期之前,广西省南宁市某高速公路收费站口处发生了 -
Nat Neurosci:揭示突触后突触核蛋白介...
内源性大麻素(endocannabinoids)是一类神经递质(身体自然产生的化学 -
家家悦烟台大悦城店6月25日停业
联商快讯:6月25日,家家悦烟台大悦城店正式闭店。近日,家家悦烟台大 -
小贵但能撑大学四年的手机:除了iPhone...
为了呈现更好的游戏画面,ROG游戏手机7用了一块6 78英寸、165Hz高刷新 -
香港政府更要全力巩固香港在国际金融、...
香港政府更要全力巩固香港在国际金融、贸易、航运等的传统优势,,航运, -
全球热推荐:胡锡进:老胡正式进入中国...
老胡今天下午刚办了件“大事”:我给自己开了一个股票账户,正式进... -
世界热点!这就叫独立女性!“我就是我...
【这就是独立女性的典范】意大利足球运动员托纳利即将登陆英超加盟纽卡 -
环球微速讯:雷蛇推出游戏主机定制外壳...
外设大厂雷蛇为PS5、XSS|X以及SteamDeck推出了全新的自定义外壳。这些 -
郭广盈:6.26黄金避险上行终是昙花一现...
郭广盈:黄金避险上行终是昙花一现,3浪下行看千八!黄金走势分析1、1 -
一年期大额存单利率降至2%,还值不值得...
自古以来,我国居民便有存钱的习惯,即便生活有可能并不那么富裕,但也 -
施光南音乐艺术馆开馆,3000余页手稿捐...
“我们的家乡,在希望的田野上……”伴随着这段扎根在一代代中国人... -
当前时讯:贵州茅台6月30日将派发现金红...
新京报讯(记者薛晨)新京报记者6月26日了解到,贵州茅台酒股份有限公 -
世界微资讯!海螺水泥: 鉴于上市公司信...
海螺水泥(600585)06月26日在投资者关系平台上答复了投资者关心的问题。 -
失败的恒星温度接近8000℃,比太阳还热...
褐矮星是宇宙中一种特殊的天体。它比行星大,比恒星小,质量在木星的13 -
世界焦点!海南省三江监狱党委书记、监...
海南省三江监狱党委书记、监狱长、一级高级警长贺爱国涉嫌严重违纪违法 -
焦点观察:vivo X90s 首发评测:“中...
【【【前言:新旗舰芯与新影像,vivoX系列首迎半代升级】】】自多年前 -
每日热文:6月26日新疆宜化PVC最新报价
6月26日,新疆宜化化工有限公司,PVC装置开工正常。电石法PVCSG5型,最 -
童话中的五位公主分别都是谁
公主,是婴儿语音“山丘、主妇、王后”等词语的合成词。在现代汉语...