# 2024q1 Homework1 (lab0) contributed by < [`wilson20010327`](https://github.com/wilson20010327) > ## 實驗環境 ```bash $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 $ 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): 6 On-line CPU(s) list: 0-5 Thread(s) per core: 1 Core(s) per socket: 6 Socket(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 158 Model name: Intel(R) Core(TM) i5-9400F CPU @ 2.90GHz Stepping: 10 CPU MHz: 2903.998 BogoMIPS: 5807.99 ``` ## 指定的佇列操作 ### `q_reverse` > commit [31e3fd3](https://github.com/wilson20010327/lab0-c/commit/31e3fd3c75430c67aec3d99bb54c231bf339d9e5) 將佇列中的所有節點以原先的順序走訪,並將所經歷的每個節點移至佇列的頭部,此就可以形成一個與原先佇列反轉的新佇列。 :::warning 改進你的漢語表達,用精準的詞彙。 ::: ```c /* Reverse elements in queue */ void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *pos, *safe; list_for_each_safe (pos, safe, head) { list_move(pos, head); } return; } ``` ### `q_delete_dup` > commit [96337bf](https://github.com/wilson20010327/lab0-c/commit/96337bf25330560d4d9c88d5736c95de371099fa) :::danger 注意用語。 $\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary) ::: <s>函數</s> 函式會先將一個指標 `new_head` 指向佇列最初的節點,然後開始走訪整個佇列。`pos` 作為現在所抵達的節點,`safe` 為下一個節點。 :::danger 避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。 ::: 若 `pos` 以及 `safe` 的資料相同以及 `pos` 不是佇列最後一個節點,則 `new_head` 不做更新。反之當 `pos` 以及 `safe` 的資料不相相同或 `pos` 是佇列最後一個節點,檢查 `new_head` 是否為 `pos` 的 `list`:若為是,表示前面沒有擁有重複的資料,將 `new_head` 賦予 `safe->list` 的位置。若為否,表示前面節點有重複的資料,將從 `new_head` 至 `pos` 所有節點剪下併接至新指派的佇列 `pending_head` 並將 `new_head` 賦予 `safe->list` 的位置。 :::danger 「走訪」和「整個」具備相同的語境,避免贅詞。可改說「逐一走訪」 ::: <s>走訪整個</s> 逐一走訪佇列後,再將 `pending_head` 指向的佇列清除。 ```c /* Delete all nodes that have duplicate string */ bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head) return 0; LIST_HEAD(pending_head); element_t *pos, *safe; struct list_head *new_head = head->next; list_for_each_entry_safe (pos, safe, head, list) { if ((&safe->list != head) && !strcmp(pos->value, safe->value)) /* if match*/ continue; else { /* if no match or the end */ if (&pos->list != new_head) { LIST_HEAD(temp_head); list_cut_position(&temp_head, new_head->prev, &pos->list); list_splice(&temp_head, &pending_head); } new_head = &safe->list; } } list_for_each_entry_safe (pos, safe, &pending_head, list) q_release_element(pos); return true; } ``` ### `q_merge2` 在實作中,需要執行將兩佇列結合,因此將此部分實作成一個函式。 :::danger `q_merge2` 存在的考量是什麼? ::: > commit [cfd28e8](https://github.com/wilson20010327/lab0-c/commit/cfd28e8c68c7835a6556570bca30c67d189998ff) 將兩個依照 `descend` 方向排列的佇列合成一個 `descend` 方向排列的佇列 ```c void q_merge2(struct list_head *head,struct list_head *second_head,bool descend) { /* merge two list and head will point to the new list */ if (!head || !second_head) return; LIST_HEAD(temp); while (!list_empty(head) && !list_empty(second_head)) { element_t *node1 = list_first_entry(head, element_t, list); element_t *node2 = list_first_entry(second_head, element_t, list); bool cmp = (strcmp(node1->value, node2->value) <= 0); cmp = descend ? !cmp : cmp; if (cmp) { list_move_tail(&node1->list, &temp); } else { list_move_tail(&node2->list, &temp); } } /* splice the list in front of the list pointed by head and head point to * the new list */ list_splice(&temp, head); if (!list_empty(second_head)) { list_splice_tail(second_head, head); } } ``` ### q_merge > commit [c9bef2e](https://github.com/wilson20010327/lab0-c/commit/c9bef2ef57ba680f5abcf148dea006a3bb8fe8cb) ```c int q_merge(struct list_head *head, bool descend) { // https://leetcode.com/problems/merge-k-sorted-lists/ if (!head || list_empty(head)) return 0; if (list_is_singular(head)) return list_first_entry(head, queue_contex_t, chain)->size; LIST_HEAD(stack); queue_contex_t *cur, *safe; int count = 0; list_for_each_entry_safe (cur, safe, head, chain) { count += cur->size; q_merge2(cur->q, &stack, descend); } list_splice(&stack, list_first_entry(head, queue_contex_t, chain)->q); return count; } ``` 在持續開發後發現在 Linux 核心風格的 List API 中,在串接鏈結串列時,通常以第一個參數接至第二個參數中 ( e.g., `list_splice`, `list_move_tail`),因此我將自己撰寫的 `q_merge2` 的參數順序做變更滿足此要求。除此之外,在使用時使用者或許會在兩個佇列合併之後繼續使用 `second_head` , 若未在合併之後重新初始化 `second_head` 會造成程式錯誤,程式持續執行但是已無法對其正常操作,以下為執行的執行過程以及結果。 ``` cmd> new l = [] cmd> it aaa l = [aaa] cmd> it bbb l = [aaa bbb] cmd> it ccc l = [aaa bbb ccc] cmd> it ddd l = [aaa bbb ccc ddd] cmd> it eee l = [aaa bbb ccc ddd eee] cmd> new l = [] cmd> it aaa l = [aaa] cmd> it bbb l = [aaa bbb] cmd> merge ``` 當執行後整個程式仍然在執行,輸入也會正常顯示,但已經跟無法跟正常時一樣做相關的操作,我認為是因為並未初始化 `head` 使的 console 沒辦法取的正常的數值,而其就卡在要列印出現在 `head` 指向的佇列,我無法驗證我的猜想,我目前認為是因為在 console 呈現佇列數值的程式不在測時的範圍,而此就會造成這個問題,會進一步研究。 ```diff - void q_merge2(struct list_head *head,struct list_head *second_head,bool descend) + void q_merge2(struct list_head *second_head,struct list_head *head,bool descend) { /* merge two list and head will point to the new list */ @@ -224,7 +224,7 @@ void q_merge2(struct list_head *head, * the new list */ list_splice(&temp, head); if (!list_empty(second_head)) { - list_splice_tail(second_head, head); + list_splice_tail_init(second_head, head); } } ``` ### 尋找上述問題以及試著解決 > commit [4fe0c35](https://github.com/wilson20010327/lab0-c/commit/4fe0c350ed2a9dee604271c5ad6263ac7a706132) > 使用 gdb 進行除錯結果為下,在 qtest.c 中的 q_show 函式會在印出之佇列之前檢測佇列是否為雙向循環佇列,問題點出在檢測的函式。 ```shell is_circular () at qtest.c:883 883 while (cur != current->q) { (gdb) print cur $1 = (struct list_head *) 0x55555556a408 (gdb) step 884 if (!cur) (gdb) step 886 cur = cur->next; (gdb) step 883 while (cur != current->q) { (gdb) print cur $2 = (struct list_head *) 0x55555556a118 (gdb) step 3 883 while (cur != current->q) { (gdb) print cur $3 = (struct list_head *) 0x55555556a498 (gdb) step 3 883 while (cur != current->q) { (gdb) print cur $4 = (struct list_head *) 0x55555556a1c8 (gdb) step 3 883 while (cur != current->q) { (gdb) print cur $5 = (struct list_head *) 0x55555556a278 (gdb) step 3 883 while (cur != current->q) { (gdb) print cur $6 = (struct list_head *) 0x55555556a308 (gdb) step 3 883 while (cur != current->q) { (gdb) print cur $7 = (struct list_head *) 0x55555556a068 (gdb) step 3 883 while (cur != current->q) { (gdb) print cur $8 = (struct list_head *) 0x55555556a408 ``` 在 is_circular 函式中檢測方式是以 while 迴圈做檢測,其中止條件為節點為佇列的頭或是節點為 NULL ,若此佇列為中的一部分為 loop 會造成無窮迴圈。將 is_circular 函式從單指針方法改為使用慢指針和快指針。單指針方法無法在不使用額外儲存的情況下檢測到發生在佇列中的 loop 。利用慢指針和快指針可以有效地檢測到佇列中的 loop 。但這種方法稍微增加的條件檢查。 ```diff= static bool is_circular() { struct list_head *cur = current->q->next; + struct list_head *fast = (cur) ? cur->next : NULL; while (cur != current->q) { - if (!cur) + if (!cur || !fast || !(fast->next)) return false; + if (cur == fast) + return false; cur = cur->next; + fast = fast->next->next; } ... } ``` --- before rebase ## 整合網頁伺服器 note : 作業描述的 `linenoiseEdit` 位於 `linenoise.c/line_edit` 在作業介紹以及測試時,都可以看到若透過瀏覽器發送 http request 可以順利的產生對應指令,但會多出 `cmd> Unknown command 'favicon.ico'` 根據了作業說明做了更改。 ```diff @@ -617,7 +617,14 @@ static int cmd_select(int nfds, accept(web_fd, (struct sockaddr *) &clientaddr, &clientlen); char *p = web_recv(web_connfd, &clientaddr); - char *buffer = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; + char *buffer = + "HTTP/1.1 200 OK\r\n%s%s%s%s%s%s" + "Content-Type: text/html\r\n\r\n" + "<html><head><style>" + "body{font-family: monospace; font-size: 13px;}" + "td {padding: 1.5px 6px;}" + "</style><link rel=\"shortcut icon\" href=\"#\">" + "</head><body><table>\n"; web_send(web_connfd, buffer); if (p) ``` ### 後續發現與探討 經過改正後,以瀏覽器測試確實不會再有 `cmd> Unknown command 'favicon.ico'` 提示了,但也生成了其他問題。首先,以瀏覽器所送出的指令會被執行兩次,且若以 `curl http://localhost:9999/new` 進行測試則會出現我們在 `buffer` 補上後續的額外資料,如下圖 <s> ![image](https://hackmd.io/_uploads/rkZjEXGpT.png) </s> :::danger 文字訊息避免用圖片來表示,否則不好搜尋和分類。 ::: #### 驗證問題點 * 以 `wireshark` 檢測封包狀況,如下圖。以下情況為以瀏覽器下達一次 `new` <s>指令</s> 命令後下達 `quit` <s>指令</s> 命令一次,可以看到瀏覽器竟然會傳送兩次相同的請求![image](https://hackmd.io/_uploads/SJ7Czz7p6.png) :::danger command 是「命令」,而非「指令」(instruction) ::: ### 額外發現 在此次測試中,使用兩個不同的瀏覽器 (Brave, Microsoft Edge) 做為測試媒介分別稱之為 A、B。首先執行程式並且執行 `web` 模式,以 A 先下達指令一系列<s>指令</s> 命令,再開啟 B 下達<s>指令</s> 命令,這時 B 所下達的命令部會被執行直到再以 A 執行一次命令, B 所下達的指令才會被執行,相同的狀況也發生在兩個不同的執行輸入(command line、瀏覽器)此情況下前者為 B ,後者為 A。 ## Rebase from remote ```shell git remote add upstream https://github.com/sysprog21/lab0-c.git git fetch upstream git rebase upstream/master git push origin master --force ```