线程局部存储TLS, Thread Local Storage
TLS是C/C++运行库的一部分,而非操作系统的一部分。
分为动态TSL 和 静态TLS
一、动态TLS
应用程序通过调用一组4个函数来使用动态TLS, 这些函数实际上最为DLL所使用。
系统中的每个进程都有一组 正在使用标志(in-use flag), 每个标志可被设置为FREE 或者 INUSE, 表示该TLS元素是否正在使用。
微软平台保证至少有TLS_MINUMUM_AVALIABLE个标志位可供使用, TLS_MINUMUM_AVALIABLE在WinNT.h中被定义为64. 系统会在需要的时候分配更多的TLS元素,最多可达1000多个。
下面介绍下函数
DWORD TlsAlloc();
这个函数让系统对进程中中的标志位进行检索并找到一个FREE标志,然后将该元素的标志从FREE置为INUSE, 并让TlsAlloc返回该元素的索引(下标)。
一个DLL(或应用程序)通常会将此索引保存在一个全局变量中,在整个进程范围内访问。
如果找不到一个标志为FREE的元素,则函数返回TLS_OUT_OF_INDEXES(在WinBase.h中被定义为0XFFFFFFFF)。
每个线程都有一个PVOID数组,每个线程使用每次TlsAlloc成功时的返回值作为各自线程中该数组的索引。
注意:该函数返回时会将每个线程中的数组对应的索引位置元素清零!
返回值:
失败:TLS_OUT_OF_INDEXES
成功:其他
BOOL TlsSetValue(
DWORD dwTlsIndex, // TlsAlloc()函数的返回值
PVOID pvTlsValue //要设置的值
);
返回值:
TRUE: 成功
FALSE:失败
该函数的主调线程只能更改自己的数组,而不能修改其他线程的数组。
PVOID TlsGetValue(
DWORD dwTlsIndex //TlsAlloc()函数的返回值
);
BOOL TlsFree(
DWORD dwTlsIndex //TlsAlloc()函数的返回值
);
将该标志置为FREE, 并且将元素值清零。
二、静态TLS
__declspec(thread) int g_value = 0;
__declspec(thread)前缀是微软为VC++编译器增加的一个修饰符,它告诉编译器应该在可执行文件或DLL中,把对应的变量放到他自己的段中。
__declspec(thread)后面的变量必须声明为 全局变量 或 静态变量(全局或局部都可), 不可以将局部变量声明为__desclspec(thread)类型。
当编译器对程序进行编译的时候,会将所有TLS变量放到他自己的段中,这个段为.tls段。 链接器会将所有对象模块中的.tls段合并成一个大的.tls段,并将他保存到生成的exe或DLL文件中。
当系统将应用程序载入到内存的时候,会查看可执行文件中的.tls段,并分配一块足够的的内存来保存所有的静态TLS变量。 每当应用程序中的代码引用到这些变量之一事,相应的引用会被解析到刚分配的这块内存中的一个位置。 因此编译器必须生成额外的代码来引用静态TLS。
如果进程创建了一个新的线程,那么系统会得知这一情况并自动分配另一块内存来保存新线程的静态TLS变量。
当隐式加载DLL的时候:
如果exe使用了静态TLS, DLL也使用了TSL, 系统会首先确定exe的.tls段的大小,并将exe所有链接到的DLL的.tls段的大小相加。当系统在创建新线程的时候,会自动分配一块足够大的内存来保证应用程序和所有隐式链接的DLL需要的TLS变量。
当显式加载DLL的时候:
某DLL包含了静态TLS变量, exe使用LoadLibrary来显式加载DLL。 系统查看进程中已有线程,然后根据DLL中的TLS段扩大每个线程的TLS内存块。
当调用FreeLibrary释放DLL时,则该进程中的所有线程的TLS内存块也应该相应的缩减,缩减的部分是DLL中的TLS的部分。