# 2019q3 Homework3 (list)
contributed by < `YenHengLin` >
## macro 跟 function 差別
* macro 是透過 Preprocesser 將程式碼內有對應到 macro 定義的文字,轉換成其定義好的其他文字。由於只做文字轉換而已,所以沒辦法做型別確認。
* function 則是在程式執行到 function 時,跳轉到 function 程式所儲存的位址,其跳轉前必須要先將當前程式執行的 return address 、 local varible、 parameter 紀錄起來,以便 function 執行結束時,能知道原本的程式的狀態。其執行速度相對於 macro 慢,由於必須要做 stack 儲存的動作。
## 為何 Linux 採用 macro 來實作 linked list?
* linked list 這種指標的操作需要考驗程式設計師的專業,如果讓不了解 c 語言的人來寫可能會是個悲劇,所以我們用 macro 包裝起來,讓工程師少一點出錯的可能
* 而且我們不用像 function 特地知道指標的 type 因為 macro 只是替換文字而已
* 另外 macro 執行速度比 function 快
## typeof 功用跟好處
* typeof 為一種存取 type 跟 expression 的 type 的方式
* 使用方式為:當我想要設定變數 y 他的 type 要跟 變數 x 一樣時,我可以這樣表示 `typeof(x) y;`
* 前面我們已經提過 macro 屬於 no type checking,當我們想再 macro 裡得知傳入的參數的 type 就必須使用 typeof
* 範例 code
```c
#define swap((a),(b))\
typeof(a) tmp=(a);\
(a)=(b);\
(b)=tmp;
```
如果沒有 typeof 則我們在實做這段 a b 值互換的 code 時還必須傳入 type
## 了解以下代碼的功用
```c
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
```
#### offsetof
* 定義在 <linux/stddef.h> 這個檔案裡
```cpp
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
```
* 透過強制轉型將數字 0 轉成 TYPE 型態的指標,在從 0 指向 MEMBER 這個成員。造裡來說 0 是 null pointer 由它來指向 MEMBER 會產生 segementation fault,然而我們在前面加上 **&** 代表我們只是要取出位址並不取出元素,所以就不會發生錯誤
* 因為是由 0 當作起點,所以我們也可以把得到的位址當作是, MEMBER 這個元素與(自己所在的 structure 的)起始位址的偏移 (offset)
#### pmember
* 紀錄了指向 member 型別指標的位址
#### 功用
* 利用 pmember 減去其 member 在這個 structure 的偏移,可以求出該 structure 的起始位址
## list.h 包含的操作跟好處
* 定義了初使化的操作。由於 linked list 的實做方式有很多種,為了確保使用者是使用環狀雙向鍊結的方式,我們規範了初始化的 prev 跟 next 都指向 head
```c
#define LIST_HEAD(head) struct list_head head = {&(head), &(head)}
```
## linked list 採用環狀是基於哪些考量?
* 環狀結構為頭尾相連,所以我們可以利用 head->prev 而快速的求得 linked list 的尾巴是誰。時間複雜度為 $O(1)$
* 透過雙向環狀結構能使得資料的鍊結在遭到破壞時,多一層保障,例如某個鍊結的 next 指標壞了,我們可以透過不斷的往前追朔 prev 而能得到原本 next 指標指向的物件
## linked list 的排序應用
* 每一個 process 都對應著一個 process control block 簡稱 pcb,這些 process 的 pcb 由 linked list 串接起來放在待命佇列,等著 cpu 排班程式挑選進 cpu 執行
* pcb 示意圖
![](https://i.imgur.com/4Rf0F7U.png)
* 如果這些 pcb 彼此之間有優先順序的話,就必須要透過 linked list 排序將這些 pcb 順序調換,讓 os 選擇優先權大的先執行
## 實作 merge sort
### divide
* merge sort 是先將資料從中間作切割,分成左半邊跟右半邊,直到無法再分割為止
* 一開始我原本在想是否需要將 list 長度計算出來,才能回推中間的資料是哪個,後來我想到了當我用兩個指針, next 跟 prev 分別從 head 往後跟往前每次走一格,next 指針經歷的資料接在 list_left 後頭,而 prev 指針經歷的資料接在 list_right 後頭,則我們能剛好將資料分成兩個部分
* 當 `next->next == prev` 代表我們將所有的資料都走訪過了,這時我們將 visitall 設為 true 表示不用再將最後一個 prev 放在 list_left 尾端
* code
```c
INIT_LIST_HEAD(&list_left);
INIT_LIST_HEAD(&list_right);
/* split half*/
struct list_head *next = head->next;
struct list_head *prev = head->prev;
bool visit_all = false;
for (; next != prev; next = next->next, prev = prev->prev) {
list_move_tail(next, &list_left);
list_move_tail(prev, &list_right);
if (next->next == prev) {
visit_all = true;
}
}
if (!visit_all)
list_move_tail(prev, &list_right);
list_mergesort(&list_left);
list_mergesort(&list_right);
```
### divide 改進
* 使用 list_move_tail 會導致錯誤因為當我 list_del 完後 next 釋放掉了,就無法在 next 指向 next,prev 也同理,所以我就去參考 quick-sort.c 的寫法
* quick-sort.c 使用了 list_for_each_safe,使用了 safe 指針去指向 `node->next`,這樣就能確保在釋放掉時還能知道下一個物件位址在哪
* code
```c=
for (safen = next->next, safep = prev->prev; next != prev;
next = safen, prev = safep, safen = safen->next, safep = safep->prev) {
list_move_tail(next, &list_left);
list_move_tail(prev, &list_right);
if (next->next == prev) {
visit_all = true;
}
}
```
### conquer
* conquer 就是將 list_left 跟 list_right 的元素一一取出,使用 list_entry 取出 listhead 現在位於哪個 listitem 底下,將左值跟右值作比較 `iteml->i <= itemr->i`,將比較小的值插入 head 的尾端,為了避免使用 list_move_tail 後造成指針消失,使用了 safel 跟 safer 記錄下一個指向的位址
* 由於有可能還有剩餘的物件沒有被加進 head ,所以在最後加上 `list_splice_tail`
* code
```c
/* merge*/
struct list_head *left_next, *right_next;
struct list_head *safel = NULL, *safer = NULL;
left_next = list_left.next;
right_next = list_right.next;
safel = left_next->next;
safer = right_next->next;
while (left_next != &list_left && right_next != &list_right) {
iteml = list_entry(left_next, struct listitem, list);
itemr = list_entry(right_next, struct listitem, list);
if (iteml->i <= itemr->i) {
list_move_tail(&iteml->list, head);
left_next = safel;
safel = safel->next;
} else {
list_move_tail(&itemr->list, head);
right_next = safer;
safer = safer->next;
}
}
list_splice_tail(&list_left, head);
list_splice_tail(&list_right, head);
```
### 跑完 unitest
* 發現有 segmentation fault
## 參考資料
* [wiki macro](https://zh.wikipedia.org/wiki/%E5%B7%A8%E9%9B%86)
* [stack overflow:Macro vs Function in C](https://stackoverflow.com/questions/9104568/macro-vs-function-in-c/9104584)
* [你所不知道的C語言:函式呼叫篇](https://hackmd.io/@sysprog/SJ6hRj-zg?type=view)
* [GNU: The C Preprocessor 導讀](http://wen00072.github.io/blog/2013/10/13/talk-about-c-macros/)
* [GCC typeof在kernel中的使用——C语言的“编译时多态”](http://deltamaster.is-programmer.com/posts/37253.html)
* [Linux Kernel: 強大又好用的list_head結構 ](https://adrianhuang.blogspot.com/2007/10/linux-kernel-listhead.html)
* [Linux的container_of 與 offsetof巨集](https://myao0730.blogspot.com/2016/09/linuxcontainerof-offsetof.html)
* 作業系統基本概念 第二板
* [Process Control Block in Operating System (OS)](https://prepinsta.com/operating-systems/process-control-block/)