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

时间:2015-07-09 08:11:12

标签: c++ multithreading

我们正在将嵌入式应用程序从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 *)。如果我理解正确,那就是我们想要的。这可能有用吗?

3 个答案:

答案 0 :(得分:3)

不考虑性能而不考虑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]);
}

答案 1 :(得分:1)

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

对于更复杂的内容,请参阅this idea

答案 2 :(得分:1)

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

这是一篇描述__threadthread_local在ELF图像中的行为的文章(虽然它没有谈到AEABI的ARM架构):

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

执行摘要是:

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

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

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

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

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

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