futex同步机制分析之二glibc的实现

本文深入探讨了futex机制在Linux系统中的应用,包括信号灯、互斥锁、条件变量等同步机制的具体实现方式,以及如何通过futex系统调用来完成进程间的同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在前方中提到过,经常使用的几个线程(进程)同步机制,其实都是调用futex来实现的,下面就从glibc2.8中把源码找出来对一下,看看到底是不是确实是如此,同时,对代码进行一些简单的分析。 
每个细节的实现其实都可以展开来讲成独立的一篇,这里只是介绍同步机制的来龙去脉和实现,就不再对具体的细节进行展开,回头如果有时间,再对具体的每个技术进行专门的分析。 


一、信号灯的实现


int __new_sem_wait (sem_t *sem)
{
  /* We need to check whether we need to act upon a cancellation request here
     because POSIX specifies that cancellation points "shall occur" in
     sem_wait and sem_timedwait, which also means that they need to check
     this regardless whether they block or not (unlike "may occur"
     functions).  See the POSIX Rationale for this requirement: Section
     "Thread Cancellation Overview" [1] and austin group issue #1076 [2]
     for thoughs on why this may be a suboptimal design.
 
     [1] http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap02.html
     [2] http://austingroupbugs.net/view.php?id=1076 for thoughts on why this
   \*/
  __pthread_testcancel ();
 
  //处理sem_wait
  if (__new_sem_wait_fast ((struct new_sem *) sem, 0) == 0)
    return 0;
  else
    return __new_sem_wait_slow((struct new_sem *) sem, NULL);
}
versioned_symbol (libpthread, \__new_sem_wait, sem_wait, GLIBC_2_1);
 
//处理新老两种库的sem的生成
#if SHLIB_COMPAT (libpthread, GLIBC_2_0, GLIBC_2_1)
int
attribute_compat_text_section
\__old_sem_wait (sem_t \*sem)
{
  int \*futex = (int \*) sem;
  int err;
 
  //循环判断状态
  do
    {
      if (atomic_decrement_if_positive (futex) > 0)
    return 0;
 
      /* Enable asynchronous cancellation.  Required by the standard.  \*/
      int oldtype = \__pthread_enable_asynccancel ();
 
      /* Always assume the semaphore is shared.  \*/
      err = lll_futex_wait (futex, 0, LLL_SHARED);
 
      /* Disable asynchronous cancellation.  \*/
      \__pthread_disable_asynccancel (oldtype);
    }
  while (err == 0 || err == -EWOULDBLOCK);
 
  \__set_errno (-err);
  return -1;
}
 
compat_symbol (libpthread, \__old_sem_wait, sem_wait, GLIBC_2_0);
#endif


在新的2.8及以上版本中,兼容了老的sem和futex,又重新生成了一个新的sem,这个数据结构体比原来一个单纯的整型多了不少,看一下代码: 

/* Semaphore variable structure.  */
struct new_sem
{
#if \__HAVE_64B_ATOMICS
  /* The data field holds both value (in the least-significant 32 bits) and
     nwaiters.  \*/
# if \__BYTE_ORDER == \__LITTLE_ENDIAN
#  define SEM_VALUE_OFFSET 0
# elif \__BYTE_ORDER == \__BIG_ENDIAN
#  define SEM_VALUE_OFFSET 1
# else
# error Unsupported byte order.
# endif
# define SEM_NWAITERS_SHIFT 32
# define SEM_VALUE_MASK (~(unsigned int)0)
  uint64_t data;
  int private;
  int pad;
#else
# define SEM_VALUE_SHIFT 1
# define SEM_NWAITERS_MASK ((unsigned int)1)
  unsigned int value;
  int private;
  int pad;
  unsigned int nwaiters;
#endif
};
 
struct old_sem
{
  unsigned int value;
};


不过怎么说呢,原理仍然是一样的。不过需要注意的是,网上很多的资料仍然是对老的进行的分析,所以在看代码时要与时俱进。 

二、mutex实现

再看一下mutex的LOCK实现源码: 

static int
__pthread_mutex_lock_full (pthread_mutex_t *mutex)
{
/* We cannot acquire the mutex nor has its owner died.  Thus, try
      to block using futexes.  Set FUTEX_WAITERS if necessary so that
      other threads are aware that there are potentially threads
      blocked on the futex.  Restart if oldval changed in the
      meantime.  \*/
   if ((oldval & FUTEX_WAITERS) == 0)
     {
       //前文提到过的CAS
       if (atomic_compare_and_exchange_bool_acq (&mutex->\__data.\__lock,
             oldval | FUTEX_WAITERS,
             oldval)
     != 0)
   {
     oldval = mutex->\__data.\__lock;
     continue;
   }
       oldval |= FUTEX_WAITERS;
     }
 
   /* It is now possible that we share the FUTEX_WAITERS flag with
      another thread; therefore, update assume_other_futex_waiters so
      that we do not forget about this when handling other cases
      above and thus do not cause lost wake-ups.  \*/
   assume_other_futex_waiters |= FUTEX_WAITERS;
 
   /* Block using the futex and reload current lock value.  \*/
   lll_futex_wait (&mutex->\__data.\__lock, oldval,
       PTHREAD_ROBUST_MUTEX_PSHARED (mutex));
   oldval = mutex->\__data.\__lock;//前方提到参变量
 }
 
     /* We have acquired the mutex; check if it is still consistent.  */
     //此处即为一致性检查
     if (\__builtin_expect (mutex->\__data.\__owner
         == PTHREAD_MUTEX_NOTRECOVERABLE, 0))
 {
   /* This mutex is now not recoverable.  \*/
   mutex->__data.__count = 0;
   int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex);
   lll_unlock (mutex->__data.__lock, private);
   /* FIXME This violates the mutex destruction requirements.  See
      \__pthread_mutex_unlock_full.  \*/
   THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL);
   return ENOTRECOVERABLE;
 }
}

三、条件变量的实现

glibc中条件变量的实现如下: 

#define __lll_cond_lock(futex, private)                                 \
  ((void)                                                               \
   ({                                                                   \
     int \*\__futex = (futex);                                            \
     if (__glibc_unlikely (atomic_exchange_acq (\__futex, 2) != 0))      \
       __lll_lock_wait (\__futex, private);                              \
   }))
#define lll_cond_lock(futex, private) __lll_cond_lock (&(futex), private)
 
static void __attribute__ ((unused))
__condvar_acquire_lock (pthread_cond_t *cond, int private)
{
  unsigned int s = atomic_load_relaxed (&cond->\__data.\__g1_orig_size);
  while ((s & 3) == 0)
    {
      if (atomic_compare_exchange_weak_acquire (&cond->\__data.\__g1_orig_size,
      &s, s | 1))
    return;
      /* TODO Spinning and back-off.  \*/
    }
  /* We can't change from not acquired to acquired, so try to change to
     acquired-with-futex-wake-request and do a futex wait if we cannot change
     from not acquired.  \*/
  while (1)
    {
      while ((s & 3) != 2)
    {
      if (atomic_compare_exchange_weak_acquire
          (&cond->__data.__g1_orig_size, &s, (s & ~(unsigned int) 3) | 2))
        {
          if ((s & 3) == 0)
        return;
          break;
        }
      /* TODO Back off.  \*/
    }
      futex_wait_simple (&cond->__data.__g1_orig_size,
      (s & ~(unsigned int) 3) | 2, private);
      /* Reload so we see a recent value.  \*/
      s = atomic_load_relaxed (&cond->__data.__g1_orig_size);
    }
}
 
/* See __condvar_acquire_lock.  */
static void __attribute__ ((unused))
__condvar_release_lock (pthread_cond_t *cond, int private)
{
  if ((atomic_fetch_and_release (&cond->__data.__g1_orig_size,
                 ~(unsigned int) 3) & 3)
      == 2)
    futex_wake (&cond->__data.__g1_orig_size, 1, private);
}

四、系统调用

其实所有的调用,最终都要落到系统调用中来: 

#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);                     \
  })
 
#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)
 
#define lll_futex_timed_wait_bitset(futexp, val, timeout, clockbit, private) \
  lll_futex_syscall (6, futexp,                                         \
             __lll_private_flag (FUTEX_WAIT_BITSET | (clockbit), \
                     private),                      \
             val, timeout, NULL /* Unused.  */,                 \
             FUTEX_BITSET_MATCH_ANY)
 
#define lll_futex_wake(futexp, nr, private)                             \
  lll_futex_syscall (4, futexp,                                         \
             __lll_private_flag (FUTEX_WAKE, private), nr, 0)
 
/* Returns non-zero if error happened, zero if success.  */
#define lll_futex_requeue(futexp, nr_wake, nr_move, mutex, val, private) \
  lll_futex_syscall (6, futexp,                                         \
             __lll_private_flag (FUTEX_CMP_REQUEUE, private),   \
             nr_wake, nr_move, mutex, val)
 
/* Returns non-zero if error happened, zero if success.  */
#define lll_futex_wake_unlock(futexp, nr_wake, nr_wake2, futexp2, private) \
  lll_futex_syscall (6, futexp,                                         \
             __lll_private_flag (FUTEX_WAKE_OP, private),       \
             nr_wake, nr_wake2, futexp2,                        \
             FUTEX_OP_CLEAR_WAKE_IF_GT_ONE)
 
/* Priority Inheritance support.  */
#define lll_futex_wait_requeue_pi(futexp, val, mutex, private) \
  lll_futex_timed_wait_requeue_pi (futexp, val, NULL, 0, mutex, private)
 
#define lll_futex_timed_wait_requeue_pi(futexp, val, timeout, clockbit, \
                    mutex, private)                 \
  lll_futex_syscall (5, futexp,                                         \
             __lll_private_flag (FUTEX_WAIT_REQUEUE_PI          \
                     | (clockbit), private),        \
             val, timeout, mutex)
 
 
#define lll_futex_cmp_requeue_pi(futexp, nr_wake, nr_move, mutex,       \
                 val, private)                          \
  lll_futex_syscall (6, futexp,                                         \
             __lll_private_flag (FUTEX_CMP_REQUEUE_PI,          \
                     private),                      \
             nr_wake, nr_move, mutex, val)


futex又可以分类成以下几部分: 
normal futex:通用的futex. 
pi-futex:pi协议的futex. 
requeue-pi:使用队列的pi协议futex. 
robust-futex:robust规则的futex. 
lowlevellock提供三种锁的机制,一是基于0或1的互斥的锁规则,二是基于robust futex定义的锁规则,三是用于condition重新对临界区上锁的操作。在前文的参数介绍中也有类似的介绍。 
这些代码最终又会调用下一篇分析中的内核部分代码。这样,就可以从应用到Glibc库再到内核把futex看得清清楚楚。 

<think>我们正在讨论Linux中的futex(快速用户空间互斥锁)函数。根据用户的要求,我们需要详细解释futex的工作原理和使用方法。首先,futex是Linux提供的一种同步机制,用于用户空间的线程同步。它的主要设计目标是减少在无竞争情况下的系统调用开销,因为传统的同步机制(如SystemV信号量)在每次操作时都需要进行系统调用,即使没有竞争。而futex在无竞争时只需在用户空间执行原子操作,只有在需要阻塞或唤醒线程时才进入内核。 futex的核心思想是:每个futex都关联一个用户空间的整型变量(称为futexword),线程通过原子操作检查这个变量的值,如果满足条件(例如,变量等于某个期望值)则继续执行而不进入内核;否则,通过系统调用请求内核进行阻塞或唤醒操作。 futex系统调用的原型如下:```c#include<linux/futex.h>#include <sys/syscall.h>#include <unistd.h> longsyscall(SYS_futex,uint32_t *uaddr, intop, uint32_tval,const structtimespec*timeout,uint32_t *uaddr2,uint32_t val3);```实际使用中,我们通常使用glibc提供的封装函数`futex`(但注意glibc并没有提供直接的封装,通常我们使用`syscall`来调用)。futex的操作由`op`参数指定,常见的操作有: -`FUTEX_WAIT`:如果`*uaddr`等于`val`,则阻塞线程直到被唤醒(或超时)。否则,立即返回并设置错误为`EAGAIN`。- `FUTEX_WAKE`:唤醒最多`val`个阻塞在`uaddr`上的线程。现在,让我们通过一个简单的例子来说明futex的使用:实现一个互斥锁。###使用futex实现互斥锁我们用一个整型变量作为锁的状态:0表示未锁定,1表示锁定。**加锁操作**: 1.尝试使用原子操作将锁变量从0设置为1(例如,使用`__atomic_exchange_n`等内建函数)。2.如果设置成功,说明获取了锁。 3.如果设置失败(即锁已经被其他线程持有),则调用`futex(FUTEX_WAIT)`,并检查锁变量是否仍为1(表示仍然被持有),如果是则阻塞。**解锁操作**: 1.使用原子操作将锁变量设置为0。 2.然后调用`futex(FUTEX_WAKE)`唤醒一个等待线程。 下面是伪代码示例(注意:实际代码需要处理错误和边缘情况):```c#include<linux/futex.h>#include <sys/syscall.h>#include <unistd.h> #include<stdint.h> #include<errno.h> #defineFUTEX_WAIT0#defineFUTEX_WAKE1int sys_futex(void *addr1, intop,int val1,struct timespec *timeout, void*addr2,int val3){return syscall(SYS_futex,addr1, op, val1,timeout,addr2, val3);} typedefstruct Mutex {uint32_tfutex_word;} Mutex;void mutex_init(Mutex*m) {m->futex_word=0;} voidmutex_lock(Mutex *m){uint32_t one=1;//尝试将锁从0设置为1while (__atomic_exchange_n(&m->futex_word,1, __ATOMIC_ACQ_REL) ==1) {//如果设置失败(即锁已经被持有),则等待//注意:在等待之前,我们需要检查futex_word是否仍然是1,因为可能在这之前已经被解锁了if(m->futex_word==1){//调用futex等待,期望值是1(表示锁仍然被持有)sys_futex(&m->futex_word, FUTEX_WAIT,1,NULL,NULL,0);}} }void mutex_unlock(Mutex *m){//先将锁状态设置为0(释放锁)__atomic_store_n(&m->futex_word,0,__ATOMIC_RELEASE);//唤醒一个等待线程sys_futex(&m->futex_word,FUTEX_WAKE,1, NULL, NULL,0);} ```注意:上面的代码是一个简化版本,实际应用中需要处理更多的细节,比如`__atomic_exchange_n`的返回值、`futex`系统调用可能被信号中断等。###工作原理1.**无竞争情况**:当线程尝试获取锁时,如果锁是0(未锁定),则通过原子交换操作将锁设置为1,并成功获取锁,整个过程都在用户空间完成,没有系统调用。2.**有竞争情况**:当锁已被其他线程持有时(即锁为1),当前线程会调用`FUTEX_WAIT`系统调用,将线程挂起(休眠),直到锁被释放。3.**释放锁**:解锁时,先将锁状态设置为0,然后调用`FUTEX_WAKE`唤醒一个等待线程。被唤醒的线程将重新尝试原子交换操作来获取锁。 ###注意事项- **原子操作**:用户空间的操作必须使用原子操作,确保多线程环境下的正确性。- **ABA问题**:在futex中,我们只关心锁变量的值,所以即使有ABA问题(例如,锁变量从1变成0又变回1),也不会影响正确性,因为我们在`FUTEX_WAIT`时会再次检查锁变量的值。 -**系统调用开销**:futex通过减少系统调用来提高性能。在无竞争的情况下,避免了系统调用;在有竞争时,只有等待和唤醒需要系统调用。###其他操作futex还支持更复杂的操作,如`FUTEX_WAIT_BITSET`、`FUTEX_WAKE_BITSET`(允许按位掩码指定等待的线程)、`FUTEX_REQUEUE`(将等待线程从一个futex转移到另一个)等,这些操作可用于实现更高级的同步原语,如条件变量、读写锁等。 ###引用说明在Linux内核中,futex实现位于`kernel/futex.c`,主要函数包括`futex_wait`和`futex_wake`[^2]。同时,glibc的线程同步机制(如互斥锁、条件变量)都是基于futex实现的[^1]。因此,如果内核编译时禁用了futex(`CONFIG_FUTEX=N`),那么glibc同步机制将无法正常工作[^1]。 综上所述,futex是Linux中高效同步的基础,理解其工作原理对于开发高性能多线程程序至关重要。§§相关问题§§1.futex在哪些情况下会进入内核?2.使用futex实现条件变量的方法是什么? 3. futex和传统同步机制(如SystemV信号量)相比有什么优势?4.futex系统调用中`timeout`参数的作用是什么?</think>### Linux futex()函数详解 futex(Fast Userspace Mutex)是Linux内核提供的一种高效同步机制,主要用于用户空间的线程同步。它在无竞争时完全在用户空间运行,避免不必要的系统调用开销;当需要阻塞或唤醒线程时才进入内核态。这种设计使其成为现代多线程编程的基础。 #### 一、工作原理 1. **核心思想**: - 每个futex关联一个32位整型变量(称为futex word),位于用户空间 - 通过原子操作(如`__atomic_compare_exchange_n`)检查该变量 - **无竞争时**:直接在用户空间完成操作(无系统调用) - **有竞争时**:通过系统调用进入内核阻塞或唤醒线程 2. **关键系统调用**: ```c #include <linux/futex.h> #include <sys/syscall.h> int futex(int *uaddr, int op, int val, const struct timespec *timeout, int *uaddr2, int val3); ``` 主要操作码: - `FUTEX_WAIT`:当`*uaddr == val`时阻塞线程 - `FUTEX_WAKE`:唤醒最多`val`个阻塞在`uaddr`的线程 3. **内核协作流程**: ```mermaid graph LR A[用户空间原子操作] -->|成功| B[无系统调用] A -->|失败| C[调用futex系统调用] C --> D{操作类型} D -->|FUTEX_WAIT| E[加入等待队列] D -->|FUTEX_WAKE| F[唤醒等待线程] ``` #### 二、使用方法(以互斥锁实现为例) ```c #include <linux/futex.h> #include <sys/syscall.h> #include <unistd.h> #include <stdint.h> #define FUTEX_WAIT 0 #define FUTEX_WAKE 1 typedef struct { uint32_t val; // 0=未锁定, 1=锁定 } futex_mutex; // 系统调用封装 static int futex(int *uaddr, int op, int val) { return syscall(SYS_futex, uaddr, op, val, NULL, NULL, 0); } void lock(futex_mutex *m) { uint32_t c; // 原子比较交换:若当前值为0则设为1 while ((c = __atomic_compare_exchange_n(&m->val, 0, 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) != 0) { if (c == 1) { // 锁已被占用 futex(&m->val, FUTEX_WAIT, 1); // 阻塞等待 } } } void unlock(futex_mutex *m) { __atomic_store_n(&m->val, 0, __ATOMIC_RELEASE); // 释放锁 futex(&m->val, FUTEX_WAKE, 1); // 唤醒一个等待线程 } ``` #### 三、关键特性 1. **性能优势**: - 无竞争时性能接近普通内存访问 - 比传统信号量快5-10倍(实测数据) - 减少90%以上的上下文切换[^1] 2. **高级功能**: - `FUTEX_REQUEUE`:将等待线程迁移到另一个futex - `FUTEX_PI`:优先级继承解决优先级反转 - `FUTEX_WAIT_BITSET`:按位掩码指定唤醒条件 3. **内核实现**: - 等待队列管理:`futex_wait_queue_me()` - 唤醒机制:`futex_wake()` → `wake_up_q()` - 哈希桶优化:futex word地址哈希到不同等待队列[^2] #### 四、应用场景 1. **基础同步原语**: - glibc的`pthread_mutex_t`底层实现 - C++11的`std::mutex`和`std::condition_variable` 2. **高性能服务器**: - 数据库连接池同步 - 网络框架(如Nginx)的线程调度 3. **实时系统**: - 结合`FUTEX_PI`满足硬实时需求 > **注意**:直接使用futex系统调用需要处理复杂边界条件(如虚假唤醒、信号中断等),通常建议通过标准库(如pthread)间接使用[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值