TLS 数据地址在所有线程中相同
TLS Data Address the Same in All Threads
我使用 GDB
追踪了 glibc-2.27
。在sysdeps/unix/sysv/linux/getsysstats.c
中的178
行,存在一个thread local storage
访问,如下图:
while (l < re && isspace (*l))
IIUC,isspace()
好像访问了一个table映射ASCII
字符到符号键入以快速确定当前字符是否为 space。这个table好像是一个TLS
。相关反汇编如下:
0x7f8f9ef480de <__GI___get_nprocs+318> mov 0x2cbd1b(%rip),%rax # 0x7f8f9f213e00
0x7f8f9ef480e5 <__GI___get_nprocs+325> mov %fs:(%rax),%rdi
rax
包含 0xffffffffffffff98
,IIUC 表示 table 的 地址,对于 each thread,使用以下等式计算:$fs_base + 0xffffffffffffff98
。当我使用这个等式为每个线程找到 table 地址时,它们都是 return 相同的 值 0x00007f8f9732b82c
。如下所示:
(gdb) thread apply all x/2x $fs_base + 0xffffffffffffff98
Thread 47 (Thread 22457.22471):
0x7f8f75dfc698: 0x9732b82c 0x00007f8f
Thread 46 (Thread 22457.22470):
0x7f8f768fd698: 0x9732b82c 0x00007f8f
Thread 45 (Thread 22457.22469):
0x7f8f773fe698: 0x9732b82c 0x00007f8f
Thread 44 (Thread 22457.22468):
0x7f8f77eff698: 0x9732b82c 0x00007f8f
Thread 43 (Thread 22457.22467):
0x7f8f80a53698: 0x9732b82c 0x00007f8f
Thread 37 (Thread 22457.22465):
0x7f8f81c55698: 0x9732b82c 0x00007f8f
Thread 36 (Thread 22457.22464):
0x7f8f82456698: 0x9732b82c 0x00007f8f
Thread 35 (Thread 22457.22463):
0x7f8f8e6b0698: 0x9732b82c 0x00007f8f
Thread 34 (Thread 22457.22461):
0x7f8f8f480698: 0x9732b82c 0x00007f8f
Thread 33 (Thread 22457.22460):
0x7f8f94824698: 0x9732b82c 0x00007f8f
Thread 32 (Thread 22457.22459):
0x7f8f9649f698: 0x9732b82c 0x00007f8f
Thread 31 (Thread 22457.22458):
0x7f8f96ea0698: 0x9732b82c 0x00007f8f
Thread 30 (Thread 22457.22457):
0x7f8fa2570a18: 0x9732b82c 0x00007f8f
Thread 29 (Thread 22457.22466):
0x7f8f81454698: 0x9732b82c 0x00007f8f
我认为 TLS
对 each 线程 exclusive,但是,在这里,all 线程在 0x00007f8f9732b82c
处使用 相同的 变量。为什么会这样?似乎链接器 识别 变量是 read-only
并且 保存 一些 space?
你已经证明每个线程都有一个不同的指针变量,在
的TLS存储中
0x7f8f75dfc698
对比
0x7f8f768fd698
等
它们都指向相同的 table 这一事实是完全正常的,除非您使用 uselocale(3)
在不同的线程中使用不同的语言环境。
我认为 glibc 有针对不同语言环境的静态常量 (.section .rodata
) 字符映射 table,它会根据语言环境设置一个指向右侧 table 的指针。为每个线程复制整个 table 会非常低效,浪费更多的 L3 缓存空间。如果它要这样做,您会期望整个 table 就在那里,没有任何间接级别。
看看 glibc 如何实现 isupper()
之类的功能 - 通过索引字符属性标志数组,并检查该数组元素中的特定位。 (即每个数组元素都是标志位图)。
https://code.woboq.org/userspace/glibc/ctype/ctype.h.html和相关的.c文件在同一目录下。
我使用 GDB
追踪了 glibc-2.27
。在sysdeps/unix/sysv/linux/getsysstats.c
中的178
行,存在一个thread local storage
访问,如下图:
while (l < re && isspace (*l))
IIUC,isspace()
好像访问了一个table映射ASCII
字符到符号键入以快速确定当前字符是否为 space。这个table好像是一个TLS
。相关反汇编如下:
0x7f8f9ef480de <__GI___get_nprocs+318> mov 0x2cbd1b(%rip),%rax # 0x7f8f9f213e00
0x7f8f9ef480e5 <__GI___get_nprocs+325> mov %fs:(%rax),%rdi
rax
包含 0xffffffffffffff98
,IIUC 表示 table 的 地址,对于 each thread,使用以下等式计算:$fs_base + 0xffffffffffffff98
。当我使用这个等式为每个线程找到 table 地址时,它们都是 return 相同的 值 0x00007f8f9732b82c
。如下所示:
(gdb) thread apply all x/2x $fs_base + 0xffffffffffffff98
Thread 47 (Thread 22457.22471):
0x7f8f75dfc698: 0x9732b82c 0x00007f8f
Thread 46 (Thread 22457.22470):
0x7f8f768fd698: 0x9732b82c 0x00007f8f
Thread 45 (Thread 22457.22469):
0x7f8f773fe698: 0x9732b82c 0x00007f8f
Thread 44 (Thread 22457.22468):
0x7f8f77eff698: 0x9732b82c 0x00007f8f
Thread 43 (Thread 22457.22467):
0x7f8f80a53698: 0x9732b82c 0x00007f8f
Thread 37 (Thread 22457.22465):
0x7f8f81c55698: 0x9732b82c 0x00007f8f
Thread 36 (Thread 22457.22464):
0x7f8f82456698: 0x9732b82c 0x00007f8f
Thread 35 (Thread 22457.22463):
0x7f8f8e6b0698: 0x9732b82c 0x00007f8f
Thread 34 (Thread 22457.22461):
0x7f8f8f480698: 0x9732b82c 0x00007f8f
Thread 33 (Thread 22457.22460):
0x7f8f94824698: 0x9732b82c 0x00007f8f
Thread 32 (Thread 22457.22459):
0x7f8f9649f698: 0x9732b82c 0x00007f8f
Thread 31 (Thread 22457.22458):
0x7f8f96ea0698: 0x9732b82c 0x00007f8f
Thread 30 (Thread 22457.22457):
0x7f8fa2570a18: 0x9732b82c 0x00007f8f
Thread 29 (Thread 22457.22466):
0x7f8f81454698: 0x9732b82c 0x00007f8f
我认为 TLS
对 each 线程 exclusive,但是,在这里,all 线程在 0x00007f8f9732b82c
处使用 相同的 变量。为什么会这样?似乎链接器 识别 变量是 read-only
并且 保存 一些 space?
你已经证明每个线程都有一个不同的指针变量,在
的TLS存储中
0x7f8f75dfc698
对比
0x7f8f768fd698
等
它们都指向相同的 table 这一事实是完全正常的,除非您使用 uselocale(3)
在不同的线程中使用不同的语言环境。
我认为 glibc 有针对不同语言环境的静态常量 (.section .rodata
) 字符映射 table,它会根据语言环境设置一个指向右侧 table 的指针。为每个线程复制整个 table 会非常低效,浪费更多的 L3 缓存空间。如果它要这样做,您会期望整个 table 就在那里,没有任何间接级别。
看看 glibc 如何实现 isupper()
之类的功能 - 通过索引字符属性标志数组,并检查该数组元素中的特定位。 (即每个数组元素都是标志位图)。
https://code.woboq.org/userspace/glibc/ctype/ctype.h.html和相关的.c文件在同一目录下。