Linux(x86_64)のpagingをちゃんと理解したいな〜ということで。見ているのはLinuxカーネル3.15。
まずはx86_64アーキテクチャのページングの仕組みとLinuxでの対応(どんな型で表すのか)を調べる。
| name | bits | c type | |----------- ----|-------|--------| | PML4 | 39-47 | pgd_t | | Directory Ptr | 30-38 | pud_t | | Directory | 21-29 | pmd_t | | Table | 12-20 | pte_t | | Offset | 0-11 | |
PML4に対応するのはPage Global Directoryのpgd_t。これはどのようなタイミングで設定するのか?
PGDを作るのはpgd_alloc()でこれは以下のような流れでexecveシステムコールの発行時にPGDは作成されている。fork()実行時じゃなくてexecve()の時なのはCOWのためだからでしょうね。
sys_execve() @fs/exec.c
-> do_execve() @fs/exec.c
-> do_execve_common() @fs/exec.c
-> bprm_mm_init() @fs/exec.c
-> mm_alloc() @kernel/fork.c
-> mm_init() @kernel/fork.c
-> mm_alloc_pgd() @kernel/fork.c
-> pgd_alloc() @arch/x86/mm/pgtable.c
コードをいくつか見ていきます。pgd_alloc()のプロトタイプは下記の通りでmm構造体が渡ってきます。これはどんなふうになっているのでしょう(親プロセスからコピーしたもの?新規作成?)
pgd_t *pgd_alloc(struct mm_struct *mm)
mm_alloc()にあるmm_alloc()を確認するとmmをallocate_mm()で新規に作っています。allocate_mm()はスラブキャッシュからmm_strcut用のメモリを確保します。
578 /* 579 * Allocate and initialize an mm_struct. 580 */ 581 struct mm_struct *mm_alloc(void) 582 { 583 struct mm_struct *mm; 584 585 mm = allocate_mm(); 586 if (!mm) 587 return NULL; 588 589 memset(mm, 0, sizeof(*mm)); 590 mm_init_cpumask(mm); 591 return mm_init(mm, current); 592 }
ここで作ったmmはmm_init()にcurrentと一緒に渡します。
mm_init()では受けったmmを設定していきます。currentタスクは変数pですが、これが使われるのはmm_init_owner()だけですが、CONFIG_MM_OWNERが定義されていない場合は何もしません。ということで、ここは完全に新規のプロセス用にmmが設定されていきます(当たり前といえば当たり前か)。552行目にmm_alloc_pgd()の呼び出しが合ってこれに成功すれば設定したmmをreturnして関数は終了です。
530 static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p) 531 { 532 atomic_set(&mm->mm_users, 1); 533 atomic_set(&mm->mm_count, 1); 534 init_rwsem(&mm->mmap_sem); 535 INIT_LIST_HEAD(&mm->mmlist); 536 mm->core_state = NULL; 537 atomic_long_set(&mm->nr_ptes, 0); 538 memset(&mm->rss_stat, 0, sizeof(mm->rss_stat)); 539 spin_lock_init(&mm->page_table_lock); 540 mm_init_aio(mm); 541 mm_init_owner(mm, p); 542 clear_tlb_flush_pending(mm); 543 544 if (current->mm) { 545 mm->flags = current->mm->flags & MMF_INIT_MASK; 546 mm->def_flags = current->mm->def_flags & VM_INIT_DEF_MASK; 547 } else { 548 mm->flags = default_dump_filter; 549 mm->def_flags = 0; 550 } 551 552 if (likely(!mm_alloc_pgd(mm))) { 553 mmu_notifier_mm_init(mm); 554 return mm; 555 } 556 557 free_mm(mm); 558 return NULL; 559 }
mm_alloc_pgd()はpgd_alloc()を呼び出してエラーチェックをするぐらいなので飛ばして、pgd_alloc()を見ましょう。
274 pgd_t *pgd_alloc(struct mm_struct *mm) 275 { 276 pgd_t *pgd; 277 pmd_t *pmds[PREALLOCATED_PMDS]; 278
PREALLOCATED_PMDSはいくつかのdefineを調べた先にあり、arch/x86/include/asm/pgtable_64_types.hで定義されています。大方の予想通りで512ですね。
29 #define PTRS_PER_PGD 512
では引き続きpgd_alloc()を。pgdのメモリは__get_free_page()で1ページ分の領域を確保します。8byteのアドレスを512個保存するのできっかり1ページ(4096KiB)ですね。
そしてmm構造体のpgdメンバ変数に代入します。
279 pgd = (pgd_t *)__get_free_page(PGALLOC_GFP); 280 281 if (pgd == NULL) 282 goto out; 283 284 mm->pgd = pgd; 285
preallocate_pmds()はPage Middle Directoryの領域を確保します。ここで疑問なのはPage Upper DirectoryじゃなくてPMDってことなんだけどpreという接頭詞も付いているしとりあえず先に進む。
286 if (preallocate_pmds(pmds) != 0) 287 goto out_free_pgd; 288
次のparavirt_pgd_alloc()はCONFIG_PARAVIRTのy/nで動作が変わるんだけど、arch.confgにはあるのでyの場合を見る。
289 if (paravirt_pgd_alloc(mm) != 0) 290 goto out_free_pmds; 291
CONFIG_PARAVIRT=yの場合、paravirt_pgd_alloc()はarch/x86/include/asm/paravirt.hにある処理が使われるのですが、マクロにつぐマクロでよく分かりません/(^o^)\
365 static inline int paravirt_pgd_alloc(struct mm_struct *mm) 366 { 367 return PVOP_CALL1(int, pv_mmu_ops.pgd_alloc, mm); 368 }
といっても大体は想像できて、pv_mmu_ops構造体の関数ポインタな変数pgd_allocに設定されているintを返す関数にmmを引数として渡すんじゃね?っと考えられます。そこで、愛用しているlxr.free-electrons.comでpv_mmu_ops.pgd_allocをfree text searchで検索します。 そして見つかったのがarch/x86/kernel/paravirt.cにあったこれです。__paravirt_pgd_alloc()を見れば良さそうですね。
406 struct pv_mmu_ops pv_mmu_ops = { 407 408 .read_cr2 = native_read_cr2, 409 .write_cr2 = native_write_cr2, 410 .read_cr3 = native_read_cr3, 411 .write_cr3 = native_write_cr3, 412 413 .flush_tlb_user = native_flush_tlb, 414 .flush_tlb_kernel = native_flush_tlb_global, 415 .flush_tlb_single = native_flush_tlb_single, 416 .flush_tlb_others = native_flush_tlb_others, 417 418 .pgd_alloc = __paravirt_pgd_alloc,
__paravirt_pgd_alloc()を見て見ると、あれ、0を返すだけ。。。
8 static inline int __paravirt_pgd_alloc(struct mm_struct *mm) { return 0; }
ちなみにを展開していくと最後に行き着くのが↓に貼った____PVOP_CALLマクロです。ここの処理はparavirtって付いているし、準仮想化に絡んだ処理みたいだしそんなに気にしなくても良いのか?
538 #define ____PVOP_CALL(rettype, op, clbr, call_clbr, extra_clbr, \ 539 pre, post, ...) \ 540 ({ \ 541 rettype __ret; \ 542 PVOP_CALL_ARGS; \ 543 PVOP_TEST_NULL(op); \ 544 /* This is 32-bit specific, but is okay in 64-bit */ \ 545 /* since this condition will never hold */ \ 546 if (sizeof(rettype) > sizeof(unsigned long)) { \ 547 asm volatile(pre \ 548 paravirt_alt(PARAVIRT_CALL) \ 549 post \ 550 : call_clbr \ 551 : paravirt_type(op), \ 552 paravirt_clobber(clbr), \ 553 ##__VA_ARGS__ \ 554 : "memory", "cc" extra_clbr); \ 555 __ret = (rettype)((((u64)__edx) << 32) | __eax); \ 556 } else { \ 557 asm volatile(pre \ 558 paravirt_alt(PARAVIRT_CALL) \ 559 post \ 560 : call_clbr \ 561 : paravirt_type(op), \ 562 paravirt_clobber(clbr), \ 563 ##__VA_ARGS__ \ 564 : "memory", "cc" extra_clbr); \ 565 __ret = (rettype)__eax; \ 566 } \ 567 __ret; \ 568 })
では元に戻ります。pgd_ctor()はswapper_pg_dirのデータをmemcpyでコピーし、mappingの更新とpgdをpgd_listというpgdを管理しているリストにつなげます。実際にはpgd先頭の仮想アドレスからpage構造体にしてpage構造体を登録ですが。
292 /* 293 * Make sure that pre-populating the pmds is atomic with 294 * respect to anything walking the pgd_list, so that they 295 * never see a partially populated pgd. 296 */ 297 spin_lock(&pgd_lock); 298 299 pgd_ctor(mm, pgd);
最後の処理はpgd_prepopulate_pmd()ですね。これが終わるとすべて完了です。
300 pgd_prepopulate_pmd(mm, pgd, pmds); 301 302 spin_unlock(&pgd_lock); 303 304 return pgd; 305 306 out_free_pmds: 307 free_pmds(pmds); 308 out_free_pgd: 309 free_page((unsigned long)pgd); 310 out: 311 return NULL; 312 }
pgd_prepopulate_pmd()はこのようになってます。ここでPUDが出てきてます。
pgdからpudの先頭アドレスを最初に持ってきて、512回のループでpmdにアドレスが設定されていきます。ここではswapper_pg_dirに設定されているアドレスが使用されます。
253 static void pgd_prepopulate_pmd(struct mm_struct *mm, pgd_t *pgd, pmd_t *pmds[]) 254 { 255 pud_t *pud; 256 int i; 257 258 if (PREALLOCATED_PMDS == 0) /* Work around gcc-3.4.x bug */ 259 return; 260 261 pud = pud_offset(pgd, 0); 262 263 for (i = 0; i < PREALLOCATED_PMDS; i++, pud++) { 264 pmd_t *pmd = pmds[i]; 265 266 if (i >= KERNEL_PGD_BOUNDARY) 267 memcpy(pmd, (pmd_t *)pgd_page_vaddr(swapper_pg_dir[i]), 268 sizeof(pmd_t) * PTRS_PER_PMD); 269 270 pud_populate(mm, pud, pmd); 271 } 272 }
ところで、swapper_pg_dirはx86_64ではarch/x86/include/asm/pgtable_64.hでinit_level4_pgtにdefineされてます。
24 #define swapper_pg_dir init_level4_pgt
pmdの要素を設定するたびにpud_populate()が呼ばれます。set_pud()はさきほどのpgd_alloc()と同様にPVOP_CALLマクロを使っていますがarch/x86/include/asm/pgtable_64.hのnative_set_pud()が呼ばれます。演っていることとしては右側の引数をpudpに代入するだけです。処理としては__pud()にありますが、これもマクロでめんどい。。まあ、pmdの物理アドレを取得して、そのアドレスにPresetnビットを立ててたものをpudに設定したいだけだと思います。
170 void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmd) 171 { 172 paravirt_alloc_pmd(mm, __pa(pmd) >> PAGE_SHIFT); 173 174 /* Note: almost everything apart from _PAGE_PRESENT is 175 reserved at the pmd (PDPT) level. */ 176 set_pud(pudp, __pud(__pa(pmd) | _PAGE_PRESENT)); 177 178 /* 179 * According to Intel App note "TLBs, Paging-Structure Caches, 180 * and Their Invalidation", April 2007, document 317080-001, 181 * section 8.1: in PAE mode we explicitly have to flush the 182 * TLB via cr3 if the top-level pgd is changed... 183 */ 184 flush_tlb_mm(mm); 185 }
そして最後はflush_tlb_mm()で〆る。これも相変わらずいくつかのマクロをくぐり抜けた先に本当の関数が出てきます。arch/x86/include/asm/tlbflush.hの__native_flush_tlb()がcr3を読みだした結果を書き戻す感じですね。
18 static inline void __native_flush_tlb(void) 19 { 20 native_write_cr3(native_read_cr3()); 21 } 22
native_read_cr3()とnative_write_cr3()はarch/x86/include/asm/special_insns.hにあります。
45 static inline unsigned long native_read_cr3(void) 46 { 47 unsigned long val; 48 asm volatile("mov %%cr3,%0\n\t" : "=r" (val), "=m" (__force_order)); 49 return val; 50 } 51 52 static inline void native_write_cr3(unsigned long val) 53 { 54 asm volatile("mov %0,%%cr3": : "r" (val), "m" (__force_order)); 55 } 56
TLBのフラッシュ時にはmmを使ってませんがmmはflush_tlb_mm()で今動いているプロセスのメモリ空間が変更したmmと同じ場合のみフラッシュするためのチェックとして使います。
116 static inline void flush_tlb_mm(struct mm_struct *mm) 117 { 118 if (mm == current->active_mm) 119 __flush_tlb_up(); 120 }
ここまでがpgdの設定で逆にpgdを取得したい場合はpgd_offsetマクロを使う。
使い方は単純で、mm構造体とアドレスを渡すだけ。
648 /* 649 * pgd_offset() returns a (pgd_t *) 650 * pgd_index() is used get the offset into the pgd page's array of pgd_t's; 651 */ 652 #define pgd_offset(mm, address) ((mm)->pgd + pgd_index((address)))
pgd_indexはこのようなマクロでPGDIR_SHIFT分の右シフトでPML4の部分の値を取って、それと511との&を取ることで配列のオフセットを取得。
640 /* 641 * the pgd page can be thought of an array like this: pgd_t[PTRS_PER_PGD] 642 * 643 * this macro returns the index of the entry in the pgd page which would 644 * control the given virtual address 645 */ 646 #define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
(´-`).。oO(次はpudの使われ方でも見よう

コンピュータアーキテクチャ技術入門 ~高速化の追求×消費電力の壁 (WEB+DB PRESS plus)
- 作者: Hisa Ando
- 出版社/メーカー: 技術評論社
- 発売日: 2014/05/01
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る