--- tags: linux2022 --- # 2022q1 Homework1 (lab0) contributed by < [25077667](https://github.com/25077667) > > [作業要求](https://hackmd.io/@sysprog/linux2022-lab0) ## 實驗環境 ``` ➜ ~ neofetch; gcc -v -` scc@scc-lab .o+` ----------- `ooo/ OS: Arch Linux x86_64 `+oooo: Host: WS E500 G5_WS690T Rev 1.xx `+oooooo: Kernel: 5.16.10-arch1-1 -+oooooo+: Uptime: 25 mins `/:-:++oooo+: Packages: 1123 (pacman) `/++++/+++++++: Shell: zsh 5.8.1 `/++++++++++++++: Resolution: 1920x1080 `/+++ooooooooooooo/` Terminal: /dev/pts/0 ./ooosssso++osssssso+` CPU: Intel i7-8700 (12) @ 4.600GHz .oossssso-````/ossssss+` GPU: Intel CoffeeLake-S GT2 [UHD Graphics 630] -osssssso. :ssssssso. Memory: 639MiB / 31902MiB :osssssss/ osssso+++. /ossssssss/ +ssssooo/- `/ossssso+/:- -:/+osssso+- `+sso+:-` `.-/+oso: `++:. `-/+/ .` `/ Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/11.2.0/lto-wrapper Target: x86_64-pc-linux-gnu Chread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.2.0 (GCC) ``` * 過去 2020 年之[開發紀錄](https://hackmd.io/@25077667/sysprog2020-lab0) ## 開發過程 使用 Linux API list.h,第一手資料於 [The Linux Kernel API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html) ### q_new ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(struct list_head)); if (head) INIT_LIST_HEAD(head); return head; } ``` ### q_free 使用 Linux API list_for_each_safe,並且重用相同函式 `q_release_element` ```c void q_free(struct list_head *l) { if (!l) return; if (list_empty(l)) { free(l); return; } struct list_head *cur = NULL, *safe = NULL; list_for_each_safe (cur, safe, l) q_release_element(container_of(cur, element_t, list)); free(l); } ``` ### q_insert_head 由於 insert node 都是相同的程式碼, 因此定義 static 函式 `create_node` 及 `q_insert`: ```c static inline element_t *create_node(const char *s) { element_t *const new_node = (element_t *const) malloc(sizeof(element_t)); if (!new_node) return NULL; const size_t len = strlen(s); new_node->value = malloc(len + 1); if (!new_node->value) { free(new_node); return NULL; } strncpy(new_node->value, s, len); new_node->value[len] = 0; /* * NOTE: I think that use strndup is better, but the test_malloc hook * would make you failed in test_free here. * * new_node->value = strndup(s, len); */ return new_node; } ``` 但是這邊原先設計上,遇到一個問題: `strndup` 不會經過 Jserv 老師所 hook 的 `test_malloc` 所以在後續 `q_free` 做釋放時會出現對不上 `block_ele_t` 的不預期錯誤。 ::: info 改良方法,修改 harness.c 新增 strndup 函式,以支援 strndup 標準函式庫之==簡潔粗曠==操作。 ::: `q_insert` 函式使用 function pointer 方法,將函數抽象化為一操作方法之物件 (an object of operating method) $\lambda$ ,用 C 語言寫 Functional Programming. 詳細可以參考 [Functional Programming 風格的 C 語言實作](https://hackmd.io/@sysprog/c-functional-programming)。 ```c= static inline bool q_insert(struct list_head *head, char *s, void (*op)(struct list_head *, struct list_head *)) { if (!head) return false; element_t *new_node = create_node(s); if (!new_node) return false; op(&new_node->list, head); return true; } ``` 因此,前後這兩個 insert 操作,即可不論頭尾,變成一行。 ```c= bool q_insert_head(struct list_head *head, char *s) { return q_insert(head, s, list_add); } ``` ### q_insert_tail ```c= bool q_insert_tail(struct list_head *head, char *s) { return q_insert(head, s, list_add_tail); } ``` ### q_remove_head 同上,建立 `q_remove` 函式,將 remove node 操作合併。 ```c= static inline element_t *q_remove(struct list_head *head, char *sp, size_t bufsize, struct list_head *rm_node) { element_t *ele = list_entry(rm_node, element_t, list); if (sp && ele->value) { strncpy(sp, ele->value, bufsize); sp[bufsize - 1] = 0; } list_del(rm_node); return ele; } ``` ```c= element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; return q_remove(head, sp, bufsize, head->next); } ``` ### q_remove_tail ```c= element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; return q_remove(head, sp, bufsize, head->prev); } ``` ### q_size 遍歷所有節點(我記得我上次寫這作業時是 2020 年,這題要 $O(1)$ 時間複雜度,要修改 `element_t` 結構體,新增紀錄元素數量之變數) ```c= int q_size(struct list_head *head) { if (!head || list_empty(head)) return 0; size_t size = 0; struct list_head *p = NULL; list_for_each (p, head) ++size; return size; } ``` ### q_delete_mid ```c= bool q_delete_mid(struct list_head *head) { // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ if (!head || list_empty(head)) return false; struct list_head *fast = head->next, *slow = head->next; while (fast != head && fast->next != head) slow = slow->next, fast = fast->next->next; list_del(slow); q_release_element(list_entry(slow, element_t, list)); return true; } ``` 採用之前(2020)寫作經驗, fast/slow 模式,找出中點。 ### q_delete_dup ```c= bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head || list_empty(head)) return false; struct list_head *node = NULL, *safe = NULL; bool last_dup = false; list_for_each_safe (node, safe, head) { element_t *cur = list_entry(node, element_t, list); const bool match = node->next != head && !strcmp(cur->value, list_entry(node->next, element_t, list)->value); if (match || last_dup) { list_del(node); q_release_element(cur); } last_dup = match; } return true; } ``` ### q_swap 採用兩兩成一對的子問題,將整個 circular linked-list 分割分治。 而後觀摩 tinyynoob 的實做,驚為天人。 [參考程式碼](https://hackmd.io/@tinyynoob/linux2022q1-lab0) ### q_reverse list 反轉是常見考題,聽過很多次,但是沒寫過。 第一次直覺想法是,遍歷並交換 prev, next 指標。不料測試發現在「頭尾相接」的 circular linked-list 上會造成無窮迴圈。 所以改變思路,走訪時將期節點放到「頭」。 ```c= void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *cur = NULL, *safe = NULL; list_for_each_safe (cur, safe, head) list_move(cur, head); // put to beginning } ``` ### q_sort