在软件中实现线程本地存储

Implementing Thread Local Storage in Software

本文关键字:存储 线程 实现 软件      更新时间:2023-10-16

我们正在将一个嵌入式应用程序从Windows CE移植到不同的系统上。目前的处理器是STM32F4。我们当前的代码库大量使用TLS。新的原型机正在运行KEIL CMSIS RTOS,它的功能非常少。

在http://www.keil.com/support/man/docs/armcc/arm CC_chr1359124216560.htm上,它说自5.04以来支持线程本地存储。现在我们使用的是5.04。问题是,当链接我们的程序与__thread int a;的变量定义时,链接器找不到对我有意义的__aeabi_read_tp

我的问题是:是否有可能实现__aeabi_read_tp,它将工作或有更多的?

如果这对我们来说是不可能的:有没有一种方法只在软件中实现TLS ?我们现在先不讨论性能。

编辑我尝试通过查看旧的freeBSD源代码和其他源代码来实现__aeabi_read_tp。虽然该函数主要是在汇编中实现的,但我发现C中的一个版本可以归结为:

extern "C"
{
    extern osThreadId svcThreadGetId(void);
    void *__aeabi_read_tp()
    {
        return (void*)svcThreadGetId();
    }
}

这基本上是给我当前正在执行的线程的ID (void*)。如果我没理解错的话,这就是我们想要的。这可行吗?

不考虑性能和不进入CMIS RTOS细节(我不知道),您可以为变量分配所需的空间-无论是在堆上还是作为静态或全局变量-我建议有一个结构数组。然后,在创建线程时,将指向下一个未使用结构体的指针传递给线程函数。

在静态或全局变量的情况下,最好知道有多少线程并行工作,以限制预分配内存的大小。

EDIT:添加了基于pthreads的TLS实现示例:

#include <pthread.h>
#define MAX_PARALLEL_THREADS 10
static pthread_t threads[MAX_PARALLEL_THREADS];
static struct tls_data tls_data[MAX_PARALLEL_THREADS];
static int tls_data_free_index = 0;
static void *worker_thread(void *arg) {
    static struct tls_data *data = (struct tls_data *) arg;
    /* Code omitted. */
}
static int spawn_thread() {
    if (tls_data_free_index >= MAX_PARALLEL_THREADS) {
        // Consider increasing MAX_PARALLEL_THREADS
        return -1;
    }
    /* Prepare thread data - code omitted. */
    pthread_create(& threads[tls_data_free_index], NULL, worker_thread, & tls_data[tls_data_free_index]);
}

不那么令人印象深刻的解决方案是std::map<threadID, T>。需要用互斥锁包装以允许新线程。

要了解更复杂的内容,请参见

我相信这是可能的,但可能有点棘手。

这是一篇描述__threadthread_local在ELF图像中的行为的论文(尽管它没有讨论AEABI的ARM架构):

https://www.akkadia.org/drepper/tls.pdf

执行摘要如下:

    链接器在生成的可执行文件中创建.tbss和/或.tdata节,以提供每个线程所需的线程本地数据的原型图像。
  • 在运行时,每个线程控制块(TCB)都有一个指向d动态t thread-local v指针表(本文中的dtv),该表包含该线程的线程本地存储。它是在线程第一次尝试访问线程局部变量时惰性分配和初始化的。(大概是__aeabi_read_tp())
  • Initialization将原型.tdatamemset的图像复制到分配的存储中。
  • 当源代码访问线程局部变量时,编译器生成代码从__aeabi_read_tp()中读取线程指针,并执行所有适当的间接操作以获取该线程局部变量的存储。

编译器和链接器正在做你期望它做的所有工作,但是你需要初始化并返回一个"线程指针",这个指针的结构和填充方式是编译器期望的,因为它直接生成指令来跟随跳转。

如本文所述,有几种访问TLS变量的方法,这些方法可能完全适用于您的编译器和体系结构,也可能不完全适用:

http://www.fsfla.org/lxoliva/那样/TLS/RFC-TLSDESC-x86.txt

但是,问题大致相同。当您有运行时加载的库时,可能会带来自己的.tbss.tdata部分,这就变得更加复杂了。对于突然试图访问一个变量的线程,您必须扩展线程本地存储,该变量是在初始化该线程的存储后加载的库引入的。编译器必须根据声明TLS变量的位置生成不同的访问代码。您需要处理和测试您想要支持的所有用例。

这是,所以你可能已经解决或没有解决你的问题。在这种情况下,直接使用操作系统的TLS API可能是最简单的。