トータルメモリ
Rev.1を表示中。最新版はこちら。
システムの全メモリーはproc/meminfoのMemTotal:項目で表示されまが、一体この値はどのように取得されているのでしょうか?[kitamura@dhcppc4 ~]$ cat /proc/meminfo MemTotal: 473404 kB MemFree: 15140 kB Buffers: 51860 kB Cached: 350048 kB : :proc/meminfoを読み込むと、meminfo_read_proc()がコールされます。以下はその処理を端折ったものですが、MemTotal:はsi_meminfo()で取得した値を、K(i.totalram)として、そのまま表示しています。(KマクロはKバイトに変換するものです。)
static int meminfo_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data) { si_meminfo(&i); si_swapinfo(&i); len = sprintf(page, "MemTotal: %8lu kB\n" "MemFree: %8lu kB\n" "Buffers: %8lu kB\n" K(i.totalram), K(i.freeram), K(i.bufferram), ); }si_meminfo()関数ではval->totalram = totalram_pagesと、totalram_pagesをスタティック変数をそのまま設定しているに過ぎません。この変数が全メモリーの実態と言えそうですが、ではこの値とは?と言う事です。
void si_meminfo(struct sysinfo *val) { val->totalram = totalram_pages; val->sharedram = 0; val->freeram = global_page_state(NR_FREE_PAGES); val->bufferram = nr_blockdev_pages(); val->totalhigh = totalhigh_pages; val->freehigh = nr_free_highpages(); val->mem_unit = PAGE_SIZE; }totalram_pagesを設定する処理としては、start_kernel()から呼ばれるmem_init()およびrest_init()で行われているようです。mem_init()では通常メモリとハイメモリ間での処理で、rest_init()ではカーネル初期化コードで必要なくなったメモリ空間の処理です。
void start_kernel(void) { : mem_init(); : rest_init(); } void __init mem_init(void) { : totalram_pages += free_all_bootmem(); : set_highmem_pages_init(); : }free_all_bootmem()関数はbootmem_data_t *bdataを引数としてfree_all_bootmem_core()をコールしています。bootmem_data_t *bdataにはBIOSから取得したメモリ情報をページフレーム単位で設定しているようですが、そうだとすると、なぜわざわざ本関数をコールするのか?と言うことです。bootmem_data_t *bdataに設定されているページフレームには、BIOSで使用しているとか、VRAM等で使用できないメモリ空間があります。その辺りを本関数で考慮しています。その情報はbdata->node_bootmem_mapにビットマップとして有していて、この情報を元に使用ページフレームをバディーシステムに登録することにあるようです。
bdata->node_min_pfnにはカーネルコードの終端ページフレームで、bdata->node_min_pfnにはハイメモリーでないメモリ空間のページフレーム数のようです。(たぶん)この間をbdata->node_bootmem_mapを元にバディーシステムに使用可能ページとして登録します。
aligned = !(start & (BITS_PER_LONG - 1))はスタートページフレームがワードサイズでアライメントされたいるかチェックします。これは続く32ページも利用可能ページだった場合、バディーシステムへの登録の際、6のオーダで登録するかどうかの判断のようです。(たぶん)
で、while (start < end)で、全ページフレームをチェックします。idx = start - bdata->node_min_pfnでチェックするページフレームの0オリジンとするインデックスで、vec = ~map[idx / BITS_PER_LONG]で対応する32ページフレームのビットとチェックすることで、そのインデックスに対する32個のページフレームが利用可能かどうかのチェックとなるわけです。(この辺りのビット処理での実装は凡人には思いつかないな。と感心させられます。)
そうなら、一挙にorder = ilog2(BITS_PER_LONG)のオーダで(たぶんBITS_PER_LONGが32として6?)、__free_pages_bootmem()でバディーシステムに登録です。
そでないなら、BITS_PER_LONG回、vec >>= 1でビットマップをチェックすることで、1ページづつ__free_pages_bootmem()でバディーシステムに登録しているようです。
whileループを抜けた後の処理は、bdata->node_bootmem_mapで使っていたページフレームは用済みとなったので、本ページを利用可能ページとしてバディーシステムに登録しています(実にきっちりしています。)page = virt_to_page(bdata->node_bootmem_map)はbdata->node_bootmem_mapが使用している先頭ページです。pages = bdata->node_low_pfn - bdata->node_min_pfnはチェックするページ数、すなわちbdata->node_bootmem_mapのサイズが決定し、従ってbdata->node_bootmem_mapで使用されていたページ数が算出しています。そしてそのページをすべてバディーシステムに登録しています。
バディーシステムに登録し全ページはcountに保持されています。すなわちこれがここでの全メモリ(ページ数)というわけです。
static unsigned long __init free_all_bootmem_core(bootmem_data_t *bdata) { int aligned; struct page *page; unsigned long start, end, pages, count = 0; if (!bdata->node_bootmem_map) return 0; start = bdata->node_min_pfn; end = bdata->node_low_pfn; aligned = !(start & (BITS_PER_LONG - 1)); bdebug("nid=%td start=%lx end=%lx aligned=%d\n", bdata - bootmem_node_data, start, end, aligned); 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); bdebug("nid=%td released=%lx\n", bdata - bootmem_node_data, count); return count; }なお、set_highmem_pages_init()関数では、ハイメモリ空間のページ数の処理をして、そのページ数もバディーシステムに登録しtotalram_pagesに足しこんでいます。そしてrest_init()関数からkernel_initスレッドが起動され其処から呼ばれるfree_initmem()で、カーネル初期化等で再使用されないメモリー空間も同様な処理を行っています。
static void noinline __init_refok rest_init(void) __releases(kernel_lock) { : kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); : } static int __init kernel_init(void * unused) { : init_post(); return 0; } static int noinline init_post(void) { free_initmem(); : } void free_initmem(void) { free_init_pages("unused kernel memory", (unsigned long)(&__init_begin), (unsigned long)(&__init_end)); }