スレッドローカルストレージ(TLS)その3
TLSは同じ論理アドレスでもプロセス毎に異なるディスクリプタを有することで、異なるリニアアドレスとなり、結果的に独自の領域を持たせるものでした。実は前回の記事を書くにあたって、疑問に思っていたことがあるのです。それはユーザプロセス下(DSセグメント)下でのメモリ空間とその領域が干渉しないか。という事です。私の現在のこの辺りの理解では、リニアアドレスが同じなら同じページテーブルを参照すると言う事です。
論理アドレスからそのセグメント(セレクタ)をGDT内のインデックスとして、ディスクリプタテーブルを参照します。そこでのペースアドレスと論理アドレスのオフセットで、リニアアドレスを算出します。このリニアアドレスの上位20ビットをページテーブルインデックス、下位12ビットをそのオフセットとして、ページングテーブルを参照することで物理アドレスを決定します。なおページング機能が有効の時で、Linuxではもちろん有効です。OSレベルからみると、このリニアアドレス空間を仮想空間とも言うのではと。私の理解です・・・。
すなわち、リニアアドレスが同じなら同じページテーブルを参照することになるわけです。プロセス間では独自にページテーブルを有しているため、実際の物理アドレスが同じになることはありません。しかしTLSについて言うと・・・。確かスレッドも独自のページテーブルを有していたと思います。しかしスレッドの性格上その内容はスレッドグループ間で同じになっているはずです。
スレッドローカルストレージ(TLS)その2ではTLSのリニアアドレスはユーザプロセスの上位に割り当て、しかもベースアドレスを微妙に異なるように割り当てていました。結果的に参照するページテーブルを独自に持たせることが可能なわけです。
TLSのリニアアドレスを上位に割り当てるというのは、たぶんこの辺りはTLS用として予約しているからでないでしょうか? でないと実現する術が無いように思います。そう思って調べてみましたが分かりませんでした。
上の仮定で話を進めるとして、従ってgccで展開する場合、このあたりを考慮して展開してくれる。と期待できます。しかし、set_thread_area()というユーザが、任意のTLSディスクリプタを設定する関数が、システムコールとして用意されています。この関数はユーザの設定した内容を、そのままTLSディスクリプタの設定しているだけで、現在のページテーブルの使用状況は考慮されていません。set_thread_area()で設定できるベースアドレスに何か制限はないのでしょうか?
カーネル空間もアドレッシング可能ですが、特権レベルは常にレベル3(desc->dpl = 0x3)で固定です。従って、属性を実行可能として、その領域にプログラムを埋め込むことで、ユーザスレッドがカーネル空間を参照する事は不可能のようです。
論理アドレスからそのセグメント(セレクタ)をGDT内のインデックスとして、ディスクリプタテーブルを参照します。そこでのペースアドレスと論理アドレスのオフセットで、リニアアドレスを算出します。このリニアアドレスの上位20ビットをページテーブルインデックス、下位12ビットをそのオフセットとして、ページングテーブルを参照することで物理アドレスを決定します。なおページング機能が有効の時で、Linuxではもちろん有効です。OSレベルからみると、このリニアアドレス空間を仮想空間とも言うのではと。私の理解です・・・。
すなわち、リニアアドレスが同じなら同じページテーブルを参照することになるわけです。プロセス間では独自にページテーブルを有しているため、実際の物理アドレスが同じになることはありません。しかしTLSについて言うと・・・。確かスレッドも独自のページテーブルを有していたと思います。しかしスレッドの性格上その内容はスレッドグループ間で同じになっているはずです。
スレッドローカルストレージ(TLS)その2ではTLSのリニアアドレスはユーザプロセスの上位に割り当て、しかもベースアドレスを微妙に異なるように割り当てていました。結果的に参照するページテーブルを独自に持たせることが可能なわけです。
TLSのリニアアドレスを上位に割り当てるというのは、たぶんこの辺りはTLS用として予約しているからでないでしょうか? でないと実現する術が無いように思います。そう思って調べてみましたが分かりませんでした。
上の仮定で話を進めるとして、従ってgccで展開する場合、このあたりを考慮して展開してくれる。と期待できます。しかし、set_thread_area()というユーザが、任意のTLSディスクリプタを設定する関数が、システムコールとして用意されています。この関数はユーザの設定した内容を、そのままTLSディスクリプタの設定しているだけで、現在のページテーブルの使用状況は考慮されていません。set_thread_area()で設定できるベースアドレスに何か制限はないのでしょうか?
カーネル空間もアドレッシング可能ですが、特権レベルは常にレベル3(desc->dpl = 0x3)で固定です。従って、属性を実行可能として、その領域にプログラムを埋め込むことで、ユーザスレッドがカーネル空間を参照する事は不可能のようです。
int do_set_thread_area(struct task_struct *p, int idx, struct user_desc __user *u_info, int can_allocate) { struct user_desc info; if (copy_from_user(&info, u_info, sizeof(info))) return -EFAULT; if (idx == -1) idx = info.entry_number; if (idx == -1 && can_allocate) { idx = get_free_idx(); if (idx < 0) return idx; if (put_user(idx, &u_info->entry_number)) return -EFAULT; } if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX) return -EINVAL; set_tls_desc(p, idx, &info, 1); return 0; } static void set_tls_desc(struct task_struct *p, int idx, const struct user_desc *info, int n) { struct thread_struct *t = &p->thread; struct desc_struct *desc = &t->tls_array[idx - GDT_ENTRY_TLS_MIN]; int cpu; cpu = get_cpu(); while (n-- > 0) { if (LDT_empty(info)) desc->a = desc->b = 0; else fill_ldt(desc, info); ++info; ++desc; } if (t == ¤t->thread) load_TLS(t, cpu); put_cpu(); } static inline void fill_ldt(struct desc_struct *desc, const struct user_desc *info) { desc->limit0 = info->limit & 0x0ffff; desc->base0 = info->base_addr & 0x0000ffff; desc->base1 = (info->base_addr & 0x00ff0000) >> 16; desc->type = (info->read_exec_only ^ 1) << 1; desc->type |= info->contents << 2; desc->s = 1; desc->dpl = 0x3; desc->p = info->seg_not_present ^ 1; desc->limit = (info->limit & 0xf0000) >> 16; desc->avl = info->useable; desc->d = info->seg_32bit; desc->g = info->limit_in_pages; desc->base2 = (info->base_addr & 0xff000000) >> 24; }