無料Wikiサービス | デモページ
検索

アクセス数
最近のコメント
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
はじめ - ノース
はじめ - 楽打連動ユーザー
はじめ - 楽打連動ユーザー
Adsense
広告情報が設定されていません。

スレッドローカルストレージ(TLS)その2


int get_thread_area(struct user_desc *u_info)はu_info->entry_numberで指定するTLSディスクリプタテーブルを返します。各プロセスのTLSディスクリプタは、プロセスディスクリプタのthread.tls_array[]に有しています。(プロセス切り替え毎に、これをGDTに設定します。)

do_get_thread_area()は、fill_user_desc()をコールして、取得したTLSディスクリプタテーブルをユーザ引数のu_infoに設定しているだけです。
int do_get_thread_area(struct task_struct *p, int idx,
                      struct user_desc __user *u_info)
{
       struct user_desc info;

       if (idx == -1 && get_user(idx, &u_info->entry_number))
               return -EFAULT;

       if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)
               return -EINVAL;

       fill_user_desc(&info, idx,
                      &p->thread.tls_array[idx - GDT_ENTRY_TLS_MIN]);

       if (copy_to_user(u_info, &info, sizeof(info)))
               return -EFAULT;
       return 0;
}
fill_user_desc()はstruct desc_structからstruct user_descに再編集しているだけです。
static void fill_user_desc(struct user_desc *info, int idx,
                          const struct desc_struct *desc)

{
       memset(info, 0, sizeof(*info));
       info->entry_number = idx;
       info->base_addr = get_desc_base(desc);
       info->limit = get_desc_limit(desc);
       info->seg_32bit = desc->d;
       info->contents = desc->type >> 2;
       info->read_exec_only = !(desc->type & 2);
       info->limit_in_pages = desc->g;
       info->seg_not_present = !desc->p;
       info->useable = desc->avl;
#ifdef CONFIG_X86_64
       info->lm = desc->l;
#endif
}
struct desc_structは以下のフォーマットをしていて、CPUのフォーマットに準じています。(ちなみに、たぶん以下のような感じ・・・)
limit0サイズ
base0ベースアドレス
base1ベースアドレス
typeセグメントの種類(読み出し/読み書き/実行用とか)
sセグメントかそうでないか(ゲートなんかがあります。)
dp特権レベル
pセグメントがメモリが存在する。スワップはページ単位のためlinuxでは常に1
limitセグメントサイズ
avlLinuxでは使用していない
lたぶんページサイズ(4Kか4M)
dオペランドのサイズが16ビットか32ビットか
gセグメントサイズは4K倍される
base2ベースアドレス
ディスクリプタのベースは32ビット、リミットは20ビットで、ディスクリプタテーブル内で連続する並びでアサインされていません。そのためbase0+base1+base2=32とlimit0+limit1=20と言う事です。
(リミットは20ビットでも、Gビットをセットするkとで、4Gとなります。Linuxではカーネル/ユーザ用セレクタとしてベースは0で、セグメントサイズが4Gとなるように設定されています。)
struct desc_struct {
       union {
               struct {
                       unsigned int a;
                       unsigned int b;
               };
               struct {
                       u16 limit0;
                       u16 base0;
                       unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
                       unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
               };
       };
} __attribute__((packed));
gccでTLSを使った時の、それぞれのスレッドのディスクリプタテーブルを、do_get_thread_area()で調べて見ました。ところでget_thread_area()はglibでインプリメントされていません。本システムコールの使用に当たってはsyscall()で行う必要があります。

下記サンプルはベースアドレス、セグメントサイズ、Gビット(limit_in_pagesに設定される。)、および変数のリニアアドレスを表示しています。(論理アドレスとリニアアドレスですが、セグメントとそのオフセットの構成を論理アドレス、それをセグメントのベースからのオフセットをリニアアドレスと理解していますが・・・)
#include <stdio.h>
#include <pthread.h>
#include <linux/unistd.h>
#include <asm/ldt.h>
#include <sys/syscall.h>

__thread    int a = 0;
            int b = 0;

void *func(void *arg)
{
   int i;
   struct user_desc u_info;
   for (i = 6; i <=8; i++) {
       u_info.entry_number = i;
       syscall(SYS_get_thread_area, &u_info);
       printf("base=%x limit=%x g=%d off1=%x 0ff2=%x\n",
               u_info.base_addr, u_info.limit, u_info.limit_in_pages, &a, &b);
   }
}

int main()
{
   pthread_t tid0, tid1;

   pthread_create(&tid0, NULL, func, NULL);
   pthread_create(&tid1, NULL, func, NULL);
   pthread_join(tid0, NULL);
   pthread_join(tid1, NULL);
}

[root@localhost kitamura]# ./a.out
base=b770ab70 limit=fffff g=1 off1=b770ab6c 0ff2=8049894
base=0        limit=0     g=0 off1=b770ab6c 0ff2=8049894
base=0        limit=0     g=0 off1=b770ab6c 0ff2=8049894
base=b6d09b70 limit=fffff g=1 off1=b6d09b6c 0ff2=8049894
base=0        limit=0     g=0 off1=b6d09b6c 0ff2=8049894
base=0        limit=0     g=0 off1=b6d09b6c 0ff2=8049894
上記の結果から、gccではGDTテーブルの6番目(セグメントで言えば6×8となります。)をスレッド共通に使っています。なお、TLSディスクリプタテーブルのベースは0でなく(てっきり0と思っていました。)、スレッド毎に異なっています。従ってその__thread int aのリニアアドレスは異なっています。ただし論理アドレスでのオフセットは全てのスレッドで同じです。

Gビットは1ですから、4G 空間をアクセス出来ることになるのですが、4Gの配列でテストしてみたらコンパイラで跳ねられました。



最終更新 2011/06/05 15:51:05 - north
(2011/06/01 01:48:43 作成)