文章目录
- 互斥锁加锁流程分析:从用户态到内核态
-
- 一、整体架构概述
- 二、用户态流程详解
-
- [2.1 入口函数:pthread_mutex_lock()](#2.1 入口函数:pthread_mutex_lock())
- [2.2 低级锁层:LLL_MUTEX_LOCK](#2.2 低级锁层:LLL_MUTEX_LOCK)
- [2.3 核心加锁逻辑:lll_lock](#2.3 核心加锁逻辑:lll_lock)
- [2.4 等待函数:__lll_lock_wait](#2.4 等待函数:__lll_lock_wait)
- [2.5 futex系统调用封装](#2.5 futex系统调用封装)
- [2.6 系统调用发起](#2.6 系统调用发起)
- 三、内核态流程详解
-
- [3.1 系统调用入口](#3.1 系统调用入口)
- [3.2 futex分发处理](#3.2 futex分发处理)
- [3.3 futex等待核心逻辑](#3.3 futex等待核心逻辑)
- [3.4 等待设置:futex_wait_setup](#3.4 等待设置:futex_wait_setup)
- [3.5 进入等待队列:futex_wait_queue_me](#3.5 进入等待队列:futex_wait_queue_me)
- [3.6 futex数据结构](#3.6 futex数据结构)
- 四、唤醒流程
-
- [4.1 用户态解锁](#4.1 用户态解锁)
- [4.2 内核态唤醒](#4.2 内核态唤醒)
- [4.3 唤醒具体实现](#4.3 唤醒具体实现)
- 五、流程总结
-
- [5.1 加锁完整流程图](#5.1 加锁完整流程图)
- [5.2 性能优化点](#5.2 性能优化点)
- [5.3 关键设计思想](#5.3 关键设计思想)
- 六、参考资料
互斥锁加锁流程分析:从用户态到内核态
本文档基于 glibc-2.31 和 linux-4.9.37 源码,详细分析 pthread_mutex_lock 从用户态到内核态的完整调用流程。
一、整体架构概述
┌─────────────────────────────────────────────────────────────────────────┐
│ 用户态 (User Space) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ 应用程序 │───▶│ pthread_mutex │───▶│ Low Level Lock │ │
│ │ pthread_mutex_ │ │ _lock() │ │ (lll_lock) │ │
│ │ lock() │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ 快速路径 │ │ 慢速路径 │ │
│ │ (原子操作) │ │ (futex系统调用) │ │
│ └─────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 内核态 (Kernel Space) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ 系统调用入口 │───▶│ futex_wait() │───▶│ 等待队列管理 │ │
│ │ sys_futex() │ │ │ │ (hash bucket) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ 进程状态设置 │ │ 调度器 │ │
│ │ TASK_INTERRUPT │ │ schedule() │ │
│ └─────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
二、用户态流程详解
2.1 入口函数:pthread_mutex_lock()
文件 : glibc-2.31/nptl/pthread_mutex_lock.c
c
int __pthread_mutex_lock (pthread_mutex_t *mutex)
{
unsigned int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);
if (__builtin_expect (type & ~(PTHREAD_MUTEX_KIND_MASK_NP
| PTHREAD_MUTEX_ELISION_FLAGS_NP), 0))
return __pthread_mutex_lock_full (mutex);
if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_NP))
{
simple:
/* Normal mutex. */
LLL_MUTEX_LOCK (mutex);
assert (mutex->__data.__owner == 0);
}
// ... 其他类型的mutex处理
}
关键逻辑:
- 首先检查mutex类型,如果是普通类型(PTHREAD_MUTEX_TIMED_NP),走快速路径
- 调用
LLL_MUTEX_LOCK宏进行加锁 - 对于递归锁、自适应锁等特殊类型,有额外的处理逻辑
2.2 低级锁层:LLL_MUTEX_LOCK
文件 : glibc-2.31/nptl/pthread_mutex_lock.c
c
#define LLL_MUTEX_LOCK(mutex) \
lll_lock ((mutex)->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex))
2.3 核心加锁逻辑:lll_lock
文件 : glibc-2.31/sysdeps/nptl/lowlevellock.h
c
#define __lll_lock(futex, private) \
((void) \
({ \
int *__futex = (futex); \
if (__glibc_unlikely \
(atomic_compare_and_exchange_bool_acq (__futex, 1, 0))) \
{ \
if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) \
__lll_lock_wait_private (__futex); \
else \
__lll_lock_wait (__futex, private); \
} \
}))
加锁状态说明:
- 0: 锁未被获取
- 1: 锁已被获取,无等待者
- 2: 锁已被获取,可能有等待者
快速路径(无竞争):
- 使用原子操作
atomic_compare_and_exchange_bool_acq尝试将锁值从0改为1 - 如果成功,直接返回,无需进入内核
慢速路径(有竞争):
- 如果快速路径失败(锁已被占用),调用
__lll_lock_wait - 将锁值设为2,表示可能有等待者
- 进入futex等待
2.4 等待函数:__lll_lock_wait
文件 : glibc-2.31/nptl/lowlevellock.c
c
void __lll_lock_wait (int *futex, int private)
{
if (atomic_load_relaxed (futex) == 2)
goto futex;
while (atomic_exchange_acquire (futex, 2) != 0)
{
futex:
LIBC_PROBE (lll_lock_wait, 1, futex);
lll_futex_wait (futex, 2, private); /* Wait if *futex == 2. */
}
}
逻辑说明:
- 如果锁值已经是2,直接跳到futex等待
- 否则,使用原子交换将锁值设为2
- 如果之前的值不为0,说明锁被占用,调用
lll_futex_wait进入内核等待
2.5 futex系统调用封装
文件 : glibc-2.31/sysdeps/nptl/lowlevellock-futex.h
c
#define lll_futex_wait(futexp, val, private) \
lll_futex_timed_wait (futexp, val, NULL, private)
#define lll_futex_timed_wait(futexp, val, timeout, private) \
lll_futex_syscall (4, futexp, \
__lll_private_flag (FUTEX_WAIT, private), \
val, timeout)
futex操作码定义:
c
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_FD 2
#define FUTEX_REQUEUE 3
#define FUTEX_CMP_REQUEUE 4
#define FUTEX_WAKE_OP 5
#define FUTEX_LOCK_PI 6
#define FUTEX_UNLOCK_PI 7
#define FUTEX_TRYLOCK_PI 8
#define FUTEX_WAIT_BITSET 9
#define FUTEX_WAKE_BITSET 10
#define FUTEX_PRIVATE_FLAG 128
2.6 系统调用发起
文件 : glibc-2.31/sysdeps/nptl/lowlevellock-futex.h
c
#define lll_futex_syscall(nargs, futexp, op, ...) \
({ \
INTERNAL_SYSCALL_DECL (__err); \
long int __ret = INTERNAL_SYSCALL (futex, __err, nargs, futexp, op, \
__VA_ARGS__); \
(__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (__ret, __err)) \
? -INTERNAL_SYSCALL_ERRNO (__ret, __err) : 0); \
})
三、内核态流程详解
3.1 系统调用入口
文件 : linux-4.9.37/kernel/futex.c
c
SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val,
struct timespec __user *, utime, u32 __user *, uaddr2,
u32, val3)
{
struct timespec ts;
ktime_t t, *tp = NULL;
u32 val2 = 0;
int cmd = op & FUTEX_CMD_MASK;
// 处理超时参数
if (utime && (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK_PI ||
cmd == FUTEX_WAIT_BITSET ||
cmd == FUTEX_WAIT_REQUEUE_PI)) {
if (copy_from_user(&ts, utime, sizeof(ts)) != 0)
return -EFAULT;
// ...
tp = &t;
}
return do_futex(uaddr, op, val, tp, uaddr2, val2, val3);
}
参数说明:
uaddr: futex用户空间地址op: futex操作码val: 期望值utime: 超时时间uaddr2: 第二个futex地址(用于requeue操作)val3: 第三个值
3.2 futex分发处理
文件 : linux-4.9.37/kernel/futex.c
c
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
u32 __user *uaddr2, u32 val2, u32 val3)
{
int cmd = op & FUTEX_CMD_MASK;
unsigned int flags = 0;
if (!(op & FUTEX_PRIVATE_FLAG))
flags |= FLAGS_SHARED;
if (op & FUTEX_CLOCK_REALTIME) {
flags |= FLAGS_CLOCKRT;
// ...
}
switch (cmd) {
case FUTEX_WAIT:
val3 = FUTEX_BITSET_MATCH_ANY;
case FUTEX_WAIT_BITSET:
return futex_wait(uaddr, flags, val, timeout, val3);
case FUTEX_WAKE:
val3 = FUTEX_BITSET_MATCH_ANY;
case FUTEX_WAKE_BITSET:
return futex_wake(uaddr, flags, val, val3);
case FUTEX_REQUEUE:
return futex_requeue(uaddr, flags, uaddr2, val, val2, NULL, 0);
// ... 其他操作
}
return -ENOSYS;
}
3.3 futex等待核心逻辑
文件 : linux-4.9.37/kernel/futex.c
c
static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
ktime_t *abs_time, u32 bitset)
{
struct hrtimer_sleeper timeout, *to = NULL;
struct restart_block *restart;
struct futex_hash_bucket *hb;
struct futex_q q = futex_q_init;
int ret;
if (!bitset)
return -EINVAL;
q.bitset = bitset;
// 设置超时定时器
if (abs_time) {
to = &timeout;
hrtimer_init_on_stack(&to->timer, (flags & FLAGS_CLOCKRT) ?
CLOCK_REALTIME : CLOCK_MONOTONIC,
HRTIMER_MODE_ABS);
hrtimer_init_sleeper(to, current);
hrtimer_set_expires_range_ns(&to->timer, *abs_time,
current->timer_slack_ns);
}
retry:
// 准备等待:获取hash bucket锁,检查futex值
ret = futex_wait_setup(uaddr, val, flags, &q, &hb);
if (ret)
goto out;
// 将当前进程加入等待队列并调度
futex_wait_queue_me(hb, &q, to);
// 被唤醒后的处理
ret = 0;
if (!unqueue_me(&q))
goto out;
ret = -ETIMEDOUT;
if (to && !to->task)
goto out;
// 处理信号或虚假唤醒
if (!signal_pending(current))
goto retry;
ret = -ERESTARTSYS;
// ...
out:
if (to) {
hrtimer_cancel(&to->timer);
destroy_hrtimer_on_stack(&to->timer);
}
return ret;
}
3.4 等待设置:futex_wait_setup
文件 : linux-4.9.37/kernel/futex.c
c
static int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags,
struct futex_q *q, struct futex_hash_bucket **hb)
{
u32 uval;
int ret;
retry:
// 获取futex key(用于hash)
ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &q->key, VERIFY_READ);
if (unlikely(ret != 0))
return ret;
retry_private:
// 锁定hash bucket
*hb = queue_lock(q);
// 获取futex当前值
ret = get_futex_value_locked(&uval, uaddr);
if (ret) {
queue_unlock(*hb);
ret = get_user(uval, uaddr);
if (ret)
goto out;
if (!(flags & FLAGS_SHARED))
goto retry_private;
put_futex_key(&q->key);
goto retry;
}
// 检查futex值是否匹配期望值
if (uval != val) {
queue_unlock(*hb);
ret = -EWOULDBLOCK; // 值不匹配,返回EAGAIN
}
out:
if (ret)
put_futex_key(&q->key);
return ret;
}
关键逻辑:
- 获取futex key,用于在hash表中定位
- 锁定对应的hash bucket
- 读取futex当前值,与期望值比较
- 如果不匹配,解锁并返回错误
- 如果匹配,保持hash bucket锁定状态返回
3.5 进入等待队列:futex_wait_queue_me
文件 : linux-4.9.37/kernel/futex.c
c
static void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q,
struct hrtimer_sleeper *timeout)
{
/*
* 设置当前进程状态为可中断睡眠
* 使用smp_store_mb()保证顺序性
*/
set_current_state(TASK_INTERRUPTIBLE);
queue_me(q, hb); // 将当前进程加入等待队列
/* 启动超时定时器 */
if (timeout)
hrtimer_start_expires(&timeout->timer, HRTIMER_MODE_ABS);
/*
* 如果已被从hash列表中移除,说明有其他任务唤醒了我们,
* 可以跳过schedule()调用
*/
if (likely(!plist_node_empty(&q->list))) {
/*
* 如果没有超时,或超时未到期,调用调度器
*/
if (!timeout || timeout->task)
freezable_schedule(); // 让出CPU,进入睡眠
}
__set_current_state(TASK_RUNNING);
}
关键步骤:
- 设置进程状态为
TASK_INTERRUPTIBLE(可中断睡眠) - 将当前进程加入futex等待队列
- 启动超时定时器(如果有)
- 调用
schedule()让出CPU,进入睡眠状态 - 被唤醒后,设置进程状态为
TASK_RUNNING
3.6 futex数据结构
文件 : linux-4.9.37/include/linux/futex.h
c
/*
* Futex等待队列项
*/
struct futex_q {
struct plist_node list; // 优先级排序的链表节点
struct task_struct *task; // 指向等待的任务
spinlock_t *lock_ptr; // 指向hash bucket锁
union futex_key key; // futex key(用于hash)
struct futex_pi_state *pi_state; // PI状态(优先级继承)
struct rt_mutex_waiter *rt_waiter; // RT互斥锁等待者
union futex_key *requeue_pi_key; // requeue PI key
u32 bitset; // bitset用于bitset等待
};
/*
* Futex hash bucket
*/
struct futex_hash_bucket {
atomic_t waiters; // 等待者计数
struct plist_head chain; // 等待队列链表头
spinlock_t lock; // bucket锁
};
四、唤醒流程
4.1 用户态解锁
文件 : glibc-2.31/sysdeps/nptl/lowlevellock.h
c
#define __lll_unlock(futex, private) \
((void) \
({ \
int *__futex = (futex); \
int __private = (private); \
int __oldval = atomic_exchange_rel (__futex, 0); \
if (__glibc_unlikely (__oldval > 1)) \
lll_futex_wake (__futex, 1, __private); \
}))
逻辑:
- 原子地将futex值设为0(释放锁)
- 如果之前的值大于1(有等待者),调用
futex_wake唤醒一个等待者
4.2 内核态唤醒
文件 : linux-4.9.37/kernel/futex.c
c
static int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)
{
struct futex_hash_bucket *hb;
struct futex_q *this, *next;
union futex_key key = FUTEX_KEY_INIT;
int ret;
// 获取futex key
ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, VERIFY_READ);
if (unlikely(ret != 0))
return ret;
// 锁定hash bucket
hb = hash_futex(&key);
spin_lock(&hb->lock);
// 遍历等待队列,唤醒匹配的等待者
plist_for_each_entry_safe(this, next, &hb->chain, list) {
if (match_futex (&this->key, &key)) {
if (this->bitset & bitset) {
wake_futex(this);
if (++ret >= nr_wake)
break;
}
}
}
spin_unlock(&hb->lock);
put_futex_key(&key);
return ret;
}
4.3 唤醒具体实现
c
static void wake_futex(struct futex_q *q)
{
struct task_struct *p = q->task;
/*
* 将任务从等待队列中移除
*/
plist_del(&q->list, &q->list.plist);
/*
* 唤醒任务
*/
wake_up_state(p, TASK_NORMAL);
}
五、流程总结
5.1 加锁完整流程图
┌────────────────────────────────────────────────────────────────────────────┐
│ 应用程序调用 pthread_mutex_lock() │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ 用户态:glibc-2.31/nptl/pthread_mutex_lock.c │
│ 1. 检查mutex类型 │
│ 2. 调用 LLL_MUTEX_LOCK() │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ 用户态:glibc-2.31/sysdeps/nptl/lowlevellock.h │
│ lll_lock() 宏展开: │
│ 1. 尝试原子操作:CAS(lock, 0 → 1) │
│ ├─ 成功:直接返回(快速路径) │
│ └─ 失败:进入慢速路径 │
└────────────────────────────────────────────────────────────────────────────┘
│
▼ (慢速路径)
┌────────────────────────────────────────────────────────────────────────────┐
│ 用户态:glibc-2.31/nptl/lowlevellock.c │
│ __lll_lock_wait(): │
│ 1. 原子交换:lock = 2 │
│ 2. 如果原值 != 0,调用 lll_futex_wait() │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ 用户态:glibc-2.31/sysdeps/nptl/lowlevellock-futex.h │
│ lll_futex_wait() → lll_futex_syscall() │
│ 发起 futex(FUTEX_WAIT, addr, val) 系统调用 │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ 内核态:linux-4.9.37/kernel/futex.c │
│ sys_futex() → do_futex() → futex_wait() │
│ │
│ futex_wait(): │
│ 1. futex_wait_setup(): │
│ - 获取futex key │
│ - 锁定hash bucket │
│ - 读取futex值,验证是否匹配 │
│ 2. futex_wait_queue_me(): │
│ - 设置进程状态:TASK_INTERRUPTIBLE │
│ - 将进程加入等待队列 │
│ - 启动超时定时器(如果有) │
│ - 调用 schedule() 进入睡眠 │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ 进程睡眠,等待被唤醒... │
│ 唤醒条件: │
│ 1. 其他线程调用 pthread_mutex_unlock() │
│ 2. 收到信号 │
│ 3. 超时 │
└────────────────────────────────────────────────────────────────────────────┘
5.2 性能优化点
- 快速路径优化: 无竞争时,纯用户态原子操作,无需进入内核
- 自适应自旋: 对于短临界区,可以先自旋等待,避免上下文切换
- 优先级继承: PI-futex防止优先级反转
- Private Futex: 进程内私有futex,减少内核开销
5.3 关键设计思想
- 两阶段设计: 用户态快速路径 + 内核态慢速路径
- 哈希表管理: 使用hash bucket管理futex等待队列,提高查找效率
- 条件等待: futex_wait只在值匹配时才阻塞,避免虚假唤醒
- 原子性保证: 通过内存屏障和原子操作保证多线程安全
六、参考资料
- glibc-2.31:
nptl/pthread_mutex_lock.c,sysdeps/nptl/lowlevellock.h,sysdeps/nptl/lowlevellock-futex.h - linux-4.9.37:
kernel/futex.c,include/uapi/linux/futex.h,include/linux/futex.h - Futex man page
- Understanding Futexes