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

linux:x86_64のメモリ管理のうちe820のメモリマップを扱っている部分読む

linux kernel x86_64

x86_64のページングを設定している部分を読もうと思って読み始めて前回の続きになるsparse_memory_present_with_active_regions()を読もうと思ったんだけど、この関数で使う変数(struct memblockのメンバ変数のmemory(型はstruct memblock_type))を調べたほうが良さそうだったので、先にこっちを読む。

struct memblockはこのようになっていてstruct memblock_type memoryがメンバ変数にある。 この構造体はざっくりと言ってしまうとメモリのブロックをmemoryに登録して管理している。

 37 struct memblock {
 38         bool bottom_up;  /* is bottom up direction? */
 39         phys_addr_t current_limit;
 40         struct memblock_type memory;
 41         struct memblock_type reserved;
 42 };

で、メモリブロックを登録するのはstruct memblock_typeでこちらはでは領域の情報は保存せず、領域が何個あるとか、最大数とかの情報が置かれる。メモリの範囲が設定されるのはregionsのほう。

 30 struct memblock_type {
 31         unsigned long cnt;      /* number of regions */
 32         unsigned long max;      /* size of the allocated array */
 33         phys_addr_t total_size; /* size of all regions */
 34         struct memblock_region *regions;
 35 };

struct memblock_regionはこのような変数でメモリの開始アドレスとサイズが必ずある。

 22 struct memblock_region {
 23         phys_addr_t base;
 24         phys_addr_t size;
 25 #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
 26         int nid;
 27 #endif
 28 };
 29 

そして、sparse_memory_present_with_active_regions()の段階ではこのstruct membloc.memoryを手繰る処理があるのでこの関数が呼ばれる以前で設定されているということ。

まず、struct memblockだけど、これはmm/memblock.cにあってmemblock_memory_init_regionsという構造体がセットされている。

 28 struct memblock memblock __initdata_memblock = {
 29         .memory.regions         = memblock_memory_init_regions,

この構造体もmm/memblock.cにあり、以下のようになっている。

 25 static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;

そして、これにデータを設定している部分なんだけど、まずはe820のメモリマップが必要になるのでそちらを読んでみる。 これはarch/x86/kernel/setup.cのarch_setup()内でsetup_memory_map()を呼び出しで実行。

922         x86_init.oem.arch_setup();
923 
924         iomem_resource.end = (1ULL << boot_cpu_data.x86_phys_bits) - 1;
925         setup_memory_map();
926         parse_setup_data();

setup_memory_map()はarch/x86/kernel/e820.cにあって、これは割と短い関数。

1062 void __init setup_memory_map(void)
1063 {
1064         char *who;
1065 
1066         who = x86_init.resources.memory_setup();
1067         memcpy(&e820_saved, &e820, sizeof(struct e820map));
1068         printk(KERN_INFO "e820: BIOS-provided physical RAM map:\n");
1069         e820_print_map(who);
1070 }

まあ、実際の処理はx86_init.resources.memory_setup()なのでそこを見る。x86_initは前回も出てきたx86の初期化用の関数が登録されている構造体。memory_setup()はdefault_machine_specific_memory_setup()がセットされていて、これもarch/x86/kernel/e820.cにある。

1024 char *__init default_machine_specific_memory_setup(void)
1025 {
1026         char *who = "BIOS-e820";
1027         u32 new_nr;
〜中略〜
1058         /* In case someone cares... */
1059         return who;
1060 }

ここの中身は飛ばしてsetup_memory_map()に戻ると最後にe820_print_map()でe820のメモリマップを表示する部分があり、これはdmesgで確認できる。 dmesgのこの部分がe820_print_map()で表示している内容。

[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009d7ff] usable
[ 0.000000] BIOS-e820: [mem 0x000000000009d800-0x000000000009ffff] reserved
〜略〜
[ 0.000000] BIOS-e820: [mem 0x0000000078ef8000-0x0000000078ef8fff] ACPI data
[ 0.000000] BIOS-e820: [mem 0x0000000078ef9000-0x00000000797a7fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000797a8000-0x000000007a30dfff] reserved
[ 0.000000] BIOS-e820: [mem 0x000000007a30e000-0x000000007a30efff] usable
[ 0.000000] BIOS-e820: [mem 0x000000007a30f000-0x000000007a351fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x000000007a352000-0x000000007aca1fff] usable
[ 0.000000] BIOS-e820: [mem 0x000000007aca2000-0x000000007afd6fff] reserved
[ 0.000000] BIOS-e820: [mem 0x000000007afd7000-0x000000007affffff] usable
〜略〜
[ 0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000ff000000-0x00000000ffffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000087f5fffff] usable

ここまででe820のメモリマップが取得できたことがわかったので、次はmemblockを調べるほうに戻る。 arch_setup()ではmemblockのreservedにメモリ領域の予約をしている部分もあるけどここは飛ばす。

975         /* after early param, so could get panic from serial */
976         memblock_x86_reserve_range_setup_data();

そしてsetup_arch()をまた読み進めて行ってあたるのがここでmemblock_x86_fill()が探していたもの。

1071         memblock_set_current_limit(ISA_END_ADDRESS);
1072         memblock_x86_fill();

memblock_set_current_limit()は物理アドレスISA_END_ADDRESS以降は触らないようにって設定だと思う。

ではmemblock_x86_fill()を見てみとこれは比較的シンプル!

1072 void __init memblock_x86_fill(void)
1073 {
1074         int i;
1075         u64 end;
1076 
1077         /*
1078          * EFI may have more than 128 entries
1079          * We are safe to enable resizing, beause memblock_x86_fill()
1080          * is rather later for x86
1081          */
1082         memblock_allow_resize();

memblock_allow_resize()はmemblock_can_resizeを1にするだけ。使用方法は名前の通りですね。リサイズ可能かのチェックはmemblock_double_array()で唯一行ってた。

ここからはe820のメモリマップを見ていく。

1083 
1084         for (i = 0; i < e820.nr_map; i++) {
1085                 struct e820entry *ei = &e820.map[i];
1086 
1087                 end = ei->addr + ei->size;

メモリの範囲を計算して、

1088                 if (end != (resource_size_t)end)
1089                         continue;
1090

アドレスの範囲チェック。

1091                 if (ei->type != E820_RAM && ei->type != E820_RESERVED_KERN)
1092                         continue;
1093 

領域がe820のramじゃないとかカーネル予約済みかチェック

1094                 memblock_add(ei->addr, ei->size);

そして、ついにmemblockにデータを登録するmemblock_add()さんの登場。 とりあえず、これはひとまず置いておいて続きを見てしまう。

1095         }
1096 
1097         /* throw away partial pages */
1098         memblock_trim_memory(PAGE_SIZE);
1099 

ループを抜けたらmemblock_trim_memory()の呼び出しがあり、これはe820の各メモリマップの開始・終了アドレスをページ境界に揃えている。

1100         memblock_dump_all();
1101 }
1102 

最後はmemblock_dump_all()でメモリマップの表示だけど、これはカーネルコマンドラインパラメータmemblock=debugがセットされている場合だけ実行。 memblock_x86_fill()の処理は以上なので次はmemblock_add()を見てみる。

これはmemblock_add_region()のラッパーなのでさくっと。

543 int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
544 {
545         return memblock_add_region(&memblock.memory, base, size, MAX_NUMNODES);
546 }

memblock_add_region()は今まで出てきた奴らよりはちょい長めの関数。 関数冒頭にコメントもあり良い感じ。

447 /**
448  * memblock_add_region - add new memblock region
449  * @type: memblock type to add new region into
450  * @base: base address of the new region
451  * @size: size of the new region
452  * @nid: nid of the new region
453  *
454  * Add new memblock region [@base,@base+@size) into @type.  The new region
455  * is allowed to overlap with existing ones - overlaps don't affect already
456  * existing regions.  @type is guaranteed to be minimal (all neighbouring
457  * compatible regions are merged) after the addition.
458  *
459  * RETURNS:
460  * 0 on success, -errno on failure.
461  */
462 static int __init_memblock memblock_add_region(struct memblock_type *type,
463                                 phys_addr_t base, phys_addr_t size, int nid)
464 {

変数宣言をしつつ、メモリの開始アドレス、終了アドレスの設定。

465         bool insert = false;
466         phys_addr_t obase = base;
467         phys_addr_t end = base + memblock_cap_size(base, &size);
468         int i, nr_new;

memblock_cap_size()はこのような関数。

 58 /* adjust *@size so that (@base + *@size) doesn't overflow, return new size */
 59 static inline phys_addr_t memblock_cap_size(phys_addr_t base, phys_addr_t *size)
 60 {
 61         return *size = min(*size, (phys_addr_t)ULLONG_MAX - base);
 62 }

サイズのチェック。コメントからすると特殊ケースっぽい。

473         /* special case for empty array */
474         if (type->regions[0].size == 0) {
475                 WARN_ON(type->cnt != 1 || type->total_size);
476                 type->regions[0].base = base;
477                 type->regions[0].size = size;
478                 memblock_set_region_node(&type->regions[0], nid);
479                 type->total_size = size;
480                 return 0;
481         }

このへんからが本番。 memblock.memoryに登録されているデータ数分のループ。

483         /*
484          * The following is executed twice.  Once with %false @insert and
485          * then with %true.  The first counts the number of regions needed
486          * to accomodate the new area.  The second actually inserts them.
487          */
488         base = obase;
489         nr_new = 0;
490 
491         for (i = 0; i < type->cnt; i++) {

ループの最初は変数定義から。

492                 struct memblock_region *rgn = &type->regions[i];
493                 phys_addr_t rbase = rgn->base;
494                 phys_addr_t rend = rbase + rgn->size;
495 

resions[i]に登録されている(e820の)アドレスチェック

496                 if (rbase >= end)
497                         break;
498                 if (rend <= base)
499                         continue;

regionsに登録されているメモリ開始位置(rbase)がendを超えたらループ終了。

開始アドレスに問題がなければmemblock_insert_region()でデータ登録。

500                 /*
501                  * @rgn overlaps.  If it separates the lower part of new
502                  * area, insert that portion.
503                  */
504                 if (rbase > base) {
505                         nr_new++;
506                         if (insert)
507                                 memblock_insert_region(type, i++, base,
508                                                        rbase - base, nid);
509                 }

baseアドレスを更新してループ冒頭に。

510                 /* area below @rend is dealt with, forget about it */
511                 base = min(rend, end);
512         }

ループを抜けてからの処理。 ループを抜けた状態でbaseがendを超えていなければそれも登録する。

514         /* insert the remaining portion */
515         if (base < end) {
516                 nr_new++;
517                 if (insert)
518                         memblock_insert_region(type, i, base, end - base, nid);
519         }
520

最後にregionのリサイズもしくはマージで終了。ここはとりあえず読まない。

521         /*
522          * If this was the first round, resize array and repeat for actual
523          * insertions; otherwise, merge and return.
524          */
525         if (!insert) {
526                 while (type->cnt + nr_new > type->max)
527                         if (memblock_double_array(type, obase, size) < 0)
528                                 return -ENOMEM;
529                 insert = true;
530                 goto repeat;
531         } else {
532                 memblock_merge_regions(type);
533                 return 0;
534         }
535 }

memblock_insert_region()はデータの登録だけなのでシンプル。

421 /**
422  * memblock_insert_region - insert new memblock region
423  * @type:       memblock type to insert into
424  * @idx:        index for the insertion point
425  * @base:       base address of the new region
426  * @size:       size of the new region
427  * @nid:        node id of the new region
428  *
429  * Insert new memblock region [@base,@base+@size) into @type at @idx.
430  * @type must already have extra room to accomodate the new region.
431  */
432 static void __init_memblock memblock_insert_region(struct memblock_type *type,
433                                                    int idx, phys_addr_t base,
434                                                    phys_addr_t size, int nid)
435 {
436         struct memblock_region *rgn = &type->regions[idx];
437 
438         BUG_ON(type->cnt >= type->max);
439         memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
440         rgn->base = base;
441         rgn->size = size;
442         memblock_set_region_node(rgn, nid);
443         type->cnt++;
444         type->total_size += size;
445 }

ということで、setup_memory_map()でe820のメモリマップを取得し、その中から使える領域をmemblock_x86_fill()で調べつつmemblock_add()でmemblock構造体のmemoryにあるregionsに登録していくというのが 今回見た範囲。

( ´ー`)フゥー...

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版