読者です 読者をやめる 読者になる 読者になる

Linux x86_64のPaging:Page Global Directory辺りの扱いを見てみる

linux kernel x86_64

Linuxx86_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の使われ方でも見よう