2.1 x86 平台 linux 程序記憶體布局
linux 系統在裝載 elf 格式的程式檔案時,會呼叫 loader 把可執行檔案中的各個段依次載入到從某一位址開始的空間中(載入位址取決 link editor(ld)和機器位址位數,在 32 位機器上是 0x8048000,即 128m 處)。如下圖所示,
以 32 位機器為例,首先被載入的是.text 段, 然後是.data 段,最後是.bss 段。這可以看作是程式的開始空間。程式所能訪問的最後的位址是 0xbfffffff,也就是到 3g 位址處,3g 以上的 1g 空間是核心使用的,應用程式不可以直接訪問。應用程式的堆疊從最高位址處開始向下生長,.bss 段與堆疊之間的空間是空閒的, 空閒空間被分成兩部分,一部分為 heap,一部分為 mmap 對映區域,mmap 對映區域一般從 task_size/3 的地方開始,但在不同的 linux 核心和機器上,mmap 區域的開始位置一般是不同的。heap 和 mmap 區域都可以供使用者自由使用,但是它在剛開始的時候並沒有對映到記憶體空間內,是不可訪問的。在向核心請求分配該空間之前,對這個空間的訪問會導致segmentation fault。使用者程式可以直接使用系統呼叫來管理 heap 和mmap 對映區域,但更多的時候程式都是使用c 語言提供的 malloc()和 free()函式來動態的分配和釋放記憶體。stack 區域是唯一不需要對映,使用者卻可以訪問的記憶體區域,這也是利用堆疊溢位進行攻擊的基礎。
2.2.132位模式下程序預設記憶體布局
在預設的程序記憶體布局中,棧至頂向下擴充套件,並且棧是有界的。堆至底向上擴充套件,mmap 對映區域至頂向下擴充套件,mmap 對映區域和堆相對擴充套件,直至耗盡虛擬位址空間中的剩餘區域,這種結構便於 c 執行時庫使用 mmap 對映區域和堆進行記憶體分配。
上圖的布局形式是在核心2.6.7 以後才引入的,這是 32 位模式下程序的預設記憶體布局形式。
2.1作業系統記憶體分配的相關函式
heap 和mmap 對映區域是可以提供給使用者程式使用的虛擬記憶體空間,如何獲得該區域的記憶體呢?作業系統提供了相關的系統呼叫來完成相關工作。對 heap 的操作,作業系統提供了brk()函式,c 執行時庫提供了sbrk()函式;對 mmap對映區域的操作,作業系統提供了 mmap()和 munmap()函式。sbrk(),brk() 或者 mmap() 都可以用來向我們的程序新增額外的虛擬記憶體。glibc 同樣是使用這些函式向作業系統申請虛擬記憶體。
這裡要提到乙個很重要的概念,記憶體的延遲分配,只有在真正訪問乙個位址的時候才建立這個位址的物理對映,這是 linux 記憶體管理的基本思想之一。linux 核心在使用者申請記憶體的時候,只是給它分配了乙個線性區(也就是虛擬記憶體區域),並沒有分配實際物理記憶體;只有當使用者使用這塊記憶體的時候,核心才會分配具體的物理頁面給使用者,這時候才占用寶貴的物理記憶體。核心釋放物理頁面是通過釋放線性區,找到其所對應的物理頁面,將其全部釋放的過程。
2.2.1heap 操作相關函式
heap 操作函式主要有兩個,brk()為系統呼叫,sbrk()為 c 庫函式。系統呼叫通常提供一種最小功能,而庫函式通常提供比較複雜的功能。glibc 的 malloc 函式族(realloc,calloc 等) 就呼叫 sbrk()函式將資料段的下界移動,sbrk()函式在核心的管理下將虛擬位址空間對映到記憶體,供 malloc()函式使用。
核心資料結構 mm_struct 中的成員變數 start_code 和 end_code 是程序**段的起始和終止位址,start_data 和 end_data 是程序資料段的起始和終止位址,start_stack 是程序堆疊段起始位址,start_brk 是程序動態記憶體分配起始位址(堆的起始位址),還有乙個 brk(堆的當前最後位址),就是動態記憶體分配當前的終止位址。c語言的動態記憶體分配基本函式是 malloc(),在 linux 上的實現是通過核心的 brk 系統呼叫,sbrk()是對brk()簡單封裝。brk()是乙個非常簡單的系統呼叫, 只是簡單地改變mm_struct 結構的成員變數 brk 的值。
這兩個函式的定義如下:
#include int brk(void *addr);
void *sbrk(intptr_t increment);
需要說明的是,但 sbrk()的引數 increment 為 0 時,sbrk()返回的是程序的當前 brk 值,
increment 為正數時擴充套件 brk 值,當 increment 為負值時收縮 brk 值。
2.2.2mmap 對映區域操作相關函式
mmap()函式將乙個檔案或者其它物件對映進記憶體
。檔案被對映到多個頁上,如果檔案的大小不是所有頁的大小之和,最後乙個頁不被使用的空間將會清零。munmap 執行相反的操作,刪除特定位址區域的物件對映。函式的定義如下:
#include
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);
引數:start:對映區的開始位址。
length:對映區的長度。
prot:期望的記憶體保護標誌,不能與檔案的開啟模式衝突。是以下的某個值,可以通過
or 運算合理地組合在一起。ptmalloc 中主要使用了如下的幾個標誌:
prot_exec //頁內容可以被執行,ptmalloc 中沒有使用
prot_read //頁內容可以被讀取,ptmalloc 直接用 mmap 分配記憶體並立即返回給使用者時設定該標誌
prot_write //頁可以被寫入,ptmalloc 直接用 mmap 分配記憶體並立即返回給使用者時設定該標誌
prot_none //頁不可訪問,ptmalloc 用 mmap 向系統「批發」一塊記憶體進行管理時設定該標誌
flags:指定對映物件的型別,對映選項和對映頁是否可以共享。它的值可以是乙個或者多個以下位的組合體
map_fixed //使用指定的對映起始位址,如果由 start 和 len 引數指定的記憶體區重疊於現存的對映空間,重疊部分將會被丟棄。如果指定的起始位址不可用,
操作將會失敗。並且起始位址必須落在頁的邊界上。ptmalloc 在**從系統中「批發」的記憶體時設定該標誌。
map_private //建立乙個寫入時拷貝的私有對映。記憶體區域的寫入不會影響到原檔案。這個標誌和以上標誌是互斥的,只能使用其中乙個。ptmalloc 每次呼叫mmap 都設定該標誌。
map_noreserve //不要為這個對映保留交換空間。當交換空間被保留,對對映區修改的可能會得到保證。當交換空間不被保留,同時記憶體不足,對對映區的修改
會引起段違例訊號。ptmalloc 向系統「批發」記憶體塊時設定該標誌。
map_anonymous //匿名對映,對映區不與任何檔案關聯。ptmalloc 每次呼叫 mmap
都設定該標誌。
fd:有效的檔案描述詞。如果 map_anonymous 被設定,為了相容問題,其值應為-1。
offset:被對映物件內容的起點。
glibc記憶體管理方式
程式設計師接觸的記憶體空間和系統接觸的物理記憶體空間是有所區別的。對於一般程序來講,他面對的是乙個線性虛擬記憶體空間 位址從0到最大值。每乙個程序面對的虛擬記憶體空間都是一樣的,都享有全部的記憶體位址。虛擬記憶體空間是線性的,但並不意味著是連續的。部分位址段的虛擬空間可以是缺失的 不是所有位址都可以...
GLIBC記憶體池管理 分箱管理
1.glibc記憶體管理核心結構 1.1.malloc chunk struct malloc chunk struct malloc chunk,該結構是glibc中記憶體管理的最小單元,每個使用者分配的最小記憶體塊必須為 2 user size offsetof struct malloc ch...
GLIBC記憶體分配機制引發的「記憶體洩露」
我們正在開發的類資料庫系統有乙個記憶體模組,出現了乙個疑似 記憶體洩露 問題,現象如下 記憶體模組的記憶體釋放以後沒有歸還作業系統,比如記憶體模組占用的記憶體為10gb,釋放記憶體以後,通過top命令或者 proc pid status檢視占用的記憶體有時仍然為10g,有時為5g,有時為3g,etc...