在自定义libc中实现线程本地存储

我正在为一些非常小的和静态链接的程序实现一个小的libc子集,我认为添加TLS支持将是一个很好的学习体验。 我使用Ulrich Drepper的TLS文件作为参考。

我有两个string设置来试试这个:

 static __thread const char msg1[] = "TLS (1).\n"; /* 10 bytes */ static __thread const char msg2[] = "TLS (2).\n"; /* 10 bytes */ 

编译器生成以下指令来访问它们:

 mov rbx, QWORD PTR fs:0x0 ; Load TLS. lea rsi, [rbx-0x14] ; Get a pointer to 'msg1'. 20 byte offset. lea rsi, [rbx-0xa] ; Get a pointer to 'msg2'. 10 byte offset. 

假设我把TCB放在堆栈的某个地方:

 struct tcb { void* self; /* Points to self. I read that this was necessary somewhere. */ int errno; /* Per-thread errno variable. */ int padding; }; 

然后在tls = &tcb - tls_size处将TLS区域放在它tls = &tcb - tls_size 。 然后我将FS寄存器设置为指向fs = tls + tls_size ,并将TLS初始化映像复制到tls

但是,这是行不通的。 我已经validation了我通过将tls_image的20个字节写入stdout来正确定位TLS初始化映像。 这要么导致我相信我错误地放置了TCB和/或TLS区域,或者我不符合ABI。

  • 我使用arch_prctl(2)设置FS寄存器。 我需要使用set_thread_area(2)吗?
  • 我没有dtv 。 我假设这是没有必要的,因为我静态链接。

任何想法,我做错了什么? 非常感谢!

我正在实现一个很小的静态链接程序的libc的一小部分,我认为添加TLS支持将是一个很好的学习经验。

真棒的想法! 我不得不在一个项目中实现我自己的TLS,因为我不能使用像pthread这样的任何通用线程库。 我没有完全解决您的问题,但分享我的经验可能是有用的。

也看看这个链接 ,这可能是有用的。

我使用arch_prctl(2)设置FS寄存器。 我需要使用set_thread_area(2)吗?

答案取决于架构,你实际上正在使用。 如果您使用的是x86-64位,则应该只使用arch_prctl将FS寄存器设置为要用作TLS的内存区域(它允许您寻址大于4GB的内存区域)。 而x86-32则必须使用set_thread_area,因为它是内核支持的唯一系统调用。

我的实现背后的想法是为每个线程分配一个专用内存区域,并将其地址保存到%GS寄存器中。 这是一个相当简单的方法,但在我的情况下,它工作得很好。 每次你想访问一个线程的私有区域,你只需要使用%GS保存的值和一个标识内存位置的偏移量作为基地址。 我通常为每个线程分配一个内存页(4096),并将它分成8个字节块。 所以,我为每个线程有512个私有内存插槽,可以像索引从0到511的数组那样访问。

这是我使用的代码:

 define _GNU_SOURCE 1 #include "tls.h" #include <asm/ldt.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/prctl.h> #include <asm/prctl.h> #include <sys/syscall.h> #include <unistd.h> void * install_tls() { void *addr = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (syscall(SYS_arch_prctl,ARCH_SET_GS, addr) < 0) return NULL; return addr; } void freeTLS() { void *addr; syscall(SYS_arch_prctl,ARCH_GET_GS, &addr); munmap(addr, 4096); } bool set_tls_value(int idx, unsigned long val) { if (idx < 0 || idx >= 4096/8) { return false; } asm volatile( "movq %0, %%gs:(%1)\n" : : "q"((void *)val), "q"(8ll * idx)); return true; } unsigned long get_tls_value(int idx) { long long rc; if (idx < 0 || idx >= 4096/8) { return 0; } asm volatile( "movq %%gs:(%1), %0\n" : "=q"(rc) : "q"(8ll * idx)); return rc; } 

这是一些宏的头文件:

 #ifndef TLS_H #define TLS_H #include <stdbool.h> void *install_tls(); void freeTLS(); bool set_tls_value (int, unsigned long); unsigned long get_tls_value(int ); /* *macros used to set and retrieve the values from the tls area */ #define TLS_TID 0x0 #define TLS_FD 0x8 #define TLS_MONITORED 0x10 #define set_local_tid(_x) \ set_tls_value(TLS_TID, (unsigned long)_x) #define set_local_fd(_x) \ set_tls_value(TLS_FD, (unsigned long)_x) #define set_local_monitored(_x) \ set_tls_value(TLS_MONITORED, (unsigned long)_x) #define get_local_tid() \ get_tls_value(TLS_TID) #define get_local_fd() \ get_tls_value(TLS_FD) #define get_local_monitored() \ get_tls_value(TLS_MONITORED) #endif /* end of include guard: TLS_H */ 

每个线程要完成的第一个动作是安装TLS存储区。 一旦TLS被初始化,每个线程就可以开始使用这个区域作为私有的TLS。