malloc()函數有始有終

1 malloc()

malloc()函數是申請一塊堆區內存的常用函數,簡單應用如下:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[]){
    char *test = (char *)malloc(100);
    test[0] = 'A';
    printf("0x%02x\n", test[4095]);
    return 0;
}

編譯執行後的結果如下:
這裏寫圖片描述
問題出現了,明明上述malloc()函數只開闢了100個字節的空間,爲什麼可以訪問第4096個字節呢?
因爲malloc()函數執行時,調用的系統調用是sys_brk(),該系統調用分配進程虛擬地址空間的時候是以4KB頁爲單位進行分配的,而且當出現缺頁異常的時候,默認操作的基本單位同樣是4KB的頁,於是sys_brk()系統調用最小分配4096字節的內容。

2 用戶空間的malloc堆區管理

  其實,malloc()函數並沒有直接調用sys_brk()、sys_mmap()、sys_mumap()系統調用,malloc()函數是glibc函數庫對於進程堆區內存管理的一種實現方法,類似於內核中的kmalloc函數用於分配和管理內核中需要的內存空間,但是內核中的kmalloc()函數依賴於內核中的slab分配器實現具體的內核虛擬地址空間內存的分配,而malloc()依賴於glibc庫的管理機制和sys_brk()、sys_mmap()、sys_mumap()系統調用實現具體的用戶虛擬地址空間內存的分配,兩者的工作原理和工作場景是不一樣的。其中,sys_mmap()用於在堆區和棧區之間的虛擬地址空間分配一塊獨立的虛擬地址,使用vm_area_struct結構體表示,使用sys_mumap()進行內存釋放,而sys_brk()用於調整mm->brk的大小,來增加/釋放堆區的虛擬地址空間,那什麼時候使用sys_mmap()h函數、什麼時候使用sys_brk()函數呢?glibc根據兩個系統調用的優缺點規定:當malloc分配的內存不大於M_MMAP_THRESHOLD(通常爲128KB)時,使用sys_brk()系統調用分配內存,否則使用sys_mmap()系統調用分配內存。(備註:使用sys_brk()系統調用不能隨意地釋放已經分配的內存,只能首先釋放最後一次通過sys_brk()系統調用分配的內存;而sys_mmap()分配的任何一塊內存可以使用sys_mumap()系統調用隨意的釋放掉。參考:brk和mmap
  glibc函數庫使用塊來劃分通過sys_brk()、sys_mmap()系統調用獲得的內存區域,然後使用隱式鏈表結構(在塊的開始幾個字節內存儲塊之間的鏈表關係)來管理劃分的這些塊,根據塊是否被佔用給,分爲:空閒塊和佔用塊。其中,glibc參照內核中slab分配器的工作原理根據空閒塊的大小將空閒塊劃分爲了多個類別,在每個類別中使用鏈表來管理所有的塊,比如:size爲1024字節的塊放到一個鏈表中,size爲512字節的塊放到一個鏈表中等。因此,塊的分配和回收會涉及到隱式鏈表結構中元素的分割、合併等操作,而常見的堆區溢出的原理也存在於glibc庫中對堆區隱式鏈表操作的代碼中,具體詳解請見文章“Understanding glibc malloc”。

3 內核空間的sys_brk()系統調用

  《奔跑吧,linux內核》將malloc比作是零售商,而把sys_brk比作代理商,個人感覺還是相當貼切的。
sys_brk()負責在進程虛擬地址空間的堆區(start_brk, mmap_base)開闢一段適合的虛擬地址空間,根據sys_brk()接收的參數對該部分虛擬地址空間進行相應的操作。
一個進程的虛擬地址空間使用結構體mm_struct來表示,其中mm->start_brk代表是堆區的起始地址,由於堆是向上(向地址較高處)增長,於是其最大不會超過mm->mmap_base所表示的mmap內存區域的大小,《奔跑吧,Linux內核》給出32位系統中堆區內存長度最大爲1GB。另外,進程堆區當前的最高點是mm->brk。
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章