1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Linux线程同步(三)---互斥锁源码分析

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

时间:2020-10-10 06:54:26

相关推荐

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.htypedef 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 expectedto use the default layout, however architecture can redefine it toadd arch-specific extension (such as lock-elision). The struct havea 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 == 64unsigned int __nusers;#endif/* KIND must stay at this position in the structure to maintainbinary compatibility with static initializers.Concurrency notes:The __kind of a mutex is initialized either by the staticPTHREAD_MUTEX_INITIALIZER or by a call to pthread_mutex_init.After a mutex has been initialized, the __kind of a mutex is usually notchanged. BUT it can be set to -1 in pthread_mutex_destroy or elision canbe enabled. This is done concurrently in the pthread_mutex_*lockfunctions by using the macro FORCE_ELISION. This macro is only definedfor architectures which supports lock elision.For elision, there are the flags PTHREAD_MUTEX_ELISION_NP andPTHREAD_MUTEX_NO_ELISION_NP which can be set in addition to the alreadyset type of a mutex. Before a mutex is initialized, onlyPTHREAD_MUTEX_NO_ELISION_NP can be set with pthread_mutexattr_settype.After a mutex has been initialized, the functions pthread_mutex_*lock canenable elision - if the mutex-type and the machine supports it - bysetting the flag PTHREAD_MUTEX_ELISION_NP. This is done concurrently.Afterwards the lock / unlock functions are using specific elisioncode-paths. */int __kind;#if __WORDSIZE != 64unsigned int __nusers;#endif#if __WORDSIZE == 64int __spins;__pthread_list_t __list;# define __PTHREAD_MUTEX_HAVE_PREV1#else__extension__ union{int __spins;__pthread_slist_t __list;};# define __PTHREAD_MUTEX_HAVE_PREV0#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表示当前被持有的次数,一般来说对不可重入的锁,这个值只可能是0和1,对于可重入的锁,比如递归锁,这个值会大于1。3.owner用来记录持有当前mutex的线程id,如果没有线程持有,这个值为0。4.nusers用来记录当前有多少线程持有该互斥体,一般来说,这个值只能是0和1,但是对于读写锁来说,多个读线程是可以共同持有mutex的,因此用nusers来记录线程的数量。

3.___pthread_mutex_init初始化函数

#/glibc-2.34/nptl/pthread_mutex_init.cint___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 toavoid 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_LISTif ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_PSHARED) != 0&& !__nptl_set_robust_list_avail)return ENOTSUP;#endifmutex_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 usesFUTEX_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_sin 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 memsetLIBC_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 toavoid 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_LISTif ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_PSHARED) != 0&& !__nptl_set_robust_list_avail)return ENOTSUP;#endifmutex_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 usesFUTEX_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_sin 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 memsetLIBC_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_VERSIONSlibc_hidden_ver (___pthread_mutex_lock, __pthread_mutex_lock)# ifndef SHAREDstrong_alias (___pthread_mutex_lock, __pthread_mutex_lock)# endif//这里声明了pthread_mutex_lock,实际上就是___pthread_mutex_lockversioned_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,完整实现部分如下

intPTHREAD_MUTEX_LOCK (pthread_mutex_t *mutex){/* See concurrency notes regarding mutex type which is loaded from __kindin 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_SUPPORTelse 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 notallow to set the elision flags. *//* Don't record owner or users for elision case. This is atail call. */return LLL_MUTEX_LOCK_ELISION (mutex);}#endifelse 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;#endifLIBC_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 voidlll_mutex_lock_optimized (pthread_mutex_t *mutex){/* The single-threaded optimization is only valid for privatemutexes. For process-shared mutexes, the mutex could be in ashared mapping, so synchronization with another process is neededeven without any threads. If the lock is already marked asacquired, POSIX requires that pthread_mutex_lock deadlocks fornormal mutexes, so skip the optimization in that case aswell. */int private = PTHREAD_MUTEX_PSHARED (mutex);if (private == LLL_PRIVATE && SINGLE_THREAD_P && mutex->__data.__lock == 0)mutex->__data.__lock = 1;elselll_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 isvoid, so that it can be used in a comma expression or as an expressionthat's cast to void. *//* The inner conditional compiles to a call to __lll_lock_wait_private ifprivate 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) andreturn. 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);\} \}))

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。