1. 程式人生 > >linux記憶體使用方法詳細介紹

linux記憶體使用方法詳細介紹

我是一名程式設計師,那麼我在這裡以一個程式設計師的角度來講解Linux記憶體的使用。

一提到記憶體管理,我們頭腦中閃出的兩個概念,就是虛擬記憶體,與實體記憶體。這兩個概念主要來自於linux核心的支援。

Linux在記憶體管理上份為兩級,一級是線性區,類似於00c73000-00c88000,對應於虛擬記憶體,它實際上不佔用實際實體記憶體;一級是具體的物理頁面,它對應我們機器上的實體記憶體。

這裡要提到一個很重要的概念,記憶體的延遲分配。Linux核心在使用者申請記憶體的時候,只是給它分配了一個線性區(也就是虛存),並沒有分配實際實體記憶體;只有當用戶使用這塊記憶體的時候,核心才會分配具體的物理頁面給使用者,這時候才佔用寶貴的實體記憶體。核心釋放物理頁面是通過釋放線性區,找到其所對應的物理頁面,將其全部釋放的過程。

1 2 3 char *p=malloc(2048) //這裡只是分配了虛擬記憶體2048,並不佔用實際記憶體。 strcpy(p,”123”) //分配了物理頁面,雖然只是使用了3個位元組,但記憶體還是為它分配了2048位元組的實體記憶體。 free(p) //通過虛擬地址,找到其所對應的物理頁面,釋放物理頁面,釋放線性區。

我們知道使用者的程序和核心是執行在不同的級別,程序與核心之間的通訊是通過系統呼叫來完成的。程序在申請和釋放記憶體,主要通過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 char *p=malloc(2); free(p)

為什麼申請記憶體的時候,需要兩個引數,一個是記憶體大小,一個是返回的指標;而釋放記憶體的時候,卻只要記憶體的指標呢?

這主要是和glibc的記憶體管理機制有關。glibc中,為每一塊記憶體維護了一個chunk的結構。glibc在分配記憶體時,glibc先填寫chunk結構中記憶體塊的大小,然後是分配給程序的記憶體。

1 2 chunk ------size p------------ content

在程序釋放記憶體時,只要 指標-4 便可以找到該塊記憶體的大小,從而釋放掉。

注:glibc在做記憶體申請時,最少分配16個位元組,以便能夠維護chunk結構。

glibc提供的除錯工具:

為了方便除錯,glibc 為使用者提供了 malloc 等等函式的鉤子(hook),如 __malloc_hook

對應的是一個函式指標,

1 void *function (size_t size, const void *caller)

其中 caller 是呼叫 malloc 返回值的接受者(一個指標的地址)。另外有 __malloc_initialize_hook函式指標,僅僅會呼叫一次(第一次分配動態記憶體時)。(malloc.h)

一些使用 malloc 的統計量(SVID 擴充套件)可以用 struct mallinfo 儲存,可呼叫獲得。

1 struct mallinfo mallinfo (void)

如何檢測 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 #include #include #include intmain( int argc, char *argv[] ) {   int *p, *q ;   #ifdef DEBUGGING   mtrace( ) ;   #endif   p = malloc( sizeof( int ) ) ;   q = malloc( sizeof( int ) ) ;   printf( "p = %p\nq = %p\n", p, q ) ;   *p = 1 ;   *q = 2 ;   free( p ) ;   return 0 ; }

很簡單的程式,其中 q 沒有被釋放。我們設定了環境變數後並且 touch 出該檔案

執行結果如下:

p = 0x98c0378q = 0x98c0388

該檔案內容如下

1 2 3 4 = Start @./test30:[0x8048446] + 0x98c0378 0x4 @./test30:[0x8048455] + 0x98c0388 0x4 @./test30:[0x804848f] - 0x98c0378

到這裡我基本上講完了,我們寫程式時,資料部分記憶體使用的問題。

程式碼佔用的記憶體

資料部分佔用記憶體,那麼我們寫的程式是不是也佔用記憶體呢?

在linux中,程式的載入,涉及到兩個工具,linker 和loader。Linker主要涉及動態連結庫的使用,loader主要涉及軟體的載入。

  1. exec執行一個程式
  2. elf為現在非常流行的可執行檔案的格式,它為程式執行劃分了兩個段,一個段是可以執行的程式碼段,它是隻讀,可執行;另一個段是資料段,它是可讀寫,不能執行。
  3. loader會啟動,通過mmap系統呼叫,將程式碼端和資料段對映到記憶體中,其實也就是為其分配了虛擬記憶體,注意這時候,還不佔用實體記憶體;只有程式執行到了相應的地方,核心才會為其分配實體記憶體。
  4.  loader會去查詢該程式依賴的連結庫,首先看該連結庫是否被對映進記憶體中,如果沒有使用mmap,將程式碼段與資料段對映到記憶體中,否則只是將其加入程序的地址空間。這樣比如glibc等庫的記憶體地址空間是完全一樣。

因此一個2M的程式,執行時,並不意味著為其分配了2M的實體記憶體,這與其運行了的程式碼量,與其所依賴的動態連結庫有關。

執行過程中連結動態連結庫與編譯過程中連結動態庫的區別

我們呼叫動態連結庫有兩種方法:一種是編譯的時候,指明所依賴的動態連結庫,這樣loader可以在程式啟動的時候,來所有的動態連結對映到記憶體中;一種是在執行過程中,通過dlopen和dlfree的方式載入動態連結庫,動態將動態連結庫載入到記憶體中。

這兩種方式,從程式設計角度來講,第一種是最方便的,效率上影響也不大,在記憶體使用上有些差別。

第一種方式,一個庫的程式碼,只要執行過一次,便會佔用實體記憶體,之後即使再也不使用,也會佔用實體記憶體,直到程序的終止。

第二中方式,庫程式碼佔用的記憶體,可以通過dlfree的方式,釋放掉,返回給實體記憶體。

這個差別主要對於那些壽命很長,但又會偶爾呼叫各種庫的程序有關。如果是這類程序,建議採用第二種方式呼叫動態連結庫。

佔用記憶體的測量

測量一個程序佔用了多少記憶體,linux為我們提供了一個很方便的方法,/proc目錄為我們提供了所有的資訊,實際上top等工具也通過這裡來獲取相應的資訊。

1 2 3 4 5 /proc/meminfo 機器的記憶體使用資訊 /proc/pid/maps pid為程序號,顯示當前程序所佔用的虛擬地址。 /proc/pid/statm 程序所佔用的記憶體 [[email protected] ~]# cat /proc/self/statm 654 57 44 0 0 334 0

輸出解釋

CPU 以及CPU0。。。的每行的每個引數意思(以第一行為例)為:

引數 解釋 /proc//status

1 2 3 4 5 6 7 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

檢視機器可用記憶體

1 2 3 4 5 /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/pid/statm統計出來的資料,會將這些動態連結庫所佔用的記憶體也簡單的算進來。

這樣帶來的問題,動態連結庫佔用的記憶體有些是其他程式使用時佔用的,卻算在了你這裡。你的程式中包含了子程序,那麼有些動態連結庫重用的記憶體會被重複計算。

因此要想準確的評估一個程式所佔用的記憶體是十分困難的,通過寫一個module的方式,來準確計算某一段虛擬地址所佔用的記憶體,可能對我們有用。

相關推薦

linux記憶體使用方法詳細介紹

我是一名程式設計師,那麼我在這裡以一個程式設計師的角度來講解Linux記憶體的使用。 一提到記憶體管理,我們頭腦中閃出的兩個概念,就是虛擬記憶體,與實體記憶體。這兩個概念主要來自於linux核心的支援。 Linux在記憶體管理上份為兩級,一級是線性區,類似於00c73000

Linux目錄結構詳細介紹(一)

linux 目錄結構 頂點 / ,其它所有目錄都在根下根下面的目錄及目錄裏的子目錄是一個有層次的倒掛樹狀結構目錄描述/處於linux系統樹形結構的最頂端,它是linux文件系統的入口,所有的目錄、文件、設備都在/之下。/bin/bin是Binary的縮寫,存放著linux系統命令。/boot/包括內核

linux目錄結構詳細介紹

linux目錄1、樹狀目錄結構圖2、/目錄3、/etc/目錄4、/usr/目錄5、/var/目錄6、/proc/目錄7、/dev/目錄1、樹狀目錄結構圖2、/目錄目錄描述/第一層次結構的根、整個文件系統層次結構的根目錄。/bin/需要在單用戶模式可用的必要命令(可執行文件);面向所有用戶,例如:cat、ls、

(轉)linux目錄結構詳細介紹

中產 沒有 默認 耗時 共享 而是 控制臺 toc inf linux目錄結構詳細介紹 原文:http://blog.51cto.com/yangrong/1288072 目錄 1、樹狀目錄結構圖 2、/目錄 3、/etc/目錄 4、/usr/目錄 5、/var/目錄 6、

python模塊之calendar方法詳細介紹

pan 情況下 即使 str 行數 pre 參數 給定 pack calendar,是與日歷相關的模塊。calendar模塊文件裏定義了很多類型,主要有Calendar,TextCalendar以及HTMLCalendar類型。其中,Calendar是TextCalenda

linux檔案目錄詳細介紹

linux檔案目錄 目錄 /bin 存放二進位制可執行檔案(ls,cat,mkdir等),常用命令一般都在這裡 /etc 存放系統管理和配置檔案 /home 存放

java向多執行緒中傳遞引數的三種方法詳細介紹

在傳統的同步開發模式下,當我們呼叫一個函式時,通過這個函式的引數將資料傳入,並通過這個函式的返回值來返回最終的計算結果。但在多執行緒的非同步開發模式下,資料的傳遞和返回和同步開發模式有很大的區別。由於執行緒的執行和結束是不可預料的,因此,在傳遞和返回資料時就無法象函式一樣通過

linux 目錄結構詳細介紹

目錄 1、樹狀目錄結構圖 2、/目錄 3、/etc/目錄 4、/usr/目錄 5、/var/目錄 6、/proc/目錄 7、/dev/目錄 該文章主要來自於網路進行整理。 目錄結構參考地址: 下面紅色字型為比較重要的目錄 1、樹狀目錄結構 2、/目錄

Linux】awk詳細介紹

awk簡介     awk是一種使用方便且表現力很強的程式語言,它可以應用在多種不同的計算與資料處理任務中。由於awk天生提供對檔案中文字分列進行處理,所以如果一個檔案中的每行都被特定的分隔符(常見的是空格)隔開,我們可以將這個檔案看成是由很多列的文字組成,這樣的檔案最適合用aw

8、vi 使用方法詳細介紹——未完成

vi編輯器是所有Unix及Linux系統下標準的編輯器,它的強大不遜色於任何最新的文字編輯器,這裡只是簡單地介紹一下它的用法和一小部分指令。由於對Unix及Linux系統的任何版本,vi編輯器是完全相同的,因此您可以在其他任何介紹vi的地方進一步瞭解它。vi也是Linux中最基本的文字編輯器

Android記憶體管理詳細介紹 native heap dalvikheap 超有用的!!!!!!

尊重原創作者,轉載請註明出處: 最近在網上看了不少Android記憶體管理方面的博文,但是文章大多都是就單個方面去介紹記憶體管理,沒有能全域性把握,缺乏系統性闡述,而且有些觀點有誤,僅僅知道這些,還是無法從整體上理解記憶體管理,對培養系統優化和系統穩定性分析方面的能力是不夠的。     我結合自己的一些思

Linux各目錄詳細介紹

【常見目錄說明】 目錄 /bin 存放二進位制可執行檔案(ls,cat,mkdir等),常用命令一般都在這裡。 /etc 存放系統管理和配置檔案 /home 存放所有使用者檔案的根目錄,是使用者主目錄的基點,比如使用者user的主目錄就是/home

什麼是UPNP協議:UPNP協議作用及啟用路由器UPNP支援的方法詳細介紹

目錄 [隱藏] UPNP簡介 基本概念 官網解釋 以下是微軟官方網站對UPnP的解釋: 以下是BC官方網站對UPnP的解釋: UPnP是用來幹什麼的? 經典應用 網路地址轉換 NAT 穿越技術 實際應用 UPnP協議特色 UPnP協議

vb mid函式的使用方法詳細介紹

mid函式從字串中返回指定數量的字元。 語法 Mid(string, start[, length]) 對語法的理解就是:返回string中從start開始的後面的length長度的字串 string是必需的引數,如果string包含Null,返回結果也將是Nul

Python字串方法詳細介紹3_變形

# 3.變形 lower(), upper(), capitalize(), swapcase(), title() 這幾個方法比較簡單,它們不需要輸入引數,返回相應的結果 (1)lower() 將原字串的字元全部轉成小寫字母,若有數字或其他字元就原樣輸出 >>

Linux目錄及詳細介紹

/:根目錄,位於Linux檔案系統目錄結構的頂層,一般根目錄下只存放目錄,不要存放檔案,/etc、/bin、/dev、/lib、/sbin應該和根目錄放置在一個分割槽中。 /bin,/usr/bin:該目錄為命令檔案目錄,也稱為二進位制目錄。包含了供系統管理員及普通使用者使用的重要的linux命令和二進位制(

thinkphp5增刪改查方法詳細介紹

sel set php 更新記錄 多條 think 四種方法 spa exec 1 <? 2 // 1.thinkphp5添加記錄 3 // 第一種方法 4 $result=Db::execute(‘insert into think_data (n

Linux記憶體管理—詳細講解

`摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之間的關係,希

linux的gzip、bzip2、xz三種壓縮使用方法工具介紹

linux bzip2 gzip xz 壓縮打包介紹:常用壓縮文件類型:1、windows:.rar/.zip/.7z2、linux:.zip/.gz/.bz2/.xz/.tar/.gz/.tar/.bz2/.tar/.xzgzip壓縮工具:1、實驗,cd到/tmp目錄下,使用mkdi創建一個

Linux各目錄及每個目錄的詳細介紹

直接 系統引導 ilo 硬盤驅動 另有 升級 針對 更新 pool Linux各目錄及每個目錄的詳細介紹,包括linux常見核心目錄諸如/bin,/etc,/home,/usr,/root,/dev,/var,/proc等常見目錄詳細介紹及說明。 linux常見目錄說明 /