free(1)は/proc/meminfoを読みに行くので、Linuxカーネルでどのような変数を見せているのかを調べてみます。カーネルのバージョンは4.5です。あ、swapのほうは今回は見ません。
最初に見るのはfs/proc/meminfo.cで、このファイルが/proc/meminfoに対する操作を定義しています。/proc/meminfoをopenする処理はmeminfo_proc_open()で、実際の処理はmeminfo_proc_show()が行います。
211 static int meminfo_proc_open(struct inode *inode, struct file *file) 212 { 213 return single_open(file, meminfo_proc_show, NULL); 214 } 215 216 static const struct file_operations meminfo_proc_fops = { 217 .open = meminfo_proc_open, 218 .read = seq_read, 219 .llseek = seq_lseek, 220 .release = single_release, 221 }; 222 223 static int __init proc_meminfo_init(void) 224 { 225 proc_create("meminfo", 0, NULL, &meminfo_proc_fops); 226 return 0; 227 }
この関数の中で、メモリに関する情報はsi_meminfo()とsi_swapinfo()で取得します。
41 #define K(x) ((x) << (PAGE_SHIFT - 10)) 42 si_meminfo(&i); 43 si_swapinfo(&i);
si_meminfo()はこのような関数で、引数で渡されたsysinfo構造体にデータを設定します。
3606 void si_meminfo(struct sysinfo *val) 3607 { 3608 val->totalram = totalram_pages; 3609 val->sharedram = global_page_state(NR_SHMEM); 3610 val->freeram = global_page_state(NR_FREE_PAGES); 3611 val->bufferram = nr_blockdev_pages(); 3612 val->totalhigh = totalhigh_pages; 3613 val->freehigh = nr_free_highpages(); 3614 val->mem_unit = PAGE_SIZE; 3615 }
変数の内容は名前から大体想像つきますね。totalram_pagesがfree(1)実行時のtotalのところに出る値です。ここでの単位はbyteではなくてページ数です。他のデータも単位はページ数です。これをKiBにするのはmeminfo_proc_show()にあるK()マクロです。x86_64環境ならPAGE_SHIFTは12となっています。なので、2bit左シフトしてページ数からKiBに変更しています。
41 #define K(x) ((x) << (PAGE_SHIFT - 10))
totalhighとfreehighはCONFIG_HIGHMEMが設定されている場合に意味があるんですが、x86_64用のカーネルならCONFIG_HIGHMEMは定義されていないので、値は0です。
で、free(1)のtotalですが、totalram_pagesはman 5 procに書かれているように、搭載しているRAMの総量ではありません。細かくは追ってないですが、このページ数を設定しているのは主にfree_bootmem_late()やfree_all_bootmem_core()ですね。 この値を表示するときは下記のようにしています。
85 seq_printf(m, 86 "MemTotal: %8lu kB\n" 〜略〜 143 K(i.totalram),
物理メモリの搭載量を知りたい場合は↓でできます。
$ sudo dmidecode -t memory | grep "Size:.*MB" | awk '{ m += $2} END {print m}'
次にusedを見てみようと思いますが、man 1 freeでUsed memory (calculated as total - free - buffers - cache)と書かれているので、先にfreeを見ましょう。
まず、表示する時にどの値を見ているか確認して、freeramのまま使っているというのがわかります。
87 "MemFree: %8lu kB\n" 〜略〜 144 K(i.freeram),
freeramはこのように設定されます。
3610 val->freeram = global_page_state(NR_FREE_PAGES);
procのmanではMemFree(LowFree+HighFree)というように書かれていますが、64bit環境にはLowFreeもHighFreeもないので、単なる空きページ数です。空きページ数の取得にはglobal_page_state()を使っているので、これを見てみます。引数のitemはNR_FREE_PAGESですね。値の読み出しはatomic_long_read()を使っていますが、これはlong型のデータをアトミックに読み込む関数です。なので、読み込んでいるのはvm_stat[NR_FREE_PAGES]です。
120 static inline unsigned long global_page_state(enum zone_stat_item item) 121 { 122 long x = atomic_long_read(&vm_stat[item]); 123 #ifdef CONFIG_SMP 124 if (x < 0) 125 x = 0; 126 #endif 127 return x; 128 }
というわけで、vm_stat[NR_FREE_PAGES]に値を増やすところを見てみます。これを設定するのは__mod_zone_freepage_state()です。
259 static inline void __mod_zone_freepage_state(struct zone *zone, int nr_pages, 260 int migratetype) 261 { 262 __mod_zone_page_state(zone, NR_FREE_PAGES, nr_pages); 263 if (is_migrate_cma(migratetype)) 264 __mod_zone_page_state(zone, NR_FREE_CMA_PAGES, nr_pages); 265 }
実際は、__mod_zone_page_state() -> zone_page_state_add()という流れで下記のように値が設定されます。
113 static inline void zone_page_state_add(long x, struct zone *zone, 114 enum zone_stat_item item) 115 { 116 atomic_long_add(x, &zone->vm_stat[item]); 117 atomic_long_add(x, &vm_stat[item]); 118 }
それで、__mod_zone_freepage_state()が呼ばれるのはどんな時かというと、ページを取得するときと解放するときですよね。なので、__free_pages()やfree_pages()が呼ばれた時にということになります。
取得時はpage_alloc.cにあるbuffered_rmqueue()でpageが取得できている場合に、取得したページ数を減らしています。
2265 if (!page) 2266 goto failed; 2267 __mod_zone_freepage_state(zone, -(1 << order), 2268 get_pcppage_migratetype(page));
そんなわけで、ここまでがMemFree/freeの値です。
つぎはbuffersを見ましょう。これはmanでMemory used by kernel buffers (Buffers in /proc/meminfo)と書かれています。 procのmanにはこのように書かれています。
Buffers %lu Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
Cachedはこうです。
Cached %lu In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
BuffersとCachedは違いますね。 Buffersの表示は下記のようにやっているのでbufferramそのものです。
146 K(i.bufferram),
Buffersはnr_blockdev_pages()という関数から取得していますので、これを見てましょう。
3611 val->bufferram = nr_blockdev_pages();
nr_blockdev_pages()はこのようにblock_device構造体にあるnrpagesの合計になっています。
677 long nr_blockdev_pages(void) 678 { 679 struct block_device *bdev; 680 long ret = 0; 681 spin_lock(&bdev_lock); 682 list_for_each_entry(bdev, &all_bdevs, bd_list) { 683 ret += bdev->bd_inode->i_mapping->nrpages; 684 } 685 spin_unlock(&bdev_lock); 686 return ret; 687 }
block_device構造はメンバ変数が結構あるのでbd_inodeのがあるところだけ抜粋です。米野都の意味は全くわかりません/(^o^)\
452 struct block_device { 453 dev_t bd_dev; /* not a kdev_t - it's a search key */ 454 int bd_openers; 455 struct inode * bd_inode; /* will die */
bd_inodeは見ての通りinodeです。
inode構造体のi_mappingはaddress_space構造体です。
600 struct address_space *i_mapping;
address_space構造体のnrpagesはこれです。
435 unsigned long nrpages; /* number of total pages */
というわけで、Buffersに表示される値はブロックデバイスに関連したinodeのaddress_space構造体に設定されているpage数ということがわかります。そして、Cachedですが、これはsi_meminfo()では設定していません。これを設定するのはmeminfo_proc_show()で計算にはbufferramの値が必要になります。
meminfo_proc_show()でのcachedの計算はこのようにやっています。global_page_state()](http://lxr.free-electrons.com/source/include/linux/vmstat.h?v=4.5#L120)はさっき見たやつですね。
46 cached = global_page_state(NR_FILE_PAGES) - 47 total_swapcache_pages() - i.bufferram;
NR_FILE_PAGESはenumなんですが、コメントがないので定義している場所だと何をする変数なのかわかりません。コミットメッセージを見てみると、もともとはnr_pagecacheという変数だったのが2.6.18-rc1から変更されたようです。
Remove the special implementation for nr_pagecache and make it a zoned counter named NR_FILE_PAGES.
これもわかりにくいので、使っているところを探します。例えば、__delete_from_page_cache()。これはページキャッシュからページを削除する関数です。__dec_zone_page_state()で値を減らす処理をしています。
227 /* hugetlb pages do not participate in page cache accounting. */ 228 if (!PageHuge(page)) 229 __dec_zone_page_state(page, NR_FILE_PAGES);
また、replace_page_cache_page()でページキャッシュにあるページを別のpage構造体に置き換える場合、古いほうのページをキャッシュから削除して、新しいページをセットするときに__inc_zone_page_state()を使っています。
566 __delete_from_page_cache(old, NULL, memcg); 〜略〜 574 if (!PageHuge(new)) 575 __inc_zone_page_state(new, NR_FILE_PAGES);
他にも__add_to_page_cache_locked()で使ってます。これはpageをページキャッシュに登録する時に使われるものです。
その他、swapキャッシュへの登録時とswapキャッシュからの削除時にも値の更新があります。swapキャッシュ登録時は__add_to_swap_cache()で。
96 if (likely(!error)) { 97 address_space->nrpages++; 98 __inc_zone_page_state(page, NR_FILE_PAGES); 99 INC_CACHE_INFO(add_total); 100 }
削除は__delete_from_swap_cache()で。
135 void __delete_from_swap_cache(struct page *page) 136 { 〜略〜 149 address_space->nrpages--; 150 __dec_zone_page_state(page, NR_FILE_PAGES);
そんなわけで、NR_FILE_PAGESはpage cacheとswap cacheに登録されたpageの数でしょう。しかし、procのmanではCachedにはswap cacheは含まないと書かれています。
Cached %lu In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
もう一度計算式を見ると、total_swapcache_pages()の返り値を減算してますね。これは名前からしてswap cacheに登録されているページ数でしょう。
46 cached = global_page_state(NR_FILE_PAGES) - 47 total_swapcache_pages() - i.bufferram;
total_swapcache_pages()はこのような処理で、address_space構造体の配列「swapper_spaces」のnrpagesの総数です。
35 struct address_space swapper_spaces[MAX_SWAPFILES] = { 36 [0 ... MAX_SWAPFILES - 1] = { 37 .page_tree = RADIX_TREE_INIT(GFP_ATOMIC|__GFP_NOWARN), 38 .i_mmap_writable = ATOMIC_INIT(0), 39 .a_ops = &swap_aops, 40 } 41 }; 〜略〜 52 unsigned long total_swapcache_pages(void) 53 { 54 int i; 55 unsigned long ret = 0; 56 57 for (i = 0; i < MAX_SWAPFILES; i++) 58 ret += swapper_spaces[i].nrpages; 59 return ret; 60 }
nrpagesを更新しているのはNR_FILE_PAGESを更新するタイミングでやってます。
そして、また計算式に戻ります。global_page_state(NR_FILE_PAGES) がpage cache/swap cacheにあるpage数。total_swapcache_pages()はswap cacheに登録されているpage数。bufferramはblock_device構造体に関連したファイルが使用しているpage数で、page cacheのpage数から色々差っ引いてCachedの値が出ます。
46 cached = global_page_state(NR_FILE_PAGES) - 47 total_swapcache_pages() - i.bufferram;
usedはtotal - free - buffers - cacheということなので、これでuserの値に必要なデータは全部揃ったので表示可能ですね。
free(1)の最後の項目はavailableで、これはmanによると/proc/meminfoのMemAvailableということです。
available Estimation of how much memory is available for starting new applications, without swapping. Unlike the data provided by the cache or free fields, this field takes into account page cache and also that not all reclaimable memory slabs will be reclaimed due to items being in use (MemAvailable in /proc/meminfo, available on kernels 3.14, emulated on kernels 2.6.27+, otherwise the same as free)
このavailableもmeminfo_proc_show()で計算します。
57 /* 58 * Estimate the amount of memory available for userspace allocations, 59 * without causing swapping. 60 */ 61 available = i.freeram - totalreserve_pages; 62 63 /* 64 * Not all the page cache can be freed, otherwise the system will 65 * start swapping. Assume at least half of the page cache, or the 66 * low watermark worth of cache, needs to stay. 67 */ 68 pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE]; 69 pagecache -= min(pagecache / 2, wmark_low); 70 available += pagecache; 71 72 /* 73 * Part of the reclaimable slab consists of items that are in use, 74 * and cannot be freed. Cap this estimate at the low watermark. 75 */ 76 available += global_page_state(NR_SLAB_RECLAIMABLE) - 77 min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low); 78
最初に、空きページ数のMemFree(i.freeram)から予約済みのページ数を引きます。 次に、使用可能な分のpage cacheのページ数を足します。ここはまず、LRU_ACTIVE_FILEとLRU_INACTIVE_FILEを探します。これは本当にindex用途っぽくて、include/linux/mmzone.hでこのように定義されています。
171 #define LRU_BASE 0 172 #define LRU_ACTIVE 1 173 #define LRU_FILE 2 174 175 enum lru_list { 176 LRU_INACTIVE_ANON = LRU_BASE, 177 LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE, 178 LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE, 179 LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE, 180 LRU_UNEVICTABLE, 181 NR_LRU_LISTS 182 };
変数のpagesはこのように取得しています。global_page_state()はいつものやつです。NR_LRU_LISTSは5なので、0から4までのindexを使いますね。
34 unsigned long pages[NR_LRU_LISTS]; 〜略〜 51 for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++) 52 pages[lru] = global_page_state(NR_LRU_BASE + lru);
global_page_state()に渡すのは列挙型のzone_stat_itemです。NR_ACTIVE_ANONまでの値を取ってきてますね。
114 enum zone_stat_item { 115 /* First 128 byte cacheline (assuming 64 bit words) */ 116 NR_FREE_PAGES, 117 NR_ALLOC_BATCH, 118 NR_LRU_BASE, 119 NR_INACTIVE_ANON = NR_LRU_BASE, /* must match order of LRU_[IN]ACTIVE */ 120 NR_ACTIVE_ANON, /* " " " " " */ 121 NR_INACTIVE_FILE, /* " " " " " */
そして、↓なので、LRU_ACTIVE_FILEは3、LRU_INACTIVE_FILEは2です。よって、zone_stat_itemでいうと、3に該当するのはNR_ACTIVE_ANON、2の方はNR_LRU_BASE/NR_INACTIVE_ANONです。なので、Anonymous mappingされたpage数の合計がpagecacheの初期値でしょうね。
68 pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE];
NR_LRU_BASEは使用箇所からして、pageをlruリストに登録・削除した時に設定されます。これの実装はlinux/mm_inline.hにあります。
25 static __always_inline void add_page_to_lru_list(struct page *page, 26 struct lruvec *lruvec, enum lru_list lru) 27 { 28 int nr_pages = hpage_nr_pages(page); 29 mem_cgroup_update_lru_size(lruvec, lru, nr_pages); 30 list_add(&page->lru, &lruvec->lists[lru]); 31 __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, nr_pages); 32 } 33 34 static __always_inline void del_page_from_lru_list(struct page *page, 35 struct lruvec *lruvec, enum lru_list lru) 36 { 37 int nr_pages = hpage_nr_pages(page); 38 mem_cgroup_update_lru_size(lruvec, lru, -nr_pages); 39 list_del(&page->lru); 40 __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, -nr_pages); 41 }
次の行はどちらか小さいほうが選択されて引かれます。
69 pagecache -= min(pagecache / 2, wmark_low);
wmark_lowはこのように設定されます。実行中のLinuxカーネルが管理している各zoneのwatermarkのindexをWMARK_LOWとしたほうのwatermarkを使います。
54 for_each_zone(zone) 55 wmark_low += zone->watermark[WMARK_LOW];
そして、pagecacheの値をavailableに足します。その次のNR_SLAB_RECLAIMABLEはslabオブジェクトのうち、page回収が可能とセットされているslabのページ数です。
70 available += pagecache; 71 72 /* 73 * Part of the reclaimable slab consists of items that are in use, 74 * and cannot be freed. Cap this estimate at the low watermark. 75 */ 76 available += global_page_state(NR_SLAB_RECLAIMABLE) - 77 min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low);
これは、例えば、allocate_slab()でこのように使っています。sはkmem_cache構造体です。この構造体のflags変数にSLAB_RECLAIM_ACCOUNTがセットされていればNR_SLAB_RECLAIMABLEを使う感じです。
1482 1483 mod_zone_page_state(page_zone(page), 1484 (s->flags & SLAB_RECLAIM_ACCOUNT) ? 1485 NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE, 1486 1 << oo_order(oo));
で、flagsにSLAB_RECLAIM_ACCOUNTを設定するのはkmem_cache_create()の実行時です。例えば、fs/inode.cにあるinode_init()でinode用のslabを作る時に設定しています。
1887 /* inode slab cache */ 1888 inode_cachep = kmem_cache_create("inode_cache", 1889 sizeof(struct inode), 1890 0, 1891 (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC| 1892 SLAB_MEM_SPREAD|SLAB_ACCOUNT), 1893 init_once); 1894
そして、回収可能なslabのページ数から引き算を行うわけですが、これもどちらか小さいほうが引かれます。
76 available += global_page_state(NR_SLAB_RECLAIMABLE) - 77 min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low);
これでavailableの値決定ですね。

動くメカニズムを図解&実験! Linux超入門 (My Linuxシリーズ)
- 作者: 宗像尚郎/海老原祐太郎
- 出版社/メーカー: CQ出版
- 発売日: 2016/04/15
- メディア: 単行本
- この商品を含むブログ (3件) を見る