--- title: 2023q1 Homework6 (quiz5) tags: Linux核心設計 --- # [2023q1 Homework6 (quiz5)](https://hackmd.io/@sysprog/linux2023-quiz5) ## [測驗一](https://hackmd.io/@sysprog/linux2023-quiz5#%E6%B8%AC%E9%A9%97-1) ### 程式碼原理 #### Basic data structure ```c typedef struct block { int size; /**< Size of the data payload */ struct block *prev, *next; /**< Pointer to the previous/next block */ } block_t; ``` `block` 為每塊可使用區塊的 metadata ,其中紀錄每個區塊的大小及前一個與後一個區塊的 link。 #### pool_init 首先會檢查要配置的記憶體位址是否合法,再去檢查大小是否能至少容納一個區塊所需的 metadata 大小,一個 block 包含 `size, *prev, *next` ,所以至少要 3 個 word 大小,所以 `header_size` 為 `word_size * 3`,`current` 去紀錄此可用區塊的位址。 #### round_up ```c static inline int round_up(const int *x) { return ((*x + 7) >> log2_word_size) << log2_word_size; } ``` `round_up` 是為了使宣告的大小能對齊記憶體,因為 `x` 至少為 1 ,所以當 `word_size == 8` 時,因為要對齊 8 bytes,所以最右邊 3 bits 要清除為 0 ,所以要 `*x + 7`,接著右移 3 bits 後,再左移 3 bits ,就能對齊 8 bytes。但這裡適用於 `word_size == 8` 時,所以當`word_size == 4` ,並不適用,所以改成: ```c static inline int round_up(const int *x) { return ((*x + word_size - 1) >> log2_word_size) << log2_word_size; } ``` 則當`word_size == 4 or 8` 時,都能對齊。 #### pool_malloc 用 `pool_malloc` 來配置在 memory pool 中的可用區塊,先檢查要配置的記憶體大小是否 > 0,再將要配置的大小對齊 `word_size` ,最後檢查 `pool_free_space` 須配置的大小是否足夠,然後用 `ret` 去紀錄要使用的區塊,由 `get_loc_to_place` 去尋找可使用的區塊。 ```c void *ret = get_loc_to_place(current, _size); ``` 因為採用 first-fit ,所以當有足夠大小的區塊時就直接配置,否則就往下尋找。這裡有個問題是 `get_loc_to_place` 傳入的參數名稱與全域變數 `current` 是一樣的,會導致自己判斷時可能會錯誤。 ```c static inline void *get_loc_to_place(void *current, int size) { block_t *parse = current; if (parse->size >= (size + header_size)) return current; ... } ``` 因為將 memory pool 的區塊配置,所以更新 free space 的值,先更新 `current` 的位址,再更新 `current->size` 的值,為目前 free space 大小 - 以配置的空間大小 - 配置出去的區塊的 metadata 大小(`size`)。 ```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; ``` 最後將 free block 的 `prev, next` 的 link 接上。 ```c current->prev = prev_pt, current->next = next_pt; /* 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_free 首先求出要釋放的 block 的位址,並將它的大小更新到 free space 中。 ```c /* Get block info */ void *block_pt = (char *) addr - word_size; block_t *block = block_pt; /* Update pool arena statistics */ pool_free_space += block->size; ``` 用 `get_loc_to_free` 去找到可以連接的 free block。 ```c void *free_pt = get_loc_to_free(block_pt); ``` 再來會檢查 released block 的位址是否跟 free block 相鄰,會有兩種情況,一是 free block 在 released block 前,二是 free block 在 released block 後,再進行合併。 ```ㄏ /* 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; ... } ... } ``` 最後合併完的 free block 可能也會跟其他 free block 相鄰,所以需要再次合併,分成兩種情況,分別是存在相鄰的 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; } } ... ``` ### 答案 :::success * `AAAA = header_size` * `BBBB = log2_word_size` * `CCCC = log2_word_size` * `DDDD = header_size` * `EEEE = _size` * `FFFF = word_size` * `GGGG = word_size` * `HHHH = word_size` ::: --- ## [測驗二](https://hackmd.io/@sysprog/linux2023-quiz5#%E6%B8%AC%E9%A9%97-2) ### [alloc.c](https://gist.github.com/jserv/2133fb04f30b1a08c306643521b096d3) 程式碼原理 #### split_block 主要將 `node` 從 freed list 移出,並用 `new` 去更新大小跟節點關係,最後更新 rb_tree 的資訊。 ```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; //IIII 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; } ``` #### fusion 這裡主要是做將兩塊 free block 合併,首先將兩塊空間大小加總,再將 freed list 的 link 連接,若是合併後的空間為最後一塊,則會去更新最後一個節點的資訊 `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; //JJJJ return first; } ``` #### malloc 因為考慮到多執行序的問題,所以在配置 block 時,使用 mutex lock,防止可能重複配置空間的問題,首先檢查要配置的大小是否不足 4 bytes,再將 `size` 加上所需的 metadata 大小,最後去 freed list 尋找合適的空間,若有找到,則回傳位址,若無,則回傳 `NULL` 。 ```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; if (!g_info.page_size) g_info.page_size = getpagesize(); if ((tmp = search_freed_block(g_info.root_rbtree, size))) ptr = split_block(tmp, size); else ptr = get_heap(size); pthread_mutex_unlock(&g_info.mutex); return ptr ? (GET_PAYLOAD(ptr)) : NULL; } ``` #### free 首先判斷要釋放的指標是否合法,再去判斷是否為 double free ,最後將這塊空間加入到 freed list 中。 ```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); node = try_fusion(node); if (!node->next) change_break(node); else g_info.root_rbtree = insert_in_freed_list(g_info.root_rbtree, node); pthread_mutex_unlock(&g_info.mutex); } ``` ### 答案 :::success * `IIII = node->size - size` * `JJJJ = g_info.last_node = first` * `KKKK = pthread_mutex_lock(&g_info.mutex);` * `LLLL = pthread_mutex_unlock(&g_info.mutex);` :::