# 2023q1 Homework6 (quiz5) contributed by < `yanjiew1` > ## 測驗`1` `mpool` 為 memory pool 實作。 `pool_init` 是用來初始化 `mpool` 系統,透過它告訴 `mpool` 使用哪塊空間作為 memory pool 使用 ,空間大小有多少。 `mpool` 實作了類似 C 語言標準函式庫提供的記憶體配置器函式,分別為 `pool_malloc` 、 `pool_calloc` 、 `pool_realloc` 、 `pool_free` 。功能與 C 語言的記憶體置器很像,差別在於 `mpool` 是從一個設定好的一塊 memory pool 來配置空間。 ### 程式碼運作原理 已配置的空間為系統 word size 大小的 `size` 和 `payload` 組成。`malloc` 會回傳 `payload` 部份的指標。`payload` 為使用 `mpool` 程式可以利用的空間。 ``` +------------------------------------------------+ | size | payload | +------------------------------------------------+ ``` 未配置空間除了 `size` 外,後面還會接續 `prev` 和 `next` 二個指標,分別指向上一個和下一個未配置空間。 ``` -------------------------------------------------- | size | prev | next | .......... | -------------------------------------------------- ``` 上述二種狀態都可用 `block_t` 結構來表示。`size` 為已配置和未配置空間都會使用的欄位,而 `prev` 和 `next` 只會在未配置空間使用。 ```c /* The basic data structure describing a free space arena element */ typedef struct block { int size; /**< Size of the data payload */ struct block *prev, *next; /**< Pointer to the previous/next block */ } block_t; ``` #### `pool_init` 程式一開始會呼叫 `pool_init` 來告訴 `mpool` 可以使用哪塊空間作為 memory pool 。函式中會檢查大小是否大於 `header_size` (即 3 個 word size) ,若大小小於 `head_size` 代表連未配置空間的標頭都放不下,無法使用。 ```c if (size <= header_size) // AAAA /* size is too small, can notstore a header */ return false; ``` `pool_init` 也會設定全域變數 `pool_size` 和 `pool_free_space` 。 `pool_size` 為總共 memory pool 空間, `pool_free_space` 為未配置空間。這二個空間都要減去 `word_size` 是因為即使一次把整塊空間都配置出去,其最前面的標頭還是需要放配置出去的空間大小,故使用者可用的空間需要減去 `word_size` 。 ```c pool_size = size - word_size; pool_free_space = size - word_size; ``` `pool_init` 會把傳入的空間放到 free list 上,並寫入未配置空間的標頭,即 `size` 、 `prev` 和 `next` 。 程式中的 `current` 為 free list 。 ```c current = (block_t *) addr; current->size = pool_free_space; current->prev = NULL, current->next = NULL; ``` #### `pool_malloc` `pool_malloc` 會從 memory pool 配置空間。配置時,會使用 `round_up` 取得大於等於 `size` 的最小可以被 word_size 整除的數字,作為真正配置的空間。這樣做是為了讓得到的記憶體空間都能跟 word size 對齊,避免出現未對齊的操作。其中 `round_up` 實作如下: ```c /* Round up a size to the next multiple of 32 or 64 bits size */ static inline int round_up(const int *x) { return ((*x + 7) >> log2_word_size) << log2_word_size; // BBBB and CCCC } ``` 其實就是先把原本的數加上 7 再 round down 至 word size。 :::warning 但這裡有問題是, `+ 7` 是針對 64 bits 系統 (word size 為 8 bytes), 32 bits 系統應該要 `+ 3` (word size 為 4 bytes)。 雖然在 32 bits 系統也能用,但會造成空間浪費。 > TODO: 學習 Linux 核心的 ilog2,編譯時期決定要調整的空間。 :notes: jserv ::: 接著 `pool_malloc` 會透過 `get_loc_to_place` 用 first-fit 的演算法,從 free list 中找到一塊可用的空間。 `get_loc_to_place` 會先檢查 `current` 指標所指向的空間是否可用,若不可用則會先走訪 `prev` 指標,若仍然未找到可用的空間再走訪 `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)->prev; parse; parse = parse->next) { if (parse->size >= (size + header_size)) return parse; } /* No space found, stop the allocation */ return NULL; } ``` :::warning 程式有錯誤。在走訪 `next` 指標是,應該是由 `((block_t *) current)->next` 開始走訪,而不是 `((block_t *) current)->prev` 。這樣子導致當 `((block_t *) current)->prev` 為 `NULL` 時,無法正常配置空間,即使走訪 `next` 有可用的空間。 此外,這裡確保取得的空間有 `size + header_size` ,是因為在 `pool_malloc` 中,會把一個 free block 的前半部分配出去,後半部作為剩下的空間, `pool_malloc` 沒有考慮到整塊空間配置出去的情況,一定會留下一個 free block 。 ::: 呼叫 `get_loc_to_place` 取得可用的 block 。回傳值設為取得的空間加上 word size 。因為最前面要存放 `size` 。 ```c void *ret = get_loc_to_place(current, _size); // EEEE if (!ret) return NULL; /* payload's address the application can use */ ret = (char *) current + word_size; ``` :::danger 程式錯誤: - 用 `get_loc_to_place` 取得 free block 後,卻又把 `current` 指向的 free block 配置出去,沒有利用到 `get_loc_to_place` 找到的空間。 - 沒有設定配置出去的空間標頭中的 `size` 欄位。 ::: 從 `current` 配置空間後,備份原本 `current` 的 `prev` 和 `next` 指標,並設定把 `current` 設定成新的 free block 。 ```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; // FFFF /* Adjust free space of current block and update its meta-data */ void *new_space = (char *) current + word_size + _size; // GGGG current = new_space; if (new_size < 0) { current->size = 0; return NULL; } current->size = new_size; current->prev = prev_pt, current->next = next_pt; ``` :::danger 當 `new_size` 小於 0 時,會放棄設定新的 free block ,但裡面有把 `current->size` 指定為 0 ,可能就覆寫到其他空間了。此外如果要有足夠空間存放新的 free block ,至少 `new_size` 要有二個 word size 的空間才夠,但這裡也沒考慮到。 不過因為前面的程式已經錯了,所以後面的程式自然就接連有問題。 ::: 最後更新前後 free blocks 的指標: ```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; } ``` #### `pool_calloc` `pool_calloc` 可以利用原有的 `pool_malloc` ,再把新配置的空間填 0 。 ```c void *pool_calloc(int size) { void *ptr = pool_malloc(size); if (!ptr) return NULL; memset(ptr, 0, size); return ptr; } ``` #### `pool_realloc` `pool_realloc` 重新呼叫 `pool_malloc` 配置新空間,再把原有的內容拷貝過去,再把原有的空間釋放。 ```c void *pool_realloc(void *addr, int size) { void *ptr = pool_malloc(size); if (!ptr) return NULL; memcpy(ptr, addr, size); pool_free(addr); return ptr; } ``` #### `pool_free` 取得傳入位址的包含標頭的位址。因為知道所有經由 `mpool` 配置出來的空間前一個 word ,就是記錄配置空間的大小。 ```c /* Get block info */ void *block_pt = (char *) addr - word_size; // HHHH block_t *block = block_pt; ``` 使用 `get_loc_to_free` 取得鄰近的 free block ```c /* Free space zone to connect or merge with the block to release. Multiple * free blocks are suitable to connect, ensuring we will parse fastly the * linked list. */ void *free_pt = get_loc_to_free(block_pt); block_t *free_block = (block_t *) free_pt; ``` 若找到的 free block 與傳入要釋放的記憶體相鄰,則將此 free block 與目前要釋放的記憶體合併。 ```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; } } ``` :::danger 若 `free_pt` 與 `block_pt` 不相鄰怎麼辦? 這樣子 `block` 內的 `prev` 和 `next` 就沒被設置。 `pool_free` 顯然沒考慮此情況,會產生不可預期的結果。 ::: 最後再嘗試合併相鄰的 free block 。 ```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; } } ``` ### 程式的缺失與改進 #### 缺失 `mpool` 不論是 `pool_malloc` 或是 `pool_free` 都有明顯的錯誤。`pool_realloc` 採用重新配置空間的策略,沒有考慮是不有可能就地縮小或增加空間,也還有改進的空間。在 `pool_free` 需要再走訪 free list ,才能找到鄰近的 free block ,時間複雜度為 $O(n)$ 較沒效率。 > 可對照 CS:APP 第 9 章閱讀。 :notes: jserv 此外程式碼註解的英文有許多文法或單字錯誤,註解很難閱讀。 > 其實一開始是給 ChatGPT 的 prompt :notes: jserv #### 錯誤修正 嘗試保留原有的程式碼,但修復程式中的錯誤。 #### 重新實作 --- ## 測驗 `2`