# 2022q1 Homework1 (lab0) contributed by < `raysun0729` > ###### tags: `linux2022` ## 實驗環境 ```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): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 2 Core(s) per socket: 2 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 61 Model name: Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz Stepping: 4 CPU MHz: 1752.500 CPU max MHz: 2700.0000 CPU min MHz: 500.0000 BogoMIPS: 4389.82 Virtualization: VT-x L1d cache: 64 KiB L1i cache: 64 KiB L2 cache: 512 KiB L3 cache: 3 MiB NUMA node0 CPU(s): 0-3 ``` ## queue.h ![](https://i.imgur.com/rwrAdKi.png) ### 結構體說明 ```c struct list_head { struct list_head *prev; struct list_head *next; }; ``` `struct list_head` 為 `list.h` 中定義的結構體,這個結構體中包含了 `prev` 與 `next` 兩個定義於 `list_head` 的指標,分別藉由指向前、後一個節點,以便實作出 circular doubly-linked list。 ```c typedef struct { char *value; struct list_head list; } element_t; ``` `struct element_t` 為 `queue.h` 中定義的結構體,而在這個結構體中,除了定義 `value` 來儲存資料之外,也定義了結構體 `list_head` 的變數,並讓 `list_head` 中定義的 `prev` 以及 `next` 分別指向前、後一個佇列節點中的 `list`,以便在佇列中的各個節點間移動。 ## queue.c ### q_release_element ```c= void q_release_element(element_t *e) { free(e->value); free(e); } ``` ### q_new ```c= struct list_head *q_new() { struct list_head *q = malloc(sizeof(struct list_head)); if (q ! NULL) { INIT_LIST_HEAD(q); } return q; } ``` * ```INIT_LIST_HEAD(struct list_head *list)```用來進行list_head的初始化 * 為什麼要檢查q ! NULL? 根據[GNU C Library](https://www.gnu.org/software/libc/manual/html_node/Basic-Allocation.html) ```malloc```會在 size 為 0 或是無法分配空間時回傳 null ### q_free ```c= void q_free(struct list_head *l) { if (!l) return; /* queue is NULL */ element_t *cur, *next; /* del, the element to be free. next, the next element of del */ list_for_each_entry_safe (cur, next, l, list) { list_del(&cur->list); q_release_element(cur); } free(l); return; } ``` * 釋放 queue 前務必先檢查 queue 是否為 NULL。之後,先釋放 queue 上的每個 element 所用的空間(包含字串 allocate 的),再釋放掉 queue 本身。 * q_free要釋放佇列的所有空間,先用```list_for_each_entry_safe```走訪佇列中所有節點 * ```list_del```將節點移出佇列,```q_release_element```用以釋放空間 * 最後,釋放佇列```l``` ### q_insert_head ```c= bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; /* queue 不應該為 NULL */ element_t *node = malloc(sizeof(element_t)); if (!node) return NULL; /* could not allocate space */ int len = strlen(s) + 1; node->value = malloc(len); if (!node->value) { free(node); return NULL; /* could not allocate space */ } memcpy(node->value, s, len); INIT_LIST_HEAD(&node->list); list_add(&node->list, head); /* add element to queue head return true; } ``` * 先建立一個新的 element_t。因為 element_t 裡的 value 也是指標,所以要供 value 一段空間來存字串,故使用了 ```strlen()``` 來計算需要多少 byte 來存 value。因為要把 '\0' 也算進去,所以要再 +1。 * 也就是以```malloc```取得字串```s```大小的記憶體空間,若失敗則釋放```node```的空間 * 配置完 value 需要的空間後就將字串複製過去,```memcpy()``` 從原本的指標 s 複製到新的指標 value 。 * 最後透過```INIT_LIST_HEAD```進行節點初始化,及用```list_add```將節點加入佇列的頭 ### q_insert_tail ```c= bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; /* queue is NULL */ element_t *node = malloc(sizeof(element_t)); if (!node) return NULL; /* could not allocate space */ int len = strlen(s) + 1; node->value = malloc(len); if (!node->value) { free(node); return NULL; /* could not allocate space */ } memcpy(node->value, s, len); INIT_LIST_HEAD(&node->list); list_add_tail(&node->list, head); return true; } ``` * 與q_insert_head大致相同,差別僅在於這邊利用```list_add_tail```將節點加入佇列尾巴 ### q_remove_head ```c= element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || q_size(head) == 0) return NULL; element_t *node = list_entry(head->next, element_t, list); list_del(head->next); if (sp) { strncpy(sp, node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return node; } ``` * Remove 之前,首先要檢查 queue 是否為空,以及參數中的 sp 是否為具有空間的 buffer。q->head 指到的 element 是移除的目標,根據要求,先把目標中儲存字串複製到 sp 中,調整 q->head 指到的地方後,再釋放原本 q->head 的 element 所使用的空間。 * 如果佇列不為空,利用`list_first_entry()`取得佇列的第一個元素 node,用`list_del()`將這個 node 移除 * 再使用 `strncpy` copied `bufsize - 1` 個字元至 sp,最後回傳移除的節點node。 * 程式改善: 將`list_entry()`改為`list_first_entry(head, element_t, list)` `list_del()`改為`list_del_init(&node->list)` ### q_remove_tail ```c= element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || q_size(head) == 0) return NULL; element_t *node = list_entry(head->prev, element_t, list); //ptr redirect list_del(head->prev); // to copy a string if (sp) { strncpy(sp, node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return node; } ``` * 與前題類似,差別在於本題利用```list_entry```取得last node再將其以```list_del```自佇列刪除 * 程式改善: 以`list_last_entry()`可以直接取得list中的last element取代`list_entry()` ```c= element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || q_size(head) == 0) return NULL; element_t *node = list_last_entry(head, element_t, list); if (sp) { strncpy(sp, node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(&node->list); return node; } ``` ### q_size ```c= int q_size(struct list_head *head) { if (!head || list_empty(head)) return 0; struct list_head *node; int size = 0; list_for_each (node, head) ++size; return size; } ``` * 參考[作業要求](https://hackmd.io/@sysprog/linux2022-lab0#%E7%89%9B%E5%88%80%E5%B0%8F%E8%A9%A6)用```list_empty``` api,如果佇列為空,直接回傳0,否則透過```list_for_each```走訪所有節點,每經過一點將size+1 ### q_delete_mid ```c= bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) return false; struct list_head *front = head->next; struct list_head *back = head->prev; while (front != back) { back = back->prev; if (front == back) break; front = front->next; } list_del(front); q_release_element(list_entry(front, element_t, list)); return true; } ``` * 想法為藉由兩個指標*front, *back,一個從前往後走,一個從後往前走,當`front == back`(strlen is odd)時,即已走到中點,再將其刪去即可;若`front->next == back`(長度為偶數),刪去front指標 ### q_delete_dup ```c= bool q_delete_dup(struct list_head *head) { if (!head || list_empty(head)) return false; bool flag = false; element_t *cur, *safe; list_for_each_entry_safe (cur, safe, head, list) { if (&safe->list != head && !strcmp(cur->value, safe->value)) { list_del(&cur->list); q_release_element(cur); flag = true; } else if (flag) { list_del(&cur->list); q_release_element(cur); flag = false; } return true; } ``` * 以```list_for_each_entry_safe```來traverse每個節點 * 用```strcmp```比較當前的節點與下一個節點之value是否相同,如果相同會return 0,否則return 非0 * 若value相同且目前節點不是最後一個,則刪除目前節點。 * 以```flag```紀錄走到的節點是否與已刪除之節點有相同之value * 更新版本:因為list已排序,所以直接比較相鄰的點是否相同 ```c= bool q_delete_dup(struct list_head *head) { if (!head || list_empty(head)) return false; element_t *cur, *safe; list_for_each_entry_safe (cur, safe, head, list) { if (&safe->list != head && !strcmp(cur->value, safe->value)) { list_del(&(cur->list)); q_release_element(cur); } } return true; } ``` ### q_swap ```c= void q_swap(struct list_head *head) { struct list_head *prev, *cur; list_for_each_safe (prev, cur, head) { list_move_tail(cur, prev); cur = prev->next; } } ``` * ```list_move_tail```可直接將2個節點交換位置 ### q_reverse ```c= void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *cur = head; struct list_head *next = cur->next; do { cur->next = cur->prev; cur->prev = next; cur = next; next = cur->next; } while (cur != head); } ``` * 想法為traverse整個佇列,將每個節點的prev及next交換,即完成整個佇列的reverse ### q_sort * Linux Kernel API 中已有`list_sort`,該 function implements `merge_sort`,所以複雜度為O(nlg(n)) ```c= void q_sort(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) return; list_sort(NULL, head, sort_comp); } ``` * 因為quicksort的複雜度可能到O(n^2),所以這邊要用Mergesort來實作。參考[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/YA7RMqd6RE2UL8OmmjU9PQ?view)中案例研討的題目解答 ```c= /* * Sort elements of queue in ascending order * No effect if q is NULL or empty. In addition, if q has only one * element, do nothing. */ struct list_head *sep_list(struct list_head *head); struct list_head *merge_list(struct list_head *left, struct list_head *right); void q_sort(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) return; struct list_head *new_head = head->next; list_del(head); new_head = sep_list(new_head); list_add_tail(head, new_head); } struct list_head *sep_list(struct list_head *head) { if (head == head->next) return head; struct list_head *fast = head; struct list_head *mid = head->prev; while (fast != mid && fast->next != mid) { fast = fast->next; mid = mid->prev; } struct list_head *left = head; struct list_head *right = mid; struct list_head *node = left->prev; if (list_is_singular(left)) { list_del_init(left); list_del_init(right); } else { left->prev = right->prev; left->prev->next = left; node->next = right; right->prev = node; } return merge_list(sep_list(left), sep_list(right)); } struct list_head *merge_list(struct list_head *left, struct list_head *right) { struct list_head *head = NULL; struct list_head *tmp = NULL; left->prev->next = NULL; right->prev->next = NULL; while (left || right) { if (!right || (left && strcmp(list_entry(left, element_t, list)->value, list_entry(right, element_t, list)->value) < 0)) { if (!tmp) { head = tmp = left; left = left->next; if (left) left->prev = tmp->prev; INIT_LIST_HEAD(tmp); } else { tmp = left; left = left->next; if (left) left->prev = tmp->prev; list_add_tail(tmp, head); } } else { if (!tmp) { head = tmp = right; right = right->next; if (right) right->prev = tmp->prev; INIT_LIST_HEAD(tmp); } else { tmp = right; right = right->next; if (right) right->prev = tmp->prev; list_add_tail(tmp, head); } } } return head; } ```