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

The*_*mad 0 assembly linker gdb glibc thread-local-storage

glibc-2.27使用GDB. 在178in行sysdeps/unix/sysv/linux/getsysstats.c,存在一个thread local storage访问,如下图:

while (l < re && isspace (*l))
Run Code Online (Sandbox Code Playgroud)

IIUC,isspace()似乎访问了一个字符映射到符号类型的 ,以快速确定当前字符是否为空格。这张桌子似乎是一个. 相关拆解如下: ASCIITLS

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

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
Run Code Online (Sandbox Code Playgroud)

我以为TLS独家每个线程,但是,在这里,所有线程使用相同的变量0x00007f8f9732b82c。为什么会这样?似乎链接器识别了变量 isread-only节省了一些空间?

Pet*_*des 5

您已经展示了每个线程在vs.等的TLS 存储中都有一个不同的指针变量。
0x7f8f75dfc698
0x7f8f768fd698

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

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

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

https://code.woboq.org/userspace/glibc/ctype/ctype.h.html以及同一目录下的相关 .c 文件。

  • @TheAhmad:我认为恰恰相反。土耳其语表有一个全局副本,法语表有一个全局副本,依此类推(进程启动时通过 mmap 加载到内存中,而不是链接到内存中)。每个线程在其线程本地存储中都有一个指向它打算使用的表的指针。如果线程 A 将其语言环境从土耳其语更改为法语,它只需将其线程本地指针更改为指向(全局)法语表。 (2认同)
  • @TheAhmad 不,Peter 是说有一份“C”语言环境数据副本、一份“UTF-8”语言环境数据副本和一份土耳其语言环境数据副本。如果所有线程都使用相同的区域设置,则所有线程区域设置指针将指向相同的区域设置数据。这里没有链接器魔法。C 库刚刚将语言环境指针设置为相同的值。语言环境指针本身位于线程本地存储中,但它们指向的语言环境数据位于普通静态存储中。 (2认同)