引言
_IO_lock_t 是 GLibc 库中广泛用于 IO 读取的锁,关于它的结构体,相关的函数使用是相当有研究价值的,尤其是使用了大量的宏技术。我们就来深入分析学习一下_IO_lock_t 的实现机制
代码位置
glibc 源码下载:https://www.gnu.org/software/libc/libc.html
glibc/sysdeps/nptl/stdio-lock.h
结构体定义
非常简单:
int lock---lock 状态
int cnt---持有锁计数
void* owner---线程对象指针
typedef struct { int lock; int cnt; void *owner; } _IO_lock_t;
复制代码
宏定义
_IO_lock_t_defined---标识_IO_lock_t 已定义
#define _IO_lock_t_defined 1
复制代码
_IO_lock_initializer---初始化器
即上面结构体的赋初值,分别为 0,0,NULL。
#define _IO_lock_initializer { LLL_LOCK_INITIALIZER, 0, NULL }
//glibc/sysdeps/nptl/lowlevellock.h
170 /* Initializers for lock. */
171 #define LLL_LOCK_INITIALIZER (0)
复制代码
_IO_lock_init---变量赋初值
宏展开后为((void)((_name)=(_IO_lock_t) {(0),0,NULL}))
#define _IO_lock_init(_name) \
((void) ((_name) = (_IO_lock_t) _IO_lock_initializer))
复制代码
_IO_lock_fini---变量销毁
实际上是空语句
#define _IO_lock_fini(_name) \
((void) 0)
复制代码
_IO_lock_lock---lock
使用 do{...}while(0)保证语句只执行一次
37 #define _IO_lock_lock(_name) \
38 do { \
39 void *__self = THREAD_SELF; \
40 if (SINGLE_THREAD_P && (_name).owner == NULL) \
41 { \
42 (_name).lock = LLL_LOCK_INITIALIZER_LOCKED; \
43 (_name).owner = __self; \
44 } \
45 else if ((_name).owner != __self) \
46 { \
47 lll_lock ((_name).lock, LLL_PRIVATE); \
48 (_name).owner = __self; \
49 } \
50 else \
51 ++(_name).cnt; \
52 } while (0)
复制代码
首先获取当前的线程描述符 x86_64 架构中的 THREAD_SELF 实现(glibc/sysdeps/x86_64/nptl/tls.h)
实际上就是获取 pthread*
171 /* Return the thread descriptor for the current thread.
172
173 The contained asm must *not* be marked volatile since otherwise
174 assignments like
175 pthread_descr self = thread_self();
176 do not get optimized away. */
177 # if __GNUC_PREREQ (6, 0)
178 # define THREAD_SELF \
179 (*(struct pthread *__seg_fs *) offsetof (struct pthread, header.self))
180 # else
181 # define THREAD_SELF \
182 ({ struct pthread *__self; \
183 asm ("mov %%fs:%c1,%0" : "=r" (__self) \
184 : "i" (offsetof (struct pthread, header.self))); \
185 __self;})
186 # endif
复制代码
SINGLE_THREAD_P(判断当前进程是否是单线程),THREAD_GETMEM 对应读取 thread descriptor 中的对应成员变量,直接内嵌汇编,读取 header.multiple_threads 的值,判断是否等于 0
//glibc/sysdeps/unix/sysv/linux/single-thread.h
26 /* The default way to check if the process is single thread is by using the
27 pthread_t 'multiple_threads' field. However, for some architectures it is
28 faster to either use an extra field on TCB or global variables (the TCB
29 field is also used on x86 for some single-thread atomic optimizations).
30
31 The ABI might define SINGLE_THREAD_BY_GLOBAL to enable the single thread
32 check to use global variables instead of the pthread_t field. */
33
34 #if !defined SINGLE_THREAD_BY_GLOBAL || IS_IN (rtld)
35 # define SINGLE_THREAD_P \
36 (THREAD_GETMEM (THREAD_SELF, header.multiple_threads) == 0)
37 #else
38 # define SINGLE_THREAD_P (__libc_single_threaded_internal != 0)
39 #endif
19 /* Read member of the thread descriptor directly. */
20 # define THREAD_GETMEM(descr, member) \
21 ({ __typeof (descr->member) __value; \
22 _Static_assert (sizeof (__value) == 1 \
23 || sizeof (__value) == 4 \
24 || sizeof (__value) == 8, \
25 "size of per-thread data"); \
26 if (sizeof (__value) == 1) \
27 asm volatile ("movb %%fs:%P2,%b0" \
28 : "=q" (__value) \
29 : "0" (0), "i" (offsetof (struct pthread, member))); \
30 else if (sizeof (__value) == 4) \
31 asm volatile ("movl %%fs:%P1,%0" \
32 : "=r" (__value) \
33 : "i" (offsetof (struct pthread, member))); \
34 else /* 8 */ \
35 { \
36 asm volatile ("movq %%fs:%P1,%q0" \
37 : "=r" (__value) \
38 : "i" (offsetof (struct pthread, member))); \
39 } \
40 __value; })
复制代码
3.第一种情况:如果当前是单线程而且传入的_name.owner 为空,说明是刚 init 成功后的空_IO_lock_t,那么对 lock 和 owner 成员变量进行初始化,lock 初始化为 1,表示锁住,owner 保存当前线程描述符__self
42 (_name).lock = LLL_LOCK_INITIALIZER_LOCKED; \
43 (_name).owner = __self;
//glibc/sysdeps/nptl/lowlevellock.h
172 #define LLL_LOCK_INITIALIZER_LOCKED (1)
复制代码
第二种情况:如果传入的_name.owner 不等于__self,说明是之前被其它线程持有过,先使用 lll_lock 加锁,然后将_name.owner 保存为当前线程描述符__self;lll_lock(lowlevellock,底层锁)的逻辑后面讲解
47 lll_lock ((_name).lock, LLL_PRIVATE); \
48 (_name).owner = __self;
//glibc/sysdeps/nptl/lowlevellock-futex.h
47 /* Values for 'private' parameter of locking macros. Yes, the
48 definition seems to be backwards. But it is not. The bit will be
49 reversed before passing to the system call. */
50 #define LLL_PRIVATE 0
51 #define LLL_SHARED FUTEX_PRIVATE_FLAG
复制代码
第三种情况:不是前面两种情况,说明是当前线程,而且之前已经 lock 了,此时就++(_name).cnt,计数加一;
_IO_lock_trylock---trylock
trylock 的逻辑与 lock 的逻辑大致相似,首先获取当前线程描述符 THREAD_SELF,
54 #define _IO_lock_trylock(_name) \
55 ({ \
56 int __result = 0; \
57 void *__self = THREAD_SELF; \
58 if ((_name).owner != __self) \
59 { \
60 if (lll_trylock ((_name).lock) == 0) \
61 (_name).owner = __self; \
62 else \
63 __result = EBUSY; \
64 } \
65 else \
66 ++(_name).cnt; \
67 __result; \
68 })
复制代码
_IO_lock_unlock---unlock
unlock 与 lock 的逻辑也大致相似:
第一种情况:单线程且该线程中持有该锁的计数为 0,即在此次之前的 lock 和 unlock 都是匹配的,只剩一个 lock 没有匹配,那么将_name 变量的 owner 和 lock 恢复到初始值 NULL 和 0;
第二种情况:不是单线程,但持有计数为 0,那么将(_name).owner 置为 NULL,同时调用 lll_unlock 真正解锁;
第三种情况:持有计数不为 0,那就计数减一即可。
70 #define _IO_lock_unlock(_name) \
71 do { \
72 if (SINGLE_THREAD_P && (_name).cnt == 0) \
73 { \
74 (_name).owner = NULL; \
75 (_name).lock = 0; \
76 } \
77 else if ((_name).cnt == 0) \
78 { \
79 (_name).owner = NULL; \
80 lll_unlock ((_name).lock, LLL_PRIVATE); \
81 } \
82 else \
83 --(_name).cnt; \
84 } while (0)
复制代码
总结
_IO_lock_t 保存当前 lock 的状态,持有计数,当前线程信息,通过计数控制保证当前线程的 lock 和 unlock。
扩展知识
lll_lock
lock 初始化
//第一次调用_IO_lock_lock时初始化
(_name).lock = LLL_LOCK_INITIALIZER_LOCKED;
//第二次不同线程调用_IO_lock_lock时调用lll_lock,入参是LLL_LOCK_INITIALIZER_LOCKED和LLL_PRIVATE
lll_lock ((_name).lock, LLL_PRIVATE);
//glibc/sysdeps/nptl/lowlevellock.h
172 #define LLL_LOCK_INITIALIZER_LOCKED (1)
//glibc/sysdeps/nptl/lowlevellock-futex.h
47 /* Values for 'private' parameter of locking macros. Yes, the
48 definition seems to be backwards. But it is not. The bit will be
49 reversed before passing to the system call. */
50 #define LLL_PRIVATE 0
51 #define LLL_SHARED FUTEX_PRIVATE_FLAG
复制代码
lll_lock 的调用原理
实际上还是利用了底层的原子操作,LLL_PRIVATE 方式是调用__lll_lock_wait_private 加锁等待。atomic_compare_and_exchange_bool_acq (mem,newval,oldval)
函数的作用是如果 mem 的值等于 oldval,则把 newval 赋值给 mem,返回 0,否则不做任何处理,返回 1.即,如果之前有线程释放了锁,lock=0,这时一个其它线程进来拿锁,futex=0,所以被置为 1,返回 0,此时没有资源冲突,lock 结束;
如果之前有线程还拿着锁,即 lock=LLL_LOCK_INITIALIZER_LOCKED=1,这时进来,futex!=0,所以什么都没有做,返回 1,此时出现资源冲突,进入等待,根据 private 区分为两种__lll_lock_wait_private 和__lll_lock_wait。
//glibc/sysdeps/nptl/lowlevellock.h
84 /* This is an expression rather than a statement even though its value is
85 void, so that it can be used in a comma expression or as an expression
86 that's cast to void. */
87 /* The inner conditional compiles to a call to __lll_lock_wait_private if
88 private is known at compile time to be LLL_PRIVATE, and to a call to
89 __lll_lock_wait otherwise. */
90 /* If FUTEX is 0 (not acquired), set to 1 (acquired with no waiters) and
91 return. Otherwise, ensure that it is >1 (acquired, possibly with waiters)
92 and then block until we acquire the lock, at which point FUTEX will still be
93 >1. The lock is always acquired on return. */
94 #define __lll_lock(futex, private) \
95 ((void) \
96 ({ \
97 int *__futex = (futex); \
98 if (__glibc_unlikely \
99 (atomic_compare_and_exchange_bool_acq (__futex, 1, 0))) \
100 { \
101 if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) \
102 __lll_lock_wait_private (__futex); \
103 else \
104 __lll_lock_wait (__futex, private); \
105 } \
106 }))
107 #define lll_lock(futex, private) \
108 __lll_lock (&(futex), private)
复制代码
__lll_lock_wait_private 的源码如下:首先加载(原子操作)futex 的值如果等于 2,进入循环体内部,开始加入 futex 队列等待资源
//glibc/nptl/lowlevellock.c
24 void
25 __lll_lock_wait_private (int *futex)
26 {
27 if (atomic_load_relaxed (futex) == 2)
28 goto futex;
29
30 while (atomic_exchange_acquire (futex, 2) != 0)
31 {
32 futex:
33 LIBC_PROBE (lll_lock_wait_private, 1, futex);
34 futex_wait ((unsigned int *) futex, 2, LLL_PRIVATE); /* Wait if *futex == 2. */
35 }
36 }
复制代码
lll_trylock
实际上逻辑与 lll_lock 的逻辑一致,但没有在资源冲突时等待,而是返回非 0 值,表示获取失败,则在_IO_lock_trylock 中表示为 EBUSY,被占用。
65 /* If LOCK is 0 (not acquired), set to 1 (acquired with no waiters) and return
66 0. Otherwise leave lock unchanged and return non-zero to indicate that the
67 lock was not acquired. */
68 #define __lll_trylock(lock) \
69 __glibc_unlikely (atomic_compare_and_exchange_bool_acq ((lock), 1, 0))
70 #define lll_trylock(lock) \
71 __lll_trylock (&(lock))
复制代码
lll_unlock
会通过__futex 的读取来唤醒处于等待队列中的任务拿到对应的资源
133 /* This is an expression rather than a statement even though its value is
134 void, so that it can be used in a comma expression or as an expression
135 that's cast to void. */
136 /* Unconditionally set FUTEX to 0 (not acquired), releasing the lock. If FUTEX
137 was >1 (acquired, possibly with waiters), then wake any waiters. The waiter
138 that acquires the lock will set FUTEX to >1.
139 Evaluate PRIVATE before releasing the lock so that we do not violate the
140 mutex destruction requirements. Specifically, we need to ensure that
141 another thread can destroy the mutex (and reuse its memory) once it
142 acquires the lock and when there will be no further lock acquisitions;
143 thus, we must not access the lock after releasing it, or those accesses
144 could be concurrent with mutex destruction or reuse of the memory. */
145 #define __lll_unlock(futex, private) \
146 ((void) \
147 ({ \
148 int *__futex = (futex); \
149 int __private = (private); \
150 int __oldval = atomic_exchange_rel (__futex, 0); \
151 if (__glibc_unlikely (__oldval > 1)) \
152 { \
153 if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) \
154 __lll_lock_wake_private (__futex); \
155 else \
156 __lll_lock_wake (__futex, __private); \
157 } \
158 }))
159 #define lll_unlock(futex, private) \
160 __lll_unlock (&(futex), private)
复制代码
__lll_lock_wake_private 原理通过汇编指令唤醒前面等待资源的线程
54 void
55 __lll_lock_wake_private (int *futex)
56 {
57 lll_futex_wake (futex, 1, LLL_PRIVATE);
58 }
86 /* Wake up up to NR waiters on FUTEXP. */
87 # define lll_futex_wake(futexp, nr, private) \
88 lll_futex_syscall (4, futexp, \
89 __lll_private_flag (FUTEX_WAKE, private), nr, 0)
复制代码
评论