linux動態記憶體分配-詳解
程序可以通過增加堆的大小來分配記憶體,堆就是一段長度可變的連續的虛擬記憶體,開始於未初始化資料段末尾,隨著記憶體的分配和釋放增減。通常堆的當前記憶體邊界稱為program break。最初,program break正好位於未出華資料段末尾之後(&end位置)。
當program break位置上升後,程式可以訪問新分配的任何記憶體地址,而此時實體記憶體尚未分配,核心會在程序試圖首次訪問這些虛擬記憶體地址時才分配實體記憶體頁。
下面來講講2個操作program break的系統呼叫:brk()和sbrk(),雖然在程式碼中我們很少用到,但是瞭解下有助於我們弄清記憶體分配的工作過程。
int brk(void *end_data_segment); void *sbrk(intptr_t increment);
系統呼叫brk()會將program break設定為引數end_data_segment所指定的位置,由於虛擬記憶體是以頁為單位分配的,end_data_segment實際會四捨五入到下一個記憶體頁的邊界處。
sbrk()將program break在原來的地址上增加increment大小,並返回前一個program break的位置。所以sbrk(0)則返回當前program break位置。
獲取記憶體頁大小
//pagesize=4096
printf("pagesize=%d\n", getpagesize());
malloc()和free()
相較於brk()和sbrk(),他們更簡單,允許分配小塊記憶體,允許隨意釋放記憶體。malloc分配的記憶體會自動對齊。比如我的64位linux會以16位元組對齊。
free()用來釋放所指的記憶體塊,通常情況下,free()並不降低program break的位置,而只是將這塊記憶體新增到空閒記憶體列表,供後續的malloc()函式迴圈使用。這樣做的好處是
1.被釋放的記憶體塊通常位於堆的中間,而非堆的頂部,因而降低program break是不可能的。
2.這樣可以最大幅度的減少sbrk的呼叫次數,因為系統呼叫更耗資源。
malloc()和free()內部實現
malloc()實現很簡單,它首先會掃描之前由free()所釋放的空閒記憶體塊,以一定策略(first-fit或best-fit)找到大於或等於要求的一塊空閒記憶體,如果有直接返回,如果是大塊記憶體,則對其進行分割返回,其他的繼續保留在空閒記憶體列表。malloc分配記憶體時會額外分配幾個位元組用來儲存記憶體大小如圖,而實際返回給使用者的是這個長度記錄位元組之後。
如果找不到,則會呼叫sbrk()以分配更多的記憶體。為了減少sbrk()的呼叫次數,sbrk()會申請更多的記憶體。
記憶體操作應該遵循以下幾個規則:
1.分配一塊記憶體後應該謹慎使用,避免操作該記憶體以外的位元組。
2.不允許釋放一個記憶體超過1次。
3.如果不是由malloc函式包分配的記憶體,決不能用free()函式釋放。
分配對齊的記憶體memalign()和posix_memalign()
我們知道malloc已經幫我們實現了記憶體對齊,比如我的64位linux為16位元組對齊,所以大多數情況下都不需要下面2個函式來手動指定對齊引數。只有在一些特殊場合malloc滿足不了的時候才會用下面2個函式。
void *memalign(size_t boundary, size_t size);
分配size個位元組記憶體,記憶體的起始地址時引數boundary的整數倍,而boundary必須是2的整數次冪。返回分配記憶體的地址。
int posix_memalign(void **memptr, size_t alignment, size_t size);
已分配的記憶體地址通過memptr返回,記憶體的起始地址必須是alignment的整數倍,alignment必須是sizeof(void*)與2的整數次冪兩者的乘積。
在棧上分配記憶體:alloca()
void *alloca(size_t size);
在棧上分配的記憶體不能手動釋放,棧幀的移除(函式返回)的時候自動釋放,速度比在堆上分配更快。另一個優點是在訊號處理程式中呼叫longjmp()或siglongjmp以執行非區域性跳轉時,起跳函式和落地函式之間的函式中如果使用了malloc()來分配記憶體,想要避免記憶體洩漏及其困難,甚至不可能。但是alloca完全可以避免這個問題,因為堆疊是由這些呼叫展開的,當堆疊重置,棧幀被移除時,記憶體會隨棧幀一起被移除。
總結
本文對linux記憶體分配以及相關的做了簡單的介紹,如果有疑問可以給我留言。