线程本地存储(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. CSS-clear属性的作用

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  2. C语言之简易了解程序环境

    C语言之简易了解程序环境 大纲: 程序的翻译环境 预编译 编译 汇编 链接 程序的运行环境 在ANSI C的任何一种实现中,存在两个不同的环境. 第1种是翻译环境,在这个环境中源代码被转换为可执行的机 ...

  3. 什么是SSR SSR有什么用 如何使用使用SSR

    什么是SSR 以下信息来自维基百科: Shadowsocks(简称SS)是一种基于Socks5代理方式的加密传输协议,也可以指实现这个协议的各种开发包.当前包使用Python.C.C++.C#.Go语 ...

  4. vue中常见的问题以及解决方法

    有一些问题不限于 Vue,还适应于其他类型的 SPA 项目. 1. 页面权限控制和登陆验证 页面权限控制 页面权限控制是什么意思呢? 就是一个网站有不同的角色,比如管理员和普通用户,要求不同的角色能访 ...

  5. Spring笔记(四)

    Spring JdbcTemplate 一.JdbcTemplate概念及使用 1. 什么是JdbcTemplate: Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库 ...

  6. 清明节特辑 |记忆存储、声音还原、性格模仿……AI可以让人类永生吗?

    摘要:如果能用AI "复活"逝去的亲人 你愿意吗? 清明节,很少有人会去特地想这样一个问题:我们为什么要给过世的人修墓,然后每年固定的时间去扫墓?当农耕文化的色彩褪去,清明节的祭祀 ...

  7. PAT (Advanced Level) Practice 1006 Sign In and Sign Out (25 分) 凌宸1642

    PAT (Advanced Level) Practice 1006 Sign In and Sign Out (25 分) 凌宸1642 题目描述: At the beginning of ever ...

  8. Spring01-模块划分

    Test:pring的单元测试模块 Spring-test-4.0.0.RELEASE Core Continer:核心容器(IOC):黑色代表这部分的功能由哪些jar包组成:要使用这个部分的完整功能 ...

  9. IDEA xml 注解快捷键

    注释:CTRL + SHIFT + / 撤销注释:CTRL + SHIFT + \

  10. 代码安全丨第六期:XPath注入漏洞样例

    1.什么是XPath注入漏洞? XPath是一种用来在内存中导航整个XML树的语言,它使用路径表达式来选取XML文档中的节点或者节点集. XPath注入是指程序使用外部输入动态构造用于从XML数据库检 ...