# 2023q1 Homework1 (lab0) contributed by < [Welly0902](https://github.com/Welly0902) > 本作業參考 [zeddyuu](https://hackmd.io/@zeddyuu/2023lab0) 同學及 [paul90317](https://hackmd.io/@paul90317/linux2023-spring-lab0) 同學 :::danger 注意書寫規範:中英文間用一個半形空白字元區隔。 :notes: jserv ::: ## 作業要求 :::info - [x] 完成實作 queue.c 所有功能 - [x] `make test` 取得 100 分 - [ ] 研讀 `lib/list_sort.c` 並將其加入專案中 - [ ] 實作新的命令 `shuffle` - [ ] 完成 entropy 相關命令 / 選項 - [ ] 研讀論文[〈Dude, is my code constant time?〉](https://eprint.iacr.org/2016/1123.pdf)並且解釋 [Student’s t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) 及程式實作的原理 - [ ] 使用 Valgrind 排除 qtest 實作的記憶體錯誤 - [ ] 開啟 Address Sanitizer,修正 qtest 執行過程中的錯誤 ::: ## 環境設置 使用 WSL2 Ubuntu 22.04 * Perf 在 WSL2上使用: Perf 在 WSL2上需另外下載[WSL2-Linux-Kernel](https://github.com/microsoft/WSL2-Linux-Kernel) 再把 `/tools/perf` make 起來,過程如下: ```shell $ git clone https://github.com/microsoft/WSL2-Linux-Kernel --depth 1 $ cd WSL2-Linux-Kernel/tools/perf $ make -j8 $ sudo cp perf /usr/local/bin ``` * 修改 `kernel.perf_event_paranoid` 的方式: 在 `/etc/sysctl.conf` 中加入 `kernel.perf_event_paranoid = -1` 修改完成後 `sudo sysctl -p` 即修改完成 * Perf 測試使用: ```shell $ perf record ./example $ perf report ``` ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 46 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 20 On-line CPU(s) list: 0-19 Vendor ID: GenuineIntel Model name: 12th Gen Intel(R) Core(TM) i7-12700 CPU family: 6 Model: 151 Thread(s) per core: 2 Core(s) per socket: 10 Socket(s): 1 Stepping: 2 BogoMIPS: 4224.00 ``` ## 開發過程 Q: 如何實際檢驗所寫 function 對 queue 的運行狀況? A: 修改完程式後執行 `make` 再運用 `./qtest` 去模擬各個 function 的運行狀態 目標: 利用 list.h 中[定義好的 API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html) ### q_new ```c /* Create an empty queue */ struct list_head *q_new() { struct list_head *head = malloc(sizeof(*head)); if (head) INIT_LIST_HEAD(head); return head; } ``` ### q_free ```c /* Free all storage used by queue */ void q_free(struct list_head *l) { if (!l || list_empty(l)) { free(l); return; } element_t *entry = NULL, *safe = NULL; list_for_each_entry_safe (entry, safe, l, list) { list_del(&entry->list); q_release_element(entry); } free(l); } ``` ### q_insert_head ```c /* Insert an element at head of queue */ bool q_insert_head(struct list_head *head, char *s) { if(!head) return false; element_t *entry = malloc(sizeof(*entry)); if (!entry) return false; size_t len = strlen(s) + 1; entry->value = malloc(len * sizeof(char)); if (!entry->value) { free(entry); return false; } memcpy(entry->value, s, len); INIT_LIST_HEAD(&entry->list); list_add(&entry->list, head); return true; } ``` ### q_insert_tail ```c /* Insert an element at tail of queue */ bool q_insert_tail(struct list_head *head, char *s) { if(!head) return false; element_t *entry = malloc(sizeof(*entry)); if (!entry) return false; size_t len = strlen(s) + 1; entry->value = malloc(len * sizeof(char)); if (!entry->value) { free(entry); return false; } memcpy(entry->value, s, len); INIT_LIST_HEAD(&entry->list); list_add_tail(&entry->list, head); return true; } ``` ### q_remove_head Q: 為何 romove element 需要 return element_t 型態的 pointer? ```c /* Remove an element from head of queue */ element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) { return NULL; } element_t *element = list_first_entry(head, element_t, list); if (sp && bufsize > 0) { strncpy(sp, element->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(&element->list); return element; } ``` strncpy: 將 source 的前 num 個字元從 source 複製到 destination。 `char * strncpy ( char * destination, const char * source, size_t num );` 需注意要預留 null terminator `\0` 的位置。 ### q_remove_tail `list_first_entry` 換成 `list_first_entry` 即可找到 tail ```c /* Remove an element from tail of queue */ element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) { return NULL; } element_t *element = list_last_entry(head, element_t, list); if (sp && bufsize > 0) { strncpy(sp, element->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(&element->list); return element; } ``` ### q_size ```clike= int q_size(struct list_head *head) { if (!head || list_empty(head)){ return 0; } int len = 0; struct list_head *node; list_for_each (node, head) { len++; } return len; } ``` ### q_delete_mid `list_for_each_entry_safe` delete 後需要break ```c bool q_delete_mid(struct list_head *head) { // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ if (!head || list_empty(head)){ return false; } int half = q_size(head)/2; element_t *node, *t; int cnt = 0; list_for_each_entry_safe(node, t, head, list){ if (cnt++ == half){ list_del(&node->list); q_release_element(node); break; } } return true; } ``` ### q_delete_dup 根據 list.h 註解,`list_for_each_entry_safe(entry, safe, head, member)` 中的 `safe` 參數為當前節點的下一個節點,先記錄下一個節點的位置以避免在刪除節點時發生錯誤。 ```c 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; } element_t *node, *t; bool flag = false; list_for_each_entry_safe(node, t, head, list){ // head is the start and end of the linked list // strcmp = 0 when strings are equal if (node->list.next != head && strcmp(node->value, t->value) == 0) { list_del(&node->list); q_release_element(node); flag = true; } else if (flag) { list_del(&node->list); q_release_element(node); flag = false; } } return true; } ``` 運用 `flag` 去紀錄是否重複,以刪除最後一個重複節點。 ### q_swap 運用 `list_move(node, head)` ,可以用 `list_move(node, node->next)` 來達到交換節點的效果。 `list_move` 會進行兩個動作: `list_del` 及 `list_add` ,以下面是意圖來呈現如何交換: :::warning 此處再以graphviz改進 ::: ``` list_del(node): x n1 <-> n2 <-> n3 n1 n2 <-> n3 | | => | | node node->next node node->next ``` ```graphviz digraph g { label="Before list_del" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr1[shape=plaintext, label="node", fontname="Arial"] ptr2[shape=plaintext, label="node->next", fontname="Arial"] a [label="{<ref1> | <data> n1 | <ref2>}", color=red]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] } ``` ```graphviz digraph g { label="After list_del" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr1[shape=plaintext, label="node", fontname="Arial"] ptr2[shape=plaintext, label="node->next", fontname="Arial"] a [label="{<ref1> | <data> n1 | <ref2>}",color=6]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] } ``` `list_add` 的部分在 `list.h` 中為: ```clike= static inline void list_add(struct list_head *node, struct list_head *head) { struct list_head *next = head->next; //1 next->prev = node; //2 node->next = next; //3 node->prev = head; //4 head->next = node; //5 } ``` 因此 `list_add(node, node->next)` 為: ```graphviz digraph g { label="struct list_head *next = head->next; //1" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr1[shape=plaintext, label="node", fontname="Arial"] ptr2[shape=plaintext, label="node->next", fontname="Arial"] ptr3[shape=circle label="next", color=6] a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] ptr3 -> c:data:s[color=red] } ``` ```graphviz digraph g { label="next->prev = node; //2" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr1[shape=plaintext, label="node", fontname="Arial"] ptr2[shape=plaintext, label="node->next", fontname="Arial"] ptr3[shape=circle label="next"] a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] ptr3 -> c:data:s } ``` ```graphviz digraph g { label="node->next = next; //3" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr1[shape=plaintext, label="node", fontname="Arial"] ptr2[shape=plaintext, label="node->next", fontname="Arial"] ptr3[shape=circle label="next"] a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; a:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] ptr3 -> c:data:s } ``` ```graphviz digraph g { label="node->prev = head; //4" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr1[shape=plaintext, label="node", fontname="Arial"] ptr2[shape=plaintext, label="node->next", fontname="Arial"] ptr3[shape=circle label="next"] a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; a:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] ptr3 -> c:data:s } ``` ```graphviz digraph g { label="head->next = node; //5" // fontname="Courier New" rankdir=LR; node [shape=record, colorscheme=reds9]; ptr2[shape=plaintext, label="node->next", fontname="Arial"] ptr1[shape=plaintext, label="node", fontname="Arial"] ptr3[shape=circle label="next"] b [label="{<ref1> | <data> n2 | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; b:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red]; a:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:w[weight = 100, style = invis]; a:ref1:c -> c:data:w[weight = 100, style = invis]; a:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false,style=invis]; c:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // c:ref1:c -> b:data:w[weight = 100, style = invis]; // b:ref1:c -> a:data:w[weight = 100, style = invis]; c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed,style=invis]; ptr1 -> a:data:n[color=blue] ptr2 -> b:data:n[color=blue] ptr3 -> c:data:s } ``` ``` list_add(node, node->next): n1 n2 <-> n3 | | node node->next //head //1// next | n1 n2 <-> n3 | | node node->next //2// node->next next | | n2 n1<-n3 | | ^ | node | --------------- //3// node->next next | | n2 n1<->n3 | | ^ | node | ----------------- //4// node->next next | | n2 <- n1<->n3 | | ^ | node | ----------------- //5// node->next next | | n2 <-> n1<->n3 | node ``` 由上過程可見,兩個 node 前後 swap了,程式碼為下: ```c /* Swap every two adjacent nodes */ void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (!head || list_empty(head)){ return; } for (struct list_head *node = head->next; node != head && node->next != head; node = node->next) { list_move(node, node->next); } } ``` ### q_reverse 用 `list_for_each_safe` 走訪每個節點,走訪該節點時從 list 中刪除再加到 head 下一個即可達到 list reverse 的效果。 考量第一個 iteration 不會對 list 造成任何變動,加一個變數 flag 來跳過第一個 iteration。 ```c /* Reverse elements in queue */ void q_reverse(struct list_head *head) { if (!head || list_empty(head)) { return; } struct list_head *node, *t; bool flag = true; list_for_each_safe (node, t, head) { if (flag) {flag = false; continue;} list_del(node); list_add(node, head); } } ``` ### q_reverseK 此為每 k 個元素做一次 reverse。可利用上一個 `q_reverse` 每走過k個元素用 `list_cut_position` 拆分成剩餘 list 中前方 k 個要 reverse 的,以及後方剩餘還沒做的。再將切出來的前 k 個 cut 用 `q_reverse` 做 reverse。最後再以 `list_splice_init` 接到目前 `tmp_head` 的前方。達成 [reverse](https://leetcode.com/problems/reverse-nodes-in-k-group/) 的效果。 ```clike= void q_reverseK(struct list_head *head, int k) { // https://leetcode.com/problems/reverse-nodes-in-k-group/ if (!head || list_empty(head)) { return; } struct list_head *node, *t, *tmp_head = head; struct list_head cut; INIT_LIST_HEAD(&cut); int counter = 0; list_for_each_safe (node, t, head) { counter++; // every k element do reverse if (counter == k) { list_cut_position(&cut, tmp_head, node); q_reverse(&cut); list_splice_init(&cut, tmp_head); // reset count counter = 0; // record the start of the list to be dealed with tmp_head = t->prev; } } return; } ``` ### q_sort `merge` 的部分取兩 list 的節點出來比大小: ```c static inline void merge(struct list_head *head, struct list_head *l1, struct list_head *l2) { while (!list_empty(l1) && !list_empty(l2)) { if (strcmp(list_entry(l1->next, element_t, list)->value, list_entry(l2->next, element_t, list)->value) < 0) { list_move_tail(l1->next, head); } else { list_move_tail(l2->next, head); } } if (!list_empty(l1)) { list_splice_tail_init(l1, head); } else { list_splice_tail_init(l2, head); } } ``` 再來將 input linked list 切分為二後,用前面寫好的 `q_sort` 排好,再進行 merge: ```clike= /* Sort elements of queue in ascending order */ void q_sort(struct list_head *head) { if (!head || q_size(head) < 2) { return; } // split the target linked list to two list for merging struct list_head *fast = head, *slow = head; do { slow = slow->next; fast = fast->next->next; } while (fast != head && fast->next != head); LIST_HEAD(left); LIST_HEAD(right); list_splice_init(head, &right); // get left and right list list_cut_position(&left, &right, slow); //sort left and right list and ready for merging q_sort(&left); q_sort(&right); // merge merge(head, &left, &right); } ``` Q: `merge` function 中初始為何是l1->next,而不是l1? ### q_descend 此實作若節點右側有值比他大,則當下點刪除。思路可利用 doubly linked list 雙向的特性,從又往左走訪各節點。以一個變數記錄當下最大,小於即刪除來達到相同效果。 ```c /* Remove every node which has a node with a strictly greater value anywhere to * the right side of it */ int q_descend(struct list_head *head) { // https://leetcode.com/problems/remove-nodes-from-linked-list/ if (!head || list_empty(head)) { return 0; } element_t *entry = list_entry(head->prev, element_t, list); element_t *np = list_entry(entry->list.prev, element_t, list); char *curr_max = entry->value; while (&entry->list != head) { if (strcmp(entry->value, curr_max) < 0) { list_del(&entry->list); q_release_element(entry); } else { curr_max = entry->value; } // move the pointer backward entry = np; np = list_entry(entry->list.prev, element_t, list); } return q_size(head); } ``` ### q_merge 此實作的 input 為一個 linked list,每個節點連著一個 queue。故透過 while 迴圈每次合成 `floor((q_count + 1) / 2) ` 個 queue。 ```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 || list_empty(head)) { return 0; } int q_count = q_size(head); while (q_count > 1) { // fast pointer for merging struct list_head *fast = head->next; // slow pointer for recording the position for the final result queue list struct list_head *slow = head->next; for (int i = 0; i < q_count / 2; i++) { // create a head for the temp merging list LIST_HEAD(temp_head); merge(&temp_head, container_of(fast, queue_contex_t, chain)->q, container_of(fast->next, queue_contex_t, chain)->q); list_splice_tail(&temp_head, container_of(slow, queue_contex_t, chain)->q); fast = fast->next->next; slow = slow->next; } if (q_count & 1) list_splice_tail_init(container_of(fast, queue_contex_t, chain)->q, container_of(slow, queue_contex_t, chain)->q); q_count = (q_count + 1) / 2; } return q_size(container_of(head->next, queue_contex_t, chain)->q); } ```