天天看點

Linux:/proc/meminfo參數詳細解釋

最近公司的機器在使用的過程中,記憶體占用率随着運作逐漸升高,是以計劃學習下linux記憶體方面的知識。 參考:http://linuxperf.com/?p=142

一、Linux記憶體總覽圖

Linux:/proc/meminfo參數詳細解釋

二、meminfo參數的詳細介紹

/proc/meminfo是了解Linux系統記憶體使用狀況的主要接口,我們最常用的”free”、”vmstat”等指令就是通過它擷取資料的 ,/proc/meminfo所包含的資訊比”free”等指令要豐富得多,然而真正了解它并不容易,比如我們知道”Cached”統計的是檔案緩存頁,manpage上說是“In-memory cache for files read from the disk (the page cache)”,那為什麼它不等于[Active(file)+Inactive(file)]?AnonHugePages與AnonPages、HugePages_Total有什麼聯系和差別?很多細節在手冊中并沒有講清楚,本文對此做了一點探究。

負責輸出/proc/meminfo的源代碼是:

fs/proc/meminfo.c : meminfo_proc_show()

MemTotal:        3809036 kB
MemFree:          282012 kB
MemAvailable:     865620 kB
Buffers:               0 kB
Cached:           854972 kB
SwapCached:       130900 kB
Active:          1308168 kB
Inactive:        1758160 kB
Active(anon):    1010416 kB
Inactive(anon):  1370480 kB
Active(file):     297752 kB
Inactive(file):   387680 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:       4063228 kB
SwapFree:        3357108 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:       2104412 kB
Mapped:            40988 kB
Shmem:            169540 kB
Slab:             225420 kB
SReclaimable:     134220 kB
SUnreclaim:        91200 kB
KernelStack:        5936 kB
PageTables:        35628 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     5967744 kB
Committed_AS:    5626436 kB
VmallocTotal:   34359738367 kB
VmallocUsed:      351900 kB
VmallocChunk:   34359363652 kB
HardwareCorrupted:     0 kB
AnonHugePages:    139264 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      204484 kB
DirectMap2M:     3915776 kB
           

MemTotal

系統從加電開始到引導完成,firmware/BIOS要保留一些記憶體,kernel本身要占用一些記憶體,最後剩下可供kernel支配的記憶體就是MemTotal。這個值在系統運作期間一般是固定不變的。可參閱解讀DMESG中的記憶體初始化資訊。

MemFree

表示系統尚未使用的記憶體。[MemTotal-MemFree]就是已被用掉的記憶體。

MemAvailable

有些應用程式會根據系統的可用記憶體大小自動調整記憶體申請的多少,是以需要一個記錄目前可用記憶體數量的統計值,MemFree并不适用,因為MemFree不能代表全部可用的記憶體,系統中有些記憶體雖然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,是以這部分可回收的記憶體加上MemFree才是系統可用的記憶體,即MemAvailable。/proc/meminfo中的MemAvailable是核心使用特定的算法估算出來的,要注意這是一個估計值,并不精确。

記憶體黑洞

追蹤Linux系統的記憶體使用一直是個難題,很多人試着把能想到的各種記憶體消耗都加在一起,kernel text、kernel modules、buffer、cache、slab、page table、process RSS…等等,卻總是與實體記憶體的大小對不上,這是為什麼呢?因為Linux kernel并沒有滴水不漏地統計所有的記憶體配置設定,kernel動态配置設定的記憶體中就有一部分沒有計入/proc/meminfo中。

我們知道,Kernel的動态記憶體配置設定通過以下幾種接口:

  • alloc_pages/__get_free_page: 以頁為機關配置設定
  • vmalloc: 以位元組為機關配置設定虛拟位址連續的記憶體塊
  • slab allocator
    • kmalloc: 以位元組為機關配置設定實體位址連續的記憶體塊,它是以slab為基礎的,使用slab層的general caches — 大小為2^n,名稱是kmalloc-32、kmalloc-64等(在老kernel上的名稱是size-32、size-64等)。

      通過slab層配置設定的記憶體會被精确統計,可以參見/proc/meminfo中的slab/SReclaimable/SUnreclaim;

通過vmalloc配置設定的記憶體也有統計,參見/proc/meminfo中的VmallocUsed 和 /proc/vmallocinfo(下節中還有詳述);

而通過alloc_pages配置設定的記憶體不會自動統計,除非調用alloc_pages的核心子產品或驅動程式主動進行統計,否則我們隻能看到free memory減少了,但從/proc/meminfo中看不出它們具體用到哪裡去了。比如在VMware guest上有一個常見問題,就是VMWare ESX主控端會通過guest上的Balloon driver(vmware_balloon module)占用guest的記憶體,有時占用得太多會導緻guest無記憶體可用,這時去檢查guest的/proc/meminfo隻看見MemFree很少、但看不出記憶體的去向,原因就是Balloon driver通過alloc_pages配置設定記憶體,沒有在/proc/meminfo中留下統計值,是以很難追蹤。

記憶體都到哪裡去了? 使用記憶體的,不是kernel就是使用者程序,下面我們就分類讨論。

注:page cache比較特殊,很難區分是屬于kernel還是屬于程序,其中被程序mmap的頁面自然是屬于程序的了,而另一些頁面沒有被mapped到任何程序,那就隻能算是屬于kernel了。

1. 核心

核心所用記憶體的靜态部分,比如核心代碼、頁描述符等資料在引導階段就配置設定掉了,并不計入MemTotal裡,而是算作Reserved(在dmesg中能看到)。而核心所用記憶體的動态部分,是通過上文提到的幾個接口申請的,其中通過alloc_pages申請的記憶體有可能未納入統計,就像黑洞一樣。

下面讨論的都是/proc/meminfo中所統計的部分。

1.1 SLAB

通過slab配置設定的記憶體被統計在以下三個值中:

  • SReclaimable: slab中可回收的部分。調用kmem_getpages()時加上SLAB_RECLAIM_ACCOUNT标記,表明是可回收的,計入SReclaimable,否則計入SUnreclaim。
  • SUnreclaim: slab中不可回收的部分。
  • Slab: slab中所有的記憶體,等于以上兩者之和。

1.2 VmallocUsed

通過vmalloc配置設定的記憶體都統計在/proc/meminfo的 VmallocUsed 值中,但是要注意這個值不止包括了配置設定的實體記憶體,還統計了VM_IOREMAP、VM_MAP等操作的值,譬如VM_IOREMAP是把IO位址映射到核心空間、并未消耗實體記憶體,是以我們要把它們排除在外。從實體記憶體配置設定的角度,我們隻關心VM_ALLOC操作,這可以從/proc/vmallocinfo中的vmalloc記錄看到:

# grep vmalloc /proc/vmallocinfo
...
0xffffc90004702000-0xffffc9000470b000   36864 alloc_large_system_hash+0x171/0x239 pages=8 vmalloc N0=8
0xffffc9000470b000-0xffffc90004710000   20480 agp_add_bridge+0x2aa/0x440 pages=4 vmalloc N0=4
0xffffc90004710000-0xffffc90004731000  135168 raw_init+0x41/0x141 pages=32 vmalloc N0=32
0xffffc90004736000-0xffffc9000473f000   36864 drm_ht_create+0x55/0x80 [drm] pages=8 vmalloc N0=8
0xffffc90004744000-0xffffc90004746000    8192 dm_table_create+0x9e/0x130 [dm_mod] pages=1 vmalloc N0=1
0xffffc90004746000-0xffffc90004748000    8192 dm_table_create+0x9e/0x130 [dm_mod] pages=1 vmalloc N0=1
...
           

注:/proc/vmallocinfo中能看到vmalloc來自哪個調用者(caller),那是vmalloc()記錄下來的,相應的源代碼可見:

mm/vmalloc.c: vmalloc > __vmalloc_node_flags > __vmalloc_node > __vmalloc_node_range > __get_vm_area_node > setup_vmalloc_vm

通過vmalloc配置設定了多少記憶體,可以統計/proc/vmallocinfo中的vmalloc記錄,例如:

# grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'
23375872
           

一些driver以及網絡子產品和檔案系統子產品可能會調用vmalloc,加載核心子產品(kernel module)時也會用到,可參見 kernel/module.c。

1.3 kernel modules (核心子產品)

系統已經加載的核心子產品可以用 lsmod 指令檢視,注意第二列就是核心子產品所占記憶體的大小,通過它可以統計核心子產品所占用的記憶體大小,但這并不準,因為”lsmod”列出的是[init_size+core_size],而實際給kernel module配置設定的記憶體是以page為機關的,不足 1 page的部分也會得到整個page,此外每個module還會分到一頁額外的guard page。下文我們還會細說。

# lsmod | less
Module                  Size  Used by
rpcsec_gss_krb5        31477  0 
auth_rpcgss            59343  1 rpcsec_gss_krb5
nfsv4                 474429  0 
dns_resolver           13140  1 nfsv4
nfs                   246411  1 nfsv4
lockd                  93977  1 nfs
sunrpc                295293  5 nfs,rpcsec_gss_krb5,auth_rpcgss,lockd,nfsv4
fscache                57813  2 nfs,nfsv4
...
           

lsmod的資訊來自/proc/modules,它顯示的size包括init_size和core_size,相應的源代碼參見:

// kernel/module.c
static int m_show(struct seq_file *m, void *p)
{
...
        seq_printf(m, "%s %u",
                   mod->name, mod->init_size + mod->core_size);
...
}
           

注:我們可以在 /sys/module// 目錄下分别看到coresize和initsize的值。

kernel module的記憶體是通過vmalloc()配置設定的(參見下列源代碼),是以在/proc/vmallocinfo中會有記錄,也就是說我們可以不必通過”lsmod”指令來統計kernel module所占的記憶體大小,通過/proc/vmallocinfo就行了,而且還比lsmod更準确,為什麼這麼說呢?

// kernel/module.c
static int move_module(struct module *mod, struct load_info *info)
{
...
        ptr = module_alloc_update_bounds(mod->core_size);
...
        if (mod->init_size) {
                ptr = module_alloc_update_bounds(mod->init_size);
...
}
 
// 注:module_alloc_update_bounds()最終會調用vmalloc_exec()
           

因為給kernel module配置設定記憶體是以page為機關的,不足 1 page的部分也會得到整個page,此外,每個module還會分到一頁額外的guard page。

詳見:mm/vmalloc.c: __get_vm_area_node()

而”lsmod”列出的是[init_size+core_size],比實際配置設定給kernel module的記憶體小。我們做個實驗來說明:

# 先解除安裝floppy子產品
$ modprobe -r floppy
# 确認floppy子產品已經不在了
$ lsmod | grep floppy
# 記錄vmallocinfo以供随後比較
$ cat /proc/vmallocinfo > vmallocinfo.1
 
# 加載floppy子產品
$ modprobe -a floppy
# 注意floppy子產品的大小是69417位元組:
$ lsmod | grep floppy
floppy                 69417  0 
$ cat /proc/vmallocinfo > vmallocinfo.2
# 然而,我們看到vmallocinfo中記錄的是配置設定了73728位元組:
$ diff vmallocinfo.1 vmallocinfo.2
68a69
> 0xffffffffa03d7000-0xffffffffa03e9000   73728 module_alloc_update_bounds+0x14/0x70 pages=17 vmalloc N0=17
 
# 為什麼lsmod看到的記憶體大小與vmallocinfo不同呢?
# 因為給kernel module配置設定記憶體是以page為機關的,而且外加一個guard page
# 我們來驗證一下:
$ bc -q
69417%4096
3881    <--- 不能被4096整除
69417/4096
16      <--- 相當于16 pages,加上面的3881位元組,會配置設定17 pages
18*4096 <--- 17 pages 加上 1個guard page
73728   <--- 正好是vmallocinfo記錄的大小
           

是以結論是kernel module所占用的記憶體包含在/proc/vmallocinfo的統計之中,不必再去計算”lsmod”的結果了,而且”lsmod”也不準。

1.4 HardwareCorrupted

當系統檢測到記憶體的硬體故障時,會把有問題的頁面删除掉,不再使用,/proc/meminfo中的HardwareCorrupted統計了删除掉的記憶體頁的總大小。相應的代碼參見 mm/memory-failure.c: memory_failure()。

1.5 PageTables

Page Table用于将記憶體的虛拟位址翻譯成實體位址,随着記憶體位址配置設定得越來越多,Page Table會增大,/proc/meminfo中的PageTables統計了Page Table所占用的記憶體大小。

注:請把Page Table與Page Frame(頁幀)區分開,實體記憶體的最小機關是page frame,每個實體頁對應一個描述符(struct page),在核心的引導階段就會配置設定好、儲存在mem_map[]數組中,mem_map[]所占用的記憶體被統計在dmesg顯示的reserved中,/proc/meminfo的MemTotal是不包含它們的。(在NUMA系統上可能會有多個mem_map數組,在node_data中或mem_section中)。

而Page Table的用途是翻譯虛拟位址和實體位址,它是會動态變化的,要從MemTotal中消耗記憶體。

1.6 KernelStack

每一個使用者線程都會配置設定一個kernel stack(核心棧),核心棧雖然屬于線程,但使用者态的代碼不能通路,隻有通過系統調用(syscall)、自陷(trap)或異常(exception)進入核心态的時候才會用到,也就是說核心棧是給kernel code使用的。在x86系統上Linux的核心棧大小是固定的8K或16K(可參閱我以前的文章:核心棧溢出)。

Kernel stack(核心棧)是常駐記憶體的,既不包括在LRU lists裡,也不包括在程序的RSS/PSS記憶體裡,是以我們認為它是kernel消耗的記憶體。統計值是/proc/meminfo的KernelStack。

1.7 Bounce

有些老裝置隻能通路低端記憶體,比如16M以下的記憶體,當應用程式發出一個I/O 請求,DMA的目的位址卻是高端記憶體時(比如在16M以上),核心将在低端記憶體中配置設定一個臨時buffer作為跳轉,把位于高端記憶體的緩存資料複制到此處。這種額外的資料拷貝被稱為“bounce buffering”,會降低I/O 性能。大量配置設定的bounce buffers 也會占用額外的記憶體。

2. 使用者程序

/proc/meminfo統計的是系統全局的記憶體使用狀況,單個程序的情況要看/proc//下的smaps等等。

2.1 Hugepages

Hugepages在/proc/meminfo中是被獨立統計的,與其它統計項不重疊,既不計入程序的RSS/PSS中,又不計入LRU Active/Inactive,也不會計入cache/buffer。如果程序使用了Hugepages,它的RSS/PSS不會增加。

注:不要把 Transparent HugePages (THP)跟 Hugepages 搞混了,THP的統計值是/proc/meminfo中的”AnonHugePages”,在/proc//smaps中也有單個程序的統計,這個統計值與程序的RSS/PSS是有重疊的,如果使用者程序用到了THP,程序的RSS/PSS也會相應增加,這與Hugepages是不同的。

在/proc/meminfo中與Hugepages有關的統計值如下:

MemFree: 570736 kB
...
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
           

HugePages_Total 對應核心參數 vm.nr_hugepages,也可以在運作中的系統上直接修改 /proc/sys/vm/nr_hugepages,修改的結果會立即影響空閑記憶體 MemFree的大小,因為HugePages在核心中獨立管理,隻要一經定義,無論是否被使用,都不再屬于free memory。在下例中我們設定256MB(128頁)Hugepages,可以立即看到Memfree立即減少了262144kB(即256MB):

# echo 128 > /proc/sys/vm/nr_hugepages
# cat /proc/meminfo
...
MemFree: 308592 kB
...
HugePages_Total: 128
HugePages_Free: 128
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
           

使用Hugepages有三種方式:

(詳見 https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt)

mount一個特殊的 hugetlbfs 檔案系統,在上面建立檔案,然後用mmap() 進行通路,如果要用 read() 通路也是可以的,但是 write() 不行。

通過shmget/shmat也可以使用Hugepages,調用shmget申請共享記憶體時要加上 SHM_HUGETLB 标志。

通過 mmap(),調用時指定MAP_HUGETLB 标志也可以使用Huagepages。

使用者程式在申請Hugepages的時候,其實是reserve了一塊記憶體,并未真正使用,此時/proc/meminfo中的 HugePages_Rsvd 會增加,而 HugePages_Free 不會減少。

HugePages_Total: 128
HugePages_Free: 128
HugePages_Rsvd: 128
HugePages_Surp: 0
Hugepagesize: 2048 kB
           

等到使用者程式真正讀寫Hugepages的時候,它才被消耗掉了,此時HugePages_Free會減少,HugePages_Rsvd也會減少。

HugePages_Total: 128
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
           

我們說過,Hugepages是獨立統計的,如果程序使用了Hugepages,它的RSS/PSS不會增加。下面舉例說明,一個程序通過mmap()申請并使用了Hugepages,在/proc//smaps中可以看到如下記憶體段,VmFlags包含的”ht”表示Hugepages,kernelPageSize是2048kB,注意RSS/PSS都是0:

2aaaaac00000-2aaabac00000 rw-p 00000000 00:0c 311151 /anon_hugepage (deleted)
Size: 262144 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 2048 kB
MMUPageSize: 2048 kB
Locked: 0 kB
VmFlags: rd wr mr mw me de ht
...
           

2.2 AnonHugePages

AnonHugePages統計的是Transparent HugePages (THP),THP與Hugepages不是一回事,差別很大。

上一節說過,Hugepages在/proc/meminfo中是被獨立統計的,與其它統計項不重疊,既不計入程序的RSS/PSS中,又不計入LRU Active/Inactive,也不會計入cache/buffer。如果程序使用了Hugepages,它的RSS/PSS不會增加。

而AnonHugePages完全不同,它與/proc/meminfo的其他統計項有重疊,首先它被包含在AnonPages之中,而且在/proc//smaps中也有單個程序的統計,與程序的RSS/PSS是有重疊的,如果使用者程序用到了THP,程序的RSS/PSS也會相應增加,這與Hugepages是不同的。下例截取自/proc//smaps中的一段:

7efcf0000000-7efd30000000 rw-p 00000000 00:00 0 
Size:            1048576 kB
Rss:              313344 kB
Pss:              313344 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:    313344 kB
Referenced:       239616 kB
Anonymous:        313344 kB
AnonHugePages:    313344 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me dc ac hg mg
           

THP也可以用于shared memory和tmpfs,預設是禁止的,打開的方法如下(詳見 https://www.kernel.org/doc/Documentation/vm/transhuge.txt):

mount時加上”huge=always”等選項

通過/sys/kernel/mm/transparent_hugepage/shmem_enabled來控制

因為預設情況下shared memory和tmpfs不使用THP,是以程序之間不會共享AnonHugePages,于是就有以下等式:

  • 【/proc/meminfo的AnonHugePages】==【所有程序的/proc//smaps中AnonHugePages之和】

舉例如下:

# grep AnonHugePages /proc/[1-9]*/smaps | awk '{total+=$2}; END {print total}'
782336
# grep AnonHugePages /proc/meminfo 
AnonHugePages:    782336 kB
           

2.3 LRU

LRU是Kernel的頁面回收算法(Page Frame Reclaiming)使用的資料結構,在解讀vmstat中的Active/Inactive memory一文中有介紹。Page cache和所有使用者程序的記憶體(kernel stack和huge pages除外)都在LRU lists上。

LRU lists包括如下幾種,在/proc/meminfo中都有對應的統計值:

  • LRU_INACTIVE_ANON – 對應 Inactive(anon)
  • LRU_ACTIVE_ANON – 對應 Active(anon)
  • LRU_INACTIVE_FILE – 對應 Inactive(file)
  • LRU_ACTIVE_FILE – 對應 Active(file)
  • LRU_UNEVICTABLE – 對應 Unevictable

注:

Inactive list裡的是長時間未被通路過的記憶體頁,Active list裡的是最近被通路過的記憶體頁,LRU算法利用Inactive list和Active list可以判斷哪些記憶體頁可以被優先回收。

括号中的 anon 表示匿名頁(anonymous pages)。

使用者程序的記憶體頁分為兩種:file-backed pages(與檔案對應的記憶體頁),和anonymous pages(匿名頁),比如程序的代碼、映射的檔案都是file-backed,而程序的堆、棧都是不與檔案相對應的、就屬于匿名頁。file-backed pages在記憶體不足的時候可以直接寫回對應的硬碟檔案裡,稱為page-out,不需要用到交換區(swap);而anonymous pages在記憶體不足時就隻能寫到硬碟上的交換區(swap)裡,稱為swap-out。

括号中的 file 表示 file-backed pages(與檔案對應的記憶體頁)。

Unevictable LRU list上是不能pageout/swapout的記憶體頁,包括VM_LOCKED的記憶體頁、SHM_LOCK的共享記憶體頁(又被統計在”Mlocked”中)、和ramfs。在unevictable list出現之前,這些記憶體頁都在Active/Inactive lists上,vmscan每次都要掃過它們,但是又不能把它們pageout/swapout,這在大記憶體的系統上會嚴重影響性能,設計unevictable list的初衷就是避免這種情況,參見:

https://www.kernel.org/doc/Documentation/vm/unevictable-lru.txt

LRU與/proc/meminfo中其他統計值的關系:

  • LRU中不包含HugePages_*。
  • LRU包含了 Cached 和 AnonPages。

2.4 Shmem

/proc/meminfo中的Shmem統計的内容包括:

shared memory

tmpfs和devtmpfs。

注:所有tmpfs類型的檔案系統占用的空間都計入共享記憶體,devtmpfs是/dev檔案系統的類型,/dev/下所有的檔案占用的空間也屬于共享記憶體。可以用ls和du指令檢視。如果檔案在沒有關閉的情況下被删除,空間仍然不會釋放,shmem不會減小,可以用 “lsof -a +L1 /<mount_point>” 指令列出這樣的檔案。

此處所講的shared memory又包括:

SysV shared memory [shmget etc.]

POSIX shared memory [shm_open etc.]

shared anonymous mmap [ mmap(…MAP_ANONYMOUS|MAP_SHARED…)]

因為shared memory在核心中都是基于tmpfs實作的,參見:

https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt

也就是說它們被視為基于tmpfs檔案系統的記憶體頁,既然基于檔案系統,就不算匿名頁,是以不被計入/proc/meminfo中的AnonPages,而是被統計進了:

  • Cached (i.e. page cache)
  • Mapped (當shmem被attached時候)

    然而它們背後并不存在真正的硬碟檔案,一旦記憶體不足的時候,它們是需要交換區才能swap-out的,是以在LRU lists裡,它們被放在:

  • Inactive(anon) 或 Active(anon)

注:雖然它們在LRU中被放進了anon list,但是不會被計入 AnonPages。這是shared memory & tmpfs比較擰巴的一個地方,需要特别注意。

或 unevictable (如果被locked的話)

注意:

當shmget/shm_open/mmap建立共享記憶體時,實體記憶體尚未配置設定,要直到真正通路時才配置設定。/proc/meminfo中的 Shmem 統計的是已經配置設定的大小,而不是建立時申請的大小。

2.5 AnonPages

前面提到使用者程序的記憶體頁分為兩種:file-backed pages(與檔案對應的記憶體頁),和anonymous pages(匿名頁)。Anonymous pages(匿名頁)的數量統計在/proc/meminfo的AnonPages中。

以下是幾個事實,有助于了解Anonymous Pages:

所有page cache裡的頁面(Cached)都是file-backed pages,不是Anonymous Pages。”Cached”與”AnoPages”之間沒有重疊。

注:shared memory 不屬于 AnonPages,而是屬于Cached,因為shared memory基于tmpfs,是以被視為file-backed、在page cache裡,上一節解釋過。

mmap private anonymous pages屬于AnonPages(Anonymous Pages),而mmap shared anonymous pages屬于Cached(file-backed pages),因為shared anonymous mmap也是基于tmpfs的,上一節解釋過。

Anonymous Pages是與使用者程序共存的,一旦程序退出,則Anonymous pages也釋放,不像page cache即使檔案與程序不關聯了還可以緩存。

AnonPages統計值中包含了Transparent HugePages (THP)對應的 AnonHugePages 。參見:

fs/proc/meminfo.c:
 
static int meminfo_proc_show(struct seq_file *m, void *v)
{
...
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
                K(global_page_state(NR_ANON_PAGES)
                  + global_page_state(NR_ANON_TRANSPARENT_HUGEPAGES) *
                  HPAGE_PMD_NR),
...
           

2.6 Mapped

上面提到的使用者程序的file-backed pages就對應着/proc/meminfo中的”Mapped”。Page cache中(“Cached”)包含了檔案的緩存頁,其中有些檔案目前已不在使用,page cache仍然可能保留着它們的緩存頁面;而另一些檔案正被使用者程序關聯,比如shared libraries、可執行程式的檔案、mmap的檔案等,這些檔案的緩存頁就稱為mapped。

/proc/meminfo中的”Mapped”就統計了page cache(“Cached”)中所有的mapped頁面。”Mapped”是”Cached”的子集。

因為Linux系統上shared memory & tmpfs被計入page cache(“Cached”),是以被attached的shared memory、以及tmpfs上被map的檔案都算做”Mapped”。

程序所占的記憶體頁分為anonymous pages和file-backed pages,理論上應該有:

【所有程序的PSS之和】 == 【Mapped + AnonPages】。

然而我實際測試的結果,雖然兩者很接近,卻總是無法精确相等,我猜也許是因為程序始終在變化、采集的/proc/[1-9]*/smaps以及/proc/meminfo其實不是來自同一個時間點的緣故。

2.7 Cached

Page Cache裡包括所有file-backed pages,統計在/proc/meminfo的”Cached”中。

Cached是”Mapped”的超集,就是說它不僅包括mapped,也包括unmapped的頁面,當一個檔案不再與程序關聯之後,原來在page cache中的頁面并不會立即回收,仍然被計入Cached,還留在LRU中,但是 Mapped 統計值會減小。【ummaped = (Cached – Mapped)】

Cached包含tmpfs中的檔案,POSIX/SysV shared memory,以及shared anonymous mmap。

注:POSIX/SysV shared memory和shared anonymous mmap在核心中都是基于tmpfs實作的,參見:

https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt

“Cached”和”SwapCached”兩個統計值是互不重疊的,源代碼參見下一節。是以,Shared memory和tmpfs在不發生swap-out的時候屬于”Cached”,而在swap-out/swap-in的過程中會被加進swap cache中、屬于”SwapCached”,一旦進了”SwapCached”,就不再屬于”Cached”了。

2.8 SwapCached

我們說過,匿名頁(anonymous pages)要用到交換區,而shared memory和tmpfs雖然未統計在AnonPages裡,但它們背後沒有硬碟檔案,是以也是需要交換區的。也就是說需要用到交換區的記憶體包括:”AnonPages”和”Shmem”,我們姑且把它們統稱為匿名頁好了。

交換區可以包括一個或多個交換區裝置(裸盤、邏輯卷、檔案都可以充當交換區裝置),每一個交換區裝置都對應自己的swap cache,可以把swap cache了解為交換區裝置的”page cache”:page cache對應的是一個個檔案,swap cache對應的是一個個交換區裝置,kernel管理swap cache與管理page cache一樣,用的都是radix-tree,唯一的差別是:page cache與檔案的對應關系在打開檔案時就确定了,而一個匿名頁隻有在即将被swap-out的時候才決定它會被放到哪一個交換區裝置,即匿名頁與swap cache的對應關系在即将被swap-out時才确立。

并不是每一個匿名頁都在swap cache中,隻有以下情形之一的匿名頁才在:

匿名頁即将被swap-out時會先被放進swap cache,但通常隻存在很短暫的時間,因為緊接着在pageout完成之後它就會從swap cache中删除,畢竟swap-out的目的就是為了騰出空閑記憶體;

【注:參見mm/vmscan.c: shrink_page_list(),它調用的add_to_swap()會把swap cache頁面标記成dirty,然後它調用try_to_unmap()将頁面對應的page table mapping都删除,再調用pageout()回寫dirty page,最後try_to_free_swap()會把該頁從swap cache中删除。】

曾經被swap-out現在又被swap-in的匿名頁會在swap cache中,直到頁面中的内容發生變化、或者原來用過的交換區空間被回收為止。

【注:當匿名頁的内容發生變化時會删除對應的swap cache,代碼參見mm/swapfile.c: reuse_swap_page()。】

/proc/meminfo中的SwapCached背後的含義是:系統中有多少匿名頁曾經被swap-out、現在又被swap-in并且swap-in之後頁面中的内容一直沒發生變化。也就是說,如果這些匿名頁需要被swap-out的話,是無需進行I/O write操作的。

“SwapCached”不屬于”Cached”,兩者沒有交叉。參見:

fs/proc/meminfo.c:
static int meminfo_proc_show(struct seq_file *m, void *v)
{
...
        cached = global_page_state(NR_FILE_PAGES) -
                        total_swapcache_pages() - i.bufferram;
...
}
           

“SwapCached”記憶體同時也在LRU中,還在”AnonPages”或”Shmem”中,它本身并不占用額外的記憶體。

2.9 Mlocked

“Mlocked”統計的是被mlock()系統調用鎖定的記憶體大小。被鎖定的記憶體因為不能pageout/swapout,會從Active/Inactive LRU list移到Unevictable LRU list上。也就是說,當”Mlocked”增加時,”Unevictable”也同步增加,而”Active”或”Inactive”同時減小;當”Mlocked”減小的時候,”Unevictable”也同步減小,而”Active”或”Inactive”同時增加。

“Mlocked”并不是獨立的記憶體空間,它與以下統計項重疊:LRU Unevictable,AnonPages,Shmem,Mapped等。

2.10 Buffers

“Buffers”表示塊裝置(block device)所占用的緩存頁,包括:直接讀寫塊裝置、以及檔案系統中繼資料(metadata)比如SuperBlock所使用的緩存頁。它與“Cached”的差別在于,”Cached”表示普通檔案所占用的緩存頁。參見我的另一篇文章http://linuxperf.com/?p=32

“Buffers”所占的記憶體同時也在LRU list中,被統計在Active(file)或Inactive(file)。

注:通過閱讀源代碼可知,塊裝置的讀寫操作涉及的緩存被納入了LRU,以讀操作為例,do_generic_file_read()函數通過 mapping->a_ops->readpage() 調用塊裝置底層的函數,并調用 add_to_page_cache_lru() 把緩存頁加入到LRU list中。參見:

filemap.c: do_generic_file_read > add_to_page_cache_lru

其它問題

DirectMap

/proc/meminfo中的DirectMap所統計的不是關于記憶體的使用,而是一個反映TLB效率的名額。TLB(Translation Lookaside Buffer)是位于CPU上的緩存,用于将記憶體的虛拟位址翻譯成實體位址,由于TLB的大小有限,不能緩存的位址就需要通路記憶體裡的page table來進行翻譯,速度慢很多。為了盡可能地将位址放進TLB緩存,新的CPU硬體支援比4k更大的頁面進而達到減少位址數量的目的, 比如2MB,4MB,甚至1GB的記憶體頁,視不同的硬體而定。”DirectMap4k”表示映射為4kB的記憶體數量, “DirectMap2M”表示映射為2MB的記憶體數量,以此類推。是以DirectMap其實是一個反映TLB效率的名額。

Dirty pages到底有多少?

/proc/meminfo 中有一個Dirty統計值,但是它未能包括系統中全部的dirty pages,應該再加上另外兩項:NFS_Unstable 和 Writeback,NFS_Unstable是發給NFS server但尚未寫入硬碟的緩存頁,Writeback是正準備回寫硬碟的緩存頁。即:

系統中全部dirty pages = ( Dirty + NFS_Unstable + Writeback )

注1:NFS_Unstable的記憶體被包含在Slab中,因為nfs request記憶體是調用kmem_cache_zalloc()申請的。

注2:anonymous pages不屬于dirty pages。

參見mm/vmscan.c: page_check_dirty_writeback()

“Anonymous pages are not handled by flushers and must be written from reclaim context.”

為什麼【Active(anon)+Inactive(anon)】不等于AnonPages?

因為Shmem(即Shared memory & tmpfs) 被計入LRU Active/Inactive(anon),但未計入 AnonPages。是以一個更合理的等式是:

【Active(anon)+Inactive(anon)】 = 【AnonPages + Shmem】

但是這個等式在某些情況下也不一定成立,因為:

如果shmem或anonymous pages被mlock的話,就不在Active(non)或Inactive(anon)裡了,而是到了Unevictable裡,以上等式就不平衡了;

當anonymous pages準備被swap-out時,分幾個步驟:先被加進swap cache,再離開AnonPages,然後離開LRU Inactive(anon),最後從swap cache中删除,這幾個步驟之間會有間隔,而且有可能離開AnonPages就因某些情況而結束了,是以在某些時刻以上等式會不平衡。

【注:參見mm/vmscan.c: shrink_page_list():

它調用的add_to_swap()會把swap cache頁面标記成dirty,然後調用try_to_unmap()将頁面對應的page table mapping都删除,再調用pageout()回寫dirty page,最後try_to_free_swap()把該頁從swap cache中删除。】

為什麼【Active(file)+Inactive(file)】不等于Mapped?

因為LRU Active(file)和Inactive(file)中不僅包含mapped頁面,還包含unmapped頁面;

Mapped中包含”Shmem”(即shared memory & tmpfs),這部分記憶體被計入了LRU Active(anon)或Inactive(anon)、而不在Active(file)和Inactive(file)中。

為什麼【Active(file)+Inactive(file)】不等于 Cached?

因為”Shmem”(即shared memory & tmpfs)包含在Cached中,而不在Active(file)和Inactive(file)中;

Active(file)和Inactive(file)還包含Buffers。

如果不考慮mlock的話,一個更符合邏輯的等式是:

【Active(file) + Inactive(file) + Shmem】== 【Cached + Buffers】

如果有mlock的話,等式應該如下(mlock包括file和anon兩部分,/proc/meminfo中并未分開統計,下面的mlock_file隻是用來表意,實際并沒有這個統計值):

【Active(file) + Inactive(file) + Shmem + mlock_file】== 【Cached + Buffers】

注:

測試的結果以上等式通常都成立,但記憶體發生交換的時候以上等式有時不平衡,我猜可能是因為有些屬于Shmem的記憶體swap-out的過程中離開Cached進入了Swapcached,但沒有立即從swap cache删除、仍算在Shmem中的緣故。

Linux的記憶體都用到哪裡去了?

盡管不可能精确統計Linux系統的記憶體,但大體了解還是可以的。

kernel記憶體的統計方式應該比較明确,即

【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】

注1:VmallocUsed其實不是我們感興趣的,因為它還包括了VM_IOREMAP等并未消耗實體記憶體的IO位址映射空間,我們隻關心VM_ALLOC操作,(參見1.2節),是以實際上應該統計/proc/vmallocinfo中的vmalloc記錄,例如(此處機關是byte):

# grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'
23375872
           

注2:kernel module的記憶體被包含在VmallocUsed中,見1.3節。

注3:X表示直接通過alloc_pages/__get_free_page配置設定的記憶體,沒有在/proc/meminfo中統計,不知道有多少,就像個黑洞。

使用者程序的記憶體主要有三種統計口徑:

圍繞LRU進行統計

【(Active + Inactive + Unevictable) + (HugePages_Total * Hugepagesize)】

圍繞Page Cache進行統計

當SwapCached為0的時候,使用者程序的記憶體總計如下:

【(Cached + AnonPages + Buffers) + (HugePages_Total * Hugepagesize)】

當SwapCached不為0的時候,以上公式不成立,因為SwapCached可能會含有Shmem,而Shmem本來被含在Cached中,一旦swap-out就從Cached轉移到了SwapCached,可是我們又不能把SwapCached加進上述公式中,因為SwapCached雖然不與Cached重疊卻與AnonPages有重疊,它既可能含有Shared memory又可能含有Anonymous Pages。

圍繞RSS/PSS進行統計

把/proc/[1-9]*/smaps 中的 Pss 累加起來就是所有使用者程序占用的記憶體,但是還沒有包括Page Cache中unmapped部分、以及HugePages,是以公式如下:

ΣPss + (Cached – mapped) + Buffers + (HugePages_Total * Hugepagesize)

是以系統記憶體的使用情況可以用以下公式表示:

MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【Active + Inactive + Unevictable + (HugePages_Total * Hugepagesize)】

MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【Cached + AnonPages + Buffers + (HugePages_Total * Hugepagesize)】

MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【ΣPss + (Cached – mapped) + Buffers + (HugePages_Total * Hugepagesize)】

繼續閱讀