精华内容
下载资源
问答
  • TLS线程局部存储

    2018-10-28 13:32:55
    如图: 后来知道在程序中主动使用TlsAlloc( )、TlsSetValue( )、TlsGetValue( )、TlsFree( )函数,这种叫动态线程局部存储,生成的pe文件中不会有tls这些东西,使用静态线程局部存储pe文件中就会有tls这中东西。...

    直接贴代码:

    #include <stdio.h>
    #include <windows.h>
    #include <process.h>
    
    DWORD g_tlsUsedTime; 
    
    UINT __stdcall ThreadFunc(LPVOID)
    {
    	TlsSetValue(g_tlsUsedTime, (LPVOID)GetTickCount());
    	/* 给WaitForMultipleObjects()函数足够时间 */
    	Sleep(2000); 
    	/* printf()函数不可重入 */
    	printf("Thread ID: %d, Start Time: %d \n", GetCurrentThreadId(), (DWORD)TlsGetValue(g_tlsUsedTime));
    	return 0;
    }
    
    int main(int argc, char* argv[])
    {
    	g_tlsUsedTime = TlsAlloc();		// 全局变量定义成线程局部存储
    
    	HANDLE h[10];
    	for (int i = 0; i < 10; ++i)	// 十个线程间隔0.1秒开启
    	{
    		h[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL);	
    		Sleep(100);
    	}
    	WaitForMultipleObjects(10, h, TRUE, INFINITE);
    	for (int j = 0; j < 10;++j)
    	{
    		CloseHandle(h[j]);
    	}
    
    	TlsFree(g_tlsUsedTime);
    	system("pause");
    	return 0;
    }

    把生成的可执行文件拖到PEview中观察,TLS Table为空,节区中也没有.tls节区,如图:

    后来知道在程序中主动使用TlsAlloc( )、TlsSetValue( )、TlsGetValue( )、TlsFree( )函数,这种叫动态线程局部存储,生成的pe文件中不会有tls这些东西,使用静态线程局部存储pe文件中就会有tls这中东西。

    拿《逆向工程核心原理》第45章的Tlstest.exe文件做例子,源代码:

    #include <windows.h>
    #pragma comment(linker, "/INCLUDE:__tls_used")
    
    void print_console(char* szMsg)
    {
        HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
        WriteConsoleA(hStdout, szMsg, strlen(szMsg), NULL, NULL);
    }
    
    void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
    {
        char szMsg[80] = {0,};
        wsprintfA(szMsg, "TLS_CALLBACK1() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
        print_console(szMsg);
    }
    
    void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
    {
        char szMsg[80] = {0,};
        wsprintfA(szMsg, "TLS_CALLBACK2() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
        print_console(szMsg);
    }
    
    #pragma data_seg(".CRT$XLX")
        PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
    #pragma data_seg()
    
    DWORD WINAPI ThreadProc(LPVOID lParam)
    {
        print_console("ThreadProc() start\n");
        print_console("ThreadProc() end\n");
        return 0;
    }
    
    int main(void)
    {
        HANDLE hThread = NULL;
        print_console("main() start\n");
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        WaitForSingleObject(hThread, 60*1000);
        CloseHandle(hThread);
        print_console("main() end\n");
        return 0;
    }

    代码作用:向操作系统注册2个tls回调函数,在main函数中创建线程,创建的线程结束后主线程退出。

    先看pe文件中的TLS Table

    指向的是IMAGE_TLS_DIRECTORY32结构体,大小0x18:

    struct _IMAGE_TLS_DIRECTORY32 
    {
        DWORD   StartAddressOfRawData;   // tls数据起始地址
        DWORD   EndAddressOfRawData;    // tls数据结束地址
        DWORD   AddressOfIndex;            // PDWORD 索引位置
        DWORD   AddressOfCallBacks;        // 向操作系统注册的回调函数数组指针
        DWORD   SizeOfZeroFill;       // 据说一直是0
        DWORD   Characteristics;           // 据说一直是0  
    } IMAGE_TLS_DIRECTORY32;

    在ida中观察:

    IMAGE_TLS_DIRECTORY32结构体内容:

    tls数据存放地址(都是0,就是没使用数据):

    tls回调函数指针数组(有2个函数,数组以空结束):

    再看tls节区头(如果使用了数据部分,tls节区在镜像中的大小应该很大了):

    镜像中tls节区只有2个字节大小(没用数据部分),文件中tls节区大小0x200(是为了文件对齐)

     

    TlsTest.exe输出截图:

    至于为什么没有主线程分离,尚不清楚,估计是主进程分离的时候已经,操作系统已经实现了主线程分离。

    注册的tls回调函数是顺序执行,不会出现函数1调用了,函数2没有调用的情况。

     

     

    展开全文
  • 本文实例讲述了C++采用TLS线程局部存储的用法。分享给大家供大家参考。 具体方法如下: 代码如下:// useTLS.cpp : 定义控制台应用程序的入口点。  //    #include “stdafx.h”  #include   #include     //...
  • TLS 线程局部存储

    2019-10-14 17:32:42
            在学习逆向的过程中,大家可能了解到利用TLS,可以实现反调试,因为TLS回调函数会在入口函数之前执行,但是可能不知道具体的原理,以及...线程局部存储(Thread Local Storage),又简称T...

            在学习逆向的过程中,大家可能了解到利用TLS,可以实现反调试,因为TLS回调函数会在入口函数之前执行,但是可能不知道具体的原理,以及TLS是个什么东西,通过下面的介绍,你将会了解TLS的原理以及用法。

    什么是线程局部存储

           线程局部存储(Thread Local Storage),又简称TLS, 设计的目的是解决多线程之间变量的同步问题,程序员通过使用TLS机制,可以实现让程序拥有全局变量,但在不同的线程里却对应有不同的值,也就是说,进程中的所有线程都可以拥有全局变量,但这些变量其实是特定对某个线程才有意义的。
           线程局部存储共分动态和静态两种,分类的主要依据是: 线程局部存储的数据所用空间,操作系统完成的是动态申请还是静态分配

    动态线程局部存储

    动态TLS存在以下四个API函数
    TlsAlloc
    TlsGetValue
    TlsSetValue
    TlsFree
    应用程序或DLL在合适的时候会调用这四个函数,通过索引对进程中每个线程的存储区进行统一操作。他们位于动态链接库文件kernel32.dll
    下面通过一个例子(不用动态TLS和使用动态TLS)来说明一下TLS的作用以及这4个API的用法

    例子(不用TLS):

    #include <stdio.h>
    #include <windows.h>
    
    int num;
    //线程函数模板
    DWORD WINAPI ThreadFunc(LPVOID lParam)
    {
    	num = 5;
    	for (int i = 0; i < 5; i++)
    	{
    		printf("当前线程ID: %d, num = %d\n", GetCurrentThreadId(), num);
    		num--;
    	}
    	return 0;
    }
    
    int main()
    {
    	HANDLE hThread[4] = {};
    
    	for (int i = 0; i < 4; i++)
    	{
    		hThread[i] = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
    	}
    
    	WaitForMultipleObjects(4, hThread, TRUE, INFINITE);
    	printf("All threads has been destroyed!\n");
    	for (int i = 0; i < 4; i++)
    	{
    		CloseHandle(hThread[i]);
    	}
    	getchar();
    
    
    	return 0;
    }
    

    期望的输出是每个线程num的输出都是 5 4 3 2 1,但是结果却是
    在这里插入图片描述
    使用了动态TLS例子

    #include <stdio.h>
    #include <windows.h>
    
    int num;
    //线程函数模板
    DWORD WINAPI ThreadFunc(LPVOID lParam)
    {
    	TlsSetValue(num, (LPVOID)5);			//设置某个线程该索引处对应的值
    	for (int i = 0; i < 5; i++)
    	{
    		printf("当前线程ID: %d, num = %d\n", GetCurrentThreadId(), TlsGetValue(num));			
    		TlsSetValue(num, (LPVOID)((DWORD)TlsGetValue(num) - 1));
    	}
    	return 0;
    } 
    
    int main()
    {
    	HANDLE hThread[4] = {};
    
    	num = TlsAlloc();			//分配一个索引
    	if (num == TLS_OUT_OF_INDEXES)
    	{
    		printf("分配失败!\n");
    		return 0;
    	}
    
    	for (int i = 0; i < 4; i++)
    	{
    		hThread[i] = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
    	}
    
    	WaitForMultipleObjects(4, hThread, TRUE, INFINITE);
    	printf("All threads has been destroyed!\n");
    
    	TlsFree(num);			//释放线程Tls分配的空间
    	for (int i = 0; i < 4; i++)
    	{
    		CloseHandle(hThread[i]);
    	}
    	getchar();
    
    
    	return 0;
    }
    

    输出结果为
    在这里插入图片描述
    可以看出TLS为线程绑定了数据,到了这里,大家也应该明白TLS 为啥叫线程局部存储了吧,,,就是把数据绑定到线程中,线程在哪,数据就在哪

    线程局部存储的具体实现原理
        线程局部存储机制的示意图
    在这里插入图片描述
    进程环境块的0X44偏移处的数据为 ULONG TlsBitmapBits[2] 共64位,每个位可以是0或者1,分别代表未使用和已使用
    线程环境块TEB的0XE10偏移处为 PVOID TlsSlot[0X40]; 是一个PVOID类型的数组,长度为64,数组的索引号对应着进程标志位的位索引

    什么意思呢?我画张图,再结合上面的代码,大家就明白了
    在这里插入图片描述
    我们通过TlsAlloc是分配一个索引,上面的代码中,num就是2,然后在PEB的TlsBitmapBits的索引是2的位就会被设置成1
    然后我们通过TlsSetValue设置就是根据索引,将对应线程的TlsSlot数组中对应的索引设置成对应的数据,如上代码中就设置成5了,然后用TlsGetValue是得到成对应的值
    TlsFree是释放这个索引对应的线程的空间,将PEB中TlsBitmapBits的对应位设置为0

    静态线程局部存储

           静态线程局部存储是操作系统提供的另外一种线程与数据绑定的技术。它与动态TLS的区别在于,通过静态线程局部存储指定的数据无需使用专门的API函数,随意在易用性上会更好一些。

           静态线程局部存储预先将变量定义在PE文件内部,一般使用.tls节存储(也不一定),对于相关API的调用由操作系统来完成。这种方式的有点就是从高级语言程序员角度来看更简单了。这种实现方式使得TLS数据的定义与初始化就像程序中使用普通的静态变量那样。

           对静态TLS变量的定义不需要想动态线程局部存储一样,调用相关API,只需要做如下声明即可:

    _declspec(thread) int tlsFlag=1;
    为了支持这种编程模式。PE中的.tls节会包含以下信息:

    1、初始化数据
    2、用于每个线程初始化和终止的回调函数
    3、TLS索引

    可执行代码访问静态TLS数据一般需要经过一下几个步骤:

    1.在链接的时候,连接器设置TLS目录中的AddressOfIndex字段。这个字段指向一个位置,在这个位置保存程序用到的TLS索引。

    2.当创建线程是,加载器通过将线程环境块TEB的地址放入FS寄存器来传递线程的TLS数组地址。距TEB开头0x2c的位置处的字段ThreadLocalStoragePointer指向TLS数组。

    3.加载器将TLS索引值保存到AddressOfIndex字段指向的位置处。

    4.可执行代码获取TLS索引以及TLS数组的位置。

    5.可执行代码将索引乘以4,并将该值作为这个数组内的偏移来使用。通过以上方法获取给定程序和模块的TLS数据区的地址。每个线程拥有他自己的TLS数据区,但这对于线程是透明的,它并不需要知道怎为单个线程分配数据的。

    6.单个的TLS数据对象都位于TLS数据区的某个固定偏移处,因此可以用这种方式访问。

    静态的TLS主要的应用是TLS回调函数。它可以在入口函数之前执行,所以可以做很多事情,比如反调试,加解密啥的。

    下面 写代码 实验一下

    #include <stdio.h>
    #include <windows.h>
    
    __declspec(thread) int value = 0X12345678;
    
    #define THREAD_NUM 3
    
    //线程函数模板
    DWORD WINAPI ThreadFunc(LPVOID lParam)
    {
    	printf("线程ID ->%d, value = %#X\n", GetCurrentThreadId(), value);
    	return 0;
    }
    
    int main()
    {
    	HANDLE hThreads[THREAD_NUM] = {};
    	value = 5;
    
    	for (int i = 0; i < THREAD_NUM; i++)
    	{
    		hThreads[i] = CreateThread(NULL, 0, ThreadFunc, 0, 0, 0);
    	}
    
    	WaitForMultipleObjects(THREAD_NUM, hThreads, TRUE, INFINITE);
    	getchar();
    	return 0;
    }
    

    这里的运行结果是:
    在这里插入图片描述
    正是因为静态线程局部存储的原因,使得每个线程都有一份value的拷贝,所以在这里即使主线程将value赋值为5,在其他线程当中value的值仍然是0X12345678
    我们将生成的exe文件拖入到CFF Explorer中,查看其TLS 目录
    在这里插入图片描述

    StartAddressOfRawData  	4025A4
    EndAddressOfRawData	 	4025AC
    AddressOfindex			403380	
    AddressOfCallBacks		4020EC
    SizeOfZeroFill			0
    Characteristics			00300000
    

    在每个线程中都对应的着value的拷贝,那么他的拷贝原数据是在哪的,
    这里StartAddressOfRawDataEndAddressOfRawData 之间就是拷贝的源数据,针对这个程序来说,我们找到这个StartAddressOfRawData在文件中对应的偏移
    在这里插入图片描述
    我们发现果然是这样,在代码中写的value的值0X12345678就在这里存着

    那么线程是怎么找到其对应的value 的,在动态线程局部存储中,是在TEB 偏移E10处(TLS槽)存着,但静态线程局部存储不是这样的

    我们用OD调试,找到线程函数那里
    在这里插入图片描述
    在TEB的2C处存的一个指针, 这个指针指向一个TLS槽(注意这里不是TEB中的那个TLS槽,而是自己分配的空间)

    对于这个程序来说,索引是0,结构是这样的
    在这里插入图片描述
    我们在看看回调函数,由于程序中没有定义回调函数,所以AddressOfCallBacks指向的数据是0
    在这里插入图片描述
    下面我们重写代码添加回调函数

    #include <stdio.h>
    #include <windows.h>
    
    #pragma comment (linker, "/INCLUDE:__tls_used")
    #pragma comment (linker, "/INCLUDE:__tls_callback")
    
    __declspec(thread) int value = 0X12345678;
    #define THREAD_NUM 3
    
    void NTAPI tls_callback(PVOID h, DWORD reason, PVOID pv)
    {
    	if (reason == DLL_PROCESS_ATTACH)
    	{
    		MessageBox(NULL, "线程局部存储回调函数", "测试", MB_OK);
    	}
    
    	return;
    }
    
    //线程函数模板
    DWORD WINAPI ThreadFunc(LPVOID lParam)
    {
    	printf("线程ID ->%d, value = %#X\n", GetCurrentThreadId(), value);
    	return 0;
    }
    
    int main()
    {
    	HANDLE hThreads[THREAD_NUM] = {};
    	value = 5;
    
    	for (int i = 0; i < THREAD_NUM; i++)
    	{
    		hThreads[i] = CreateThread(NULL, 0, ThreadFunc, 0, 0, 0);
    	}
    
    	WaitForMultipleObjects(THREAD_NUM, hThreads, TRUE, INFINITE);
    	getchar();
    	return 0;
    }
    
    EXTERN_C
    #pragma data_seg (".CRT$XLB")
    PIMAGE_TLS_CALLBACK _tls_callback = tls_callback;
    #pragma data_seg ()
    
    

    至于为啥子这样添加反调试,请参考https://www.jianshu.com/p/841d360777de
    我们运行一下程序,会先弹出窗口,在打印
    在这里插入图片描述
    我们来到AddressOfCallBacks指向的地方
    在这里插入图片描述
    将程序拖入到OD中
    在这里插入图片描述会发现程序先执行TLS回调函数,然后再执行main函数
    (补充,,利用这一点可以实现很多事情,比如:加解密,反调试啥的)

    参考资料:
    《Windows PE权威指南》
    https://blog.csdn.net/lixiangminghate/article/details/46770635
    https://www.jianshu.com/p/841d360777de

    展开全文
  • Tls 线程局部存储

    2016-04-06 09:35:04
     如果需要在一个线程内部的各个函数调用都能访问、但其他线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。  TLS声明的空间,其他线程无法访问,...

    什么是TLS?

            如果需要在一个线程内部的各个函数调用都能访问、但其他线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。

          TLS声明的空间,其他线程无法访问,增加了可移植性。

            TlsAlloc(): 返回TLS索引

            TlsSetValue(DWORD dwTlsIndex,LPVOID lpTlsValue):将一块空间与索引进行绑定。

            TlsGetValue(DWORD dwTlsIndex): 获取与索引绑定的空间

            TlsFree(DWORD dwTlsIndex):释放索引,使索引由占用转变为未占用,供下次索引申请使用。

           这几个函数中,比较有有意思的是,TlsSetValue(DWORD dwTlsIndex ,LPVOID lpTlsValue),这个函数的具体意思是将索引与某个地址进行绑定,当再一次进行绑定的时候,上一个绑定地址不会释放,仍然会占用空间。在第一次对索引与地址进行绑定时,需要检查就地址是否需要释放(TlsFree(DWORD dwTlsIndex)只是将索引进行释放,不释放绑定地址空间)。

            注意两点:

    使用TlsSetValue(DWORD dwTlsIndex,LPVOID lpTlsValue)时,思考是否以前绑定地址,重新绑定地址时,是否需要释放旧地址。

                    使用TlsFree(DWORD dwTlsIndex)时,思考是否需要释放绑定地址。

    理论验证代码:

                      

    int main()
    {
    DWORD dwTlsIndex;
    LPVOID lpvSomeValue;
    dwTlsIndex = TlsAlloc();
    LPTSTR pszBuf = NULL;
    pszBuf = (LPTSTR)LovalAlloc(LPTR,sizeof(DWOR));
    DWOR  s = 0x12345678;
    char k[100] = "hello my world ! ";
    TlsSetValue(dwTlsIndex,(LPVOID)k);
    //TlsSetValue(dwTlsIndex,pszBuf);
    //TlsSetValue(dwTlsIndex,(lpvoid)&s);
    lpvSomeValue = TlsGetValue(dwTlsIndex);
    TlsFree(dwTlsIndex);
    cout<<k<<endl;
    return 0;
    }

           

    展开全文
  • 线程局部存储TLS

    2017-12-28 22:46:44
    1. 运行库支持多线程操作...所以线程需要配备只有本线程专属的线程专属存储空间(Thread Local Storage, TLS),即系统为线程单独开辟的存储空间。对于C/C++标准库来说,线程相关的部分是不属于标准库的内容的,它跟网

    1. 运行库支持多线程操作的更改

    多线程编程时,其实线程的访问权限是很高的,可以访问进程内存里的所有数据,甚至包括其他线程的堆栈,但是这种毫无边界的感觉就和不知边界的亲戚一样,显然是需要制衡的。所以线程需要配备只有本线程专属的 线程专属存储空间(Thread Local Storage, TLS),即系统为线程单独开辟的存储空间。

    对于C/C++标准库来说,线程相关的部分是不属于标准库的内容的,它跟网络、图形图像等一样,属于标准库之外的系统相关库。

    由于多线程在现代的程序设计中占据非常重要的地位,主流的C运行库在设计时都会考虑到多线程相关的内容,这里说到的“多线程相关”主要两方面:
    1.提供那些多线程操作的接口,比如创建线程、退出线程、设置线程优先级等函数接口;
    2.C运行库本身要能够在多线程的环境下正确运行。

    关于第一方面glibc提供了pthread库,而MSVC提供了_beginthread()_endthread()等系列API。但是难在第二方面,因为最初运行库编写时多线程的概念还没出现,现在随着多核CPU的普及和对计算速度的要求越来越高,故而C语言运行库的很多核心函数得重新编写以实现线程安全:

    (1) errno: C运行提供errno全局变量,但是线程A处处的errno值可能在被读取之前就已经被线程B的error信息给覆盖掉;
    (2)printf等IO函数:流输出函数是线程不安全的,早期的CRT运行库并会出现多线程交错输出的情况;
    (3)malloc/free等堆处理函数:因为堆是由运行库管理的,如果运行库不支持多线程,则很可能出现堆空间分配错乱的问题。

    也正是因为这个问题,所以CRT后期补充了多线程专用的运行库比如msvcprtd.lib等,在cl期间通过添加/MT、/MTd或/MDd等便可以启用相应的多线程运行库。

    解决运行库在多线程情形下的不适用性,核心是将存在线程不安全的函数实现为线程安全的,显然有如下几种方式:
    1. 锁
    在多线程版本的运行库中,线程不安全的函数都被加上了锁,从而实现互斥操作,比如malloc/new这类操作共同堆空间的函数。使用多线程版本的运行库,可以直接调用malloc等函数,而不必要程序员再额外加锁。

    2. 改进函数调用方式

    char* strtok(char* strToken, const char* strDelimit);
    char* strtok_s(char* strToken, const char* strDelimit, char **context);

    直接在运行库中实现一个线程安全版的替代函数,如将strtok()换成线程安全版的strtok_s()函数,但是这种方式不是很推荐,因为标准库应该在保证API接口标准的前提下,尽可能地提供较少的API数量,否则会让调用者使用难度。

    3. 使用TLS
    前面说到glibc下errno是全局变量,多线程环境下可能导致errno被误覆盖。所以可以使用TLS机制,在每个线程内部实现errno,如在多线程版本的glibc中,errno被定义为
    #define errno (*__errno_location () )
    函数__errno_location()在多线程版本下,将会返回不同的线程专属的errno存储地址。

    1. 隐式TLS

    在编写多线程程序时很自然的想要为每个线程保存一些私有的数据,而我们知道属于线程私有的数据包括线程的栈和当前的寄存器,前者一直处于动态伸缩的过程中不可靠;寄存器容量小且珍贵,不能用来干这种粗活。所以这时候就需要TLS机制了,TLS的用法很简单,如果定义一个全局变量为TLS类型的:
    1.GCC: __thread int number;
    2.MSVC: __declspec(thread) int number;

    一旦一个全局变量被定义成TLS类型的,那么每个线程都会拥有这个变量的一个副本,任何线程对该变量的修改都不会影响其他线程中该变量的副本。

    Windows下TLS的实现
    对于Windows系统而言,正常情况下一个全局变量或静态变量会被放到.data或.bss段中,但当我们使用_declspec(thread)定义一个线程私有变量的时候,编译器会把这些变量放到PE文件的“.tls”段中,当系统启动一个新的线程时,它会从进程的堆中分配一块足够大小的空间,然后把“.tls”段中的内容复制到这块空间中,于是每个线程都有自己独立的一个“.tls”副本。

    TLS变量可能是一个C++的全局对象,那么每个线程在启动时不仅仅是复制.tls的内容那么简单,还需要负责把这TLS对象初始化,必须逐个调用它们的全局构造函数,而且当线程退出时,还要逐个地将它们析构。Windows PE文件结构下的Datadirectory数据目录结构数组共16个元素,其中有一个元素Image_direct_entry_TLS,该元素便是指向TLS表,TLS表中每项便对应一个TLS变量的构造函数和析构函数的地址。TLS表本身位于PE文件的.rdata段中。

    而每个线程可能有的TLS变量并不相同,显然Image_direct_entry_TLS支出的全局TLS变量集合对于单个线程而言过于宽泛。其实每个线程都配备了一个线程环境块的数据结构(TEB, thread environment block),这个结构里面保存了线程的堆栈地址、线程ID等信息,其中便有一个域是指向该线程专属的TLS变量地址的指针数组。

    2. 显式TLS

    前面提到的使用 __thread__declspec(thread)关键字定义全局变量为TLS变量的方法往往被称为隐式TLS,即程序员无需关心TLS变量(托管)的申请、分配赋值和释放,编译器、运行库还有操作系统已经在背后将一切处理好了。

    显式TLS则是是通过Windows系统API 共4个函数TlsAlloc(), TlsGetValue(), TlsSetValue(), TlsFree()用来手动地控制TLS变量的申请和处理。Linux系统下是pthread_key_create(), pthread_getspecific(), pthread_setspecific(), pthread_key_delete()

    前面提到过TEB(线程环境模块)中有个TLS数组,实际上显式TLS也是利用这个数组来保存TLS数据的。线程专属的TLS变量空间显然不能过分大,一般TEB中都限制了TLS变量地址数组大小为64各表项,如果第一批TLS变量个数超过了64个,则系统还可以再批发4KB空间供以存放更多的TLS变量地址(32位系统),这意味着Windows XP下最多可以拥有(1024+64=1088)个TLS变量。但是考虑到显式使用TLS变量过于复杂,一般都很少用,毕竟通过__declspec(thread)修饰后,由操作系统直接统一分配,并且进行读取时的路由导向,而不需要通过显式调用TlsGetValue(), TlsSetValue()等函数来操作TLS变量。但是隐式TLS有一点不好,就是使用在DLL中的TLS变量在通过LoadLibrary()显式加载DLL时会无法正确初始化,这意味着在实际应用当中除了exe文件和保证会静态连接的dll库,其它都无法使用。

    使用全局共享的资源:如全局变量或者静态变量等数据,是导致多线程编程中非线程安全的常见原因。在多线程程序中,保障非线程安全的常用手段是使用互斥锁来做保护,但这显然会导致并发处理速度下降。如果有些数据只能有一个线程可以访问,那么这一类数据就可以使用线程局部存储机制来处理,虽然使用这种机制会给程序执行效率上带来一定的影响,但对于使用锁机制来说,这些性能影响将可以忽略。

    一点想法:虽然暂且并没有解除到太多的使用__declspec(thread)创建隐式TLS的场景,但是在多线程场景下,将多线程共享的资源组(比如对象池),则每个线程在每次操作时都会对对象池加锁申请一个对象然后解锁离开,这样子其实是人为地导致各处理线程在对象池外侧的等待阻塞,其实”完全可以借鉴运行库从系统中批发一块大内存零售给程序中各种需求“的思想,完全可以让各线程事先从全局的资源组总批发小批量资源,然后本线程专属使用,这样采用分而治之的策略可以减少对象池外waiting-to-do的阻塞情况。根据使用场景调整参数应该可以取得不错的加速效果。根据这样的想法搜到一篇类似的文章,可以参考https://www.cnblogs.com/sniperHW/p/3575816.html

    展开全文
  • 线程局部存储TLS

    2018-01-24 13:27:10
    为了实现局部变量人们引入了tls 用途:动态TLS和静态TLS这两项技术在创建DLL的时候更加有用,这是因为DLL通常并不知道它们被链接到的应用程序的结构是什么样的。 动态TLS 系统中每个进程都有一组正在使用标志(in...
  • TLS线程局部存储 首先TLS线程局部存储,目的是让线程拥有自己独立的数据空间,每个线程都有自己的TLS,这样线程就不单单是使用进程的数据空间了。同时线程之间的TLS应该是互不干扰的(有说可以读取其他线程的TLS)...
  • 在笔者分析glibc源码中的内存分配模块时,遇到了线程局部变量thread_arena,该变量是线程专有的局部变量。在glibc源码中,有相似的变量errno,也是线程专有的变量。尽管在glibc的其他头文件中,errno被定义为 (* __...
  • TLS(线程局部存储).zip

    2020-04-02 22:33:45
    《Windows PE权威指南》TLS学习代码 实现TLS表定位,枚举; 实现动态线程存储 实现静态线程存储 可与《PE文件:TLS线程局部存储》配套使用 https://blog.csdn.net/weixin_43742894/article/details/105235426
  • l 适用情形:线程局部存储适用于多线程共享数据,而又不需要同步的情形。(线程同步的开支比较大。) l 原理在每个线程中有一个存储区域,该存储区域有64个slot(数据槽)。可以通过该slot的索引值(一个DWORD数值),...
  • TLS线程局部存储

    2020-05-16 15:46:40
    TLS全称线程局部存储器,它用来保存变量或回调函数。  TLS里面的变量和回调函数都在程序入口点(AddressOfEntry)之前执行,也就是说程序在被调试时,还没有在入口点处断下来之前,TLS中的变量和回调函数就已经...
  • TLS--线程局部存储

    2018-07-26 17:36:30
    这个东西并不陌生了,之前写过了一个关于这个的应用,利用静态TLS姿势实现代码段静态加密免杀或者所谓的加壳思路。地址在这:http://blog.csdn.net/u013761036/article/details/53967943今天就简单的整理下TLS的相关...
  • 这也就是说在某一个线程已经调用了一个函数时,如果你再调用同一个函数,那么这样是不安全的。一个不可重入的函数通过连续的调用来保存静态变量或者是返回一个指向静态数据的指针。 举例来说,std::strtok就是不可重...
  • windows_35_Thread_Tls 线程局部存储 // windows_35_Thread_Tls.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <windows.h>#include <stdio.h>#include <string.h>int...
  • TLS--线程局部存储1

    2018-08-09 17:03:36
    概念:线程局部存储(Thread Local Storage,TLS)用来将数据与一个正在执行的指定线程关联起来。 进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,...
  • 动态TLS windows系统中每个进程都有一组正在使用标志(in-use-flag),如图21-1所示。每个标志可以被设为FREE或着INUSE,表示改TLS元素是否正在被使用。Microsoft保证至少有TLS_MINIMUM_AVAILABLE = 64个位标志可供...
  • TLS是各线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或者修改进程的全局数据或是静态数据,就像对待自身的局部变量一样。TLS回调函数常用于反调试,因为TLS回调函数运行会先于EP代码执行。 若在...
  • TLS线程局部存储的使用 // useTLS.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include //声明 VOID InitStartTime(); DWORD GetUserTime(); //TLS索引,作全局变量 DWORD g_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,055
精华内容 2,422
关键字:

tls线程局部存储