1. 程式人生 > >linux 程序佔用記憶體查詢

linux 程序佔用記憶體查詢

作者: 黃永兵/譯 出處:51CTO.com 閱讀提示:本文是為那些經常疑惑的人準備的,“為什麼一個簡單的KDE文字編輯器要佔用25M記憶體?”導致大多數人認為許多Linux應用程式,特別是KDE或GNOME程式都象ps報告一樣臃腫...【51CTO.com獨家譯文】本文是為那些經常疑惑的人準備的,“為什麼一個簡單的KDE文字編輯器要佔用25M記憶體?”導致大多數人認為許多Linux應用程式,特別是KDE或GNOME程式都象ps報告一樣臃腫,雖然這可能是也可能不是真的,依賴於具體的程式,它通常不是真的,一些程式比它們看起來消耗更多的記憶體。ps工具能為一個程序輸出許多塊有關的資訊,象程序ID,當前執行狀態,資源利用情況等。其中可能輸出VSZ(代表虛擬設定大小)和RSS(駐留設定大小),它們經常被世界各地的計算機愛好者用來檢視程序佔用了多少記憶體。例如:下面是在我電腦上用ps aux命令為KEit的輸出:USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDdbunker 3468 0.0 2.7 25400 14452 ? S 20:19 0:00 kdeinit:kedit 按照ps的輸出,KEdit佔用了大約25M的虛擬大小記憶體空間,大約14M駐留大小空間(上面報告中的兩個數字都用k為單位),看起來大部分人都喜歡隨意選擇其中一個數字來表示某個程序的真實記憶體佔用情況。我現在暫時先不解釋VSZ和RSS之間的不同之處。不用說,前面那種認識是錯誤的!想要知道為什麼,必須先學習Linux是如何在程式中控制共享庫的。在Linux上的大部分主要程式使用共享庫有助於確定功能,例如:一個KDE檔案編輯程式將使用幾個KDE共享庫(為了允許與其他的KDE元件進行互動),幾個X庫(為了允許它顯示、拷貝和貼上影象)和幾個常用系統庫(為了允許它執行基本的操作)。大部分這些庫,特別是象libc這樣常用的庫,是被許多Linux程式使用的,正是由於有這些共享,Linux可以使用一個巨大的訣竅:它將只載入單個共享庫的拷貝到記憶體中,使用這一個拷貝就可以供每個引用它的程式使用。許多工具不再關心這個非常通用的技巧,這可能是個好現象也可能是個壞現象;它們只是簡單地報告某個程序使用了多少記憶體,而不管是否是與其他程序共享了部分記憶體,兩個程式使用一個很大的共享庫,並且它的大小傾向於它們記憶體使用的總和,共享庫被計入了雙倍的大小,如果你不清楚這一點將使你產生誤解。不幸的是,關於程序記憶體使用的準確表示法不是那麼容易獲得,不僅需要你理解系統是如何真實地工作的,而且還需要解決你想處理的一些困難問題,一個共享庫應該為那個使用它的程序記憶體使用進行計數嗎?如果一個共享庫被許多程序使用,在這些程序間它的記憶體使用是平均分佈的嗎?或者剛好可以忽略?沒有一個確定的及快速的規則,依賴於你面對的位置你可能會有不同的答案。這下容易看出來為什麼ps不會盡力嘗試報告一個正確的記憶體使用總量,而是給出一個模糊的數字。看一個程序的記憶體映像足以說明,讓我們來看一看那個龐大的Kedit程序的位置,要檢視Kedit的記憶體象什麼樣子,我們將使用pmap程式(使用-d標誌)Address Kbytes Mode Offset Device Mapping08048000 40 r-x– 0000000000000000 0fe:00000 kdeinit08052000 4 rw— 0000000000009000 0fe:00000 kdeinit08053000 1164 rw— 0000000008053000 000:00000 [ anon ]40000000 84 r-x– 0000000000000000 0fe:00000 ld-2.3.5.so40015000 8 rw— 0000000000014000 0fe:00000 ld-2.3.5.so40017000 4 rw— 0000000040017000 000:00000 [ anon ]40018000 4 r-x– 0000000000000000 0fe:00000 kedit.so40019000 4 rw— 0000000000000000 0fe:00000 kedit.so40027000 252 r-x– 0000000000000000 0fe:00000 libkparts.so.2.1.040066000 20 rw— 000000000003e000 0fe:00000 libkparts.so.2.1.04006b000 3108 r-x– 0000000000000000 0fe:00000 libkio.so.4.2.040374000 116 rw— 0000000000309000 0fe:00000 libkio.so.4.2.040391000 8 rw— 0000000040391000 000:00000 [ anon ]40393000 2644 r-x– 0000000000000000 0fe:00000 libkdeui.so.4.2.040628000 164 rw— 0000000000295000 0fe:00000 libkdeui.so.4.2.040651000 4 rw— 0000000040651000 000:00000 [ anon ]40652000 100 r-x– 0000000000000000 0fe:00000 libkdesu.so.4.2.04066b000 4 rw— 0000000000019000 0fe:00000 libkdesu.so.4.2.04066c000 68 r-x– 0000000000000000 0fe:00000 libkwalletclient.so.1.0.04067d000 4 rw— 0000000000011000 0fe:00000 libkwalletclient.so.1.0.04067e000 4 rw— 000000004067e000 000:00000 [ anon ]4067f000 2148 r-x– 0000000000000000 0fe:00000 libkdecore.so.4.2.040898000 64 rw— 0000000000219000 0fe:00000 libkdecore.so.4.2.0408a8000 8 rw— 00000000408a8000 000:00000 [ anon ]…. (trimmed) …mapped: 25404K writeable/private: 2432K shared: 0K我剪掉了許多輸出內容,剩下的與展示出來的類似,即使沒有完整的輸出,我們也可以看到一些非常有趣的內容,一個重要的內容就是注意到每一個共享庫都列出了兩次,一次為它的程式碼段一次為它的資料段,程式碼段具有“r-x-”樣式,而資料段具有“rw--”樣式,我們關心的只有位元組數、樣式和映像欄,剩下的對於討論都是不重要的。如果你仔細檢查輸出內容,你會發現最大位元組數的行通常是包含共享庫(以lib開頭的行就是共享庫)的程式碼段行,如果你找出了在程序之間所有的共享部分,它們以“writeable/private” 結束,顯示在輸出的底端,這可以理解為程序的消耗增量,因此,執行Kedit(假設所有共享庫都已經被載入了)例項大約要佔用2M,這和ps報告的14或25M完全不是一回事。這意味著什麼?這個故事的寓意是Linux程序記憶體使用是一個複雜的事情,你不能僅通過執行ps來了解,當你處理一個建立了大量子程序的程式時特別真實,如Apache,ps可能報告每個Apache程序使用10M記憶體,實際上每個Apache程序只消耗了1M記憶體,在調整Apache的MaxClients引數(它決定了你的伺服器能同時處理的請求數量,)設定時這個資訊變得非常重要。同時,它也適應於桌面軟體,如果你運行了KDE,但是幾乎全部使用Gnome應用程式,那麼你將為多餘的(但是不同的)共享庫付出巨大的代價,因此請儘量保持要麼全部執行KDE應用程式要麼全部執行Gnome應用程式,這樣Linux就可以使用更多的記憶體來做其他事情(如檔案快取,它可以極大地提高檔案的訪問速度)。原文出處:http://www.linuxquestions.org/linux/articles/Technical/Understanding_memory_usage_on_Linux

想必在linux上寫過程式的同學都有分析程序佔用多少記憶體的經歷,或者被問到這樣的問題——你的程式在執行時佔用了多少記憶體(實體記憶體)?通常我們可以通過top命令檢視程序佔用了多少記憶體。這裡我們可以看到VIRT、RES和SHR三個重要的指標,他們分別代表什麼意思呢?這是本文需要跟大家一起探討的問題。當然如果更加深入一點,你可能會問程序所佔用的那些實體記憶體都用在了哪些地方?這時候top命令可能不能給到你你所想要的答案了,不過我們可以分析proc檔案系統提供的smaps檔案,這個檔案詳盡地列出了當前程序所佔用實體記憶體的使用情況。

這篇blog總共分為三個部分。第一部分簡要闡述虛擬記憶體和駐留記憶體這兩個重要的概念;第二部分解釋top命令中VIRT、RES以及SHR三個引數的實際參考意義;最後一部分向大家介紹一下smaps檔案的格式,通過分析smaps檔案我們可以詳細瞭解程序實體記憶體的使用情況,比如mmap檔案佔用了多少空間、動態記憶體開闢消耗了多少空間、函式呼叫棧消耗了多少空間等等。

關於記憶體的兩個概念

要理解top命令關於記憶體使用情況的輸出,我們必須首先搞清楚虛擬記憶體(Virtual Memory)和駐留記憶體(Resident Memory)兩個概念。

【虛擬記憶體】

首先需要強調的是虛擬記憶體不同於實體記憶體,雖然兩者都包含記憶體字眼但是它們屬於兩個不同層面的概念。程序佔用虛擬記憶體空間大並非意味著程式的實體記憶體也一定佔用很大。虛擬記憶體是作業系統核心為了對程序地址空間進行管理(process address space management)而精心設計的一個邏輯意義上的記憶體空間概念。我們程式中的指標其實都是這個虛擬記憶體空間中的地址。比如我們在寫完一段C++程式之後都需要採用g++進行編譯,這時候編譯器採用的地址其實就是虛擬記憶體空間的地址。因為這時候程式還沒有執行,何談實體記憶體空間地址?凡是程式執行過程中可能需要用到的指令或者資料都必須在虛擬記憶體空間中

。既然說虛擬記憶體是一個邏輯意義上(假象的)的記憶體空間,為了能夠讓程式在物理機器上執行,那麼必須有一套機制可以讓這些假象的虛擬記憶體空間對映到實體記憶體空間(實實在在的RAM記憶體條上的空間)。這其實就是作業系統中頁對映表(page table)所做的事情了。核心會為系統中每一個程序維護一份相互獨立的頁對映表。。頁對映表的基本原理是將程式執行過程中需要訪問的一段虛擬記憶體空間通過頁對映表對映到一段實體記憶體空間上,這樣CPU訪問對應虛擬記憶體地址的時候就可以通過這種查詢頁對映表的機制訪問實體記憶體上的某個對應的地址。“頁(page)”是虛擬記憶體空間向實體記憶體空間對映的基本單元。

      下圖1演示了虛擬記憶體空間和實體記憶體空間的相互關係,它們通過Page Table關聯起來。其中虛擬記憶體空間中著色的部分分別被對映到實體記憶體空間對應相同著色的部分。而虛擬記憶體空間中灰色的部分表示在實體記憶體空間中沒有與之對應的部分,也就是說灰色部分沒有被對映到實體記憶體空間中。這麼做也是本著“按需對映”的指導思想,因為虛擬記憶體空間很大,可能其中很多部分在一次程式執行過程中根本不需要訪問,所以也就沒有必要將虛擬記憶體空間中的這些部分對映到實體記憶體空間上。 到這裡為止已經基本闡述了什麼是虛擬記憶體了。總結一下就是,虛擬記憶體是一個假象的記憶體空間,在程式執行過程中虛擬記憶體空間中需要被訪問的部分會被對映到實體記憶體空間中。虛擬記憶體空間大隻能表示程式執行過程中可訪問的空間比較大,不代表實體記憶體空間佔用也大。 t1

駐留記憶體】

駐留記憶體,顧名思義是指那些被對映到程序虛擬記憶體空間的實體記憶體。上圖1中,在系統實體記憶體空間中被著色的部分都是駐留記憶體。比如,A1、A2、A3和A4是程序A的駐留記憶體;B1、B2和B3是程序B的駐留記憶體。程序的駐留記憶體就是程序實實在在佔用的實體記憶體。一般我們所講的程序佔用了多少記憶體,其實就是說的佔用了多少駐留記憶體而不是多少虛擬記憶體。因為虛擬記憶體大並不意味著佔用的實體記憶體大。

關於虛擬記憶體和駐留記憶體這兩個概念我們說到這裡。下面一部分我們來看看top命令中VIRT、RES和SHR分別代表什麼意思。

top命令中VIRT、RES和SHR的含義       搞清楚了虛擬記憶體的概念之後解釋VIRT的含義就很簡單了。VIRT表示的是程序虛擬記憶體空間大小。對應到圖1中的程序A來說就是A1、A2、A3、A4以及灰色部分所有空間的總和。也就是說VIRT包含了在已經對映到實體記憶體空間的部分和尚未對映到實體記憶體空間的部分總和。 RES的含義是指程序虛擬記憶體空間中已經對映到實體記憶體空間的那部分的大小。對應到圖1中的程序A來說就是A1、A2、A3以及A4幾個部分空間的總和。所以說,看程序在執行過程中佔用了多少記憶體應該看RES的值而不是VIRT的值。 最後來看看SHR所表示的含義。SHR是share(共享)的縮寫,它表示的是程序佔用的共享記憶體大小。在上圖1中我們看到程序A虛擬記憶體空間中的A4和程序B虛擬記憶體空間中的B3都對映到了實體記憶體空間的A4/B3部分。咋一看很奇怪。為什麼會出現這樣的情況呢?其實我們寫的程式會依賴於很多外部的動態庫(.so),比如libc.so、libld.so等等。這些動態庫在記憶體中僅僅會儲存/對映一份,如果某個程序執行時需要這個動態庫,那麼動態載入器會將這塊記憶體對映到對應程序的虛擬記憶體空間中。多個進展之間通過共享記憶體的方式相互通訊也會出現這樣的情況。這麼一來,就會出現不同程序的虛擬記憶體空間會對映到相同的實體記憶體空間。這部分實體記憶體空間其實是被多個程序所共享的,所以我們將他們稱為共享記憶體,用SHR來表示。某個程序佔用的記憶體除了和別的程序共享的記憶體之外就是自己的獨佔記憶體了。所以要計算程序獨佔記憶體的大小隻要用RES的值減去SHR值即可。

程序的smaps檔案

檢視命令是:cat /proc/程序的pid/smaps

通過top命令我們已經能看出程序的虛擬空間大小(VIRT)、佔用的實體記憶體(RES)以及和其他程序共享的記憶體(SHR)。但是僅此而已,如果我想知道如下問題:

  1. 程序的虛擬記憶體空間的分佈情況,比如heap佔用了多少空間、檔案對映(mmap)佔用了多少空間、stack佔用了多少空間?
  2.  程序是否有被交換到swap空間的記憶體,如果有,被交換出去的大小?
  3. mmap方式開啟的資料檔案有多少頁在記憶體中是髒頁(dirty page)沒有被寫回到磁碟的?
  4. mmap方式開啟的資料檔案當前有多少頁面已經在記憶體中,有多少頁面還在磁碟中沒有載入到page cahe中?
  5. 等等

以上這些問題都無法通過top命令給出答案,但是有時候這些問題正是我們在對程式進行效能瓶頸分析和優化時所需要回答的問題。所幸的是,世界上解決問題的方法總比問題本身要多得多。linux通過proc檔案系統為每個程序都提供了一個smaps檔案,通過分析該檔案我們就可以一一回答以上提出的問題。

在smaps檔案中,每一條記錄(如下圖2所示)表示程序虛擬記憶體空間中一塊連續的區域。其中第一行從左到右依次表示地址範圍、許可權標識、對映檔案偏移、裝置號、inode、檔案路徑。詳細解釋可以參見understanding-linux-proc-id-maps

接下來8個欄位的含義分別如下:

  1. Size:表示該對映區域在虛擬記憶體空間中的大小。
  2. Rss:表示該對映區域當前在實體記憶體中佔用了多少空間。
  3. Shared_Clean:和其他程序共享的未被改寫的page的大小。
  4. Shared_Dirty: 和其他程序共享的被改寫的page的大小。
  5. Private_Clean:未被改寫的私有頁面的大小。
  6. Swap:表示非mmap記憶體(也叫anonymous memory,比如malloc動態分配出來的記憶體)由於實體記憶體不足被swap到交換空間的大小。
  7. Pss:該虛擬記憶體區域平攤計算後使用的實體記憶體大小(有些記憶體會和其他程序共享,例如mmap進來的)。比如該區域所對映的實體記憶體部分同時也被另一個程序映射了,且該部分實體記憶體的大小為1000KB,那麼該程序分攤其中一半的記憶體,即Pss=500KB。t_2

圖2. smaps檔案示例

有了smap如此詳細關於虛擬記憶體空間到實體記憶體空間的對映資訊,相信大家已經能夠通過分析該檔案回答上面提出的4個問題。

最後希望所有讀者能夠通過閱讀本文對程序的虛擬記憶體和實體記憶體有一個更加清晰認識,並能更加準確理解top命令關於記憶體的輸出,最後可以通過smaps檔案更進一步分析程序使用記憶體的情況

引 : top命令作為Linux下最常用的效能分析工具之一,可以監控、收集程序的CPUIO、記憶體使用情況。比如我們可以通過top命令獲得一個程序使用了多少虛擬記憶體(VIRT)、實體記憶體(RES)、共享記憶體(SHR)。

最近遇到一個諮詢問題,某產品做效能分析需要獲取程序佔用實體記憶體的實際大小(不包括和其他程序共享的部分),看似很簡單的問題,但經過研究分析後,發現背後有很多故事……

VIRT RES SHR的準確含義

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G

三個記憶體指標,VRITRESSHR準確含義是什麼?誰能告訴我們?MAN頁?Linux專家?SUSE工程師?Linus?誰能說出最正確答案?沒人!因為惟有原始碼才是最正確的答案。

那我們就去看下原始碼吧,這就是開源軟體的最大的好處。

首先這三個資料的源頭,肯定是核心,程序的相關資料結構肯定是由核心維護。那麼top作為一個使用者空間的程式,要想獲取核心空間的資料,就需要通過系統介面(API)獲取。而proc檔案系統是Linux核心空間和使用者空間交換資料的一個途徑,而且是非常重要的一種途徑,這點和windows更傾向於基於函式呼叫的形式不同。

當你呼叫系統函式read讀取一個普通檔案時,核心執行對應檔案系統的程式碼從磁碟傳送檔案內容給你。

當你呼叫系統函式read讀取一個 proc檔案時,核心執行對應的proc檔案系統的程式碼從核心的資料結構中傳送相關內容給你。proc檔案和磁碟沒有關係。只是系統介面而已。

而一個程序的相關資訊,Linux全部通過/proc/<pid>/內的檔案告訴了我們。

如下,你可以使用普通的檔案讀寫工具,比如cat獲取程序的各種資訊。這比函式呼叫的方式靈活多了、豐富多了。

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G

回到我們的問題,top命令顯示的程序資訊,肯定也是通過proc獲取的,因為除此之外沒有其他途徑,沒有系統函式可以做這個事情,top也不可能越過使用者層直取核心獲取資料。

帶著以上資訊,很快就可以從top的原始碼中找到關鍵程式碼:

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G  

啊哈,statm檔案:

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G

根據sscanf的順序,第一個值是VIRT,第二個值是RES,第三個值是SHR

等等,好像數值對不上,top顯示的SHR344k,而statm給出的是86

再來看一行關鍵程式碼:

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G  

statm顯示的是頁數,top顯示的是KBX86下,一頁是4KB86 344。這就對了!

於是乎,我們找到了最關鍵的入口,接下來按圖索驥,看看核心是怎麼產生statm檔案內容就可以了。~~

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G

proc_pid_statm函式負責產生statm檔案內容,當你使用cat命令列印statm檔案時,核心中的這個函式會執行。

proc_pid_statm獲取程序的mm_struct資料結構,而這個資料結構就是程序的記憶體描述符,通過它可以獲取程序記憶體使用、對映的全部資訊。

     進一步考察task_statm函式,可以看到:

剖析top命令顯示的VIRT RES SHR值 - yalung - Y A L U N G

第一個值(VIRT)就是mm->total_vm,即程序虛存的總大小,這個比較清晰,只要程序申請了記憶體,無論是malloc還是堆疊還是全域性,都會計入這個值;

第二個值(RES)是mm->file_rss+mm->anon_rss

第三個值(SHR)是mm->file_rss

 RES要和SHR結合者看,核心把實體記憶體分為了兩部分,一部分是對映至檔案的,一部分是沒有對映至檔案的即匿名記憶體,完全和共不共享沒有關係!

file_rss為什麼叫做shared呢?應該是一種指示性表述,表示這部分記憶體可能是共享的。但並不代表真正共享了。那麼到底哪些計入file_rss?通過查閱相關程式碼,發現(可能有遺漏):

程式的程式碼段。

動態庫的程式碼段。

通過mmap做的檔案對映。

通過mmap做的匿名對映,但指明瞭MAP_SHARED屬性。

通過shmget申請的共享記憶體。

 即程序通過以上方式佔用的實體記憶體,計入file_rss,也就是topSHR欄位。我們看到一般這些記憶體都是以共享方式存在。但如果某個動態庫只一個程序在使用,它的程式碼段就沒有被共享著。

反過來再來看anon_rss統計的內容,是否就一定是獨佔的?也不是,比如新fork之後的子程序,由於copy on write機制,在頁面被修改之前,和父程序共享。這部分值並不體現在top命令的SHR欄位內。

 綜上所述top命令顯示的SHR欄位,並不是準確描述了程序與其他程序共享使用的記憶體數量,是存在誤差的。 

那麼如何獲取程序準確的共享記憶體數量?

獲取程序準確的共享記憶體數量

我們注意到在描述程序資訊的proc/<pid>內,有一個smaps檔案,裡面展示了所有記憶體段的資訊,其中有Shared_CleanShared_DirtyPrivate_CleanPrivate_Dirty:幾個欄