# 2023q1 homework1 (lab0)開發日誌 contributed by < `Urbaner3` > :::warning 注意 GitHub 帳號名稱。我已經幫你修改了。 > [name=yanjiew1] ::: :::info 感謝,其實我也很猶豫,那是我一開始設定的情形。 > [name=Urbaner] ::: ###### tags: `linux2022`,`linux kernel` ### Reviewed by `yanjiew1` - commit message 語意表達不太清楚,開頭沒有大寫。例如: [c7fe7ff](https://github.com/Urbaner3/lab0-c/commit/c7fe7ff5132087ae7d32f1ff08f01d1c5f757e25) ,`implem_t del_mid ` 可改為 `Implement q_delete_mid` 。 - Coding style 沒有符合規範,建議善用 `clang-format -i` 來協助調整 Coding style 。例如下方 `q_delete_dup` 的程式: ```c= if(strcmp(val1->value, val2->value) == 0){ list_del(val1); q_release_element(cur); break; } else printf("mulp is %c\n",mulp); ``` 5, 6 行 : `else` 和 `}` 應為同一行; 第 1 行: `)` 和 `{` 中間要有空格。可改為: ```c= if(strcmp(val1->value, val2->value) == 0) { list_del(val1); q_release_element(cur); break; } else printf("mulp is %c\n", mulp); ``` - 完成度不高,沒達到作業要求,但可以從共筆中看到你的努力。未來繼續努力,加油! ## 實驗環境 ```shell! (base) urbaner@urbaner:~$ gcc --version gcc (Ubuntu 12.2.0-3ubuntu1) 12.2.0 (base) urbaner@urbaner:~$ lscpu 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): 4 On-line CPU(s) list: 0-3 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i3-7100 CPU @ 3.90GHz CPU family: 6 Model: 158 Thread(s) per core: 2 Core(s) per socket: 2 Socket(s): 1 Stepping: 9 CPU(s) scaling MHz: 21% CPU max MHz: 3900.0000 CPU min MHz: 800.0000 BogoMIPS: 7799.87 Virtualization features: Virtualization: VT-x Caches (sum of all): L1d: 64 KiB (2 instances) L1i: 64 KiB (2 instances) L2: 512 KiB (2 instances) L3: 3 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-3 ``` ## 作業要求 > [lab0](https://hackmd.io/@sysprog/linux2023-lab0) [queue.c 介面](https://hackmd.io/@sysprog/linux2023-lab0/%2F%40sysprog%2Flinux2023-lab0-a#-%E6%94%B9%E5%AF%AB%E8%87%AA-CMU-%E9%9B%BB%E8%85%A6%E7%B3%BB%E7%B5%B1%E6%A6%82%E8%AB%96%E7%9A%84%E4%BD%9C%E6%A5%AD) - `q_new`: 建立新的「空」佇列; - `q_free`: 釋放佇列所佔用的記憶體; - `q_insert_head`: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則); ︙ (後續…參閱[queue.c 介面]) ## 開發過程 ### 簡介: 完成作業要求,得到一個完整可控制排序的介面,包含自動偵錯、結果輸出、測試。熟悉專案開發環境,多人一起合作,認識程式從編碼,執行過程中控制,錯誤停止相關的觀念及技術等。這份作業可以輸入多個文字節點,利用 linux 內核鏈結序列結構,依照開頭順序,做需要的處理,包含排序、刪除重複、交換、翻轉順序等等。如[例題:刪除重複](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/)。 [你所不知道的 C 語言:編譯器和最佳化原理篇](https://hackmd.io/@sysprog/c-compiler-optimization),背景知識回顧: The C++ Build Process Explained 此章節有 AST 產生之程序,以利於確認所有標頭檔,如下: ```shell! gcc -H -O0 -std=c99 -g -c -o queue.o queue.c ``` 參考作業: [laneser_2022q1](https://hackmd.io/@laneser/linux2022_lab0) ,[共筆示範](https://hackmd.io/@cccccs100203/linux2020-lab0) ### q_new 三步驟,配置空間、檢查開頭非空、控制指標。 :::warning 建議寫清楚一點,這樣子太文言看不懂 > [name=yanjiew1] ::: :::info 我放程式碼加上註解 > [name=Urbaner3] ::: ```cpp! struct list_head *q = malloc(sizeof(struct list_head)) // 配置空間 if (!list_empty(q)) // 檢查開頭非空 INIT_LIST_HEAD(q); //控制指標 return q; ``` ```diff @@ -16,7 +16,8 @@ { struct list_head *q = malloc(sizeof(struct list_head)); if (!list_empty(q)) - INIT_LIST_HEAD(q); + return NULL; + INIT_LIST_HEAD(q); return q; } ``` 改為 [`!q`寫法](https://github.com/yanjiew1/lab0-c/commit/0f09bf6ab87795c13f8aae2e6f773e48ce41eb0a) 通過 `make test` 測試13,原理待釐清。 ### q_free (1)element_t (2)q_release_element (3)list_for_each_entry_safe 經過兩次修改三處,閱讀並理解後,確認函數為如下: ```cpp void q_free(struct list_head *l) { if (l == NULL) return; element_t *n, *s; list_for_each_entry_safe (n, s, l, list) q_release_element(n); free(l); } ``` 老師舉 yanjiew 同學的例子,讓我另一個疑惑,free 到底作用在哪個結構更有興趣去探討了。 :::warning `free` 要作用在當初用 `malloc` 配置記憶體的結構上。故串列的頭是 `struct list_head` ,而串上去有存值的節點是 `element_t` 。 > [name=yanjiew1] ::: ### q_insert_head & tail ~~因為對list結構,只知道細節,目前當作規格一樣遵守,日後維護時一併觀察。~~ 越用越習慣了。 注意到需要用 strdup 函數,並將 insert 共用部份縮減為 part_ins 函式。 ```cpp element_t *part_ins(char *s) { element_t *new_ele = malloc(sizeof(element_t)); if (new_ele == NULL) return NULL; new_ele->value = strdup(s); if (new_ele->value == NULL) { free(new_ele); return NULL; } return new_ele; } bool q_insert_head(struct list_head *head, char *s) { if (head == NULL) return false; element_t *new_ele = part_ins(s); if (new_ele == NULL) return false; list_add(&new_ele->list, head); return true; } bool q_insert_tail(struct list_head *head, char *s) { if (head == NULL) return false; element_t *new_ele = part_ins(s); if (new_ele == NULL) return false; list_add_tail(&new_ele->list, head); return true; } ``` :::warning 可以想想 `q_insert_tail` 可以怎麼利用 `q_insert_head` 來達成。可以參考[我寫的](https://github.com/yanjiew1/lab0-c/blob/92d98c6902dcc681297d2485141a51508100f6a3/queue.c#L70)。 > [name=yanjiew1] ::: ### q_remove_head 很疑惑 q_remove_head 函數,其中的兩個參數,{ char *sp, size_t bufsize },還有 q_remove_head 程式碼的 macro 巨集 container_of,發現 jserv 之前有做說明 [container_of](https://hackmd.io/@sysprog/linux-macro-containerof) 附註於[你所不知道的 C 語言: linked list 和非連續記憶體(含目錄)](https://hackmd.io/@owlfox/BJkrdnExL/https%3A%2F%2Fhackmd.io%2Fs%2FSkE33UTHf#%E4%BD%A0%E6%89%80%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84-C-%E8%AA%9E%E8%A8%80-linked-list-%E5%92%8C%E9%9D%9E%E9%80%A3%E7%BA%8C%E8%A8%98%E6%86%B6%E9%AB%94) 另外 [linked list 案例實作](https://hackmd.io/@sysprog/c-linked-list#Linked-list-%E5%9C%A8-Linux-%E6%A0%B8%E5%BF%83%E5%8E%9F%E5%A7%8B%E7%A8%8B%E5%BC%8F%E7%A2%BC),這兩個連結是一樣的,但一個有包含你所不知道的 C 語言系列目錄。此處與 [link](https://hackmd.io/@sysprog/linux2023-lab0/%2F%40sysprog%2Flinux2023-lab0-b#%E8%BF%BD%E8%B9%A4%E8%A8%98%E6%86%B6%E9%AB%94%E9%85%8D%E7%BD%AE%E5%92%8C%E9%87%8B%E6%94%BE%E7%9A%84%E7%8B%80%E6%B3%81) 情形類似,可以注意它的插圖,幫助理解。注意有用到 strncpy 貼上 bufsize ,有些同學提到使用 memcpy 列於考慮中。[好的說明](https://hackmd.io/@Risheng/linux2023-lab0#q_remove_head),類似複製變數值的道理,將第三個變數稱作 buffer ,最後在結尾補上0,完成刪除的動作。 ```cpp! element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { element_t *n = container_of(head->next, element_t, list); list_del(head->next); if (sp != NULL) { strncpy(sp, n->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return n; } ``` :::warning 共筆中英文和中文之間要有空格。 漢語表達不太清楚。例如:「有用到 strncpy 貼上 bufsize 」這句話, 應該可以改成「使用 `strncpy` 函式,將 `n->value` 中的字串複製到 `sp` 中,最多複製 `bufsize - 1` 個字元。」。可以善用 ChatGPT 來改進。 > [name=yanjiew1] ::: ### q_remove_tail 修改一次兩處: (1)head 要做檢查。 (2)可以盡量使用 q_remove_head 因為對head的檢查,以及類似的指標是否為空,此一類檢查另我覺得很冗贅,所以思索原因並設計實驗確認,如以下,實驗方法與結果。 ```shell! (base) urbaner@urbaner:~/linux2022/lab0-c$ make SANITIZER=1 CC queue.o queue.c: In function ‘q_new’: queue.c:20:8: error: ‘rr’ is used uninitialized [-Werror=uninitialized] 20 | if (!rr) | ^ queue.c:19:23: note: ‘rr’ was declared here 19 | struct list_head *rr; | ^~ cc1: all warnings being treated as errors make: *** [Makefile:50: queue.o] Error 1 (base) urbaner@urbaner:~/linux2022/lab0-c$ vim queue.c ``` :::warning 只看文字無法了解你想表達的意思,建議在共筆貼出此段程式碼,文字描述寫完整一點。 > [name=yanjiew1] ::: :::info 已修正並標示下方段落。 >[name=Urbaner3] ::: ### 實驗方法與結果 ```cpp! struct list_head *q_new() { struct list_head *q = malloc(sizeof(struct list_head)); puts("check if sturct initial and malloc are null"); if (q != NULL) printf("malloc is not null\n"); else puts("sorry, malloc is null @@"); if (list_empty(q)) puts("list is empty"); else if (list_empty(q)==0) puts("not empty!!"); if (q != NULL) INIT_LIST_HEAD(q); if (q != NULL) printf("malloc is not null\n"); else puts("sorry, malloc is null @@"); if (list_empty(q)) puts("list is empty"); else if (list_empty(q)==0) puts("not empty!!"); return q; } void q_free(struct list_head *l) { if (l == NULL) return; element_t *n, *s; int lp_l, lp_nl; list_for_each_entry_safe (n, s, l, list) q_release_element(n); raise(SIGINT); free(l); lp_l=list_empty(l); if (lp_l == 0){ puts("l not empty in q_free"); } lp_nl=list_empty(&n->list); raise(SIGINT); if (lp_nl == 0) puts("n not empty in q_free"); } ``` ```shell! (base) urbaner@urbaner:~/linux2022/lab0-c$ make SANITIZER=1 CC queue.o CC random.o CC dudect/constant.o CC dudect/fixture.o CC dudect/ttest.o CC linenoise.o LD qtest (base) urbaner@urbaner:~/linux2022/lab0-c$ ./qtest cmd> new check if sturct initial and malloc are null malloc is not null not empty!! malloc is not null list is empty l = [] cmd> (base) urbaner@urbaner:~/linux2022/lab0-c$ scripts/debug.py -d Reading symbols from /home/urbaner/linux2022/lab0-c/qtest... cmd> free Program received signal SIGINT, Interrupt. __pthread_kill_implementation (no_tid=0, signo=2, threadid=<optimized out>) at ./nptl/pthread_kill.c:44 ``` malloc 函式初始化指標,其值為非 NULL ,這樣我們知道不需要測試,足以繼續開發。 list_empty 在 new 指令 q_new 函數前後能分別指令效果。所以應該把有關測試的檔案,其中 if(q!=NULL) 片段修改為 if(list_empty(q)) 才有意義。~~這告訴我們要習慣用 else if 來作測試,尤其是必須確認 if 條件式當中的變數值時。 於是將 q_new, q_free, q_insert_head, q_insert_tail, q_remove_head, q_remove_tail 都進行 if 條件式的修正。~~ CONTRIBUTING.md 檔案有說明,有~~比較好~~標準的 if 敘述參考。 至此完成初步操作,通過 `make check` 的要求。~~接下來的部份,會在發布前先暫時貼在這裡,通過測試後,再整理筆記。~~ :::warning 無法理解你的意思。 當 malloc 回傳值不為 NULL 時,表示空間配置成功。然而,配置的記憶體空間是未初始化的,可能包含先前該記憶體位置上的舊資料。因此,在使用前應先進行初始化。 > [name=yanjiew1] ::: ### q_delete_mid ```cpp! 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 *h = head->next; struct list_head *t = head->prev; while (true) { if (h == t || h->next == t) { list_del(h); q_release_element(list_entry(h, element_t, list)); break; } else { puts("del tail case."); break; } h = h->next; t = t->prev; } return true; } ``` 按照 [leetcode](https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/) 的提示,刪去前方的節點。 **失敗案例**,沒定好結束條件,有最優化的程式提示錯誤: ```shell! cmd> it tig l = [meer dolp bea ger bea ger tig] cmd> rt tig Removed tig from queue l = [meer dolp bea ger bea ger] cmd> dm del tail case. l = [meer dolp bea ger bea ... ] ERROR: Queue has more than 5 elements cmd> show Current queue ID: 0 l = [meer dolp bea ger bea ... ] ERROR: Queue has more than 5 elements ``` :::warning `h` 和 `t` 二個指標相遇時,應該要刪除 `t` 才對。 > [name=yanjiew1] ::: :::info 已標注失敗案例,考慮今後省略,對開發無幫助,也無法交流,僅為我個人的疑慮。 下方補上程式碼。 ::: 補完程式碼: ```cpp! if (h->next == t) { list_del(t); q_release_element(list_entry(t, element_t, list)); break; } h = h->next; t = t->prev; ``` ### q_delete_dup 需要比對字串,因為 element_t 型別預設 value 為 char 型別,先指定strcmp做函數,找到三種作法,參考 [for](https://hackmd.io/@freshLiver/2022q1-hw1#%E9%87%9D%E5%B0%8D%E4%BD%87%E5%88%97%E7%9A%84%E6%93%8D%E4%BD%9C), [while](https://hackmd.io/@jim12312321/linux2022-lab0#q_delete_dup), [cut_position](https://hackmd.io/@yanjiew/linux2023q1-lab0#q_delete_dup), 可以學到不同的函式用法,能較快熟練 linked list 的細節。 :::info 縮減筆記篇幅,將疑惑與困難記錄收錄到另一份筆記。 [link: dedup](https://hackmd.io/@Urbaner/page1#delete_dup) ::: ```graphviz digraph structs { rankdir=LR; node[shape=circle]; struct0 [label= "head"]; struct1 [label= "1"]; struct2 [label= "1"]; struct3 [label= "1"]; struct4 [label= "2"]; struct5 [label= "3"]; struct6 [shape=plaintext label="cut"]; struct7 [shape=plaintext label="it"]; struct8 [shape=plaintext label="safe"] struct0 -> struct1 -> struct2 -> struct3 -> struct4 -> struct5; struct7 struct6 struct8 {rank = same; struct0; struct6}; {rank = same; struct1; struct7}; {rank = same; struct2; struct8}; } ``` ```graphviz digraph structs { rankdir=LR; node[shape=circle]; struct0 [label= "head"]; struct1 [label= "1"]; struct2 [label= "1"]; struct3 [label= "1"]; struct4 [label= "2"]; struct5 [label= "3"]; struct6 [shape=plaintext label="cut"]; struct7 [shape=plaintext label="it"]; struct8 [shape=plaintext label="safe"] struct0 -> struct1 -> struct2 -> struct3 -> struct4 -> struct5; struct7 struct6 struct8 {rank = same; struct0; struct6}; {rank = same; struct2; struct7}; {rank = same; struct3; struct8}; } ``` ```graphviz digraph structs { rankdir=LR; node[shape=circle]; struct0 [label= "head"]; struct1 [label= "1"]; struct2 [label= "1"]; struct3 [label= "1"]; struct4 [label= "2"]; struct5 [label= "3"]; struct6 [shape=plaintext label="cut"]; struct7 [shape=plaintext label="it"]; struct8 [shape=plaintext label="safe"] struct0 -> struct1 -> struct2 -> struct3 -> struct4 -> struct5; struct7 struct6 struct8 {rank = same; struct0; struct6}; {rank = same; struct3; struct7}; {rank = same; struct4; struct8}; } ``` 如圖示,保持 cut 位置,移到比對相異時,safe 的節點,分開兩個 list。按照 list_cut_position 的定義,傳入參數。 [code_page](https://github.com/Urbaner3/lab0-c/blob/b65f692d80f5dabeb08e796044e87e370231dcbf/queue.c#L139) ### swap and sort merge_sort [node ed.](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C), [list ed.](https://npes87184.github.io/2015-09-12-linkedListSort/), [recursive](https://hackmd.io/@sysprog/linux2023-quiz1#%E6%B8%AC%E9%A9%97-1) [my work of merge sort](https://github.com/Urbaner3/lab0-c/blob/182237afbb53f941ede406a2ff93671f81284b30/queue.c#L227) 其中含有 [merge_two](https://github.com/Urbaner3/lab0-c/blob/182237afbb53f941ede406a2ff93671f81284b30/queue.c#L273) 自訂函式 [work of swap](https://github.com/Urbaner3/lab0-c/blob/182237afbb53f941ede406a2ff93671f81284b30/queue.c#L180) ```cpp! void q_sort(struct list_head *head) { /* Remove every node which has a node with a strictly greater value anywhere * to the right side of it */ /*https://npes87184.github.io/2015-09-12-linkedListSort/ */ if (!head || !head->next) return head; struct list_head *fast = head->next; struct list_head *slow = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } /*reset fast node, cut the list*/ fast = slow->next; slow->next = NULL; q_sort(head); q_sort(fast); two_merge(head, fast); ``` trace 04, 05 ### reverse (and merge) 依序將節點從頭走訪,再將開頭視為另一表單入口,很像瓶口向瓶底送,先進後出,FILO。 ### merge 實作時使用 list 儲存排序後結果。把 [iterative merge](https://npes87184.github.io/2015-09-12-linkedListSort/) 排序後接上,結合 [yanjiew](https://hackmd.io/@yanjiew/linux2023q1-lab0#q_merge_two), [alan](https://github.com/alanjian85/lab0-c/blob/master/queue.c#L183) 的作法,寫出表單版本的合併。最後考慮若迴圈結束時, l1 或 l2 為空,將剩餘表單依序接在新做表單,再去做切割。 trace 03, 05 ```cpp! static void merge_two(struct list_head *l1, struct list_head *l2){ if (!l1 || !l2) return 0; element_t *nod1, *nod2; LIST_HEAD(newhd); nod1 = list_first_entry(l1,element_t,list); nod2 = list_first_entry(l2,element_t,list); while (l1 && l2){ if (strcmp(nod1->value, nod2->value) < 0) /*add l1 node to tail of newhd list */ list_move_tail(l1, &newhd); else list_move_tail(l2, &newhd); } list_splice(&newhd, l1); list_splice( } ``` ```cpp! struct list_head *temp = LIST_HEAD(nh); struct list_head *q = temp; element_t *ll1, *ll2; while (l1 && l2) { ll1 = list_entry(l1, element_t, list); ll2 = list_entry(l2, element_t, list); if (*ll1->value < *ll2->value) { temp->next = l1; temp = temp->next; l1 = l1->next; } else { temp->next = l2; temp = temp->next; l2 = l2->next; } } if (l1) temp->next = l1; if (l2) temp->next = l2; struct list_head *head = q->next; list_del(q); free(q); return head; ``` 釐清 queue_context 這個結構之後,可以理解 [alan](https://github.com/alanjian85/lab0-c/blob/f34b3b61b64347cc98abdbfe8b3df8dc62a97bed/queue.c#L267) 的程式碼,把 qc 命名之後,改正 size 的名字為 cnt_len,避免與 q_size 搞混。 先記錄第一列的資料,再引入迴圈,讀取第二列數列資料,合併和刪除空數列,即完成。 [work of code](https://github.com/Urbaner3/lab0-c/blob/f1a0c592b5f5cae50c71e45c39e75835a863fd9a/queue.c#L299) ### dedup reverseK, descend trace 06 根據 [yanjiew](https://hackmd.io/@yanjiew/linux2023q1-lab0#q_descend) descend 為所有點右邊不可有更大的值或字串,從右邊開始,刪去小於尾端的值或字串,整理到開頭。 ascend 則相反。 [work of code](https://github.com/Urbaner3/lab0-c/blob/6b118495d9bbe9df2426e4c973c5d1f34cd4f6ef/queue.c#L256) ## Valgrind 與 自動程式設計 ## Web server ```shell! ./qtest cmd> web listen on port 9999, fd is 3 cmd> Unknown command '.' cmd> Unknown command 'favicon.ico' cmd> l = [] cmd> Unknown command 'favicon.ico' cmd> l = [1] cmd> Unknown command 'favicon.ico' cmd> l = [2 1] cmd> Unknown command 'favicon.ico' Error limit exceeded. Stopping command execution ``` [epoll_program](https://hackmd.io/@qwe661234/S1tuJOC6s#Explain-how-select-system-call-work-in-this-program-and-examine-the-consolec-implementation) ## 亂數 & Dudect 論文 [chi_square_test](https://soc.utah.edu/sociology3112/chi-square.php) [t-test](https://highscope.ch.ntu.edu.tw/wordpress/?p=73780) [excel](https://highscope.ch.ntu.edu.tw/wordpress/?p=73782) 檢查,[yanjiew](https://hackmd.io/@yanjiew/linux2023q1-lab0#%E7%A0%94%E8%AE%80%E8%AB%96%E6%96%87%E3%80%88Dude-is-my-code-constant-time%E3%80%89) /lab-0/dudect/ttest.c , only `push`, `compute` and `init` 3 functions. /ob.../dudect/src/dudect.h, 4 `struct`s ; public,`init`,`main`,`free`, `randombits`, `randombytes` `cmp`, `percentile`, `cpucycles()`, `measure`, `report_test`, `test` `percentile` in `dedect_ctx_t`