返回

Glibc---_IO_lock_t的源码学习

发布时间:2022-12-16 03:31:42 294
# linux# 研究# 技术# 信息

引言

_IO_lock_t是GLibc库中广泛用于IO读取的锁,关于它的结构体,相关的函数使用是相当有研究价值的,尤其是使用了大量的宏技术。我们就来深入分析学习一下_IO_lock_t的实现机制

代码位置

glibc源码下载:​​www.gnu.org/software/li…​​

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)
  1. 首先获取当前的线程描述符 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
  1. SINGLE_THREAD_P(判断当前进程是否是单线程),THREAD_GETMEM对应读取thread descriptor中的对应成员变量,直接内嵌汇编,读取header.multiple_threads的值,判断是否等于0
//glibc/sysdeps/unix/sysv/linux/single-thread.h26 /* 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 #else38 # define SINGLE_THREAD_P (__libc_single_threaded_internal != 0)39 #endif19 /* 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.h172 #define LLL_LOCK_INITIALIZER_LOCKED (1)
  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.h47 /* 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
  1. 第三种情况:不是前面两种情况,说明是当前线程,而且之前已经lock了,此时就++(_name).cnt,计数加一;

_IO_lock_trylock---trylock

trylock的逻辑与lock的逻辑大致相似,首先获取当前线程描述符THREAD_SELF,

  • 第一种情况:(_name).owner != __self,说明是之前其它线程持有过该lock,那么调用lll_trylock,返回值为0说明加锁成功,那么将_name.owner更新为当前的线程描述符,否则将返回结果置为EBUSY,忙状态;
  • 第二种情况:说明是当前线程,而且之前已经lock了,此时就++(_name).cnt,计数加一;
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.h172 #define LLL_LOCK_INITIALIZER_LOCKED (1)//glibc/sysdeps/nptl/lowlevellock-futex.h47 /* 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.h84 /* 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.c24 void25 __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 void55 __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)
特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
C语言-比较两个数的大小 2022-12-16 03:15:13