--- tags: linux kernel --- # 2022q1 Homework1 (lab0) contributed by < `zoanana990` > ## 實驗環境 ```shell $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 Copyright (C) 2019 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 Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 158 Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz Stepping: 9 CPU MHz: 900.106 CPU max MHz: 3800.0000 CPU min MHz: 800.0000 BogoMIPS: 5599.85 L1d cache: 128 KiB L1i cache: 128 KiB L2 cache: 1 MiB L3 cache: 6 MiB NUMA node0 CPU(s): 0-7 ``` ## 作業要求 - [x] 在 GitHub 上 fork [lab0-c](https://github.com/zoanana990/lab0-c) - [x] 依據上述指示著手修改 queue.[ch] 和連帶的檔案,測試後用 Git 管理各項修改,要能滿足 `$ make test` 自動評分系統的所有項目。 - [ ] 研讀 Linux 核心原始程式碼的 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 並嘗試引入到 lab0-c 專案,比較你自己實作的 merge sort 和 Linux 核心程式碼之間效能落差 - [ ] 利用 `Valgrind` 分析 `q_test` - [x] 在 `qtest` 提供新的命令 `shuffle` ,允許藉由 Fisher–Yates shuffle 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作 - [ ] 在 `qtest` 提供新的命令 `web`,提供 `web` 伺服器功能,注意: `web` 伺服器運作過程中,`qtest` 仍可接受其他命令 - [ ] 閱讀論文 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf) ## 作業 1. 修改`queue.[ch]` ### `q_new` 實作 - 一開始的想法,要先初始化一個佇列(queue),再將佇列中的 `list_head` 初始化,寫下以下程式碼: ```c struct list_head *q_new() { // make an element element_t *q = malloc(sizeof(element_t)); // if not malloc memory if (!q){ free(q); return NULL; } // Initial the list node and value q->value = NULL; INIT_LIST_HEAD(&q->list); return &q->list; } ``` 然而這個程式碼無法編譯成功,顯示錯誤為 `Attempted to free unallocated block` ,這給我兩個想法: 1. `element_t` 沒有分配成功,因此不需要為了它去free ```c struct list_head *q_new() { // make an element element_t *q = malloc(sizeof(element_t)); // if not malloc memory if (!q){ return NULL; } q->value = malloc(sizeof(char)); if(!q->value){ free(q); return NULL; } // Initial the list node and value q->value = NULL; INIT_LIST_HEAD(&q->list); return &q->list; } ``` 這邊仍然會出現錯誤,因此我改變 `Makefile` 指定測試13,看一下出現結果,目前我沒有在對上面版本的程式碼繼續調整 ```shell WARNING: Malloc returning NULL l = NULL cmd> new WARNING: Malloc returning NULL l = NULL cmd> new WARNING: Malloc returning NULL l = NULL cmd> new l = [] cmd> new Freeing old queue ERROR: Attempted to free unallocated block. ``` 2. 函式回傳值要求為 `struct list_head` ,直接做一個 `struct list_head` 回傳就好, `element_t` 可以使用 `list_entry` 呼叫。 ```c struct list_head *q_new() { // make an element struct list_head *l = malloc(sizeof(struct list_head)); // if not malloc memory if (!l) return NULL; // Initial the list node INIT_LIST_HEAD(l); return l; } ``` 這個程式碼可以繼續成功運行 ### `q_free` 實作 - 這個函式突顯了 `container_of` 的重要性,之前上課的時候完全不覺的 `container_of` 可以有什麼大的作用。在寫這個函式之後,完全沒有頭緒,因此回去重看了[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)這時候才理解為什麼老師上課說 `container_of` 非常重要可以幫助程式變得很簡潔 - 一開始的程式碼: ```c void q_free(struct list_head *l) { if (!l) return; struct list_head *curr; list_for_each (curr, l) q_release_element(list_entry(curr, element_t, list)); free(l); } ``` - 這裡直接報錯,原因是 `curr` 指向的佇列被刪除後, `curr` 也會被刪除,會出現 `segmentation fault` 錯誤 - 因此需要調整成先創立一個節點,等現在的節點跑到下一個之後,再把之前的節點獨立出來,在刪掉 - 最終程式碼: ```c void q_free(struct list_head *l) { if (!l) return; struct list_head *curr = l->next; while(curr != l){ struct list_head *prev = curr; curr = curr->next; list_del(prev); q_release_element(list_entry(prev, element_t, list)); } free(l); } ``` 圖解說明: - 一開始: ```graphviz digraph{ rankdir = LR l[color=red, fontcolor=red] curr[color=blue, fontcolor=blue] prev[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] curr -> bear l -> {bear, meerkat} bear -> {dophin, l} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {l, gerbil} } ``` - 將 `prev` 紀錄 `curr` 及 `curr` 右移,程式碼對應如下: ```c struct list_head *prev = curr; curr = curr->next; ``` ```graphviz digraph{ rankdir = LR l[color=red, fontcolor=red] curr[color=blue, fontcolor=blue] prev[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] curr -> dophin l -> {bear, meerkat} prev -> bear bear -> {dophin, l} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {l, gerbil} } ``` - 刪除 `bear` ,先將節點獨立出來,再將記憶體釋放,程式碼對應如下 ```c list_del(prev); q_release_element(list_entry(prev, element_t, list)); ``` ```graphviz digraph{ rankdir = LR l[color=red, fontcolor=red] curr[color=blue, fontcolor=blue] prev[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] curr -> dophin l -> {dophin, meerkat} prev -> bear dophin -> {gerbil, l} gerbil -> {meerkat, dophin} meerkat -> {l, gerbil} } ``` - 利用 `container_of` 找到 `queue` 的位址進行刪除,經過n個迴圈後剩下原本的 `head` ```graphviz digraph{ l[color=red, fontcolor=red] } ``` - 將 `head` 釋放 - 結束 ### `q_insert` 實作 - `q_insert_head` 和 `q_insert_tail` 很像,而重複的地方我寫成 `q_insert` ,這個函式主要是開 `element_t` 的空間及 `char*` 的空間,如果 `element_t *` 的空間沒有開成功則回傳false,如果 `char *` 的空間沒有開成功則須先釋放 `element_t` 的空間,再回傳false,否則會出現 `Allocated Blocks` 的錯誤 ```c /* * Due to the repeated code in q_insert_head and q_insert_tail * write a functionto replace them */ element_t *q_insert(char *s) { element_t *q = malloc(sizeof(element_t)); if (!q) return NULL; q->value = malloc(sizeof(char) * (strlen(s) + 1)); if(!q->value){ free(q); return NULL; } strncpy(q->value, s, strlen(s)); q->value[strlen(s)] = '\0'; return q; } ``` - 圖解說明:以插入頭為例 - 一開始 ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] want_to_insert[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] head -> {dophin, meerkat} want_to_insert -> bear dophin -> {gerbil, head} gerbil -> {meerkat, dophin} meerkat -> {head, gerbil} } ``` - 先分配佇列空間並檢驗是否分配成功 - 在分配字串空間並檢驗是否分配成功,==若分配失敗須釋放佇列空間==,否則會出現 `allocated blocks` 的錯誤 - 利用 `linux API` 直接插入即可 ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] dophin[shape = square] bear[shape = square, color=blue, fontcolor=blue] gerbil[shape = square] meerkat[shape = square] head -> {bear, meerkat} bear -> {dophin, head} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {head, gerbil} } ``` - 如此, `q_insert_head` 和 `q_insert_tail` 就可以寫出來: - `q_insert_head` ```c bool q_insert_head(struct list_head *head, char *s) { // Boundary Condition if (!head) return false; // make a new element element_t *q = q_insert(s); if(!q) return NULL; // INIT_LIST_HEAD(&q_head->list); list_add(&q->list, head); return true; } ``` - `q_insert_tail` ```c bool q_insert_tail(struct list_head *head, char *s) { // Boundary Condition if (!head) return false; // make a new element element_t *q = q_insert(s); if(!q) return s; list_add_tail(&q->list, head); return true; } ``` ### `q_remove_head` 實作 - `q_remove_head` 是將佇列的開頭移出鏈結串列中,因此需要利用 `container_of` 找出佇列的位址: ```c element_t *q_head = list_first_entry(head, element_t, list); ``` - 將節點斷開後,利用 `memset`, `memcpy` 或 `strncpy`等函式將字串複製到 `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 *q_head = list_first_entry(head, element_t, list); list_del_init(&q_head->list); sp = realloc(sp, sizeof(char) * bufsize); if (sp) { strncpy(sp, q_head->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return q_head; } ``` - 然後直接報錯,因為 ```c sp = realloc(sp, sizeof(char) * bufsize); ``` 會出現 `copying of string in remove_head overflowed destination buffer` 如果把上面的程式碼換成: ```c sp = calloc(bufsize, sizeof(char)); ``` 則會發生`Failed to store removed value` 如果都沒有寫的話,則會通過測試,這裡是因為記憶體分配的問題,這幾天會看你所不知道的C語言找答案 - 最終程式碼: ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *q_head = list_first_entry(head, element_t, list); list_del_init(&q_head->list); if (sp) { strncpy(sp, q_head->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return q_head; } ``` - 圖解說明: - 一開始 ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] want_to_delete[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square, color=blue, fontcolor=blue] gerbil[shape = square] meerkat[shape = square] want_to_delete ->bear head -> {bear, meerkat} bear -> {dophin, head} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {head, gerbil} } ``` - 與 `q_free` 相同,先將 `bear` 獨立出來後利用 `container_of` 返回佇列 ```c element_t *q_head = list_first_entry(head, element_t, list); list_del_init(&q_head->list); ``` ```graphviz digraph{ bear[shape=square, color=blue, fontcolor=blue] } ``` ### `q_remove_tail` 實作 - 可以直接使用 `q_remove_head` 的結果 ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { return q_remove_head(head->prev->prev, sp, bufsize); } ``` ### `q_delete_mid` 實作 - 這個函式老師在上課的時候有做範例,於是我也在 [leetcode 2095](https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/submissions/) 寫一下草稿: ```c struct ListNode* deleteMiddle(struct ListNode* head){ if(!head) return NULL; struct ListNode *fast=head, **slow=&head; while(fast && fast->next){ fast=fast->next->next; slow=&(*slow)->next; } (*slow) = (*slow)->next; return head; } ``` - 將函式移植到作業時需要做一些調整: ```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, *slow = head->next; for (;fast != head && fast->next!= head; fast = fast->next->next) slow = slow->next; q_release_element(list_entry(slow, element_t, list)); list_del(slow); return true; } ``` - 於是就順利的報錯了,錯誤內容 `Segmentation fault occurred. You dereferenced a NULL or invalid pointer` 也就是對空指標進行操作 這邊感謝 <`Risheng1128`> 的協助,問題出在下面這兩行: ```c q_release_element(list_entry(slow, element_t, list)); list_del(slow); ``` 我先把整的佇列刪除了,如果再把 `list_head` 刪掉會造成重複刪除,就會報上面那個錯誤,所以應該先把節點抓出來再將佇列刪除,這樣就部會造成上面那個錯誤 ```c list_del(slow); q_release_element(list_entry(slow, element_t, list)); ``` - 最終程式碼: ```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, *slow = head->next; for (;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; } ``` - 圖解說明 - 一開始,`slow` 和 `fast` 設置在 `head->next` 。龜兔賽跑開始,設定迴圈終止條件為 `fast != head` 及`fast->next != head` ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] fast[color=blue, fontcolor=blue] slow[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] fast -> bear head -> {bear, meerkat} slow -> bear bear -> {dophin, head} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {head, gerbil} } ``` - 第一次迴圈, `slow` 前進一格、 `fast` 前進兩格 ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] fast[color=blue, fontcolor=blue] slow[color=purple, fontcolor=purple] slow -> dophin fast -> gerbil head -> {bear, meerkat} bear -> {dophin, head} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {head, gerbil} } ``` - 第二次迴圈,`slow`前進一格、`fast`前進兩格,達成終止條件 ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] fast[color=blue, fontcolor=blue] slow[color=purple, fontcolor=purple] slow -> gerbil fast -> head head -> {bear, meerkat} bear -> {dophin, head} dophin -> {gerbil, bear} gerbil -> {meerkat, dophin} meerkat -> {head, gerbil} } ``` - 將 `gerbil` 刪除,如前面做法相似 ### `q_delete_dup` 實作 - 與前幾題相同,先利用[leetcode82](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/)打草稿,這邊我是使用間接指標(indirect pointer),屬於疊代法 - 由於 `leetcode` 屬於單向鏈結串列,這邊需要因為 `linux kernel` 調整程式碼的 ```c bool q_delete_dup(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) return false; struct list_head *prev = NULL, *curr = head->next, *next = curr->next; while (curr != head && next != head) { element_t *q_curr = list_entry(curr, element_t, list); element_t *q_next = list_entry(next, element_t, list); while (next != head && !strcmp(q_curr->value, q_next->value)) { prev = curr; list_del(next); q_release_element(list_entry(next, element_t, list)); next = curr->next; q_next = list_entry(next, element_t, list); } if (prev) { list_del(prev); q_release_element(list_entry(prev, element_t, list)); prev = NULL; } curr = next; next = curr->next; } return true; } ``` ### `q_swap` 實作 - 先利用 [leetcode 24](https://leetcode.com/problems/swap-nodes-in-pairs/) 打草稿: ```c struct ListNode* swapPairs(struct ListNode* head) { struct ListNode **indirect=&head, *future=NULL, *current=head; while((*indirect) && (*indirect)->next){ current = *indirect; future = current->next; current->next = future->next; future->next = current; *indirect = future; indirect = &(current)->next; } return head; } ``` 這邊需要對幾個地方做調整, ### `q_reverse` 實作 - 在寫這題之前,我先去 [leetcode 206](https://leetcode.com/problems/reverse-linked-list/submissions/) 打草稿,草稿寫完如下: - 大致上思路是很簡單的,用個指標一個一個前進,然後改變所只的方向即可,當然也可以使用遞迴呼叫的方式,但是作業的回傳值是 `void` 所以我使用比較直觀的方式去寫,以這題leetcode來說,遞迴法是比較好的選擇 - 遞迴法: ```c struct ListNode* reverseList(struct ListNode* head){ if(head==NULL || head->next == NULL){ return head; } struct node* next = reverseList(head->next); head->next->next = head; head->next = NULL; return next; } ``` - 疊代法: ```c struct ListNode* reverseList(struct ListNode* head){ struct ListNode *prev=NULL; while(head){ struct ListNode* next=head->next; head->next=prev; prev=head; head=next; } return prev; } ``` - 作業是用疊帶法去寫的,但是仍然需要做一些調整: 1. `linux_kernel` 屬於doubly-circular linked list,因此 `while` 迴圈的中止條件及每個節點的連結需要調整 2. 作業中的函式回傳值為 `void` 因此避免改動 `head` 改動完如下所示: ```c void q_reverse(struct list_head *head) { if (!head) return; struct list_head *curr = head, *prev = head->prev; while (next != head) { struct list_head *next = curr->next; curr->next = prev; curr->prev = next; prev = curr; curr = next; } } ``` - 於是就順利的報錯了,主要的原因出在 `struct list_head *next = curr->next;` 及 `curr->prev = next;` 的關聯,如果是單向鏈結串列,這個寫法沒有問題,但是他是雙向,因此我們必須保留每個節點的存在,調整方式很簡單,只要把 `next` 在外面宣告即可 ```c void q_reverse(struct list_head *head) { if (!head) return; struct list_head *curr = head, *prev = head->prev, *next = NULL; while (next != head) { next = curr->next; curr->next = prev; curr->prev = next; prev = curr; curr = next; } } ``` ### `q_sort` 實作 - 本題我採用 `merge sort` 進行,在寫這題之前,我先對 [leetcode 21](), [leetcode 148](https://leetcode.com/problems/sort-list/) 進行練習,而本函式的撰寫即是這兩題的結合。 - 合併的程式碼有參考[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)中的案例一 [leetcode 21](https://leetcode.com/problems/merge-two-sorted-lists/) 的解說,程式碼如下: ```c struct ListNode* merge(struct ListNode* left, struct ListNode* right){ // leetcode 21 struct ListNode *head = NULL, **ptr = &head, **node; for(node = NULL; left && right; *node = (*node)->next){ node = left->val > right->val ? &right : &left; *ptr = *node; ptr = &(*ptr)->next; } *ptr = (struct ListNode *)((uintptr_t) left | (uintptr_t) right); struct ListNode* h = head; return head; } struct ListNode* sortList(struct ListNode* head){ // leetcode 148 if(!head || !head->next) return head; struct ListNode *fast = head->next, *slow = head; for(;fast && fast->next; fast = fast->next->next) slow = slow->next; struct ListNode *next = slow->next; slow->next = NULL; return merge(sortList(head), sortList(next)); } ``` - 實際程式碼如下,這邊有參考<`Risheng1128`>的建議 ```c struct list_head *merge(struct list_head *left, struct list_head *right) { struct list_head *head = NULL, **ptr = &head, **node; for (node = NULL; left && right; *node = (*node)->next) { element_t *q_left = list_entry(left, element_t, list); element_t *q_right = list_entry(right, element_t, list); node = strcmp(q_left->value, q_right->value) < 0 ? &left : &right; *ptr = *node; ptr = &(*ptr)->next; } *ptr = (struct list_head *) ((uintptr_t) left | (uintptr_t) right); return head; } struct list_head *MergeSort(struct list_head *head) { if (!head || !head->next) return head; struct list_head *fast = head->next, *slow = head; for (; fast && fast->next; fast = fast->next->next) slow = slow->next; struct list_head *next = slow->next; slow->next = NULL; return merge(MergeSort(head), MergeSort(next)); } void q_sort(struct list_head *head) { if (!head || list_empty(head)) return; head->prev->next = NULL; head->next = MergeSort(head->next); struct list_head *curr, *prev = head; for (curr = head->next; curr; curr = curr->next) { curr->prev = prev; prev = curr; } prev->next = head; head->prev = prev; } ``` ## 作業 2. 以 `Valgrind` 分析記憶體問題 目前使用 `valgrind`進行分析時發現程式碼有記憶體洩漏的問題 ```shell ==27236== 8 bytes in 1 blocks are still reachable in loss record 1 of 33 ==27236== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==27236== by 0x4A5250E: strdup (strdup.c:42) ==27236== by 0x110E1D: linenoiseHistoryAdd (linenoise.c:1236) ==27236== by 0x1119B0: linenoiseHistoryLoad (linenoise.c:1325) ==27236== by 0x10CC24: main (qtest.c:1175) ``` ## 作業 3. Shuffle 參考[作業說明](https://hackmd.io/@sysprog/linux2022-lab0#qtest-%E5%91%BD%E4%BB%A4%E7%9B%B4%E8%AD%AF%E5%99%A8%E7%9A%84%E5%AF%A6%E4%BD%9C)將 `shuffle` 命令 (Command) 加入,寫下以下程式碼 ```c bool do_shuffle(int argc, char *argv[]){ ... } ... add_cmd("shuffle", do_shuffle, " | Shuffle the list"); ``` 已經參閱 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 演算法,並且實作出來,程式碼如下 ```c void shuffle(struct list_head *l, size_t size){ if(size < 2) return; for(size_t i=size; i>0; i--){ size_t index = rand()%i + 1; struct list_head *node = l; for(int j=0; j<index; j++) node = node->next; list_move_tail(node, l); } } ``` 其原理如下:隨機選取一個數字,該數字小於目前鏈結串列的尺寸。 例如:打亂下面這個鍊結串列,首先說隨機有一個索引目標假設現在是3,就會將 `gerbil` 移到最後面 ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] target[color=purple, fontcolor=purple] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] squirrel[shape = square] vulture[shape = square] head -> {bear, vulture} bear -> {dophin, head} dophin -> {gerbil, bear} target -> gerbil gerbil -> {meerkat, dophin} meerkat -> {squirrel, gerbil} squirrel -> {vulture, meerkat} vulture -> {head, squirrel} } ``` ```graphviz digraph{ rankdir = LR head[color=red, fontcolor=red] dophin[shape = square] bear[shape = square] gerbil[shape = square] meerkat[shape = square] squirrel[shape = square] vulture[shape = square] head -> {bear, gerbil} bear -> {dophin, head} dophin -> {meerkat, bear} meerkat -> {squirrel, dophin} squirrel -> {vulture, meerkat} vulture -> {gerbil, squirrel} gerbil -> {head, vulture} } ``` 如此就完成置換,重複這個動作,直到所有節點都被移動過為止 ### 分析 Linux 核心原始程式碼的 `list_sort.c` 有了 `shuffle` 的功能,就可以分析 `linux kernel` 裡面的 `list_sort` 了,這邊使用老師提供的[連結](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)進行調整 說明效能如下表所示: || 10000 | 60000 | | -------- | -------- | -----| | My sort | 0.004068| 0.032433 | | linux kernel | 0.003466| 0.017465 | | linux kernel (消除檢查機制)| 0.004291| 0.039406 | 接下來讓我們探討速度落差在哪裡: :::danger 注意書寫規範:中英文間用一個空白字元區隔 :notes: jserv ::: ## 作業 4. 加入 `Web` 命令 這邊有參考 <`laneser`> 的筆記進行實作,由於背景知識不足,這邊也先去閱讀 `CS:APP` 第11章 [CSAPP Chapter 11 筆記](https://hackmd.io/oG-aoiK3Trm_x0jxeCJCcg) ## 作業 5. 閱讀論文並實做 ### 論文閱讀 ### 程式實作 ## 參考資料 - 作業要求 [K01: lab0](https://hackmd.io/@sysprog/linux2022-lab0)