前言
我們都知道,free命令可以檢視linux系統裡的記憶體使用情況:
但是這個粒度比較粗,對實際的記憶體問題的除錯,幫助並不大。我們在開發或者除錯問題的過程中,有時候需要掌握更細粒度的系統記憶體的詳細的使用情況,這個時候/proc/meminfo的輸出內容就派上用場了。透過它的輸出資訊,我們可以:
清晰地知道系統中的記憶體瓶頸究竟在哪裡?(畢竟核心裡面使用記憶體的單元太多了)記憶體是被塊裝置的IO,也就是Buffer給佔用了,還是被Cache給佔用了?如果是被Cache給佔了,具體是被share memory給佔了,還是被mmap對映給佔了?透過slab記憶體的使用情況,也可以幫助定位我們的問題。比如:如果slab的dentry或者inode_cache佔據太多,就要考慮是不是開啟的檔案太多了。匿名對映的記憶體使用量是多少?既然大家碰到的問題裡有60%以上都是記憶體問題,那我們不妨仔細來探究下linux核心裡的記憶體詳情。
一、Linux記憶體總覽圖
二、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 kBMemFree: 282012 kBMemAvailable: 865620 kBBuffers: 0 kBCached: 854972 kBSwapCached: 130900 kBActive: 1308168 kBInactive: 1758160 kBActive(anon): 1010416 kBInactive(anon): 1370480 kBActive(file): 297752 kBInactive(file): 387680 kBUnevictable: 0 kBMlocked: 0 kBSwapTotal: 4063228 kBSwapFree: 3357108 kBDirty: 0 kBWriteback: 0 kBAnonPages: 2104412 kBMapped: 40988 kBShmem: 169540 kBSlab: 225420 kBSReclaimable: 134220 kBSUnreclaim: 91200 kBKernelStack: 5936 kBPageTables: 35628 kBNFS_Unstable: 0 kBBounce: 0 kBWritebackTmp: 0 kBCommitLimit: 5967744 kBCommitted_AS: 5626436 kBVmallocTotal: 34359738367 kBVmallocUsed: 351900 kBVmallocChunk: 34359363652 kBHardwareCorrupted: 0 kBAnonHugePages: 139264 kBHugePages_Total: 0HugePages_Free: 0HugePages_Rsvd: 0HugePages_Surp: 0Hugepagesize: 2048 kBDirectMap4k: 204484 kBDirectMap2M: 3915776 kB12345678910111213141516171819202122232425262728293031323334353637383940414243
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 allocatorkmalloc: 以位元組為單位分配物理地址連續的記憶體塊,它是以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中留下統計值,所以很難追蹤。
注:page cache比較特殊,很難區分是屬於kernel還是屬於程序,其中被程序mmap的頁面自然是屬於程序的了,而另一些頁面沒有被mapped到任何程序,那就只能算是屬於kernel了。
1. 核心
核心所用記憶體的靜態部分,比如核心程式碼、頁描述符等資料在引導階段就分配掉了,並不計入MemTotal裡,而是算作Reserved(在dmesg中能看到)。而核心所用記憶體的動態部分,是透過上文提到的幾個介面申請的,其中透過alloc_pages申請的記憶體有可能未納入統計,就像黑洞一樣。
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=80xffffc9000470b000-0xffffc90004710000 20480 agp_add_bridge+0x2aa/0x440 pages=4 vmalloc N0=40xffffc90004710000-0xffffc90004731000 135168 raw_init+0x41/0x141 pages=32 vmalloc N0=320xffffc90004736000-0xffffc9000473f000 36864 drm_ht_create+0x55/0x80 [drm] pages=8 vmalloc N0=80xffffc90004744000-0xffffc90004746000 8192 dm_table_create+0x9e/0x130 [dm_mod] pages=1 vmalloc N0=10xffffc90004746000-0xffffc90004748000 8192 dm_table_create+0x9e/0x130 [dm_mod] pages=1 vmalloc N0=1...123456789
注:/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}'2337587212
一些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 | lessModule Size Used byrpcsec_gss_krb5 31477 0 auth_rpcgss 59343 1 rpcsec_gss_krb5nfsv4 474429 0 dns_resolver 13140 1 nfsv4nfs 246411 1 nfsv4lockd 93977 1 nfssunrpc 295293 5 nfs,rpcsec_gss_krb5,auth_rpcgss,lockd,nfsv4fscache 57813 2 nfs,nfsv4...1234567891011
lsmod的資訊來自/proc/modules,它顯示的size包括init_size和core_size,相應的原始碼參見:
// kernel/module.cstatic int m_show(struct seq_file *m, void *p){... seq_printf(m, "%s %u", mod->name, mod->init_size + mod->core_size);...}12345678
注:我們可以在 /sys/module// 目錄下分別看到coresize和initsize的值。
kernel module的記憶體是透過vmalloc()分配的(參見下列原始碼),所以在/proc/vmallocinfo中會有記錄,也就是說我們可以不必透過”lsmod”命令來統計kernel module所佔的記憶體大小,透過/proc/vmallocinfo就行了,而且還比lsmod更準確,為什麼這麼說呢?
// kernel/module.cstatic 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()123456789101112
因為給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 floppyfloppy 69417 0 $ cat /proc/vmallocinfo > vmallocinfo.2# 然而,我們看到vmallocinfo中記錄的是分配了73728位元組:$ diff vmallocinfo.1 vmallocinfo.268a69> 0xffffffffa03d7000-0xffffffffa03e9000 73728 module_alloc_update_bounds+0x14/0x70 pages=17 vmalloc N0=17 # 為什麼lsmod看到的記憶體大小與vmallocinfo不同呢?# 因為給kernel module分配記憶體是以page為單位的,而且外加一個guard page# 我們來驗證一下:$ bc -q69417%40963881 <--- 不能被4096整除69417/409616 <--- 相當於16 pages,加上面的3881位元組,會分配17 pages18*4096 <--- 17 pages 加上 1個guard page73728 <--- 正好是vmallocinfo記錄的大小12345678910111213141516171819202122232425262728
所以結論是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: 0HugePages_Free: 0HugePages_Rsvd: 0HugePages_Surp: 0Hugepagesize: 2048 kB1234567
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: 128HugePages_Free: 128HugePages_Rsvd: 0HugePages_Surp: 0Hugepagesize: 2048 kB12345678910
使用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: 128HugePages_Free: 128HugePages_Rsvd: 128HugePages_Surp: 0Hugepagesize: 2048 kB12345
等到使用者程式真正讀寫Hugepages的時候,它才被消耗掉了,此時HugePages_Free會減少,HugePages_Rsvd也會減少。
HugePages_Total: 128HugePages_Free: 0HugePages_Rsvd: 0HugePages_Surp: 0Hugepagesize: 2048 kB12345
我們說過,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 kBRss: 0 kBPss: 0 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 0 kBPrivate_Dirty: 0 kBReferenced: 0 kBAnonymous: 0 kBAnonHugePages: 0 kBSwap: 0 kBKernelPageSize: 2048 kBMMUPageSize: 2048 kBLocked: 0 kBVmFlags: rd wr mr mw me de ht...1234567891011121314151617
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 kBRss: 313344 kBPss: 313344 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 0 kBPrivate_Dirty: 313344 kBReferenced: 239616 kBAnonymous: 313344 kBAnonHugePages: 313344 kBSwap: 0 kBKernelPageSize: 4 kBMMUPageSize: 4 kBLocked: 0 kBVmFlags: rd wr mr mw me dc ac hg mg12345678910111213141516
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 kB1234
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.txtLRU與/proc/meminfo中其他統計值的關係:
LRU中不包含HugePages_*。LRU包含了 Cached 和 AnonPages。2.4 Shmem
/proc/meminfo中的Shmem統計的內容包括:
shared memorytmpfs和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),...12345678910
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 CachedPage 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;...}12345678
“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}'2337587212
注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)】
轉自:https://blog.csdn.net/whbing1471/article/details/105468139/