线程局部存储(TLS)

线程局部存储(Thread Local Storage,TLS),是一种变量的存储方法,这个变量在它所在的线程内是全局可访问的,但是不能被其他线程访问到,这样就保持了数据的线程独立性。而熟知的全局变量,是所有线程都可以访问的,这样就不可避免需要锁来控制,增加了控制成本和代码复杂度。

一、C/C++编程接口

POSIX线程库提供了如下API管理TLS:

// 创建一个TLS变量,并设置析构函数
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
// 回收TLS变量,但是注意并不调用TLS的析构函数
int pthread_key_delete(pthread_key_t key);
// 获取TLS变量的当前值
void *pthread_getspecific(pthread_key_t key);
// 给TLS变量赋值
int pthread_setspecific(pthread_key_t key, const void *value);

除了上面API的方式,GCC的编译器也支持语言级别的用法,这样比用API调用,更简单。使用 __thread 关键字可将变量声明为线程局部变量,如下例所示:

__thread int i;
__thread char *p;
__thread struct state s;

使用GCC编译级别支持的方式来实现TLS的一个Demo:

// 编译:g++ main.cc -lpthread
#include<iostream>
#include<pthread.h>
#include<unistd.h>using namespace std;
__thread int iVar = 100;void* Thread1(void *arg)
{iVar += 200;cout<<"Thead1 Val : "<<iVar<<endl;
}void* Thread2(void *arg)
{iVar += 400;sleep(1);cout<<"Thead2 Val : "<<iVar<<endl;
}int main()
{pthread_t pid1, pid2;pthread_create(&pid1, NULL, Thread1, NULL);pthread_create(&pid2, NULL, Thread2, NULL);pthread_join(pid1, NULL);pthread_join(pid2, NULL);return 0;
}

在循环优化期间,编译器可根据需要选择创建临时线程局部变量。

适用性
__thread 关键字可以应用于任何全局变量、文件作用域静态变量或函数作用域静态变量。它对于始终是线程局部变量的自动变量没有影响。

初始化
在 C++ 中,如果初始化需要静态构造函数,将无法初始化线程局部变量。否则,可以将线程局部变量初始化为对于普通静态变量合法的任何值。无论是线程局部变量还是其他变量,都不能静态地初始化为线程局部变量的地址。

绑定
线程局部变量可以在外部声明和引用。线程局部变量遵循与普通符号相同的插入规则。

二、ELF中的TLS节

代码中所有的全局变量都存储在.data(静态初始化变量)和.bss(未静态初始化的变量)这两个段。而TLS变量存储位置有所不同:

  • 已初始化的线程局部变量分配在 .tdata.tdata1 节中,节类型为SHT_NOBITS,节属性为SHF_ALLOC + SHF_WRITE + SHF_TLS。此初始化可能需要重定位。
  • 未初始化的线程局部变量定义为 COMMON 符号,最终分配在 .tbss 节中进行,节类型为SHT_PROGBITS,节属性为SHF_ALLOC + SHF_WRITE + SHF_TLS

.data不一样的是,运行时程序不会直接访问这些TLS段。在分配了任何已初始化的节后会立即分配未初始化的节,并进行填充以便正确对齐(内存中.tbss紧跟在.tdata后)。

.tdata.tbss合并的节一起构成 TLS 模板,每次创建新线程时,都会使用此模板分配 TLS,所以每个线程启动时TLS都是相同的。此模板的已初始化部分称为 TLS 初始化映像。所有因已初始化的线程局部变量而生成的重定位将应用于此模板。当新线程需要初始值时,将使用重定位的值。

每个线程的TLS块都是运行时分配的,所以在链接时是不知道其地址的,要访问TLS变量必须借助动态链接器才能计算出其地址。链接时只能知道TLS变量在TLS段中的偏移。

TLS 符号的符号类型为 STT_TLS,这些符号表示相对于 TLS 模板开头的偏移量,而不是实际的虚拟地址。TLS 符号指向 TLS 模板的开头,而不是每个数据项的每个线程副本。在exec文件和共享目标文件中,对于已定义的 TLS 符号,其 st_value 字段包含指定的 TLS 偏移量,而对于未定义的 TLS 符号,此字段通常包含零。

访问 TLS 符号通常需要进行重定位,以便在运行时能够正确地计算 TLS 数据的地址。这些重定位引用 STT_TLS 类型的符号,并且还可以引用与 GOT 项关联的局部节符号。

对于根据 TLS 项进行的重定位,重定位地址在 TLS 模板的末尾编码为负偏移。计算该偏移时,首先将模板大小舍入到 32 位目标文件中最接近的 8 字节边界,然后舍入为 64 位目标文件中最接近的 16 字节边界。此舍入操作确保静态 TLS 模板合理对齐以便可用于任何用途。

在exec文件和共享目标文件中,PT_TLS 程序项用于描述 TLS 模板。此模板包含以下成员:

成员
p_offsetTLS 初始化映像的文件偏移
p_vaddrTLS 初始化映像的虚拟内存地址
p_paddr0
p_fileszTLS 初始化映像的大小
p_memszTLS 模板的总大小
p_flagsPF_R
p_alignTLS 模板的对齐方式

三、TLS运行时分配

在程序的生命周期中,会在三个时间创建 TLS。

  • 程序启动时。
  • 创建新线程时。
  • 程序启动后装入共享目标文件之后,线程第一次引用 TLS 块时。

3.1 TLS布局结构

运行时线程局部数据存储的布局如下图所示。

在这里插入图片描述

线程指针

每个线程 t 都有一个关联的线程指针 t p t tp_t tpt ,该指针指向线程控制块 TCB。线程指针 tp 始终包含当前正在运行的线程的 t p t tp_t tpt 值。

TLS模块偏移

动态链接器将exec文件装载之后,假设与exec文件相关联的动态库有多m个(再假设每个都有TLS模块),所以也就会有m + 1个模块(一个是exec的,假设其有)。动态链接器会将这些模块合并成单个静态模板,在合并的模板中,为每个动态目标文件(exec和共享库)的 TLS模板指定一个偏移 t l s o f f s e t m tlsoffset_m tlsoffsetm

t l s o f f s e t 1 = r o u n d ( t l s s i z e 1 , a l i g n 1 ) tlsoffset_1 = round(tlssize_1, align_1 ) tlsoffset1=round(tlssize1,align1)
t l s o f f s e t m + 1 = r o u n d ( t l s o f f s e t m + t l s s i z e m + 1 , a l i g n m + 1 ) tlsoffset_{m+1} = round(tlsoffset_m + tlssize{m+1}, align_{m+1}) tlsoffsetm+1=round(tlsoffsetm+tlssizem+1,alignm+1)

动态线程向量

动态线程向量(Dynamic Thread Vector,dtv)是在多线程程序中用于管理线程局部存储(TLS)的数据结构之一。每个线程都有一个 dtv,用于存储该线程的 TLS 变量的地址列表。dtv 是一个数组或指针数组,其中的每个元素都指向一个 TLS 变量的地址,这些地址通常相对于线程基址或线程指针(TP)进行偏移(可通过tp + tlsoffset进行访问)。

线程库为当前线程 t 创建一个指针向量 d t v t dtv_t dtvt。每个向量的第一个元素都包含一个生成编号 g e n t gen_t gent,该生成编号用于确定需要扩展向量的时间。 d t v t , m dtv_{t,m} dtvt,m 向量中剩余的每个元素都是一个指针,指向为属于动态目标文件 m 的 TLS的块的地址。

分配模型

有些模块的TLS块跟TCB放在一起,是程序启动时就分配 的(如exec及其依赖的.so),称为静态模型;有些模块是程序运行中动态加载的(通过dlopen()动态加载), TLS块在线程第一次访问时分配,称为动态模型

对于静态模型,在程序启动时动态链接器就可以确定其相对于 t p t tp_t tpt 的偏移值,如 t l s o f f s e t 1 、 t l s o f f s e t 2 、 t l s o f f s e t 3 tlsoffset_1、tlsoffset_2、tlsoffset_3 tlsoffset1tlsoffset2tlsoffset3,编译器生成代码时可以直接使用这些偏移值来访问。

对于动态模型,线程库将延迟分配 TLS 块。分配将在第一次引用已装入的目标文件中的 TLS 变量时进行,需要调用运行时系统提供的__tls_get_addr()获取其地址,如 t l s o f f s e t 4 、 t l s o f f s e t 5 tlsoffset_4、tlsoffset_5 tlsoffset4tlsoffset5

3.2 延迟分配TLS

对于延迟分配的TLS,由于其偏移值在启动时未知,必须借助于__tls_get_addr()获取,定义类似如下:

struct tls_index {size_t module_id;size_t offset;
};void* __tls_get_addr(struct tls_index* ti)
{// Get the DTV of current thread.dtv_t* dtv = GET_CURRENT_DTV();// Check if the DTV is stale, and if so, update it.if (dtv[0].counter != dl_tls_generation) {update_dtv();}// Get the TLS block. If not allocated yet, allocate now.char* tls_block = dtv[ti->module_id];if (tls_block == UNALLOCATED_TLS_BLOCK) {tls_block = dtv[ti->module_id] = allocate_tls(module_id);}return tls_block + ti->offset;
}

module_id是模块ID,由动态链接器在加载模块时分配,从1开始(exec文件的模块ID固定是1)。

当动态加载或卸载一个模块时,动态链接器维护的dl_tls_generation会加1,表示模块信息有了变化。由于每个线 程的DTV时延迟更新的,所以每个线程的dtv[0]也会维护自己的generation counter,用于在访问TLS时判断 是否需要更新DTV。

四、TLS的访问模型

每个 TLS 引用都遵循下列访问模型之一。这些模型按照最常见、但最少优化到速度最快、但限制最大的顺序列出。要访问TLS变量需要确定两个信息:

  • 定义TLS变量的模块(可执行程序exec或动态共享库.so)。
  • TLS变量在该模块的TLS段的偏移。

4.1 常规动态 (General Dynamic, GD)-动态 TLS

此模型允许从共享目标文件或exec文件中引用所有 TLS 变量。如果是第一次从特定线程引用 TLS 块,此模型还支持延迟分配此块。

这种模式下不需要链接时知道模块ID和 偏移值。程序启动时动态链接器通过重定向确定模块ID和TLS变量的偏移值,存储在GOT表中。在访问TLS时调用 __tls_get_addr(),传入这两个参数,获取TLS变量的地址。

4.2 局部动态 (Local Dynamic, LD)-局部符号的动态 TLS

此模型是对 GD 模型的优化。编译器可能会确定变量在要生成的目标文件中是局部绑定或受到保护的。在这种情况下,编译器将指示链接器静态绑定动态的 tlsoffset 并使用此模型。与 GD 模型相比,此模型可提供更好的性能。每个函数只需要调用一次 tls_get_addr() 即可确定 d t v 0 , m dtv_{0,m} dtv0,m 的地址。进行链接编辑时绑定的动态 TLS 偏移会与每个引用的 d t v 0 , m dtv_{0,m} dtv0,m 地址相加。

如果链接器确定访问的TLS变量属于本模块(如文件作用域的TLS变量),则采用此模型。TLS变量的偏移值在链接时即可确定,只需要调用__tls_get_addr()确定TLS块的地址即可。由于TLS块的地址可以在不同的本地TLS变量访问时复用,所以相比于GD模型编译器可利用此模型生成有效的代码减少对__tls_get_addr()的调用次数。

4.3 初始可执行 (Initial exec文件utable, IE)-具有指定偏移的静态 TLS

此模型只能引用初始静态 TLS 中包含的 TLS 变量。此模板由进程启动时可用的所有 TLS 块和一个小的备份预留空间组成。在此模型中,给定变量 x 相对于线程指针的偏移存储在 x 的 GOT 项中。

此模型可以从初始进程启动后通过延迟装入、过滤器或 dlopen() 装入的共享库中引用有限数量的 TLS 变量。该访问可通过固定的备份预留空间来实现。此预留空间只能为未初始化的 TLS 数据项提供存储空间。为实现最大的灵活性,共享目标文件应使用动态的 TLS 模型引用线程局部变量。

如果可以确定访问的TLS变量在程序启动时就已分配好,则采用此模型。TLS变量相对于线程寄存器的偏移量可在程序启动时由动态链接器计算好存放在GOT表中。访问TLS变量相当于一次间接地址访问,不需要调用__tls_get_addr()

4.4 局部可执行 (Local exec文件utable, LE)-静态 TLS

此模型只能引用exec文件的 TLS 块中包含的 TLS 变量。链接器静态地计算相对于线程指针的偏移,而不需要进行动态重定位或额外引用 GOT。此模型不能用于引用exec文件外部的变量。

如果可以确定在exec文件中访问exec文件定义的TLS变量,则采用此模型。链接时即可知道TLS变量相对于线程寄存器的偏移量, 计算其地址相当于寄存器加上一个常量,因此访问TLS变量与访问局部变量没有区别。

4.5 模式转换

链接器可以将代码从更常规的访问模型转换为更优化的模型(如果确定适合进行转换)。这种转换可以使用独特的 TLS 重定位来实现。这些重定位不仅请求执行更新,还会标识要使用的 TLS 访问模型。

链接器在了解 TLS 访问模型和要创建的目标文件类型后,便可执行转换。例如,如果一个可重定位目标文件使用 GD 访问模型,被链接到一个exec文件中。在这种情况下,链接器可以适当地使用 IE 或 LE 访问模型转换引用。然后执行模型所需的重定位。

下图说明了不同的访问模型,以及从一个模型到另一个模型的转换。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/288674.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac-git上传至github(ssh版本,个人tokens总出错)

第一步 git clone https://github.com/用户名/项目名.git 第二步 cd 项目名 第三步 将本地的文件移动到项目下 第四步 git add . 第五步 git commit -m "添加****文件夹" 第六步 git push origin main 报错&#xff1a; 采用ssh验证 本地文件链接公钥 …

超级会员卡积分收银系统源码:积分+收银+商城三合一小程序 带完整的安装代码包以及搭建教程

信息技术的迅猛发展&#xff0c;移动支付和线上购物已经成为现代人生活的常态。在这样的背景下&#xff0c;商家对于能够整合收银、积分管理和在线商城的综合性系统的需求日益强烈。下面&#xff0c;罗峰给大家分享一款超级会员卡积分收银系统源码&#xff0c;它集积分、收银、…

读所罗门的密码笔记04_社会信用

1. 人工智能 1.1. 人工智能可以帮助人们处理复杂的大气问题&#xff0c;完善现有的气候变化模拟&#xff0c;帮助我们更好地了解人类活动对环境造成的危害&#xff0c;以及如何减少这种危害 1.2. 人工智能也有助于减少森林退化和非法砍伐 1.3. 人工智能甚至可以将我们从枯燥…

205基于matlab的关于多目标跟踪的的滤波程序

基于matlab的关于多目标跟踪的的滤波程序&#xff0c;包括采用联合概率数据互联&#xff08;JPDA&#xff09;算法实现两个个匀速运动目标的点迹与航迹的关联&#xff0c;输出两个目标跟踪的观测位置、估计位置以及估计误差。程序已调通&#xff0c;可直接运行。 205 多目标跟踪…

Flink on Kubernetes (flink-operator) 部署Flink

flink on k8s 官网 https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-release-1.1/docs/try-flink-kubernetes-operator/quick-start/ 我的部署脚本和官网不一样&#xff0c;有些地方官网不够详细 部署k8s集群 注意&#xff0c;按照默认配置至少有两台wo…

C语言:文件操作详解

什么是文件 文件是是计算机硬盘存储的数据的集合&#xff0c;它可以是文本文档&#xff0c;也可以是图片&#xff0c;程序等等。将数据存储进文件内可以很好的保存数据&#xff0c;方便程序员对文件的操作。 文件的类型 一般根据存储数据类型的不同可以分为二进制文件和文本文…

服务器监控软件夜莺采集监控(三)

文章目录 一、采集器插件1. exec插件2. rabbitmq插件3. elasticsearch插件 二、监控仪表盘1. 系统信息2. 数据服务3. NginxMQ4. Docker5. 业务日志 一、采集器插件 1. exec插件 input.exec/exec.toml [[instances]] commands ["/home/monitor/categraf/scripts/*.sh&q…

AI智能分析网关V4数字农场智能监控方案

随着大数据时代的到来&#xff0c;数据成为国家基础性战略资源&#xff0c;加快数字化转型、以数字化谋求国际竞争新优势已成为全球普遍共识&#xff0c;利用大数据推动经济发展、优化社会治理、改善公共服务成为了世界各国的必然选择。农村为实现产业转型升级和治理创新&#…

HBase的Python API操作(happybase)

一、Windows下安装Python库&#xff1a;happyhbase pip install happybase -i https://pypi.tuna.tsinghua.edu.cn/simple 二、 开启HBase的Thrift服务 想要使用Python API连接HBase&#xff0c;需要开启HBase的Thrift服务。所以&#xff0c;在Linux服务器上&#xff0c;执行…

算法之美:二叉树演进之多叉树及B-Tree树原理

在上篇文章我们了解了平衡二叉树的优势&#xff0c;了解到平衡二叉树能够对不平衡的节点施加旋转&#xff0c;使得树达趋于平衡&#xff0c;以提升查询效率&#xff0c;操作效率很高&#xff0c;与之同时也存在着不少的问题&#xff0c;例如我们在实际使用中会通常会将树加载到…

【Flink架构】关于FLink BLOB的组织架构:FLIP-19: Improved BLOB storage architecture:官网解读

文章目录 一. BlobServer架构1.BlobClient2. BlobServer3. BlobCache4. LibraryCacheManager 二、BLOB的生命周期1. 分阶段清理2. BlobCache的生命周期3. BlobServer 三、文件上下载流程1. BlobCache 下载2. BlobServer 上传3. BlobServer 下载 四. Flink中支持的BLOB文件类型1…

SPI机制详解

在上一篇 gRPC源码剖析-Server启动流程 有提到过SPI机制&#xff0c;SPI对于大多数业务开发人员可能并不熟悉&#xff0c;但是在各底层基础框架中用得还是比较多的&#xff0c;今天我们来详细了解一下。 一、SPI机制 SPI&#xff0c;全称是Service Provider Interface,就是为…

微软正在改进其AI驱动的Copilot在Microsoft Teams中的工作方式,为会议聊天、总结等引入了新的召唤助手方式

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【spring】@Value注解学习

Value介绍 Value 是 Spring 框架中一个非常有用的注解&#xff0c;它允许你将来自配置文件、系统属性、环境变量或者通过 SpEL&#xff08;Spring Expression Language&#xff09;表达式计算得出的值注入到 Spring 管理的 Bean 中。这个注解可以用在字段、setter 方法或者构造…

自动化面试常见算法题!

1、实现一个数字的反转&#xff0c;比如输入12345&#xff0c;输出54321 num 12345 num_str str(num) reversed_num_str num_str[::-1] reversed_num int(reversed_num_str) print(reversed_num) # 输出 54321代码解析&#xff1a;首先将输入的数字转换为字符串&#xff…

【研发日记】Matlab/Simulink开箱报告(十)——Signal Routing模块模块

文章目录 前言 Signal Routing模块 虚拟模块和虚拟信号 Mux和Demux Vector Concatenate和Selector Bus Creator和Bus Selector 分析和应用 总结 前言 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;五&#xff09;——S-Fuction模块(C MEX S-Fun…

python学习15:python中的input语句

python中的input语句 我们前面学习过print语句&#xff0c;可以将内容输出到屏幕上&#xff1b;在python中&#xff0c;与之对应的还有一个input语句&#xff0c;用来获取键盘输入。 数据输出&#xff1a;print 数据输入&#xff1a;input 使用上也很简单&#xff1a; 使用inp…

LIS、LCS算法模型

文章目录 1.LCS算法模型2.LIS算法模型 1.LCS算法模型 LCS问题就是给定两个序列A和B&#xff0c;求他们最长的公共子序列。 在求解时&#xff0c;我们会设dp[i][j]表示为A[1 ~ i]序列和B[1 ~ j]序列中&#xff08;不规定结尾&#xff09;的最长子序列的长度。 if(a[i]b[i]) dp…

信号处理--情绪分类数据集DEAP预处理(python版)

关于 DEAP数据集是一个常用的情绪分类公共数据&#xff0c;在日常研究中经常被使用到。如何合理地预处理DEAP数据集&#xff0c;对于后端任务的成功与否&#xff0c;非常重要。本文主要介绍DEAP数据集的预处理流程。 工具 图片来源&#xff1a;DEAP: A Dataset for Emotion A…

从零开始为香橙派orangepi zero 3移植主线linux——1.uboot

从零开始为香橙派orangepi zero 3移植主线linux——1.uboot 0.前言一、准备二、制作引导文件1.BL312.SCP firmware (Crust)3.uboot 三、烧录四、运行 0.前言 之前买了块香橙派zero3&#xff0c;CPU是全志H618&#xff0c;四核cortex-A53&#xff0c;烧录了官方的ubuntu系统后就…