--- tags: linux2023 --- # 2023q1 Homework1 (lab0) contributed by < `JoshuaLee0321` > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 43 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 12 On-line CPU(s) list: 0-11 Vendor ID: AuthenticAMD Model name: AMD Ryzen 5 2600X Six-Core Processor CPU family: 23 Model: 8 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 Stepping: 2 Frequency boost: enabled CPU max MHz: 3600.0000 CPU min MHz: 2200.0000 BogoMIPS: 7199.10 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht ``` ## 目前分數 <s> 目前分數 100/100 都卡在一些memory的分配問題 2023/2/23 由於課程時發現 C 語言的前置處理器可用來定義 function-like macro,從而使程式碼更容易維護,所以本日會專注在定義macro 2023/2/27 完成 delete_dup 2023/2/27 make valgrind 中出現非常多錯誤,目前正在debug 2023/2/27 由於 `memcpy` 會報錯,所以把所有有關於 `memcpy` 的都改成 `strncpy` </s> :::warning HackMD 提供時間戳記,不需要自行列舉。 注意作業書寫規範:中英文間用一個半形空白字元區隔。 :notes: jserv > 好的 ::: ## 開發 `queue.c` 的過程 ### `q_new` * 首先利用 `malloc` 分配空間給queue,考量到`malloc`有機會出錯,那先判斷是不是沒有分配成功,之後再利用 Linux 核心中的 `INIT_LIST_HEAD` 來把指針指向自己 :::spoiler ```c struct list_head *q_new() { struct list_head *head = malloc(1 * sizeof(struct list_head)); if(!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` ::: ### `q_free` * 最剛開始的寫法,利用 `list_for_each_safe` run 過所有的 item,一個一個節點 release 但這樣會出先 segmentation fault :::danger 改進你的漢語描述。 :notes: jserv ::: :::spoiler ```c void q_free(struct list_head *l) { if (!l || list_empty(l)) { free(l); return; } struct list_head *it,*safe; list_for_each_safe(it, safe, l){ element_t *tmp = list_entry(it, element_t, list); list_del(it); free(tmp); } free(list_entry(l, element_t, list)); return; } ``` ::: :::info 後續改成 `q_release_element` 的內建API ::: ```c void q_free(struct list_head *l) { if(!l || list_empty(l)) return; // ~~ struct list_head *it, *safe; // list_for_each_safe(it, safe, l){ // q_release_element(it); // }~~ /*以上為錯誤版本,已修正成以下版本*/ list_for_each_entry_safe(it, safe, l, list) { q_release_element(it); } free(l) } ``` 更正版本: `element_t *it = NULL, *safe = NULL;` `list_for_each_entry_safe(it, safe, l, list) { q_release_element(it); }` :::info (更新)發現 safe 並沒有初始化成NULL 導致出錯,已修正 (更新)發現 list_for_each_entry_safe 以及 q_release_element 才可以把東西刪除完全 ::: ### `q_insert_head` `q_insert_tail` `q_delete_head` `q_delete_tail` :::info (更)剛開始的實做如下,但這樣會發生我的實做並不是constant time,錯誤還有代釐清 (更)以上課後學習到的新知 (macro) function 為基準 重新製作了 q_insert_##type 在macro 中 (更)已把 q_insert 以及 q_remove 類型的都把它做成macro 方便維護,除此之外還找到了 `q_remove_macro` 中少加入 if(list_empty(head)) 而導致 `segmentation fault` ::: #### macro 如下 :::spoiler ```c #define q_insert_macro(type, func) \ bool q_insert_##type(struct list_head *head, char *s) \ { \ if (!head) \ return false; \ element_t *new_node = malloc(1 * sizeof(*new_node)); \ if (!new_node) \ return false; \ new_node->value = malloc((strlen(s) + 1) * sizeof(char)); \ if (!new_node->value) { \ q_release_element(new_node); \ return false; \ } \ memcpy(new_node->value, s, strlen(s) + 1); \ func(&new_node->list, head); \ return true; \ } \ #define q_remove_macro(type, func) \ element_t *q_remove_##type(struct list_head *head, char *sp, \ size_t bufsize) \ { \ if (!head || list_empty(head)) \ return NULL; \ element_t *del = func(head, element_t, list); \ list_del(&del->list); \ if (sp != NULL) { \ memcpy(sp, del->value, bufsize - 1); \ sp[bufsize - 1] = '\0'; \ } \ return del; \ } ``` ::: 而實際上我就可以只利用四行程式碼來實作 :::success ```c /* Insert an element at head of queue */ q_insert_macro(head, list_add); /* Insert an element at tail of queue */ q_insert_macro(tail, list_add_tail); /* Remove an element from head of queue */ q_remove_macro(head, list_first_entry); /* Remove an element from tail of queue */ q_remove_macro(tail, list_last_entry); ``` ::: * 如此一來 insert_head 跟 insert_tail 都可以在組譯階段被完成,如此一來可以節省空間之外還可以更好的維護兩個function * 不過目前還在找出為甚麼此function 無法在constant time 完成,之後會研讀論文 ### `q_delete_dup` * 利用 `linux/list.h` 中的 API 跑過迴圈;要注意的事情是,這個function 必須把重複的東西通通刪除掉。由於`list_for_each_safe` 中的 `safe` 可以當作 `next` 來使用,如此一來我們只要 1. 跑過 `head` 中的所有元素 2. 使用 iterator `it` 來觀察是否當前元素與下個元素相同 3. 利用 `bool not_finished` 變數來記錄是否要把當前 `it` 刪除 以下為實作code :::spoiler ```c bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head || list_is_singular(head)) return false; struct list_head *it, *safe = NULL; bool not_finished = false; list_for_each_safe (it, safe, head) { element_t *prev = list_entry(it, element_t, list); element_t *cur = list_entry(safe, element_t, list); if (!strcmp(prev->value, cur->value)) { not_finished = true; list_del(it); q_release_element(prev); if (safe->next == (head)) { list_del(safe); q_release_element(cur); break; } } else if (not_finished) { not_finished = false; list_del(it); q_release_element(prev); } } return true; } ``` ::: ### `q_size` * 已經由 `jserv` 老師撰寫好了 ```c int q_size(struct list_head *head) { if (!head) return 0; int len = 0; struct list_head *li; list_for_each(li,head) len++; return len; } ``` ### `q_delete_mid` * 基本上這個題目有兩種作法,一種先找出 `queue` 的長度除以2之後給定一個`int cnt` 去計算這個list目前到哪裡,但這個實做方法有一個問題,它會花到O(2N)的時間 * N = 得到 `size` 的時間 * N = traverse 到中間的時間 * 第二個作法就是利用兩種不同方向或速度的指標,而我的實做方法就是利用一個每一次只會往後走一歩的 `single_p` 以及每一次會往後走兩步的 `double_p`,在double走到底的時候single會直接在中間 * 如此一來只需要直接把 `single_p` 給移除掉即可 :::spoiler ```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 *single_p, *double_p; single_p = double_p = head->next; while(double_p != (head) && double_p->next != (head)) { single_p = single_p->next; double_p = double_p->next->next; } list_del_init(single_p); q_release_element(list_entry(single_p,element_t,list)); return true; } ``` ::: ### `q_swap` * 在 `swap-nodes-in-pairs` 中,可以使用 `iterative` 跟 `recursive` 的方法實做,而因為有linux kernel 的 API,我選擇使用 `iterative` 的作法 * 使用 `it` 當作 每一次走過的node * 每次 `it` 往前走一步 * 每次 `it` 跟 `it->next` 互換 如此一來就可以達到每兩個 node 互相交換了 :::spoiler ```c void q_swap(struct list_head *head) { if(!head || list_empty(head) || head->next->next == head) return; struct list_head *it = head->next; while(it != (head) && it->next != (head)) { list_move(it,it->next); it = it->next; } // https://leetcode.com/problems/swap-nodes-in-pairs/ } ``` ::: ### `q_reverse` * `q_reverse` 如果使用 Linux 核心的 api 就會非常簡單 * 儘需要走過這整個串列,並且每一次都把當前的node放到最前方即可 ```c void q_reverse(struct list_head *head) { if(!head || list_empty(head) || list_is_singular(head)) return; struct list_head *it, *safe = NULL; list_for_each_safe(it, safe, head) { list_move(it, head); } return; } ``` ### `q_sort` * 參考[你所不知道的 c 語言: linked list 和非連續記憶體及 Linked List Sort](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C) 中提到的Merge Sort ,並以 [LiChiii](https://hackmd.io/@NYC6Z-WqQ3W-61xcE-2SvA/SJDBv7Cps#q_sort) 的程式碼為實作。 ## `Valgrind` ```shell # Explicitly disable sanitizer(s) $ make clean SANITIZER=0 qtest make[1]: Entering directory '/home/joshua/linux2023/lab0-c' rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o shannon_entropy.o linenoise.o web.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .shannon_entropy.o.d .linenoise.o.d .web.o.d *~ qtest /tmp/qtest.* rm -rf .dudect rm -rf *.dSYM (cd traces; rm -f *~) CC qtest.o CC report.o CC console.o CC harness.o CC queue.o CC random.o CC dudect/constant.o CC dudect/fixture.o CC dudect/ttest.o CC shannon_entropy.o CC linenoise.o CC web.o LD qtest make[1]: Leaving directory '/home/joshua/linux2023/lab0-c' cp qtest /tmp/qtest.83iQ10 chmod u+x /tmp/qtest.83iQ10 sed -i "s/alarm/isnan/g" /tmp/qtest.83iQ10 scripts/driver.py -p /tmp/qtest.83iQ10 --valgrind --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 5/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, remove_head, reverse and merge --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 6/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK --- trace-06-ops 6/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail --- trace-16-perf 6/6 +++ 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 Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 Test with specific case by running command: scripts/driver.py -p /tmp/qtest.83iQ10 --valgrind -t <tid> ``` ## debug 紀錄 #### 2 / 23 :::info 目前一直卡在 q_insert 的function 並沒有在constant time 處理完畢 除此之外,每一次free佇列都會發生一些問題 (更新) 發先是 q_free 中的safe 沒有初始化 ::: #### 2 / 27 :::info 測試時一直發現 trace - 17 一直報告不是常數時間。同時我也很確定我的實作方法為constant time,而經由閱讀 [KHLEE](https://hackmd.io/@KHLee529/linux2023-lab0) 的實作方法,更改了 dudect/constant.c 以及 dudect/fixture 的計算方法後才得到目前的滿分 :::