引言:spinlock 不能睡,mutex 能睡------怎么"能睡"还防饿死
上一篇 #4 讲了 qspinlock:等待者忙自旋 ,所以临界区必须极短、绝不能 sleep。但内核里大量锁的临界区没那么短 (比如操作一个链表、调一个会 sleep 的函数),忙等会白白烧 CPU。mutex(互斥锁) 就是"能 sleep 的锁":抢不到时,等待者先乐观自旋一小会 (万一 owner 马上就释放),还抢不到就把自己放进睡眠队列、让出 CPU ,等 owner 释放时唤醒队头交接锁。
它和 qspinlock 一样解决"缓存乒乓"和"防饿死",但换成了睡眠版本:
这条链路:mutex_lock fast path 一次 CAS 抢锁 → 失败进 slow path:先乐观自旋 (osq 队列里 spin on owner)→ 还抢不到就入队睡眠 (wait_list)→ owner 释放时handoff 唤醒队头。
全景图
#mermaid-svg-ZxArsg7IRuh51wrf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZxArsg7IRuh51wrf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZxArsg7IRuh51wrf .error-icon{fill:#552222;}#mermaid-svg-ZxArsg7IRuh51wrf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZxArsg7IRuh51wrf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZxArsg7IRuh51wrf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZxArsg7IRuh51wrf .marker.cross{stroke:#333333;}#mermaid-svg-ZxArsg7IRuh51wrf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZxArsg7IRuh51wrf p{margin:0;}#mermaid-svg-ZxArsg7IRuh51wrf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZxArsg7IRuh51wrf .cluster-label text{fill:#333;}#mermaid-svg-ZxArsg7IRuh51wrf .cluster-label span{color:#333;}#mermaid-svg-ZxArsg7IRuh51wrf .cluster-label span p{background-color:transparent;}#mermaid-svg-ZxArsg7IRuh51wrf .label text,#mermaid-svg-ZxArsg7IRuh51wrf span{fill:#333;color:#333;}#mermaid-svg-ZxArsg7IRuh51wrf .node rect,#mermaid-svg-ZxArsg7IRuh51wrf .node circle,#mermaid-svg-ZxArsg7IRuh51wrf .node ellipse,#mermaid-svg-ZxArsg7IRuh51wrf .node polygon,#mermaid-svg-ZxArsg7IRuh51wrf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZxArsg7IRuh51wrf .rough-node .label text,#mermaid-svg-ZxArsg7IRuh51wrf .node .label text,#mermaid-svg-ZxArsg7IRuh51wrf .image-shape .label,#mermaid-svg-ZxArsg7IRuh51wrf .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZxArsg7IRuh51wrf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZxArsg7IRuh51wrf .rough-node .label,#mermaid-svg-ZxArsg7IRuh51wrf .node .label,#mermaid-svg-ZxArsg7IRuh51wrf .image-shape .label,#mermaid-svg-ZxArsg7IRuh51wrf .icon-shape .label{text-align:center;}#mermaid-svg-ZxArsg7IRuh51wrf .node.clickable{cursor:pointer;}#mermaid-svg-ZxArsg7IRuh51wrf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZxArsg7IRuh51wrf .arrowheadPath{fill:#333333;}#mermaid-svg-ZxArsg7IRuh51wrf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZxArsg7IRuh51wrf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZxArsg7IRuh51wrf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZxArsg7IRuh51wrf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZxArsg7IRuh51wrf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZxArsg7IRuh51wrf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZxArsg7IRuh51wrf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZxArsg7IRuh51wrf .cluster text{fill:#333;}#mermaid-svg-ZxArsg7IRuh51wrf .cluster span{color:#333;}#mermaid-svg-ZxArsg7IRuh51wrf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZxArsg7IRuh51wrf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZxArsg7IRuh51wrf rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZxArsg7IRuh51wrf .icon-shape,#mermaid-svg-ZxArsg7IRuh51wrf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZxArsg7IRuh51wrf .icon-shape p,#mermaid-svg-ZxArsg7IRuh51wrf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZxArsg7IRuh51wrf .icon-shape .label rect,#mermaid-svg-ZxArsg7IRuh51wrf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZxArsg7IRuh51wrf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZxArsg7IRuh51wrf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZxArsg7IRuh51wrf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功(无竞争)
失败
是
否(owner 没在跑/要调度)
mutex_lock mutex.c:281
might_sleep + trylock_fast
fast path
CAS owner 0→current 成功?
拿到锁
__mutex_lock_common mutex.c:569
preempt_disable
乐观自旋
mutex_optimistic_spin :441
osq_lock 排队 spin on owner
owner 在别的 CPU 上跑才自旋
owner 释放了?
入队睡眠
__mutex_add_waiter :205 + schedule :679
睡在 wait_list 队尾
set_current_state(TASK_UNINTERRUPTIBLE)
unlock 唤醒队头
__mutex_handoff :231 直接交接 owner
mutex_unlock :537
fast path 清 owner / 唤醒队头
一句话:mutex_lock fast path 一次 CAS 抢锁 → 失败进 slow path:先 mutex_optimistic_spin 乐观自旋(osq 队列里 spin on owner,owner 在跑才等)→ 抢不到就 __mutex_add_waiter 入队 + schedule 睡眠 → owner 释放时 __mutex_handoff 直接交接给队头,防新 spinner 插队饿死。
一、struct mutex:owner 指针压缩 3 个标志位
c
// struct mutex(include/linux/mutex.h:64)
struct mutex {
atomic_long_t owner; // 持有者 task_struct *(低 3 位是标志)
raw_spinlock_t wait_lock; // 保护 wait_list 的自旋锁
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; // 乐观自旋者的 MCS 队列
#endif
struct list_head wait_list; // 睡眠等待队列(FIFO)
...
};
核心是 owner------一个 atomic_long_t,把持有者指针 和三个标志位压在一起:
c
// kernel/locking/mutex.c:68-72
#define MUTEX_FLAG_WAITERS 0x01 // 有人在 wait_list 睡眠等待
#define MUTEX_FLAG_HANDOFF 0x02 // 锁要交接给队头(禁止新 spinner 抢)
#define MUTEX_FLAG_PICKUP 0x04 // handoff 已完成、等队头认领(队头认领时清掉)
#define MUTEX_FLAGS 0x07
static inline struct task_struct *__mutex_owner(struct mutex *lock) // :79
{
return (struct task_struct *)(atomic_long_read(&lock->owner) & ~MUTEX_FLAGS);
}
Why 指针 + 标志位压缩 :
task_struct *是 8 字节对齐的(低 3 位必然为 0),所以低 3 位可以免费塞 3 个标志位。这是和 #4 qspinlock 的 32 位字、#1 里READ_ONCE同类思路------用对齐的"空位"存状态,一个原子变量同时表达"谁持有 + 什么状态" 。这样"抢锁 / 交接 / 判等待者"都能用单次atomic_long_cmpxchg完成。
二、fast path:一次 CAS 抢锁
c
// mutex_lock(kernel/locking/mutex.c:281)
void __sched mutex_lock(struct mutex *lock)
{
might_sleep(); // ① 检查不在原子上下文
if (!__mutex_trylock_fast(lock)) // ② fast path
__mutex_lock_slowpath(lock); // ③ 失败进慢路径
}
// __mutex_trylock_fast(mutex.c:166)
static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
{
unsigned long curr = (unsigned long)current;
unsigned long zero = 0UL;
if (atomic_long_try_cmpxchg_acquire(&lock->owner, &zero, curr)) // 0 → current
return true;
return false;
}
无竞争时 owner 是 0,atomic_long_try_cmpxchg_acquire 把它从 0 改成 current(当前 task),拿到锁。和 qspinlock 的 fast path 完全同构------一次 CAS 抢锁,acquire 语义。
might_sleep()(:283)是 mutex 的"身份检查":它内部检查当前是否在原子上下文(关抢占/关中断/持有自旋锁),是的话就 WARN------因为 mutex 要能 sleep ,绝不能在不能 sleep 的上下文里调(这是 #3 的类①底层纪律,might_sleep 是运行时抓它的钩子)。
三、slow path 三级:乐观自旋 → 入队睡眠 → handoff 唤醒
抢锁失败,说明有人持有。__mutex_lock_common(mutex.c:569)按"owner 有没有在跑"决定自旋还是睡:
3.1 乐观自旋:owner 在别的 CPU 上跑,才值得等
c
// __mutex_lock_common(mutex.c:569)------ 关键片段
preempt_disable(); // :602
if (__mutex_trylock(lock) || // ① 再试一次
mutex_optimistic_spin(lock, ww_ctx, NULL)) { // ② 乐观自旋(:606-607)
/* got the lock, yay! */
...
preempt_enable();
return 0;
}
raw_spin_lock(&lock->wait_lock); // ③ 自旋失败,拿 wait_lock 准备睡(:617)
mutex_optimistic_spin(:441)是"自旋"与"睡"的分水岭:
c
// mutex_optimistic_spin(mutex.c:441)------ 关键片段
if (!waiter) {
if (!mutex_can_spin_on_owner(lock)) // ① owner 没在跑/要调度,别自旋
goto fail;
if (!osq_lock(&lock->osq)) // ② 进乐观自旋队列(MCS)
goto fail;
}
for (;;) {
owner = __mutex_trylock_or_owner(lock); // ③ 试着抢锁,顺便拿到 owner
if (!owner)
break; // 抢到了!
if (!mutex_spin_on_owner(lock, owner, ww_ctx, waiter)) // ④ spin on owner
goto fail_unlock;
cpu_relax();
}
...
关键判据在 mutex_spin_on_owner(:352):
c
// mutex_spin_on_owner(mutex.c:352)------ spin 的退出条件
while (__mutex_owner(lock) == owner) {
barrier();
if (!owner_on_cpu(owner) || need_resched()) { // ① owner 不在 CPU 上跑 / 该调度了(:373)
ret = false;
break; // 放弃自旋,去睡
}
cpu_relax();
}
Why 乐观自旋的判据是"owner 在不在 CPU 上跑" :mutex 的临界区可能较长,owner 可能自己 sleep 或被抢占 。如果
owner_on_cpu(owner)为真,说明 owner 正在某个 CPU 上执行 ,大概率很快释放锁------自旋值得;如果 owner 已经不在 CPU 上(被抢占/睡眠),它要等调度器 把它重新调度回来,自旋纯属浪费,还不如自己 sleep 把 CPU 让出来。这就是"乐观"二字的含义------赌临界区短、owner 在线,赌输了就睡,不硬扛。
3.2 osq 队列:防"stampede"(复用 MCS 思想)
多个 CPU 同时乐观自旋同一把锁,都会 spin 在 owner 字段上------又是缓存乒乓。osq_lock(osq_lock.c:90)是专门给乐观自旋者的 MCS 队列,让 spinners 排队、各自 spin 自己的节点:
c
// struct optimistic_spin_node(include/linux/osq_lock.h:9)
struct optimistic_spin_node {
struct optimistic_spin_node *next, *prev;
int locked; // 1 = 轮到自己自旋了
int cpu; // 编码的 CPU 号 +1
};
// osq_lock(kernel/locking/osq_lock.c:90)------ 关键片段
node = this_cpu_ptr(&osq_node);
node->locked = 0; node->next = NULL; node->cpu = curr;
old = atomic_xchg(&lock->tail, curr); // ① 发布"我是队尾"(:107)
if (old == OSQ_UNLOCKED_VAL)
return true; // 队列空,直接进自旋
prev = decode_cpu(old);
smp_wmb();
WRITE_ONCE(prev->next, node); // ② 挂到前驱
if (smp_cond_load_relaxed(&node->locked, VAL || need_resched() ||
vcpu_is_preempted(node_cpu(node->prev)))) // ③ spin 自己的节点(:143)
return true;
/* unqueue: 该调度了,把自己摘出队列去 sleep */
这和 #4 qspinlock 的 MCS 队列是同一套思想 :atomic_xchg 发布尾 + spin 自己的 node->locked + prev->next 链接,避免所有 spinner 挤在同一个 cache line 上。区别是 osq 的节点带了 prev(乐观自旋者可能在等锁途中被 need_resched 打断,需要把自己摘出队列去 sleep)。
3.3 入队睡眠:wait_list FIFO
乐观自旋失败(owner 没在跑 / 该调度了),等待者正式去睡:
c
// __mutex_lock_common(mutex.c:569)------ 睡眠路径
raw_spin_lock(&lock->wait_lock); // ① 保护 wait_list
if (__mutex_trylock(lock)) // ② 拿锁间隙再抢一次(:621)
goto skip_wait;
__mutex_add_waiter(lock, &waiter, &lock->wait_list); // ③ 加进 FIFO 队尾(:637)
set_current_state(state); // ④ 置 TASK_UNINTERRUPTIBLE(:648)
for (;;) {
if (__mutex_trylock(lock)) // ⑤ 抢锁(含 handoff 交接)
goto acquired;
...
raw_spin_unlock(&lock->wait_lock);
schedule_preempt_disabled(); // ⑥ 睡!(:679)
first = __mutex_waiter_is_first(lock, &waiter); // ⑦ 醒来后看自己是不是队头
set_current_state(state);
if (__mutex_trylock_or_handoff(lock, first)) // ⑧ 队头抢锁/置 HANDOFF 请求交接(:689)
break;
if (first)
mutex_optimistic_spin(lock, ww_ctx, &waiter); // ⑨ 队头还可以再乐观自旋(:694)
raw_spin_lock(&lock->wait_lock);
}
__mutex_add_waiter(:205)把等待者加到 wait_list 队尾(list_add_tail,FIFO),如果是第一个等待者就置 MUTEX_FLAG_WAITERS 标志(:212),告诉 owner"有人在等,释放时别走 fast path 直接返回"。然后 set_current_state(TASK_UNINTERRUPTIBLE) + schedule_preempt_disabled()(:679)------任务睡眠,让出 CPU,这就是 mutex 和 spinlock 的本质区别。
3.4 handoff:防新 spinner 插队饿死
睡眠等待者醒来后,靠 __mutex_handoff(:231)拿到锁。释放侧:
c
// __mutex_unlock_slowpath(mutex.c:901)------ 关键片段
owner = atomic_long_read(&lock->owner);
for (;;) {
if (owner & MUTEX_FLAG_HANDOFF) // ① 已声明交接,退出循环(:921)
break;
if (atomic_long_try_cmpxchg_release(&lock->owner, &owner, __owner_flags(owner))) {
if (owner & MUTEX_FLAG_WAITERS) // ② 有等待者,跳出走唤醒(:925)
break;
return; // ③ 无等待者,直接返回(fast unlock)
}
}
raw_spin_lock(&lock->wait_lock);
if (!list_empty(&lock->wait_list)) {
next = list_first_entry(&lock->wait_list, struct mutex_waiter, list)->task; // :936
wake_q_add(&wake_q, next); // ④ 唤醒队头(:943)
}
if (owner & MUTEX_FLAG_HANDOFF)
__mutex_handoff(lock, next); // ⑤ 直接把 owner 设给队头(:947)
raw_spin_unlock(&lock->wait_lock);
wake_up_q(&wake_q);
__mutex_handoff(:231)把 owner 直接设成队头 task、置 MUTEX_FLAG_PICKUP、清 HANDOFF------锁不经过"清空再抢"的窗口,直接交接 。交接窗口内 owner 不为 0,新来的乐观 spinner 抢锁必然失败(__mutex_trylock_common 里 task 非 0 就走 break 放弃,:112-123),队头等待者一定拿到锁。
Why HANDOFF 防饿死 :如果没有 handoff,unlock 清空 owner 后,一个刚到的乐观 spinner 可能抢在睡眠等待者醒来之前 把锁拿走,反复如此,睡眠等待者永远拿不到锁------饿死 。HANDOFF 解决:队头等待者醒来发现锁仍被持有(被 spinner 抢了),就置
HANDOFF请求(__mutex_trylock_or_handoff),此后持有者释放时__mutex_handoff把 owner 直接交接给队头、新 spinner 抢不到------和 #4 qspinlock 的 handoff 是同一个防饿死思路。
四、unlock:fast path 清 owner / 唤醒队头
c
// mutex_unlock(mutex.c:537)
void __sched mutex_unlock(struct mutex *lock)
{
if (__mutex_unlock_fast(lock)) // ① 无等待者:直接清 owner
return;
__mutex_unlock_slowpath(lock, _RET_IP_); // ② 有等待者:走 §3.4 唤醒队头
}
// __mutex_unlock_fast(mutex.c:177)
static __always_inline bool __mutex_unlock_fast(struct mutex *lock)
{
unsigned long curr = (unsigned long)current;
return atomic_long_try_cmpxchg_release(&lock->owner, &curr, 0UL); // current → 0
}
无等待者时,atomic_long_try_cmpxchg_release 把 owner 从 current 改回 0(release 语义),一条 CAS 搞定。有等待者(WAITERS 标志)或 HANDOFF 时,走 §3.4 的 __mutex_unlock_slowpath 唤醒队头。
五、mutex vs spinlock 选型
| spinlock(#4) | mutex | |
|---|---|---|
| 等待者 | 忙自旋(烧 CPU) | 先乐观自旋 → 睡眠(让出 CPU) |
| 临界区 | 极短(纳秒~微秒) | 可长(能 sleep、调 schedule) |
| 中断上下文 | ✅ 可用 | ❌ 不能(要 sleep,might_sleep 检查) |
| owner | 无(锁里只有位) | task_struct 指针(能知道谁持有) |
| 防饿死 | handoff | HANDOFF 标志 |
| 典型场景 | 驱动热路径、中断 handler | 进程上下文、文件系统、子系统内部 |
选型规则 :临界区短、可能被中断上下文访问 → spinlock(spin_lock_irqsave);临界区长、需要 sleep、只在进程上下文 → mutex。中间地带靠 mutex 的乐观自旋兜住------短临界区时它自旋很快,长临界区时它睡了不浪费 CPU,是"两头兼顾"的自适应设计。
六、与前面各篇的关系 + 记住三点
6.1 与前面各篇的关系
| 篇 | 关系 |
|---|---|
| #4 qspinlock | 同构的两套设计:32 位字 vs owner 指针压缩、MCS 队列 vs osq、handoff 防饿死;区别只在"忙等" vs "睡眠" |
| #3 死锁与锁序 | might_sleep() 抓"原子上下文里睡"(类①纪律);mutex 不能用于中断上下文 |
| #1 原子操作 | owner 的 CAS 抢锁 = cmpxchg_acquire/cmpxchg_release,osq 的 atomic_xchg 发布队尾 |
6.2 与 AI Infra 的关系
- 驱动/文件系统:进程上下文里要 sleep 的锁用 mutex(如 GPU 驱动的 ioctl 路径、文件系统 inode 锁)。
- 用户态对照 :
pthread_mutex在无竞争时用用户态 CAS(futex 快路径),竞争时进内核 futex 睡眠------正是 mutex"乐观自旋→睡眠"思想的用户态版本(接 #14 futex)。 - osq 的 MCS 思想:SPDK/DPDK 用户态锁、无锁队列复用(接 #15)。
6.3 记住三点
-
owner 指针压缩 3 个标志位 :
struct mutex(mutex.h:64)的owner是atomic_long_t------高 61 位存task_struct *(8 字节对齐,低 3 位空),低 3 位存WAITERS/HANDOFF/PICKUP(mutex.c:68-70)。抢锁/交接/判等待者全用单次atomic_long_cmpxchg。 -
slow path 三级 = 乐观自旋 → 入队睡眠 → handoff :
mutex_lock(:281)→__mutex_lock_common(:569)先mutex_optimistic_spin(:441,osq 队列里 spin on owner,owner_on_cpu才等、need_resched就退)→ 抢不到__mutex_add_waiter(:205)+schedule(:679)睡眠 → 释放时__mutex_handoff(:231)直接交接 owner 给队头。 -
乐观自旋的判据是"owner 在不在 CPU 上跑" :
mutex_spin_on_owner(:352)里!owner_on_cpu(owner) || need_resched()就放弃自旋去睡------赌"临界区短、owner 在线",赌输了就睡不硬扛。选型:短临界区/中断上下文 → spinlock;长临界区/要 sleep → mutex。
附:本篇关键源码索引
| 符号 | 位置 |
|---|---|
struct mutex(owner/wait_lock/osq/wait_list) |
include/linux/mutex.h:64 |
MUTEX_FLAG_WAITERS/HANDOFF/PICKUP |
kernel/locking/mutex.c:68-70 |
__mutex_owner(owner & ~MUTEX_FLAGS 取持有者) |
kernel/locking/mutex.c:79 |
mutex_lock(might_sleep + trylock_fast) |
kernel/locking/mutex.c:281 |
__mutex_trylock_fast(CAS owner 0→current) |
kernel/locking/mutex.c:166 |
__mutex_trylock_common(trylock + handoff 逻辑) |
kernel/locking/mutex.c:103 |
__mutex_trylock / __mutex_trylock_or_handoff(队头抢锁/置 HANDOFF) |
kernel/locking/mutex.c:150 / :142 |
mutex_optimistic_spin(乐观自旋) |
kernel/locking/mutex.c:441 |
mutex_spin_on_owner(owner_on_cpu 判据) |
kernel/locking/mutex.c:352 |
mutex_can_spin_on_owner |
kernel/locking/mutex.c:392 |
__mutex_lock_common(慢路径核心) |
kernel/locking/mutex.c:569 |
__mutex_add_waiter / __mutex_waiter_is_first |
kernel/locking/mutex.c:205 / :195 |
mutex_unlock / __mutex_unlock_slowpath |
kernel/locking/mutex.c:537 / :901 |
__mutex_handoff(交接 owner 给队头) |
kernel/locking/mutex.c:231 |
struct optimistic_spin_node / optimistic_spin_queue |
include/linux/osq_lock.h:9 / :15 |
osq_lock / osq_unlock |
kernel/locking/osq_lock.c:90 / :207 |