Risheng
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- tags: 2023 linux kernel --- # 2023q1 Homework1 (lab0) contributed by < [`Risheng1128`](https://github.com/Risheng1128) > > [作業說明](https://hackmd.io/@sysprog/linux2023-lab0) ## 實驗環境 ```shell $ 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): 12 On-line CPU(s) list: 0-11 Vendor ID: GenuineIntel Model name: 11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz CPU family: 6 Model: 141 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 Stepping: 1 CPU max MHz: 4500.0000 CPU min MHz: 800.0000 BogoMIPS: 5376.00 Virtualization features: Virtualization: VT-x Caches (sum of all): L1d: 288 KiB (6 instances) L1i: 192 KiB (6 instances) L2: 7.5 MiB (6 instances) L3: 12 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-11 $ 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. ``` ## 佇列實作 ### 使用的結構 實作之前,先分析目前 lab0-c 對管理佇列的設計 雙向鏈結串列 ```ㄏ struct list_head { struct list_head *prev; struct list_head *next; }; ``` 佇列元素 ```c typedef struct { char *value; struct list_head list; } element_t; ``` 由上述兩種的結構,可以知道佇列的元素本身就是鏈結串列的節點,會產生如下的示意圖 ```graphviz digraph { rankdir=LR; splines=false; node [shape="record"] head [label="<list>head"] element1 [label="<s>val|<list>list"] element2 [label="<s>val|<list>list"] element3 [label="<s>val|<list>list"] dots [shape=none, label="..."] head:list -> element1:list element1:list -> head:list element1:list -> element2:list element2:list -> element1:list element2:list -> dots dots -> element2:list dots -> element3:list element3:list ->dots element3:list -> head:list head:list -> element3:list } ``` 接著是和去年不同的部份,現在可以管理多個佇列,主要是由於以下的結構實作 ```c typedef struct { struct list_head *q; struct list_head chain; int size; int id; } queue_contex_t; ``` 上述的結構為每個佇列的節點,成員說明如下: - `q`: 指向佇列的第一個節點 - `chain`: 負責管理佇列的鏈結串列的節點 - `size`: 佇列節點的數量 - `id`: 鏈結串列編號 接著以下的結構為管理佇列的鏈結串列的第一個節點 ```c typedef struct { struct list_head head; int size; } queue_chain_t; static queue_chain_t chain = {.size = 0}; ``` 由上述的結構,可以得知目前管理佇列的鏈結串列示意圖如下: ```graphviz digraph { rankdir=LR; splines=false; node [shape="record"] chain [label="<c>chain"] queue1 [label="q|<c>chain|size|id"] queue2 [label="q|<c>chain|size|id"] queue3 [label="q|<c>chain|size|id"] dots [shape=none, label="..."] chain:c -> queue1:c queue1:c -> chain:c queue1:c -> queue2:c queue2:c -> queue1:c queue2:c -> dots dots -> queue2:c dots -> queue3:c queue3:c ->dots queue3:c -> chain:c chain:c -> queue3:c } ``` 其中 `q` 則是對應到上述佇列的結構 ### q_new 函式功能: 建立新的「空」佇列 :::info 函式流程 1. 使用 malloc 分配記憶體空間,並由指標 new 指向 2. 如果空間分配失敗 malloc 會回傳 NULL ,此時回傳 NULL 3. 使用函式 `INIT_LIST_HEAD` 初始化 ::: 實際程式碼: ```c struct list_head *q_new() { struct list_head *new = malloc(sizeof(*new)); if (!new) return NULL; // initialize the head INIT_LIST_HEAD(new); return new; } ``` ### q_free 函式功能: 釋放佇列所佔用的記憶體 :::info 函式流程 1. 如果傳入的 head 為 NULL ,直接結束函式 2. 走訪整個鏈結串列,每經過一個節點,就對其使用函式 `list_del` 移除該節點並釋放其空間 3. 釋放 head 的記憶體空間 ::: 實際程式碼: ```c void q_free(struct list_head *l) { if (!l) return; element_t *entry, *safe; list_for_each_entry_safe (entry, safe, l, list) { list_del(&entry->list); q_release_element(entry); } // free list head free(l); } ``` ### q_insert_head 函式功能: 在佇列開頭 (head) 插入 (insert) 給定的新節點 :::info 函式流程 1. 使用函式 `malloc` 分配 `element_t` 大小的記憶體空間,若失敗則回傳 `false` 2. 使用函式 `strdup` 建立複製字串資料 3. 使用函式 `list_add` 將節點加在 `head` 的下一個位置 ::: 實際程式碼: ```c bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *element = malloc(sizeof(*element)); if (!element) return false; // copy string element->value = strdup(s); if (!element->value) { free(element); return false; } // add node into doubly-linked list at head list_add(&element->list, head); return true; } ``` 其中函式 `strdup` 的實作如下 ```c /* Use undef to avoid strdup redefined error */ #undef strdup #define strdup test_strdup char *test_strdup(const char *s) { size_t len = strlen(s) + 1; void *new = test_malloc(len); if (!new) return NULL; return memcpy(new, s, len); } ``` ### q_insert_tail 函式功能: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 :::info 函式流程 1. 使用函式 `malloc` 分配 `element_t` 大小的記憶體空間,若失敗則回傳 `false` 2. 使用函式 `strdup` 建立複製字串資料 3. 使用函式 `list_add_tail` 將節點加在 `head` 的前一個位置 ::: 實際程式碼: ```c bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *element = malloc(sizeof(*element)); if (!element) return false; // copy string element->value = strdup(s); if (!element->value) { free(element); return false; } // add node into doubly-linked list at tail list_add_tail(&element->list, head); return true; } ``` ### q_remove_head 函式功能: 在佇列開頭 (head) 移去 (remove) 給定的節點 :::info 函式流程 1. 如果佇列為 `NULL` 或是空佇列,則回傳 `NULL` 2. 移除佇列的第一個元素 (`head->next`) 並且取得其資料 3. 將資料複製到 buffer 中 ::: 實際程式碼: ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; struct list_head *node = head->next; element_t *element = list_entry(node, element_t, list); list_del(node); if (sp) { strncpy(sp, element->value, bufsize - 1); // add null terminator sp[bufsize - 1] = 0; } return element; } ``` ### q_remove_tail 函式功能: 在佇列尾端 (tail) 移去 (remove) 給定的節點 :::info 函式流程 1. 如果佇列為 `NULL` 或是空佇列,則回傳 `NULL` 2. 移除佇列的最後一個元素 (`head->prev`) 並且取得其資料 3. 將資料複製到 buffer 中 ::: 實際程式碼: ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; struct list_head *node = head->prev; element_t *element = list_entry(node, element_t, list); list_del(node); if (sp) { strncpy(sp, element->value, bufsize - 1); // add null terminator sp[bufsize - 1] = 0; } return element; } ``` ### q_size 函式功能: 計算目前佇列的節點總量 :::info 函式流程 1. 如果佇列為 `NULL` 則回傳 `0` 2. 走訪鏈結串列並計算走過的數量 ::: 實際程式碼: ```c int q_size(struct list_head *head) { if (!head) return 0; int count = 0; struct list_head *node; list_for_each (node, head) count++; return count; } ``` ### q_delete_mid 函式功能: 移走佇列中間的節點 :::info 函式流程 1. 如果佇列為 `NULL` 或是空佇列則回傳 `false` 2. 使用指標分別往前 (`forward`) 及往後 (`afterward`) 走,並找到中間的節點 (最後為 `forward` ) 3. 移除中間的節點並釋放其記憶體空間 ::: 實際程式碼: ```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 *afterward = head->next, *forward = head->prev; // move point to find the middle node for (; afterward != forward && afterward->next != forward; afterward = afterward->next, forward = forward->prev) ; // remove the middle node in queue list_del(forward); // delete the node q_release_element(list_entry(forward, element_t, list)); return true; } ``` ### q_delete_dup 函式功能: 在**已經排序**的狀況,移走佇列中具備重複內容的節點 :::info 函式流程 1. 如果佇列為 `NULL` 則回傳 `false` 2. 走訪整個鏈結串列並取得 `curr` 和 `next` 的元素地址 ```c list_for_each_entry_safe (curr_entry, next_entry, head, list) ``` 3. 當資料相同時,會對 `next` 指向的節點進行移除並釋放空間,同時 `flag` 被設置為 `true` ,字串比較則使用 [strcmp](https://man7.org/linux/man-pages/man3/strcmp.3.html) 函式,當資料相同時回傳 `0` ```c while (&next_entry->list != head && !strcmp(curr_entry->value, next_entry->value)) { list_del(&next_entry->list); q_release_element(next_entry); // update next pointer next_entry = list_entry(curr_entry->list.next, element_t, list); flag = true; } ``` 4. 當 `flag` 為 `true` 時,表示發生過資料相同的情況,但 `curr` 指到的節點尚未釋放,因此要記得釋放該空間 ```c // need remove current node if (flag) { list_del(&curr_entry->list); q_release_element(curr_entry); flag = false; } ``` ::: 實際程式碼: ```c bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head) return false; bool flag = false; element_t *curr_entry, *next_entry; list_for_each_entry_safe (curr_entry, next_entry, head, list) { while (&next_entry->list != head && !strcmp(curr_entry->value, next_entry->value)) { list_del(&next_entry->list); q_release_element(next_entry); // update next pointer next_entry = list_entry(curr_entry->list.next, element_t, list); flag = true; } // need remove current node if (flag) { list_del(&curr_entry->list); q_release_element(curr_entry); flag = false; } } return true; } ``` :::warning TODO: 用更精簡的方式撰寫程式碼。 :notes: jserv ::: 後來參考 [alanjian85](https://hackmd.io/@alanjian85/lab0-2023#2023q1-Homework1-lab0) 同學的作業,發現了更好的解法,兩者相同的部份都是使用指標 `prev` 及 `curr` 來判斷資料是否相同,而不同之處在於我原本的實作是移除 `curr` 指向的節點,後者則是移除 `prev` 的節點,如此一來可以節省 `curr` 的迴圈 修改後的程式碼如下所示,修改紀錄為 [commit](https://github.com/Risheng1128/lab0-c/commit/863444d138c17dd9698476af3a0e62376b2a67b3): ```c bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head) return false; bool flag = false; element_t *curr_entry, *next_entry; list_for_each_entry_safe (curr_entry, next_entry, head, list) { bool equal = &next_entry->list != head && !strcmp(curr_entry->value, next_entry->value); if (flag || equal) { list_del(&curr_entry->list); q_release_element(curr_entry); flag = equal; } } // need remove current node if (flag) { list_del(&curr_entry->list); q_release_element(curr_entry); } return true; } ``` ### q_swap 函式功能: 交換佇列中鄰近的節點 :::info 函式流程 1. 如果佇列為 `NULL` 則直接離開函式 2. 走訪鏈結串列,每次都將 `first` 指到的節點取出並加到 `second` 指到的節點後,如此一來就達到交換的功能 ::: 實際程式碼: ```c void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (!head) return; struct list_head *first = head->next; for (struct list_head *second = first->next; first != head && second != head; first = first->next, second = first->next) { // can swap list_del(first); list_add(first, second); } } ``` ### q_reverse 函式功能: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點 :::info 函式流程 1. 建立三個指標 prev, curr 及 next ,並依照 prev → curr → next 的順序指向不同節點 ```graphviz digraph { rankdir=LR; node[shape=record]; head [label="<list>head"] element1 [label="<list>bear"] element2 [label="<list>dolphin"] element3 [label="<list>meerkat"] dots [shape=none, label="..."] prev [shape=none, label="prev"] curr [shape=none, label="curr"] next [shape=none, label="next"] head:list -> element1:list element1:list -> head:list element1:list -> element2:list element2:list -> element1:list element2:list -> dots dots -> element2:list dots -> element3:list element3:list -> dots element3:list -> head:list head:list -> element3:list prev -> element3:list [color=red] curr -> head:list [color=red] } ``` 2. `next` 指到目前節點的下一個 ```graphviz digraph { rankdir=LR; node[shape=record]; head [label="<list>head"] element1 [label="<list>bear"] element2 [label="<list>dolphin"] element3 [label="<list>meerkat"] dots [shape=none, label="..."] prev [shape=none, label="prev"] curr [shape=none, label="curr"] next [shape=none, label="next"] head:list -> element1:list element1:list -> head:list element1:list -> element2:list element2:list -> element1:list element2:list -> dots dots -> element2:list dots -> element3:list element3:list -> dots element3:list -> head:list head:list -> element3:list prev -> element3:list curr -> head:list next -> element1:list [color=red] } ``` 3. 交換目前節點的下一個及上一個節點 ```graphviz digraph { rankdir=LR; node[shape=record]; head [label="<list>head"] element1 [label="<list>bear"] element2 [label="<list>dolphin"] element3 [label="<list>meerkat"] dots [shape=none, label="..."] prev [shape=none, label="prev"] curr [shape=none, label="curr"] next [shape=none, label="next"] head:list -> element1:list [label="prev" , color=red] element1:list -> head:list element1:list -> element2:list element2:list -> element1:list element2:list -> dots dots -> element2:list dots -> element3:list element3:list -> dots element3:list -> head:list head:list -> element3:list [label="next" , color=red] prev -> element3:list curr -> head:list next -> element1:list } ``` 4. 更新 `prev` 及 `curr` ```graphviz digraph { rankdir=LR; node[shape=record]; head [label="<list>head"] element1 [label="<list>bear"] element2 [label="<list>dolphin"] element3 [label="<list>meerkat"] dots [shape=none, label="..."] prev [shape=none, label="prev"] curr [shape=none, label="curr"] next [shape=none, label="next"] head:list -> element1:list element1:list -> head:list element1:list -> element2:list element2:list -> element1:list element2:list -> dots dots -> element2:list dots -> element3:list element3:list -> dots element3:list -> head:list head:list -> element3:list prev -> head:list [color=red] curr -> element1:list [color=red] next -> element2:list [color=red] } ``` 5. 完整走訪整個鏈結串列,即可完成反轉 ::: 實際程式碼: ```c void q_reverse(struct list_head *head) { if (!head) return; struct list_head *prev = head->prev, *curr = head, *next = NULL; while (next != head) { next = curr->next; curr->next = prev; curr->prev = next; prev = curr; curr = next; } } ``` ### q_reverseK 函式功能: 類似 `q_reverse` ,但指定每 k 個節點為一組,進行反向順序排列 以下的函式流程以 `trace-06-ops.cmd` 的測資為例 > l = [e, e, d, c, b, a, a, a], k = 3 :::info 函式流程 1. 如果佇列為 NULL 或是空佇列則直接離開函式 2. 切開鏈結串列裡最後一個節點回到 head 的連線 ```c head->prev->next = NULL; ``` ```graphviz digraph { rankdir=LR; node[shape=record]; h [label="head"] e1 [label="e"] e2 [label="e"] e3 [label="d"] e4 [label="c"] e5 [label="b"] e6 [label="a"] e7 [label="a"] e8 [label="a"] NULL [shape=none, label="NULL"] h -> e1 e1 -> h e1 -> e2 e2 -> e1 e2 -> e3 e3 -> e2 e3 -> e4 e4 -> e3 e4 -> e5 e5 -> e4 e5 -> e6 e6 -> e5 e6 -> e7 e7 -> e6 e7 -> e8 [color=red] e8 -> e7 e8 -> NULL h -> e8 } ``` 3. 當 `count` 第一次和 `k` 相同且在執行函式 `q_reverse` 之前,此時從佇列頭開始,可以看到形成了一個單向環狀鏈結串列 [e, e, d] ,這麼做的目的是要滿足函式 `q_reverse` 輸入,也就是函式 `q_reverse`是**以輸入節點的下一個節點 (head->next) 開始反轉** ```graphviz digraph { rankdir=LR; node[shape=record]; h [label="head"] e1 [label="e"] e2 [label="e"] e3 [label="d"] e4 [label="c"] e5 [label="b"] e6 [label="a"] e7 [label="a"] e8 [label="a"] sub_head [shape=none, label="sub_head"] sub_tail [shape=none, label="sub_tail"] old_tail [shape=none, label="old_tail"] next_head [shape=none, label="next_head"] NULL [shape=none, label="NULL"] h -> e1 e1 -> h e1 -> e2 e2 -> e1 e2 -> e3 e3 -> e2 e3 -> h [color=red] e4 -> e3 e4 -> e5 e5 -> e4 e5 -> e6 e6 -> e5 e6 -> e7 e7 -> e6 e7 -> e8 e8 -> e7 e8 -> NULL h -> e8 old_tail -> h [color=red] sub_head -> e1 [color=red] sub_tail -> e3 [color=red] next_head -> e4 [color=red] } ``` 4. 接著開始反轉,需注意的是原本的 `sub_head` 及 `sub_tail` 指向節點的相對位置會相反 ```graphviz digraph { rankdir=LR; node[shape=record]; h [label="head"] e1 [label="e"] e2 [label="e"] e3 [label="d"] e4 [label="c"] e5 [label="b"] e6 [label="a"] e7 [label="a"] e8 [label="a"] sub_head [shape=none, label="sub_head"] sub_tail [shape=none, label="sub_tail"] old_tail [shape=none, label="old_tail"] next_head [shape=none, label="next_head"] NULL [shape=none, label="NULL"] h -> e3 e3 -> h e3 -> e2 e2 -> e3 e2 -> e1 e1 -> e2 e1 -> h h -> e1 e4 -> e3 e4 -> e5 e5 -> e4 e5 -> e6 e6 -> e5 e6 -> e7 e7 -> e6 e7 -> e8 e8 -> e7 e8 -> NULL old_tail -> h sub_head -> e1 [color=red] sub_tail -> e3 [color=red] next_head -> e4 } ``` 5. 將 `old_tail->next` 指向 `sub_tail` 指到的節點且 `sub_head->next` 指向 `next->head` 指到的節點,前者在這部份不會有任何改變,但在鏈結串列之間反轉後,需要上次反轉後的 tail 接上,就會有所功能 ```graphviz digraph { rankdir=LR; node[shape=record]; h [label="head"] e1 [label="e"] e2 [label="e"] e3 [label="d"] e4 [label="c"] e5 [label="b"] e6 [label="a"] e7 [label="a"] e8 [label="a"] sub_head [shape=none, label="sub_head"] sub_tail [shape=none, label="sub_tail"] old_tail [shape=none, label="old_tail"] next_head [shape=none, label="next_head"] NULL [shape=none, label="NULL"] h -> e3 [color=red] e3 -> h e3 -> e2 e2 -> e3 e2 -> e1 e1 -> e2 e1 -> e4 [color=red] h -> e1 e4 -> e3 e4 -> e5 e5 -> e4 e5 -> e6 e6 -> e5 e6 -> e7 e7 -> e6 e7 -> e8 e8 -> e7 e8 -> NULL old_tail -> h sub_head -> e1 sub_tail -> e3 next_head -> e4 } ``` 6. 更新所有指標並重複上述的步驟 7. 最後利用函式 `restructure_list` 將鏈結串列接好 ::: 實際程式碼: ```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 *sub_head = head->next, *next_head = NULL, *old_tail = head; // cut the linked list to be singly-linked list head->prev->next = NULL; for (struct list_head *sub_tail = head->next; sub_tail; sub_tail = sub_tail->next) { if (++count == k) { next_head = sub_tail->next; sub_tail->next = old_tail; q_reverse(old_tail); // old node connects to the head of new list old_tail->next = sub_tail; // the new list connect to the next node sub_head->next = next_head; old_tail = sub_tail = sub_head; sub_head = next_head; count = 0; } } /* restructure the doubly-linked list */ restructure_list(head); } ``` 其中函式 `restructure_list` 的實作如下,功能為將雙向鏈結串列的 `prev` 指標接好 ```c void restructure_list(struct list_head *head) { struct list_head *curr = head, *next = curr->next; while (next) { next->prev = curr; curr = next; next = next->next; } curr->next = head; head->prev = curr; } ``` ### q_sort 函式功能: 以**遞增順序**來排序鏈結串列的節點 :::info 函式流程 函式 `q_sort` 的實作被主要拆為三個函式 `q_sort`, `mergesort` 及 `mergelist` `q_sort`: 1. 首先將指向 `head` 的指標改成 `NULL`,目的在於把雙向鏈結串列的終點從 `head` 改為 `NULL`,變成單向鏈結串列 ```c head->prev->next = NULL; ``` 2. 接著呼叫函式 `mergesort` 開始進行 merge sort ```c head->next = mergesort(head->next); ``` 3. 排序完後每個節點的 `prev` 會亂掉,因此需要走訪各個節點並且把所有 `prev` 接回來 ```c restructure_list(head); ``` `mergesort`: 參考[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)裡, merge sort 的實作方式並做修改 1. 使用「快慢指標」[Floyd’s Cycle detection](https://en.wikipedia.org/wiki/Cycle_detection) 演算法找出鏈結串列正中間的節點,並將鏈結串列切成 `left` 及 `right` 兩組鏈結串列 ```c struct list_head *fast = head, *slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } struct list_head *mid = slow; slow->prev->next = NULL; struct list_head *left = mergesort(head); struct list_head *right = mergesort(mid); ``` 2. 呼叫函式 `mergelist` 合併 `left` 及 `right` ```c return mergelist(left, right); ``` `mergelist`: 參考 [21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/) ,並實作合併兩個鏈結串列 1. 利用指標的指標 `indirect` 指向指標 `res` ,並藉由修改指標完成鏈結串列合併的動作 ```c struct list_head *res = NULL; struct list_head **indirect = &res; ``` 2. 使用函式 `strcmp` 判斷資料的大小 ```c node = strcmp(list1_entry->value, list2_entry->value) < 0 ? &list1 ``` ::: 實際程式碼: ```c /* Merge the two lists in a one sorted list. */ static struct list_head *mergelist(struct list_head *list1, struct list_head *list2) { struct list_head *res = NULL; struct list_head **indirect = &res; for (struct list_head **node = NULL; list1 && list2; *node = (*node)->next) { element_t *list1_entry = list_entry(list1, element_t, list); element_t *list2_entry = list_entry(list2, element_t, list); node = strcmp(list1_entry->value, list2_entry->value) < 0 ? &list1 : &list2; *indirect = *node; indirect = &(*indirect)->next; } *indirect = (struct list_head *) ((size_t) list1 | (size_t) list2); return res; } /* * Divide the list into several nodes and merge to sorted list. * No effect if q is NULL or empty. */ static struct list_head *mergesort(struct list_head *head) { if (!head || !head->next) return head; struct list_head *fast = head, *slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } struct list_head *mid = slow; slow->prev->next = NULL; struct list_head *left = mergesort(head); struct list_head *right = mergesort(mid); return mergelist(left, right); } /* Sort elements of queue in ascending order */ void q_sort(struct list_head *head) { if (!head || list_empty(head)) return; // cut the linked list to be singly-linked list head->prev->next = NULL; head->next = mergesort(head->next); /* restructure the doubly-linked list */ restructure_list(head); } ``` ### q_descend 函式功能: 對所有節點而言,移除所有在其之前且資料較小的節點 思考邏輯: 目標是把對每個節點之前,所有資料比其小的節點移除,換言之,其實可以利用雙向鏈結串列的特性,反向走訪所有的節點,並產生遞增的鏈結串列 實際程式碼: ```c int q_descend(struct list_head *head) { // https://leetcode.com/problems/remove-nodes-from-linked-list/ if (!head || list_empty(head) || list_is_singular(head)) return 0; for (struct list_head *curr = head->prev, *next = curr->prev; next != head; next = curr->prev) { element_t *curr_entry = list_entry(curr, element_t, list); element_t *next_entry = list_entry(next, element_t, list); // if current node is greater than next node if (strcmp(curr_entry->value, next_entry->value) > 0) { list_del(next); q_release_element(next_entry); } else curr = next; } return q_size(head); } ``` ### q_merge 函式功能: 合併所有**已排序**的佇列,並合而為一個新的已排序佇列 :::info 函式流程 1. 利用指標 `merge` 暫時儲存已經合併的佇列 2. 走訪所有的佇列並一個一個和 `merge` 合併 (利用函式 `mergelist`) ,由於函式 `mergelist` 的輸入皆為單向鏈結串列,因此這裡需要先將原本的佇列變成單向鏈結串列,再進行合併 ```c head_entry->q->prev->next = NULL; merge = mergelist(merge, head_entry->q->next); ``` 3. 將合併完的佇列復原 ```c head_entry->q->next = head_entry->q; ``` 4. 最後將合併的佇列接在 `chain` 的第一個元素上,並使用函式 `restructure_list` 將 `prev` 完整連接 ```c head_entry = list_entry(head->next, queue_contex_t, chain); head_entry->q->next = merge; /* restructure the doubly-linked list */ restructure_list(head_entry->q); ``` ::: 實際程式碼: ```c int q_merge(struct list_head *head) { // https://leetcode.com/problems/merge-k-sorted-lists/ if (!head || list_empty(head)) return 0; struct list_head *merge = NULL; queue_contex_t *head_entry = NULL; list_for_each_entry (head_entry, head, chain) { // cut the linked list to be singly-linked list head_entry->q->prev->next = NULL; merge = mergelist(merge, head_entry->q->next); head_entry->q->next = head_entry->q; } head_entry = list_entry(head->next, queue_contex_t, chain); head_entry->q->next = merge; /* restructure the doubly-linked list */ restructure_list(head_entry->q); return q_size(head_entry->q); } ``` ## 研讀 Linux 核心原始碼 [list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 原始碼的運作分析放在額外的筆記 [研讀 Linux 核心原始程式碼 `list_sort.c`](https://hackmd.io/@Risheng/list_sort) ### 引入 Linux 核心原始碼 [list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 到 lab0-c 完整的實作紀錄可參考 [commit](https://github.com/Risheng1128/lab0-c/commit/0368c53c4d81dbaac9c582db2e9f45b938c49dfd) ,以下為實作的流程 首先,將 Linux 核心原始檔案 [list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 及 [list_sort.h](https://github.com/torvalds/linux/blob/master/include/linux/list_sort.h) 的相關程式碼加進 lab0-c 並作修改,其中包含 list_sort 完整的程式碼及其函式定義,接著將以下 lab0-c 沒有的巨集函式加進 `list_sort.h` - `likely()` (位於 [`include/linux/compiler.h`](https://github.com/torvalds/linux/blob/master/include/linux/compiler.h)) - `unlikely()` (位於 [`include/linux/compiler.h`](https://github.com/torvalds/linux/blob/master/include/linux/compiler.h)) 完成的 `list_sort.h` 實際程式碼如下所示: ```c #pragma once #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) #endif #ifndef unlikely #define unlikely(x) __builtin_expect(!!(x), 0) #endif struct list_head; __attribute__((nonnull(2, 3))) typedef int (*list_cmp_func_t)( void *, const struct list_head *, const struct list_head *); __attribute__((nonnull(2, 3))) void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp); __attribute__((nonnull(2, 3))) int list_cmp(void *priv, const struct list_head *list1, const struct list_head *list2); ``` 接著在 `Makefile` 新增要編譯出的目標檔案,這裡可由變數 `ENABLE_LINUX_SORT` 讓使用者決定是否要使用 `list_sort` ``` ENABLE_LINUX_SORT ?= 1 ifeq ($(ENABLE_LINUX_SORT), 1) CFLAGS += -D FEATURE_LINUX_SORT=1 OBJS += list_sort.o endif ``` 在 `list_sort.c` 新增函式 `list_cmp` ,用來比較節點的資料 ```c /* List compare funciton */ __attribute__((nonnull(2, 3))) int list_cmp(void *priv, const struct list_head *list1, const struct list_head *list2) { // cppcheck-suppress nullPointer element_t *list1_entry = list_entry(list1, element_t, list); // cppcheck-suppress nullPointer element_t *list2_entry = list_entry(list2, element_t, list); return strcmp(list1_entry->value, list2_entry->value) <= 0 ? 0 : 1; } ``` 最後則是修改檔案 `qtest.c` 裡的函式 `do_sort` ,並搭配剛剛在 `Makefile` 裡設定的參數來決定要使用原本的 `q_sort` 或是新引入的 list_sort ```c bool do_sort(int argc, char *argv[]) { ... if (current && exception_setup(true)) #if FEATURE_LINUX_SORT list_sort(NULL, current->q, list_cmp); #else q_sort(current->q); #endif ... } ``` ### 比較自己實作的 merge sort 和 Linux 核心程式碼 首先建立一個用來量測排序時間的測試檔 `trace-time-measure.cmd` ,內容如下所示,其中隨機產生的資料數會改變 ``` option fail 0 option malloc 0 new ih RAND 100000 time sort time ``` 接著修改以下的 `Makefile` 腳本並輸入命令 `make check` 來分別測試 `q_sort` 及 `list_sort` ``` check: qtest ./$< -v 3 -f traces/trace-time-measure.cmd ``` 可以統計出兩者執行的時間,測試方式為對每個函式分別測試 3 次並紀錄其排序時間,同時資料總數從 100000 以公差為 100000 遞增到 500000 | 資料總數 | `q_sort` 第一次 (s) | `q_sort` 第二次 (s) | `q_sort` 第三次 (s) | `list_sort` 第一次 (s) | `list_sort` 第二次 (s) | `list_sort` 第三次 (s) | | ------- | ----- | ----- | ----- | ----- | ----- | ----- | | 100000 | 0.072 | 0.071 | 0.068 | 0.039 | 0.040 | 0.038 | | 200000 | 0.190 | 0.222 | 0.200 | 0.114 | 0.120 | 0.114 | | 300000 | 0.333 | 0.361 | 0.330 | 0.196 | 0.198 | 0.194 | | 400000 | 0.488 | 0.528 | 0.517 | 0.286 | 0.284 | 0.301 | | 500000 | 0.646 | 0.685 | 0.699 | 0.376 | 0.393 | 0.363 | 接著將以上的數據取平均後如下所示 | 資料總數 | `q_sort` 平均 (s) | `list_sort` 平均 (s) | | ------- | ----- | ----- | | 100000 | 0.070 | 0.039 | | 200000 | 0.204 | 0.116 | | 300000 | 0.341 | 0.196 | | 400000 | 0.511 | 0.290 | | 500000 | 0.676 | 0.377 | 最後使用 [gnuplot](https://en.wikipedia.org/wiki/Gnuplot) 畫圖,參考 [gnuplot 語法解說和示範](https://hackmd.io/@sysprog/gnu-linux-dev/https%3A%2F%2Fhackmd.io%2Fs%2FSkwp-alOg) ,以下為比較結果,可以發現 `list_sort` 的排序時間比起 `q_sort` 快了不少 ![](https://i.imgur.com/E2XXG1A.png) 接著利用 [perf](https://perf.wiki.kernel.org/index.php/Main_Page) 找到兩者的差異,安裝可以參考 [Linux 效能分析工具: Perf](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial) 輸入命令 `perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles ./qtest -f traces/trace-time-measure.cmd` &rarr; 程式會執行 5 次,並且自動平均,其中每次測試的資料數都設定為 500000 ,以下為 `q_sort` 及 `list_sort` 執行的結果 首先是 `q_sort` 的結果 ```shell Performance counter stats for './qtest -f traces/trace-time-measure.cmd' (5 runs): 2256,8287 cache-misses # 68.299 % of all cache refs ( +- 0.15% ) 3288,3709 cache-references ( +- 0.36% ) 22,6838,1411 instructions # 0.44 insn per cycle ( +- 0.00% ) 50,7244,9200 cycles ( +- 0.51% ) 1.18335 +- 0.00408 seconds time elapsed ( +- 0.34% ) ``` 接著是 `list_sort` 的結果 ``` Performance counter stats for './qtest -f traces/trace-time-measure.cmd' (5 runs): 1887,5232 cache-misses # 72.800 % of all cache refs ( +- 0.22% ) 2577,8636 cache-references ( +- 0.26% ) 22,7975,0582 instructions # 0.71 insn per cycle ( +- 0.02% ) 31,8624,7214 cycles ( +- 0.38% ) 0.73931 +- 0.00590 seconds time elapsed ( +- 0.80% ) ``` 將輸出的結果做成表格,如下表所示 | | `q_sort` | `list_sort` | | ---------------- | ------------ | ------------ | | cycles | 50,7244,9200 | 31,8624,7214 | | instructions | 22,6838,1411 | 22,7975,0582 | | cache-references | 3288,3709 | 2577,8636 | | cache-misses | 2256,8287 | 1887,5232 | | insn per cycle | 0.44 | 0.71 | 可以發現 `list_sort` 發生 cache miss 的次數比 q_sort 來的少,可以對應到 [Linux 核心的鏈結串列排序](https://hackmd.io/@sysprog/linux2023-lab0/%2F%40sysprog%2Flinux2023-lab0-e) 裡提到「用 bottom up 實作 merge sort 對 cache 較友善,可避免大量的 [cache trashing](https://en.wikipedia.org/wiki/Thrashing_(computer_science))」 :::warning 探討 `list_sort.c` 的比較次數。 :notes: jserv ::: ## 運用 Valgrind 排除 qtest 實作的記憶體錯誤 輸入命令 `make valgrind` 對資料夾 `traces` 裡的所有測試資料使用 valgrind 進行檢查 :::spoiler 測試結果 ```shell +++ 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 ``` ::: 由以上的測試結果,可以發現對於目前的實作, valgrind 沒有發出任何的警告 接著使用 [Massif](https://valgrind.org/docs/manual/ms-manual.html) 觀察記憶體使用情況,輸入命令 `valgrind -v --tool=massif ./qtest -f traces/trace-01-ops.cmd` 產生輸出檔 `massif.out.17672` 接著輸入命令 `massif-visualizer ./massif.out.17672` 讀取上述指令產生的輸出檔,以下為結果: ![](https://i.imgur.com/OX1gyZg.png) 以 `traces/trace-01-ops.cmd` 為例,可以發現所有分配的記憶體在最後會完全被釋放 --- ## 確保 qtest 提供的 `web` 命令得以搭配上述佇列實作使用 經過實驗證實,目前的實作會有以下的問題 :::warning 問題: 在輸入命令 `web` 並建立連線後,會有以下的問題 1. 當對伺服器端送出請求後,此時再從命令列透過 [stdio](https://man7.org/linux/man-pages/man3/stdio.3.html) 送出命令後,會產生一段很長的延遲,也就是後者的命令過了很久才會被處理 2. 對伺服器端送出請求後,會產生以下的錯誤訊息 > Unknown command 'favicon.ico' ::: 首先從第一個問題,可以得知上述提到的延遲是由於 lab0-c 內部的函式 [`read`](https://man7.org/linux/man-pages/man2/read.2.html) 壅塞所導致,主要是 I/O 正在處理來自客戶端或是來自命令列的命令,使其一無法馬上執行。因此這邊使用函式 [select](https://man7.org/linux/man-pages/man2/select.2.html) 對檔案描述子 (file descriptor) 進行管理,以下為 select 的 I/O 模型,屬於 [Multiplexing](https://en.wikipedia.org/wiki/Multiplexing) 模型,分成兩部份,第一部份為 select ,目的為等待準備好的資料,並回傳已經準備好的資料數,第二部份則是讀取準備好的資料。透過這樣的方法,可以一次管理多個檔案描述子。 > descriptor 翻譯為「描述子」,參見 [operator 為什麼翻譯為運算子](https://dev.to/codemee/kao-gu-operator-wei-shi-mo-fan-yi-wei-yun-suan-zi--1m7) > :notes: jserv ![](https://i.imgur.com/uzNyxDA.png) 而目前的目標則是**同時管理 stdin 及 socket 的資料**,首先可以參考原本管理 I/O 的實作,位於檔案 `console.c` 的函式 `run_console` ```c= bool run_console(char *infile_name) { if (!push_file(infile_name)) { report(1, "ERROR: Could not open source file '%s'", infile_name); return false; } if (!has_infile) { char *cmdline; while (use_linenoise && (cmdline = linenoise(prompt))) { interpret_cmd(cmdline); line_history_add(cmdline); /* Add to the history. */ line_history_save(HISTORY_FILE); /* Save the history on disk. */ line_free(cmdline); while (buf_stack && buf_stack->fd != STDIN_FILENO) cmd_select(0, NULL, NULL, NULL, NULL); has_infile = false; } if (!use_linenoise) { while (!cmd_done()) cmd_select(0, NULL, NULL, NULL, NULL); } } else { while (!cmd_done()) cmd_select(0, NULL, NULL, NULL, NULL); } return err_cnt == 0; } ``` 將目光放在上述程式的變數 `use_linenoise` ,其功能是用來區別目前輸入為 stdin 或是 socket ,當輸入命令 `web` 時,根據函式 `do_while` 的內容,可以發現變數 `use_linenoise` 被設定為 `false` (下列程式碼第 12 行) ,同時也表示下次將讀取 socket 的資料 ```c= static bool do_web(int argc, char *argv[]) { int port = 9999; if (argc == 2) { if (argv[1][0] >= '0' && argv[1][0] <= '9') port = atoi(argv[1]); } web_fd = web_open(port); if (web_fd > 0) { printf("listen on port %d, fd is %d\n", port, web_fd); use_linenoise = false; } else { perror("ERROR"); exit(web_fd); } return true; } ``` 因此可以得知函式 `run_console` 第 10 行的迴圈用來處理來自 stdin 的資料,而第 19 行則是處理來自 socket 的資料 接著修改函式 `run_console` 如下所示,移除變數 `use_linenoise` 並且由函式 `linenoise` 來同時管理兩者的資料 ```c bool run_console(char *infile_name) { if (!push_file(infile_name)) { report(1, "ERROR: Could not open source file '%s'", infile_name); return false; } if (!has_infile) { char *cmdline; while ((cmdline = linenoise(prompt))) { interpret_cmd(cmdline); line_history_add(cmdline); /* Add to the history. */ line_history_save(HISTORY_FILE); /* Save the history on disk. */ line_free(cmdline); while (buf_stack && buf_stack->fd != STDIN_FILENO) cmd_select(0, NULL, NULL, NULL, NULL); has_infile = false; } } else { while (!cmd_done()) cmd_select(0, NULL, NULL, NULL, NULL); } return err_cnt == 0; } ``` 接著引入上述提到的函式 `select` 到函式 `linenoise` 內部,主要修改位於檔案 `linenoise.c` 的核心函式 `line_edit` 。首先使用關鍵字 `extern` 取得 `console.c` 的變數 `web_fd` ,其為 socket 檔案描述子 ```c extern int web_fd; fd_set read_set; ``` 當然變數 `web_fd` 的宣告也要作修改 ```diff - static int web_fd; + int web_fd = -1; ``` 接著在函式 `line_edit` 開始實作 select 的設定,可以參考 CS:APP 的第 12 章,前面提到要管理 stdin 及 socket ,因此設定的部份可以對應到下列程式的第 3 行及第 7 行,使用巨集函式 `FD_SET` 啟動 select 的監聽,其他的巨集函式可以參考 [select(2) — Linux manual page](https://man7.org/linux/man-pages/man2/select.2.html) ```c= while (1) { FD_ZERO(&read_set); /* clear read set */ FD_SET(l.ifd, &read_set); /* set stdin fd */ int fd = l.ifd + 1; /* If web not ready listen */ if (web_fd != -1) { FD_SET(web_fd, &read_set); /* set web fd */ fd = web_fd + 1; } ... } select(fd, &read_set, NULL, NULL, NULL); ``` 接著是讀取函式的部份,實作如下所示,有兩種情況 — socket 及 stdin ,前者主要是使用 `web_recv` 讀取資料,並且對客戶端回傳 [HTTP 狀態碼](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status) ,後者則是維持原本的實作,使用函式 `read` 讀取來自 stdin 的資料 ```c if ((web_fd != -1) && FD_ISSET(web_fd, &read_set)) { struct sockaddr_in clientaddr; socklen_t clientlen = sizeof(clientaddr); FD_CLR(web_fd, &read_set); int web_connfd = accept(web_fd, (struct sockaddr *) &clientaddr, &clientlen); char *p = web_recv(web_connfd, &clientaddr); int len = strlen(p); for (int i = 0; i < len; i++) line_edit_insert(&l, p[i]); char *buffer = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; web_send(web_connfd, buffer); free(p); close(web_connfd); } else if (FD_ISSET(l.ifd, &read_set)) { signed char c; int nread; char seq[5]; FD_CLR(l.ifd, &read_set); nread = read(l.ifd, &c, 1); if (nread <= 0) return l.len; ... } ``` 接著將 socket 設定為 non-blocking 模式,修改檔案 `web.c` 裡的函式 `web_open` ,如下所示 ```diff /* Create a socket descriptor */ - if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + if ((listenfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) return -1; + /* set socket non-blocking */ + int flags = fcntl(listenfd, F_GETFL); + fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); ``` 另外要特別注意,當 socket 設定為 non-blocking 模式時,此時的讀取函式 `read` 需要考慮到回傳值 `EAGAIN` 及 `EWOULDBLOCK` ,可參考 [read(2) — Linux manual page](https://man7.org/linux/man-pages/man2/read.2.html) ,以下為說明 > - **EAGAIN or EWOULDBLOCK**: The file descriptor fd refers to a socket and has been marked nonblocking (O_NONBLOCK), and the read would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities. 因此將函式 `rio_read` 進行修改 ```diff static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) { int cnt; while (rp->count <= 0) { /* refill if buf is empty */ rp->count = read(rp->fd, rp->buf, sizeof(rp->buf)); if (rp->count < 0) { + // no data available yet + if (errno == EWOULDBLOCK || errno == EAGAIN) + rp->count = 0; /* and call read() again */ - if (errno != EINTR) /* interrupted by sig handler return */ + else if (errno != EINTR) /* interrupted by sig handler return */ return -1; } else if (rp->count == 0) { /* EOF */ return 0; } else rp->bufptr = rp->buf; /* reset buffer ptr */ } ... } ``` :::warning 依循 [CONTRIBUTING.md](https://github.com/sysprog21/lab0-c/blob/master/CONTRIBUTING.md) 規範,在迴圈內讓 EOF 狀況及早脫離 :notes: jserv ::: 接著是目前的展示成果,主要展示當 stdin 已經先輸入資料但尚未處理,接著從另一個終端機透過 socket 輸入命令,並不會導致資料阻塞,完整實作可見 [commit 26a5dc](https://github.com/Risheng1128/lab0-c/commit/26a5dc3f49069fe97226faea129f4e2a6cfc7cc1) :::info Note: 由於目前讀取資料後的操作是透過函式 `line_edit_insert` 處理,因此會讓兩個 I/O 的資料拼接在一起 ::: {%youtube X_sJdTpZUdg %} :::warning TODO: 1. 處裡 "Unknown command 'favicon.ico'" 的問題 2. 確認 linenoise 對於 web 端輸入的命令也能展現歷史紀錄 ::: ## 在 qtest 提供新的命令 `shuffle`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully