JavaScript is required to for searching.
跳过导航链接
退出打印视图
链接程序和库指南     Oracle Solaris 10 1/13 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

第 1 部分使用链接编辑器和运行时链接程序

1.  Oracle Solaris 链接编辑器介绍

2.  链接编辑器

3.  运行时链接程序

4.  共享目标文件

第 2 部分快速参考

5.  链接编辑器快速参考

第 3 部分高级主题

6.  直接绑定

7.  生成目标文件以优化系统性能

8.  mapfile

9.  接口和版本控制

10.  使用动态字符串标记建立依赖性

11.  可扩展性机制

第 4 部分ELF 应用程序二进制接口

12.  目标文件格式

13.  程序装入和动态链接

14.  线程局部存储

C/C++ 编程接口

线程局部存储节

线程局部存储的运行时分配

程序启动

创建线程

启动后动态装入

延迟分配线程局部存储块

线程局部存储的访问模型

SPARC: 线程局部变量访问

SPARC: 常规动态 (General Dynamic, GD)

SPARC: 局部动态 (Local Dynamic, LD)

32 位 SPARC: 初始可执行 (Initial Executable, IE)

64 位 SPARC: 初始可执行 (Initial Executable, IE)

SPARC: 局部可执行 (Local Executable, LE)

SPARC: 线程局部存储的重定位类型

32 位 x86: 线程局部变量访问

32 位 x86: 常规动态 (General Dynamic, GD)

x86: 局部动态 (Local Dynamic, LD)

32 位 x86: 初始可执行 (Initial Executable, IE)

32 位 x86: 局部可执行 (Local Executable, LE)

32 位 x86: 线程局部存储的重定位类型

x64: 线程局部变量访问

x64: 常规动态 (General Dynamic, GD)

x64: 局部动态 (Local Dynamic, LD)

x64: 初始可执行 (Initial Executable, IE)

x64: 局部可执行 (Local Executable, LE)

x64: 线程局部存储的重定位类型

第 5 部分附录

A.  链接程序和库的更新及新增功能

B.  System V 发行版 4(版本 1)mapfile

索引

线程局部存储的运行时分配

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

运行时线程局部数据存储的布局如图 14-1 中所示。

图 14-1 线程局部存储的运行时存储布局

image:运行时线程局部存储的布局

程序启动

在程序启动时,运行时系统将为主线程创建 TLS。

首先,运行时链接程序以逻辑方式将所有已装入动态目标文件(包括动态可执行文件)的 TLS 模板合并成单个静态模板。在合并的模板中,为每个动态目标文件的 TLS 模板指定一个偏移 tlsoffsetm,如下所示。

tlssizem+1alignm+1 分别是动态目标文件 m 的分配模板的大小和对齐方式。其中,1 <= m <= M,而 M 是已装入动态目标文件的总数。round(offset, align) 函数返回一个偏移,该偏移向上舍入为最接近 align 倍数。

接下来,运行时链接程序将计算启动时为 TLS 分配的总大小 tlssizeS。此大小等于 tlsoffset M,加上 512 个字节。这额外的 512 个字节为静态 TLS 引用提供了一个备份预留空间。执行静态 TLS 引用的共享目标文件在进程初始化后将装入并指定到该备份预留空间。但是,该预留空间的大小是固定的、有限的。此外,该预留空间只能为未初始化的 TLS 数据项提供存储空间。为实现最大的灵活性,共享目标文件应使用动态的 TLS 模型引用线程局部变量。

与计算得出的 TLS 大小 tlssizeS 关联的静态 TLS 块将紧排在进程指针 tpt 之前放置。对 TLS 数据的访问基于 tpt 减法。

静态 TLS 块与一份链接的初始化记录列表相关联。此列表中的每条记录都描述一个已装入动态目标文件的 TLS 初始化映像。每条记录都包含以下字段:

线程库使用此信息为初始线程分配存储空间。此存储空间初始化后,会为初始线程创建动态 TLS 向量。

创建线程

对于初始线程和所创建的每个新线程,线程库将为每个已装入的动态目标文件分配一个新的 TLS 块。各个块可以单独进行分配,也可以作为一个连续块进行分配。

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

然后,线程库为当前线程 t 创建一个指针向量 dtvt。每个向量的第一个元素都包含一个生成号 gent,该生成编号用于确定需要扩展向量的时间。请参见延迟分配线程局部存储块

dtvt,m 向量中剩余的每个元素都是一个指针,指向为属于动态目标文件 m 的 TLS 保留的块。

对于启动后动态装入的目标文件,线程库将延迟分配 TLS 块。分配将在第一次引用已装入的目标文件中的 TLS 变量时进行。对于延迟分配的块,指针 dtvt,m 设置为实现定义的特殊值。


注 - 运行时链接程序可以将所有启动目标文件的 TLS 模板进行分组,以便在向量 dtv t,1 中共享单个元素。这种分组不会影响前面介绍的偏移计算,也不会影响初始化记录列表的创建。但是,对于以下各节,总目标文件数 M 的值从 1 开始。


然后,线程库将初始化映像复制到新存储块中的对应位置。

启动后动态装入

仅包含动态 TLS 的共享目标文件可以在进程启动后不受限制地装入。因此,运行时链接程序扩展初始化记录列表,以包含新目标文件的初始化模板。新目标文件将获得一个索引 m = M + 1。计数器 M1 递增。但是,新的 TLS 块将延迟分配,直至实际引用了这些块时。

卸载仅包含动态 TLS 的共享目标文件时,将释放该库使用的 TLS 块。

包含静态 TLS 的共享目标文件可以在进程启动后有限制地装入。静态 TLS 引用只能通过任何剩余的备份 TLS 预留空间来满足。请参见程序启动。此预留空间的大小是有限的。此外,此预留空间只能为未初始化的 TLS 数据项提供存储空间。

包含静态 TLS 的共享目标文件永远不会卸载。静态 TLS 的处理将导致共享目标文件被标记为不可删除。

延迟分配线程局部存储块

在动态 TLS 模型中,当线程 t 需要访问目标文件 m 的 TLS 块时,代码将更新 dtvt 并执行 TLS 块的初始分配。线程库将提供以下接口以便动态分配 TLS。

typedef struct {
    unsigned long ti_moduleid;
    unsigned long ti_tlsoffset;
} TLS_index;

extern void *__tls_get_addr(TLS_index *ti);     (SPARC and x64)
extern void *___tls_get_addr(TLS_index *ti);    (32–bit x86)

注 - 此函数的 SPARC 和 64 位 x86 定义具有相同的函数签名。但是,32 位 x86 版本不使用缺省调用约定来传递栈中的参数。相反,32 位 x86 版本通过更有效的 %eax 寄存器来传递其参数。为了指示将使用此替代调用方法,32 位 x86 函数的名称中有三个前导下划线。


这两个版本的 tls_get_addr() 都会检查每线程生成计数器 gent,以确定是否需要更新向量。如果 dtvt 向量已过时,例程将更新此向量,可能会重新分配此向量以便为更多项留出空间。然后,例程将检查与 dtvt,m 对应的 TLS 块是否已分配。如果该向量尚未分配,例程将分配并初始化该块。例程使用运行时链接程序提供的初始化记录列表中的信息。dtv t,m 指针被设置为指向已分配的块。例程返回一个指向块中给定偏移的指针。