Linux线程同步(三)---互斥锁源码分析

先给自己打个广告,本人的微信公众号:嵌入式Linux江湖,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题。
在这里插入图片描述

一 源码分析

1.linux中用户态的mutex实现在哪里?
下载Glibc源码后解压,mutex相关实现函数在:/work/tools/glibc-2.34/nptl中,头文件在glibc-2.34/sysdeps/nptl/bits路径下。
2.pthread_mutex_t联合体定

#/glibc-2.34/sysdeps/nptl/bits/pthreadtypes.h
typedef union
{
  struct __pthread_mutex_s __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

pthread_mutex_t主要有三个成员变量,其中最关键的是__pthread_mutex_s

#/glibc-2.34/sysdeps/nptl/bits/struct_mutex.h
/* Generic struct for both POSIX and C11 mutexes.  New ports are expected
   to use the default layout, however architecture can redefine it to
   add arch-specific extension (such as lock-elision).  The struct have
   a size of 32 bytes on LP32 and 40 bytes on LP64 architectures.  */

struct __pthread_mutex_s
{
  int __lock __LOCK_ALIGNMENT;
  unsigned int __count;
  int __owner;
#if __WORDSIZE == 64
  unsigned int __nusers;
#endif
  /* KIND must stay at this position in the structure to maintain
     binary compatibility with static initializers.

     Concurrency notes:
     The __kind of a mutex is initialized either by the static
     PTHREAD_MUTEX_INITIALIZER or by a call to pthread_mutex_init.

     After a mutex has been initialized, the __kind of a mutex is usually not
     changed.  BUT it can be set to -1 in pthread_mutex_destroy or elision can
     be enabled.  This is done concurrently in the pthread_mutex_*lock
     functions by using the macro FORCE_ELISION. This macro is only defined
     for architectures which supports lock elision.

     For elision, there are the flags PTHREAD_MUTEX_ELISION_NP and
     PTHREAD_MUTEX_NO_ELISION_NP which can be set in addition to the already
     set type of a mutex.  Before a mutex is initialized, only
     PTHREAD_MUTEX_NO_ELISION_NP can be set with pthread_mutexattr_settype.

     After a mutex has been initialized, the functions pthread_mutex_*lock can
     enable elision - if the mutex-type and the machine supports it - by
     setting the flag PTHREAD_MUTEX_ELISION_NP. This is done concurrently.
     Afterwards the lock / unlock functions are using specific elision
     code-paths.  */
  int __kind;
#if __WORDSIZE != 64
  unsigned int __nusers;
#endif
#if __WORDSIZE == 64
  int __spins;
  __pthread_list_t __list;
# define __PTHREAD_MUTEX_HAVE_PREV      1
#else
  __extension__ union
  {
    int __spins;
    __pthread_slist_t __list;
  };
# define __PTHREAD_MUTEX_HAVE_PREV      0
#endif
};

一般地,__pthread_mutex_s中最重要的成员是如下四个(其他成员先忽略)

struct __pthread_mutex_s
{
  int __lock __LOCK_ALIGNMENT;
  unsigned int __count;
  int __owner;
  unsigned int __nusers;
};
1.lock表示当前mutex的状态,0表示初始化没有被持有的状态,此时可以对mutex执行lock操作,lock为1时表示当前mutex已经被持有,并且没有其他线程在等待它的释放,当lock > 1时,表示mutex被某个线程持有并且有另外的线程在等待它的释放。
2.count表示当前被持有的次数,一般来说对不可重入的锁,这个值只可能是01,对于可重入的锁,比如递归锁,这个值会大于13.owner用来记录持有当前mutex的线程id,如果没有线程持有,这个值为04.nusers用来记录当前有多少线程持有该互斥体,一般来说,这个值只能是01,但是对于读写锁来说,多个读线程是可以共同持有mutex的,因此用nusers来记录线程的数量。

3.___pthread_mutex_init初始化函数

#/glibc-2.34/nptl/pthread_mutex_init.c
int
___pthread_mutex_init (pthread_mutex_t *mutex,
		      const pthread_mutexattr_t *mutexattr)
{
  const struct pthread_mutexattr *imutexattr;

  ASSERT_TYPE_SIZE (pthread_mutex_t, __SIZEOF_PTHREAD_MUTEX_T);

  /* __kind is the only field where its offset should be checked to
     avoid ABI breakage with static initializers.  */
  ASSERT_PTHREAD_INTERNAL_OFFSET (pthread_mutex_t, __data.__kind,
				  __PTHREAD_MUTEX_KIND_OFFSET);
  ASSERT_PTHREAD_INTERNAL_MEMBER_SIZE (pthread_mutex_t, __data.__kind, int);

  imutexattr = ((const struct pthread_mutexattr *) mutexattr
		?: &default_mutexattr);

  /* Sanity checks.  */
  switch (__builtin_expect (imutexattr->mutexkind
			    & PTHREAD_MUTEXATTR_PROTOCOL_MASK,
			    PTHREAD_PRIO_NONE
			    << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT))
    {
    case PTHREAD_PRIO_NONE << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      break;

    case PTHREAD_PRIO_INHERIT << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      if (__glibc_unlikely (prio_inherit_missing ()))
	return ENOTSUP;
      break;

    default:
      /* XXX: For now we don't support robust priority protected mutexes.  */
      if (imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_ROBUST)
	return ENOTSUP;
      break;
    }

  /* Clear the whole variable.  */
  memset (mutex, '\0', __SIZEOF_PTHREAD_MUTEX_T);

  /* Copy the values from the attribute.  */
  int mutex_kind = imutexattr->mutexkind & ~PTHREAD_MUTEXATTR_FLAG_BITS;

  if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_ROBUST) != 0)
    {
#ifndef __ASSUME_SET_ROBUST_LIST
      if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_PSHARED) != 0
	  && !__nptl_set_robust_list_avail)
	return ENOTSUP;
#endif

      mutex_kind |= PTHREAD_MUTEX_ROBUST_NORMAL_NP;
    }

  switch (imutexattr->mutexkind & PTHREAD_MUTEXATTR_PROTOCOL_MASK)
    {
    case PTHREAD_PRIO_INHERIT << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      mutex_kind |= PTHREAD_MUTEX_PRIO_INHERIT_NP;
      break;

    case PTHREAD_PRIO_PROTECT << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      mutex_kind |= PTHREAD_MUTEX_PRIO_PROTECT_NP;

      int ceiling = (imutexattr->mutexkind
		     & PTHREAD_MUTEXATTR_PRIO_CEILING_MASK)
		    >> PTHREAD_MUTEXATTR_PRIO_CEILING_SHIFT;
      if (! ceiling)
	{
	  /* See __init_sched_fifo_prio.  */
	  if (atomic_load_relaxed (&__sched_fifo_min_prio) == -1)
	    __init_sched_fifo_prio ();
	  if (ceiling < atomic_load_relaxed (&__sched_fifo_min_prio))
	    ceiling = atomic_load_relaxed (&__sched_fifo_min_prio);
	}
      mutex->__data.__lock = ceiling << PTHREAD_MUTEX_PRIO_CEILING_SHIFT;
      break;

    default:
      break;
    }

  /* The kernel when waking robust mutexes on exit never uses
     FUTEX_PRIVATE_FLAG FUTEX_WAKE.  */
  if ((imutexattr->mutexkind & (PTHREAD_MUTEXATTR_FLAG_PSHARED
				| PTHREAD_MUTEXATTR_FLAG_ROBUST)) != 0)
    mutex_kind |= PTHREAD_MUTEX_PSHARED_BIT;

  /* See concurrency notes regarding __kind in struct __pthread_mutex_s
     in sysdeps/nptl/bits/thread-shared-types.h.  */
  atomic_store_relaxed (&(mutex->__data.__kind), mutex_kind);

  /* Default values: mutex not used yet.  */
  // mutex->__count = 0;	already done by memset
  // mutex->__owner = 0;	already done by memset
  // mutex->__nusers = 0;	already done by memset
  // mutex->__spins = 0;	already done by memset
  // mutex->__next = NULL;	already done by memset

  LIBC_PROBE (mutex_init, 1, mutex);

  return 0;
}

init函数就比较简单了,将mutex结构体清零,设置结构体中__kind属性。函数的前三行部分是参数合法性判断

ASSERT_TYPE_SIZE (pthread_mutex_t, __SIZEOF_PTHREAD_MUTEX_T);

/* __kind is the only field where its offset should be checked to
     avoid ABI breakage with static initializers.  */
ASSERT_PTHREAD_INTERNAL_OFFSET (pthread_mutex_t, __data.__kind,
				  __PTHREAD_MUTEX_KIND_OFFSET);
ASSERT_PTHREAD_INTERNAL_MEMBER_SIZE (pthread_mutex_t, __data.__kind, int);

紧接着的一段语句都是为了设置mutex_kind的值

imutexattr = ((const struct pthread_mutexattr *) mutexattr
		?: &default_mutexattr);

  /* Sanity checks.  */
  switch (__builtin_expect (imutexattr->mutexkind
			    & PTHREAD_MUTEXATTR_PROTOCOL_MASK,
			    PTHREAD_PRIO_NONE
			    << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT))
    {
    case PTHREAD_PRIO_NONE << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      break;

    case PTHREAD_PRIO_INHERIT << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      if (__glibc_unlikely (prio_inherit_missing ()))
	return ENOTSUP;
      break;

    default:
      /* XXX: For now we don't support robust priority protected mutexes.  */
      if (imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_ROBUST)
	return ENOTSUP;
      break;
    }

  /* Clear the whole variable.  */
  memset (mutex, '\0', __SIZEOF_PTHREAD_MUTEX_T);

  /* Copy the values from the attribute.  */
  int mutex_kind = imutexattr->mutexkind & ~PTHREAD_MUTEXATTR_FLAG_BITS;

  if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_ROBUST) != 0)
    {
#ifndef __ASSUME_SET_ROBUST_LIST
      if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_PSHARED) != 0
	  && !__nptl_set_robust_list_avail)
	return ENOTSUP;
#endif

      mutex_kind |= PTHREAD_MUTEX_ROBUST_NORMAL_NP;
    }

  switch (imutexattr->mutexkind & PTHREAD_MUTEXATTR_PROTOCOL_MASK)
    {
    case PTHREAD_PRIO_INHERIT << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      mutex_kind |= PTHREAD_MUTEX_PRIO_INHERIT_NP;
      break;

    case PTHREAD_PRIO_PROTECT << PTHREAD_MUTEXATTR_PROTOCOL_SHIFT:
      mutex_kind |= PTHREAD_MUTEX_PRIO_PROTECT_NP;

      int ceiling = (imutexattr->mutexkind
		     & PTHREAD_MUTEXATTR_PRIO_CEILING_MASK)
		    >> PTHREAD_MUTEXATTR_PRIO_CEILING_SHIFT;
      if (! ceiling)
	{
	  /* See __init_sched_fifo_prio.  */
	  if (atomic_load_relaxed (&__sched_fifo_min_prio) == -1)
	    __init_sched_fifo_prio ();
	  if (ceiling < atomic_load_relaxed (&__sched_fifo_min_prio))
	    ceiling = atomic_load_relaxed (&__sched_fifo_min_prio);
	}
      mutex->__data.__lock = ceiling << PTHREAD_MUTEX_PRIO_CEILING_SHIFT;
      break;

    default:
      break;
    }

  /* The kernel when waking robust mutexes on exit never uses
     FUTEX_PRIVATE_FLAG FUTEX_WAKE.  */
  if ((imutexattr->mutexkind & (PTHREAD_MUTEXATTR_FLAG_PSHARED
				| PTHREAD_MUTEXATTR_FLAG_ROBUST)) != 0)
    mutex_kind |= PTHREAD_MUTEX_PSHARED_BIT;

设置__kind的值

/* See concurrency notes regarding __kind in struct __pthread_mutex_s
     in sysdeps/nptl/bits/thread-shared-types.h.  */
  atomic_store_relaxed (&(mutex->__data.__kind), mutex_kind);

  /* Default values: mutex not used yet.  */
  // mutex->__count = 0;	already done by memset
  // mutex->__owner = 0;	already done by memset
  // mutex->__nusers = 0;	already done by memset
  // mutex->__spins = 0;	already done by memset
  // mutex->__next = NULL;	already done by memset

  LIBC_PROBE (mutex_init, 1, mutex);

有人可能会有疑问,我们在用户态调用的函数是pthread_mutex_init,但是上面的函数名称是___pthread_mutex_init,这两个是同样的函数吗?
答案是肯定的,glibc中做了如下的声明,有关这部分声明大家可以追一下代码看一下,这里就不做详细分析。
在这里插入图片描述
4.___pthread_mutex_lock函数
同样地,我们在app层调用的函数时pthread_mutex_lock,它在glibc中的声明如下,也是和pthread_mutex_init相同的方式。

#/glibc-2.34/nptl/pthread_mutex_lock.c
#if PTHREAD_MUTEX_VERSIONS
libc_hidden_ver (___pthread_mutex_lock, __pthread_mutex_lock)
# ifndef SHARED
strong_alias (___pthread_mutex_lock, __pthread_mutex_lock)
# endif
//这里声明了pthread_mutex_lock,实际上就是___pthread_mutex_lock
versioned_symbol (libpthread, ___pthread_mutex_lock, pthread_mutex_lock, GLIBC_2_0);

# if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_0, GLIBC_2_34)
compat_symbol (libpthread, ___pthread_mutex_lock, __pthread_mutex_lock,GLIBC_2_0);
# endif
#endif /* PTHREAD_MUTEX_VERSIONS */

接下来,我们看___pthread_mutex_lock函数,它的声明如下,___pthread_mutex_lock函数实际上就是PTHREAD_MUTEX_LOCK。

# define PTHREAD_MUTEX_LOCK ___pthread_mutex_lock

再看PTHREAD_MUTEX_LOCK,完整实现部分如下

int
PTHREAD_MUTEX_LOCK (pthread_mutex_t *mutex)
{
  /* See concurrency notes regarding mutex type which is loaded from __kind
     in struct __pthread_mutex_s in sysdeps/nptl/bits/thread-shared-types.h.  */
  unsigned int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);

  LIBC_PROBE (mutex_entry, 1, 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))
    {
      FORCE_ELISION (mutex, goto elision);
    simple:
      /* Normal mutex.  */
      LLL_MUTEX_LOCK_OPTIMIZED (mutex);
      assert (mutex->__data.__owner == 0);
    }
#if ENABLE_ELISION_SUPPORT
  else if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_ELISION_NP))
    {
  elision: __attribute__((unused))
      /* This case can never happen on a system without elision,
         as the mutex type initialization functions will not
	 allow to set the elision flags.  */
      /* Don't record owner or users for elision case.  This is a
         tail call.  */
      return LLL_MUTEX_LOCK_ELISION (mutex);
    }
#endif
  else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
			     == PTHREAD_MUTEX_RECURSIVE_NP, 1))
    {
      /* Recursive mutex.  */
      pid_t id = THREAD_GETMEM (THREAD_SELF, tid);

      /* Check whether we already hold the mutex.  */
      if (mutex->__data.__owner == id)
	{
	  /* Just bump the counter.  */
	  if (__glibc_unlikely (mutex->__data.__count + 1 == 0))
	    /* Overflow of the counter.  */
	    return EAGAIN;

	  ++mutex->__data.__count;

	  return 0;
	}

      /* We have to get the mutex.  */
      LLL_MUTEX_LOCK_OPTIMIZED (mutex);

      assert (mutex->__data.__owner == 0);
      mutex->__data.__count = 1;
    }
  else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
			  == PTHREAD_MUTEX_ADAPTIVE_NP, 1))
    {
      if (LLL_MUTEX_TRYLOCK (mutex) != 0)
	{
	  int cnt = 0;
	  int max_cnt = MIN (max_adaptive_count (),
			     mutex->__data.__spins * 2 + 10);
	  do
	    {
	      if (cnt++ >= max_cnt)
		{
		  LLL_MUTEX_LOCK (mutex);
		  break;
		}
	      atomic_spin_nop ();
	    }
	  while (LLL_MUTEX_TRYLOCK (mutex) != 0);

	  mutex->__data.__spins += (cnt - mutex->__data.__spins) / 8;
	}
      assert (mutex->__data.__owner == 0);
    }
  else
    {
      pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
      assert (PTHREAD_MUTEX_TYPE (mutex) == PTHREAD_MUTEX_ERRORCHECK_NP);
      /* Check whether we already hold the mutex.  */
      if (__glibc_unlikely (mutex->__data.__owner == id))
	return EDEADLK;
      goto simple;
    }

  pid_t id = THREAD_GETMEM (THREAD_SELF, tid);

  /* Record the ownership.  */
  mutex->__data.__owner = id;
#ifndef NO_INCR
  ++mutex->__data.__nusers;
#endif

  LIBC_PROBE (mutex_acquired, 1, mutex);

  return 0;
}

我们逐步分析这个函数中的实现,如下是参数检查

LIBC_PROBE (mutex_entry, 1, mutex);

如下是普通mutex(mutexkind = PTHREAD_MUTEX_NORMAL,也就是我们在电泳pthread_mutex_init设置的mutex类型)的调用,也是用户最常用的mutex类型,我们主要关注这段实现

if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_NP))
    {
      FORCE_ELISION (mutex, goto elision);
    simple:
      /* Normal mutex.  */
      LLL_MUTEX_LOCK_OPTIMIZED (mutex);
      assert (mutex->__data.__owner == 0);
    }

显然这个if分支中,主要是调用了宏LLL_MUTEX_LOCK_OPTIMIZED,现在我们重点关注一下这个宏

# define LLL_MUTEX_LOCK_OPTIMIZED(mutex) lll_mutex_lock_optimized (mutex)
static inline void
lll_mutex_lock_optimized (pthread_mutex_t *mutex)
{
  /* The single-threaded optimization is only valid for private
     mutexes.  For process-shared mutexes, the mutex could be in a
     shared mapping, so synchronization with another process is needed
     even without any threads.  If the lock is already marked as
     acquired, POSIX requires that pthread_mutex_lock deadlocks for
     normal mutexes, so skip the optimization in that case as
     well.  */
  int private = PTHREAD_MUTEX_PSHARED (mutex);
  if (private == LLL_PRIVATE && SINGLE_THREAD_P && mutex->__data.__lock == 0)
    mutex->__data.__lock = 1;
  else
    lll_lock (mutex->__data.__lock, private);
}

显然这个函数中主要是调用了lll_lock,我们继续看lll_lock宏的实现

#define lll_lock(futex, private)	\
  __lll_lock (&(futex), private)

继续看__lll_lock宏定义,如下

/* This is an expression rather than a statement even though its value is
   void, so that it can be used in a comma expression or as an expression
   that's cast to void.  */
/* The inner conditional compiles to a call to __lll_lock_wait_private if
   private is known at compile time to be LLL_PRIVATE, and to a call to
   __lll_lock_wait otherwise.  */
/* If FUTEX is 0 (not acquired), set to 1 (acquired with no waiters) and
   return.  Otherwise, ensure that it is >1 (acquired, possibly with waiters)
   and then block until we acquire the lock, at which point FUTEX will still be
   >1.  The lock is always acquired on return.  */
#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);                          \
       }                                                                \
   }))

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值