ZHCADC4A September 2011 – March 2014
在某些动态链接模型中(包括 Linux),可以在运行时使用 dlopen() 来加载模块。dlopened 模块中的 TLS 块是动态分配的,因此不能以从 TP 的固定偏移量的方式为所有线程分配。因此,访问线程局部变量的方法是通过使用模块标识符和模块 TLS 块中线程局部变量的偏移量进行引用。
图 7-1 展示了 C6x Linux TLS 运行时表示法。每个线程都有一个此运行时 TLS 结构的实例。
对于每个线程,线程指针 (TP) 都指向线程控制块 (TCB)。可执行文件的 TLS 块如果存在,则在调整对齐后放置在 TCB 后面。来自其他非动态模块的 TLS 块随后将按照其对齐要求进行放置。静态模块的随后 TCB 和 TLS 块构成程序的静态 TLS。在创建线程的过程中,将会创建线程的静态 TLS。
TCB 为 64 位宽度。前 32 位指向动态线程矢量 (DTV)。剩下的 32 位保留。
通过 TCB 指向的 dtv 是一个 32 位元素的向量。dtv[0] 元素是生成 ID,用于在装载经过 dlopen 处理的模块时管理 dtv 的动态增长。dtv[n] 元素(其中 n != 0)是指向模块 n 的 TLS 块的 32 位指针。当加载含 TLS 数据的模块时,会为该模块分配一个模块 ID。此模块 ID 特定于进程。由多个进程共享的动态共享库在每个进程中可以具有不同的模块 ID。模块 ID 1 始终分配给可执行文件。
主线程由动态加载器创建,后续线程由线程库创建。创建主线程时,dtv 阵列只需要包含指向最初加载的模块的指针。
当线程对新模块进行 dlopen 处理时,应该为进程中的所有线程分配模块的 TLS 块。如果其他线程访问这个新模块的线程本地数据,则需要执行此操作。但是,可以推迟分配经过 dlopen 处理的模块的 TLS 块,推迟到首次访问该模块的存储器后。这可以通过将相应的 dtv[module-id] 初始化为 TLS_DTV_UNALLOCATE 来实现。__tls_get_addr() 函数可以检查 dtv[module_id] 是否为 TLS_DTV_UNALLOCATED;如果是,则它会为当前线程分配并初始化 TLS 块。