Linux & Windows TLS实现
2011-12-08 13:22
507 查看
Linux的TLS(Thread
Local Storage)实现由内核和用户两部分模块配合完成的。 内核对TLS需要做的事情是能够让用户态程序(通常是 nptl——一个pthread的实现)在某个时刻能够设置线程唯一的基址值到内核的线程信息结构内。系统调用set_thread_area(x86)和prctl(X86_64)用来完成这个任务。在x86家族中,这些系统调用能够设置fs(x86_64)或者gs(x86)段选择器对应的全局段描述符(gdt)的基址信息。这样线程想访问那些线程唯一的数据的时候,只需要访问fs或者gs段的内容即可,如fs:[0]。这样的好处是使得访问线程本地的数据时候,没有额外的系统调用的消耗,和访问其他用户态数据一样简单。关于段选择器,段描述器,可以参考Intel
Software Developers' Manual。
用户态程序(通常是nptl)需要能在主线程运行时设置线程唯一的基址到内核。对于glibc的nptl实现,是在动态链接器启动之前就要把主线程的struct pthread对象的基址使用上述的系统调用设置进内核。否则将会影响errno——一个位于TLS的全局变量的使用。从而导致动态链接器的启动。因为没有errno会导致很多动态链接库无法使用。之后在创建新的线程的时候,会调用clone系统调用。nptl会在clone_flags填入CLONE_SETTLS告诉内核需要为新的线程设置新的线程唯一的基址到新的内核线程信息结构内,然后在第六个参数里面填入相应的基址信息。这个基址就是新创建的struct
pthread对象的地址。 这样虽然进程内每个线程所看到的gs或者fs段选择器的值虽然是相同的,但是操作系统在切换任务的时候会把段选择器里面对应的gdt(全局段描述符表)中的entry的基址值设置为线程想要的基址值。
Windows很显然也是用了类似的实现,32位的Windows xp使用fs段为线程唯一的基址值。用windbg在两条不同的线程上下文内键入dg fs将会发现它们对于的gdt entry的基址是不同的。
ARM CPU没有段的概念,所以Linux arm家族使用aeabi来获得线程唯一的基址。我反汇编了__aeabi_read_tp,发现以下代码
mvn r0 , 0xf000
sub pc ,r0,31
// pc在0xffff0fff - 31的代码是不存在的,所以这会触发一个异常。也许这就是arm CPU上触发系统调用的方式。
//所以说Linux arm家族上面每次访问线程唯一的基址会触发系统调用,而不像x86上面只有用户态的几条指令开销!这是一个昂贵的开销,我们的浏览器程序应该避免使用tls。
修正上两段:
在0xffff0f00地址是Linux内核保持所谓__kuser_helper代码的地方,那是个用户程序也能访问和执行的页面。__kuser_get_tls定义在0xffff0fff - 31即是0xffff0fe0,它能够返回用户调用set_tls的tls基地址。它目前有两种实现,其一是从cp15寄存器读取那个基地址;其二是从0xffff0ff0读取那个基地址。这个基地址都是在切换任务的时候设置到cp15或者0xffff0ff0的。(见entry-armv.S)因此arm版的Linux获取tls地址也没有系统调用,性能是和x86差不多的。
Local Storage)实现由内核和用户两部分模块配合完成的。 内核对TLS需要做的事情是能够让用户态程序(通常是 nptl——一个pthread的实现)在某个时刻能够设置线程唯一的基址值到内核的线程信息结构内。系统调用set_thread_area(x86)和prctl(X86_64)用来完成这个任务。在x86家族中,这些系统调用能够设置fs(x86_64)或者gs(x86)段选择器对应的全局段描述符(gdt)的基址信息。这样线程想访问那些线程唯一的数据的时候,只需要访问fs或者gs段的内容即可,如fs:[0]。这样的好处是使得访问线程本地的数据时候,没有额外的系统调用的消耗,和访问其他用户态数据一样简单。关于段选择器,段描述器,可以参考Intel
Software Developers' Manual。
用户态程序(通常是nptl)需要能在主线程运行时设置线程唯一的基址到内核。对于glibc的nptl实现,是在动态链接器启动之前就要把主线程的struct pthread对象的基址使用上述的系统调用设置进内核。否则将会影响errno——一个位于TLS的全局变量的使用。从而导致动态链接器的启动。因为没有errno会导致很多动态链接库无法使用。之后在创建新的线程的时候,会调用clone系统调用。nptl会在clone_flags填入CLONE_SETTLS告诉内核需要为新的线程设置新的线程唯一的基址到新的内核线程信息结构内,然后在第六个参数里面填入相应的基址信息。这个基址就是新创建的struct
pthread对象的地址。 这样虽然进程内每个线程所看到的gs或者fs段选择器的值虽然是相同的,但是操作系统在切换任务的时候会把段选择器里面对应的gdt(全局段描述符表)中的entry的基址值设置为线程想要的基址值。
Windows很显然也是用了类似的实现,32位的Windows xp使用fs段为线程唯一的基址值。用windbg在两条不同的线程上下文内键入dg fs将会发现它们对于的gdt entry的基址是不同的。
ARM CPU没有段的概念,所以Linux arm家族使用aeabi来获得线程唯一的基址。我反汇编了__aeabi_read_tp,发现以下代码
mvn r0 , 0xf000
sub pc ,r0,31
// pc在0xffff0fff - 31的代码是不存在的,所以这会触发一个异常。也许这就是arm CPU上触发系统调用的方式。
//所以说Linux arm家族上面每次访问线程唯一的基址会触发系统调用,而不像x86上面只有用户态的几条指令开销!这是一个昂贵的开销,我们的浏览器程序应该避免使用tls。
修正上两段:
在0xffff0f00地址是Linux内核保持所谓__kuser_helper代码的地方,那是个用户程序也能访问和执行的页面。__kuser_get_tls定义在0xffff0fff - 31即是0xffff0fe0,它能够返回用户调用set_tls的tls基地址。它目前有两种实现,其一是从cp15寄存器读取那个基地址;其二是从0xffff0ff0读取那个基地址。这个基地址都是在切换任务的时候设置到cp15或者0xffff0ff0的。(见entry-armv.S)因此arm版的Linux获取tls地址也没有系统调用,性能是和x86差不多的。
相关文章推荐
- Linux & Windows TLS实现 (http://blog.csdn.net/manjian/article/details/7053207)
- 外排序的实现(平台Linux & 语言C++)
- 【C/C++】【Linux&Windows】Windows系统下的文件夹扫描与Linux系统下的实现比较
- Linux 上实现双向进程间通信管道
- C语言实现ping功能(Linux & Mac OS系统下)有注释
- Linux-系统管理-nat&实现跨网段跨运营商代理
- Linux&C编程之Linux系统命令“cp -r”的简单实现
- poll&&epoll实现分析(二)—epoll实现 http://www.embeddedlinux.org.cn/html/yingjianqudong/201405/11-2859.html
- 调用 动态库 实现插件(windows & linux)
- 【Linux】信号的基础知识&mysleep的实现
- 在windows 与Linux间实现文件传输(C++&C实现)
- <Linux+Qt>使用Qt实现单例模式
- C &&Linux 下简单实现单词统计
- ZYNQ平台AMP模式Linux+裸机(UCOS)实现
- Linux下select&poll&epoll的实现原理(一)
- linux下交互式任务的自动化实现(expect&Pexpect)
- SercureCRT&CuteFTP实现windows与虚拟机linux互联
- Linux 环境下C/C++获取系统时间 && 每隔500ms循环一次代码实现
- Linux-Netfilter&iptables实现机制的分析及应用
- Round-Robin负载均衡算法及其实现原理& Linux下双网卡绑定bond0