私は、非常に小規模で静的にリンクされたプログラム用に libc の小さなサブセットを実装していますが、TLS サポートを追加することは良い学習体験になると考えました。
素晴らしいアイデア!pthread のような一般的なスレッド ライブラリを使用できなかったため、プロジェクトに独自の TLS を実装する必要がありました。あなたの問題に対する完全な解決策はありませんが、私の経験を共有することは役に立ちます.
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 として使用できるようになります。