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關聯起來。其中虛擬內存空間中著色的部分分別被映射到物理內存空間對應相同著色的部分。而虛擬內存空間中灰色的部分表示在物理內存空間中沒有與之對應的部分,也就是說灰色部分沒有被映射到物理內存空間中。這么做也是本著“按需映射”的指導思想,因為虛擬內存空間很大,可能其中很多部分在一次程序運行過程中根本不需要訪問,所以也就沒有必要將虛擬內存空間中的這些部分映射到物理內存空間上。
到這里為止已經基本闡述了什么是虛擬內存了??偨Y一下就是,虛擬內存是一個假象的內存空間,在程序運行過程中虛擬內存空間中需要被訪問的部分會被映射到物理內存空間中。虛擬內存空間大只能表示程序運行過程中可訪問的空間比較大,不代表物理內存空間占用也大。
【駐留內存】
駐留內存,顧名思義是指那些被映射到進程虛擬內存空間的物理內存。上圖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)。但是僅此而已,如果我想知道如下問題:
- 進程的虛擬內存空間的分布情況,比如heap占用了多少空間、文件映射(mmap)占用了多少空間、stack占用了多少空間?
- ?進程是否有被交換到swap空間的內存,如果有,被交換出去的大小?
- mmap方式打開的數據文件有多少頁在內存中是臟頁(dirty page)沒有被寫回到磁盤的?
- mmap方式打開的數據文件當前有多少頁面已經在內存中,有多少頁面還在磁盤中沒有加載到page cahe中?
- 等等
以上這些問題都無法通過top命令給出答案,但是有時候這些問題正是我們在對程序進行性能瓶頸分析和優化時所需要回答的問題。所幸的是,世界上解決問題的方法總比問題本身要多得多。linux通過proc文件系統為每個進程都提供了一個smaps文件,通過分析該文件我們就可以一一回答以上提出的問題。
在smaps文件中,每一條記錄(如下圖2所示)表示進程虛擬內存空間中一塊連續的區域。其中第一行從左到右依次表示地址范圍、權限標識、映射文件偏移、設備號、inode、文件路徑。詳細解釋可以參見understanding-linux-proc-id-maps。
接下來8個字段的含義分別如下:
- Size:表示該映射區域在虛擬內存空間中的大小。
- Rss:表示該映射區域當前在物理內存中占用了多少空間。
- Shared_Clean:和其他進程共享的未被改寫的page的大小。
- Shared_Dirty:?和其他進程共享的被改寫的page的大小。
- Private_Clean:未被改寫的私有頁面的大小。
- Swap:表示非mmap內存(也叫anonymous memory,比如malloc動態分配出來的內存)由于物理內存不足被swap到交換空間的大小。
- Pss:該虛擬內存區域平攤計算后使用的物理內存大小(有些內存會和其他進程共享,例如mmap進來的)。比如該區域所映射的物理內存部分同時也被另一個進程映射了,且該部分物理內存的大小為1000KB,那么該進程分攤其中一半的內存,即Pss=500KB。
圖2. smaps文件示例
有了smap如此詳細關于虛擬內存空間到物理內存空間的映射信息,相信大家已經能夠通過分析該文件回答上面提出的4個問題。
最后希望所有讀者能夠通過閱讀本文對進程的虛擬內存和物理內存有一個更加清晰認識,并能更加準確理解top命令關于內存的輸出,最后可以通過smaps文件更進一步分析進程使用內存的情況
?
?
?
?
?http://yalung929.blog.163.com/blog/static/203898225201212981731971/
?
引?言:?top命令作為Linux下最常用的性能分析工具之一,可以監控、收集進程的CPU、IO、內存使用情況。比如我們可以通過top命令獲得一個進程使用了多少虛擬內存(VIRT)、物理內存(RES)、共享內存(SHR)。
最近遇到一個咨詢問題,某產品做性能分析需要獲取進程占用物理內存的實際大小(不包括和其他進程共享的部分),看似很簡單的問題,但經過研究分析后,發現背后有很多故事……
1?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?通過查閱相關代碼,發現(可能有遺漏):
l?程序的代碼段。
l?動態庫的代碼段。
l?通過mmap做的文件映射。
l?通過mmap做的匿名映射,但指明了MAP_SHARED屬性。
l?通過shmget申請的共享內存。
?即進程通過以上方式占用的物理內存,計入file_rss,也就是top的SHR字段。我們看到一般這些內存都是以共享方式存在。但如果某個動態庫只一個進程在使用,它的代碼段就沒有被共享著。
反過來再來看anon_rss統計的內容,是否就一定是獨占的?也不是,比如新fork之后的子進程,由于copy?on?write機制,在頁面被修改之前,和父進程共享。這部分值并不體現在top命令的SHR字段內。
?綜上所述top命令顯示的SHR字段,并不是準確描述了進程與其他進程共享使用的內存數量,是存在誤差的。?
那么如何獲取進程準確的共享內存數量?
2?獲取進程準確的共享內存數量
我們注意到在描述進程信息的proc/<pid>內,有一個smaps文件,里面展示了所有內存段的信息,其中有Shared_Clean?Shared_Dirty?Private_Clean?Private_Dirty:幾個字段。
?
? ?
?
找到相關代碼,可以看到,一個頁面如果映射數>=2計入Shared_*?;?如果=1計入Private_*。(臟頁計入*_Dirty,否則計入*_Clean)
?
?
?
?????統計smaps文件內所有段的Shared_*值的總和就是進程準確的共享內存數量!
?????統計smaps文件內所有段的Private_*值的總和就是進程準確的獨占內存數量!
3?總結
通過以上分析,我們可以得到如下結論:
l?top命令通過解析/proc/<pid>/statm統計VIRT和RES和SHR字段值。
l?VIRT是申請的虛擬內存總量。
l?RES是進程使用的物理內存總和。
l?SHR是RES中”映射至文件”的物理內存總和。包括:
程序的代碼段。
動態庫的代碼段。
通過mmap做的文件映射。
通過mmap做的匿名映射,但指明了MAP_SHARED屬性。
通過shmget申請的共享內存。
l?/proc/<pid>/smaps內Shared_*統計的是RES中映射數量>=2的物理內存。
l?/proc/<pid>/smaps內Private_*統計的是RES中映射數量=1的物理內存。
?
?
?
?
?
在Linux下查看內存我們一般用free命令:
[root@scs-2 tmp]# free
???????????? total?????? used?????? free???? shared??? buffers???? cached
Mem:?????? 3266180??? 3250004????? 16176????????? 0???? 110652??? 2668236
-/+ buffers/cache:???? 471116??? 2795064
Swap:????? 2048276????? 80160??? 1968116
下面是對這些數值的解釋:
total:總計物理內存的大小。
used:已使用多大。
free:可用有多少。
Shared:多個進程共享的內存總額。
Buffers/cached:磁盤緩存的大小。
第三行(-/+ buffers/cached):
used:已使用多大。
free:可用有多少。
第四行就不多解釋了。
區別:第二行(mem)的used/free與第三行(-/+ buffers/cache) used/free的區別。 這兩個的區別在于使用的角度來看,第一行是從OS的角度來看,因為對于OS,buffers/cached 都是屬于被使用,所以他的可用內存是16176KB,已用內存是3250004KB,其中包括,內核(OS)使用+Application(X, oracle,etc)使用的+buffers+cached.
第三行所指的是從應用程序角度來看,對于應用程序來說,buffers/cached 是等于可用的,因為buffer/cached是為了提高文件讀取的性能,當應用程序需在用到內存的時候,buffer/cached會很快地被回收。
所以從應用程序的角度來說,可用內存=系統free memory+buffers+cached。
如上例:
2795064=16176+110652+2668236
接下來解釋什么時候內存會被交換,以及按什么方交換。 當可用內存少于額定值的時候,就會開會進行交換。
如何看額定值:
cat /proc/meminfo
[root@scs-2 tmp]# cat /proc/meminfo
MemTotal:????? 3266180 kB
MemFree:???????? 17456 kB
Buffers:??????? 111328 kB
Cached:??????? 2664024 kB
SwapCached:????????? 0 kB
Active:???????? 467236 kB
Inactive:????? 2644928 kB
HighTotal:?????????? 0 kB
HighFree:??????????? 0 kB
LowTotal:????? 3266180 kB
LowFree:???????? 17456 kB
SwapTotal:???? 2048276 kB
SwapFree:????? 1968116 kB
Dirty:?????????????? 8 kB
Writeback:?????????? 0 kB
Mapped:???????? 345360 kB
Slab:?????????? 112344 kB
Committed_AS:?? 535292 kB
PageTables:?????? 2340 kB
VmallocTotal: 536870911 kB
VmallocUsed:??? 272696 kB
VmallocChunk: 536598175 kB
HugePages_Total:???? 0
HugePages_Free:????? 0
Hugepagesize:???? 2048 kB
用free -m查看的結果:
[root@scs-2 tmp]# free -m?
???????????? total?????? used?????? free???? shared??? buffers???? cached
Mem:????????? 3189?????? 3173???????? 16????????? 0??????? 107?????? 2605
-/+ buffers/cache:??????? 460?????? 2729
Swap:???????? 2000???????? 78?????? 1921
查看/proc/kcore文件的大小(內存鏡像):
[root@scs-2 tmp]# ll -h /proc/kcore?
-r-------- 1 root root 4.1G Jun 12 12:04 /proc/kcore
備注:
占用內存的測量
測量一個進程占用了多少內存,linux為我們提供了一個很方便的方法,/proc目錄為我們提供了所有的信息,實際上top等工具也通過這里來獲取相應的信息。
/proc/meminfo 機器的內存使用信息
/proc/pid/maps pid為進程號,顯示當前進程所占用的虛擬地址。
/proc/pid/statm 進程所占用的內存
[root@localhost ~]# cat /proc/self/statm
654 57 44 0 0 334 0
輸出解釋
CPU 以及CPU0。。。的每行的每個參數意思(以第一行為例)為:
參數 解釋 /proc//status
Size (pages) 任務虛擬地址空間的大小 VmSize/4
Resident(pages) 應用程序正在使用的物理內存的大小 VmRSS/4
Shared(pages) 共享頁數 0
Trs(pages) 程序所擁有的可執行虛擬內存的大小 VmExe/4
Lrs(pages) 被映像到任務的虛擬內存空間的庫的大小 VmLib/4
Drs(pages) 程序數據段和用戶態的棧的大小 (VmData+ VmStk )4
dt(pages) 04
查看機器可用內存
/proc/28248/>free
total used free shared buffers cached
Mem: 1023788 926400 97388 0 134668 503688
-/+ buffers/cache: 288044 735744
Swap: 1959920 89608 1870312
我們通過free命令查看機器空閑內存時,會發現free的值很小。這主要是因為,在linux中有這么一種思想,內存不用白不用,因此它盡可能的cache和buffer一些數據,以方便下次使用。但實際上這些內存也是可以立刻拿來使用的。
所以 空閑內存=free+buffers+cached=total-used
?
?
?
用/proc文件系統查看進程的內存使用情況
/proc目錄Linux 內核提供了一種通過 /proc 文件系統,在運行時訪問內核內部數據結構、改變內核設置的機制。proc文件系統是一個偽文件系統
/proc/vmstat 虛擬內存統計信息
/proc/vmcore 內核panic時的內存映像
/proc/diskstats 取得磁盤信息
/proc/schedstat kernel調度器的統計信息
/proc/zoneinfo 顯示內存空間的統計信息,對分析虛擬內存行為很有用
以下是/proc目錄中進程N的信息
/proc/N pid為N的進程信息
/proc/N/cmdline 進程啟動命令
/proc/N/cwd 鏈接到進程當前工作目錄
/proc/N/environ 進程環境變量列表
/proc/N/exe 鏈接到進程的執行命令文件
/proc/N/fd 包含進程相關的所有的文件描述符
/proc/N/maps 與進程相關的內存映射信息
/proc/N/mem 指代進程持有的內存,不可讀
/proc/N/root 鏈接到進程的根目錄
/proc/N/stat 進程的狀態
/proc/N/statm 進程使用的內存的狀態
/proc/N/status 進程狀態信息,比stat/statm更具可讀性
/proc/self 鏈接到當前正在運行的進程
ps命令的輸出關于內存的情況不是很詳細,尤其是進程所使用的內存中有很大一部分是共享庫函數使用的,因此通過ps命令的輸出看不到進程自己使用了多少內存。為了查看更詳細的信息,可以借助于/proc文件系統。這個文件系統并存在于磁盤上,但是可以象操作其它普通文件一樣操作它。它是Linux提供給用戶查看進程相關信息的接口。在/proc下有2個文件和進程內存有關:/proc/<pid>/status和/proc/<pid>/smaps。
通過/proc/<pid>/status可以查看進程的內存使用情況,包括虛擬內存大小(VmSize),物理內存大小(VmRSS),數據段大小(VmData),棧的大小(VmStk),代碼段的大小(VmExe),共享庫的代碼段大小(VmLib)等等。
$ cat /proc/10069/status
Name:?? a.out
State:? S (sleeping)
Tgid:?? 10069
Pid:??? 10069
PPid:?? 6793
TracerPid:????? 0
Uid:??? 1001??? 1001??? 1001??? 1001
Gid:??? 1001??? 1001??? 1001??? 1001
FDSize: 256
Groups: 1000 1001?
VmPeak:???? 1692 kB
VmSize:???? 1616 kB
VmLck:???????? 0 kB
VmHWM:?????? 304 kB
VmRSS:?????? 304 kB
VmData:?????? 28 kB
VmStk:??????? 88 kB
VmExe:???????? 4 kB
VmLib:????? 1464 kB
VmPTE:??????? 20 kB
Threads:??????? 1
SigQ:?? 0/16382
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000000000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: ffffffffffffffff
Cpus_allowed:?? f
Cpus_allowed_list:????? 0-3
Mems_allowed:?? 1
Mems_allowed_list:????? 0
voluntary_ctxt_switches:??????? 1
nonvoluntary_ctxt_switches:???? 1注意,VmData,VmStk,VmExe和VmLib之和并不等于VmSize。這是因為共享庫函數的數據段沒有計算進去(VmData僅包含a.out程序的數據段,不包括共享庫函數的數據段,也不包括通過mmap映射的區域。VmLib僅包括共享庫的代碼段,不包括共享庫的數據段)。
通過/proc/<pid>/smaps可以查看進程整個虛擬地址空間的映射情況,它的輸出從低地址到高地址按順序輸出每一個映射區域的相關信息,如下所示:
$ cat /proc/10069/smaps
00110000-00263000 r-xp 00000000 08:07 128311???? /lib/tls/i686/cmov/libc-2.11.1.so
Size:?????????????? 1356 kB
Rss:???????????????? 148 kB
Pss:?????????????????? 8 kB
Shared_Clean:??????? 148 kB
Shared_Dirty:????????? 0 kB
Private_Clean:???????? 0 kB
Private_Dirty:???????? 0 kB
Referenced:????????? 148 kB
Swap:????????????????? 0 kB
KernelPageSize:??????? 4 kB
MMUPageSize:?????????? 4 kB
......
......
bfd7f000-bfd94000 rw-p 00000000 00:00 0????????? [stack]
Size:???????????????? 88 kB
Rss:?????????????????? 8 kB
Pss:?????????????????? 8 kB
Shared_Clean:????????? 0 kB
Shared_Dirty:????????? 0 kB
Private_Clean:???????? 0 kB
Private_Dirty:???????? 8 kB
Referenced:??????????? 8 kB
Swap:????????????????? 0 kB
KernelPageSize:??????? 4 kB
MMUPageSize:?????????? 4 kB注意:rwxp中,p表示私有映射(采用Copy-On-Write技術)。 Size字段就是該區域的大小。
?
?
?
一提到內存管理,我們頭腦中閃出的兩個概念,就是虛擬內存,與物理內存。這兩個概念主要來自于linux內核的支持。
Linux在內存管理上份為兩級,一級是線性區,類似于00c73000-00c88000,對應于虛擬內存,它實際上不占用實際物理內存;一級是具體的物理頁面,它對應我們機器上的物理內存。
這里要提到一個很重要的概念,內存的延遲分配。Linux內核在用戶申請內存的時候,只是給它分配了一個線性區(也就是虛存),并沒有分配實際物理內存;只有當用戶使用這塊內存的時候,內核才會分配具體的物理頁面給用戶,這時候才占用寶貴的物理內存。內核釋放物理頁面是通過釋放線性區,找到其所對應的物理頁面,將其全部釋放的過程。
| 1 2 3 |
|
我們知道用戶的進程和內核是運行在不同的級別,進程與內核之間的通訊是通過系統調用來完成的。進程在申請和釋放內存,主要通過brk,sbrk,mmap,unmmap這幾個系統調用,傳遞的參數主要是對應的虛擬內存。
注意一點,在進程只能訪問虛擬內存,它實際上是看不到內核物理內存的使用,這對于進程是完全透明的。
?
glibc內存管理器
那么我們每次調用malloc來分配一塊內存,都進行相應的系統調用呢?
答案是否定的,這里我要引入一個新的概念,glibc的內存管理器。
我們知道malloc和free等函數都是包含在glibc庫里面的庫函數,我們試想一下,每做一次內存操作,都要調用系統調用的話,那么程序將多么的低效。
實際上glibc采用了一種批發和零售的方式來管理內存。glibc每次通過系統調用的方式申請一大塊內存(虛擬內存),當進程申請內存時,glibc就從自己獲得的內存中取出一塊給進程。
?
內存管理器面臨的困難
我們在寫程序的時候,每次申請的內存塊大小不規律,而且存在頻繁的申請和釋放,這樣不可避免的就會產生內存碎塊。而內存碎塊,直接會導致大塊內存申請無法滿足,從而更多的占用系統資源;如果進行碎塊整理的話,又會增加cpu的負荷,很多都是互相矛盾的指標,這里我就不細說了。
我們在寫程序時,涉及內存時,有兩個概念heap和stack。傳統的說法stack的內存地址是向下增長的,heap的內存地址是向上增長的。
函數malloc和free,主要是針對heap進行操作,由程序員自主控制內存的訪問。
在這里heap的內存地址向上增長,這句話不完全正確。
glibc對于heap內存申請大于128k的內存申請,glibc采用mmap的方式向內核申請內存,這不能保證內存地址向上增長;小于128k的則采用brk,對于它來講是正確的。128k的閥值,可以通過glibc的庫函數進行設置。
這里我先講大塊內存的申請,也即對應于mmap系統調用。
對于大塊內存申請,glibc直接使用mmap系統調用為其劃分出另一塊虛擬地址,供進程單獨使用;在該塊內存釋放時,使用unmmap系統調用將這塊內存釋放,這個過程中間不會產生內存碎塊等問題。
針對小塊內存的申請,在程序啟動之后,進程會獲得一個heap底端的地址,進程每次進行內存申請時,glibc會將堆頂向上增長來擴展內存空間,也就是我們所說的堆地址向上增長。在對這些小塊內存進行操作時,便會產生內存碎塊的問題。實際上brk和sbrk系統調用,就是調整heap頂地址指針。
?
那么heap堆的內存是什么時候釋放呢?
當glibc發現堆頂有連續的128k的空間是空閑的時候,它就會通過brk或sbrk系統調用,來調整heap頂的位置,將占用的內存返回給系統。這時,內核會通過刪除相應的線性區,來釋放占用的物理內存。
下面我要講一個內存空洞的問題:
一個場景,堆頂有一塊正在使用的內存,而下面有很大的連續內存已經被釋放掉了,那么這塊內存是否能夠被釋放?其對應的物理內存是否能夠被釋放?
很遺憾,不能。
這也就是說,只要堆頂的部分申請內存還在占用,我在下面釋放的內存再多,都不會被返回到系統中,仍然占用著物理內存。為什么會這樣呢?
這主要是與內核在處理堆的時候,過于簡單,它只能通過調整堆頂指針的方式來調整調整程序占用的線性區;而又只能通過調整線性區的方式,來釋放內存。所以只要堆頂不減小,占用的內存就不會釋放。
提一個問題:
| 1 2 |
|
為什么申請內存的時候,需要兩個參數,一個是內存大小,一個是返回的指針;而釋放內存的時候,卻只要內存的指針呢?
這主要是和glibc的內存管理機制有關。glibc中,為每一塊內存維護了一個chunk的結構。glibc在分配內存時,glibc先填寫chunk結構中內存塊的大小,然后是分配給進程的內存。
| 1 2 |
|
在進程釋放內存時,只要 指針-4 便可以找到該塊內存的大小,從而釋放掉。
注:glibc在做內存申請時,最少分配16個字節,以便能夠維護chunk結構。
glibc提供的調試工具:
為了方便調試,glibc 為用戶提供了 malloc 等等函數的鉤子(hook),如 __malloc_hook
對應的是一個函數指針,
| 1 |
|
其中 caller 是調用 malloc 返回值的接受者(一個指針的地址)。另外有 __malloc_initialize_hook函數指針,僅僅會調用一次(第一次分配動態內存時)。(malloc.h)
一些使用 malloc 的統計量(SVID 擴展)可以用 struct mallinfo 儲存,可調用獲得。
| 1 |
|
如何檢測 memory leakage?glibc 提供了一個函數
void mtrace (void)及其反作用void muntrace (void)
這時會依賴于一個環境變量 MALLOC_TRACE 所指的文件,把一些信息記錄在該文件中
用于偵測 memory leakage,其本質是安裝了前面提到的 hook。一般將這些函數用
#ifdef DEBUGGING 包裹以便在非調試態下減少開銷。產生的文件據說不建議自己去讀,
而使用 mtrace 程序(perl 腳本來進行分析)。下面用一個簡單的例子說明這個過程,這是
源程序:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
很簡單的程序,其中 q 沒有被釋放。我們設置了環境變量后并且 touch 出該文件
執行結果如下:
|
|
該文件內容如下
| 1 2 3 4 |
|
到這里我基本上講完了,我們寫程序時,數據部分內存使用的問題。
?
代碼占用的內存
數據部分占用內存,那么我們寫的程序是不是也占用內存呢?
在linux中,程序的加載,涉及到兩個工具,linker 和loader。Linker主要涉及動態鏈接庫的使用,loader主要涉及軟件的加載。
- exec執行一個程序
- elf為現在非常流行的可執行文件的格式,它為程序運行劃分了兩個段,一個段是可以執行的代碼段,它是只讀,可執行;另一個段是數據段,它是可讀寫,不能執行。
- loader會啟動,通過mmap系統調用,將代碼端和數據段映射到內存中,其實也就是為其分配了虛擬內存,注意這時候,還不占用物理內存;只有程序執行到了相應的地方,內核才會為其分配物理內存。
- ?loader會去查找該程序依賴的鏈接庫,首先看該鏈接庫是否被映射進內存中,如果沒有使用mmap,將代碼段與數據段映射到內存中,否則只是將其加入進程的地址空間。這樣比如glibc等庫的內存地址空間是完全一樣。
因此一個2M的程序,執行時,并不意味著為其分配了2M的物理內存,這與其運行了的代碼量,與其所依賴的動態鏈接庫有關。
?
運行過程中鏈接動態鏈接庫與編譯過程中鏈接動態庫的區別
我們調用動態鏈接庫有兩種方法:一種是編譯的時候,指明所依賴的動態鏈接庫,這樣loader可以在程序啟動的時候,來所有的動態鏈接映射到內存中;一種是在運行過程中,通過dlopen和dlfree的方式加載動態鏈接庫,動態將動態鏈接庫加載到內存中。
這兩種方式,從編程角度來講,第一種是最方便的,效率上影響也不大,在內存使用上有些差別。
第一種方式,一個庫的代碼,只要運行過一次,便會占用物理內存,之后即使再也不使用,也會占用物理內存,直到進程的終止。
第二中方式,庫代碼占用的內存,可以通過dlfree的方式,釋放掉,返回給物理內存。
這個差別主要對于那些壽命很長,但又會偶爾調用各種庫的進程有關。如果是這類進程,建議采用第二種方式調用動態鏈接庫
?
?
?
?
包含了所有CPU活躍的信息,該文件中的所有值都是從系統啟動開始累計到當前時刻。
[root@localhost ~]# cat /proc/self/statm
654 57 44 0 0 334 0
輸出解釋
CPU?以及CPU0的每行的每個參數意思(以第一行為例)為:
參數?解釋?/proc/pid/statm
Size (pages)?任務虛擬地址空間的物理內存頁數?
Resident(pages)?應用程序正在使用的物理內存頁數?
Shared(pages)?共享頁數?0
Trs(pages)?程序所擁有的可執行虛擬內存的物理內存頁數?
Lrs(pages)?被映像到任務的虛擬內存空間的庫的物理內存頁數?
Drs(pages)?程序數據段和用戶態的棧的物理內存頁數?
dt(pages) 0
linux下page的大小一般為4096,即4KB
查看linux下page大小的命令是 getconf PAGE_SIZE
打開?/proc/pid/statm 文件 即可獲取進程pid下包含了所有CPU活躍的信息,該文件中的所有值都是從系統啟動開始累計到當前時刻。
[root@localhost ~]# cat /proc/self/statm
654 57 44 0 0 334 0
輸出解釋
CPU 以及CPU0的每行的每個參數意思(以第一行為例)為:
參數 解釋 /proc/pid/statm
Size (pages) 任務虛擬地址空間的物理內存頁數?
Resident(pages) 應用程序正在使用的物理內存頁數?
Shared(pages) 共享頁數 0
Trs(pages) 程序所擁有的可執行虛擬內存的物理內存頁數?
Lrs(pages) 被映像到任務的虛擬內存空間的庫的物理內存頁數?
Drs(pages) 程序數據段和用戶態的棧的物理內存頁數?
dt(pages) 0
linux下page的大小一般為4096,即4KB
查看linux下page大小的命令是 getconf PAGE_SIZE
打開 /proc/pid/statm 文件 即可獲取進程pid下的內存使用情況的內存使用情況
?
?
?
linux 下面查看內存有多種渠道,比如通過命令 ps ,top,free 等,比如通過/proc系統,一般需要比較詳細和精確地知道整機內存/某個進程內存的使用情況,最好通過/proc 系統,下面介紹/proc系統下內存相關的幾個文件
?
單個進程的內存查看 ?cat /proc/[pid] 下面有幾個文件: maps , smaps, status
?
maps 文件可以查看某個進程的代碼段、棧區、堆區、動態庫、內核區對應的虛擬地址,如果你還不了解linux進程的內存空間,可以參考這里。
下圖是maps文件內存示例
Develop>cat /proc/self/maps 00400000-0040b000 r-xp 00000000 fd:00 48 /mnt/cf/orig/root/bin/cat 0060a000-0060b000 r--p 0000a000 fd:00 48 /mnt/cf/orig/root/bin/cat 0060b000-0060c000 rw-p 0000b000 fd:00 48 /mnt/cf/orig/root/bin/cat 代碼段 0060c000-0062d000 rw-p 00000000 00:00 0 [heap] 堆區 7f1fff43b000-7f1fff5d4000 r-xp 00000000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so 7f1fff5d4000-7f1fff7d3000 ---p 00199000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so 7f1fff7d3000-7f1fff7d7000 r--p 00198000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so 7f1fff7d7000-7f1fff7d9000 rw-p 0019c000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so 7f1fff7d9000-7f1fff7dd000 rw-p 00000000 00:00 0 7f1fff7dd000-7f1fff7fe000 r-xp 00000000 fd:00 2554 /mnt/cf/orig/root/lib64/ld-2.15.so 7f1fff9f9000-7f1fff9fd000 rw-p 00000000 00:00 0 7f1fff9fd000-7f1fff9fe000 r--p 00020000 fd:00 2554 /mnt/cf/orig/root/lib64/ld-2.15.so 7f1fff9fe000-7f1fff9ff000 rw-p 00021000 fd:00 2554 /mnt/cf/orig/root/lib64/ld-2.15.so 7f1fff9ff000-7f1fffa00000 rw-p 00000000 00:00 0 7fff443de000-7fff443ff000 rw-p 00000000 00:00 0 [stack] 用戶態棧區 7fff443ff000-7fff44400000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 內核區
有時候可以通過不斷查看某個進程的maps文件,通過查看其虛擬內存(堆區)是否不停增長來簡單判斷進程是否發生了內存溢出。
maps文件只能顯示簡單的分區,smap文件可以顯示每個分區的更詳細的內存占用數據
下圖是smaps文件內存示例, 實際顯示內容會將每一個區都顯示出來,下面我只拷貝了代碼段和堆區,
每一個區顯示的內容項目是一樣的,smaps文件各項含義可以參考這里
Develop>cat /proc/self/smaps 00400000-0040b000 r-xp 00000000 fd:00 48 /mnt/cf/orig/root/bin/cat Size: 44 kB 虛擬內存大小 Rss: 28 kB 實際使用物理內存大小 Pss: 28 kB Shared_Clean: 0 kB 頁面被改,則是dirty,否則是clean,頁面引用計數>1,是shared,否則是private Shared_Dirty: 0 kB Private_Clean: 28 kB Private_Dirty: 0 kB Referenced: 28 kB Anonymous: 0 kB AnonHugePages: 0 kB Swap: 0 kB 處于交換區的頁面大小 KernelPageSize: 4 kB 操作系統一個頁面大小 MMUPageSize: 4 kB 體系結構MMU一個頁面大小 Locked: 0 kB
0060c000-0062d000 rw-p 00000000 00:00 0 [heap]
Size: 132 kB
Rss: 8 kB
Pss: 8 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 8 kB
Referenced: 8 kB
Anonymous: 8 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
?
下圖是status文件內存示例, 加粗部分是內存相關的統計,
?
Develop>cat /proc/24475/status Name: netio 可執行程序的名字 State: R (running) 任務狀態,運行/睡眠/僵死 Tgid: 24475 線程組號 Pid: 24475 進程id PPid: 19635 父進程id TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 256 該進程最大文件描述符個數 Groups: 0 VmPeak: 6330708 kB 內存使用峰值VmSize: 268876 kB 進程虛擬地址空間大小VmLck: 0 kB 進程鎖住的物理內存大小,鎖住的物理內存無法交換到硬盤VmHWM: 16656 kBVmRSS: 11420 kB 進程正在使用的物理內存大小VmData: 230844 kB 進程數據段大小VmStk: 136 kB 進程用戶態棧大小VmExe: 760 kB 進程代碼段大小VmLib: 7772 kB 進程使用的庫映射到虛擬內存空間的大小VmPTE: 120 kB 進程頁表大小 VmSwap: 0 kB Threads: 5 SigQ: 0/63346 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000001000000 SigCgt: 0000000180000000 CapInh: 0000000000000000 CapPrm: ffffffffffffffff CapEff: ffffffffffffffff CapBnd: ffffffffffffffff Cpus_allowed: 01 Cpus_allowed_list: 0 Mems_allowed: 01 Mems_allowed_list: 0 voluntary_ctxt_switches: 201 nonvoluntary_ctxt_switches: 909
可以看到,linux下內存占用是一個比較復雜的概念,不能
簡單通過一個單一指標就判斷某個程序“內存消耗”大小,原因有下面2點:
- 進程所申請的內存不一定真正會被用到(malloc或mmap的實現)
- 真正用到了的內存也不一定是只有該進程自己在用 (比如動態共享庫)
關于內存的使用分析及本文幾個命令的說明也可以參考這里
下面是查看整機內存使用情況的文件 /proc/meminfo
Develop>cat /proc/meminfo MemTotal: 8112280 kB 所有可用RAM大小 (即物理內存減去一些預留位和內核的二進制代碼大小) MemFree: 4188636 kB LowFree與HighFree的總和,被系統留著未使用的內存 Buffers: 34728 kB 用來給文件做緩沖大小 Cached: 289740 kB 被高速緩沖存儲器(cache memory)用的內存的大小(等于 diskcache?minus?SwapCache ) SwapCached: 0 kB 被高速緩沖存儲器(cache memory)用的交換空間的大小?已經被交換出來的內存,但仍然被存放在swapfile中。用來在需要的時候很快的被替換而不需要再次打開I/O端口 Active: 435240 kB 在活躍使用中的緩沖或高速緩沖存儲器頁面文件的大小,除非非常必要否則不會被移作他用 Inactive: 231512 kB 在不經常使用中的緩沖或高速緩沖存儲器頁面文件的大小,可能被用于其他途徑. Active(anon): 361252 kB Inactive(anon): 120688 kB Active(file): 73988 kB Inactive(file): 110824 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 0 kB 交換空間的總大小 SwapFree: 0 kB 未被使用交換空間的大小 Dirty: 0 kB 等待被寫回到磁盤的內存大小 Writeback: 0 kB 正在被寫回到磁盤的內存大小 AnonPages: 348408 kB 未映射頁的內存大小 Mapped: 33600 kB 已經被設備和文件等映射的大小 Shmem: 133536 kB Slab: 55984 kB 內核數據結構緩存的大小,可以減少申請和釋放內存帶來的消耗 SReclaimable: 25028 kB 可收回Slab的大小 SUnreclaim: 30956 kB 不可收回Slab的大小(SUnreclaim+SReclaimable=Slab) KernelStack: 1896 kB 內核棧區大小 PageTables: 8156 kB 管理內存分頁頁面的索引表的大小 NFS_Unstable: 0 kB 不穩定頁表的大小 Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 2483276 kB Committed_AS: 1804104 kB VmallocTotal: 34359738367 kB 可以vmalloc虛擬內存大小 VmallocUsed: 565680 kB 已經被使用的虛擬內存大小 VmallocChunk: 34359162876 kB HardwareCorrupted: 0 kB HugePages_Total: 1536 大頁面數目 HugePages_Free: 0 空閑大頁面數目 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB 大頁面一頁大小 DirectMap4k: 10240 kB DirectMap2M: 8302592 kB
?
?
想必在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訪問對應虛擬內存地址的時候就可以通過這種查找頁映射表的機制訪問物理內存上的某個對應的地址?!绊?#xff08;page)”是虛擬內存空間向物理內存空間映射的基本單元。
??????? 下圖1演示了虛擬內存空間和物理內存空間的相互關系,它們通過Page Table關聯起來。其中虛擬內存空間中著色的部分分別被映射到物理內存空間對應相同著色的部分。而虛擬內存空間中灰色的部分表示在物理內存空間中沒有與之對應的部分,也就是說灰色部分沒有被映射到物理內存空間中。這么做也是本著“按需映射”的指導思想,因為虛擬內存空間很大,可能其中很多部分在一次程序運行過程中根本不需要訪問,所以也就沒有必要將虛擬內存空間中的這些部分映射到物理內存空間上。
??????? 到這里為止已經基本闡述了什么是虛擬內存了。總結一下就是,虛擬內存是一個假象的內存空間,在程序運行過程中虛擬內存空間中需要被訪問的部分會被映射到物理內存空間中。虛擬內存空間大只能表示程序運行過程中可訪問的空間比較大,不代表物理內存空間占用也大。
圖1. 虛擬內存空間到物理內存空間映射
-
駐留內存
駐留內存,顧名思義是指那些被映射到進程虛擬內存空間的物理內存。上圖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文件
通過top命令我們已經能看出進程的虛擬空間大小(VIRT)、占用的物理內存(RES)以及和其他進程共享的內存(SHR)。但是僅此而已,如果我想知道如下問題:
- 進程的虛擬內存空間的分布情況,比如heap占用了多少空間、文件映射(mmap)占用了多少空間、stack占用了多少空間?
- 進程是否有被交換到swap空間的內存,如果有,被交換出去的大小?
- mmap方式打開的數據文件有多少頁在內存中是臟頁(dirty page)沒有被寫回到磁盤的?
- mmap方式打開的數據文件當前有多少頁面已經在內存中,有多少頁面還在磁盤中沒有加載到page cahe中?
- 等等
以上這些問題都無法通過top命令給出答案,但是有時候這些問題正是我們在對程序進行性能瓶頸分析和優化時所需要回答的問題。所幸的是,世界上解決問題的方法總比問題本身要多得多。linux通過proc文件系統為每個進程都提供了一個smaps文件,通過分析該文件我們就可以一一回答以上提出的問題。
在smaps文件中,每一條記錄(如下圖2所示)表示進程虛擬內存空間中一塊連續的區域。其中第一行從左到右依次表示地址范圍、權限標識、映射文件偏移、設備號、inode、文件路徑。詳細解釋可以參見understanding-linux-proc-id-maps。
接下來8個字段的含義分別如下:
- Size:表示該映射區域在虛擬內存空間中的大小。
- Rss:表示該映射區域當前在物理內存中占用了多少空間
- Shared_Clean:和其他進程共享的未被改寫的page的大小
- Shared_Dirty:?和其他進程共享的被改寫的page的大小
- Private_Clean:未被改寫的私有頁面的大小。
- Private_Dirty:?已被改寫的私有頁面的大小。
- Swap:表示非mmap內存(也叫anonymous memory,比如malloc動態分配出來的內存)由于物理內存不足被swap到交換空間的大小。
- Pss:該虛擬內存區域平攤計算后使用的物理內存大小(有些內存會和其他進程共享,例如mmap進來的)。比如該區域所映射的物理內存部分同時也被另一個進程映射了,且該部分物理內存的大小為1000KB,那么該進程分攤其中一半的內存,即Pss=500KB。
圖2. smaps文件中的一條記錄
有了smap如此詳細關于虛擬內存空間到物理內存空間的映射信息,相信大家已經能夠通過分析該文件回答上面提出的4個問題。
最后希望所有讀者能夠通過閱讀本文對進程的虛擬內存和物理內存有一個更加清晰認識,并能更加準確理解top命令關于內存的輸出,最后可以通過smaps文件更進一步分析進程使用內存的情況。
總結
以上是生活随笔為你收集整理的linux内存实际占用分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黄山风景区去宏村怎么坐车
- 下一篇: 中国电信短信一条多少钱