# 2023q1 Homework1 (lab0) contributed by < ```terry23304``` > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 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): 16 On-line CPU(s) list: 0-15 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 167 Model name: 11th Gen Intel(R) Core(TM) i9-11900 @ 2.50GHz Stepping: 1 CPU MHz: 2500.000 CPU max MHz: 5200.0000 CPU min MHz: 800.0000 BogoMIPS: 4992.00 Virtualization: VT-x L1d cache: 384 KiB L1i cache: 256 KiB L2 cache: 4 MiB L3 cache: 16 MiB NUMA node0 CPU(s): 0-15 ``` ## 開發過程 ## queue.[ch] ### q_new 用 `INIT_LIST_HEAD` 初始化 list head ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(struct list_head)); if (!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` ### q_free ```c void q_free(struct list_head *l) { struct list_head *li, *safe; list_for_each_entry_safe (li, safe, l, list { list_del(&li->list); q_release_element(li); } free(l); } ``` ### q_insert_head pre-commit hook 告知 memory leak ,查了一下才發現```strdup``` 可能會 ```malloc```失敗 ```c bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *first = malloc(sizeof(element_t)); if (!first) return false; first->value = strdup(s); if (!first->value) { free(first); return false; } list_add(&first->list, head); return true; } ``` ### q_insert_tail ```c bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *last = malloc(sizeof(element_t)); if (!last) return false; last->value = strdup(s); if (!last->value) { free(last); return false; } list_add_tail(&last->list, head); return true; } ``` ### q_remove_head remove: 斷開節點之間的鏈結,還存在記憶體中 :::warning 注意用語: 「[節點](https://dict.revised.moe.edu.tw/dictView.jsp?ID=90405)」 :notes: jserv ::: 如果 src 字元數(含'\0')比 bufsize 少,會把剩下未滿 bufsize 的部分通通補上 '\0' 如果 src 字元數(含'\0')比 bufsize 多,要自己補 '\0' ```c char *strncpy( char *dest, const char *src, size_t count ); ``` :::warning 想問為甚麼 q_remove_head 要用 ```sp``` 儲存被移除的值 是為了讓 q_insert_head 申請 memory 的速度更快嗎,但 q_insert_head 的 parameter 也沒有記憶體位置 > 將問題列在下方開發紀錄,並詳述你的疑慮,避免只說「儲存被移除的值」,應該具體指出相關程式碼,附上你的推測和實驗。 > :notes: jserv ::: >The reason for copying the string value to sp is that the value member of the element_t struct is an internal implementation detail of the linked list and is not intended to be accessed or modified directly by the caller of the q_remove_head function. >By providing a sp buffer as an argument to the function, the caller can obtain the string value of the removed element in a way that is safe and consistent with the function's API. ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *node = list_first_entry(head, element_t, list); list_del_init(&node->list); if(sp){ strncpy(sp, node->value, bufsize-1); sp[bufsize-1] = '\0'; } return 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 *node = list_last_entry(head, element_t, list); list_del_init(&node->list); if(sp){ strncpy(sp, node->value, bufsize-1); sp[bufsize-1] = '\0'; } return node; } ``` ### q_delete_mid 忘記是 doublly-linked list ,原本 while 判斷式這樣寫會導致 infinite loop ```c while(fast && fast->next){ fast = fast->next->next; slow = slow->next; } ``` ```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; struct list_head *fast = head->next; struct list_head *slow = fast; while(fast != head->prev && fast->next != head->prev){ fast = fast->next->next; slow = slow->next; } list_del (slow); q_release_element(container_of(slow, element_t, list)); return true; } ``` ### q_delete_dup 對每個節點檢查是否與下一個節點字串相等,若相等則刪除 :::spoiler ```c list_for_each_entry_safe (cur, next, head, list) { // current value equal to next value if (!strcmp(cur->value, next->value)) { list_del(&cur->list); q_release_element(cur); dup = true; } // delete last duplicate node else if (dup) { list_del(&cur->list); q_release_element(cur); dup = false; } } ``` ::: 原本的寫法在 ```next == head``` 的時候 ```next->value```會出錯,所以改成刪除前一個節點 ```c bool dup = false; // for last duplicate string list_for_each_safe (cur, next, head) { // current value equal to next value element_t *cur_node = list_entry(cur, element_t, list); if (cur->next == head) { if (dup) { list_del(&cur_node->list); q_release_element(cur_node); } break; } element_t *next_node = list_entry(cur->next, element_t, list); if (!strcmp(cur_node->value, next_node->value)) { list_del(&cur_node->list); q_release_element(cur_node); dup = true; } // delete last duplicate node else if (dup) { list_del(&cur_node->list); q_release_element(cur_node); dup = false; } } ``` ### q_swap 每兩點交換一次,利用 ```list_add``` 與 ```list_del``` 來更改節點位置 :::spoiler ```c void q_swap(struct list_head *head) { if (!head) return; struct list_head *cur, *next, *first = NULL; list_for_each_safe (cur, next, head) { if (first) { // add first behind second list_add(first, cur); first = NULL; } else { // remove first one first = cur; list_del(cur); } } // last node if (first) list_add(first, head->prev); } ``` ::: 改成直接用 `list_move` 將目前節點跟下一個節點互換,程式碼更精簡 ```c void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (!head || list_empty(head) || list_is_singular(head)) return; struct list_head *cur = head->next; while (cur != head && cur->next != head) { list_move(cur, cur->next); cur = cur->next; } } ``` ### q_reverse 把每個 node 移出再加到 head 後面 ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *cur, *next = NULL; list_for_each_safe (cur, next, head) list_move(cur, head); } ``` ### q_reverseK 用 `count` 來記錄要 revserse 的節點數 ```c void q_reverseK(struct list_head *head, int k) { // https://leetcode.com/problems/reverse-nodes-in-k-group/ if (!head || list_empty(head)) return; int count = 0; struct list_head *cur, *next = NULL; list_for_each_safe (cur, next, head) { count++; if (count == k) { count--; struct list_head *tmp = cur->prev; struct list_head *tmp_prev; while ((count--) > 0) { tmp_prev = tmp->prev; list_del(tmp); list_add(tmp, cur); cur = cur->next; tmp = tmp_prev; } count = 0; } } } ``` ### q_descend 反向迭代串列,紀錄最大的字串,若節點字串比最大字串小則移除 原本沒有紀錄 ```prev``` ,會導致 segmentation fault (dereferenced a NULL or invalid pointer) ```c int q_descend(struct list_head *head) { // https://leetcode.com/problems/remove-nodes-from-linked-list/ if (!head || list_empty(head)) return 0; struct list_head *cur = head->prev; struct list_head *prev = cur->prev; char *max = NULL; // iterate list reversly for (; cur != head; cur = prev) { element_t *entry = list_entry(cur, element_t, list); prev = cur->prev; if (!max || strcmp(entry->value, max) > 0) max = entry->value; else { list_del(cur); q_release_element(entry); } } return q_size(head); } ``` ### q_merge 當成單向的串列合併起來做排序後再接回 `prev` 變回雙向串列 ```c int q_merge(struct list_head *head) { if (!head || list_empty(head)) return 0; if (list_is_singular(head)) return list_entry(head, queue_contex_t, chain)->size; queue_contex_t *first = list_entry(head->next, queue_contex_t, chain); queue_contex_t *cur = NULL; list_for_each_entry (cur, head, chain) { if (cur == first) continue; list_splice_init(cur->q, first->q); first->size = first->size + cur->size; cur->size = 0; } q_sort(first->q); return first->size; } ``` ## 開啟 Address Sanitizer,修正 qtest 執行過程中的錯誤 ``` make clean make SANITIZER=1 make test ``` 執行後 make test 分數未改變 ## 運用 Valgrind 排除 qtest 實作的記憶體錯誤 ```c +++ TESTING trace trace-13-malloc: # Test of malloc failure on new ==160726== 32 bytes in 1 blocks are still reachable in loss record 1 of 3 ==160726== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==160726== by 0x10CBCE: do_new (qtest.c:145) ==160726== by 0x10DDFA: interpret_cmda (console.c:181) ==160726== by 0x10E3AF: interpret_cmd (console.c:201) ==160726== by 0x10E7B0: cmd_select (console.c:610) ==160726== by 0x10F09C: run_console (console.c:705) ==160726== by 0x10D1EC: main (qtest.c:1223) ==160726== ==160726== 160 bytes in 5 blocks are possibly lost in loss record 2 of 3 ==160726== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==160726== by 0x10CBCE: do_new (qtest.c:145) ==160726== by 0x10DDFA: interpret_cmda (console.c:181) ==160726== by 0x10E3AF: interpret_cmd (console.c:201) ==160726== by 0x10E7B0: cmd_select (console.c:610) ==160726== by 0x10F09C: run_console (console.c:705) ==160726== by 0x10D1EC: main (qtest.c:1223) ==160726== ==160726== 224 bytes in 4 blocks are still reachable in loss record 3 of 3 ==160726== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==160726== by 0x10F110: test_malloc (harness.c:133) ==160726== by 0x10F515: q_new (queue.c:17) ==160726== by 0x10CC07: do_new (qtest.c:149) ==160726== by 0x10DDFA: interpret_cmda (console.c:181) ==160726== by 0x10E3AF: interpret_cmd (console.c:201) ==160726== by 0x10E7B0: cmd_select (console.c:610) ==160726== by 0x10F09C: run_console (console.c:705) ==160726== by 0x10D1EC: main (qtest.c:1223) ==160726== --- trace-13-malloc 0/6 ``` 發現 `do_new` 會出問題, 參考 [yanjiew](https://hackmd.io/@yanjiew/linux2023q1-lab0#Valgrind-%E8%88%87-Address-Sanitizer-%E8%A8%98%E6%86%B6%E9%AB%94%E6%AA%A2%E6%9F%A5) 的解決方式,把 `qtest.c` 中 `q_quit` 第一行的 `return true` 刪除,讓記憶體釋放正常執行 ## 論文閱讀: Dude, is my code constant time? 用兩 class data 做 statistical test ,若兩個 data 的 timing distribution 相等則為 constant time ### Measure execution time perform a leakage detection test on execution time. 再執行時期用兩類 data 測試兩個 timing distribution is statistically different or not. #### Class definition - first clas: fixed to a constant value might be chosen to trigger certain special corner-case(e.g. low weight input in arthmetic operations) ::: info low weight input: 較小的數值通常可以使用較少的位元(或較小的數據類型)來表示。使用較小的數據類型可以節省記憶體使用和運算時間,因此可以提高計算效率。 如計算 5 + 1000000,可以將 5 表示為一個 8 位元的整數(如 unsigned char),而將 1000000 表示為一個 32 位元或 64 位元的整數(如 unsigned int 或 unsigned long long)。 ::: - second class: chosen at randomfor each measturement #### cycle counters 現代處理器提供 cycle counter,可以更準確的測量執行時間 #### enviromental conditions 為了最小化環境變化帶來的影響,每項測試隨機選擇 class ### Apply post-processing 再做統計分析前可對各個 measurement 進行一些後處理 - cropping:典型的 timing distribution 呈現 [positive skewed](https://zh.wikipedia.org/wiki/%E5%81%8F%E5%BA%A6),可能是由於測量工具、主程式被 os 或其他外部活動中斷等原因引起的。在這種情況下可以裁剪或刪除過大或極端的 measurement。 - 高階預處理 ### Apply statistical test 第三步是應用統計檢驗來嘗試否定 NULL Hypothesis “兩個 timing distribution 相等”, 統計檢驗方法: - t-test:檢測平均值的等價性,測試失敗很意味著存在一個 first order timing information leakage。 - non-parametric test:優點是這些檢驗通常對潛在分佈做出較少的假設,缺點是它們可能收斂得更慢並且需要更多樣本。 ### lab0 dudect 缺陷 參考 [KHLee529](https://hackmd.io/@KHLee529/linux2023-lab0#lab0-%E4%B8%AD%E7%9A%84-dudect-%E5%AF%A6%E4%BD%9C) 的方法 在論文中的 post-processing 中有提到可以裁減或刪除過大的 measurement ,而在 dudect/constant.c 中的實作是頭尾少做 DROP_SIZE 次測量 cycle 數,要改成排序執行時間後把前後 DROP_SIZE 個執行時間去除後再比較分布 ```c qsort(exec_times, N_MEASURES, sizeof(int64_t), cmp); memset(exec_times, 0, DROP_SIZE); memset(&exec_times[N_MEASURES - DROP_SIZE], 0, DROP_SIZE); ``` ## 在 qtest 提供新的命令 shuffle 原本只把選出來的 node 移到串列最後,但用 qtest shuffle 了幾次感覺不夠亂 ```c for (int i = size; i > 0; i--){ int r = rand() % i; struct list_head *rand_node = head->next; for (int j = 0; j < r; j++) rand_node = rand_node->next; list_move_tail(rand_node, head); } ``` 把移到最後改成跟 `Fisher–Yates shuffle algo` 一樣交換亂數到的節點與最後的節點 ```c element_t *rand_entry = list_entry(rand_node, element_t, list); element_t *last_entry = list_entry(last, element_t, list);\ char *tmp = rand_entry->value; rand_entry->value = last_entry->value; last_entry->value = tmp; ``` 參考 do_merge 及 do_reservse_K 在 do_descend、do_merge 等有做檢查是否正確執行函式功能的函式才需要 return ok ```c static bool do_shuffle(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); return false; } if (!current || !current->q) { report(3, "Warning: Calling shuffle on null queue"); return false; } error_check(); set_noallocate_mode(true); if (exception_setup(true)) q_shuffle(current->q); exception_cancel(); set_noallocate_mode(false); q_show(3); return !error_check(); } ``` ## github workflow make test 達到 100 分後 github action 還是會卡在 Run webfactory/ssh-agent@v0.7.0 ,參考 [commit 紀錄](https://github.com/sysprog21/lab0-c/commit/5fcff8eba8886e52023062ce1fa373915fe06aed)後加上 `continue-on-error: true` 就可以看到皮卡丘