线程本地存储(TLS)

对于多线程应用程序,如果线程过于依赖全局变量和静态局部变量就会产生线程安全问题。也就是一个线程的使用全局变量可能会影响到其他也使用此全局变量的线程,有可能会造成一定的错误,这可以通过线程同步机制解决当然也可以通过线程本地存储解决。线程本地存储意思是每一个线程在使用全局变量的时候都产生一个只属于本线程的副本,这样我们就可以直接使用这个副本,而不会影响到其他线程。

TLS分类

动态TLS

动态TLS涉及四个函数:

TlsAlloc()                               //分配可用的TLS索引并初始化索引
TlsFree(DWORD dwTlsIndex) //释放TLS索引,下次可继续使用
TlsSetValue(DWORD dwTlsIndex,LPVOID p) //设置TLS索引的值
TlsGetValue(DWORD dwTlsIndex) //获取tls索引的值

windows系统为每一个线程都预留了一个TLS数组,这个TLS数组的每一个索引都可以存储数据。我们需要先申请一个可用的索引,这个索引在任何一个线程都可以使用,而且因为其是不同的TLS数组所以同一个索引所代表的并不是同一个内存块,所以其不会相互影响。TLS的内部数据结构如下。

下面通过一个例子来验证:主线程申请一个可用的TLS索引,主线程设置此TLS索引的值为1,接着创建一个子线程并且子线程设置TLS索引为2。

#include <Windows.h>
#include <process.h>
#include <stdio.h>
DWORD dwTlsIndex;
//子线程函数
unsigned int _stdcall _ThreadProc(PVOID pvParam)
{
DWORD dwNum; TlsSetValue(dwTlsIndex, (PVOID)2); //子线程设置TLS索引的值
dwNum = (DWORD)TlsGetValue(dwTlsIndex);
printf("子线程:%d\n", dwNum);
return 0;
} int main(int argc, char* argv[])
{
DWORD dwNum;
uintptr_t hThread; dwTlsIndex = TlsAlloc(); //申请一个索引
if(dwTlsIndex != -1)
{ TlsSetValue(dwTlsIndex, (PVOID)1); //主线程设置TLS索引的值
dwNum = (DWORD)TlsGetValue(dwTlsIndex);
printf("主线程:%d\n", dwNum); hThread = _beginthreadex(NULL, NULL, _ThreadProc, 0, NULL, NULL);
Sleep(100); dwNum = (DWORD)TlsGetValue(dwTlsIndex);
printf("主线程:%d\n", dwNum); TlsFree(dwTlsIndex); //释放一个索引
}
return 0;
}

通过运行结果可以看到在子线程改变TLS索引对应的值为2后,主线程TLS索引对应的值依然为1。两个线程不会相互影响实现了线程本地存储。

我们用OD调试查看TlsSetValue(),发现TLS数组位于线程的fs:[0] + 0xE10地址处。接着通过TlsAlloc()获得的索引就可以访问对应的数组项了。因为每一个线程的fs不同,所以其TLS数组是不同的内存块互补影响。

静态TLS

静态TLS就是利用.tls区段,将全局变量声明为线程本地全局变量并将其放入.tls区段中。只需在声明全局变量时加上关键字 _declspec(thread)就行了。

_declspec(thread) int iTest = 0;

下面通过一个例子来查看静态TLS,主线程使用的线程本地全局变量和定义的线程本地全局变量是同一块内存,而子线程使用的线程本地全局变量就和他们不是一个内存块而是其副本。

#include <Windows.h>
#include <process.h>
#include <stdio.h>
//静态TLS
_declspec(thread) int iTest = 0;
unsigned int _stdcall ThreadProc(PVOID pvParam)
{
iTest = 3;
printf("0x%x:%d\n",&iTest,iTest);
return 0;
} int main(int argc, char* argv[])
{
uintptr_t hThread;
iTest = 5;
printf("0x%x:%d\n",&iTest,iTest);
hThread = _beginthreadex(NULL, NULL, ThreadProc, NULL, NULL, NULL);
Sleep(100);
return 0;
}



下面我们用OD调试程序来看一下TLS的内存布局。

通过在PE文件头中的数据目录表中找到TLS结构,得到TLS表的基地址和大小。



接着来到TLS表处,TLS表是一个IMAGE_TLS_DIRECTORY32的结构(此结构一般在.rdata区块中),查看AddressOfIndex的值为0x52713C

typedef struct _IMAGE_TLS_DIRECTORY32
{
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
PDWORD AddressOfIndex; //用来定位线程局部数据的索引
PIMAGE_TLS_CALLBACK *AddressOfCallBacks; //TLS回调函数指针数组的地址
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32

我们运行程序,可以看到当在主线程中对线程局部数据赋值时,其是将[fs:[0x2C]] + 0x104作为线程局部存储内存块的基地址,而0x52713c作为索引值,也就是TLS结构中的AddressOfIndex作为索引值。

子线程的[fs:[0x2C]] + 0x104则会指向另一个内存块,所以二者不会相互影响。也就是没创建一个新线程,系统就会开辟一块新的内存空间,将.tls区块中的线程局部变量复制到这块空间中,接着会将这块内存空间的地址保存在fs:[0x2C]中。

TLS线程回调函数

每当线程创建或结束前(包括主线程)都会调用TLS线程回调函数,程序可以包含多个TLS线程回调函数,这些函数的地址构成一个数组。数组的首地址保存在IMAGE_TLS_DIRECTORY3结构中的AddressOfCallBacks字段中。

线程本地存储(动态TLS和静态TLS)的更多相关文章

  1. 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理

    原文链接地址:http://www.cppblog.com/Tim/archive/2012/07/04/181018.html 本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们 ...

  2. 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理

    本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们知道在一个进程中,所有线程是共享同一个地址空间的.所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线 ...

  3. Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic

    Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...

  4. 线程本地存储(Thread Local Storage, TLS)简单分析与使用

    在多线程编程中, 同一个变量, 如果要让多个线程共享访问, 那么这个变量可以使用关键字volatile进行声明; 那么如果一个变量不想使多个线程共享访问, 那么该怎么办呢? 呵呵, 这个办法就是TLS ...

  5. ThreadLocal(线程本地存储)

    1. ThreadLocal,即线程本地变量或线程本地存储. threadlocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的 ...

  6. 线程本地存储 ThreadLocal

    线程本地存储 · 语雀 (yuque.com) 线程本地存储提供了线程内存储变量的能力,这些变量是线程私有的. 线程本地存储一般用在跨类.跨方法的传递一些值. 线程本地存储也是解决特定场景下线程安全问 ...

  7. .NET:线程本地存储、调用上下文、逻辑调用上下文

    .NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...

  8. C# 线程本地存储 调用上下文 逻辑调用上下文

    线程本地存储 using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleAppTest ...

  9. Java线程本地存储ThreadLocal

    前言 ThreadLocal 是一种 无同步 的线程安全实现 体现了 Thread-Specific Storage 模式:即使只有一个入口,内部也会为每个线程分配特有的存储空间,线程间 没有共享资源 ...

随机推荐

  1. ASP.NET .Core 集成 React SPA 应用

    AgileConfig的UI使用react重写快完成了.上次搞定了基于jwt的登录模式(AntDesign Pro + .NET Core 实现基于JWT的登录认证),但是还有点问题.现在使用reac ...

  2. 08、元组tuple

    元组(tuple) 是一个有序且不可变的容器,在里面可以存放多个不同类型的元素 元组是在最后多一个逗号,用于表示它是一个元组 tuple = (11,22,'阿斯顿','媚媚',) #后面多加一个逗号 ...

  3. 攻防世界 reverse 进阶 -gametime

    19.gametime csaw-ctf-2016-quals 这是一个小游戏,挺有意思的 's'-->' '    'x'-->'x'   'm'-->'m' 观察流程,发现检验函 ...

  4. go-ini入门教程

    go-ini入门教程 go-ini简介 Package ini provides INI file read and write functionality in Go. 在实际开发时,配置信息一般不 ...

  5. [系统重装日志3]autocad和ps破解安装

    cad 以前保存的安装包注册机还让杀毒软件给自动删除了!!!(╯‵□′)╯︵┻━┻!!!又在网上找的注册机!!!(╯‵□′)╯︵┻━┻!!!软件安装包必须保存一份压缩的!!!(╯‵□′)╯︵┻━┻!! ...

  6. CMS前世今生

    CMS一直是面试中的常考点,今天我们用通俗易懂的语言简单介绍下. 垃圾回收器为什么要分区分代? 如上图:JVM虚拟机将堆内存区域分代了,先生代是朝生夕死的区域,老年代是老不死的区域,不同的年代对象有不 ...

  7. OO第一单元感悟与体会

    第一单元的三次编程作业结束了,现在分享一些我对自己作业的分析和感想 1.程序结构的分析 第一次作业: 本次作业我的主要思路是,为每一项写一个正则表达式,在输入的字符串中匹配每一项,多项式类中保存着一个 ...

  8. Github 镜像资源

    1.GitHub 镜像访问 这里提供两个最常用的镜像地址(别登录账号): https://github.com.cnpmjs.org https://hub.fastgit.org 也就是说上面的镜像 ...

  9. Spring Cloud:面向应用层的云架构解决方案

    Spring Cloud:面向应用层的云架构解决方案 上期文章我们介绍了混合云,以及在实际操作中我们常见的几种混合云模式.今天我们来聊一聊Spring Cloud如何解决应用层的云架构问题. 对于Sp ...

  10. 剑指offer--孩子们的游戏(圆圈中最后剩下的数字)

    每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指定一个数m ...