聊聊 Linux 的內存統計

寫在前面

本文主要分析 Linux 系統內存統計的一些指標以及進程角度內存使用監控的一些方法。

開始閱讀這篇文章前,請先簡單閱讀下面的幾篇文章。

  • 《進程眼中的線性地址空間》

  • 《線程眼中的線性地址空間》

  • 《聊聊內存管理》

想必這幾篇文章過後,基本概念就不需要再贅述了。所以下文直接就找一臺 Intel x86_64 架構下安裝了 64bit Linux 系統的服務器作為例進行相關的實驗和結果分析。Linux 的內存管理從物理內存管理到虛擬內存管理涉及的概念和統計項實在太多,本文從實用和系統運維的角度出發,只列舉一些最實用的統計。

從 free 命令開始

上面的背景介紹文章把內存相關的基礎概念講的差不多了,這裡不再贅述。本文定位是內存統計,所以從最基礎的內存統計的命令—<code>free/<code>命令開始。執行<code>free/<code>命令,可以看到如下的輸出:

聊聊 Linux 的内存统计

縱向是內存和<code>Swap/<code>分區,橫向是統計項。縱向的含義以及<code>Swap/<code>不需要解釋,我們看橫向的統計項:

  • total — 系統總內存(其實就是從 <code>/proc/meminfo/<code>獲取的)

  • used — 已使用內存

  • free — 未使用的內存

  • shared — 共享內存的大小,主要是 <code>tmpfs/<code>

  • buff / cache — <code>buffers/<code>和<code>cache/<code>使用的內存之和

  • available — 可用內存,可以簡單理解為未使用的內存和可釋放的內存之和(buffer、cache 可以釋放大部分,所以這裡近似等於 free + buffer / cache 的大小)

這臺機器的系統和內核稍微新一點,這個輸出可能和你看到的不一樣,早先的<code>free/<code>命令的輸出是這樣:

聊聊 Linux 的内存统计

這裡的<code>shared/<code>為0,因為這臺服務器沒用共享內存。這裡多解釋下<code>-/+ buffer/cache/<code>這行,字面意思就是<code>used - buffers/cache/<code>和<code>used + buffers/cache/<code>。前者指的是從應用程序角度系統被用掉了多少內存,後者指的是從應用程序角度看系統還有多少內存能用。聽起來很複雜,其實說白了就是因為<code>buffers/<code>和<code>cached/<code>可以被釋放出來,多幾個指標看看系統還能用多少內存而已。

下面用幾個公式來解釋這個輸出:

<table><tbody>
1
2
3
4
5
6
7
8
9
10
11
# 內存總量 = 已使用內存 + 空閒內存
`total` = `used` + `free`

# 系統被用掉的內存
`-buffers/cache` = `used` - `buffers` - `cached`

# 系統還能用的內存
`+buffers/cache` = `free` + `buffers` + `cached`

# 所以,其實還有下面的公式
`total` = `-buffers/cache` + `+buffers/cache`

/<tbody>/<table>

<code>buffers/cached/<code>不是100%都能釋放出來使用的,上面的“可用內存”其實就是個近似值。最上面新版本系統的輸出中有一個<code>available/<code>項目表示可用內存,值小於<code>free + buff/cache/<code>,內核 3.14 之後支持該特性(雖然也不是絕對意義上的精確的可用內存大小,囧)。

這裡稍微多說一點<code>buffers/<code>和<code>cached/<code>。Linux 2.4.10 內核之前,磁盤的緩存有兩種,即<code>Buffer Cache/<code>和<code>Page Cache/<code>。前者緩存管理磁盤文件系統時讀取的塊,後者存放訪問具體文件內容時生成的頁。在 2.4.10 之後,<code>Buffer Cache/<code>這個概念就不存在了,這些數據被放在<code>Page Cache/<code>中(這種<code>Page/<code>被稱為<code>Buffer Pages/<code>)。

簡而言之,現在磁盤的 cache 只有 <code>Page Cache/<code>一種,在<code>Page Cache/<code>中,有一種<code>Page/<code>叫<code>Buffer Page/<code>,這種<code>Page/<code>都與一個叫<code>buffer_head/<code>的數據結構關聯,這些頁也就在內存統計中用<code>buffers/<code>這個指標來單獨統計了。

/proc/meminfo 詳解

很多命令的內存統計都是從<code>/proc/meminfo/<code>讀取的。鑑於<code>/proc/meminfo/<code>的 man 文檔(<code>man proc/<code>)寫的實在不夠清晰,很多條目居然還是<code>To be documented/<code>狀態,所以這裡逐一列舉出來常見的統計項解釋一下。

首先明確一點,內核目前並沒有絕對精確的統計所有的內存使用量,比如<code>alloc_pages/<code>接口申請的內存不一定被統計在內(除非所有調用<code>alloc_pages/<code>的代碼主動進行統計,如果某些不講究的驅動程序沒有主動統計的話統計值就肯定對不上了)。

先看這三項全局統計:

  • MemTotal — 總的全局可用內存大小(即物理<code>RAM/<code>減去保留的以及內核代碼佔用的,系統啟動後一般固定不變)

  • MemFree — 總的全局未使用內存大小

  • MemAvailable — 內核估計出來的全局可用內存大小,非精確值(<code>MemFree/<code>不代表所有可用的內存,<code>Cache/Buffer/<code>、<code>Slab/<code>均有部分可以臨時釋放的內存要計算在內)

用戶進程的內存頁分為兩種:

  1. 與文件關聯的內存頁(<code>File-backed Pages/<code>), 比如程序文件、讀取文件時數據對應的緩存頁

  2. 與文件無關的匿名內存頁(<code>Anonymous Pages/<code>),比如進程的堆、棧等分配的內存

所有<code>Page Cache/<code>裡的頁面都是<code>File-backed Pages/<code>,<code>File-backed Pages/<code>在內存不足的時候可以直接寫回對應的硬盤文件裡,即<code>Page-out/<code>。而<code>Anonymous Pages/<code>在內存不足時就只能寫到硬盤上的交換區<code>Swap/<code>裡來釋放內存,稱之為<code>Swap-out/<code>。

<code>Anonymous Pages/<code>與用戶進程共存,進程退出則<code>Anonymous pages/<code>釋放,而<code>Page Cache/<code>即使在進程退出後還可以緩存。

下面是磁盤緩存相關的統計項:

  • Buffers — 塊設備所佔用的緩存頁,比如磁盤文件系統的<code>meta/<code>信息如<code>SuperBlock/<code>等,直接讀寫塊設備產生的緩存也統計在這裡(例如<code>dd/<code>命令)

  • Cached — 從磁盤讀取的文件內容緩存(即<code>Page cache/<code>)

  • SwapCached — <code>Swap/<code>中包含的確定要被換出,但是尚未寫入物理交換區的匿名內存頁

  • SwapTotal — 可用的磁盤<code>Swap/<code>總大小

  • SwapFree — 磁盤<code>Swap/<code>的<code>free/<code>大小

  • Dirty — 修改了等待寫回磁盤的內存大小

  • Writeback — 正在寫回磁盤的內存大小

以下幾項和內核的<code>頁面回收算法(Page Frame Reclaiming)/<code>相關,<code>Page Cache/<code>和所有用戶進程的內存(除內核棧和<code>HugePages/<code>外)都在相關的<code>LRU Lists/<code>上。內核在 2.6 以前就引入了<code>增強的LRU算法/<code>來解決<code>樸素的LRU算法/<code>完全不考慮使用頻率的問題。具體的<code>Active 鏈表/<code>和<code>Inactive 鏈表/<code>的使用詳情請參閱其他資料。

  • Active — 最近使用的內存,回收的優先級低

  • Inactive — 最近較少使用的內存,回收的優先級高

  • Active (anon) — <code>Active 鏈表/<code>中的匿名頁(<code>Anonymous Pages/<code>)部分

  • Inactive (anon) — <code>Inactive 鏈表/<code>中的匿名頁(<code>Anonymous Pages/<code>)部分

  • Active (file) — <code>Active 鏈表/<code>中的<code>File-backed Pages/<code>部分

  • Inactive (file) — <code>Inactive 鏈表/<code>中的<code>File-backed Pages/<code>部分

  • Unevictable — 禁止換出的頁,對應<code>Unevictable 鏈表/<code>,其中包括<code>VM_LOCKED/<code>的內存頁、<code>SHM_LOCK/<code>的共享內存頁(也統計在<code>Mlocked/<code>中)、和<code>Ramfs/<code>等

  • Mlocked — <code>mlock/<code>系統調用鎖定的內存大小

共享內存在 Linux 中細分的話可以分為以下幾種:

  • SystemV Shared Memory — <code>shmget/<code>

  • POSIX Shared Memory — <code>shm_open/<code>

  • Shared Anonymous Memory — <code>mmap(MAP_ANONYMOUS | MAP_SHARED)/<code>

共享內存在內核中都是 基於tmpf機制實現 的。因為基於文件系統所以就不能算是匿名頁,不能計入<code>AnonPages/<code>的統計項,而只能計入<code>Cached/<code>和<code>Mapped/<code>統計項。但是,<code>tmpfs/<code>背後並沒有真實的磁盤文件存在,如果想要被臨時釋放出來,只能通過<code>Swap/<code>的方式,所以內存頁被鏈接到了<code>Inactive(anon)/<code>和<code>Active(anon)/<code>裡。

也就是說,共享內存的頁面屬於<code>File-backed Pages/<code>,但是被放在<code>Inactive(anon)/<code>和<code>Active(anon)/<code>鏈表裡,統計也不算在<code>AnonPages/<code>裡,而是算在<code>Cached/<code>和<code>Mapped/<code>裡。特別地,如果這些頁被<code>mlock/<code>的話,就放在<code>Unevictable/<code>鏈裡並計算在內。所以從數值上看,<code>Inactive(anon)/<code>項 +<code>Active(anon)/<code>項 不等於<code>AnonPages/<code>項,因為前者包括共享內存的部分。<code>Active(file)/<code>項 +<code>Inactive(file)/<code>項 也不等於<code>M

apped/<code>項,因為前者中包括<code>Unmapped/<code>的內存,後者還包含共享內存的部分(這部分在<code>Inactive(anon)/<code>和<code>Active(anon)/<code>裡)。

這裡有一個情況要注意,與文件關聯的頁也有可能是匿名頁(<code>MAP_PRIVATE/<code>映射的頁面被修改時會產生一個匿名頁拷貝),會被算到<code>AnonPages/<code>裡。

與此相關的相關的統計項有:

  • AnonPages — 匿名頁(<code>Anonymous pages/<code>)的大小,同時也包含<code>Transparent HugePages (THP)/<code>對應的 AnonHugePages

  • Mapped — 設備和文件等映射的大小,<code>Mapped/<code>統計了<code>Cached/<code>中所有的<code>Mapped/<code>頁面,是<code>Cached/<code>的子集(滿足<code>Cached/<code>-<code>Mapped/<code>=<code>Unmapped/<code>)。共享內存、可執行程序的文件、動態庫、<code>mmap/<code>的文件等都統計在這裡

  • Shmem — 共享內存的大小,包括<code>Shared Memory/<code>、<code>tmpfs/<code>和<code>devtmpfs/<code>

注意 Linux 的內存是真正使用時才分配的,所以注意這裡的大小都是已分配的大小,而不是程序裡申請的大小。

下面都是內核使用的內存相關的統計項:

  • Slab — 內核<code>Slab/<code>結構使用的大小(就是那個Slab分配器佔用的)

  • SReclaimable — 內核<code>Slab/<code>裡面可回收的部分(調用<code>kmem_getpages()/<code>時帶有 SLAB_RECLAIM_ACCOUNT 標的)

  • SUnreclaim — <code>Slab/<code>裡面無法回收的大小,等於<code>Slab/<code>項 -<code>SReclaimable/<code>項

  • KernelStack — 分配給內核棧的大小(每個用戶線程都會分配一個<code>Kernel Stack/<code>,系統調用<code>syscall/<code>、<code>trap/<code>、<code>exception/<code>後進入內核態執行代碼時候使用)

  • PageTables — 頁表的大小(就是經常掛在嘴上的那個頁表)

  • NFS_Unstable — 發送到服務端但尚未提交的 NFS 頁的大小

  • Bounce — 塊設備 “bounce buffers” 部分的大小(有些老設備只能訪問低端內存,比如 16M 以下,這部分分配的 buffer 統計在這裡)

  • WritebackTmp — FUSE 用於寫回磁盤的緩衝區的大小

  • VmallocTotal — vmalloc 區域大小

  • VmallocUsed — vmalloc 區域使用大小

  • VmallocChunk — vmalloc 區域最大的 free 連續區塊大小

  • HardwareCorrupted — 系統檢測到內存的硬件故障的內存大小(問題頁會被記錄不再使用)

之前說過,HugePages 是獨立統計的,如果進程使用了 HugePages,是不會計入自身的<code>RSS/PSS/<code>的。注意下面的<code>AnonHugePages/<code>指的是透明大頁(THP,Transparent HugePages),<code>THP/<code>是統計在進程的<code>RSS/PSS/<code>裡的,要注意區別。下面是相關的統計項:

  • AnonHugePages — 透明大頁 THP 使用的大小

  • HugePages_Total — 內存大頁的總量,對應 <code>/proc/sys/vm/nr_hugepages/<code>,可以動態改

  • HugePages_Free — 內存大頁中 free 的大小

  • HugePages_Rsvd — 內存大頁中能分配出來的大小

  • HugePages_Surp — 內存大頁中超過 <code>/proc/sys/vm/nr_hugepages/<code>的大小, 最大值由<code>/proc/sys/vm/nr_overcommit_hugepages/<code>限制

  • Hugepagesize — 內存大頁的頁大小

進程級別的統計

先介紹幾個通用概念:

  • VSS - <code>Virtual Set Size/<code>,虛擬內存大小,包含共享庫佔用的全部內存,以及分配但未使用內存

  • RSS - <code>Resident Set Size/<code>,實際使用物理內存,包含了共享庫佔用的全部內存

  • PSS - <code>Proportional Set Size/<code>,實際使用的物理內存,共享庫佔用的內存按照進程數等比例劃分

  • USS - <code>Unique Set Size/<code>,進程獨自佔用的物理內存,不包含共享庫佔用的內存

/proc/{pid}/smaps 文件

在<code>/proc/{pid}/smaps/<code>文件對應每個進程的詳細內存分段統計。截取一部分:

聊聊 Linux 的内存统计

下面分別解釋下含義:

  • Size:映射的大小(<code>mapping size/<code>)

  • Rss:實際駐留在<code>RAM/<code>的內存大小(包括共享庫的大小,不包括已經交換出去的頁面)

  • Pss:Rss 的基礎上,把共享庫的大小均攤給所有被映射的進程後的大小

  • Shared_Clean:共享的<code>Clean/<code>內存的大小

  • Shared_Dirty:共享的<code>Dirty/<code>內存的大小

  • Private_Clean:私有的<code>Clean/<code>內存的大小

  • Private_Dirty:私有的<code>Dirty/<code>內存的大小

  • Referenced:當前被標記為引用的頁的大小

  • Anonymous:匿名內存的大小

  • AnonHugePages:透明大頁內存的大小

  • Swap:<code>Swap/<code>的大小

  • KernelPageSize:內核頁大小

  • MMUPageSize:<code>MMU/<code>頁大小

  • Locked:被<code>mlock/<code>的內存大小

  • VmFlags:頁的標誌位,有點多這裡不列舉,詳見參考資料 [4]

可以看到<code>Rss/<code>這個指標實際上是包含了共享庫的大小的,不同的進程會共享這個映射的,如果想通過累加這個值來計算所有進程用到的內存的話就不準確了,而<code>Pss/<code>把共享庫的大小均攤給了所有用到映射了這個庫的進程,所以累加起來就不會重複計算共享庫大小了。

P.S. 最新的內核文檔提到了要加<code>smaps_rollup/<code>這個統計,支持<code>Pss_Anon/<code>、<code>Pss_File/<code>和<code>Pss_Shmem/<code>三個分類統計,這個在進程級別看,用到內存就很清晰了。

我們可以累加一下這個值看看某進程用到的內存總和:

聊聊 Linux 的内存统计

注意單位是KB,所以這裡進程用到的內存是 1.17 GB 左右。

這是個使用共享內存作為存儲的服務,所以這是符合預期的。如果想要看排除共享內存的部分,那要看<code>Anonymous/<code>部分的總和:

聊聊 Linux 的内存统计

所以實際匿名內存使用是 63 MB 左右。

top 命令

<code>top/<code>命令中關於內存使用的統計:

聊聊 Linux 的内存统计

內存相關的統計有<code>VIRT/<code>、<code>RES/<code>、<code>SHR/<code>、<code>SWAP/<code>、<code>CODE/<code>、<code>DATA/<code>、<code>USED/<code>

  • VIRT — <code>Virtual Memory Size/<code>,虛擬內存大小,包括所有代碼、數據和共享庫,以及已交換的頁面和已映射但未使用的內存

  • RES — <code>Resident Memory Size/<code>,駐留內存大小,共享的內存比如動態庫也會計算在內

  • SHR — <code>Shared Memory Size/<code>,共享的內存大小,並非所有共享的內存都是常駐的

  • SWAP — Swapped Size,非駐留內存大小

  • CODE — Code Size,程序可執行代碼的大小

  • DATA — Data + Stack Size,可執行代碼以外的物理內存量,也稱為數據駐留集大小

  • USED — Memory in Use,RES + SWAP 的大小

其他的內存查看命令

常用的還有這些:<code>vmstat/<code>、<code>sar/<code>、<code>slabtop/<code>、<code>kmstat/<code>、<code>ps/<code>、<code>prstat/<code>、<code>pmap/<code>等等。懶得寫了,有問題看<code>man/<code>文檔得了。

參考文獻

[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


分享到:


相關文章: