您的位置:首页 > 运维架构 > Linux

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差不多的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: