【问题标题】:Implementing thread-local storage in custom libc在自定义 libc 中实现线程本地存储
【发布时间】:2013-01-25 21:21:29
【问题描述】:

我正在为非常小的静态链接程序实现libc 的一小部分,我认为添加 TLS 支持将是一个很好的学习体验。我使用Ulrich Drepper's TLS document 作为参考。

我设置了两个字符串来试试这个:

static __thread const char msg1[] = "TLS (1).\n"; /* 10 bytes */
static __thread const char msg2[] = "TLS (2).\n"; /* 10 bytes */

并且编译器生成以下指令来访问它们:

mov    rbx, QWORD PTR fs:0x0 ; Load TLS.
lea    rsi, [rbx-0x14]       ; Get a pointer to 'msg1'. 20 byte offset.
lea    rsi, [rbx-0xa]        ; Get a pointer to 'msg2'. 10 byte offset.

假设我将 TCB 放在堆栈上的某个位置:

struct tcb {
    void* self; /* Points to self. I read that this was necessary somewhere. */
    int errno;  /* Per-thread errno variable. */
    int padding;
};

然后将 TLS 区域放在它旁边的 tls = &tcb - tls_size。然后我设置FS寄存器指向fs = tls + tls_size,并将TLS初始化镜像复制到tls

但是,这不起作用。我已经通过将tls_image 的20 个字节写入stdout 来验证我正确定位了TLS 初始化映像。这要么让我相信我错误地放置了 TCB 和/或 TLS 区域,要么我不符合 ABI。

  • 我使用arch_prctl(2) 设置FS 寄存器。我是否需要以某种方式使用set_thread_area(2)
  • 我没有dtv。我假设这不是必需的,因为我是静态链接的。

关于我做错了什么有什么想法吗?非常感谢!

【问题讨论】:

  • 如果你在 Linux 上工作,你应该尝试习惯 AT&T 语法。
  • 不确定你有什么问题,但看看我们在 musl 中的实现可能会有所帮助:git.musl-libc.org/cgit/musl/tree/src/env/__init_tls.c
  • @R.. 很好的参考。但我不明白libc.tls_size = 2*sizeof(void *)+size+align+sizeof(struct pthread);mem += sizeof(void *) * 2; 中的两个空指针是干什么用的?
  • 好点 - 这可能应该被评论。不看代码,我怀疑它是为 dtv 准备的。
  • 请注意,原则上,__tls_get_addr 也需要适用于静态链接程序,因为没有要求链接器执行 TLS 优化,并且特定的目标文件可能已使用需要它的模型构建(例如,如果使用 -fPIC,通常会出现这种情况)。但是,由于只有一个 TLS 段,__tls_get_addr 并不真正需要 dtv 来操作。

标签: c linux x86-64 libc thread-local-storage


【解决方案1】:

我正在为非常小的静态实现一小部分 libc 链接程序,我认为添加 TLS 支持会很好 学习经验。

好主意!我不得不在一个项目中实现我自己的 TLS,因为我不能使用任何常见的线程库,比如 pthread。对于您的问题,我没有完全的解决方案,但分享我的经验可能会很有用。

我使用 arch_prctl(2) 设置 FS 寄存器。我需要使用 set_thread_area(2) 不知何故?

答案取决于您实际使用的架构。如果您使用的是 x86-64 位,则应仅使用 arch_prctl 将 FS 寄存器设置为您要用作 TLS 的内存区域(它允许您寻址大于 4GB 的内存区域)。而对于 x86-32,您必须使用 set_thread_area,因为它是内核支持的唯一系统调用。

我的实现背后的想法是为每个线程分配一个私有内存区域并将其地址保存到%GS 寄存器中。这是一个相当简单的方法,但就我而言,它工作得很好。每次您想要访问线程的私有区域时,您只需使用保存在%GS 中的值和标识内存位置的偏移量作为基地址。我通常为每个线程分配一个内存页(4096),并将其划分为 8 个字节的块。所以,我为每个线程设置了 512 个私有内存槽,可以像索引从 0 到 511 的数组一样访问它们。

这是我使用的代码:

#define _GNU_SOURCE 1 

#include "tls.h"
#include <asm/ldt.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <asm/prctl.h>
#include <sys/syscall.h> 
#include <unistd.h> 

void * install_tls() {
  void *addr = mmap(0, 4096, PROT_READ|PROT_WRITE,
                       MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  if (syscall(SYS_arch_prctl,ARCH_SET_GS, addr) < 0) 
      return NULL;

   return addr;
}

void freeTLS() {
    void *addr;
    syscall(SYS_arch_prctl,ARCH_GET_GS, &addr);  
    munmap(addr, 4096);
}

bool set_tls_value(int idx, unsigned long val) {
    if (idx < 0 || idx >= 4096/8) {
      return false;
    }
    asm volatile(
        "movq %0, %%gs:(%1)\n"
        :
        : "q"((void *)val), "q"(8ll * idx));
    return true;
}


unsigned long get_tls_value(int idx) {
    long long rc;
    if (idx < 0 || idx >= 4096/8) {
      return 0;
    }
    asm volatile(
        "movq %%gs:(%1), %0\n"
        : "=q"(rc)
        : "q"(8ll * idx));
    return rc;
  }

这是带有一些宏的标题:

#ifndef TLS_H
#define TLS_H

#include <stdbool.h>

void *install_tls(); 
void freeTLS();
bool set_tls_value (int, unsigned long); 
unsigned long get_tls_value(int ); 

/*
 *macros used to set and retrieve the values 
 from the tls area
*/ 

#define TLS_TID 0x0
#define TLS_FD  0x8 
#define TLS_MONITORED 0x10

#define set_local_tid(_x) \
    set_tls_value(TLS_TID, (unsigned long)_x)

#define set_local_fd(_x) \
    set_tls_value(TLS_FD, (unsigned long)_x)

#define set_local_monitored(_x) \
    set_tls_value(TLS_MONITORED, (unsigned long)_x)

#define get_local_tid() \
    get_tls_value(TLS_TID)

#define get_local_fd() \
    get_tls_value(TLS_FD)

#define get_local_monitored() \
    get_tls_value(TLS_MONITORED)



#endif /* end of include guard: TLS_H */

每个线程要完成的第一个动作是安装 TLS 内存区域。一旦 TLS 被初始化,每个线程都可以开始使用这个区域作为私有 TLS。

【讨论】:

  • 感谢您的意见。我实际上还没有尝试修复我的实现,但是这个答案包含一些有用的信息。我将不得不度过一个周末,并尽快解决这个问题。 :)
猜你喜欢
  • 2020-08-27
  • 1970-01-01
  • 1970-01-01
  • 2014-06-01
  • 2011-01-04
  • 2021-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多