--- tags: linux2022, linux --- # 2022q1 Homework1 (lab0) contributed by < `Andy240881` > > [作業要求](https://hackmd.io/@sysprog/linux2022-lab0) ## 實驗環境 ```shell $ gcc --version gcc (Ubuntu 6.5.0-2ubuntu1~18.04) 6.5.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 12 On-line CPU(s) list: 0-11 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 158 Model name: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz Stepping: 10 CPU MHz: 2267.463 CPU max MHz: 3201.0000 CPU min MHz: 800.0000 BogoMIPS: 6399.96 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 12288K NUMA node0 CPU(s): 0-11 ``` ## 開發過程 ### q_new * 檢查 malloc 是否成功。 * 觀摩同學 [laneser](https://hackmd.io/@laneser/linux2022_lab0) 的開發紀錄後發現可以使用 `INIT_LIST_HEAD()` 使得程式碼更加簡潔。 ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(struct list_head)); if (!head) { return NULL; } INIT_LIST_HEAD(head); return head; } ``` ### q_free * 檢查 l 是否為空。 * 利用 list.h 中的 `list_for_each_entry_safe()` 迭代刪除目標,並使用 queue.c 中既有的 `q_release_element` 釋放 queue 中的每個 node。 * queue 的 head 也要記得釋放空間。 ```c void q_free(struct list_head *l) { if (l == NULL) { return; } element_t *iter, *tmp; list_for_each_entry_safe (iter, tmp, l, list) q_release_element(iter); free(l); } ``` ### q_insert_head * 檢查 head 是否為空。 * 建立要插入的 node,並將 s 複製到對應欄位。 * 使用 `list_add` 將新的節點連結至 queue 的開頭。 ```cpp bool q_insert_head(struct list_head *head, char *s) { if (head == NULL) { return false; } element_t *item = malloc(sizeof(element_t)); if (item == NULL) { return false; } item->value = strdup(s); if (item->value == NULL) { q_release_element(item); return false; } list_add(&item->list, head); return true; } ``` ### q_insert_tail * 將 q_insert_head 中的 `list_add` 抽換成`list_add_tail`。 ```cpp bool q_insert_tail(struct list_head *head, char *s) { if (head == NULL) { return false; } element_t *item = malloc(sizeof(element_t)); if (item == NULL) { return false; } item->value = strdup(s); if (item->value == NULL) { q_release_element(item); return false; } list_add_tail(&item->list, head); return true; } ``` ### q_remove_head * 需要加上 `cppcheck-suppress nullPointer` 的註解來關閉 cppcheck 的 null pointer 錯誤。 * 一開始誤以為 queue 的每個節點皆是 `element_t` ,但事實上 head 是不含 value 欄位的 `list_head`,因此在使用 `container_of()` 時第一個參數要填 `head->next` 才會得到正確的節點位址。 * 使用 `list_del_init` 移除queue的第一個節點,並讓該節點的 prev 與 next 指向自己。 ```cpp element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { // cppcheck-suppress nullPointer element_t *q = container_of(head->next, element_t, list); if (!q) { return NULL; } list_del_init(head->next); if (sp != NULL) { strncpy(sp, q->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return q; } ``` ### q_remove_tail * 將 `q_remove_head` 中的操作對象 head->next (第一個節點) 換成 head->prev (最後一個節點) 即可。 ```cpp element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { // cppcheck-suppress nullPointer element_t *q = container_of(head->prev, element_t, list); if (!q) { return NULL; } list_del_init(head->prev); if (sp != NULL) { strncpy(sp, q->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return q; } ``` ### q_size ```cpp int q_size(struct list_head *head) { if (!head) return 0; int len = 0; struct list_head *li; list_for_each (li, head) len++; return len; } ``` ### q_delete_mid * 利用 q_size() 取得 queue 的長度。 * 使用 list.h 中的 `list_for_each_entry_safe()` 遍歷 queue 並刪除第 ⌊n / 2⌋ 個節點。 * 先 remove 節點後,再delete。 ```cpp bool q_delete_mid(struct list_head *head) { // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ int len = q_size(head); if (len == 0) return NULL; int i = 0; element_t *iter, *tmp; list_for_each_entry_safe (iter, tmp, head, list) if (i != len / 2 ) { i++; } else { list_del_init(&iter->list); q_release_element(iter); break; } return true; } ``` ### q_reverse ```cpp void q_reverse(struct list_head *head) { if (head == NULL) return; struct list_head *pre = head->prev; struct list_head *current = head; struct list_head *next = NULL; next = current->next; current->next = pre; current->prev = next; pre = current; current = next; while (current != head) { next = current->next; current->next = pre; current->prev = next; pre = current; current = next; } } ``` :::warning 思考 circular doubly-linked list 的特性,上述程式碼可更精簡,並善用 Linux List APIs :notes: jserv ::: * 利用 circular doubly-linked list 可以反向遍歷的特性,從最後一個節點開始,以反向將每個節點用 `list_move_tail` 放到 list 的最後面,達到 reverse 的效果。 ```cpp void q_reverse(struct list_head *head) { if (head == NULL) return; if (list_empty(head)) return; struct list_head *current = head->prev; struct list_head *next = NULL; next = current->prev; list_move_tail(current, head); current = next; while (current != head) { next = current->prev; list_move_tail(current, head); current = next; } } ``` :::warning 上方程式碼尚可更精簡,請繼續改進 :notes: jserv ::: * 參考同學 [kevinshieh0225](https://hackmd.io/@Masamaloka/2022q1-hw1#q_reverse) 的做法,使用 `do-while` 讓程式碼更加精簡。 * 加入 singular 的判斷,若 list 長度為 1 ,則不用 reverse 。 ```cpp void q_reverse(struct list_head *head) { if (head == NULL || list_empty(head) || list_is_singular(head)) return; struct list_head *current = head->prev; do{ struct list_head *next = current->prev; list_move_tail(current, head); current = next; }while (current != head); } ``` ### q_swap * 遍歷 list 的每個節點,且將節點倆倆交換(每個節點僅被交換一次)。 * while 迴圈的終止條件會分別因為list長度為奇數或偶數而有不同的情況。 ```cpp void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (!head || list_empty(head) || list_is_singular(head)) return; struct list_head *current = head->next; struct list_head *next = NULL; while (current != head && current->next != head) { next = current->next; list_move_tail(next, current); current = current->next; } } ``` ### q_delete_dup * 原本沒想清楚,只有單純比較相鄰的兩個節點,若相同則刪除,但沒有考慮到有兩個以上的節點重複的狀況。 * 參考同學 [laneser](https://hackmd.io/@laneser/linux2022_lab0)的做法,用 `flag` 紀錄是否有重複的情形發生。 * 遍歷 list 時,若 flag = 1 (代表自己與上個節點相同)或是與隔壁節點 value 相同,就會刪除目前遍歷到的節點。 ```cpp 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; if (list_is_singular(head)) return true; element_t *iter, *tmp; int flag = 0; list_for_each_entry_safe (iter, tmp, head, list) if ((iter->list.next != head) && !strcmp(iter->value, // cppcheck-suppress nullPointer list_entry(iter->list.next, element_t, list)->value)) { list_del(&iter->list); q_release_element(iter); flag = 1; } else if (flag) { list_del(&iter->list); q_release_element(iter); flag = 0; } return true; } ``` ### q_sort * 利用 linux 現有的 list_sort。 * TODO: 了解 linux 的 list_sort 原理。 ```cpp void q_sort(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) return; list_sort(NULL, head, sort_comp); } ```