TLS 数据在所有线程中寻址相同

问题描述

我使用 glibc-2.27 追踪了 GDB。在 178 中的 sysdeps/unix/sysv/linux/getsysstats.c 行,存在一个 thread local storage 访问权限,如下所示:

while (l < re && isspace (*l))

IIUC,isspace() 似乎访问一个 映射 ASCII 字符到符号类型,以快速确定当前字符是否为空格或不。这个表似乎是一个TLS。相关拆解如下:

0x7f8f9ef480de <__GI___get_nprocs+318>  mov    0x2cbd1b(%rip),%rax        # 0x7f8f9f213e00                                                                        
0x7f8f9ef480e5 <__GI___get_nprocs+325>  mov    %fs:(%rax),%rdi                                                                                                    

rax 包含 0xffffffffffffff98,IIUC 表示 每个线程表地址,使用以下公式计算等式:$fs_base + 0xffffffffffffff98。当我使用这个等式来查找每个线程的表地址时,它们都返回相同值,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每个线程独占,但是,在这里所有线程使用相同的 变量位于 0x00007f8f9732b82c。为什么会这样?似乎链接识别变量是read-only节省一些空间?

解决方法

您已经证明每个线程都有一个不同的指针变量,在 TLS 存储中
0x7f8f75dfc698
0x7f8f768fd698

它们都指向同一个表的事实是完全正常的,除非您使用 uselocale(3) 在不同的线程中有不同的语言环境。

我认为 glibc 具有用于不同语言环境的静态常量 (.section .rodata) 字符映射表,并且它根据语言环境设置了一个指向正确表的指针。为每个线程复制整个表将非常低效,浪费更多 L3 缓存占用空间。如果要这样做,您会期望整个表就在那里,没有间接级别。

看看 glibc 如何实现 isupper() 之类的函数 - 通过索引字符属性标志数组,并检查该数组元素中的某个位。 (即每个数组元素都是一个标志位图)。

https://code.woboq.org/userspace/glibc/ctype/ctype.h.html 和相同目录中的相关 .c 文件。