# 2018q3 Homework3 (list) contributed by < `happyincent` > ## 自我檢查事項 ### 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本? 從 [list.h](https://github.com/sysprog21/linux-list/blob/master/include/list.h) 可以看出 * 更動 list 的 function 是使用 `static inline` 而非 `#define` * `#define` 除了使用在 ==簡化並展開程式碼==,也在 `container_of` 使用 [Statements and Declarations in Expressions](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) 來替換 function call #### macro * 在 preprocessed 時處理 * 文本替換 (增加程式碼可讀性),重複使用 macro (large code) 會使執行檔變大 * 不檢查輸入參數的類型,很難發現編譯錯誤,也可能導致無法預料的後果,[例如:](https://www.geeksforgeeks.org/macros-vs-functions/) ```c #include<stdio.h> #define CUBE(b) b*b*b // 1+2*1+2*1+2 #define CUBE2(b) (b)*(b)*(b) // (1+2)*(1+2)*(1+2) int main() { printf("%d %d\n", CUBE(1+2), CUBE2(1+2)); // 7 27 return 0; } ``` * [Macro v.s. Function](https://stackoverflow.com/a/33953761) * ==function 呼叫時需要做額外的 stack 操作== #### [inline function](http://gnitnaw.github.io/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/2016/06/14/C_inline.html) * inline 是一種編譯最佳化的方式,將指定的函式插入並取代每一處呼叫該函式的地方 * 編譯器可以不接受原始碼給的 inline 建議,不用取代的方式而維持呼叫該指定函式。但是 macro 一定得替換。 --- ### Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量 * Android kernel 3.18 - OSS sound_core ([./sound/sound_core.c](https://android.googlesource.com/kernel/common/+/android-3.18/sound/sound_core.c)) ```c struct sound_unit { int unit_minor; const struct file_operations *unit_fops; struct sound_unit *next; char name[32]; }; ``` * sound_unit * unit_minor: 0 ~ 15 (SOUND_STEP=16, device number % SOUND_STEP) * unit_fops: filesystem 檔案操作 ```c static const struct file_operations soundcore_fops = { /* We must have an owner or the module locking fails */ .owner = THIS_MODULE, .open = soundcore_open, .llseek = noop_llseek, }; ``` * name: device name ("mixer", "sequencer", "midi", "dsp", ...) * sound_insert_unit: * 註冊特定種類的 device 時 (e.g., `register_sound_mixer`),會傳入 global 的 `static struct sound_unit *chains[SOUND_STEP];` 中的其中一個 list head (e.g., Mixers: `&chains[0]`) 給此 function 的 parameter `struct sound_unit **list`,然後 insert 一個新的 sound_unit 到這個 chain 中 --- ### GNU extension 的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 有何作用?在程式碼中扮演什麼角色? [GNU typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) (ISO C - `__typeof__`) * refer to the type of an expression * `typeof` is evaluated if and only if it is * an expression of variably modified type * the name of such a type * [__builtin_types_compatible_p](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) ```c #include <stdio.h> /* https://medium.com/@maicki/type-inference-with-auto-type-55a38ef56372 */ #define let __auto_type const #define var __auto_type /* https://stackoverflow.com/a/36588221 */ #define __is_constant_int(X) _Generic((&X), \ const int *: "a const int", \ int *: "a non-const int") /* https://stackoverflow.com/a/1666362 */ typedef void (*stdfx)(int); void fx_typ(stdfx fx); // ok #define FX_TYPE void (*)(int) // void fx_def(FX_TYPE fx); // error, no type: void (*)(int) int main() { let a = 123; var b = 123.0; printf("%d\n", __builtin_types_compatible_p( typeof(a), int const )); // 1 printf("%d\n", __builtin_types_compatible_p( typeof(a), int )); // 1 printf("%d\n", __builtin_types_compatible_p( typeof(a), typeof(int *) )); // 0 printf("%d\n", __builtin_types_compatible_p( typeof(a), typeof(b) )); // 0 var a1 = a; printf("%s\n", __is_constant_int(a1)); // a const int var b1 = (int)b; printf("%s\n", __is_constant_int(b1)); // a non-const int } ``` --- ### 解釋以下巨集的原理 ```Clike #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) ``` #### container_of 的 parameters ```c /** * container_of() - Calculate address of object that contains address ptr * @ptr: pointer to member variable * @type: type of the structure containing ptr * @member: name of the member variable in struct @type * * Return: @type pointer of object containing ptr */ ``` * ptr: pointer to list node * type: 包含 list node 的 struct 是什麼 type * member: struct 中 type 為 list node 的 member 的名字 * return: pointer to 包含 list node 的 struct #### list.h 中 container_of 只使用於 list_entry ```c #define list_entry(node, type, member) container_of(node, type, member) ``` #### `({ ... })` * A compound statement enclosed in parentheses ```c #define Square(x) ({ \ typeof(x) y = (x); \ y*y; \ }) ``` * [Statements and Declarations in Expressions](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) #### `__extension__` * `-pedantic` or `-Wpedantic` * Issue all the warnings demanded by strict ISO C and ISO C++; reject all programs that use forbidden extensions. * ISO C `-std`、`-ansi` * `-Wpedantic` 不會 warning 開頭和結尾為 `__` * 不會 Warning the expression that follows `__extension__` * only system header files should use these escape routes * source: gnu gcc * [Warning-Options](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#Warning-Options) * [Alternate-Keywords](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#Alternate-Keywords) #### `__typeof__` 同 `typeof` * `((type *) 0)->member`: 將位址 0 轉為 pointer (point to the struct type) * `__typeof__(((type *) 0)->member)`: 取得 struct 中此 member 的 type :::info `const __typeof__(((type *) 0)->member) *__pmember = (ptr);` 可以用來確認傳入 ptr 的 type 是否正確 [[stackoverflow](https://stackoverflow.com/questions/6083734/rationale-behind-the-container-of-macro-in-linux-list-h)] ::: ```c #include <stdio.h> #include <stddef.h> #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #define container_of_2(ptr, type, member) \ ((type *) ((char *) (ptr) -offsetof(type, member))) typedef struct _foo { int bar; } foo; int main() { foo f; double *ptr = (double *)&f.bar; foo *p1 = container_of(ptr, foo, bar); // ↑ warning: pointer type mismatch in conditional expression // const __typeof__(((type *) 0)->member) *__pmember = (ptr); // ^ foo *p2 = container_of_2(ptr, foo, bar); // ↑ no warning printf("%d\n", (p1 == p2) ? 1 : 0); // 1 } ``` #### `offsetof` * [stddef.h](https://github.com/torvalds/linux/blob/master/include/linux/stddef.h) ```c #undef offsetof #ifdef __compiler_offsetof #define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) #endif ``` * 第一種使用 compiler 內建函式,`__compiler_offsetof` 定義在 `compiler_types.h` ```c #define __compiler_offsetof(a, b) __builtin_offsetof(a, b) ``` * 第二種將 0 轉為 popinter (TYPE 起始位址為 0),然後以 `&` 拿到 MEMBER 的位址,即可得到 member 在 TYPE 中的 offest * 由以下範例,可以看出 * a structure is a type consisting of a sequence of members, whose storage is allocated in an ordered sequence (6.7.2.1 - 5) * struct 會對齊 word size (8 bytes for 64-bit cpu, 4 bytes for 32-bit cpu) ```c #include <stdio.h> #include <stddef.h> #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) typedef struct T { int member_bit : 9; char member_char; long member_long; } TYPE; int main() { printf("%zu\n", sizeof(TYPE)); // 16, 8 (armv7l) printf("%zu\n", offsetof(TYPE, member_char)); // 2, 2 (armv7l) printf("%zu\n", offsetof(TYPE, member_long)); // 8, 4 (armv7l) // printf("%zu\n", offsetof(TYPE, member_bit)); // error TYPE t; printf("%p, ", &t); printf("%p, ", &t.member_long); printf("%p\n", container_of(&t.member_long, TYPE, member_long)); // 0x7ffff9572f00, 0x7ffff9572f08, 0x7ffff9572f00 (x86-64) // 0x7ea6b20c, 0x7ea6b210, 0x7ea6b20c (armv7l) } ``` #### `(type *) ((char *) __pmember - offsetof(type, member));` * offsetof return `type size_t with the number of bytes (offset)` [[source](http://www.cplusplus.com/reference/cstddef/offsetof/)] * `type pointer` 在做 arithmetic 運算時會以 `sizeof(type)` 的作為一個單位的大小,因此要先將 `__pmember` 轉為 (char *) ==以 byte 為單位==進行運算 --- ### 除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處? * 還定義了 `list_empty`、`list_is_singular`、`list_splice`、`list_move` 以及 `#define` 了許多可以簡化程式碼的 Macro * [Manipulating Linked Lists](https://notes.shichao.io/lkd/ch6/#manipulating-linked-lists) * 在 `list.h` 定義常用 linked list 的實作可以避免在 kernel 其他地方用到 list 時實作重複的程式碼 ### `LIST_POISONING` 這樣的設計有何意義? `list_del()` 的註解中提到刪除時先將 `next/prev` 改為 predefined 的 memory address (systems prohibit access),之後使用這段記憶體時才能知道錯誤是發生在用到 poisoning 的記憶體 ### linked list 採用環狀是基於哪些考量? 環狀可以做到單向或雙向 linked list 做到的事情 ### `list_for_each_safe` 和 `list_for_each` 的差異在哪?"safe" 在執行時期的影響為何? ```c #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) ``` 在 `list_for_each` 裡面做 `list_del` 時 ([list_for_each.c](https://github.com/sysprog21/linux-list/blob/master/tests/list_for_each.c#L37)) 如果 * 有 `LIST_POISONING` 會改掉 `node->next` * 迴圈結束後 node->next 不是預期的 * `free(node)` * 迴圈結束後 node->next 是 undefined behavior ```c #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 = safe 可以確保 node 是正確的 ### for_each 風格的開發方式對程式開發者的影響為何? * Python - For loops * The Python `for` statement iterates over `the members of a sequence` in order, executing the block each time. * 不用額外寫結束的判斷、移動到下一步的程式碼 * 可以明確地知道迴圈是在 iterate 使用的資料結構 ### 程式註解裡頭大量存在 `@` 符號,這有何意義?你能否應用在後續的程式開發呢? * `@` 代表 parameter * Name of a function parameter. (No cross-referencing, just formatting.) * linux 的 [kernel doc](https://www.kernel.org/doc/html/v4.9/kernel-documentation.html) 使用 [Sphinx](https://github.com/sphinx-doc/sphinx) (Python 69.5%) 生成 * example from linux kernel doc ```c /** * foobar() - Brief description of foobar. * @arg: Description of argument of foobar. * * Longer description of foobar. * * Return: Description of return value of foobar. */ int foobar(int arg) ``` * [Doxygen](https://github.com/doxygen/doxygen) (C++ 85.4%) * example from Doxygen doc ```c /*! * Copies bytes from a source memory area to a destination memory area, * where both areas may not overlap. * @param[out] dest The memory area to copy to. * @param[in] src The memory area to copy from. * @param[in] n The number of bytes to copy */ void memcpy(void *dest, const void *src, size_t n); ``` ### `tests/` 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何? * `tests/` 目錄下的每個 .c 檔使用 `assert(expr)` 做測試 * `man assert`: If expression is false , assert() prints an error message to standard error and terminates the program by calling abort(3). * 針對 list.h 中的 `static inline` function 或 `#define` macro 做測試除了 ``` $ diff -y <(cat ./include/list.h | grep "static inline\|#define" | sed 's/#define //g' | sed 's/static inline void //g' | sed 's/static inline int //g' | cut -d'(' -f1 | tail -n+4 | sort) <(ls ./tests/ | tail -n+2 | cut -d'.' -f1 | sed 's/containerof/container_of/' | sort) container_of container_of INIT_LIST_HEAD < list_add list_add list_add_tail list_add_tail list_cut_position list_cut_position list_del list_del list_del_init list_del_init list_empty < list_entry list_entry list_first_entry list_first_entry list_for_each list_for_each list_for_each_entry list_for_each_entry list_for_each_entry_safe list_for_each_entry_safe list_for_each_safe list_for_each_safe LIST_HEAD | list_init-explicit > list_init-global > list_init-local list_is_singular list_is_singular list_last_entry list_last_entry list_move list_move list_move_tail list_move_tail list_splice list_splice list_splice_init list_splice_init list_splice_tail list_splice_tail list_splice_tail_init list_splice_tail_init ``` * Unit testing ([wiki](https://en.wikipedia.org/wiki/Unit_testing)) * Advantages * to isolate each part of the program and show that the individual parts are correct * finds problems early in the development cycle * bugs in the programmer's implementation * flaws or missing parts of the specification for the unit * Limitations and disadvantages * cannot evaluate every execution path in any but the most trivial programs * not catch integration errors or broader system-level errors * rigorous discipline is needed ### `tests/` 目錄的 unit test 可如何持續精進和改善呢? 加上註解來解釋個別 unit test 想達成的 specification --- ## 改寫 [Homework1: lab0](https://hackmd.io/s/BJp_jq-tm) ### queue.h 更動 ```c typedef struct ELE { char *value; struct list_head list; } list_ele_t; typedef struct { struct list_head q_head; size_t q_size; } queue_t; ``` * list_ele_t 改為存放 1. 字串 value 2. 存放 prec 和 next 的 list (`struct list_head`, `list.h`) * queue_t 改為存放 1. circular doubly-linked list 中的 q_head 2. queue 現在的大小 q_size ### queue.c 更動 #### q_new ```c INIT_LIST_HEAD(&(q->q_head)); ``` 使用 macro `INIT_LIST_HEAD` 初始化 empty list head (circular) #### q_free ```c struct list_head *li = NULL, *lis = NULL; list_ele_t *item; list_for_each_safe(li, lis, &(q->q_head)) { item = list_entry(li, list_ele_t, list); if (item->value != NULL) { free(item->value); item->value = NULL; } list_del(&(item->list)); free(item); item = NULL; } ``` 使用 macro `list_for_each_safe`、`list_entry` 來進行 free 的動作 #### q_insert_head、q_insert_tail ```c list_ele_t *item = malloc(sizeof(list_ele_t)); /* insert head */ list_add_tail(&(item->list), &(q->q_head)); /* insert tail */ list_add_tail(&(item->list), &(q->q_head)); ``` 使用 macro `list_add`、`list_add_tail` 進行 insert :::info * cppcheck 指出之後會用到的 item 沒有被 free 掉 * 暫時在 `queue.c` 加上 `// cppcheck-suppress memleak`、修改 `pre-commit.hook` 加上 `--inline-suppr` 到 `CPPCHECK_OPTS` ``` $ cppcheck queue.c Checking queue.c... Checking queue.c: INTERNAL... [queue.c:106]: (error) Memory leak: item [queue.c:153]: (error) Memory leak: item Checking queue.c: LIST_POISONING... Checking queue.c: __GNUC__... Checking queue.c: __cplusplus... ``` ::: #### q_remove_head ```c list_ele_t *item = list_first_entry(&(q->q_head), list_ele_t, list); ... list_del(&(item->list)); free(item); ... ``` 使用 macro `list_first_entry` 取得 queue 的第一個 element,`list_del` 更新 list 中指標的連接 #### q_reverse ```c /* iterate the queue to reverse each node's next and prev */ struct list_head *li = NULL, *lis = NULL, *tmp = &(q->q_head); list_for_each_safe(li, lis, &(q->q_head)) { li->prev = lis; li->next = tmp; tmp = li; } q->q_head.prev = q->q_head.next; q->q_head.next = tmp; ``` 以 macro `list_for_each_safe` 對 list 中 (屬於 queue) 的 node 反轉其 prev、next,最後更改 q_head 的 prev、next ### qtest.c 更動 #### show_queue ```c ... struct list_head *e = &(q->q_head); ... if (ok && !list_empty(&(q->q_head)) && cnt < qcnt) { list_ele_t *item; list_for_each(e, &(q->q_head)) { item = list_entry(e, list_ele_t, list); if (cnt < big_queue_size) report_noreturn(vlevel, cnt == 0 ? "%s" : " %s", item->value); cnt++; ok = ok && !error_check(); } } ... if (e == &(q->q_head)) { if (cnt <= big_queue_size) report(vlevel, "]"); else report(vlevel, " ... ]"); } ... ``` 改用 list_for_each 來 traverse list 直到 `node == (head)` 時停止 #### 其他 * 將 `q->head->value` 改為 `list_first_entry(&(q->q_head), list_ele_t, list)->value` * 將 `q->head == NULL` 改為 `list_empty(&(q->q_head))` ### mytest.sh ```bash #!/bin/bash n=$(wc -l cities.txt | cut -d' ' -f1) ih=$(cat cities.txt | head -n$n | cut -d',' -f1 | sed 's/ /_/g' | sed 's/^/ih /g') it=$(cat cities.txt | head -n$n | cut -d',' -f2 | sed 's/ /_/g' | sed 's/^/it /g') rh=$(for (( i=0; i<$n; i++ )); do echo rh; done) SECONDS=0 cat <<EOF | ./qtest &> /dev/null option fail 0 option malloc 0 new $ih $it size reverse size $rh show free quit EOF echo "echo \$? = $(echo $?)" echo "Elapsed $SECONDS seconds." ``` ``` $ ./mytest.sh echo $? = 0 Elapsed 1006 seconds. ``` 先將 qtest.c 第 309、449、453-456 行 report 的部分註解掉,再以 cities.txt 為測資丟入 qtest