# 2023q1 Homework6 (quiz5) contributed by < `ItisCaleb` > ## 測驗 `1` 題目的 memory pool 可以運用的空間是使用者自行先分配出來,再使用 `pool_init` 並傳入 `size` 來讓程式可以運用 在 memory pool 中,可以使用的空間會有個 header,用來紀錄上/下一個可以使用的空間及大小 ```c typedef struct block { int size; /**< Size of the data payload */ struct block *prev, *next; /**< Pointer to the previous/next block */ } block_t; ``` 而使用中的空間則只會有一個 `size` 來紀錄 `payload` 的大小。 ```graphviz digraph { node [shape=plaintext, fontcolor=black, fontsize=16]; "Free Block" -> "In-use Block" [color=white]; node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true]; block1 [label="size|next|prev|..."]; block2 [label="size|payload"]; { rank=same; "Free Block"; block1 } { rank=same; "In-use Block"; block2 } } ``` 全域變數的 `current` 指向一個可以使用的空間 ### pool_malloc 會先把要的 `size` 變成 4/8 byte的倍數,如果 memory pool 剩下的空間塞不下 `_size` 跟 header 就直接回傳 ```c void *pool_malloc(int size) { if (!size) return NULL; /* Round up the size up to the arch width. Ensure the size is at minimum a * register size and a multiple of that register. So if use 64 bits arch, a * 4 bytes allocation is round up to 8 bytes, and 28 bytes is round up to * 32 bytes. */ int _size = round_up(&size); /* Return NULL if failed to allocate a buffer */ if (pool_free_space <= (_size + header_size)) return NULL; ``` 接下來則是用 `get_loc_to_place` 並使用 `current` 去尋找符合 `_size` 的空間 > `ret` 應該設為 `ret + word_size` 而非 `current + word_size`,同時應該放到最後,這樣才方便去更新他的 `size` ```c void *ret = get_loc_to_place(current, _size); if (!ret) return NULL; /* payload's address the application can use */ //ret = (char *) ret + word_size; ``` `get_loc_to_place` 的行為很簡單 就只是透過 `current` 去往前或往後搜尋大於等於 `size` 的 free block > 第二段的 `parse = ((block_t *) current)->prev;` 應改成 `parse = ((block_t *) current)->next;` ```c /* Search for a free space to place a new block */ static inline void *get_loc_to_place(void *current, int size) { block_t *parse = current; if (parse->size >= (size + header_size)) return current; /* parse the prev blocks to find a place */ for (parse = ((block_t *) current)->prev; parse; parse = parse->prev) { if (parse->size >= (size + header_size)) return parse; } /* parse the next blocks to find a place */ for (parse = ((block_t *) current)->next; parse; parse = parse->next) { if (parse->size >= (size + header_size)) return parse; } /* No space found, stop the allocation */ return NULL; } ``` 最後則是更新 `current` ,把原本的 `prev` 跟 `next` 接上去 ```c /* Store current's info before moving its head */ void *next_pt = current->next, *prev_pt = current->prev; int new_size = current->size - word_size - _size; /* Adjust free space of current block and update its meta-data */ void *new_space = (char *) current + word_size + _size; current = new_space; if (new_size < 0) { current->size = 0; return NULL; } current->size = new_size; current->prev = prev_pt, current->next = next_pt; ``` 如果 `current` 的 `prev` 跟 `next` 不為 NULL 則也同樣把他們接上 `current` ```c /* Update previous block to link current */ if (current->prev) { tmp_block = prev_pt; tmp_block->next = current; } /* Update next block to link current, only if exists */ if (current->next) { tmp_block = next_pt; tmp_block->prev = current; } ``` 在回傳 `ret` 之前,應該要先更新 `pool_free_space` 以及他本身的 `size` ```c pool_free_space -= _size; /* payload's address the application can use */ ((block_t *) ret)->size = _size; return (char *) ret + word_size;; ``` ## pool_free 在 free 之前會先獲取 block 的 size 然後加到 `pool_free_space` 裡 ```c void pool_free(void *addr) { /* Get block info */ void *block_pt = (char *) addr - word_size; //HHHH block_t *block = block_pt; /* Update pool arena statistics */ pool_free_space += block->size; ``` 接著是用 `get_loc_to_free` 來找到最近的 free block 為了等下可以 merge ```c void *free_pt = get_loc_to_free(block_pt); block_t *free_block = (block_t *) free_pt; ``` 在 merge 的時候會有兩種情況 分別是 free block 在要釋放的 block 前面及後面 ```graphviz digraph { node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true]; block1 [label="free block|block"]; } ``` ```graphviz digraph { node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true]; block1 [label="block|free block"]; } ``` 在檢查 free block 是否跟要釋放的 block 相鄰後 如果是後者的情況則要將 free block 的 header 移到要釋放的 block 前面,並且更新 `size` 及連結的區塊 前者的情況則只要更新 free block 的 `size` 就好 ```c /* Check the block can be merged with the current free space selected */ if ((uintptr_t) block_pt < (uintptr_t) free_pt) { /* If block space matches free space start address, merge */ region = (char *) block_pt + block->size + word_size; if (region == free_pt) { block->prev = free_block->prev, block->next = free_block->next; block->size += free_block->size + header_size; pool_free_space += word_size; /* If a next block exists, connect it */ if (block->next) { tmp_block = block->next; tmp_block->prev = block_pt; } /* If a previsou block exists, connect it */ if (block->prev) { tmp_block = block->prev; tmp_block->next = block_pt; } } } else { /* if free space range macthes the block start address, merge in this * case, the free space becomes the block to release */ region = (char *) free_pt + free_block->size + header_size; if (region == block_pt) { free_block->size += block->size + word_size; block_pt = free_pt; block = block_pt; pool_free_space += word_size; } } ``` 最後如果 merge 之後前方或後方還有 free block 則再一次去做 merge ```c /* Try to merge with next block if exists */ if (block->next) { /* if next block is contiguous the one to free, merge them */ region = (char *) block_pt + block->size + word_size; if (region == block->next) { /* extend block size with next size */ tmp_block = block->next; block->size += tmp_block->size + word_size; /* link next->next block with current block */ tmp_block = tmp_block->next; tmp_block->prev = block_pt; /* Update pool's statistics */ pool_free_space += word_size; } } /* Try to merge with previous block if exists */ if (block->prev) { /* if previous block is contiguous the one to free, merge them */ tmp_block = block->prev; region = (char *) block->prev + tmp_block->size + header_size; if (region == block_pt) { /* Update previous block by extending its size with block */ tmp_block->size += word_size + block->size; /* Link block - 1 and block + 1 together */ tmp_block->next = block->next; /* Current block's prev becomes the new current block */ block = block->prev; /* (block + 1)'s prev is now linked to orignal (block - 1) */ tmp_block = block->next; tmp_block->prev = block; /* Update pool's statistics */ pool_free_space += word_size; } } ``` 整體的行為會像是下面這樣子 ```graphviz digraph { node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true]; block [label="free block|block|free block"]; } ``` ```graphviz digraph { node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true]; block [label="free block|free block"]; } ``` ```graphviz digraph { node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true]; block [label="free block"]; } ``` --- ## 測驗 `2` 這個測驗裡的記憶體配置是搭配紅黑樹來實作的 演算法大致上的概念是在把空間 free 之後,不會直接將空間還給 kernel,而是會作為節點放到紅黑樹裡,待之後可以直接取用 這邊的紅黑樹是用 size 來當作 key ,同時如果有多個區塊是同一個 size 的話,則都會放入節點的 `tab_values` 裡 ```c typedef struct rbnode { size_t size; size_t free; metadata_t *next, *prev; t_key key; t_value **tab_values; size_t size_tab; size_t n_active; rbcolor_t color; struct rbnode *left, *right; } rbnode_t; ``` 在 metadata 中除了 `size` 還會記載著 `next` 跟 `prev`,也就是相鄰的區塊 是為了之後在 free 的時候看能不能去合併 ```c typedef struct metadata { size_t size; size_t free; struct metadata *next, *prev; } metadata_t; ``` 而為了可以在多執行緒的狀況下執行,在每個操作裡都會有 `mutex_lock` 及 `mutex_unlock` ### malloc 一開始會先將要的 `size` 做記憶體對齊並且加上 metadata 的大小 ```c void *malloc(size_t size) { metadata_t *tmp; void *ptr; pthread_mutex_lock(&g_info.mutex); if (size < SIZE_DEFAULT_BLOCK) size = SIZE_DEFAULT_BLOCK; size = ALIGN_BYTES(size) + META_SIZE; ``` 接著就是先去紅黑樹裡找到符合 `size` 的記憶體區塊 如果找到了還會透過 `split_block` 去分割區塊,然後把多餘的區塊重新插入到紅黑樹中 這樣子做就一定會保證是 best-fit 而如果沒有符合 `size` 的區塊,則透過 `get_heap` 來獲得更多記憶體空間 ```c if ((tmp = search_freed_block(g_info.root_rbtree, size))) ptr = split_block(tmp, size); else ptr = get_heap(size); ``` 最後回傳 payload 的地址 ```c return ptr ? (GET_PAYLOAD(ptr)) : NULL; ``` `split_block` 會將找到的區塊分割成 `size` 的大小 並且將這兩個區塊連結在一起 然後把分割完剩下來的區塊放回紅黑樹裡 ```c static void *split_block(metadata_t *node, size_t size) { g_info.root_rbtree = remove_from_freed_list(g_info.root_rbtree, node); if (node->size > size + sizeof(size_t) && node->size - size > sizeof(rbnode_t) + SIZE_DEFAULT_BLOCK) { metadata_t *new = (void *) node + size; new->size = node->size - size; new->free = YFREE; new->prev = node; new->next = node->next; node->next = new; node->size = size; if (new->next) new->next->prev = new; g_info.root_rbtree = insert_in_freed_list(g_info.root_rbtree, new); } return node; } ``` ### free 首先會先通過檢查位置及 magic number 來確認 `ptr` 是不是有效的記憶體位址 並且也會檢查是否 free 多次同一個位址 ```c void free(void *ptr) { if (!ptr) return; pthread_mutex_lock(&g_info.mutex); metadata_t *node = GET_NODE(ptr); if (ptr < g_info.first_block || ptr > g_info.end_in_page || !IS_VALID(node)) invalid_pointer(ptr); if (node->free == YFREE) double_free(ptr); ``` 如果都沒問題則會嘗試會合併記憶體區塊 ```c node = try_fusion(node); ``` `try_fusion` 會利用 metadata 的 `next` 跟 `prev` 來讓相鄰的區塊合併 如果在合併之後 `next` 為 `NULL` 則表示現在的區塊是最後的區塊,把 `g_info.last_node` 設為現在的區塊 ```c static metadata_t *fusion(metadata_t *first, metadata_t *second) { first->size += second->size; first->next = second->next; if (first->next) first->next->prev = first; else g_info.last_node = first; return first; } static inline metadata_t *try_fusion(metadata_t *node) { while (IS_FREE(node->prev)) { g_info.root_rbtree = remove_from_freed_list(g_info.root_rbtree, node->prev); node = fusion(node->prev, node); } while (IS_FREE(node->next)) { g_info.root_rbtree = remove_from_freed_list(g_info.root_rbtree, node->next); node = fusion(node, node->next); } return node; } ``` 當合併完之後會判斷現在的區塊是否是最後的區塊 如果是的話就使用 `change_break` 來把區塊還回 kernel 否則就插入到紅黑樹裡來讓之後的 `malloc` 操作可以利用 ```c if (!node->next) change_break(node); else g_info.root_rbtree = insert_in_freed_list(g_info.root_rbtree, node); ``` `change_break` 會利用 `sbrk` 把剩下的區塊還回 kernel ```c static inline void change_break(metadata_t *node) { size_t pages_to_remove; if (node->prev) { node->prev->next = NULL; g_info.last_node = node->prev; g_info.end_in_page = (void *) g_info.last_node + g_info.last_node->size; } else { g_info.end_in_page = g_info.last_node; g_info.last_node = NULL; } g_info.page_remaining += node->size; pages_to_remove = g_info.page_remaining / g_info.page_size; /* FIXME: sbrk is deprecated */ brk((sbrk(0) - (pages_to_remove * g_info.page_size))); g_info.page_remaining = g_info.page_remaining - (pages_to_remove * g_info.page_size); } ```