# Dynamically allocate space Note --- 自幹一個普通的malloc --- * 最普通的malloc實做,就是sbrk(size),增加sbrk來得到記憶體,然後因爲sbrk是回傳原本的起始位置: * sbrk(0)回傳heap的終點 * sbrk(size)回傳(目前的終點位置-size),就是增加size之前的終點位置 ```c= void *malloc(size_t size) { void *ptr = sbrk(size); if(!ptr) return NULL; //sbrk failed else return ptr; } ``` 接下來,來談一談free的部分,爲了確保heap裏面的記憶體是連續的,所以不能隨便將中間的記憶體還給OS。因此需要一個東西來確認說,heap裏的某個部分的記憶體是否爲free。有了這個機制就能確保在free後,heap裏的記憶體還是連續的。 還有另一個好處就是每次在進行malloc時不需要動到sbrk,而是只需去到heap裏尋找能否有空閒且足夠的記憶體就行了,也就是所謂的重用機制。 爲了這個機制,我們需要一個結構在每塊記憶體的前面,size用來知道這塊block meta所管理的記憶體大小,next則連接到下一塊block meta,然後我們需要一個指標==global_base==來記錄頭節點: ```c= typedef struct __BLOCK_META { size_t size; // 這塊block meta後面的記憶體大小 struct __BLOCK_META *next; //連接到下一塊block meta int free; //是否爲free }block_meta #define META_SIZE sizeof(block_meta) void *global_base = NULL; // 記錄頭節點 ``` 每塊記憶體前面都必須要有這塊block meta,因此每次malloc時都需要更動爲: ```c= sbrk(size + META_SIZE) ``` 接下來,來實做一個在所有block裏尋找能否使用的空間,last用來記錄最後的block在哪裏(看下個實做): ```c= block_meta *find_free_block(block_meta **last, size_t size) { block_meta *cur = global_base; while (cur && !(cur->free && cur->size >= size)) { *last = cur; cur = cur->next; } return cur; } ``` 如果在目前的heap中找不到一塊空閒且足夠大小的空間,那就必須申請sbrk增加heap的大小了,在上面的`find_free_block`時已經將last找到了,因此申請新的block時,只需將last跟block連接在一起就好: ```c= block_meta *request_space(block_meta *last, size_t size) { block_meta *block; block = sbrk(0); void *request = sbrk(size + META_SIZE); assert((void*)block == request); // Not thread safe. if (request == (void*)-1) return NULL; //sbrk failed if (last) // 第一次malloc時last爲空,不需要連接 last->next; block->size = size; block->next = NULL; //新的一塊,最後面的 block->free = 0; return block; } ``` 準備工作都完成了,最後利用上面的function來實做malloc本身吧,malloc(0)爲了在free時可以安全通過,因此每次malloc(0)都回傳一個NULL。: ```c= void *malloc(size_t size) { if(size <= 0) return NULL; block_meta block; if(!global_base) { //第一次malloc block = request_space(NULL,size); if (!block) return NULL; global_base = block; } else { block_meta *last = global_base; block = find_free_block(&last, size); if (!block) { //尋找失敗,申請sbrk block = request_space(last, size); if (!block) return NULL; } else //尋找成功,設定block爲unfree block->free = 0; } /* block的大小爲block_meta size 因此block+1 = block+META_SIZE 回傳的記憶體不需要包括meta information */ return (block+1); } ``` 這樣一個普通簡單的malloc就大功告成了。 --- malloc完成後,再來是實做free的部分,很簡單,就是得到記憶體前面的那塊block_meta,然後設定free爲true就可: ```c= block_meta *get_meta_block(void *ptr) { return ((block_meta*)ptr - 1); } void free(void *ptr) { if (!ptr) //之前如果malloc<=0,就會回傳NULL return NULL; block_meta *block = get_meta_block(ptr); block->free = 1; } ``` 這樣free的部分就完成了。 --- 最後,來看看關於realloc跟calloc的部分。 * calloc * 很簡單,就是利用memset,將malloc的記憶體初始化爲0 * realloc * 如果realloc的大小是減少,那就不動它 * 如果realloc的大小是增加原本的,那就malloc一塊新的給它,然後利用memcpy將之前的資料copy去新的記憶體區塊 ```c= /* nelem 爲要allocate的數量,elsize爲每塊數量的大小 */ void *calloc(size_t nelem, size_t elsize) { size_t size = nelem * elsize; void *ptr = malloc(size); memset(ptr, 0, size); return ptr; } ``` ```c= void *realloc(void *ptr, size_t size) { if (!ptr) return malloc(size); block_meta *block = get_meta_block(ptr); if (block->size <= size) return ptr; void *new_ptr; new_ptr = malloc(size); if (!new_ptr) return NULL; memcpy(new_ptr, ptr, sizeof(block->size)); free(ptr); return new_ptr; } ``` 參考資料: * [malloc tutorial](http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf) * [A quick tutorial on implementing malloc, free, calloc, and realloc](http://danluu.com/malloc-tutorial/) --- ## Two-Level Segregated Fit (TLSF) 論文閱讀 [TLSF: a New Dynamic Memory Allocator for Real-Time Systems∗](http://www.gii.upv.es/tlsf/files/ecrts04_tlsf.pdf) #### DSA algorithms * Sequential fit * 不做任何規劃,將所有free或是nonfree的block用linked list連接起來。在RTSs的系統下是非常沒有效率的,因爲尋找free block需要拜訪所有block(worst case) * Segregated Free Lists: * 利用一個array list保管不同大小的free blocks。當一個block被釋放後,它會根據自己的size被加入到對應的array位置。這種做法是邏輯上的segregate而不是物理上的(就是實際上它們並沒有分離,只是在邏輯上看起來是分離了)。有兩種:Simple Segregated Storage and Segregated Fits。對RTOSs的系統來說挺不錯的,因爲不需要拜訪每個block * ## TIPS: * 每次通過sbrk申請時要加上meta information,賦值給meta information後,只需要回傳meta information後的第一個位置,後面就是申請malloc的大小 * DSA algorithm has to minimise the chance of exhausting the memory pool by minimising the amount of fragmetation and wasted memory. 爲了減少memory pool耗盡的可能性,DSA必須降低fragmentation跟避免記憶體被浪費 * 有兩種memory allocation會失敗的原因: * 超出系統的記憶體範圍 * DSA演算法在freelist找不到合適的區塊來重用 * internal fragmentation * external fragmentation * Fragementation的結論: * “the fragmentation problem is really a problem of poor allocator implementations” * best-fit 跟 physical first-fit 可以減少fragmentation * 每塊記憶體應該要在釋放的時候合並 * 優先選擇最近的被釋放的記憶體,然後才到很久前被釋放 * 計算First level bin的時候取:下高斯[log2(size)]。 * 只需要計算最後的1,前面有幾個bit就好了。 ``` size = 460 = 0000 0001 1100 1100 最高位的1爲9, 而2^9 = 512,一定超過460,所以取8 ```