ローカルディスクリプタテーブル(LDT)
Rev.1を表示中。最新版はこちら。
ローカルディスクリプタテーブル(以降LDT)は、タスク毎にGDTの相当するディスクリプタを持たせることで、タスク間のセキュリティを高めると言うものです。すべてのタスクは、GDTをルートディスクリプタとして共通に使う事になります。LDTはGDT内のあるエントリを、LDTディスクリプタと割り当てることで、そしてタスクスイッチング毎にそのディスクリプタを更新することで、タスク毎のLDTを実装しています。間接的なディスクリプタともいえます。(CPUレベルでの話しです。)セグメントは16ビットで、下位の3ビットは属性で、以降上位ビットがセレクタ値となります。この第3ビット目がGDT/LDTのフラグになります。すなわち0000000000001000と0000000000001100は、両者ともセレクタ値は1ですが、前者はGDTを、後者はLDTを示すことになります。
セグメントがLDTを示す場合、ldtrレジスタが、GDT内のLDTディスクリプタのセレクタとなっています。そこからLDTを取得し、そのLDT内のエントリからディスクリプタテーブルを取得するわけです。なお、タスク切り替え毎に、ldtrレジスタとGDT内のエントリを切り替える必要があります。X86ではtssという(これもディスクリプタ)タスク切り替え機能が用意されていて、これらの処理はハード的に行ってくれます。ただし、Linuxではtssは使っておらず、従って上記の処理はカーネルが行っています。
Linuxではタスク毎にページテーブルを持たせる事で、タスク間のセキュリティを持たせています。(あるタスクが他のタスクのメモリ空間を侵さない。)、またこの辺りのところはハードウエアに依存するところで、移植性の観点から等で、linuxではLDTを利用していないのだと、無理やり解釈しています。
なお、Linuxでは他のOS環境を構築するということで、LDTを操作する機能がインプリメントされていることのようです。
タスクスイッチングの一連の処理の中のcontext_switch()から、switch_mm()がコールされます。そこでLDTが切り替えタスクと異なるなら、 load_LDT_nolock()をコールすることでLDTを更新しています。
mm_strcutのmm_context_tのメンバーldtにLDTを有しています。このLDTをload_LDT_nolock()は最終的にnative_set_ldt()でGDTに設定しています。
set_tssldt_descriptor()はldtディスクリプタを作成します。引数のaddrはm_context_tのメンバーldtです。DESC_LDTはディスクリプタタイプ、entries * LDT_ENTRY_SIZE - 1はLDTが有しているエントリーの数です。これで設定したldtを、write_gdt_entry()でGDTに設定します。そして最後にアセンブラのldt命令で、ldtレジスタにGDT内のLDTのセレクタ値を設定します。
static inline void load_LDT_nolock(mm_context_t *pc) { set_ldt(pc->ldt, pc->size); } static inline void native_set_ldt(const void *addr, unsigned int entries) { if (likely(entries == 0)) asm volatile("lldt %w0"::"q" (0)); else { unsigned cpu = smp_processor_id(); ldt_desc ldt; set_tssldt_descriptor(&ldt, (unsigned long)addr, DESC_LDT, entries * LDT_ENTRY_SIZE - 1); write_gdt_entry(get_cpu_gdt_table(cpu), GDT_ENTRY_LDT, &ldt, DESC_LDT); asm volatile("lldt %w0"::"q" (GDT_ENTRY_LDT*8)); } }