聊聊 Linux 的內存統計

2022-01-25 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
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`

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 的代碼主動進行統計,如果某些不講究的驅動程序沒有主動統計的話統計值就肯定對不上了)。

先看這三項全局統計:

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

與文件關聯的內存頁(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

相關焦點

  • 【底層原理】Linux內存管理
    作者:羅道文的私房菜原文連結:http://luodw.cc/2016/02/17/linux-memory/今天這篇文章主要是之前看linux內核相關知識和博客Gustavo Duarte我(指道文)主要是看了這篇博客,並且結合之前的知識,對內存管理的的理解又上升了一個檔次。所以想通過這篇文章總結下。我們先來看下linux內存布局,此圖比我之前寫的那篇文章寫的布局更詳細
  • Linux如何調試內存洩漏
    內存洩漏是指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存
  • 嵌入式Linux內存管理的一些知識點總結
    這個內存管理的知識點還真的需要我們專門的去理解一下,今天大家一起來學習學習嵌入式Linux內存管理的知識。1.不涉及linux內核的彙編知識,僅C語言層面解析1.回答:彙編主要處理的是寄存器地址(包括內容)的計算,進行一部分的地址轉換工作(當然,它是重要的);C語言處理了極大部分的系統內存管理工作。
  • linux伺服器內存異常,究竟在哪消耗了2.5G?
    linux伺服器內存異常,究竟在哪消耗了2.5G? 今天這個問題是未解之謎,還是挺神奇的,一起來看看吧~以下是一臺2核4G的伺服器,其中伺服器上沒運行任何程序,但4G內存就用了2.5G 作者:波波說運維來源:今日頭條|2020-07-28 09:48
  • Linux Used內存到底哪裡去了?
    30M,但是free看到內存卻已經使用了7,8G了,已經開始swap了,請問ps aux的實際物理內存統計是不是漏了哪些內存沒算?我有什麼辦法確定free中used的內存都去哪兒了呢?這個問題不止一個同學遇到過了,之前小王同學也遇到這個問題,內存的計算總是一個迷糊帳。我們今天來把它算個清楚下!
  • Linux下找出吃內存的方法總結
    linux下查詢進程佔用的內存方法總結,假設現在有一個「php-cgi」的進程 ,進程id為「25282」。
  • 宋寶華:世上最好的共享內存(Linux共享內存最透徹的一篇)上集
    早期的共享內存,著重於強調把同一片內存,map到多個進程的虛擬地址空間(在相應進程找到一個VMA區域),以便於CPU可以在各個進程訪問到這片內存。共享內存的方式有很多種,目前主流的方式仍然有:1.基於傳統SYS V的共享內存;2.基於POSIX mmap文件映射實現共享內存;3.通過memfd_create()和fd跨進程共享實現共享內存;4.多媒體、圖形領域廣泛使用的基於dma-buf的共享內存。
  • 聊聊:linux的那些常見目錄
    4. dev目錄說明:device 系統硬體設備目錄(linux系統所有的硬體都通過文件表示)例如:/dev/cdrom是光碟機 /dev/sda 是第一塊scsi6. proc目錄內存映射目錄,該目錄可以查看系統的相關信息
  • 郭健:Linux內存管理系統參數配置之OOM(內存耗盡)
    按照慣例,最後一章是參考文獻,本文的參考文獻都是來自linux內核的Documentation目錄,該目錄下有大量的文檔可以參考,每一篇都值得細細品味。二、什麼是OOMOOM就是out of memory的縮寫,雖然linux kernel有很多的內存管理技巧(從cache中回收、swap out等)來滿足各種應用空間的vm內存需求,但是,當你的系統配置不合理,讓一匹小馬拉大車的時候,linux kernel會運行非常緩慢並且在某個時間點分配page frame的時候遇到內存耗盡、無法分配的狀況
  • Linux內核中的內存管理
    在多CPU系統中,每個CPU對應一個node用於描述該CPU所「擁有」的內存區域。這裡說的「擁有」是指與該CPU具有最佳親緣性的內存區域,該CPU訪問這些內存時將獲得最高的性能。一塊CPU也可以訪問*非*它所「擁有」的內存區域,但訪問「非擁有內存區域」的性能不如訪問「擁有的內存區域」的性能。
  • Linux內存、Swap、Cache、Buffer
    在Linux系統下,我們一般不需要去釋放內存,因為系統已經將內存管理的很好,但是凡事也有例外。通過free命令看Linux內存    total:總內存大小。  used:已經使用的內存大小(這裡面包含cached和buffers和shared部分)。  free:空閒的內存大小。  shared:進程間共享內存(一般不會用,可以忽略)。
  • Linux內存、Swap、Cache、Buffer詳解(三)
    咱接著Linux內存、Swap、Cache、Buffer詳解(二)聊,前面提到Linux 內核會在內存將要耗盡時觸發內存自動回收工作。
  • linux系統下free命令內存計算邏輯
    本篇文章介紹下linux下free命令展示的內存相關計算邏輯。
  • Linux 內存分配流程及 kmalloc 解析
    start_kernel    |--->mm_init        |--->mem_initlinux4.14/init/main.c在 mem_init 函數中會初始化夥伴系統和先說兩個概念:外部碎片:有一段小內存,夾在兩個大內存中間,兩個大內存已經被分配給進程,這一段小內存由於過小,不夠申請者使用,就一直空閒。
  • linux內存管理之全局框架
    CPU虛擬地址的4G空間,通常劃分為兩部分,一部分為內核虛擬地址,通常為3G-4G之間,另一部分為用戶虛擬地址,通常為0G-3G之間,顯然,用戶進程能使用的虛擬地址範圍遠大於內核可以使用的虛擬地址空間,但是,物理內存只有局限性的幾M,幾G,內核虛擬地址如何使用物理內存,用戶空間如何使用物理內存,這些問題正是linux內存管理的關鍵。  2.
  • KSM機制剖析 — Linux 內核中的內存去耦合
    圖 2.下面,我們將探索這種 Linux 內存共享方法,以及如何使用該方法提高伺服器的內存密度,從而增加其託管其他應用程式或 VMs 的能力。其他技術支持存儲技術中的一個稱為去耦合(de-duplication)的最 新進展是 Linux 和其他系統管理程序中的內存共享的先驅。去耦合這種技術通過刪除冗餘數據(基於數據塊,或者基於更大的數據片段,比如文件)來減少已存儲的數據。
  • Linux內存初始化(上)
    有了armv8架構訪問內存的理解,我們來看下linux在內存這塊的初始化就更容易理解了。創建啟動頁表:在彙編代碼階段的head.S文件中,負責創建映射關係的函數是create_page_tables。彙編結束後的內存映射關係如下圖所示:
  • Linux 高級編程 - 共享內存 Shared Memory
    共享內存 Shared MemoryHello,大紮好,這次跟大家分享的是在 Linux 中最快的一種 IPC 方式:共享內存
  • 10 張圖解再談 Linux 物理內存和虛擬內存
    物理內存管理在Linux系統中通過分段和分頁機制,把物理內存劃分 4K 大小的內存頁 Page(也稱作頁框Page Frame),物理內存的分配和回收都是基於內存頁進行,把物理內存分頁管理的好處大大的。
  • 每個程式設計師都該了解一點 Linux 內存管理知識
    在內存極度緊張的時候, sshd 這種不怎麼佔內存的進程也無法倖免。覆巢之下,焉有完卵。OOM Killer 的全稱叫 Out Of Memory Killer,今天簡單給大家介紹下,它是 linux 內核的內存保護機制。當應用程式大量請求內存導致內存不足的時候,通常會觸發 OOM Killer,OOM Killer 會根據相對簡單粗暴的算法殺掉某個進程以解燃眉之急。