# 2019q1 Homework7 (buddy)
contributed by < `tony2037` >
# F13: buddy
* https://hackmd.io/@sysprog/SyPmY90jV?type=view
## 測驗解析
### 第一題
#### `static void flip_parent_is_split`
```clike=
static void flip_parent_is_split(size_t index) {
index = (index - 1) / 2;
A1
}
```
* 先看看 binary tree
* - Move to parent: index = (index - 1) / 2;
* - Move to left child: index = index * 2 + 1;
* - Move to right child: index = index * 2 + 2;
* - Move to sibling: index = ((index - 1) ^ 1) + 1;
* 所以 index 目前移動到 parent node
* 答案是 `(a) node_is_split[index / 8] ^= 1 << (index % 8);`
* `node_is_split`
* $\because$ 只需要 1 個 bit 來 store state (split or not)
* $\therefore$ `static uint8_t node_is_split[(1 << (BUCKET_COUNT - 1)) / 8];`
### 第二題
#### `static size_t bucket_for_request(size_t request)`
```clike=
static size_t bucket_for_request(size_t request) {
size_t bucket = BUCKET_COUNT - 1;
size_t size = MIN_ALLOC;
while (size < request) {
A2
}
return bucket;
}
```
* 呼叫 malloc() 之後要找到對應的 size (最小且大於 request) 並且要把 allocated area 加入 doubly-linked list
* 答案是 `(c) bucket--; size *= 2;`
* bucket 從最大開始遞減
* 每次 size 不夠大, 就 times 2, 直到 size > request(包括 header 大小)
### 第三題
#### `static bool lower_bucket_limit(size_t bucket)`
```clike=
static bool lower_bucket_limit(size_t bucket) {
...
uint8_t *right_child = ptr_for_node(root + 1, bucket_limit);
if (!update_max_ptr(right_child + sizeof(list_t)))
return false;
list_push(&buckets[bucket_limit], (list_t *) right_child);
A3
...
}
```
* 當 **bucket_limit** 比 **bucket** 大的時候, 此函式 `double the size` & `makes bucket_limit lower by 1`
* 題目中做的事情是: 在確認過 **parent point slips** 後, 把 **right chlid** 加入 free list, 並且 `initialize &buckets[--bucket_limit]` 因為已經 split 出去了
* 答案是 `(b) list_init(&buckets[--bucket_limit]);`
### 第四題
#### `void *malloc(size_t request)`
```clike=
void *malloc(size_t request) {
...
if (!lower_bucket_limit(bucket - 1))
return NULL;
A4
...
}
```
* 答案是 `(c) ptr = (uint8_t *)list_pop(&buckets[bucket]);`
* 透過 `bucket_for_request()` 找到我們的 bucket 之後, 嘗試從 `free list` 尋找, 如果太大就 split
### 第五題
#### `void free(void *ptr)`
```clike=
void free(void *ptr) {
...
A5
i = (i - 1) / 2;
bucket--;
...
}
```
* 必須把 buddy 從 **free list** remove
* Move to sibling: index = ((index - 1) ^ 1) + 1;
* 答案是: `(d) list_remove((list_t *)ptr_for_node(((i - 1) ^ 1) + 1, bucket));`
## 延伸問題
**1. 分析程式碼的 sbrk 系統呼叫使用,注意其 alignment 行為,需要探討到 Linux 核心內部設計**
首先來看看 `man-pages` 怎麼解釋 `sbrk` 系統呼叫
>sbrk() increments the program's data space by increment bytes. Calling sbrk() with an increment of 0 can be used to find the current location of the program break.
>
從這裡可以知道 `sbark()` 以 `byte` 為單位增加 `data space` 的大小,若給定參數 `0` 則會回傳 `program break` 的位址
在 `buddy` 內 `alloc.c` 裡的 `malloc` 函式用到了 `sbrk` 系統呼叫,以下是 `malloc` 的部份程式碼
```cpp=
void *malloc(size_t request)
{
if (!request || request + HEADER_SIZE > MAX_ALLOC)
return NULL;
/*
* Initialize our global state if this is the first call to "malloc". At the
* beginning, the tree has a single node that represents the smallest
* possible allocation size. More memory will be reserved later as needed.
*/
if (!base_ptr) {
/* FIXME: sbrk is deprecated on some platforms where mmap is suggested
* as a better replacement (macOS, FreeBSD). sbrk is generally
* onsidered quite archaic.
*/
base_ptr = max_ptr = (uint8_t *) sbrk(0);
bucket_limit = BUCKET_COUNT - 1;
update_max_ptr(base_ptr + sizeof(list_t));
list_init(&buckets[BUCKET_COUNT - 1]);
list_push(&buckets[BUCKET_COUNT - 1], (list_t *) base_ptr);
}
...
...
...
}
```
第 16 行使用 `sbrk(0)` 取得當前的 `program break` ,存到 `base_ptr` 當作之後新建 `bucket` 得初始位址
**2.思考能否將 sbrk 換為 mmap 或者其他系統呼叫,並且該如何設計測試程式,才能涵蓋 malloc 和 free 的行為?**
* 關於 mmap 這邊有個小範例, 如何透過 mmap 寫檔案?
* 根據 [认真分析mmap:是什么 为什么 怎么用
](https://www.cnblogs.com/huxiao-tee/p/4660352.html)
* 根據 [系统调用与内存管理](https://blog.csdn.net/Apollon_krj/article/details/54565768)
* 一般的文件操作使用 **page cache**:disk -> page cache -> main memory: 需要 2 次 數據拷貝
* 使用 **mmap** 建立 **disk** & **memory** 的映射關係: 需要 1 次 數據拷貝
```clike
1 #include <sys/mman.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <string.h>
9
10 #define handle_error(msg) \
11 do { perror(msg); exit(EXIT_FAILURE); } while (0)
12
13 int main(int argc, char **argv) {
14 int fd = open("map.map", O_RDWR);
15 if (fd < 0) handle_error("open");
16
17 void *ptr1, *ptr2;
18 ptr1 = mmap(NULL, 64, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
19 ptr2 = malloc(64);
20
21 strcpy(ptr1, "Hello world");
22 //msync(ptr1, 64, MS_SYNC);
23 munmap(ptr1, 64);
24 close(fd);
25 }
```
上述程式先與 `map.map` 文件地址建立映射關係, 之後呼叫 `strcpy` 達成寫入動作, 要注意到的是: 呼叫 `msync` 或者 `munmap` 之前, 改動是不會寫回檔案的
### simple memory allocator
這是上學期修資工系作業系統的功課, 實作 buddy allocator, 如果超過上限, 則以 `mmap()` mapping
* https://github.com/oslab-csie-ncku/hw4-simple-memory-allocator-tony2037
* `hw_malloc(size_t size)`
```clike=
if(required_size <= (1 << 15)){
// Use heap to mallocate
int power = 0;
while((1 << (power)) < required_size){++power;};
printf("power required: %d\n", power);
...
// if Bin accessable give it, if not split
while(Bin[power]->next == NULL){
// no accessable, split
split(power + 1);
}
...
}
else{
// use mmap to distribute
void *ptr;
ptr = mmap(NULL, (bytes + sizeof(struct Header)), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
...
return ptr;
}
}
```
## Experience
* https://github.com/tony2037/buddy_allocator
## Reference
* http://students.mimuw.edu.pl/ZSO/Wyklady/05_pamiec/5_pamiec_en.html#wolne_ramki
* https://github.com/evanw/buddy-malloc/blob/master/buddy-malloc.c
* https://blog.csdn.net/juS3Ve/article/details/78441239