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

free(1)のtotalとかusedなどの各項目をカーネルの方から見てみる

linux kernel

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 freeUsed 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シリーズ)

動くメカニズムを図解&実験! Linux超入門 (My Linuxシリーズ)