--- tags: linux kernel --- # 2023q1 Homework1 (lab0) contributed by < [aa12551](https://github.com/aa12551/lab0-c.git) > ## 作業要求 - [ ] 修改 queue.[ch] 和連帶的檔案,測試後用 Git 管理各項修改,要能滿足 $ make test 自動評分系統的所有項目 - [ ] 研讀 Linux 核心原始程式碼的 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 並嘗試引入到 `lab0-c` 專案,比較你自己實作的 merge sort 和 Linux 核心程式碼之間效能落差 - [ ] 確保 `qtest` 提供的 `web` 命令得以搭配上述佇列實作使用,目前一旦網頁伺服器啟動後,原本處理命令列操作的 `linenoise` 套件就無法使用,請提出改善機制 - [ ] 在 `qtest` 提供新的命令 `shuffle`,允許藉由 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作,需要以統計的原理來分析你的實作,探究洗牌的「亂度」 - [ ] 在 `qtest` 中執行 `option entropy 1` 並搭配 `ih RAND 10` 一類的命令,觀察亂數字串的分布,並運用「資訊與熵互補,資訊就是負熵」的觀念,設計一組新的命令或選項,得以在 `qtest` 切換不同的 PRNG 的實作 (可選定 Xorshift),搭配統計分析工具,比較亂數產生器 (如 Xorshift vs. 內建) 的品質 - [ ] 研讀論文[〈Dude, is my code constant time?〉](https://eprint.iacr.org/2016/1123.pdf),解釋本程式的 “simulation” 模式是如何透過以實驗而非理論分析,達到驗證時間複雜度,需要解釋 [Student’s t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) 及程式實作的原理。 ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz CPU family: 6 Model: 158 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 9 CPU max MHz: 3800.0000 CPU min MHz: 800.0000 BogoMIPS: 5599.85 ``` ## 改進 `queue.c` ### 結構 - 在程式開始時,<s>會創建一個 `queue_chain_t` 的結構</s> :::warning 思考上述表達是否合理,結構體並非「程式開始時建立」,其實結構體在描述一段連續記憶體區塊的佈局 (layout)。改進你的漢語表達,應要足以彰顯你的專業。 :notes: jserv ::: ```c typedef struct { struct list_head head; int size; } queue_chain_t; ``` - 而在 `list_head` 中,為兩個指向 `list_head` 的指標,分別會指向前一個節點及後一個節點 ```c struct list_head { struct list_head *prev; struct list_head *next; }; ``` - 當我們下 `new` 指令時,即會創建一個 list ,此時就會在 `queue_chain_t` 的 `head` 指向一個 `queue_context_t` ,此結構將會儲存此 list 的相關資訊,`q` 為指向此 list 的 element ,此實再創健一個 list 時 , `chain` 會指向下一個 list 的 `queue_context_t` ```c typedef struct { struct list_head *q; struct list_head chain; int size; int id; } queue_contex_t; ``` - list 的每一個 element 則是一個 `element_t` 結構,`value` 儲存資料,`list` 指向上一個與下一個 element ```c typedef struct { char *value; struct list_head list; } element_t; ``` - 由上面的結構可以知道,所有的結構皆以 `list_head` 連接,這樣的好處是可以將指標的類型都統一,但會無法知道此指標指向的結構是哪一個,且無法直接存取結構內的參數,需要利用特定巨集來存取參數 ### list 的巨集與函式介紹 - INIT_LIST_HEAD(head) : 對 list 的 `next` 和 `prev` 初始化 - list_entry(node, type, member) : 存取 `list_head` 指向結構的其他參數 - list_for_each(node, head) : 走過 list 的所有節點 - list_for_each_entry_safe (entry, safe, l, list) : 在可能會刪除節點的運算中,也可以順利的走完 list 的所有節點 (使用 safe 先儲存下一個節點的位置) - list_add(struct list_head *node, struct list_head *head) : 增加節點在 head 前面 - list_add_tail(struct list_head *node, struct list_head *head) : 增加節點在 head 後面 - list_del(struct list_head *node) : 移除 (非刪除) node 指向的節點 ### q_new - 需要確認是否有成功分配記憶體,並初始化 list ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(*head)); if (!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` ### q_free - 走遍整個 list,因為有刪除節點的運算,使用 `list_for_each_entry_safe` 確保可以走遍整個 list - 如果傳入的指標為 NULL,不做任何事情 ```c void q_free(struct list_head *l) { if (!l) return; element_t *entry = NULL, *safe = NULL; list_for_each_entry_safe (entry, safe, l, list) { q_release_element(entry); } free(l); } ``` ### q_insert_head - 此函式需要負責完整複製字串到 `list` ,且負責分配記憶體。 - 需要檢查 `head` 是否為 `NULL` 以及在 malloc 新的 `element_t` 及 `element_t` 的成員`value` 是否有成功 - 需要注意在字串結尾需要有 `\0` ```c bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *newnode = malloc(sizeof(element_t)); if (!newnode) return false; newnode->value = malloc(sizeof(char) * (strlen(s) + 1)); if (!newnode->value) { free(newnode); return false; } memcpy(newnode->value, s, strlen(s) + 1); list_add(&newnode->list, head); return true; } ``` :::warning insert head 在 trace-17-complexity 的測試中沒有過關 ::: ### q_insert_tail 與 `q_insert_head` 的實作相同,僅將 `list_add` 改為 `list_add_tail` ```c bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *newnode; if (!(newnode = malloc(sizeof(element_t)))) return false; if (!(newnode->value = malloc(strlen(s) + 1))) { free(newnode); return false; } memcpy(newnode->value, s, strlen(s) + 1); list_add_tail(&newnode->list, head); return true; } ``` :::warning insert tail 在 trace-17-complexity 的測試中沒有過關 ::: ### q_remove_head - `sp` 由外部分配記憶體 - 如果 `sp` 的長度大於 `bufsize` ,只複製 `bufsize` 的尺寸到 `sp` - 需要注意在字串結尾需要有 `\0` ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *removeNode = list_entry(head->next, element_t, list); size_t len = strlen(removeNode->value) + 1; len = (len > bufsize) ? bufsize : len; if (sp) { memcpy(sp, removeNode->value, len); sp[len - 1] = '\0'; } list_del(head->next); return removeNode; } ``` :::warning 在一般的模式時,會檢查 `sp` 是否有被分配記憶體,不過在 `simulation` 模式時,不會確保 `sp` 的記憶體有被分配,會造成 segmentation fault ,因此需要額外增加判斷 `sp` 是否有被有效的分配到記憶體 ::: ### q_remove_tail - 與 `q_remove_head` 的實作一樣,僅更改移除的方向 ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *removeNode = list_entry(head->prev, element_t, list); size_t len = strlen(removeNode->value) + 1; len = (len > bufsize) ? bufsize : len; if (sp) { memcpy(sp, removeNode->value, len); sp[len - 1] = '\0'; } list_del(head->prev); return removeNode; } ``` ### q_size - 將整個 `list` 走遍,並計算途中有幾個節點 ```c int q_size(struct list_head *head) { struct list_head *iterator = NULL; if (!head) return 0; int count = 0; list_for_each (iterator, head) count++; return count; } ``` ### q_delete_mid - 因為之後的函式會常需要從 list 移除節點並釋放空間,以 `q_delete_element` 完成移除節點並釋放空間 ```c void q_delete_element(element_t *node) { list_del(&node->list); q_release_element(node); } ``` - 使用快慢指標找出中間節點並刪除 ```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; while (fast != head && fast->next != head) { fast = fast->next->next; slow = slow->next; } q_delete_element(list_entry(back, element_t, list)); return true; } ``` - 而因為我們使用的是 doubly linked list , 可以使用兩個指標分別由前後找,當前後的指標指向一樣的節點或是只差一個節點時,即為中間節點,可以減少 traverse 的次數,將以上的程式碼改為如下 ```c bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) return false; struct list_head *front = head->next, *back = head->prev; while (front != back && front->next != back) { front = front->next; back = back->prev; } q_delete_element(list_entry(back, element_t, list)); return true; } ``` ### q_delete_dup ```c bool q_delete_dup(struct list_head *head) { if (!head) return false; if (list_empty(head)) return true; element_t *entry, *safe; bool delete = false; list_for_each_entry_safe (entry, safe, head, list) { if (&safe->list == head) { if (delete) { list_del(&entry->list); q_release_element(entry); } return true; } if (strcmp(entry->value, safe->value) == 0) { list_del(&entry->list); q_release_element(entry); delete = true; continue; } if (delete) { list_del(&entry->list); q_release_element(entry); delete = false; } } return true; } ``` ### q_swap - 兩兩節點交換,交換完後再往下兩個節點交換 ```c void q_swap(struct list_head *head) { if (!head) return; struct list_head *iterator = head->next; while (iterator != head && iterator->next != head) { char *temp = list_entry(iterator, element_t, list)->value; list_entry(iterator, element_t, list)->value = list_entry(iterator->next, element_t, list)->value; list_entry(iterator->next, element_t, list)->value = temp; iterator = iterator->next->next; } } ``` ### q_reverse - 將所有節點本身的 `prev` 和 `next` 互換,即可得到一個反轉的 `list` ```c void q_reverse(struct list_head *head) { if (!head) return; struct list_head *iterator = head->prev; while (iterator != head) { q_reverse_pointer(iterator); iterator = iterator->next; } q_reverse_pointer(head); } ``` - 而因為在 `q_reverse` 和 `q_reverseK` 皆需要將節點本身的 `next` 和 `prev` 互換,因此增加一個函式完成此功能 ```c void q_reverse_pointer(struct list_head *head) { struct list_head *temp = head->next; head->next = head->prev; head->prev = temp; } ``` ### q_reverseK - 先計算 list 的長度,即可知道要做幾組反轉 (len / k) 組 ```c void q_reverseK(struct list_head *head, int k) { if (!head) return; struct list_head *local_head = head; struct list_head *iterator = head->next; int len = q_size(head); for (int j = 0; j < (len / k); j++) { for (int i = 0; i < k; i++) { q_reverse_pointer(iterator); iterator = iterator->prev; } local_head->next->next = iterator; iterator->prev->prev = local_head; struct list_head *temp = iterator->prev; iterator->prev = local_head->next; local_head->next = temp; local_head = iterator->prev; } } ``` ### q_sort - 實作 merge sort :::danger 不需要張貼完整程式碼,在開發紀錄中,你應該提及自己的想法、考量因素,對現有途徑的分析等等。程式碼僅是輔佐性質,列出關鍵者即可。 :notes: jserv ::: <s> ```c /* Function to split the doubly linked list into two halves */ void splitList(struct list_head *source, struct list_head **frontRef, struct list_head **backRef) { struct list_head *slowPtr = source; struct list_head *fastPtr = source->next; /* Move fastPtr by two and slowPtr by one */ while (fastPtr != NULL) { fastPtr = fastPtr->next; if (fastPtr != NULL) { slowPtr = slowPtr->next; fastPtr = fastPtr->next; } } /* Set the front and back halves of the list */ *frontRef = source; *backRef = slowPtr->next; (*backRef)->prev = NULL; slowPtr->next = NULL; } /* Function to merge two sorted doubly linked lists */ struct list_head *mergeLists(struct list_head *a, struct list_head *b) { struct list_head *result = NULL; /* Base case */ if (a == NULL) { return b; } else if (b == NULL) { return a; } char *s1 = list_entry(a, element_t, list)->value; char *s2 = list_entry(b, element_t, list)->value; /* Recursively merge the lists */ if (strcmp(s1, s2) < 0) { result = a; result->next = mergeLists(a->next, b); result->next->prev = result; } else { result = b; result->next = mergeLists(a, b->next); result->next->prev = result; } return result; } /* Function to perform merge sort on a doubly linked list */ void sort(struct list_head **headRef) { struct list_head *head = *headRef; struct list_head *a = NULL; struct list_head *b = NULL; /* Base case: if the list is empty or has only one element */ if (head == NULL || head->next == NULL) { return; } /* Split the list into two halves */ splitList(head, &a, &b); /* Recursively sort the two halves */ sort(&a); sort(&b); /* Merge the sorted halves */ *headRef = mergeLists(a, b); } /* Sort elements of queue in ascending order */ void q_sort(struct list_head *head) { if (!head || head->next == head) return; head->prev->next = NULL; sort(&(head->next)); struct list_head *iter = head; while (iter->next != NULL) { iter = iter->next; } head->next->prev = head; head->prev = iter; iter->next = head; } ``` </s> :::warning 在 trace-14-perf 的測試中,會產生 segmentation fault,測試過後發現當 list 長度過長時才會產生 segmentation fault (大概300000筆RAND資料時) ::: - 確實也覺得的寫的有點冗長,嘗試將程式縮減並與 `q_merge` 所用到的 `mergetwolists` 與此函式合併,並將原本 `splitList` 使用快慢指標的方法改為由頭尾去找中間節點 ```c void mergetwolists(struct list_head **l1, struct list_head **l2) { struct list_head *l1_iter = (*l1)->next; struct list_head *l2_iter = (*l2)->next; struct list_head *iter = *l1; while (l1_iter != *l1 && l2_iter != *l2) { char *s1 = list_entry(l1_iter, element_t, list)->value; char *s2 = list_entry(l2_iter, element_t, list)->value; if (strcmp(s1, s2) > 0) { iter->next = l2_iter; l2_iter->prev = iter; l2_iter = l2_iter->next; iter = iter->next; } else { iter->next = l1_iter; l1_iter->prev = iter; l1_iter = l1_iter->next; iter = iter->next; } } if (l1_iter == *l1) { iter->next = l2_iter; l2_iter->prev = iter; while (l2_iter->next != *l2) l2_iter = l2_iter->next; l2_iter->next = *l1; (*l1)->prev = l2_iter; } if (l2_iter == *l2) { iter->next = l1_iter; l1_iter->prev = iter; } } void splitlists(struct list_head **l1, struct list_head **l2) { struct list_head *front = (*l1)->next, *back = (*l1)->prev; while (front != back && front->next != back) { front = front->next; back = back->prev; } list_cut_position((*l2)->next, (*l1)->next, back); } /* Sort elements of queue in ascending order */ void q_sort(struct list_head *head) { /* Base case: if the list is empty or has only one element */ if (head == NULL || head->next == head || head->next == head->prev) { return; } struct list_head l1; INIT_LIST_HEAD(&l1); struct list_head *l1_ptr = &l1; /* Split the list into two halves */ splitlists(&head, &l1_ptr); /* Recursively sort the two halves */ q_sort(head); q_sort(l1_ptr); /* Merge the sorted halves */ mergetwolists(&head, &l1_ptr); } ``` - 這樣就可以過 trace-14-perf 的測試了 ### q_descend - 從 list 的後面開始往前 traverse ,當節點的數值比 `max` 大時,`max` 的數值改為當前節點的數值,如果節點的數值比 `max` 小時,則從 list 移除此節點並刪除 ```c int q_descend(struct list_head *head) { // https://leetcode.com/problems/remove-nodes-from-linked-list/ if (!head || head->next == head) return 0; element_t *entry = list_entry(head->prev, element_t, list); element_t *safe = list_entry(entry->list.prev, element_t, list); char *max = "\0"; // the smallest value of ASCII int count = 0; while (&entry->list != (head)) { if (strcmp(max, entry->value) > 0) { q_delete_element(entry); } else { max = entry->value; count++; } entry = safe; safe = list_entry(safe->list.prev, element_t, list); } return count; } ``` ### q_merge - 使用 `mergetwolists` 將兩個已排列好的 list 合併並重新排列,並將所有的 list 都合併到第一個 list ```c /* Merge all the queues into one sorted queue, which is in ascending order */ int q_merge(struct list_head *head) { // https://leetcode.com/problems/merge-k-sorted-lists/ if (!head) return 0; struct list_head *l1 = list_entry(head->next, queue_contex_t, chain)->q; struct list_head *iter = head->next; while (iter->next != head) { struct list_head *l2 = list_entry(iter->next, queue_contex_t, chain)->q; mergetwolists(&l1, &l2); list_entry(iter->next, queue_contex_t, chain)->q = NULL; iter = iter->next; } return q_size(l1); } ``` :::warning current score 95/100 :::