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

linux:sparse_init()で細かいメモリ領域の断片達を管理できるように

linux x86_64 kernel

paging_init()の2行目、sparse_init()のところを読む。

sparse_init()の関数にあるコメントを見るとやろうとしていることはなんとなくわかる。 ここで言っているaccumulated non-linear sectionsというのは前回までの記事で見てきた部分で、e820のメモリマップを取得して開いているところカーネルが管理できるようしたりしてた部分。

514 /*
515  * Allocate the accumulated non-linear sections, allocate a mem_map
516  * for each and record the physical to section mapping.
517  */
518 void __init sparse_init(void)

では、実際に関数を見ていく。 最初は変数定義なので飛ばして。。。

520         unsigned long pnum;
521         struct page *map;
522         unsigned long *usemap;
523         unsigned long **usemap_map;
524         int size;
525 #ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
526         int size2;
527         struct page **map_map;
528 #endif
529

ここもまあ良いでしょう。

530         /* see include/linux/mmzone.h 'struct mem_section' definition */
531         BUILD_BUG_ON(!is_power_of_2(sizeof(struct mem_section)));
532 

ここではメモリアローケータが最大で何ページ分連続した領域を確保できるかのリミットを設定。この関数はCONFIG_HUGETLB_PAGE_SIZE_VARIABLEがyの時はこの関数内でオーダーの設定をするんだけど、CONFIG_HUGETLB_PAGE_SIZE_VARIABLEがnの場合はオーダーは10とコンパイル時に決定される。

533         /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
534         set_pageblock_order();

ここは見たままと言えば見たままですな。現段階では本来のメモリーアローケーターが初期化されてないのでalloc_bootmem()が使われるという程度。ブートプロセス、ブート完了後どちらからでも呼ばれるような関数だとslab_is_available()を使って使用するアローケーターを変えたりもしている模様。

536         /*
537          * map is using big page (aka 2M in x86 64 bit)
538          * usemap is less one page (aka 24 bytes)
539          * so alloc 2M (with 2M align) and 24 bytes in turn will
540          * make next 2M slip to one more 2M later.
541          * then in big system, the memory will have a lot of holes...
542          * here try to allocate 2M pages continuously.
543          *
544          * powerpc need to call sparse_init_one_section right after each
545          * sparse_early_mem_map_alloc, so allocate usemap_map at first.
546          */
547         size = sizeof(unsigned long *) * NR_MEM_SECTIONS;
548         usemap_map = alloc_bootmem(size);
549         if (!usemap_map)
550                 panic("can not allocate usemap_map\n");

alloc_usemap_and_memmap()は前回の「linux:sparse_init()の前にalloc_usemap_and_memmap()を見ておくで調べた関数。メモリ確保する関数(sparse_early_usemaps_alloc_node())と、データを格納して欲しい変数のアドレスを渡す。 alloc_usemap_and_memmap()は2個の引数しか取らないから忘れがちになるけど、alloc_usemap_and_memmap()内でセクション0からNR_MEM_SECTIONSまでを検索して〜という感じのことをやっているのでデータはalloc_usemap_and_memmap()がセットしてくれる。なんてゆーか、alloc_usemap_and_memmapって名前なのにメモリ確保以上のことをしてくれるからだな。

551         alloc_usemap_and_memmap(sparse_early_usemaps_alloc_node,
552                                                         (void *)usemap_map);
553 

ここで渡すのはpage構造体。Arch Linuxの.configではCONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHERはyなのでここは実行される。

554 #ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
555         size2 = sizeof(struct page *) * NR_MEM_SECTIONS;
556         map_map = alloc_bootmem(size2);
557         if (!map_map)
558                 panic("can not allocate map_map\n");
559         alloc_usemap_and_memmap(sparse_early_mem_maps_alloc_node,
560                                                         (void *)map_map);
561 #endif
562

セクション数のループで、セクション番号が存在しなければcontinueという今までも見てきたチェックがここでも。

563         for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
564                 if (!present_section_nr(pnum))
565                         continue;
566 

インデックスpnumでusemap_mapにデータがあるかチェック。

567                 usemap = usemap_map[pnum];
568                 if (!usemap)
569                         continue;
570 

セクション番号pnumに該当するpage構造体の取得。

571 #ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
572                 map = map_map[pnum];
573 #else
574                 map = sparse_early_mem_map_alloc(pnum);
575 #endif
576                 if (!map)
577                         continue;
578 

pageがあったらsparse_init_one_section()を呼ぶ。内容は後で。

579                 sparse_init_one_section(__nr_to_section(pnum), pnum, map,
580                                                                 usemap);
581         }
582 

vmemmap_populate_print_last()はログレベルをKERN_DEBUGでprintk()を使っているので通常のディストリビューションだとログは出ないですね。 最後に使った領域を開放して終わり。

583         vmemmap_populate_print_last();
584 
585 #ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
586         free_bootmem(__pa(map_map), size2);
587 #endif
588         free_bootmem(__pa(usemap_map), size);
589 }

これでsparse_init()が終わりなので、sparse_init_one_section()を見よう。 最初にセクションが存在しているかチェック。

230 static int __meminit sparse_init_one_section(struct mem_section *ms,
231                 unsigned long pnum, struct page *mem_map,
232                 unsigned long *pageblock_bitmap)
233 {
234         if (!present_section(ms))
235                 return -EINVAL;
236 

まあ、ビット操作ですね。

237         ms->section_mem_map &= ~SECTION_MAP_MASK;
238         ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) |
239                                                         SECTION_HAS_MEM_MAP;

ビットの意味はinclude/linux/mmzone.hにある定義を見たほうが早いですね。

1143 /*
1144  * We use the lower bits of the mem_map pointer to store
1145  * a little bit of information.  There should be at least
1146  * 3 bits here due to 32-bit alignment.
1147  */
1148 #define SECTION_MARKED_PRESENT  (1UL<<0)
1149 #define SECTION_HAS_MEM_MAP     (1UL<<1)
1150 #define SECTION_MAP_LAST_BIT    (1UL<<2)
1151 #define SECTION_MAP_MASK        (~(SECTION_MAP_LAST_BIT-1))
1152 #define SECTION_NID_SHIFT       2

sparse_encode_mem_map()はこのような関数でページセクション番号を物理ページのへーじフレーム番号に変換しているらしい。

210 /*
211  * Subtle, we encode the real pfn into the mem_map such that
212  * the identity pfn - section_mem_map will return the actual
213  * physical page frame number.
214  */
215 static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
216 {
217         return (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
218 }
219 

あとはsparse_init()から渡ってきたusemapをpageblock_flagsに代入して保存。

240         ms->pageblock_flags = pageblock_bitmap;
241 
242         return 1;
243 }

これでpaging_init()が2行読み終わった( ´∀`)bグッ!

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版