# 2019q1 Homework1 (list) contributed by < `rebvivi` > ###### tags: `linux2019` * [F02: list](https://hackmd.io/s/S12jCWKHN#F02-list) ## 作業要求 - 回答「Linux 風格的 linked list」裡頭「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼 - 從 linux-list 學習裡頭的技巧,包含裡頭 unit test 的設計,透過給定的 linked list 操作,實作出 merge sort * 附上你的修改過程,也需要讓`qtest` 得以支援 * 可將 dict 裡頭的測試資料拿來作效能分析的輸入 * 思考提升排序效率的方法,需要做平均和最壞狀況的分析 ## 自我檢查清單 1. 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本? 2. Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量 3. GNU extension 的 typeof 有何作用?在程式碼中扮演什麼角色? 4. 解釋以下巨集的原理 ```clike #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr);\ (type *) ((char *) __pmember - offsetof(type, member));\ }) ``` 5. 除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處? 6. `LIST_POISONING`這樣的設計有何意義? 7. linked list 採用環狀是基於哪些考量? 8. 什麼情境會需要對 linked list 做排序呢?舉出真實世界的應用,最好在作業系統核心出現 9. 什麼情境會需要需要找到第 k 大/小元素呢?又,你打算如何實作? 10. `list_for_each_safe` 和 `list_for_each` 的差異在哪?“safe” 在執行時期的影響為何? 11. for_each 風格的開發方式對程式開發者的影響為何? * 提示:對照其他程式語言,如 Perl 和 Python 12. 程式註解裡頭大量存在 `@`符號,這有何意義?你能否應用在後續的程式開發呢? * 提示: 對照看 Doxygen 13. `tests/` 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何? 14. `tests/` 目錄的 unit test 可如何持續精進和改善呢 ### 1. 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本? - macro:用 `#define` 這個語法,描述一段`算式或變數`,在 compile 之前, preprocessor 就會將所有`算式或變數`用 `macro_name` 做代換,執行時不需要 function call 一樣有 stack 的 push 或是 pop,讓執行的速度變快 但隨著呼叫 macro 的次數增加,會大大的增加記憶體的使用量,是一種用==空間換取時間==的方法 ```cpp #define macro_name 算式或變數 ``` - function call: 執行時需要有 stack 的 push 或是 pop ,也就是主要的成本,會讓執行的時間變慢 但是所有相同的算式都共用同一部份 code (共用同一部份記憶體) ,相對 macro 來說比較節省記憶體空間,是一種用==時間換取空間==的方法 ### 2. Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量 - 在 Linux kernel 中,有叫做 `list_head` 的 `doubly linked list`,包含 next 和 prev 兩個 pointer ,分別指向下一個跟前一個 node ```clike struct list_head { struct list_head *next, *prev; }; ``` 一開始都會把 next 和 prev 指向自己做 initialization 之後就可以藉此判斷 list 是否是空的 ```clike int list_empty(const struct list_head *head) { return head->next == head; } ``` 參考資料:[Linked Lists - Linux Device Drivers, Second Edition](https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch10s05.html) ### 3. GNU extension 的 typeof 有何作用?在程式碼中扮演什麼角色 [typeof](https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Typeof.html) 是 C 語言的 extension,用於傳回物件的 type typeof 的參數可以有兩種: - expression >假設 x 是一個 array of pointers to functions ,我們可以得到這個 function 回傳值的 type ```cpp typeof (x[0](1)) ``` - type >我們得到的 type 是 pointers to int ```cpp typeof (int *) ``` >在 macro 的時候,可能無法判斷 a 和 b 的 type,所以如果使用 typeof 的話可以讓 macro 動態的接受不同的 type ,除了避免 type 轉換中產生錯誤,也可以讓程式更有彈性 ```clike #define max(a,b) ({ typeof (a) _a = (a); \ typeof (b) _b = (b); \ _a > _b ? _a : _b; }) ``` ### 4. 解釋以下巨集的原理 ```clike #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr);\ (type *) ((char *) __pmember - offsetof(type, member));\ }) ``` - `(type *) 0)->member`: 0 轉型為 pointer to type 並將它指向 member - `__typeof__(((type *) 0)->member)`: 取得 "member 的 type" - `__typeof__(((type *) 0)->member) *__pmember`: 將 __pmember 宣告為 pointer to "member 的 type" - `__typeof__(((type *) 0)->member) *__pmember = (ptr)`: 將 ptr 的位址 assign 給 __pmember [offsetof 的 Linux Man Page](http://man7.org/linux/man-pages/man3/offsetof.3.html) 寫到: ```cpp size_t offsetof(type, member); ``` > The macro offsetof() returns the offset of the field member from the start of the structure type. - `offsetof(type, member)`: member 在 type 這個 structure 的 offset - `(char *) __pmember - offsetof(type, member)`: __pmember 是指向 “member 的 type” 的,如果再減去 “member 的 type” 在 type 這個 structure 的 offset,可以得到 type 這個 structure 的起始位置 - `(type *) ((char *) __pmember - offsetof(type, member))`: 將 type 這個 structure 的起始位置轉型為 type - `__extension__`: 避免 gcc 提出警告 ### 5. 除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處? - 這樣 list 在做這一系列操作的時候,能夠讓速度變得更快,將 time complexity 盡量壓在 O(1) 之內 - list 在做這一系列操作的時候,只有改變 list 指的位置,並沒有改變記憶體本身的資料,所以我們在放資料的時後就可以自由的放置在記憶體,並不因為使用 `linkes list` 這個 structure 而受到限制 參考資料:[Linux source code: include/linux/list.h](https://elixir.bootlin.com/linux/latest/source/include/linux/list.h) ### 6. `LIST_POISONING`這樣的設計有何意義 >如果我們將一個 `doubly linked list` 中的 node 刪除 ```cpp * LIST_POISONING can be enabled during build-time to provoke an invalid memory * access when the memory behind the next/prev pointer is used after a list_del. * This only works on systems which prohibit access to the predefined memory * addresses. */ static inline void list_del(struct list_head *node) { struct list_head *next = node->next; struct list_head *prev = node->prev; next->prev = prev; prev->next = next; #ifdef LIST_POISONING node->prev = (struct list_head *) (0x00100100); node->next = (struct list_head *) (0x00200200); #endif } ``` >在刪除 node 之後, node 變成一個 uninitialized node,當我們去 access 這個 node 會變得不安全,所以我們會將 node 的 next 和 prev 指向不合法的記憶體位址,之後如果我們試圖去 access 這個被刪除的 node,就會直接產生 page fault,確保我們 access 資料時的安全性 ### 7. linked list 採用環狀是基於哪些考量? 如果 linked list 採用環狀的結構,就沒有一般 linked list 那些 head 和 tail 的問題,對每個 node 來說並沒有操作上的不同,而且能夠任意一個 node 去 traverse 整個 linked list ,並不一定限制要從 head 開始 ### 8. 什麼情境會需要對 linked list 做排序呢?舉出真實世界的應用,最好在作業系統核心出現 當我們在搜尋網頁的時候,網頁往往會有 page rank ,用來分析網頁的相關性和重要性,而 page rank 會隨著網頁的點擊率而上升或下降,所以我們就要使用 sorting 來幫我們依照 page rank 的大小做排序 ### 9. 什麼情境會需要需要找到第 k 大/小元素呢?又,你打算如何實作? - 當搜尋引擎依照 page rank 的大小做排序之後,會需要找到第 k 大元素,把最大的 k 個網頁排在搜尋結果的前面 - 利用 worst-case linear-time selection 實作 1. Divide the n elements into groups of 5. Find the median of each 5-element group by rote 2. Recursively SELECT the median x of the n/5 group medians to be the pivot. 3. Partition around the pivot x. Let k = rank(x). 4. * if(i = k) return x * else if (i < k) recursively SELECT the ith smallest element in the lower part * else recursively SELECT the (i–k)th smallest element in the upper part ### 10. `list_for_each_safe` 和 `list_for_each` 的差異在哪?“safe” 在執行時期的影響為何? - list_for_each >我們並不能更改任何一個 node ,假如我們 delete 掉一個 node ,我們會因此找不到原本 node 的下一個 element,因此失去 node 的後面那些資料 ```clike #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) ``` - list_for_each_safe > `list_for_each_safe` 相對 `list_for_each` 多了一個 safe 的暫存空間,而 safe 指向 node 的下一個 element ,避免我們將 node 給 delete 掉之後,會找不到原本 node 的下一個 element ```clike #define list_for_each_safe(node, safe, head) \ for (node = (head)->next, safe = node->next; node != (head); \ node = safe, safe = node->next) ``` ### 11. for_each 風格的開發方式對程式開發者的影響為何?(提示:對照其他程式語言,如 Perl 和 Python) for_each 不像一般我們在 C 語言使用的 for 需要有一個 counter , for_each 會將所有的 element 都跑過一遍 ```clike mylist = ['a', 'b', 'c'] for item in mylist: print item ``` >輸出結果 ```cpp a b c ``` for_each 對我們人來說,使用起來更為精簡方便,可讀性也更高 ### 12. 程式註解裡頭大量存在 `@`符號,這有何意義?你能否應用在後續的程式開發呢?(提示: 對照看 Doxygen) - Doxygen 是一個可以自動產生文件的程式,它能夠抓取程式的內容,自動產生說明程式的文件 我們在註解的前方加入一些 Doxygen 支援的指令,比如說`@` 這個符號,可以告訴 Doxygen 這是特殊的指令,例如: >以下用於說明一個 function , parameter 是參數的名稱,而後方往往會連接對這個 parameter 的說明 ``` @parameter:parameter 的說明 ``` >以下是 `list_head` 上方的註解,也是`@` 這個符號的實際應用 ``` * @prev: pointer to the previous node in the list * @next: pointer to the next node in the list ``` 在註解前方加入`@`等等的指令之後,能夠讓 Doxygen 更容易辨識一些關鍵字,更容易產生說明程式的文件 - 之後多人共同開發一些大型程式的時候,往往需要有這些註解,能夠幫助其他共同開發者可以快速看懂彼此的程式,增快開發程式的效率 ### 13. `tests/` 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何? - unit test:針對程式裡最小的邏輯單元為對象,寫一個測試的程式,來檢驗這些邏輯單元是否正確 - * 從最小的邏輯單元開始測試,可以確保程式的正確性 * 我們輸入 make ,執行 unit test ,可以用自動化的方式測試我們的程式,而不用靠我們手動自己輸入測試資料 * 運用 unit test 能夠幫我們測試如果程式在面對一些極端情況的 input ,是否也能如期的產生我們所希望的 output ### 14. `tests/` 目錄的 unit test 可如何持續精進和改善呢? 可以根據一些極端值或邊界值做測試,看看程式會不會因此產生錯誤,讓程式能夠因應各式各樣的 input