# F02: list
contributed by < `afcidk` >
* [F02: list](https://hackmd.io/s/S12jCWKHN)
## 自我檢查事項
### 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本?
macro 在編譯時期會被編譯器展開成實際的程式碼,這樣做的好處是可以加速程式的速度。
在進行 function call 的時候,因為我們除了需要把參數 push 到特定的 register 或是 stack 上以外,還需要儲存當前 register 的值到 stack 上。function call 少時,這個影響還看不出來,但是一但數量變大,就會導致程式運作比用 macro 實作時慢。
實際執行一段簡單的[程式碼](https://gist.github.com/afcidk/441abae865be13c599b8f749792908b6),並用 gnuplot 顯示使用 function call 與 macro 的執行時間差異。
![](https://i.imgur.com/6taKMzN.png)
Linux 裡頭許多地方都會使用到 linked list 作為資料結構,因此各種 linked list 的基本操作也會被大量使用。如上段所述,這在 function call 和 macro 實作的效能差異會在量大時顯示出來,這也是為什麼 Linux 會採用 macro 來實作 linked list 的原因之一。
### Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量
我認為使用 linked list 最大的好處是程式開發者不需要在編寫程式時就決定資料的長度。根據這個觀點,我找了下面三個例子
* Process Management
* inode
* Network File System (NFS)
#### linux/sched.h
說到 process management,最常提到的應該就是 `task_struct` 這個結構了。
他定義在 [sched.h](https://github.com/torvalds/linux/blob/master/include/linux/sched.h),光是定義整個結構就需要 628 行!
因為整份程式碼太龐大,這邊我節錄在 `task_struct` 裡頭紀錄 parent/child 的部份解釋
```clike=753
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
```
在 `real_parent` 的前面有一個 `__rcu` 的巨集,全稱叫作 [Read-copy-update](https://en.wikipedia.org/wiki/Read-copy-update),他可以允許動態分配位址的資料結構(e.g. linked-list)在讀取資料時不需要把整個結構用互斥鎖鎖住。
那麼 `real_parent` 和 `parent` 的差別在哪裡?
根據上方的註解,我們可以知道 `real_parent` 肯定是整個結構的父親,而 `parent` 則是 `SIGCHLD` 還有 `wait4()` 回傳結果的對象。
查看 `man 7 signal` 得到 `SIGCHLD Child stopped or terminated`
可以知道 `SIGCHLD` 是一個 child 暫停或是終止時會發出的訊號。
`wait4()` 是 BSD 風格的函式,和他相近的另一個規範在 POSIX 裡的函式是 `waitpid()`,他們的用途是卡住程式,並等待 child 的狀態改變。
寫了寫現在還是不太明白,目前只知道大概跟 `ptrace` 有關係。
#### inode
inode(index node) 是 Unix-like 系統裡面負責處理檔案系統的資料結構。
我們可以看到 [fs.h](https://github.com/torvalds/linux/blob/master/fs/fs.h) 裡頭的 `inode` 結構也有 `list_head` 的蹤影
```clike=668
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
struct list_head i_wb_list; /* backing dev writeback list */
```
### GNU extension 的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 有何作用?在程式碼中扮演什麼角色?
typeof 允許我們傳入一個變數,代表的會是該變數的型態。
舉例來說:
```clike
int a;
typeof(a) b = 10; // equals to "int b = 10;"
char s[6] = "Hello";
char *ch;
typeof(ch) k = s; // equals to "char *k = s;"
```
`typeof` 大多用在定義巨集上,因為在巨集裏面我們沒辦法知道參數的型態,在需要宣告相同型態的變數時,`typeof` 會是一個很好的幫手。
這邊以 max 的巨集為例子
```clike
#define max(a,b) \
{(typeof(a) _a = a; \
typeof(b) _b = b; \
_a > _b ? _a : _b;)}
```
至於為什麼我們需要那麼麻煩將 max 的巨集寫成這樣的形式呢?難道不可以寫成 `#define max(a,b) (a>b?a:b)` 就好嗎?
這樣的寫法會導致 **Double evaluation** 的問題,顧名思義就是會有某些東西被執行過兩次。
試想如下情況
```clike
#define max(a,b) (a>b?a:b)
void doOneTime() {printf("called doOneTime!\n");}
int f1() {doOneTime(); return 0;}
int f2() {doOneTime(); return 1;}
int result = max(f1(), f2());
```
實際執行後,我們會發現我們竟然呼叫了**三次** `doOneTime` 函式,但是明明在 `max` 的地方我們只期待會呼叫兩次而已不是嗎?
這是因為在巨集展開後,原本的 `max(f1(), f2())` 會被改成這樣的形式
```
int result = (f1()>f2()?f1():f2());
```
為了避免這個問題,我們必須在巨集中先用變數把可能傳入的函式回傳值儲存下來,之後判斷就不要再使用一開始傳入的函式,而是是用後來宣告的回傳值變數。
### 解釋以下巨集的原理
```clike
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
```
我們一步一步拆解內容。首先看到的是 `__extension__`,他是一個修飾字,用來防止 gcc 編譯器產生警告。
什麼情況下編譯器會產生警告讓我們想要忽略呢?當我們在編譯時,編譯器可能會提醒我們,程式裏面有使用到非 ANSI C 標準的語句,我們寫的程式在現在的編譯器可能可以過,但是用其他的編譯器可能就不會過了。
在這邊的例子,出問題的地方應該是在開頭的 `({})`(braced-group within expression),實際編譯過後我們可以看到這樣的警告訊息
```
warning: ISO C forbids braced-groups within expressions [-Wpedantic]
```
事實上這是 gcc 自己擴展的一個 extension,透過這種寫法可以讓我們的 macro 更**安全**。
如果不想要看見這個警告,就可以在最前面加上 `__extension__` 修飾字。
再來的話看到 `__typeof__(((type *) 0)->member) * __pmember` 這段
可以看到我們用 `((type*)0)->member` 的型態再宣告一個新的指標 `__pmember`。
`(type*)0` 代表的是一個被轉型成 `type` 型態的 struct,在這個地方我們不管他是不是一個合法的位址,所以可以直接寫 0 就好。再來我們讓他指向 `member`,因此 `__typeof__(((type*)0)->member)` 代表的便是 `member` 的資料型態。
再往下看到 `(type *) ((char *) __pmember - offsetof(type, member));` 這段比較好解釋,其實就只是把 member 的位址扣除 member 在整個 struct 裡面的偏移後,得到整個 struct 的開頭位址而已。
以圖像的方式會長這樣
![](https://i.imgur.com/9Wa4knK.png)
### LIST_POISONING 這樣的設計有何意義?
跟 poison 相關的定義可以查看 [linux/poison.h](https://github.com/torvalds/linux/blob/master/include/linux/poison.h)
參考 Stack Overflow 上 Gil Hamilton 針對 [what is the meaning of 0xdead000000000000?](https://stackoverflow.com/questions/27801360/what-is-the-meaning-of-0xdead000000000000) 的回答,list poisoning 的目的應該是為了程式開發者除錯方便而產生的一種寫法。
在使用 linked list 時,當我們需要把一塊元素從 list 中去除,通常需要做兩件事:
1. `free(ptr)`
2. 讓 ptr 指向 `NULL`
但是 LIST_POISONING 的作法卻不是讓指標指向 NULL,他反而讓指標指向另一個違法的位址。
這格作法跟指向 NULL 相同的是,日後在存取到時都會造成程式錯誤。
不同的地方是我們可以大概得知程式到底會什麼為 crash。
在 `poison.h` 裡面的註解中提到
Architectures might want to move the poison pointer offset
into some well-recognized area such as 0xdead000000000000,
that is also not mappable by user-space exploits:
如果指標的前綴是 `0xdead`(這只是其中一個例子,這個值我們可以自己設定,但是需要確定是在非法區段),代表是因為我們手動將空間釋放後才導致錯誤,反之則可能是因為沒有初始化等其他錯誤。