无我

让内心永远燃烧着伟大的光明的精神之火!
灵活的思考,严谨的实现
豪迈的气魄、顽强的意志和周全的思考

线程本地存储TLS(Thread Local Storage)的原理和实现——实现探究

本文为线程本地存储TLS系列之实现探究。
我们在上一篇线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理中曾经说过TLS可以分为两类:静态TLS和动态TLS。然后又分别说明了两者在程序实现时的用法,并且还说明了windows对这两类TLS的实现原理,我们本文的目的是从底层实现的角度深入探究,深刻理解原理。
先考虑以下两个问题:
1、在上一篇中,我们说到静态TLS数据是在编译时放入.tls节,然后在系统加载程序时,会去寻找.tls节,并且分配一个足够大的内存空间来存放所有这些静态TLS变量。那么问题是,当程序加载后,对静态TLS数据分配的内存空间在哪里呢?用什么来表示呢?
2、在上一篇中,我们说到动态TLS是存放在每一个线程独立的TLS slot数组中,这个数组的大小是TLS_MINIMUM_AVAILABLE维,那么这个数组在哪里呢?TlsSetValue和TlsGetValue应该就是访问的这个数组,在取得索引的情况下,如果我们知道这个数组的位置,那么我们是否完全就能抛开上面两个函数自己读写来测试呢?

一、线程环境块TEB
在给出具体程序之前,我们有必要先讨论一下线程环境块TEB。
我们知道,每个线程都有属于自己的一系列数据,这些数据就是通过TEB来管理,当然就包括像TLS这样的线程私有数据。那么TEB的结构是什么样的呢?在微软的文档和头文件上,我没有查到TEB完整的信息,不过我们还是可以通过Windbg得到的,以下是TEB的详细展开信息:

nt!_TEB

这个结构体包含的信息很多,我们此处只需要关注与TLS相关的,对其他的都忽略。所以我在程序里定义了以下这个结构体,用一些保留字段来控制相关域的偏移:

//通过windgb查看_TEB得到的我的系统(winXP+SP3)中的_TEB的实现
struct STEB
{
    NT_TIB NtTib;
    PVOID EnvironmentPointer;
    
//中间若干数据,与此处研究无关,故不展开,只标记偏移。下面的Reserved2,Reserved3也是同理
    BYTE Reserved1[12]; 
    PVOID ThreadLocalStoragePointer;    
//指向存放静态TLS数据的地址的指针的地址
    BYTE Reserved2[3552];
    PVOID TlsSlots[
64];                    //指向存放动态TLS数据的TLS Slot数组
    BYTE Reserved3[132];
    PVOID TlsExpansionSlots;             
//当索引大于63时,TlsSlots数组存不下了,就会新分配内存来存放,并且将指针记录在这里
    
//后面还有若干数据,与此处研究无关,故省略
}
;

以上就是我在之后两个程序中要用到的直接访问相关内存的线程TEB定义了。那么这个结构又是存放在哪里的呢?在windows系统上,该结构体的起始地址总是FS:[0]。而为了更方便的用指针访问,我们用到了NT_TIB结构中的Self字段,Self指针就是指向自身的其实地址,也就是NT_TIB的首地址,也就是我们的STEB的首地址。在winnt.h中,NT_TIB定义如下:

NT_TIB

如同_TEB一样,因为其他字段我们不关心,所以都不讨论了,此处只需要知道Self指针就是指向自身,有了他,可以方便的进行指针访问操作了。

二、静态TLS实现探究
我们通过下面的程序来研究静态TLS的实现。先说明程序的基本框架:在文件最开始,声明了3个静态TLS变量,并定义了要启动的线程数。然后main函数启动若干个线程,在线程函数中分别对3个TLS变量赋值,然后调用一个分析函数TlsMemFunc:这个函数用临界区来防止线程的输出相互干扰,首先用正常的方式打印出TLS变量的值,然后直接访问内存存放静态TLS变量的地方,自己获取相关的值打印出来。从这个过程中,我们可以深入探究windows对静态TLS内存管理的实现。程序如下,对关键部分都做了注释,就不再额外说明了,不过要格外留心的是TlsMemFunc中指针操作的代码,这是非常有趣的:

静态TLS研究程序

要说明的是:由于debug版本,编译器会自动分配一段内存来存放调试信息,从上面的代码中,可以看到我已经对我的编译环境VS2010进行了代码调整(_DEBUG宏部分),但是如果你用的是别的编译器,那我不确定他分配的是和VS2010一样的,所以可能结果不正确,建议用Release版本来分析。
程序运行结果如下:

可以看出,通过直接访问静态TLS变量和访问TEB中相关内存得到的是一样的,由此我们就更加深入的理解了上一篇中讲的静态TLS的原理,也知道操作系统是如何管理和实现静态TLS的。

三、动态TLS实现探究
我们通过下面的程序来研究动态TLS的实现。本程序的基本框架和静态TLS程序结构大致相同,但是不需要声明静态TLS变量了,而是用TlsXXX系列函数来创建动态TLS数据。然后关键是分析函数TlsMemFunc:这个函数用临界区来防止线程的输出相互干扰,首先用TlsGetValue的方式打印出TLS变量的值,然后直接访问内存读取动态TLS变量并打印出来。要特别注意的是:TEB中存放动态TLS的数组只有64维,但是windowsNT支持1000多个TLS数据,这是利用扩展Slot指针来实现的,为了模拟这种情况,我特意在main中调用TlsAlloc直至索引超过64,而需要访问扩展空间的情况。这些代码很有趣,读者可以自己尝试修改观察。从这个过程中,我们可以深入探究windows对动态TLS内存管理的实现。程序的关键部分都做了注释,就不再额外展开了,不过与上面的程序一样,请一定要细心研究TlsMemFunc对指针操作的代码:

动态TLS研究

程序运行结果如下:

同样可以看出,通过TlsGetValue访问动态TLS变量和访问TEB中相关内存得到的是一样的,由此我们就更加深入的理解了上一篇中讲的动态TLS的原理,已经底层的Tls slots数组的实现情况。抽丝剥茧,一目了然!

posted on 2012-07-04 08:57 Tim 阅读(7305) 评论(0)  编辑 收藏 引用 所属分类: 逆向工程windows系统


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2012年7月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

导航

统计

公告

本博客原创文章,欢迎转载和交流。不过请注明以下信息:
作者:TimWu
邮箱:timfly@yeah.net
来源:www.cppblog.com/Tim
感谢您对我的支持!

留言簿(9)

随笔分类(173)

IT

Life

搜索

积分与排名

最新随笔

最新评论

阅读排行榜