# 2023q1 Homework1 (lab0) contributed by < [p96114175](https://github.com/p96114175) > :::danger 不要打錯自己的 GitHub 帳號名稱! :notes: jserv ::: ## 作業要求 參考自[2023 年 Linux 核心設計/實作課程作業 —— lab0](https://hackmd.io/@sysprog/linux2023-lab0/%2F%40sysprog%2Flinux2023-lab0-f) * 著手修改 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 9.4.0-1ubuntu1~20.04.1) 9.4.0 $ lscpu 架構: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 24 On-line CPU(s) list: 0-23 每核心執行緒數: 1 每通訊端核心數: 16 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 151 Model name: 12th Gen Intel(R) Core(TM) i9-12900F 製程: 2 CPU MHz: 2400.000 CPU max MHz: 5100.0000 CPU min MHz: 800.0000 BogoMIPS: 4838.40 虛擬: VT-x L1d 快取: 384 KiB L1i 快取: 256 KiB L2 快取: 10 MiB ``` ## q_new 實作 ```c struct list_head *q_new() { struct list_head *new = malloc(sizeof(*new)); if (!new) return NULL; INIT_LIST_HEAD(new); return new; } ``` :::danger 注意作業書寫規範:中英文間用一個半形空白字元區隔。 程式碼縮排亦有相關規範,請依循! :notes: jserv ::: :::success 好的 ::: 首先取得大小為 `list_head` 並指向分配到的記憶體的 `pointer` ,倘若新增失敗便返回 `NULL` ,成功的話就 `INIT_LIST_HEAD` ,最終返回新增好的 `list_head` :::danger 改進你的漢語表達。 :notes: jserv ::: :::success 好的 ::: ## q_free 實作 ```c void q_free(struct list_head *l) { if (!l) return; element_t *entry, *safe; list_for_each_entry_safe (entry, safe, l, list){ list_del(&entry->list); q_release_element(entry); } free(l); } ``` ## q_insert_head 實作 ```c bool q_insert_head(struct list_head *head, char *s) { if(!head) return false; element_t *new_element = malloc(sizeof(element_t)); if(!new_element) return false; size_t len = strlen(s) + 1; new_element->value = malloc(len * sizeof(char)); if(!new_element->value){ free(new_element); return false; } memcpy(new_element->value, s,len); list_add(&new_element->list, head); return true; } ``` 首先確認 `head` 是否為空,接著使用 `malloc` 得到 `new_element` ,再使用 `strlen` 取得s的 `size` 並加上1,加1的涵義為 `\0` ,再應用剛得到的 `len` 去分配大小為 `char` 的記憶體空間給 `new_element` 的 value。 第二步,將 `new_element->value` 的記憶體值複製給 `s` ,完成後將 `new_element` 插入 `head` 的開頭。 ## q_insert_tail 實作 ```c bool q_insert_tail(struct list_head *head, char *s) { if(!head) return NULL; element_t *new_element = malloc(sizeof(element_t)); if(!new_element) return NULL; size_t len = strlen(s) + 1; new_element->value = malloc(len * sizeof(char)); if(!new_element->value){ free(new_element); return NULL; } memcpy(new_element->value,s,strlen(s)+1); list_add_tail(&new_element->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||list_empty(head)) return NULL; return q_remove(list_first_entry(head, element_t, list), sp, bufsize); } ``` 此處我先用 `list_first_entry`,將 `head` 中第一個 `entry` 找出來後,再丟入 `q_remove` 進行 `remove` ## q_remove_tail 實作 ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { return q_remove(list_last_entry(head, element_t, list), sp, bufsize); } ``` 和 `q_remove_head` 差一點在於,使用了 `list_last_entry` 找出 `head` 中最後一個 `entry` 再進行 `remove` ## q_size 實作 ```c int q_size(struct list_head *head) { if (!head) return 0; int len = 0; struct list_head *cur; list_for_each (cur, head) len++; return len; } ``` 宣告變數名為 `len` 並給值為 `0`,透過 `list_for_each` 去 `iterate` 得到 `q_size` ## q_delete_mid 實作 ```c bool q_delete_mid(struct list_head *head) { if(!head||list_empty(head)) return NULL; struct list_head **indir = &head; for(struct list_head *cur = head->next; (*indir)->prev!=cur && (*indir)!=cur; indir = &(*indir)->prev){ cur = cur->next; } struct list_head *mid = (*indir)->prev; list_del(mid); q_release_element(list_entry(mid, element_t, list)); return true; } ``` 這裡使用了 `indir` 和 `cur`,分別從不同方向前進,最終找到 `mid` 並消除。 ## q_delete_dup 實作 ```c bool q_delete_dup(struct list_head *head) { if(!head||list_empty(head)) return NULL; element_t *cur, *safe; bool del_final = false; list_for_each_entry_safe (cur, safe, head, list){ if(cur->list.next!=head && strcmp(cur->value, safe->value) == 0){ list_del(&cur->list); q_release_element(cur); del_final = true; } else if(del_final){ list_del(&cur->list); q_release_element(cur); del_final = false; } } return true; } ``` 查看 list_for_each_entry_safe 的說明 > #define list_for_each_entry_safe(entry, safe, head, member) ,透過額外的 safe 紀錄每個迭代 (iteration) 所操作的節點的下一個節點,因此目前的節點可允許被移走,其他操作則同為不可預期行為 使用 `list_for_each_entry_safe` 進行 iterate,第一個 loop 時,先確認 `cur` 和 `safe` 值是否相等,若相等則刪除 `cur`,同時將 `del_final` 更改為 true,假設進入第二個 loop 時發現,`cur` 和 `safe` 不相同,則跳入 `else if(del_final)`,將重複的 `cur` 進行刪除 ## q_swap 實作 ```c void q_swap(struct list_head *head) { if(!head) return; struct list_head *node; list_for_each(node, head){ if(node->next == head) break; list_move(node, node->next); } } ``` 透過 `list_for_each` 對 `head` 進行 iterate,若 `node->next` 遇見 `head`,則跳出 loop。將 `node` 從原本的 linked list 移動到另一個 linked list `node->next` 的開頭 ## q_reverse 實作 ```c void q_reverse(struct list_head *head) { if(!head) return; struct list_head *node, *safe; list_for_each_safe(node, safe, head) list_move(node, head); } ``` 將 `node` 從原本的 linked list 移動到另一個 linked list `head` 的開頭 ## q_reverseK 實作 ```c void q_reverseK(struct list_head *head, int k) { if(!head) return; struct list_head *node, *safe, head_to, *head_from = head; int count = 0; INIT_LIST_HEAD(&head_to); list_for_each_safe(node, safe, head){ count++; if(count!=k){ list_cut_position(&head_to, head_from, node); q_reverse(&head_to); list_splice_init(&head_to, head_from); head_from = safe->prev; count=0; } } } ``` 建立一個 `count` 和初始化 `head_to`,在每次 iterate 中 count 進行累加,倘若 `count` 不等於 `K` 則進入 if 判斷式中。將從 `head_from` 的第一個節點到 node 間的一系列節點都移動到 `head_to` 上。其中`head_to` 必須是 empty 狀態,再將 `head_to` 進行 `q_reverse` 。將 `head_to` 的所有 node 都插入到 `head_from` 的開始。接著 `head_from` 指向 `safe` 的 `prev` ## q_sort 實作 **q_sort** ```c void q_sort(struct list_head *head) { if(!head || list_empty(head)) return; head->prev->next = NULL; head->next = q_divide(head->next); struct list_head *cur=head, *safe = head->next; while(safe){ safe->prev = cur; cur = safe; safe = safe->next; } cur->next = head; head->prev = cur; } ``` **q_divide** ```c struct list_head *q_divide(struct list_head *head) { if(!head->next) return head; struct list_head *l=head, *r=head->next, *left, *right; while(r && r->next){ r = r->next->next; l = l->next; } struct list_head *mid=l->next; l->next=NULL; left = q_divide(head); right = q_divide(mid); return merge_two(left, right); } ``` 透過 `l` 和 `r` 將 head 拆成兩段,再丟入 `merge_two` 中 **merge_two** ```c struct list_head *merge_two(struct list_head *left, struct list_head *right){ struct list_head head; struct list_head *cur=&head; if(!left && !right) return NULL; for(;left && right;cur = cur->next){ if(strcmp(list_entry(left, element_t, list)->value, list_entry(right, element_t, list)->value)<0){ (cur)->next = left; left = left->next; } else{ (cur)->next = right; right = right->next; } } if(left) (cur)->next = left; else if(right) (cur)->next = right; return head.next; } ``` 使用cur指向 `head` 的位址,當 `left` 和 `right` 都存在時,便進行 `left` 和 `right` 的值比較,若 `left` 小於 `right`,則將 `cur` 的 `next` 指向 `left`,`left` 向前推進一個,結束該 loop 前, `cur` 也向前推進一個,若 `left` 大於 `right` ,則將 `cur` 的 `next` 指向 `right` , `right` 向前推進一個,當 `left` 和 `right` 都不存在時,跳出 for loop。 若 `left` 存在, `cur` 的 `next` 指向 `left` ,若 `right` 存在, `cur` 的 `next` 指向 `right` ,最後返回 `head` 的 `next`。 ## q_descend 實作 ```c int q_descend(struct list_head *head) { if(!head) return false; struct list_head *a = head->prev, *b = a->prev; while(b!=head){ if(strcmp(list_entry(b, element_t,list)->value, list_entry(a, element_t, list)->value)<0){ list_del(b); } else { a=b; } b=b->prev; } return q_size(head); } ``` ## q_merge 實作 ```c int q_merge(struct list_head *head) { if(!head || list_empty(head)) return false; queue_contex_t *init = container_of(head->next, queue_contex_t, chain); struct list_head *cur = head->next->next; for(;cur !=head;cur=cur->next){ queue_contex_t *queue = container_of(cur, queue_contex_t, chain); list_splice_init(queue->q, init->q); queue->size = 0; } q_sort(init->q); init->size = q_size(init->q); return init->size; } ``` 原理為,先將全部的 queue 合併成為一個 queue , 接著再將合併好的 queue 放入 q_sort 進行排序 :::danger 注意作業書寫規範:中英文間用一個半形空白字元區隔。 程式碼縮排亦有相關規範,請依循! :notes: jserv ::: :::success 已修正,還請多多指教 ::: 老師在教材中有提到,[lab0-c](https://github.com/sysprog21/lab0-c) 已整合 [Valgrind](https://valgrind.org/) 來協助學員細緻地找出記憶體相關的問題,諸如 memory leak, buffer overflow, Dangling pointer 等等。使用方式如下: ``` $ make valgrind ``` ``` # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time ERROR: Probably not constant time or wrong implementation Probably constant time Probably constant time ==475928== ==475928== HEAP SUMMARY: ==475928== in use at exit: 0 bytes in 0 blocks ==475928== total heap usage: 625,085,604 allocs, 625,085,604 frees, 34,977,448,704 bytes allocated ==475928== ==475928== All heap blocks were freed -- no leaks are possible ==475928== ==475928== For lists of detected and suppressed errors, rerun with: -s ==475928== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) --- trace-17-complexity 0/5 --- TOTAL 89/100 ``` 從上圖結果中我們可以全部 heap blocks 都被釋放了,沒有 leak 存在的可能 ## 比較自己實作的 merge sort 和 Linux 核心程式碼之間效能落差