contributed by < LJP-TW
>
嘗試盡量使用 Linux 核心風格的鏈結串列 API 進行各函數的實作。
檢查是否能分配空間,並且做 list_head
的初始化。
釋放整條 queue 所占用的記憶體,由於都要歸還記憶體了,已經無需再將 element 從 queue 中移除鏈結。
從 queue 的頭部新增節點,檢查是否能正常分配記憶體,字串長度需要為了 NULL byte 預留空間。
與 q_insert_head
類似。
從 queue 的頭部移除一個節點,複製字串到 sp
時要預留 NULL byte 的空間。
與 q_remove_head
類似。
沒有做改動。
需要走過 queue 計算長度,時間複雜度為 O(n)
。
用兩個 pointer 走過 queue,curr
走訪速度為 mid
兩倍,因此當 curr
到達 queue 的結尾時,mid
自然是在 queue 的中間,找到 mid
後刪除該節點。
呼叫此函式前,queue 已經排序完畢,因此走訪過程中,只需要判斷下個節點的值是否與當前節點相同,作為是否要刪除節點的依據。在 do
while
後,還要再刪除 currelm
,但這個寫法不夠簡潔,還有改善空間。
每經過兩個節點就交換兩者在 queue 中的順序。list.h
沒有與交換節點相關的工具能夠使用,或許能夠增加相關工具 macro / function。
將整個 queue 的順序倒轉,只要交換每個節點 (包含 head
) 的 prev
和 next
即可。
TODO: 針對上方的若干操作,提取出可共用的 inline function
使用 merge sort 進行排序,先將 doubly-linked list 的 prev
和 next
指標拆開來變成兩個 singly-linked list,以 next
組成的 singly-linked list 用來串接排序好的節點,形成一個個獨立排序好的 sublists,再以 prev
組成的 singly-linked list 串接每一個 sublists,即可在不額外配置記憶體的情況下完成 merge sort。
首先先看一下要如何增加一個命令,在 qtest.c
搜尋 ih
,試圖從 ih
命令怎麼實作的下手,可以看到有 do_ih()
函數:
以及在 console_init()
中大量使用 ADD_COMMAND
:
檢查 ADD_COMMAND
macro,位於 console.h
:
command 是「命令」、instruction 是「指令」,請查詢英語辭典
add_cmd
會在命令列表 cmd_list
中添加新命令,明白要新增一條新命令 shuffle
則要實作 do_shuffle
,並在 console_init()
替新命令加上 ADD_COMMAND
。
在查看 swap
、reverse
命令的實作時,發現在呼叫到主要邏輯 q_swap
、q_reverse
前後都有特定的函數呼叫:
在閱讀了 K01: lab0 - Signal 處理和應用後明白這部分是設定發生 exception 時的處理方式,這邊實作 shuffle
時也可將主要邏輯以 set_noallocate_mode
和 exception_xxxx
包住。
shuffle
主要邏輯:
新增命令:
提供命令 web
,並可以用參數指定 listen port,預設 port 為 9999:
使用範例可以用 curl
進行測試:
新增了 web.h
和 web.c
,將從 tiny-web-server 的主要程式碼放置於此。新增檔案後需要修改 Makefile
:
值得一提的是,在 process()
尾端要另外配置記憶體,並複製字串的部分如下:
而 req.filename
的設定來自於 parse_request()
:
這邊最長能往 req->filename
寫入 MAXLINE
個字,也就是 1024,而 req
為 http_request
結構,其原始定義如下:
req
為 process()
的區域變數,這就導致了有 stack-based buffer overflow 的漏洞,目前修正方式是將 http_request
定義方式改成如下:
在 qtest.c
新增 do_web()
:
參照 K01: lab0 - 整合 tiny-web-server 的說明進行 run_console()
和 cmd_select()
的修改。
首先是 run_console()
,當 web server 開啟時,則改成通過 cmd_select()
進行輸入的選擇:
cmd_select()
當 server socket 有輸入時則要接收連線並處理:
使用結果 (qtest
):
Client 端:
觀察 lab0-c
中的 list.h,與 Linux source code 中的 include/linux/list.h 很像。先閱讀了一下此檔案,一方面看是否有能派上用場的工具,一方面先詳讀每個工具以避免實作時錯誤使用。
為何要寫 __typeof__
在 6.7 Referring to a Type with typeof 提到:
If you are writing a header file that must work when included in ISO C programs, write __typeof__ instead of typeof.
但這邊我真正的疑問是為何要分成兩種做法,實際使用兩者,編譯成執行檔,查看組語:
組合語言:
在 6.48 Alternate Keywords 中提到,在使用 -ansi
或 -std
的情況下,asm
、inline
、typeof
這些關鍵字不支援,解法是需要在這些關鍵字前後加上雙底線 __
。
typeof
為編譯器提供的功能,而非原本就在 C specs 中的關鍵字,因此使用 typeof
要看你用的編譯器有沒有支援,而 __typeof__
為 GNU gcc 提供的功能,在其他編譯器可能沒有,這種情況下可以用 macro __GNUC__
來判別編譯器是否為 GNU gcc,如下例子:
但以上例子成立的前提是你使用的別款編譯器要支援 typeof
。
回過頭來解釋 container_of
,裡面用到的 offsetof
就有在 C specs 中,所以其一邊的定義是用了 GNU gcc 功能,一邊則是完全遵守 C specs,但似乎還是沒解釋到為何不單純用遵守 C specs 的定義就好。
在 qtest.c
中的使用範例,可以直接從 element_t
的 list
反推取得 element_t
的位址:
從以上範例也可觀察到,element_t
中的 list
,不管是 prev
或 next
都應該指向到 element_t
中的 list
。
若有啟用 LIST_POISONING
,則可以防止被移出 list 的 node 的 prev
和 next
仍然指向有效的 node,進而防止類似 Use-After-Free 的攻擊。
將 list 中的 node 加到 head 的起頭,隨後若要再使用 list 需要重新初始化。
迴圈經過所有在 list 中的結構 entry 本身,而非用來連結的 pointer。
但是定義中還是使用到了 GNU gcc 的額外功能 __typeof__
,所以才註解了 FIXME: remove dependency of __typeof__ extension
。
這邊非常巧妙,可以使得 pending lists 滿足幾個假設:
pending
以 prev
連結成一個 singly linked list,每個 prev
指向的是一個 size sublists,等待著被合併。
以下表格逐步說明 count 增長時,pending lists 內部中各個 sublists 的變化,每一個數字表示一個 sublist size,寫在越後面的表示是在 pending
這張 singly linked list 越前面的位置:
count | pending lists |
---|---|
0b0 | NULL |
0b1 | 1 |
0b10 | |
0b11 | , 1 |
0b100 | , |
0b101 | , 1 |
0b110 | , |
0b111 | , , 1 |
0b1000 | , , |
0b1001 | , , 1 |
0b1010 | , , |
0b1011 | , , 1 |
0b1100 | , , |
0b1101 | , , 1 |
0b1110 | , , |
0b1111 | , , , 1 |
再對比自己的實作,發現自己的實作問題是在 worst case 的情況下,在 merge 時兩邊 list 的 size 沒有限制,導致單次 merge 最差的時間複雜度會是 ;反過來看 Linux 的實作方式,最差的狀況兩邊 list size 為 和 ,單次 merge 過慢的發生機率就會顯著下降。
在完成 queue 的實作後首次推上 github,CI 的結果不如預期。在本地端測試時,主要是 trace-14-perf 一直過不了;但 CI 的結果反而是卡在 trace-17-complexity,而這部分是測試 q_insert_tail
、q_insert_head
、q_remove_tail
、q_remove_head
是否為 constant time,這我反而很有信心應該要通過。錯誤訊息中有大量的 not enough measurements
訊息,可能與之相關。第二次重新跑一次 CI 就通過了,需要再研究為何會有這樣的結果發生。
TODO: 對照閱讀論文