# 2018q3 Homework3 (list) contributed by < [`butastur-rtos`](https://github.com/butastur-rtos) > ###### tags: `homework3` `list` `sysprog2018` * 預備知識 * 作業要求 ## 預備知識 * \_\_extension__ * [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) * doubly-linked list ### \_\_extension__ ### typeof * What's the different between `__typeof__` and `typeof`? * [__builtin_types_compatible_p](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) * [BUILD_BUG_ON_ZERO](https://github.com/torvalds/linux/blob/master/include/linux/build_bug.h#L23) * [__must_be_array](https://github.com/torvalds/linux/blob/master/tools/include/linux/compiler-gcc.h#L21) * [__same_type](https://github.com/torvalds/linux/blob/master/tools/include/linux/compiler.h#L25) ### [doubly-linked list ](https://www.cs.cmu.edu/~guna/15-123S11/Lectures/Lecture11.pdf) ## [作業要求](https://hackmd.io/s/SyVPDd1cX) * 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本? * Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量 * GNU extension 的 typeof 有何作用?在程式碼中扮演什麼角色? * 解釋以下巨集的原理 ```clike #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) ``` * 除了 add 和 delete 操作,[list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 還定義一系列操作,為什麼呢?這些有什麼益處? * LIST_POISONING 這樣的設計有何意義? * linked list 採用環狀是基於哪些考量? * list_for_each_safe 和 list_for_each 的差異在哪?“safe” 在執行時期的影響為何? * for_each 風格的開發方式對程式開發者的影響為何? * 程式註解裡頭大量存在 @ 符號,這有何意義?能否應用在後續的程式開發呢? * tests/ 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何? * tests/ 目錄的 unit test 可如何持續精進和改善呢? * 改寫 Homework1: lab0 使其成為 doubly-linked list 且充分考慮到 circular * 附上修改過程,也需要讓 qtest 得以支援 * 將 dict 裡頭的測試資料拿來作效能分析的輸入 ### 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本? macro 是在 compile 的時候作 replace ### Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量 [ [source] ](https://github.com/torvalds/linux/blob/master/net/ipv4/fib_lookup.h#L9) ```clike struct fib_alias { struct hlist_node fa_list; ... }; ``` [ [source] ](https://github.com/torvalds/linux/blob/master/net/ipv4/fib_trie.c#L2661) ```clike struct key_vector { ... union { /* This list pointer if valid if (pos | bits) == 0 (LEAF) */ struct hlist_head leaf; ... }; }; ... static int fib_route_seq_show(struct seq_file *seq, void *v) { struct fib_alias *fa; struct key_vector *l = v; ... hlist_for_each_entry_rcu(fa, &l->leaf, fa_list) { ... } ... } ``` 以上的 code 有幾個地方要考慮一下: * hlist_for_each_entry_rcu 會透過 l->leaf 的 address 取得什麼? * fa 作為 loop 的 cursor,是屬於哪一個 collection 的 cursor? ### 解釋以下巨集的原理 ```clike #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) ``` 解釋巨集 container_of 之前先解釋以下兩件事 * typeof * [offsetof](https://github.com/torvalds/linux/blob/master/scripts/kconfig/list.h#L10) 從 typeof 說起,舉 2 個例子 宣告 ptr_x 為一個指標,這個指標所指向的 type 和 x 一樣 ```clike int x; typeof(&x) ptr_x; ``` __builtin_types_compatible_p 是一個 built-in function,如果 2 個 type 一樣就返回 1 ```clike __builtin_types_compatible_p(typeof(x), int) ``` 接著解釋 offsetof ```clike #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ``` ```clike struct node { int a; char b; }; void main(int argc, char **argv) { int offset = offsetof(struct node, b); printf("%d\r\n", offset); // this will be 4 printf("sizeof(int):%d\r\n", sizeof(int)); } ``` 為什麼上面的輸出會是 4 呢? 因為 sizeof(int) 是 4 個 bytes, offsetof 這個 macro 是計算 TYPE 裡要 offset 幾個 byte [ [offsetof] ](https://zh.wikipedia.org/wiki/Offsetof) But, 這樣理解 offsetof 是不夠的, 要從 [C99規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 來看 offsetof 的規範 [7.17 Common definitions](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf): :::info offsetof(type, member-designator) which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type) ::: 以 [C99規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 重新審視一下 offsetof,就是取得一個 size_t,代表這一個 member 在 structure 裡,距離這個 structure 的 beginning 有幾個 bytes。 [offsetof macro](http://blog.linux.org.tw/~jserv/archives/001399.html) 對 offsetof 有更仔細的說明,透過這篇文章,再來重新看一下這個巨集 ```clike #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ``` :::info Nigel Jones 對 offsetof macro 作了一步一步的說明,重新整理一下如下: * ((TYPE *)0) 把 0 當成是一個指向 TYPE 的 pointer 的 value * ((TYPE *)0)->MEMBER ((TYPE *)0)是一個 pointer,指向0 這個 address,((TYPE *)0)->MEMBER 就是透過這個 pointer 指向 structure member (在本例就是MEMBER) * &((TYPE *)0)->MEMBER) 就是 structure member 的 address * ((size_t) &((TYPE *)0)->MEMBER) 對 structure member 作 cast ::: :::warning :question: ((<b>size_t</b>) &((TYPE *)0)->MEMBER) 把 structure member cast 成 <b>size_t</b> 為什麼可以表示 member 到 beginning of its structure 有幾個 bytes ? ::: 先看一下沒有 \_\_extension__ 的情況 [ [source] ](https://github.com/torvalds/linux/blob/master/scripts/kconfig/list.h#L12) ```clike /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) ``` :::info container_of 的目的是用三個參數來取得一個 struct 的address * pointer to the member * 這個 member 所位於的 struct 是什麼 type * 這個 member 的名字 ::: 再講一件事就可以開始解釋 container_of 的原理了,就是==對 pointer 減 1== 實際上發生了什麼事? 看一下以下的範例 ```clike int a = 111; char b = 'a'; int *ptr_int = &a; char *ptr_char = &b; printf("ptr_int: %p\r\n", ptr_int); ptr_int++; printf("ptr_int: %p\r\n\r\n", ptr_int); printf("ptr_char: %p\r\n", ptr_char); ptr_char++; printf("ptr_char: %p\r\n", ptr_char); ``` ```shell ptr_int: 0x7ffd38371d14 ptr_int: 0x7ffd38371d18 ptr_char: 0x7ffd38371d13 ptr_char: 0x7ffd38371d14 ``` :::info * 隨著 pointer 所指向的 object 的 sizeof 不一樣,減 1 所造成的 offset 也不一樣。 * 直接對 pointer to member 減掉 offsetof 得到的偏移量 (offsetof 的單位是 byte),不會得到一定正確的結果 ::: 以下解釋 container_of 的原理 * member 的名字 * 是給 ==typeof== 使用,用來宣告 pointer to the member。這個新的 pointer 之後會被 cast to char *,因為先前提到的 offsetof 計算出的偏移量,==單位是 byte==。 * 給 ==offsetof== 使用,取得 member 到 structure 的開頭有幾個 bytes,是一個以 byte 為單位的偏移量 * 有了 offsetof 這個 macro,就可以用 pointer to the member 來取得 member 到 structure 的開頭有幾個 bytes,把這個 pointer to the member 的 value (是 value 而不是 address) ==cast to char *==(理由先前有提到),然後減掉這個 offset 就可以取得這個 structure 的 address。 * 簡單來說,就是==把一個指向 structure member 的 char * 減掉 offset 就可以取得 address of structure==。 ### LIST_POISONING 這樣的設計有何意義? * non-initialized list entries * [LIST_POISON1](https://github.com/torvalds/linux/blob/master/include/linux/poison.h#L23) #### non-initialized list entries #### [LIST_POISON1](https://github.com/torvalds/linux/blob/master/include/linux/poison.h#L23) 對 0x100 這個 address 作 dereference 會發生什麼事? 會[產生一個 page fault exception](https://github.com/torvalds/linux/blob/master/include/linux/poison.h#L19) [source](https://github.com/torvalds/linux/blob/master/include/linux/list.h#L123) ```clike static inline void list_del(struct list_head *entry) { __list_del_entry(entry); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } ``` 但是這樣的設計是為了什麼? [source](https://github.com/torvalds/linux/blob/master/include/linux/poison.h#L19) ```shell These are non-NULL pointers that will result in page faults under normal circumstances, used to verify that nobody uses non-initialized list entries. ``` 底下用一段code來實驗 uses non-initialized list entries 會發生什麼事 ```clike #include <stdio.h> void main(int argc, char **argv) { char * ptr = ((void *) 0x100); printf("%c\r\n", *ptr); } ``` 以上這段的執行結果會是 ```shell $ gcc test1.c -o test1 $ ./test1 程式記憶體區段錯誤 (核心已傾印) ``` 至此,可以說 LIST_POISONING 設計的意義就如同 [註解](https://github.com/torvalds/linux/blob/master/include/linux/poison.h#L19) 說的那樣,是用來==驗證未初始化的 list entry 不會被使用到==,如果用到就產生一個 page fault exception ### linked list 採用環狀是基於哪些考量? * 什麼是環狀? ### list_for_each_safe 和 list_for_each 的差異在哪?“safe” 在執行時期的影響為何? ### 程式註解裡頭大量存在 @ 符號,這有何意義?能否應用在後續的程式開發呢? 底下示範 @ 如何應用在文件以及如何在開發的過程中發生 @ 的意義 ### 改寫 Homework1: lab0 使其成為 doubly-linked list 且充分考慮到 circular * [doubly-linked list ](https://www.cs.cmu.edu/~guna/15-123S11/Lectures/Lecture11.pdf) * 參考 [list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) * 增加 prev 這個 node * 考慮 insert / remove node 時 prev 要另外作什麼事? * 充分考慮到 circular 先增加一個 prev,指向 list 裡==每一個 node 的前一個 node== ```clike typedef struct ELE { char *value; struct ELE *next; struct ELE *prev; } list_ele_t; ``` 先改寫 q_insert_tail,這個 function 是 insert 到 list 的 tail,所以新增的 node 的 prev 要指向 tail 所指向的 node ```clike bool q_insert_tail(queue_t *q, char *s) { if (q == NULL) { return false; } /* You need to write the complete code for this function */ /* Remember: It should operate in O(1) time */ /* newt means new tail */ list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (newt != NULL) { newt->next = NULL; newt->value = strdup(s); newt->prev = q->tail; if (q->size == 0) q->head = newt; else q->tail->next = newt; q->tail = newt; q->size++; } return newt != NULL; } ``` 但 `make test` 的結果是 ```shell +++ TESTING trace trace-06-string: # Test of truncated strings ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer --- trace-06-string 0/7 ``` q_reverse 在 size > 1 的時候才有意義 ```clike void q_reverse(queue_t *q) { if (q != NULL && q->size > 1) { ... } ... } ``` 接著改寫 q_insert_head,第一個 element (就是 newh) 的 prev 先指向 NULL,等一下再考慮如果不指向 NULL 的情況 ```clike bool q_insert_head(queue_t *q, char *s) { ... if (newh != NULL) { ... newh->prev = NULL; ... } ... } ``` `make test` 的結果 ```shell --- trace-15-perf 7/7 --- TOTAL 100/100 ``` :::info 還剩下兩個 function 要改寫 * q_remove_head * q_reverse ::: 接下來改寫 q_remove_head remove head 之後,把 head 的 prev 指向 NULL ```clike bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { ... if(q->head != NULL) q->head->prev = NULL; return true; } ``` `make test` 的結果 ```shell --- trace-15-perf 7/7 --- TOTAL 100/100 ``` 接下來改寫 q_reverse circular 的情況 :::danger 持續更新中... :::