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

linux:x86_64のページング初期化処理周りを読んでみる

x86_64 linux kernel

Linuxカーネルの解説本は何種類か出版されているけど大概はx86_32が対象なんですよね。ということでx86_32を基本として大まかな内容は解説本を参考にしつつ、実際のx86_64の処理はコードを読む必要があるのです。。。 ということでx86_64の場合のページングの初期化部分のコードを読んでみる。

ページング設定に着た時点でx86_64のlong modeに入っているんだけど、ここまでの流れは前にlinuxのlong modeへの移行処理にという記事で書いているので興味のある人はそちらもどうぞm(__)m

見ているカーネルのバージョンは3.13です。

まず、ページングの初期化処理はどこから呼ばれるのかというとinit/main.cのstart_kernel()から。 このstart_kernel()でsetup_arch()を呼んでいるところが該当部分。

512         pr_notice("%s", linux_banner);
513         setup_arch(&command_line);

x86_64の場合、setup_arch()はarch/x86/kernel/setup.cにある。この関数はx86_64、x86_32共通でx86_32固有部分は#ifdef CONFIG_X86_32やCONFIG_X86_64で切り分けている。

839 void __init setup_arch(char **cmdline_p)
840 {
841         memblock_reserve(__pa_symbol(_text),
842                          (unsigned long)__bss_stop - (unsigned long)_text);
843 
844         early_reserve_initrd();
845 
846         /*
847          * At this point everything still needed from the boot loader
848          * or BIOS or kernel text should be early reserved or marked not
849          * RAM in e820. All other memory is free game.
850          */
851 
852 #ifdef CONFIG_X86_32
853         memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));
854         visws_early_detect();
855 
856         /*
857          * copy kernel address range established so far and switch
858          * to the proper swapper page table
859          */
860         clone_pgd_range(swapper_pg_dir     + KERNEL_PGD_BOUNDARY,
861                         initial_page_table + KERNEL_PGD_BOUNDARY,
862                         KERNEL_PGD_PTRS);
863 
864         load_cr3(swapper_pg_dir);
865         __flush_tlb_all();
866 #else
867         printk(KERN_INFO "Command line: %s\n", boot_command_line);
868 #endif
869 
〜略〜

ページングとは関係ないけど、memblock_reserve()で第1引数のアドレスから第2引数のアドレスまでの範囲のメモリ領域を確保したり、x86の場合はページグローバルディレクトリの切り替えが行われたりしている。

それで、実際にページングの初期化処理を呼んでいるのはだいぶ飛んで1149行目。

1145 #ifdef CONFIG_KVM_GUEST
1146         kvmclock_init();
1147 #endif
1148 
1149         x86_init.paging.pagetable_init();

これはx86_initはx86の初期化に使う関数を設定している構造体。ファイルはarch/x86/kernel/x86_init.cでページングの初期化にはnative_pagetable_init()とい関数が設定されている。

 33 /*
 34  * The platform setup functions are preset with the default functions
 35  * for standard PC hardware.
 36  */
 37 struct x86_init_ops x86_init __initdata = {
 38 
 39         .resources = {
 40                 .probe_roms             = probe_roms,
 41                 .reserve_resources      = reserve_standard_io_resources,
 42                 .memory_setup           = default_machine_specific_memory_setup,
 43         },
 44 
 45         .mpparse = {
 46                 .mpc_record             = x86_init_uint_noop,
 47                 .setup_ioapic_ids       = x86_init_noop,
 48                 .mpc_apic_id            = default_mpc_apic_id,
 49                 .smp_read_mpc_oem       = default_smp_read_mpc_oem,
 50                 .mpc_oem_bus_info       = default_mpc_oem_bus_info,
 51                 .find_smp_config        = default_find_smp_config,
 52                 .get_smp_config         = default_get_smp_config,
 53         },
 54 
 55         .irqs = {
 56                 .pre_vector_init        = init_ISA_irqs,
 57                 .intr_init              = native_init_IRQ,
 58                 .trap_init              = x86_init_noop,
 59         },
 60 
 61         .oem = {
 62                 .arch_setup             = x86_init_noop,
 63                 .banner                 = default_banner,
 64         },
 65 
 66         .paging = {
 67                 .pagetable_init         = native_pagetable_init,
 68         },
 69 
 70         .timers = {
 71                 .setup_percpu_clockev   = setup_boot_APIC_clock,
 72                 .tsc_pre_init           = x86_init_noop,
 73                 .timer_init             = hpet_time_init,
 74                 .wallclock_init         = x86_init_noop,
 75         },
 76 
 77         .iommu = {
 78                 .iommu_init             = iommu_init_noop,
 79         },
 80 
 81         .pci = {
 82                 .init                   = x86_default_pci_init,
 83                 .init_irq               = x86_default_pci_init_irq,
 84                 .fixup_irqs             = x86_default_pci_fixup_irqs,
 85         },
 86 };

native_pagetable_init()はどんなものかとういと、arch/x86/include/asm/pgtable_types.hで設定があって、x86x86_64かで使うものが変わる。

354 #ifdef CONFIG_X86_32
355 extern void native_pagetable_init(void);
356 #else
357 #define native_pagetable_init        paging_init
358 #endif

x86_64の場合はpaging_init()になり、arch/x86/mm/init_64.cに実体がある。

650 void __init paging_init(void)
651 {
652         sparse_memory_present_with_active_regions(MAX_NUMNODES);
653         sparse_init();
654 
655         /*
656          * clear the default setting with node 0
657          * note: don't use nodes_clear here, that is really clearing when
658          *       numa support is not compiled in, and later node_set_state
659          *       will not set it back.
660          */
661         node_clear_state(0, N_MEMORY);
662         if (N_MEMORY != N_NORMAL_MEMORY)
663                 node_clear_state(0, N_NORMAL_MEMORY);
664 
665         zone_sizes_init();
666 }
667 

これは短い関数なんだけど、使用している関数の内容はちゃんと調べないといけないので次は(sparse_memory_present_with_active_regions())http://lxr.free-electrons.com/source/mm/page_alloc.c#L4380から調べよう。

ちなみにx86_32の場合のnative_pagetable_init()はこのような関数でx86_64よりも内容がわかりやすい。

452 void __init native_pagetable_init(void)
453 {
454         unsigned long pfn, va;
455         pgd_t *pgd, *base = swapper_pg_dir;
456         pud_t *pud;
457         pmd_t *pmd;
458         pte_t *pte;
459 
460         /*
461          * Remove any mappings which extend past the end of physical
462          * memory from the boot time page table.
463          * In virtual address space, we should have at least two pages
464          * from VMALLOC_END to pkmap or fixmap according to VMALLOC_END
465          * definition. And max_low_pfn is set to VMALLOC_END physical
466          * address. If initial memory mapping is doing right job, we
467          * should have pte used near max_low_pfn or one pmd is not present.
468          */
469         for (pfn = max_low_pfn; pfn < 1<<(32-PAGE_SHIFT); pfn++) {
470                 va = PAGE_OFFSET + (pfn<<PAGE_SHIFT);
471                 pgd = base + pgd_index(va);
472                 if (!pgd_present(*pgd))
473                         break;
474 
475                 pud = pud_offset(pgd, va);
476                 pmd = pmd_offset(pud, va);
477                 if (!pmd_present(*pmd))
478                         break;
479 
480                 /* should not be large page here */
481                 if (pmd_large(*pmd)) {
482                         pr_warn("try to clear pte for ram above max_low_pfn: pfn: %lx pmd: %p pmd phys: %lx, but pmd is big page and is not using pte !\n",
483                                 pfn, pmd, __pa(pmd));
484                         BUG_ON(1);
485                 }
486 
487                 pte = pte_offset_kernel(pmd, va);
488                 if (!pte_present(*pte))
489                         break;
490 
491                 printk(KERN_DEBUG "clearing pte for ram above max_low_pfn: pfn: %lx pmd: %p pmd phys: %lx pte: %p pte phys: %lx\n",
492                                 pfn, pmd, __pa(pmd), pte, __pa(pte));
493                 pte_clear(NULL, va, pte);
494         }
495         paravirt_alloc_pmd(&init_mm, __pa(base) >> PAGE_SHIFT);
496         paging_init();
497 }

と思ったら大間違いで、最後に呼び出しているpaging_init()を見るとsparse_memory_present_with_active_regions()、sparse_init()、zone_sizes_init()はここでも使われていることがわかり、やっぱりこれらを調べないとダメというのがわかる\(^o^)/

695  * that we can trap those pesky NULL-reference errors in the kernel.
696  */
697 void __init paging_init(void)
698 {
699         pagetable_init();
700 
701         __flush_tlb_all();
702 
703         kmap_init();
704 
705         /*
706          * NOTE: at this point the bootmem allocator is fully available.
707          */
708         olpc_dt_build_devicetree();
709         sparse_memory_present_with_active_regions(MAX_NUMNODES);
710         sparse_init();
711         zone_sizes_init();
712 }

(´-`).。oO(ただ、x86_32でも使っているということはどこかで解説されている可能性もあるかも

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版