contributed by < laneser >
Create empty queue.
Return NULL if could not allocate space.
但發現其實 list.h 已經有提供 INIT_LIST_HEAD
可以用,所以應該改用
Free all storage used by queue.
Return number of elements in queue.
Return 0 if q is NULL or empty
Attempt to insert element at head of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
然後才發現原來 node 是 element_t
, 我之前的 q_free
函式寫錯了,應該改為
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
此時發現 q_insert_tail
跟 q_insert_head
有重複的程式碼,所以抽出來做 new_element
Attempt to remove element from head of queue.
Return target element.
Return NULL if queue is NULL or empty.
需要花點時間讀懂 你所不知道的 C 語言: linked list 和非連續記憶體
搞到兩點才看到皮卡丘
(早上起床發現老師會貼心幫你修改作業…)
多花點時間再讀 list.h
有發現 list_for_each_entry_safe
, 使用這個函式會讓 q_free 更簡潔也更快速,不用 remove 也就不用檢查 remove 失敗。
list_first_entry
也可以用在 q_remove_head
函式當中
Attempt to remove element from tail of queue.
感謝同學 at0mCe11 提醒 head
要做檢查。
可以盡量使用 q_remove_head
, 才發現 q_remove_head
沒有檢查 list empty.
需要針對 q_remove_head
開頭補上
Delete the middle node in list
既然是 doubly-linked list, 那取中間最簡單的方法就是分別從頭跟尾一起搜尋,直到雙方碰頭為止. 不過, 如果 head
不是一個 doubly-linked list, 可能會陷入無窮迴圈。
然後又發現 q_remove_head
沒檢查 NULL。
Delete all nodes that have duplicate string,
leaving only distinct strings from the original list.
Return true if successful.
Return false if list is NULL.Note: this function always be called after sorting
既然 list 已經是排序,所以重複的字串節點一定是在相鄰的節點上,所以只要依序移除重複節點就可以留下不重複的字串了。
延伸思考:若要針對 unsorted list 移除掉重複內容的節點,該怎麼做?如何避免沈重的排序操作?
對於 unsorted list, 考慮記憶體充足的狀況,最簡單就是建立 hash,這樣也是只要 O(n) 的時間就可以做完刪除重複的節點。
如果想要減低記憶體使用,可以考慮利用 bloom filter or cuckoo filter 來過濾重複字串,可是遇到 false postive 或者真的重複的情境就無可避免需要回頭掃描重複節點。
一般而言 bloom filter 使用的記憶體會遠小於 hash, 如果 false postive 機率夠低就有優勢。而且 filter 可以依照 list size 建立不同的大小,這樣可以應對的情境就比較廣。
Google 發現還有 trie 這種做法,一邊走訪 list, 一邊建立字典樹,也是一種方法。
可以結合 bloom filer and hash, 第一次走訪 list 建立 bloom filter, 並且僅針對可能重複的節點建立 hash, 第二次走訪則根據 hash 刪除重複的節點。
目前結論:
感謝其他同學的回饋,我對於 delete duplicate 的意義弄錯了,所以再實作一次。
還是基於相鄰的節點是才會有重複的字串,只是利用 isdup
紀錄目前這個節點是否與前一個節點為重複。
Attempt to swap every two adjacent nodes.
依序倆倆節點互相交換,先移除一個節點,再加入在交換的位置上,就完成了。
判斷迴圈繼續的條件需要至少兩個節點以上。
Reverse elements in queue
對於反向排列,簡單的思考就很像 delete mid 的架構,將頭尾交換,一直到中間剩下一對或者單一個節點為止。如果有好用的 swap function, 應該會讓程式碼更簡單。
想到如果 swap element point, 連 list 的指標通通不用改,這樣程式碼果然更乾淨。
Sort elements of queue in ascending order
有了 swap, 就可以做簡單的 quick sort, 但做完看起來效能有待加強。
看來是要花時間研讀老師指定的 merge sort lib/list_sort.c 才能通過。
看了 make test 用的測試範例,發現 quicksort 之所以效能不佳,與老師上課不斷地提醒的 不夠確定性
有關係,因為 quicksort 的 worst case 很容易發生在已經完成排序的 data, 此時花費的時間會落入 O(), 而不再是 O(nlogn).
換句話說,我簡單地實現一版 mergesort, 也會落入相同的問題。效能已經跟 inserstion sort 一樣慢了。
老師提的 sort list 範例在 split list 的地方本來看不懂:
但看到隔壁同學的心得 https://hackmd.io/@ppodds/HJ20QXakq, 老師提到 Robert W. Floy 提出的循環偵測演算法, 原來這裡的 split 是要找到中間的節點。
所以 split 用這個方法就比原來好。不過還是有部分測試沒過。
要使用 lib_sort.c
, 只需把 lib/list_sort.c 直接複製貼上,在這段程式碼之前補上一些定義就可以使用了。
likely
跟 unlikely
這兩個機制蠻有趣的, https://meetonfriday.com/posts/cecba4ef 說這個是要優化 compiler 產生的指令。如果我們可以提供 if
條件發生的機率,那麼 compiler 可以幫我們產生更快的指令碼。說不定將來的 compiler 搭配靜態分析跟亂數產生的資料也可以自己判斷發生的機率而做到一樣的事情吧?
這樣搭配
雖然可以在自己的環境看到 100 分,但過不了 cppcheck 這一關。
原來 linux kernel 的 code 這行:
會讓 cppcheck 抱怨 head
指標並沒有初始化就拿來用,其實後面會利用 *tail
塞值,但是靜態分析在這邊力有未逮,只好先安撫 cppcheck:
這樣終於在 GitHub 看到抄來的滿分皮卡丘!
而原本的 mergesort 只剩下 trace-14-perf.cmd
這個測試過不了。
對於這種數量級的才發生錯誤的狀況,大概猜是 stackoverflow.
因為呼叫太多層遞迴函式才會導致 Segmentation fault.
所以優化一下 my_merge
這邊就幾乎按照 lib_sort.c
裡的,用 for 取代遞迴。
終於有一個比較是自己寫的滿分版本。
由於沒辦法新增 queue.h
的定義,所以就把 lib_sort.c
的程式放進 qtest.c
,
然後新增一個 option 來切換 sort 的實作,這樣很快就可以看看自己實作的效能跟 Linux 核心程式碼的差距。
在 qtest.c
當中把 do_sort
函式的部分改為這樣:
於是利用這樣的 commands 來呈現差距:
執行的結果顯示 lib_sort.c
效能約比自己的實作還要快 15%, 甚至用不同的資料測試還可以差到 35% 的時間。
顯然自己實作的效能輸很大。
lib_sort.c
針對不少 mergesort 的情境做了優化,而且架構上先走訪一次 list, 做了一輪可以盡早合併的 list, 切割預期數量 (power of two) 的 pending list, 然後才再把 pending list 做 mergesort. 而且會考量硬體層面,像是註解提到可以避免 cache thrashing, 但恐怕我還需要更多知識跟理解才能知道為什麼。
覺得 mergesort 一開始切割 list 這邊有優化的空間,如果一個 list 有發現已經排序好的,那就不用排了,把切割點切在沒有排序的 list 上面,若無法發現排序,才使用 cycle detection.
這樣就不太會輸那麼多了 (會贏很多是因為資料太順了,自己的實作有 overfitting 的狀況)。
在讀了同學 bakudr18 的 sort 以及 kdnvt 對於排序的比較次數 的作業後,在這裡其實可以花更多時間鑽研問題,尤其是 bakudr18 對 linux kernel 的研究方法很棒,要去挖 git log! 而不是只看 source code.
TODO: 對此問題再多花點時間。
透過 valgrind 呼叫 qtest 執行,看來沒甚麼問題。
但如果透過 -f
讓 qtest
執行命令,就會看到 memory leak 的訊息。
所以問題應該就是 -f
這個選項開啟的時候,沒有把 history 釋放掉。
也就是沒有呼叫 linenoise.c
裡面的 freehistory
這個函式,或者它的 caller linenoiseAtExit
函式。
觀察 linenoise.h
會發現, freehistory
或者 linenoiseAtExit
這些函式都沒有開放給外部呼叫,而 linenoiseAtExit
是在 enableRawMode
的時候,透過 atexit
註冊到程序結束時被自動呼叫。
一路追查 enableRawMode
,直到 linenoise
這個函式,終於找到 -f
選項差異。
如果沒有 -f
, 則會透過 linenoise
讀取命令,否則是透過檔案內容。
而透過檔案內容因為沒有呼叫 linenoise
因而沒有機會註冊 linenoiseAtExit
,自然就沒有 free history 資料了。
我的想法是,既然沒有 -f
選項的時候也用不到 history, 那就不需要那麼早呼叫 linenoiseHistoryLoad
, 而是在呼叫 linenoise
之前讀取 history 就好了。
第二個發現的問題是 make valgrind
若失敗的時候,也會有錯誤:
追查 init_cmd
所建立的 cmd_list
等變數應該是在 do_quit()
的時候被 free, 所以若有遺留,就是 do_quit()
沒有被呼叫,再一路追查到 finish_cmd()
應該也是沒被呼叫,因為 qtest.c
是這樣呼叫 finish_cmd()
:
若失敗的時候, ok
的內容已經是 false, 則 finish_cmd()
就不會被呼叫了。可以改為
就可以避免 ok
是 false 的時候沒有呼叫 finish_cmd()
.
Valgrind 還可以透過 massif 得到視覺化的資料,但我的 linux 只有 console mode, 沒有 GUI, 好在還有好心人士有提供 massif.js 可以直接視覺化資料。
會得到一個 massif.out.PID
的檔案,把內容丟到該 webpage 就有圖片, 但沒有 Massif-Visualizer 漂亮。
而因為 Valgrind 會大幅降低速度,跑分析之類的都會 timeout, 可以把 harness.c
裡面的 time_limit
暫時調高用來跑獲取分析數據
shuffle
理解 Fisher–Yates shuffle 演算法,實作如下:
要在 qtest 加命令,只需要在 console_init()
其中加入一行:
並且提供 do_shuffle
函式的實作:
就可以透過 qtest
使用 shuffle
命令:
web
既然需要引入 tiny-web-server
, 那就是把當中的 tiny.c
放進專案,然後想辦法讓 Makefile
接受它。
看來只要在 Makefile
的 OBJS
後面加上 tiny.o
, Makefile
會自動編譯 tiny.c
.
接著修改 tiny.c
的 Main
.
把 Main 改為只要啟動 web server 就回傳 listenfd
, 之後就能整合進 qtest
.
還有把每次接收連線進來的 accept
函式也寫出來。
而且因為其他 qtest
需要呼叫 tiny.c
裡面的函式跟定義, 所以抽出 tiny.h
.
把需要提供給 qtest
使用的三個函式作為 tiny-web-server
的介面, http_request
就照著搬過來。
就可以先從提供 web
命令開始做起。
在 console_init
裡面補上:
而且實作 do_web
函式:
利用 tinyweb_fd 作為 server listen socket id, 而一次只能起一個 web, 也沒有打算要關掉的意思。
web
命令了。但是,後續還有好多事情要做。
因為作業的要求是能夠在輸入指令的同時,處理 web 的輸入。現在的狀態會讓 qtest
停止在等待鍵盤輸入的狀態,因而無法接受來自網路的連線需求。所以有必要把 tinyweb_fd
這個接收連線的 socket id 傳遞給等待鍵盤出入的函式。
老師已經提示等待鍵盤輸入的函式就在 linenoise.c
的 linenoiseEdit
當中。
所以可以一路追查到 qtest
的
這行指令就是 qtest
等待鍵盤輸入的進入點。
基於執行 web
命令的時候根本沒有離開 run_console
的函式,所以 tinyweb_fd
值的變化難以透過 run_console
傳遞,但 tinyweb_fd
的位址可不會變化,所以就是給它位址。
tinyweb_conn_fd
是之後我們要跟網路輸出溝通使用的,這裡也先給它。
於是, run_console
裡面主要的接收鍵盤指令並且執行的部分就變成
在這邊考慮把 linenoise
的實作成為當 web
跟 stdio
二者任何一個有輸入的時候,就會把該輸入的字串回傳成為 cmdline
.
如果 *tinyweb_conn_fd
有值,表示 tinyweb 有收到 http 連線,於是實作 http protocol, 回傳 http header 並且在執行完命令後關閉連線。
接著就是比較難的 linenoise
實作,不過老師已經有給了範例,稍微修改就可以用。
在 while(1)
的等待迴圈中,如果同時有 web 要一起接收輸入的話,就用 select
來查看到底是誰有輸入,一旦 tinyweb_fd
有輸入,就是有連線進來了,可用 tinyweb_accept
接收,用 parse_request
處理 http, 取得 url 後面的 filename
當作命令的輸入。
不過,需要一點小轉換,把 /
換成空白,這樣命令格式就跟 stdio 的格式一樣了。
最後,處理命令的輸出,修改 report.c
當中的 report
以及 report_noreturn
也一併輸出給 http, 所以 curl
命令的輸出就會漂亮不少。
這樣就有支援 curl
的 qtest
了。
這裡只有透過 web
命令啟動 tiny-web-server
, 接著在另一個 shell
執行
有了完整的 http 實作, curl
指令的回應就會比較正式。
而且可以觀察自己實作的 http 細節:
這篇論文提出了 dudect 這個程式來判定某個測試程式是否是 constant time , 因為基於 Student’s t-test , 利用 Timing leakage detection 而做到不侷限某特定 CPU 硬體平台。
Introduction 先從 side-channel attacks 講述了 constant time 方法的重要性,還有偵測 constant time 的困難度。多數的方法都需要針對硬體設計參數,但是硬體廠商偏偏又很少洩露許多相關資訊。所以這篇論文就是貢獻了無須硬體資訊的檢測方法。
接下來陳述利用 Timing leakage detection 這個方法,先利用兩種類型的輸入資料來量測目標函式耗費的時間。一種為固定的模式,一種用亂數模式。重複執行而取得許多的量測結果。
在做統計分析之前需要先過濾極端值,以及做 centered product 這種 second-order 處理。
最後用 Welch's_t-test 來判定是否有通過檢驗, 但也提到其他 Non-parametric tests
的判定方法。
接著論文展示了這套方法用在 Memory comparison
, AES
, Curve-25519
等判定。
但蠻有趣的是,在 Curve-25519
的判定上,相同的程式用在不同的硬體上卻會有不同的判定結果。
接著討論一些改進的方向,諸如編譯器的問題,硬體方面的假設幾乎為零,需要多少重複執行測試才能確認?偽陽性的問題等等。
最後結論提到論文的方法概念簡單,而且有擴充的空間,並提出了下一步的方向跟感謝。
提示: 和 RIO 套件 有關
既然老師都已經提示跟 RIO 的實作 有關,可是對著這幾行程式也看不太出問題。
但 tiny-web-server
剛好裡面也有一套 RIO,拿來一比就發現 qtest 的 RIO 是有 stack 的,追查一下為甚麼要用 stack, 可以發現原來是因為需要支援 source
這個命令。
執行 source
命令需要開啟另一個檔案匯入命令,並且執行完畢後又回歸原本執行中的檔案位置。
所以就需要類似 stack 的方式來操作許多開啟的檔案。
stack 就會讓人聯想 stackoverflow …因為大家都去那裏查問題。
做個簡單的小實驗提供一個名為 test.cmd
的檔案內容:
然後進行
只是總覺得這算使用者操作錯誤,不像是程式有問題。
但如果要防呆,可以考慮驗證 source
命令後面的檔案是否存在目前已經使用中的檔案名稱,若有就判斷是無窮迴圈。但這樣是基於目前還沒有設計出分支命令的關係。所以也許給個上限是最簡單的。
另外 buffer 也讓人會聯想超過 buffer size 會如何?
而程式碼 readline
函式 內部 也有處理萬一超過就只好直接砍斷的作法。而我也很難想出更好的做法了。畢竟資源也沒辦法無限上綱。