mutex_lock 流程

文章目录

  • 互斥锁加锁流程分析:从用户态到内核态
    • 一、整体架构概述
    • 二、用户态流程详解
      • [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处理
}

关键逻辑:

  1. 首先检查mutex类型,如果是普通类型(PTHREAD_MUTEX_TIMED_NP),走快速路径
  2. 调用 LLL_MUTEX_LOCK 宏进行加锁
  3. 对于递归锁、自适应锁等特殊类型,有额外的处理逻辑

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: 锁已被获取,可能有等待者

快速路径(无竞争):

  1. 使用原子操作 atomic_compare_and_exchange_bool_acq 尝试将锁值从0改为1
  2. 如果成功,直接返回,无需进入内核

慢速路径(有竞争):

  1. 如果快速路径失败(锁已被占用),调用 __lll_lock_wait
  2. 将锁值设为2,表示可能有等待者
  3. 进入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.  */
    }
}

逻辑说明:

  1. 如果锁值已经是2,直接跳到futex等待
  2. 否则,使用原子交换将锁值设为2
  3. 如果之前的值不为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;
}

关键逻辑:

  1. 获取futex key,用于在hash表中定位
  2. 锁定对应的hash bucket
  3. 读取futex当前值,与期望值比较
  4. 如果不匹配,解锁并返回错误
  5. 如果匹配,保持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);
}

关键步骤:

  1. 设置进程状态为 TASK_INTERRUPTIBLE(可中断睡眠)
  2. 将当前进程加入futex等待队列
  3. 启动超时定时器(如果有)
  4. 调用 schedule() 让出CPU,进入睡眠状态
  5. 被唤醒后,设置进程状态为 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);          \
   }))

逻辑:

  1. 原子地将futex值设为0(释放锁)
  2. 如果之前的值大于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 性能优化点

  1. 快速路径优化: 无竞争时,纯用户态原子操作,无需进入内核
  2. 自适应自旋: 对于短临界区,可以先自旋等待,避免上下文切换
  3. 优先级继承: PI-futex防止优先级反转
  4. Private Futex: 进程内私有futex,减少内核开销

5.3 关键设计思想

  1. 两阶段设计: 用户态快速路径 + 内核态慢速路径
  2. 哈希表管理: 使用hash bucket管理futex等待队列,提高查找效率
  3. 条件等待: futex_wait只在值匹配时才阻塞,避免虚假唤醒
  4. 原子性保证: 通过内存屏障和原子操作保证多线程安全

六、参考资料

  • 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
相关推荐
秋风&萧瑟2 小时前
【Linux系统编程】system函数和exec函数族的使用
linux·运维·服务器
秋风&萧瑟2 小时前
【Linux系统编程】Linux多进程介绍及使用
linux·运维·网络
Tanecious.2 小时前
蓝桥杯备赛:Day7- U535982 C-小梦的AB交换
c语言·c++·蓝桥杯
三万棵雪松2 小时前
【Linux 物联网网关主控系统-Web部分(四)】
linux·前端·物联网·嵌入式linux
宵时待雨2 小时前
linux笔记归纳1:linux初识
linux·运维·笔记
Deitymoon2 小时前
linux——线程设置分离属性
linux
|_⊙2 小时前
Linux进程(上)
linux·运维·服务器
feng_you_ying_li2 小时前
linux之git/gdb的使用与介绍
linux
吕司2 小时前
Linux页表的概念
linux·运维·服务器