寫在前面
本文主要分析 Linux 系統內存統計的一些指標以及進程角度內存使用監控的一些方法。
開始閱讀這篇文章前,請先簡單閱讀下面的幾篇文章。
-
《進程眼中的線性地址空間》
-
《線程眼中的線性地址空間》
-
《聊聊內存管理》
想必這幾篇文章過後,基本概念就不需要再贅述瞭。所以下文直接就找一臺 Intel x86_64 架構下安裝瞭 64bit Linux 系統的服務器作為例進行相關的實驗和結果分析。Linux 的內存管理從物理內存管理到虛擬內存管理涉及的概念和統計項實在太多,本文從實用和系統運維的角度出發,隻列舉一些最實用的統計。
從 free 命令開始
上面的背景介紹文章把內存相關的基礎概念講的差不多瞭,這裡不再贅述。本文定位是內存統計,所以從最基礎的內存統計的命令—free
命令開始。執行free
命令,可以看到如下的輸出:
縱向是內存和Swap
分區,橫向是統計項。縱向的含義以及Swap
不需要解釋,我們看橫向的統計項:
-
total — 系統總內存(其實就是從
/proc/meminfo
獲取的) -
used — 已使用內存
-
free — 未使用的內存
-
shared — 共享內存的大小,主要是
tmpfs
-
buff / cache —
buffers
和cache
使用的內存之和 -
available — 可用內存,可以簡單理解為未使用的內存和可釋放的內存之和(buffer、cache 可以釋放大部分,所以這裡近似等於 free + buffer / cache 的大小)
這臺機器的系統和內核稍微新一點,這個輸出可能和你看到的不一樣,早先的free
命令的輸出是這樣:
這裡的shared
為0,因為這臺服務器沒用共享內存。這裡多解釋下-/+ buffer/cache
這行,字面意思就是used - buffers/cache
和used + buffers/cache
。前者指的是從應用程序角度系統被用掉瞭多少內存,後者指的是從應用程序角度看系統還有多少內存能用。聽起來很復雜,其實說白瞭就是因為buffers
和cached
可以被釋放出來,多幾個指標看看系統還能用多少內存而已。
下面用幾個公式來解釋這個輸出:
1 |
# 內存總量 = 已使用內存 + 空閑內存 |
buffers/cached
不是100%都能釋放出來使用的,上面的“可用內存”其實就是個近似值。最上面新版本系統的輸出中有一個available
項目表示可用內存,值小於free + buff/cache
,內核 3.14 之後支持該特性(雖然也不是絕對意義上的精確的可用內存大小,囧)。
這裡稍微多說一點buffers
和cached
。Linux 2.4.10 內核之前,磁盤的緩存有兩種,即Buffer Cache
和Page Cache
。前者緩存管理磁盤文件系統時讀取的塊,後者存放訪問具體文件內容時生成的頁。在 2.4.10 之後,Buffer Cache
這個概念就不存在瞭,這些數據被放在Page Cache
中(這種Page
被稱為Buffer Pages
)。
簡而言之,現在磁盤的 cache 隻有 Page Cache
一種,在Page Cache
中,有一種Page
叫Buffer Page
,這種Page
都與一個叫buffer_head
的數據結構關聯,這些頁也就在內存統計中用buffers
這個指標來單獨統計瞭。
/proc/meminfo 詳解
很多命令的內存統計都是從/proc/meminfo
讀取的。鑒於/proc/meminfo
的 man 文檔(man proc
)寫的實在不夠清晰,很多條目居然還是To be documented
狀態,所以這裡逐一列舉出來常見的統計項解釋一下。
首先明確一點,內核目前並沒有絕對精確的統計所有的內存使用量,比如alloc_pages
接口申請的內存不一定被統計在內(除非所有調用alloc_pages
的代碼主動進行統計,如果某些不講究的驅動程序沒有主動統計的話統計值就肯定對不上瞭)。
先看這三項全局統計:
-
MemTotal — 總的全局可用內存大小(即物理
RAM
減去保留的以及內核代碼占用的,系統啟動後一般固定不變) -
MemFree — 總的全局未使用內存大小
-
MemAvailable — 內核估計出來的全局可用內存大小,非精確值(
MemFree
不代表所有可用的內存,Cache/Buffer
、Slab
均有部分可以臨時釋放的內存要計算在內)
用戶進程的內存頁分為兩種:
-
與文件關聯的內存頁(
File-backed Pages
), 比如程序文件、讀取文件時數據對應的緩存頁 -
與文件無關的匿名內存頁(
Anonymous Pages
),比如進程的堆、棧等分配的內存
所有Page Cache
裡的頁面都是File-backed Pages
,File-backed Pages
在內存不足的時候可以直接寫回對應的硬盤文件裡,即Page-out
。而Anonymous Pages
在內存不足時就隻能寫到硬盤上的交換區Swap
裡來釋放內存,稱之為Swap-out
。
Anonymous Pages
與用戶進程共存,進程退出則Anonymous pages
釋放,而Page Cache
即使在進程退出後還可以緩存。
下面是磁盤緩存相關的統計項:
-
Buffers — 塊設備所占用的緩存頁,比如磁盤文件系統的
meta
信息如SuperBlock
等,直接讀寫塊設備產生的緩存也統計在這裡(例如dd
命令) -
Cached — 從磁盤讀取的文件內容緩存(即
Page cache
) -
SwapCached —
Swap
中包含的確定要被換出,但是尚未寫入物理交換區的匿名內存頁 -
SwapTotal — 可用的磁盤
Swap
總大小 -
SwapFree — 磁盤
Swap
的free
大小 -
Dirty — 修改瞭等待寫回磁盤的內存大小
-
Writeback — 正在寫回磁盤的內存大小
以下幾項和內核的頁面回收算法(Page Frame Reclaiming)
相關,Page Cache
和所有用戶進程的內存(除內核棧和HugePages
外)都在相關的LRU Lists
上。內核在 2.6 以前就引入瞭增強的LRU算法
來解決樸素的LRU算法
完全不考慮使用頻率的問題。具體的Active 鏈表
和Inactive 鏈表
的使用詳情請參閱其他資料。
-
Active — 最近使用的內存,回收的優先級低
-
Inactive — 最近較少使用的內存,回收的優先級高
-
Active (anon) —
Active 鏈表
中的匿名頁(Anonymous Pages
)部分 -
Inactive (anon) —
Inactive 鏈表
中的匿名頁(Anonymous Pages
)部分 -
Active (file) —
Active 鏈表
中的File-backed Pages
部分 -
Inactive (file) —
Inactive 鏈表
中的File-backed Pages
部分 -
Unevictable — 禁止換出的頁,對應
Unevictable 鏈表
,其中包括VM_LOCKED
的內存頁、SHM_LOCK
的共享內存頁(也統計在Mlocked
中)、和Ramfs
等 -
Mlocked —
mlock
系統調用鎖定的內存大小
共享內存在 Linux 中細分的話可以分為以下幾種:
-
SystemV Shared Memory —
shmget
-
POSIX Shared Memory —
shm_open
-
Shared Anonymous Memory —
mmap(MAP_ANONYMOUS | MAP_SHARED)
共享內存在內核中都是 基於tmpf機制實現 的。因為基於文件系統所以就不能算是匿名頁,不能計入AnonPages
的統計項,而隻能計入Cached
和Mapped
統計項。但是,tmpfs
背後並沒有真實的磁盤文件存在,如果想要被臨時釋放出來,隻能通過Swap
的方式,所以內存頁被鏈接到瞭Inactive(anon)
和Active(anon)
裡。
也就是說,共享內存的頁面屬於File-backed Pages
,但是被放在Inactive(anon)
和Active(anon)
鏈表裡,統計也不算在AnonPages
裡,而是算在Cached
和Mapped
裡。特別地,如果這些頁被mlock
的話,就放在Unevictable
鏈裡並計算在內。所以從數值上看,Inactive(anon)
項 +Active(anon)
項 不等於AnonPages
項,因為前者包括共享內存的部分。Active(file)
項 +Inactive(file)
項 也不等於Mapped
項,因為前者中包括Unmapped
的內存,後者還包含共享內存的部分(這部分在Inactive(anon)
和Active(anon)
裡)。
這裡有一個情況要註意,與文件關聯的頁也有可能是匿名頁(MAP_PRIVATE
映射的頁面被修改時會產生一個匿名頁拷貝),會被算到AnonPages
裡。
與此相關的相關的統計項有:
-
AnonPages — 匿名頁(
Anonymous pages
)的大小,同時也包含Transparent HugePages (THP)
對應的 AnonHugePages -
Mapped — 設備和文件等映射的大小,
Mapped
統計瞭Cached
中所有的Mapped
頁面,是Cached
的子集(滿足Cached
–Mapped
=Unmapped
)。共享內存、可執行程序的文件、動態庫、mmap
的文件等都統計在這裡 -
Shmem — 共享內存的大小,包括
Shared Memory
、tmpfs
和devtmpfs
註意 Linux 的內存是真正使用時才分配的,所以註意這裡的大小都是已分配的大小,而不是程序裡申請的大小。
下面都是內核使用的內存相關的統計項:
-
Slab — 內核
Slab
結構使用的大小(就是那個Slab分配器占用的) -
SReclaimable — 內核
Slab
裡面可回收的部分(調用kmem_getpages()
時帶有 SLAB_RECLAIM_ACCOUNT 標的) -
SUnreclaim —
Slab
裡面無法回收的大小,等於Slab
項 –SReclaimable
項 -
KernelStack — 分配給內核棧的大小(每個用戶線程都會分配一個
Kernel Stack
,系統調用syscall
、trap
、exception
後進入內核態執行代碼時候使用) -
PageTables — 頁表的大小(就是經常掛在嘴上的那個頁表)
-
NFS_Unstable — 發送到服務端但尚未提交的 NFS 頁的大小
-
Bounce — 塊設備 “bounce buffers” 部分的大小(有些老設備隻能訪問低端內存,比如 16M 以下,這部分分配的 buffer 統計在這裡)
-
WritebackTmp — FUSE 用於寫回磁盤的緩沖區的大小
-
VmallocTotal — vmalloc 區域大小
-
VmallocUsed — vmalloc 區域使用大小
-
VmallocChunk — vmalloc 區域最大的 free 連續區塊大小
-
HardwareCorrupted — 系統檢測到內存的硬件故障的內存大小(問題頁會被記錄不再使用)
之前說過,HugePages 是獨立統計的,如果進程使用瞭 HugePages,是不會計入自身的RSS/PSS
的。註意下面的AnonHugePages
指的是透明大頁(THP,Transparent HugePages),THP
是統計在進程的RSS/PSS
裡的,要註意區別。下面是相關的統計項:
-
AnonHugePages — 透明大頁 THP 使用的大小
-
HugePages_Total — 內存大頁的總量,對應
/proc/sys/vm/nr_hugepages
,可以動態改 -
HugePages_Free — 內存大頁中 free 的大小
-
HugePages_Rsvd — 內存大頁中能分配出來的大小
-
HugePages_Surp — 內存大頁中超過
/proc/sys/vm/nr_hugepages
的大小, 最大值由/proc/sys/vm/nr_overcommit_hugepages
限制 -
Hugepagesize — 內存大頁的頁大小
進程級別的統計
先介紹幾個通用概念:
-
VSS –
Virtual Set Size
,虛擬內存大小,包含共享庫占用的全部內存,以及分配但未使用內存 -
RSS –
Resident Set Size
,實際使用物理內存,包含瞭共享庫占用的全部內存 -
PSS –
Proportional Set Size
,實際使用的物理內存,共享庫占用的內存按照進程數等比例劃分 -
USS –
Unique Set Size
,進程獨自占用的物理內存,不包含共享庫占用的內存
/proc/{pid}/smaps 文件
在/proc/{pid}/smaps
文件對應每個進程的詳細內存分段統計。截取一部分:
下面分別解釋下含義:
-
Size:映射的大小(
mapping size
) -
Rss:實際駐留在
RAM
的內存大小(包括共享庫的大小,不包括已經交換出去的頁面) -
Pss:Rss 的基礎上,把共享庫的大小均攤給所有被映射的進程後的大小
-
Shared_Clean:共享的
Clean
內存的大小 -
Shared_Dirty:共享的
Dirty
內存的大小 -
Private_Clean:私有的
Clean
內存的大小 -
Private_Dirty:私有的
Dirty
內存的大小 -
Referenced:當前被標記為引用的頁的大小
-
Anonymous:匿名內存的大小
-
AnonHugePages:透明大頁內存的大小
-
Swap:
Swap
的大小 -
KernelPageSize:內核頁大小
-
MMUPageSize:
MMU
頁大小 -
Locked:被
mlock
的內存大小 -
VmFlags:頁的標志位,有點多這裡不列舉,詳見參考資料 [4]
可以看到Rss
這個指標實際上是包含瞭共享庫的大小的,不同的進程會共享這個映射的,如果想通過累加這個值來計算所有進程用到的內存的話就不準確瞭,而Pss
把共享庫的大小均攤給瞭所有用到映射瞭這個庫的進程,所以累加起來就不會重復計算共享庫大小瞭。
P.S. 最新的內核文檔提到瞭要加
smaps_rollup
這個統計,支持Pss_Anon
、Pss_File
和Pss_Shmem
三個分類統計,這個在進程級別看,用到內存就很清晰瞭。
我們可以累加一下這個值看看某進程用到的內存總和:
註意單位是KB,所以這裡進程用到的內存是 1.17 GB 左右。
這是個使用共享內存作為存儲的服務,所以這是符合預期的。如果想要看排除共享內存的部分,那要看Anonymous
部分的總和:
所以實際匿名內存使用是 63 MB 左右。
top 命令
top
命令中關於內存使用的統計:
內存相關的統計有VIRT
、RES
、SHR
、SWAP
、CODE
、DATA
、USED
-
VIRT —
Virtual Memory Size
,虛擬內存大小,包括所有代碼、數據和共享庫,以及已交換的頁面和已映射但未使用的內存 -
RES —
Resident Memory Size
,駐留內存大小,共享的內存比如動態庫也會計算在內 -
SHR —
Shared Memory Size
,共享的內存大小,並非所有共享的內存都是常駐的 -
SWAP — Swapped Size,非駐留內存大小
-
CODE — Code Size,程序可執行代碼的大小
-
DATA — Data + Stack Size,可執行代碼以外的物理內存量,也稱為數據駐留集大小
-
USED — Memory in Use,RES + SWAP 的大小
其他的內存查看命令
常用的還有這些:vmstat
、sar
、slabtop
、kmstat
、ps
、prstat
、pmap
等等。懶得寫瞭,有問題看man
文檔得瞭。
參考文獻
[1] Understanding the Linux Kernel, Daniel Plerre Bovet / Marco Cesati, 2005-11
[2] Professional Linux Kernel Architecture, Wolfgang Mauerer, 2008-10-13
[3] Systems Performance: Enterprise and the Cloud, Brendan Gregg, 2013-10-26
[4] https://raw.githubusercontent.com/torvalds/linux/master/Documentation/filesystems/proc.txt
[5] https://en.wikipedia.org/wiki/Resident_set_size
[6] https://en.wikipedia.org/wiki/Proportional_set_size
[7] https://en.wikipedia.org/wiki/Unique_set_size