天天看點

Linux程序的記憶體使用情況

在linux下,使用top,ps等指令檢視程序的記憶體使用情況時,經常看到virt,res,shr等,他們都代表什麼意思呢?不同的大小對程序有什麼影響呢?這篇文章将來聊一聊這個問題。閱讀本篇前建議先閱讀linux記憶體管理,了解一些linux下記憶體的基本概念,如什麼是anonymous和file backed映射等。

Linux程式的記憶體使用情況

檢視程序所使用的記憶體

在程序的眼裡,所有的記憶體都是虛拟記憶體,但是這些虛拟記憶體所對應的實體記憶體是多少呢?正如我們在linux記憶體管理中所介紹的那樣,并不是每塊虛拟記憶體都有對應的實體記憶體,可能對應的資料在磁盤上的一個檔案中,或者交換空間上的一塊區域裡。一個程序真正的實體記憶體使用情況隻有核心知道,我們隻能通過核心開放的一些接口來擷取這些統計資料。

top

先看看top的輸出(top用到的資料來自于/proc/[pid]/statm),這裡隻是摘錄了幾條資料:

 pid user      pr  ni    virt    res    shr s %cpu %mem     time+ command 

2530 root      20   0       0      0      0 s  0.3  0.0   0:02.69 kworker/0:0 

2714 dev       20   0   41824   3700   3084 r  0.3  0.7   0:00.02 top 

3008 dev       20   0   22464   5124   3356 s  0.0  1.0   0:00.02 bash  

virt:程序所使用的虛拟記憶體大小

res:系統為虛拟記憶體配置設定的實體記憶體大小,包括file backed和anonymous記憶體,其中anonymous包含了程序自己配置設定和使用的記憶體,以及和别的程序通過mmap共享的記憶體;而file backed的記憶體就是指加載可執行檔案和動态庫所占的記憶體,以及通過private方式調用mmap映射檔案所使用的記憶體(當在記憶體中修改了這部分資料且沒有寫回檔案,那麼這部分記憶體就變成了anonymous),這部分記憶體也可能跟别的程序共享。

shr:res的一部分,表示和别的程序共享的記憶體,包括通過mmap共享的記憶體和file backed的記憶體。當通過prive方式調用mmap映射一個檔案時,如果沒有修改檔案的内容,那麼那部分内容就是shr的,一旦修改了檔案内容且沒有寫回檔案,那麼這部分記憶體就是anonymous且非shr的。

%mem:等于res/total*100%,這裡total指總的實體記憶體大小。

注意:由于shr可能會被多個程序所共享,是以系統中所有程序的res加起來可能會超過總的實體記憶體數量,由于同樣的原因,所有程序的%mem總和可能超過100%。

從上面的分析可以看出,virt的參考意義不大,它隻能反應出程式的大小,而res也不能完全的代表一個程序真正占用的記憶體空間,因為它裡面還包含了shr的部分,比如三個bash程序共享了一個libc動态庫,那麼libc所占用的記憶體算誰的呢?三個程序平分嗎?如果啟動一個bash占用了4m的res,其中3m是libc占用的,由于三個程序都共享那3m的libc,那麼啟動3個bash實際占用的記憶體将是3*(4-3)+3=6m,但是如果單純的按照res來算的話,三個程序就用了12m的空間。是以了解res和shr這兩個資料的含義對我們在評估一台伺服器能跑多少個程序時尤其重要,不要一看到apache的程序占用了20m,就認為系統能跑的apache程序數就是總的實體記憶體數除以20m,其實這20m裡面有可能有很大一部分是shr的。

注意:top指令輸出中的res和pmap輸出中的rss是一個東西。

pmap

上面top指令隻是給出了一個程序大概占用了多少的記憶體,而pmap能更詳細的給出記憶體都是被誰占用了。pmap指令輸出的内容來自于/proc/[pid]/maps和/proc/[pid]/smaps這兩個檔案,第一個檔案包含了每段的一個大概描述,而後一個檔案包含了更詳細的資訊。

這裡用pmap看看目前bash的記憶體使用情況,:

#這裡$$代表目前bash的程序id,下面隻顯示了部分輸出結果 

dev@dev:~$ pmap  $$ 

2805:   bash 

0000000000400000    976k r-x-- bash 

00000000006f3000      4k r---- bash 

00000000006f4000     36k rw--- bash 

00000000006fd000     24k rw---   [ anon ] 

0000000000be4000   1544k rw---   [ anon ] 

...... 

00007f1fa0e9e000   2912k r---- locale-archive 

00007f1fa1176000   1792k r-x-- libc-2.23.so 

00007f1fa1336000   2044k ----- libc-2.23.so 

00007f1fa1535000     16k r---- libc-2.23.so 

00007f1fa1539000      8k rw--- libc-2.23.so 

00007f1fa153b000     16k rw---   [ anon ] 

00007f1fa196c000    152k r-x-- ld-2.23.so 

00007f1fa1b7e000     28k r--s- gconv-modules.cache 

00007f1fa1b85000     16k rw---   [ anon ] 

00007f1fa1b8f000      8k rw---   [ anon ] 

00007f1fa1b91000      4k r---- ld-2.23.so 

00007f1fa1b92000      4k rw--- ld-2.23.so 

00007f1fa1b93000      4k rw---   [ anon ] 

00007ffde903a000    132k rw---   [ stack ] 

00007ffde90e4000      8k r----   [ anon ] 

00007ffde90e6000      8k r-x--   [ anon ] 

ffffffffff600000      4k r-x--   [ anon ] 

 total            22464k  

這裡第一列是記憶體的起始位址,第二列是mapping的位址大小,第三列是這段記憶體的通路權限,最後一列是mapping到的檔案。這裡的位址都是虛拟位址,大小也是虛拟位址大小。

這裡的輸出有很多的[ anon ]行,表示在磁盤上沒有對應的檔案,這些一般都是可執行檔案或者動态庫裡的bss段。當然有對應檔案的mapping也有可能是anonymous,比如檔案的資料段。關于程式的資料段和bss段的介紹請參考elf的相關資料。

上面可以看到bash、libc-2.23.so等檔案出現了多行,但每行的權限不一樣,這是因為每個動态庫或者可執行檔案裡面都分很多段,有隻能讀和執行的代碼段,有能讀寫的資料段,還有比如這一行“00007f1fa153b000 16k rw--- [ anon ]”,就是它上面一行libc-2.23.so的bss段。

[ stack ]表示程序用到的棧空間,而heap在這裡看不到,因為pmap預設情況下不單獨标記heap出來,由于heap是anonymous,是以從這裡的大小可以推測出來,heap就是“0000000000be4000 1544k rw--- [ anon ]”。

其實從上面的結果根本看不出實際上每段占用了多少實體記憶體,要想看到rss,需要使用-x參數,下面看看更詳細的輸出:

dev@dev:~$ pmap -x $$ 

         address perm   offset device  inode  size  rss  pss referenced anonymous shared_hugetlb private_hugetlb swap swappss locked mapping 

        00400000 r-xp 00000000  fc:00 390914   976  888  526        888         0              0               0    0       0      0 bash 

        006f3000 r--p 000f3000  fc:00 390914     4    4    4          4         4              0               0    0       0      0 bash 

        006f4000 rw-p 000f4000  fc:00 390914    36   36   36         36        36              0               0    0       0      0 bash 

        006fd000 rw-p 00000000  00:00      0    24   24   24         24        24              0               0    0       0      0 

        00be4000 rw-p 00000000  00:00      0  1544 1544 1544       1544      1544              0               0    0       0      0 [heap] 

    ..... 

    7f1fa0e9e000 r--p 00000000  fc:00 136340  2912  400   83        400         0              0               0    0       0      0 locale-archive 

    7f1fa1176000 r-xp 00000000  fc:00 521726  1792 1512   54       1512         0              0               0    0       0      0 libc-2.23.so 

    7f1fa1336000 ---p 001c0000  fc:00 521726  2044    0    0          0         0              0               0    0       0      0 libc-2.23.so 

    7f1fa1535000 r--p 001bf000  fc:00 521726    16   16   16         16        16              0               0    0       0      0 libc-2.23.so 

    7f1fa1539000 rw-p 001c3000  fc:00 521726     8    8    8          8         8              0               0    0       0      0 libc-2.23.so 

    7f1fa153b000 rw-p 00000000  00:00      0    16   12   12         12        12              0               0    0       0      0 

    ...... 

    7f1fa196c000 r-xp 00000000  fc:00 521702   152  144    4        144         0              0               0    0       0      0 ld-2.23.so 

    7f1fa1b7e000 r--s 00000000  fc:00 132738    28   28    9         28         0              0               0    0       0      0 gconv-modules.cache 

    7f1fa1b85000 rw-p 00000000  00:00      0    16   16   16         16        16              0               0    0       0      0 

    7f1fa1b8f000 rw-p 00000000  00:00      0     8    8    8          8         8              0               0    0       0      0 

    7f1fa1b91000 r--p 00025000  fc:00 521702     4    4    4          4         4              0               0    0       0      0 ld-2.23.so 

    7f1fa1b92000 rw-p 00026000  fc:00 521702     4    4    4          4         4              0               0    0       0      0 ld-2.23.so 

    7f1fa1b93000 rw-p 00000000  00:00      0     4    4    4          4         4              0               0    0       0      0 

    7ffde903a000 rw-p 00000000  00:00      0   136   24   24         24        24              0               0    0       0      0 [stack] 

    7ffde90e4000 r--p 00000000  00:00      0     8    0    0          0         0              0               0    0       0      0 [vvar] 

    7ffde90e6000 r-xp 00000000  00:00      0     8    4    0          4         0              0               0    0       0      0 [vdso] 

ffffffffff600000 r-xp 00000000  00:00      0     4    0    0          0         0              0               0    0       0      0 [vsyscall] 

                                             ===== ==== ==== ========== ========= ============== =============== ==== ======= ====== 

                                             22468 5084 2578       5084      1764              0               0    0       0      0 kb  

權限字段多了一個s和p的标記,s表示是和别人共享的記憶體空間,讀寫會影響到其他程序,而p表示這是自己私有的記憶體空間,讀寫這部分記憶體不會對其他程序造成影響。

輸出标示出了[heap]段,并且也說明了後面幾個[anon]代表的什麼意思(vvar,vdso,vsyscall都是映射到核心的特殊段),mapping字段為空的都是上一行mapping檔案裡面的bss段(可是gconv-modules.cache後面有兩行anonymous mapping,可能跟共享記憶體有關系,沒有深究)。

anonymous列标示出了哪些是并且有多少是anonymous方式映射的實體記憶體,其大小小于等于rss

rss清單示實際占用的實體記憶體大小

top指令輸出的shr記憶體

最後來看看top指令輸出的shr到底由pmap的哪些輸出構成

dev@dev:~$ pmap -d $$ 

3108:   bash 

address           kbytes mode  offset           device    mapping 

0000000000400000     976 r-x-- 0000000000000000 0fc:00000 bash 

00000000006f3000       4 r---- 00000000000f3000 0fc:00000 bash 

00000000006f4000      36 rw--- 00000000000f4000 0fc:00000 bash 

00000000006fd000      24 rw--- 0000000000000000 000:00000   [ anon ] 

0000000000c23000    1544 rw--- 0000000000000000 000:00000   [ anon ] 

00007f53af18e000      16 rw--- 0000000000000000 000:00000   [ anon ] 

00007f53af198000       8 rw--- 0000000000000000 000:00000   [ anon ] 

00007f53af19a000       4 r---- 0000000000025000 0fc:00000 ld-2.23.so 

00007f53af19b000       4 rw--- 0000000000026000 0fc:00000 ld-2.23.so 

00007f53af19c000       4 rw--- 0000000000000000 000:00000   [ anon ] 

00007ffc5a94b000     132 rw--- 0000000000000000 000:00000   [ stack ] 

00007ffc5a9b7000       8 r---- 0000000000000000 000:00000   [ anon ] 

00007ffc5a9b9000       8 r-x-- 0000000000000000 000:00000   [ anon ] 

ffffffffff600000       4 r-x-- 0000000000000000 000:00000   [ anon ] 

mapped: 22464k    writeable/private: 1848k    shared: 28k 

dev@dev:~$ top -p $$ 

  pid user      pr  ni    virt    res    shr s %cpu %mem     time+ command 

 3108 dev       20   0   22464   5028   3264 s  0.0  1.0   0:00.02 bash 

從上面的輸出可看出shr ≈ res - writeable/private,其中writeable/private主要包含stack和heap以及可執行檔案和動态庫的data和bss段,而stack+heap=1544+132=1675,這已經占了絕大部分,進而data和bss段之類的基本上可以忽略了,是以一般情況下,shr ≈ res - [heap] - [stack],由于stack一般都比較小,上面的等式可以進一步約等于:shr ≈ res - [heap]。

總結

top指令能看到一個程序占用的虛拟記憶體空間、實體記憶體空間以及和别的程序共享的實體記憶體空間,這裡共享的空間包括通過mmap共享的記憶體以及共享的可執行檔案以及動态庫。而mmap指令能看到更詳細的資訊,比如可執行檔案和它所連結的動态庫大小,以及實體記憶體都是被哪些段給占用了。

程序占用的虛拟位址空間大小跟程式的規模有關,除了stack和heap段,其他段的大小基本上都是固定的,并且在程式連結的時候就已經确定了,是以基本上隻要關注stack和heap段就可以了,由于stack相對heap來說很小,是以隻要沒什麼stack異常,隻需要關注heap。

在實際的工作過程中,其實我們更關心的是rss用了多少,都被誰用了,簡單點說,如果我們沒有同時啟動多個程序(同一個程式),rss就是一個很好的實際實體記憶體使用參考值,但如果是像apache那樣同時跑很多個程序,那麼rss減去shr所占用的空間就是一個很好的實際實體記憶體占用參考值,當然這都是大概估算值。

要想精确評估一個程序到底占了多少記憶體,還是很難的,需要對程序的每個段有深入的了解,尤其是shr部分都有哪些程序在一起共享,不過現在伺服器上的記憶體都是以g為機關的,是以一般情況下大概的估算一下加上合理的測試就能滿足我們的需求了。

作者:wuyangchun

來源:51cto