1. 程式人生 > >程序的虛擬記憶體,實體記憶體,共享記憶體

程序的虛擬記憶體,實體記憶體,共享記憶體

想必在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:幾個欄位

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

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

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

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

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

總結

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

top命令通過解析/proc/<pid>/statm統計VIRTRESSHR欄位值。

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

RES是程序使用

相關推薦

java中程序間通訊基於檔案和共享記憶體

附上編譯執行的原始碼: 1,請分別建立兩個工程來進行讀寫操作; 2,請在相應的目錄建立一個檔案例如:/home/tory/workspace/sharedMemory.txt 3,往檔案中輸入超過12個位元組資料:echo asldkjasldkddas > /hom

程序通訊---管道、訊息佇列、共享記憶體

程序通訊分為低階通訊和高階通訊。 低階通訊是指程序互斥與同步,包括訊號、訊號量、管程等。 高階通訊方式有管道、訊息佇列、共享記憶體以及網路通訊中的套接字。 匿名管道PIPE: 管道是連線兩個程序的檔案,

java虛擬機器學習之執行緒共享記憶體區和執行緒私有區

 執行緒共享指的就是可以允許被所有執行緒共享訪問的一塊記憶體,包括堆區,方法區和執行時常量池。  1. java堆區      java堆區在虛擬機器啟動時被建立,並且他在實際記憶體中是可以不連續的。

程序間通訊的訊息佇列和共享記憶體方式的實現

共享記憶體方式使用QSharedMemory 和QSystemSemaphore兩個類實現 一個程序往共享記憶體空間中寫,一個程序往共享記憶體空間中讀 兩程序通訊時: 向共享記憶體中提供資料的一方: 1,定義QSharedMemory shareMemory,並設定標誌名shareMemory.setKey(

【Linux】程序間通訊(IPC)之共享記憶體詳解與測試用例

學習環境centos6.5 Linux核心2.6 什麼是共享記憶體 共享記憶體允許兩個或更多程序訪問同一塊記憶體。當一個程序改變了這塊記憶體中的內容的的時候,其他程序都會察覺到這個更改。 效率: 因為所有程序共享同一塊記憶體,共享記憶體在各種程序

共享記憶體及其用mmap實現共享記憶體

一、什麼是共享記憶體 顧名思義,共享記憶體就是允許兩個不相關的程序訪問同一個邏輯記憶體。共享記憶體是在兩個正在執行的程序之間共享和傳遞資料的一種非常有效的方式。不同程序之間共享的記憶體通常安排為同一段實體記憶體。程序可以將同一段共享記憶體連線到它們自己的地址空間中,所有程序都可以訪問共享記

Linux釋放記憶體及手動釋放Oracle共享記憶體

free -m #echo 3 > /proc/sys/vm/drop_caches#cat /proc/sys/vm/drop_caches3 free -m 手動刪除Oracle共享記憶

【Python】記憶體檢視(操作資料共享記憶體

memoryview memoryview可以使用不同的方式讀取和操作同一塊記憶體,並且原有的記憶體位元組不會隨意移動。類似於C中的強轉。 例如,使用memoryview修改一個短整型有符號整數陣列

程序虛擬記憶體實體記憶體共享記憶體

想必在linux上寫過程式的同學都有分析程序佔用多少記憶體的經歷,或者被問到這樣的問題——你的程式在執行時佔用了多少記憶體(實體記憶體)?通常我們可以通過top命令檢視程序佔用了多少記憶體。這裡我們可以看到VIRT、RES和SHR三個重要的指標,他們分別代表什麼意思呢

c/c++ linux 程序間通訊系列4使用共享記憶體

linux 程序間通訊系列4,使用共享記憶體 1,建立共享記憶體,用到的函式shmget, shmat, shmdt 函式名 功能描述 shmget 建立共享記憶體,返回pic key

Linux:程序間通訊(匿名管道命名管道)(共享記憶體訊息佇列訊號量)

目錄 程序間通訊的介紹 管道 匿名管道 原理: 程式碼實現 匿名管道特性 實現管道符 |  命名管道 命名管道特性 程式碼實現 管道讀寫規則 作業系統中ipc的相關命令 共享記憶體(重點) 生命週期: 程式碼實現 程式碼實現獲

嵌入式Linux併發程式設計程序間通訊方式System V IPC物件ftok()共享記憶體使用步驟建立shmget()對映shmat()撤銷對映shmdt()控制shmctl()注意

文章目錄 1,System V IPC 2,使用IPC物件的大致流程 3,生成KEY值ftok() ftok示例 4,共享記憶體 4.1,共享記憶體使用步驟 4.2,共享記憶體建立 shmget()

python多程序共享記憶體我喜歡解除安裝思維導圖裡面現在還沒有寫全python得ctype還沒有特別得熟悉

多程序與多執行緒頭大啊 1.C++ 將程序執行在指定的CPU上 1.1.https://blog.csdn.net/w2014qian/article/details/51941549 2.多程序 2.1.管道通訊 2.1.1.程序start啟動程序 join阻

虛擬記憶體虛擬記憶體地址實體記憶體實體記憶體地址

計算機會對虛擬記憶體地址空間(32位為4G)分頁產生頁(page),對實體記憶體地址空間(假設256M)分頁產生頁幀(page frame),這個頁和頁幀的大小是一樣大的,所以呢,在這裡,虛擬記憶體頁的個數勢必要大於實體記憶體頁幀的個數。在計算機上有一個頁表(page table),就是對映虛擬記憶體頁到實體

使用共享記憶體實現一個程序寫檔案兩個程序讀檔案

主要功能:讀取任意的檔案,大小不限(不超過共享記憶體設定的大小,一般為8k,但可手動重新設定,這已經很大了。),執行後兩個或多個讀程序可同時讀取該檔案並在終端列印。 要實現該功能,首先你得了解共享記憶體的搭建,有四個函式,分別為shmget,shmat,shmdt,shmc

shmget 共享記憶體 同步讀寫檔案一個程序多個程序讀和寫同步邊寫邊讀

首先,看看老大給我的任務:實現一個模組間的記憶體管理庫, 實現以下功能 1、該記憶體庫通訊的資料量不確定, 最大5Mbit/s  2、該記憶體庫用於模組間的資料互動 3、該記憶體庫只允許一個模組寫入, 但可多個模組讀取, 但需要各個讀取模組沒有任何相互干擾, 比如一個模組

boost程序間通訊常用開發一篇全(訊息佇列共享記憶體訊號)

本文概要:         敏捷開發大家想必知道而且評價甚高,縮短開發週期,提高開發質量。將大工程獨立為不同的小app開發,整個開發過程,程式可用可測,所以提高了整體的質量。基於這種開發模式和開發理念,程序間通訊必然是童鞋們必掌握技能之一了,而boost庫是眾多庫中平臺支援

共享記憶體程序間通訊程序間同步使用訊號量來實現

Linux 環境下C程式設計指南,通過共享記憶體進行程序間通訊的例子,程序間同步使用訊號量來實現。 程式碼 11-5 使用說明:這是一個簡單的伺服器和客戶端程式,如果啟動程式時不帶引數,則執行伺服器程式; 如果帶引數,則執行客戶端程式,所帶引數只有一個,就是伺服器端所顯

程序程式設計之程序間通訊-共享記憶體訊號量和套接字

1. 背景 本文將介紹程序通訊中的訊號量,共享記憶體和套接字方法。 2. 訊號量 2.1 訊號量的定義 為了防止出現因多個程式同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有一個執行執行緒

Linux程序間通訊--訊號管道訊息佇列訊號量共享記憶體,socket

Linux 傳統的程序間通訊有很多,如各類管道、訊息佇列、記憶體共享、訊號量等等。但它們都無法介於核心態與使用者態使用,原因如表 通訊方法 無法介於核心態與使用者態的原因 管道(不包括命名管道) 侷限於父子程序間的通訊。 訊息佇列 在硬、軟中斷中無法無阻塞地接收資料。 訊號量 無法介於核