--- tags: linux2022 --- # 2022q1 Homework1 (lab0) contributed by < `stupidrara` > ## 實驗環境 ```shell $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 94 Model name: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz Stepping: 3 CPU MHz: 2600.000 CPU max MHz: 3500.0000 CPU min MHz: 800.0000 BogoMIPS: 5199.98 Virtualization: VT-x L1d cache: 128 KiB L1i cache: 128 KiB L2 cache: 1 MiB L3 cache: 6 MiB NUMA node0 CPU(s): 0-7 ```` ## 開發過程 ### q_new 建立新的「空」佇列。 ```c struct list_head *q_new() { struct list_head *newHead = malloc(sizeof(struct list_head)); if (newHead) { INIT_LIST_HEAD(newHead); } return newHead; } ``` 因為 `malloc` 失敗會回傳 `NULL` ,所以 `return newHead` 已經包含了判斷 `NULL` 的結果。 ### q_free 釋放佇列所佔用的記憶體。 ```c void q_free(struct list_head *l) { if (!l) return; struct list_head *now = l->next; element_t *toDel; while (now != l) { toDel = list_entry(now, element_t, list); now = now->next; q_release_element(toDel); } free(l); } ``` 若 `l` 為 `NULL` 則跳出函式,否則 `l->next` 一定存在。此時從 `now = l->next` 開始釋放,直到 `now == l` 時 break while(如果 `l->next` 一開始指向自己的話 `while` 會直接 break)。 * 更新使用 macro `list_for_each_entry_safe` 取代 `while` ```c void q_free(struct list_head *l) { if (!l) return; element_t *toDel, *safe; list_for_each_entry_safe (toDel, safe, l, list) { q_release_element(toDel); } free(l); } ``` ### q_insert_head 在佇列開頭 ( head ) 插入 ( insert ) 給定的新節點 (以 LIFO 準則)。 ```c bool q_insert_head(struct list_head *head, char *s) { if (!(head && s)) return false; element_t *newElement = malloc(sizeof(element_t)); if (!newElement) return false; size_t strSize = strlen(s); newElement->value = malloc(strSize + 1); if (!newElement->value) { free(newElement); return false; } newElement->value[strSize] = '\0'; strncpy(newElement->value, s, strSize); list_add(&newElement->list, head); return true; } ``` 先判斷 `head` 和 `s` 是否為 `NULL` ,皆存在時則嘗試配置 element 空間,若配置失敗則釋放並回傳 false 。更新:移除 `sizeof(char)`。 ### q_insert_tail 在佇列尾端 ( tail ) 插入 ( insert ) 給定的新節點 (以 FIFO 準則)。 ```c bool q_insert_tail(struct list_head *head, char *s) { if (!(head && s)) return false; element_t *newElement = malloc(sizeof(element_t)); if (!newElement) return false; size_t strSize = strlen(s); newElement->value = malloc(strSize + 1); if (!newElement->value) { free(newElement); return false; } newElement->value[strSize] = '\0'; strncpy(newElement->value, s, strSize); list_add_tail(&newElement->list, head); return true; } ``` 與 `q_insert_head` 相似。 ### q_remove_head 在佇列開頭 ( head ) 移去 ( remove ) 給定的節點。 ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *toRemove = list_entry(head->next, element_t, list); list_del(head->next); if (sp) { strncpy(sp, toRemove->value, bufsize - 2); sp[bufsize - 1] = '\0'; } return toRemove; } ``` 先判斷 `head` 是否為 `empty` 或 `NULL` ,再判斷 `sp` 是否需要複製。 ### q_relese_element 釋放特定節點的記憶體空間。 ```c void q_release_element(element_t *e) { free(e->value); free(e); } ``` ### q_size 計算目前佇列的節點總量。 ```c int q_size(struct list_head *head) { if (!head || list_empty(head)) return 0; int counter = 1; struct list_head *now = head->next->next; while (now != head) { ++counter; now = now->next; } return counter; } ``` 先使 `counter = 1` 和 `now = head->next->next`,這樣可以在佇列長度至少為 1 時,最小化計算量。 ### q_delete_mid 移走佇列中間的節點。 ```c bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) return false; struct list_head *slow = head->next, *fast = head->next->next; while (fast != head && fast != head->prev) { slow = slow->next; fast = fast->next->next; } list_del(slow); q_release_element(list_entry(slow, element_t, list)); return true; } ``` 使用龜兔賽跑演算法找出中間點。為了使計算量最小,可初始化 `slow = head->next, fast = head->next->next` ,讓 `while` 運作最少次。 ### q_delete_dup 在已經排序的狀況,移走佇列中具備重複內容的節點。 ```c bool q_delete_dup(struct list_head *head) { if (!head || list_empty(head)) return false; bool flag = false; element_t *now, *safe; list_for_each_entry_safe (now, safe, head, list) { if (&safe->list != head && !strcmp(now->value, safe->value)) { list_del(&now->list); q_release_element(now); flag = true; } else if (flag) { list_del(&now->list); q_release_element(now); flag = false; } } return true; } ``` 以自動狀態機來思考這題,如果遇到 `now->value` 和 `safe->value` 相同時,則把 `flag` 設定為 `true` ,此時開始刪除 `now` 節點,直到遇到 `now->value` 和 `safe->value` 不同後,判斷 `flag` 是否為 `true` 來判斷是否要離開狀態,離開時要刪除 `now` 節點。 ### q_swap 交換佇列中鄰近的節點。 ```c void q_swap(struct list_head *head) { struct list_head *prev, *now; list_for_each_safe (prev, now, head) { list_move_tail(now, prev); now = prev->next; } } ``` 先利用 `list_move_tail(now, prev)` 可以使兩節點交換位置,且 `now = prev->next` 使 traverse 速度變一般的兩倍,兩個效果合併後就是 swap 了。 ### q_reverse 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點。 ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *now, *safe, *temp; list_for_each_safe (now, safe, head) { temp = now->next; now->next = now->prev; now->prev = temp; } temp = now->next; now->next = now->prev; now->prev = temp; } ``` 先交換所有節點的 `prev` 和 `next` pointer ,最後再交換 `head` 的 `prev` 和 `next` pointer。 ### q_sort 以遞增順序來排序鏈結串列的節點。 ```c void q_sort(struct list_head *head) { int listSize = q_size(head); if (listSize < 2) return; // spilt every element by using local variable 'heads[]' struct list_head heads[listSize]; struct list_head *now = head->next, *temp; for (int i = 0; i < listSize; i++) { INIT_LIST_HEAD(&heads[i]); temp = now->next; list_add(now, &heads[i]); now = temp; } // merge for (int interval = 1; interval < listSize; interval *= 2) { for (int i = 0; i + interval < listSize; i += interval * 2) { struct list_head *L1 = heads[i].next; struct list_head *L2 = heads[i + interval].next; // merged 為目前完成排列的最尾端的 pointer struct list_head **node, *merged = &heads[i]; // 正式開始 merge for (node = NULL; L1 != &heads[i] && L2 != &heads[i + interval]; \ *node = (*node)->next) { node = strcmp(list_entry(L1, element_t, list)->value, \ list_entry(L2, element_t, list)->value) < 0 ? &L1 : &L2; merged->next = *node; (*node)->prev = merged; merged = merged->next; } // 處理未接完的 linked list merged->next = (L1 != &heads[i]) ? L1 : L2; merged->next->prev = merged; heads[i].prev = (L1 != &heads[i]) ? heads[i].prev : heads[i + interval].prev; heads[i].prev->next = &heads[i]; } } // merge 完的結果會存在 heads[0], 所以要把 pointer 還給 head heads[0].next->prev = head; heads[0].prev->next = head; head->next = heads[0].next; head->prev = heads[0].prev; } ``` 使用非遞迴實作 mergesort ,過程中遇到的 bug 都是忘記接回 doubly circular link 。實作過程參考了 [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) 的非遞迴作法,並使用了 indirect pointer `node` 來省下 `L1` `L2` 的分支。 但很可惜現在還不能看到皮卡丘,因為我使用 local variables 陣列 `heads` 來 spilt 所有 elements,而這樣會導致在 elements 數量過多時無法正常運作。 目前想到的解決辦法是利用 hierarchy 階級制度,把初始資料切成好幾份資料,並且建立額外的 `secondary_heads` 來指向切開來的資料,讓 local variable 陣列的大小可以壓在允許範圍內。優點是可以重複利用創造出來的 `heads` 來節省 stack 的空間,甚至還可以建立數層的 hierarchy 來極限壓低 stack 的空間,而且我認為可以降低 cache miss 或 page fault 的機率。