--- tags: linux2022 --- # 2022q1 Homework1 (lab0) contributed by < `HScallop` > ## 作業說明與要求 > [K01: lab0](https://hackmd.io/@sysprog/linux2022-lab0) ## 實驗環境 ```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): 12 On-line CPU(s) list: 0-11 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 158 Model name: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz Stepping: 10 CPU MHz: 800.218 CPU max MHz: 4100.0000 CPU min MHz: 800.0000 BogoMIPS: 4399.99 Virtualization: VT-x L1d cache: 192 KiB L1i cache: 192 KiB L2 cache: 1.5 MiB L3 cache: 9 MiB NUMA node0 CPU(s): 0-11 ``` ## 開發過程 ### q_new * Create empty queue. * Return NULL if could not allocate space. ```c struct list_head *q_new() { struct list_head *q = malloc(sizeof(struct list_head)); if (q) { INIT_LIST_HEAD(q); } return q; } ``` 一開始用 `LIST_HEAD` 宣告一個 list_head 後,用pointer指向他,但發現會有 scope 的問題,所以後來改成使用 malloc 配置記憶體。 ### q_free * Free all storage used by queue. ```c void q_free(struct list_head *l) { if (!l) { return; } element_t *entry = NULL, *next = NULL; list_for_each_entry_safe (entry, next, l, list) { free(entry->value); free(entry); } free(l); } ``` 用 `list_for_each_entry_safe` 一個一個找到記憶體位址後,釋放記憶體。 ### q_insert_head * Attempt to insert element at head of queue. * Return true if successful. * Return false if q is NULL or could not allocate space. * Argument s points to the string to be stored. * The function must explicitly allocate space and copy the string into it. ```c bool q_insert_head(struct list_head *head, char *s) { if (!head) { return false; } element_t *e = malloc(sizeof(element_t)); if (!e) { return false; } e->value = strdup(s); if (!e->value) { free(e); return false; } list_add(&e->list, head); return true; } ``` 照著註解的要求寫。查如何複製字串時,找到 `strdup` 。因為實作有使用 malloc ,使用上要手動 `free` ,避免 memory leak 。 ### q_insert_tail * Attempt to insert element at tail of queue. * Return true if successful. * Return false if q is NULL or could not allocate space. * Argument s points to the string to be stored. * The function must explicitly allocate space and copy the string into it. ```c bool q_insert_tail(struct list_head *head, char *s) { if (!head) { return false; } element_t *e = malloc(sizeof(element_t)); if (!e) { return false; } e->value = strdup(s); if (!e->value) { free(e); return false; } list_add_tail(&e->list, head); return true; } ``` 與 `q_insert_head` 極其類似,只需要把後面改成 `list_add_tail` 。 ### q_remove_head * Attempt to remove element from head of queue. * Return target element. * Return NULL if queue is NULL or empty. * If sp is non-NULL and an element is removed, copy the removed string to *sp ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) { return NULL; } element_t *e = list_first_entry(head, element_t, list); if (sp) { strncpy(sp, e->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(&e->list); return e; } ``` 判斷是否為空後,使用 `list_first_entry` 找到第一個 element 的位址,照著註解的要求複製給 `*sp` ,最後 `list_del` remove list node 。 ### q_remove_tail ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) { return NULL; } element_t *e = list_last_entry(head, element_t, list); if (sp) { strncpy(sp, e->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_del(&e->list); return e; } ``` 基本上跟 `q_remove_head` 一樣,頭改尾。 ### q_size * Return number of elements in queue. * Return 0 if q is NULL or empty. ```c int q_size(struct list_head *head) { if (!head || list_empty(head)) { return 0; } struct list_head *node; int count = 0; list_for_each (node, head) { count++; } return count; } ``` 用 `list_for_each` 一個一個去數,看有幾個。 ### q_delete_mid * Delete the middle node in list. * The middle node of a linked list of size n is the * ⌊n / 2⌋th node from the start using 0-based indexing. * If there're six element, the third member should be return. * Return true if successful. * Return false if list is NULL or empty. ```c bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) { return false; } struct list_head *fast, *slow; fast = slow = head->next; while (fast != head && fast->next != head) { fast = fast->next->next; slow = slow->next; } list_del(slow); q_release_element(list_entry(slow, element_t, list)); return true; } ``` 參考 [leetcode discussion](https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/discuss/1807699/2-pointer-method-in-C) 作法,使用兩個 pointer 用兩倍速差去跑,快的跑到底時,慢的就是中間。 ### q_delete_dup * Delete all nodes that have duplicate string, leaving only distinct strings from the original list. * Return true if successful. * Return false if list is NULL. ```c { if (!head) { return false; } if (list_empty(head) || list_is_singular(head)) { return true; } element_t *node, *safe; bool dup = false; list_for_each_entry_safe (node, safe, head, list) { if ((node->list.next != head) && (strcmp(node->value, list_entry(node->list.next, element_t, list)->value) == 0)) { list_del(&node->list); q_release_element(node); dup = true; } else if (dup) { list_del(&node->list); q_release_element(node); dup = false; } } return true; } ``` 一直做不出來,後來參考 [laneser](https://hackmd.io/@laneser/linux2022_lab0) 的作法。 *改進方向:應該可以先找出所有重複的相同 element 一起拿掉。* ### q_swap * Attempt to swap every two adjacent nodes. ```c void q_swap(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) { return; } struct list_head *node; for (node = head->next; node != head && node->next != head; node = node->next) { struct list_head *tmp = node->next; list_del(tmp); list_add_tail(tmp, node); } } ``` 先排除空、只有一個的情況,接著兩個兩個交換。 ### q_reverse * Reverse elements in queue. * No effect if q is NULL or empty. * This function should not allocate or free any list elements. ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) { return; } struct list_head *node, *safe; list_for_each_safe (node, safe, head) { list_move(node, head); } } ``` 先排除不用移動的情況後,一個一個慢慢移動。 ### q_sort * 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 #### mergeTwoLists ```c struct list_head *mergeTwoLists(struct list_head *l1, struct list_head *l2) { struct list_head *head = NULL, **ptr = &head, **node; for (node = NULL; l1 && l2; *node = (*node)->next) { node = (strcmp(list_entry(l1, element_t, list)->value, list_entry(l2, element_t, list)->value) < 0) ? &l1 : &l2; *ptr = *node; ptr = &(*ptr)->next; } *ptr = (struct list_head *) ((u_int64_t) l1 | (u_int64_t) l2); return head; } ``` 使用 indirect pointer. #### mergesort ```c struct list_head *mergesort(struct list_head *head) { if (!head->next) { return head; } struct list_head *fast = head, *slow = head, *mid; while (true) { if (!fast || !fast->next) { break; } fast = fast->next->next; slow = slow->next; } mid = slow; slow->prev->next = NULL; struct list_head *l1 = mergesort(head), *l2 = mergesort(mid); return mergeTwoLists(l1, l2); } ``` #### q_sort ```c void q_sort(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) { return; } struct list_head *node = head->next, *tmp; head->prev->next = NULL; head->next = NULL; node = mergesort(node); tmp = head; tmp->next = node; while (tmp->next) { tmp->next->prev = tmp; tmp = tmp->next; } tmp->next = head; head->prev = tmp; } ``` 參考 [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C) 實作 (recursion method)。 *可以實作 iteration 部份* ## valgrind 用 MakeFile 裡面寫好的指令來測試 ```shell $ make valgrind ``` 結果: ```shell --- trace-17-complexity 0/5 --- TOTAL 95/100 make: *** [Makefile:68: valgrind] Error 1 ``` 回頭找 trace-17 ```shell +++ TESTING trace trace-17-complexity: # 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 Probably constant time ERROR: Probably not constant time ``` q_insert_head, q_remove_head 被 valgrind 認為可能不是 O(1) >目前還沒想出原因 valgrind 沒有發現 memory leak 所有輸出皆為: ```shell 160 bytes in 1 blocks are still reachable in loss record 30 of 30 ``` ## 記憶體分析 valgrind tool [Massif: a heap profiler](https://valgrind.org/docs/manual/ms-manual.html) > Massif is a heap profiler. It measures how much heap memory your program uses. This includes both the useful space, and the extra bytes allocated for book-keeping and alignment purposes. It can also measure the size of your program's stack(s), although it does not do so by default. The topic of valgrind user manual says it's a heap profiler, but the manual itself shows it can also measure the size of stack actually. A memory test trace is added. To observe heap, use the ih command. And use sort command to observe stack (currently implemented w/ recurrsive merge sort). >trace-massif.cmd ``` new ih RAND 1000 sort free quit ``` ```shell $ valgrind --tool=massif --stacks=yes ./qtest -f traces/trace-massif.cmd $ massif-visualizer massif.out.<pid> ``` ## signal | signal | description | default action | | ------- | --------------------- | -------------- | | SIGABRT | Process abort signal. | | | SIGFPE | | | | SIGILL | | | | SIGINT | | | | SIGSEGV | | | | SIGTERM | | | ## Why merge sort? 在 linux kernel 中, list_sort.c 為何使用 merge sort 實作,而不使用 quick sort ? 跟在教科書上用 array 的討論不同,在 list 不是連續記憶體,如果使用 merge sort 我們可以良好的運用 [locality of reference](https://en.wikipedia.org/wiki/Locality_of_reference) 的特性。 ### 實驗 ### 閱讀 [linux/lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) ## [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf) ### 運作方式 以下敘述論文中提供的實作步驟 `step1 measure execution time` 用 t-test 來偵測是否在運行時是 constant time 的行為,造成可能的 leakage 。 #### TODO LIST - [x] valgrind 測試 - [ ] valgrind 視覺化 (massif) - [x] 開發歷程說明 - [ ] tiny-web - [ ] 分析 console.c 研究 CS:APP RIO 套件 - [ ] 研究 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf) - [ ] 解決 vscode `git push` 卡住問題