【问题标题】:Implementing Thread Local Storage in Software在软件中实现线程本地存储
【发布时间】:2015-07-09 08:11:12
【问题描述】:

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

http://www.keil.com/support/man/docs/armcc/armcc_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*)。如果我理解正确,这就是我们想要的。这可能行得通吗?

【问题讨论】:

  • 我认为您提出的解决方案可能可行,但我认为符号 __aeabi_read_tp 应该由标准 C 库或编译器运行时库提供。
  • 您基本上是对的,但是编译器和 RTOS 没有一起交付。我也将这个问题发布给了 KEIL 开发人员。我很高兴看到它是否有效。

标签: c++ multithreading


【解决方案1】:

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

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

编辑:添加了基于 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]);
}

【讨论】:

    【解决方案2】:

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

    有关更复杂的内容,请参阅this idea

    【讨论】:

    • “我提供这个可怕的噩梦创作”我喜欢这个! :)
    【解决方案3】:

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

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

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

    执行摘要是:

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

    编译器和链接器正在完成您期望的所有工作,但是您需要初始化并返回一个结构正确并按照编译器期望的方式填写的“线程指针”,因为它正在生成直接按照啤酒花的说明进行操作。

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

    http://www.fsfla.org/~lxoliva/writeups/TLS/RFC-TLSDESC-x86.txt

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

    几年之后,所以您可能已经解决或没有解决您的问题。在这种情况下,直接使用操作系统的 TLS API 可能是最容易的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-01
      • 2011-01-04
      • 1970-01-01
      • 2011-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多