http://jallenoy.spaces.live.com/Blog/cns!B70F21D986C9C057!136.entry
The TLS of Bionic
jallen.o.y@gmail.com20090818
本文基于在Bionic移植过程中对TLS的一些调研,加上个人理解,所以可能会有疏漏的地方。因此,我会尽力给出信息的来源。
什么是TLS
TLS即Thread Local Storage。支持TLS的线程有三种类型的变量:线程局部变量,线程本地变量(TLS)和全局变量。TLS的不同之处:所有线程以同一个变量名或索引 访问TLS变量,但不同线程的TLS变量存储在不同的内存区域。简而言之,就是同一个名字,不同的存储。
例如,有TLS变量errno,每个线程执行相同的语句“errno=i;”,errno在不同线程下有了不同的值。注意,这并不是在每一个线程中都声明一个名为errno的变量。这正是TLS的便利之处TLS的接口
从程序员的角度来看,TLS的使用方法有两种:1)POSIX接口
pthread_key_create() pthread_key_delete()
pthread_getspecific() pthread_setspecific()
2)特定语言的扩展
在gcc中,__thread int i;指定i为TLS变量
TLS的实现
POSIX接口由线程库提供TLS支持
语言扩展则需要编译、链接工具提供TLS支持,例如GNU采用的支持TLS的ELF格式
Bionic中的TLS
Bionic实现了关于TLS的POSIX接口,这些接口函数体系结构无关的部分定义在pthread.c文件中。
Bionic TLS实现机制图
下面,结合上图说明各个接口函数的使用。
Bionic支持最多64个key
Key Map:进程级变量,所有线程可见。该结构包含64个key的bitmap,记录所有key的使用情况
TLS and Stack:观察pthread.c中pthread_create()函数发现,每个线程创建之前会调用calloc()函数在堆中申请一块区域, 将tls指针指向 区域最高地址-64*sizeof(tls单元)。然后将tls作为childstack传给clone系统调用。Clone系统调用childstack用于为新创建的线程指定私有栈。由于栈向低地址增长,如果在用户态能获得tls指针,向高地址寻址可得tls数据。
pthread_key_create()
查Key Map返回一个整数类型的未使用的key的索引
pthread_getspecific() /pthread_setspecific()
参数为 index 和 val,即将索引为index的TLS变量值设为val。
其实现通过 ((unsigned *)(__get_tls())[index]), 其中__get_tls()返回TLS数组的指针。即创建线程时传入的tls指针。
__get_tls()
该函数是体系结构相关的。arm的实现位于bionic/libc/private/bionic_tls.h中,将__get_tls define为 0xffff0ff0
其他体系结构于bioni/libc/arch-xxx/bionic/ 目录下有该函数的相应实现:
x86 从寄存器 gs 获得需要的值
mips 通过 "rdhwr %0,$29"指令实现
目前的解决方案
由于最终的目的是获得tls指针,理论上内核通过系统调用返回该指针即可。所以我们修改了内核,加入了新的系统调用并实验。实验效果是:
1)系统调用成功返回了一个值,但每次返回的值都一样,理论上calloc的地址每次应该不一样
2)测试文件hello_world.cpp修改前打印后必段错误, 修改后平均4次成功运行出现一次段错误.
虽然有成功运行的情况,但结果出现了不确定性.