1. 程式人生 > >剖析top命令顯示的VIRT RES SHR值

剖析top命令顯示的VIRT RES SHR值

http://www.fuzhijie.me/?p=741 

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

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

VIRT RES SHR的準確含義

 

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

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

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

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

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

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

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

 

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

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

 

啊哈,statm檔案:

 

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

等等,好像數值對不上,top顯示的SHR

是344k,而statm給出的是86!

再來看一行關鍵程式碼:

 

statm顯示的是頁數,top顯示的是KB。X86下,一頁是4KB,86 * 4 = 344。這就對了!

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

 

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

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

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

 

第一個值(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,也就是top的SHR欄位。我們看到一般這些記憶體都是以共享方式存在。但如果某個動態庫只一個程序在使用,它的程式碼段就沒有被共享著。

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

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

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

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

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

 

找到相關程式碼,可以看到,一個頁面如果對映數>=2計入Shared_* ; 如果=1計入Private_*。(髒頁計入*_Dirty,否則計入*_Clean)

 

     統計smaps檔案內所有段的Shared_*值的總和就是程序準確的共享記憶體數量!

     統計smaps檔案內所有段的Private_*值的總和就是程序準確的獨佔記憶體數量!

總結

通過以上分析,我們可以得到如下結論:

top命令通過解析/proc/<pid>/statm統計VIRT和RES和SHR欄位值。

VIRT是申請的虛擬記憶體總量。

RES是程序使用的實體記憶體總和。

SHR是RES中對映至檔案的實體記憶體總和。包括:

程式的程式碼段。

動態庫的程式碼段。

通過mmap做的檔案對映。

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

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

/proc/<pid>/smaps內Shared_*統計的是RES中對映數量>=2的實體記憶體。

/proc/<pid>/smaps內Private_*統計的是RES中對映數量=1的實體記憶體。