# 2018q3 Homework3 (list)
contributed by < [`dange0`](https://github.com/dange0) >
###### tags: `sysprog2018`
## 自我檢查事項:
### 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本?
* macro
* 會在前置時期將 macro 展開,並且替換調原本程式碼中所有有使用到 macro 的地方
* 優點:省去對 stack frame 的操作,執行較有效率,是以空間換取時間的操作
* 缺點:如果呼叫次數過多的話,會佔用較多記憶體空間
* function call
* 在執行時期,需要用到該 function 時才跳過去使用
* 優點:
* 即使需要多次呼叫該 function,只需要放一份 function code 於記憶體中,可以節省記憶體空間,是以時間換取空間的方式。
* function call 會對 parameter 做型態的檢查
* 缺點:每次使用 function 時,都必須先為他創立一個 stack frame,比較耗時
### Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量
*
### GNU extension 的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 有何作用?在程式碼中扮演什麼角色?
* `typeof` 會將物件的類型返回
* A typeof construct can be used anywhere a typedef name can be used. For example, you can use it in a ==declaration, in a cast, or inside of sizeof or typeof==.
一個 typeof 的範例
```C
#define max(a,b) \
({ typeof (a) _a = (a); \
typeof (b) _b = (b); \
_a > _b ? _a : _b; })
```
因為在前置時期,還無法判斷 a 與 b 的型態,因此透過 `typeof` 的兩個特性:
* 可以動態返回物件類型的特性,
* 可以使用於 `declaration`
使得 `max` 可以在執行時期才去決定 `_a` 的型態要宣告為何
### 解釋以下巨集的原理
```C
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
```
這個巨集可以拆為
- `__extension__`
- `__typeof__(((type *) 0)->member) *__pmember = (ptr)`
- `offsetof`
- `(type *) ((char *) __pmember - offsetof(type, member))`
去看:
* `__extension__`
:::info
[**Gcc gnu online docs 6.46 Alternate Keywords**](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html)
-pedantic and other options cause warnings for many GNU C extensions. You can prevent such warnings within one expression by writing \_\_extension\_\_ before the expression. \_\_extension\_\_ has no effect aside from this.
:::
* 主要是 ISO C 與 GNU C 在一些關鍵字有不同的表示法,如 `asm`,`typeof`,`inline` 等等,因此加上 `__extension__` 就可以避免於編譯時期產生警告訊息
* `((type *) 0)->member) *__pmember = (ptr);`
* `((type *) 0)`:將 0 強制轉型為 pointer to type
* `__typeof__(((type *) 0)->member))`:透過 `__typeof__` 取出 member 的型態
* `const __typeof__(((type *) 0)->member) *__pmember = (ptr)`:利用 member 的型態宣告 `*__pmember`
* `offsetof`
* 於 `stddef.h` 中有定義 `offsetof` 如下:
```clike
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
```
* `&` 的關係,marco 2結果為 member 的地址
* `(TYPE *)0` 的關係,該 struct 的起始位置為 0,因此 member 的地址,也就成了 member 位於這個 struct 的偏移量
* `(type *) ((char *) __pmember - offsetof(type, member))`
* 將 `__pmember` 的位置減掉 member 於 struct 中的偏移量,就會得到整個 strcut 的起始位置
* 利用 `(char *)` 將 `__pmember` 強制轉型是因為 pointer type 在做加減法時,會依照其型態決定一次 offset 多少,將其型態轉為 char 可以確保以 `1 byte` 為單位做加減法[^1]
:::warning
:question: Question:
為什麼 `__pmember` 要先宣告為為 `(type *) 0)->member)*` 再強制轉型為 `char*` 呢?何不一開始就宣告為 `char*`?
:::
[^2]
### 除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處?
*
### `LIST_POISONING` 這樣的設計有何意義?
在 linux kernel 中,在 list_del 時,會將 node 的 next 與 prev 分別指向 `LIST_POISON1` 與 `LIST_POISON2`
[**linux/list.h**](https://github.com/torvalds/linux/blob/master/include/linux/list.h#L123)
```clike
#include <linux/poison.h>
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
```
`LIST_POISON1` 與 `LIST_POISON2` 被定義於 `poison.h` 中
[**linux/poison.h**](https://github.com/torvalds/linux/blob/master/include/linux/poison.h#L18)
```clike
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x200 + POISON_POINTER_DELTA)
```
`poison.h` 中說明了指到 `LIST_POISON1` 與 `LIST_POISON2` 是為了要偵測 page fault 的發生
透過這樣的方式,linux kernel 可以更精確的掌握錯誤情況:[^3]
- 錯誤發生於 `->next` 與 `->prev` 都指向 `NULL` 的情況,代表該 node 尚未初始
- 錯誤發生於 `->next` 與 `->prev` 分別指向 `LIST_POISON1` 與 `LIST_POISON2`,代表有 `Use after free` 發生
### linked list 採用環狀是基於哪些考量?
:::info
[**Linux Device Drivers 3/e, ch11**](https://static.lwn.net/images/pdf/LDD3/ch11.pdf)
==To reduce the amount of duplicated code==, the kernel developers have created a standard implementation of circular, doubly linked lists; others needing to manipulate lists are encouraged to use this facility.
...
Since Linux lists are circular, ==the head of the list is not generally different from any other entry==.
:::
- 減少重複的程式碼
- 不用特別去紀錄 linked list 的開頭
### `list_for_each_safe` 和 `list_for_each` 的差異在哪?"safe" 在執行時期的影響為何?
```clike
#define list_for_each(node, head) \
for (node = (head)->next; node != (head); node = node->next)
```
- 利用 list_for_each 做刪除時, `node->prev = (struct list_head *) (0x00100100)` 與 `node->next = (struct list_head *) (0x00200200)` 會將 node->next 改變,導致在做 `node = node->next` 時會出錯
```clike
#define list_for_each_safe(node, safe, head) \
for (node = (head)->next, safe = node->next; node != (head); \
node = safe, safe = node->next)
```
- 透過 safe 紀錄 node->next,使刪除節點可以順利進行
- 在刪除 node 時,雖然改變 node->next 的值,但是 safe 已經先將其記錄,因此不會有影響
### for_each 風格的開發方式對程式開發者的影響為何?
* 提示:對照其他程式語言,如 Perl 和 Python
* 代表會走訪所有的物件,類似 python 中的 `for ... in ...`
### 程式註解裡頭大量存在 `@` 符號,這有何意義?你能否應用在後續的程式開發呢?
* 提示: 對照看 Doxygen
* 在 Doxygen[^4] 中, `@` 代表的是 command 的開始,可以與 `\` 做替換
### `tests/` 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何?
* 單元測試(英語:Unit Testing)又稱為模組測試, 是針對程式模組(軟體設計的**最小單位**)來進行正確性檢驗的測試工作。[^5]
### `tests/` 目錄的 unit test 可如何持續精進和改善呢?
*
## lab-list
### 預期目標
- 包含 unit test 的設計
- 改為doubly-linked list 且充分考慮到 circular
- 讓 `qtest` 得以支援
- 可將 [dict](https://hackmd.io/s/B1Bq5Jat7) 裡頭的測試資料拿來作效能分析的輸入
[^1]: [Linux的container_of 與 offsetof巨集](https://myao0730.blogspot.com/2016/09/linuxcontainerof-offsetof.html)
[^2]: [Rationale behind the container_of macro in linux/list.h - Stackoverflow](https://stackoverflow.com/questions/6083734/rationale-behind-the-container-of-macro-in-linux-list-h)
[^3]: [What is the role of LIST_POISON1 and LIST_POISON2?
](https://lists.kernelnewbies.org/pipermail/kernelnewbies/2016-March/015879.html)
[^4]: [Doxygen](https://www.stack.nl/~dimitri/doxygen/manual/commands.html)
[^5]: [wiki 單元測試](https://zh.wikipedia.org/wiki/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95)