---
tags: linux2023
---
# [2023q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 5 週測驗題
:::info
目的: 檢驗學員對記憶體管理、紅黑樹,和並行程式設計的認知
:::
==[作答表單: 測驗 1-2](https://docs.google.com/forms/d/e/1FAIpQLSfIF6JE1bZ-kBkV2tgCk87SIxJa8saVXa63k1TQ7_Kjx7_l5w/viewform)== (針對 Linux 核心「設計」/「實作」課程)
### 測驗 `1`
在[第一次作業的解說](https://hackmd.io/@sysprog/linux2023-lab0)、〈[你所不知道的 C 語言:記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory)〉和〈[你所不知道的 C 語言:函式呼叫篇](https://hackmd.io/@sysprog/c-function)〉提到記憶體配置器的實作考量,我們可將這部份的認知應用於 [memory pool](https://en.wikipedia.org/wiki/Memory_pool),程式碼可見: [mpool](https://gist.github.com/jserv/8ae560f0095b45aaefa4386c71d7a326)
* `mpool.h` : 規範公開介面,包含 `pool_init`, `pool_malloc`, `pool_calloc`, `pool_realloc`, `pool_free` 等函式
* `mpool.c`
請補完程式碼,使其運作符合程式註解提及的行為。作答規範: (皆不包含小括號 [即 `(` 和 `)`])
* `AAAA`, `BBBB`, `CCCC`, `DDDD`, `FFFF`, `GGGG`, `HHHH` 皆為 identifier,不是常數或 integer literal
* `EEEE` : 變數名稱
* 以最精簡且考慮硬體平台特性的方式回覆
:::success
延伸問題:
1. 解釋上方程式碼運作原理,指出其設計和實作缺失,並予以改進。不用抄寫題目,專注於你對程式碼的理解
2. 原本的程式碼只實作 first-fit 演算法,請重新實作為 best-fit
3. 將 memory pool 程式碼引入到 [fibdrv](https://github.com/sysprog21/fibdrv) 或課程作業中
:::
---
### 測驗 `2`
延伸測驗 `1`,我們可運用在〈[並行和多執行緒程式設計](https://hackmd.io/@sysprog/concurrency)〉和〈[Linux 核心的紅黑樹](https://hackmd.io/@sysprog/linux-rbtree)〉所學,建構實際可在 GNU/Linux 運作的記憶體管理器。
簡易的 malloc 實作,可運用 [sbrk](https://man7.org/linux/man-pages/man2/brk.2.html) 系統呼叫,用法是 `sbrk(size)`,後者是回傳原本的起始位置:
* `sbrk(0)` 回傳 heap 的終點
* `sbrk(size)` 回傳 (目前的 heap 終點位置 - size),即增加 size 之前的終點位置
```c
void *malloc(size_t size)
{
void *ptr = sbrk(size);
if (!ptr)
return NULL;
return ptr;
}
```
爲了確保 heap 內的記憶體是連續的,因此不能任意將中間的記憶體釋放回作業系統,於是需要一個機制以確認,heap 內的某個部分的記憶體是否爲 free。有了該機制,即可就能確保在 free 後,heap 內的記憶體還是連續的。
另一個好處是,每次呼叫 `malloc` 時不用頻繁呼叫 sbrk,而是去 heap 中尋找能否有空閒且足夠的記憶體即可。為此,我們需要一個結構在每塊記憶體的前面,size 用來知道這塊 `block_meta_t` 所管理的記憶體大小,`next` 則連接到下一塊 `block_meta_t`,然後我們需要一個指標`global_base` 來記錄開頭節點:
```c
typedef struct block_meta {
size_t size; /* 本 block_meta_t 後面的記憶體大小 */
struct block_meta *next; /* 連接到下一塊 block_meta_t */
int free; /* 是否爲 free */
} block_meta_t
#define META_SIZE sizeof(block_meta_t)
void *global_base = NULL; /* 記錄開頭節點 */
```
每塊記憶體前面都必須要有這塊 `block_meta_t`,因此每次 malloc 時都需要更動爲:
```c
sbrk(size + META_SIZE)
```
接下來,實作一個在所有 block 中尋找能否使用的空間,`last` 用來記錄最後的 block 在哪裏,程式碼如下:
```c
block_meta_t *find_free_block(block_meta_t **last, size_t size)
{
block_meta_t *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_t *request_space(block_meta_t *last, size_t size)
{
block_meta_t *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;
}
```
準備工作都完成,利用上方函式來實作 malloc 進入點 `malloc(0)`。爲了在 `free` 時以安全通過,因此每次 `malloc(0)` 都回傳一個 `NULL`:
```c
void *malloc(size_t size)
{
if (size <= 0)
return NULL;
block_meta_t block;
if (!global_base) { /* 首次 malloc */
block = request_space(NULL, size);
if (!block)
return NULL;
global_base = block;
} else {
block_meta_t *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_t size,
* 因此 block + 1 = block + META_SIZE
* 回傳的記憶體不需要包括 meta 資訊
*/
return block + 1;
}
```
如此,簡易的 `malloc` 即可實作。本題額外考慮多執行緒環境,並運用紅黑樹來建構記憶體配置器,程式碼可見 [alloc.c](https://gist.github.com/jserv/2133fb04f30b1a08c306643521b096d3),編譯方式: (部分遮蔽)
```shell
$ gcc -O2 -Wall -Wextra -fPIC -I . -c alloc.c
$ gcc -shared -o libmy_alloc.so alloc.o
```
測試方式:
```shell
LD_PRELOAD=./libmy_alloc.so ps aux
```
關於此處 `LD_PRELOAD`,可對照看〈[你所不知道的 C 語言:動態連結器篇](https://hackmd.io/@sysprog/c-dynamic-linkage)〉。
請補完程式碼,使上述記憶體配置器得以正確運作。作答規範:
* `IIII` 和 `JJJJ` 是表示式,皆不包含小括號
* `KKKK` 和 `LLLL` 是表示式,需要呼叫 POSIX Thread 規範的函式
:::success
延伸問題:
1. 解釋程式碼運作原理,說明如何搭配紅黑樹來實作 best-fit 策略
2. 以[第 3 週測驗](https://hackmd.io/@sysprog/linux2023-quiz3)和[第 4 週測驗](https://hackmd.io/@sysprog/linux2023-quiz4)題目所及、調整過的紅黑樹程式碼,重寫上述程式碼,使其得以用低的記憶體開銷和處理更大的記憶體範圍
3. 用 [mmap](https://man7.org/linux/man-pages/man2/mmap.2.html) 系統呼叫替換 sbrk
4. 研讀〈[針對多執行緒環境設計的 Memory allocator](https://hackmd.io/@ljP_AG30SzmQE5qO-cjcpQ/HkICAjeJg)〉和過往課程學員針對 [mimalloc](https://github.com/microsoft/mimalloc) 做的改進,探討 Linux 核心可用於改進記憶體配置效率的機制
> mimalloc 報告: [2021 年](https://hackmd.io/@hankluo6/mimalloc) 和 [2022 年](https://hackmd.io/@hankluo6/allocator)
:::