Dive into linux-2.6/mm

@syuu1228 せんせーのお誕生日です!

この記事は [twitter:@syuu1228] せんせーのお誕生日祝いに書かれました(多分)

ここから

まぁとりあえずコードをあたりますよね!
ということで、あたってみた結果です。情報が正しいかどうかはわかりません。

そもそもこの「Reserved」ってなに?

linux-2.6/arch/x86/mm/init_64.c を見てみましょう。

void __init mem_init(void)
{
	long codesize, reservedpages, datasize, initsize;
...
	reservedpages = 0;
...
	/* this will put all low memory onto the freelists */
#ifdef CONFIG_NUMA
	totalram_pages = numa_free_all_bootmem();
#else
	totalram_pages = free_all_bootmem();
#endif

	absent_pages = absent_pages_in_range(0, max_pfn);
	reservedpages = max_pfn - totalram_pages - absent_pages;
...
	printk(KERN_INFO "Memory: %luk/%luk available (%ldk kernel code, "
			 "%ldk absent, %ldk reserved, %ldk data, %ldk init)\n",
		nr_free_pages() << (PAGE_SHIFT-10),
		max_pfn << (PAGE_SHIFT-10),
		codesize >> 10,
		absent_pages << (PAGE_SHIFT-10),
		reservedpages << (PAGE_SHIFT-10),
		datasize >> 10,
		initsize >> 10);
}

numa_free_all_bootmem() は linux-2.6/arch/x86/mm/numa_64.c から。まぁ、 free_all_bootmem() と基本的には変わらないはずで "release free pages to the buddy allocator" して、解放されたページの数が返ってきます。

ということで、 "reserved" はメモリ全体から「ブート時に使ったメモリ」と「存在してないメモリ」をぬいた分の様子であんまり関係はなさそう。

じゃあ、もともとの nr_free_pages() はどうなっているんだろうか?

linux-2.6/include/linux/swap.h

/* Definition of global_page_state not available yet */
#define nr_free_pages() global_page_state(NR_FREE_PAGES)

から

linux-2.6/include/linux/vmstat.h

static inline unsigned long global_page_state(enum zone_stat_item item)
{
	long x = atomic_long_read(&vm_stat[item]);
#ifdef CONFIG_SMP
	if (x < 0)
		x = 0;
#endif
	return x;
}

ようするに、 "vm_stat[NR_FREE_PAGES]" になる様子。

さて、この vm_stat[NR_FREE_PAGES] はどう計算するのでしょう?

linux-2.6/mm/bootmem.c から

static unsigned long __init free_all_bootmem_core(bootmem_data_t *bdata)
{
...
	while (start < end) {
		unsigned long *map, idx, vec;

		map = bdata->node_bootmem_map;
		idx = start - bdata->node_min_pfn;
		vec = ~map[idx / BITS_PER_LONG];

		if (aligned && vec == ~0UL && start + BITS_PER_LONG < end) {
			int order = ilog2(BITS_PER_LONG);

			__free_pages_bootmem(pfn_to_page(start), order);
			count += BITS_PER_LONG;
		} else {
			unsigned long off = 0;

			while (vec && off < BITS_PER_LONG) {
				if (vec & 1) {
					page = pfn_to_page(start + off);
					__free_pages_bootmem(page, 0);
					count++;
				}
				vec >>= 1;
				off++;
			}
		}
		start += BITS_PER_LONG;
	}

	page = virt_to_page(bdata->node_bootmem_map);
	pages = bdata->node_low_pfn - bdata->node_min_pfn;
	pages = bootmem_bootmap_pages(pages);
	count += pages;
	while (pages--)
		__free_pages_bootmem(page++, 0);
...
}

3個所で __free_pages_bootmem が呼ばれてますね。この関数 (linux-2.6/mm/page_alloc.c) が __free_page() を呼びにいきます。 んで、ごにょごにょとして free_one_page() が呼ばれてこれが "__mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);" で "1 << order" だけ vm_stat[NR_FREE_PAGES] を増やします。

…というわけで、 bootmem が確保していた分が計上されているようなのです。bootmem は boot 時に使われる簡易的なメモリアロケータですので、そうすると、 bootmem が使える、と思っている領域がそもそも少ない、ということだと思います。

んで、 bootmem は最初「全てを予約済み(=使えない)」にしておいて「全てをフリー(=使える)」にして、再度カーネルやらbootmem管理テーブル領域を「予約」します。

gist のを見る限り、 zone の中のページ数認識は間違えていないようなのでこの「予約」の部分になにかあるのではないでしょうか………

…というところで、お腹もすいてきたし借りてきた指輪物語見たいし dmesg がびみょうに予想にあわなかったり bootmem_debug=1 がついてたらなあと思ってきたので離脱します><

では、最後に

    @@@@
   @ Happy @
  Λ@Birthday@
 (・∀・@@@@
  ヽ っ\ /
  ∪∪ /∞ヽ

参考データ

linux-2.6/arch/x86/mm/numa_64.c

unsigned long __init numa_free_all_bootmem(void)
{
	unsigned long pages = 0;
	int i;

	for_each_online_node(i)
		pages += free_all_bootmem_node(NODE_DATA(i));

#ifdef CONFIG_NO_BOOTMEM
	pages += free_all_memory_core_early(MAX_NUMNODES);
#endif

	return pages;
}