contributed by < chensheep
>
Andrushika
根據老師撰寫的資訊科技詞彙翻譯:
「實作」會是符合上述 "implement" 語意的譯詞,取「起」以「創作」的意思,而「製造」本質上是固定輸入並通常產生固定輸出的行為。
目前全篇都寫作「實做」,建議修改。
「閱讀 lib/list_sort.c」小節有錯字:
目標是 merge list a 與 list b,因為 merge 中不「維」 prev 指標
此外,在撰寫文件時,應該注意標點符號與斷句位置,方便閱讀。
如 q_merge
小節可以修正如下(使用 Claude 協助修正):
目前實現方式是依序從第 2 個 queue 開始,將所有的 node 都接到第 1 個 queue 的後方,最後再針對第 1 個 queue 做排序。
這個作法很直覺,但因為最後需要針對第 1 個 queue 做排序,目前使用的是 Merge sort,所以其時間複雜度為 。
原文的句子:
從卡方分佈表找合適的 P value, 為 24.762842054728754,因為 14.848 < 24.762842054728754 < 32.007,所以 P value 介於 0.9 和 0.1 之間。
此處使用的是右尾檢定,但推導過程不精準,若顯著水準為 alpha = 0.05,則應該直接使用 (即)進行推導說明,而非找 0.9
, 0.1
去進行比較。
參考以下實做
malloc(3) — Linux manual page 中RETURN VALUE 提到
加入檢查 malloc
函式回傳結果,當 malloc
發生錯誤時 q_new
回傳 NULL
實做方式
透過 qtest
程式測試,產生一個空的 queue 後釋放沒有錯誤訊息
但在實做完 q_insert_head
,出現以下錯誤訊息
首先 harness.c
程式碼找到打印相關訊息的部份
是由於 footer 與 MAGICFOOTER 不一致所導致
首先想到的作法 test_free
與 alloc
打印出一些訊息,也確實觀察到 footer 被改動
但無法明確的發現在何時被更改,後來想到之前老師提到可以透過 gdb
工具除錯,步驟如下
harness.c
檔案中的函式 alloc
加入訊息,印出 footer 記憶體位置,後續會用 gdb 追蹤這個位址qtest
程式qtest
的 PID 並 attach 到 gdb
harness.c
中 aloc
的最後一行qtest
運行的 terminal 執行gdb
terminal 設定觀察 footer 記憶位置qtest
運行的 terminal 執行gdb
可以觀察到錯誤訊息,可以透過 bt 打印出 stack可以觀察到在呼叫 q_insert_head
最後執行 list_add
時就已經修改到 footer
在審視一下目前的實做方式
發現在第 3 行透過 malloc 分配記憶體給 e 時,應取 element_t 的結構大小而非 list_head
閱讀 queue.h 中關於 q_insert_head
註解提到
觀察到 q_insert_head
的 parameter 包含 head 與 s ,並未輸入字串 s 的長度,由此判斷 s 應最後一個字元應該為 '\0'
實做如下
此實做分配錯記憶體大小的問題,已更新,詳細的查測過程
處理針對 trace-11-malloc 與 trace-12-malloc 錯誤訊息
待處理
參考 q_insert_head 中透過 list_add
函式將新的節點加入到 list 的頭,參考 list.h
中相對有個函式 list_add_tail
將節點加入到 list 的尾,調整使用 list_add_tail
實做如下
使用 qtest
測試結果符合預期
目前實做方式是依序從第 2 個 queue 開始將所有的 node 都接到第一個 queue 的後方,最後在針對第 1 個 queue 做排序。
這個作法很直覺但因為最後需針對第 1 個 queue 做排序,目前排序時做為 Merge sort 所以其時間複查度為 。
實做方式透過函式 list_splice_tail
移動所有的 nodes,注意到 list_splice_tail
的註解說明中有提到
The @list head is not modified and has to be initialized to be used as a valid list head/node again.
所以需在呼叫 INIT_LIST_HEAD 才可以再當成可用的 head,參考下方。
配合 q_merge 註解中提到
There is no need to free the 'queue_contex_t' and its member 'q' since they will be released externally.
加入 INIT_LIST_HEAD(e->q);
後續做 q_free 才不會發生問題。
已更新 Commit dc074bd
透過 scripys/driver-shuffle.py
輸出 shuffle 結果如下
從圖表來看 shuffle 的頻率明顯不符合 Uniform distribution
Commit 3773ea2 已修正此問題,執行 ./scripts/driver-shuffle.py
的結果
因為 P value(0.9~0.1)> alpha(0.05),統計檢定的結果不拒絕虛無假說()。
也就是樣本資料不拒絕 shuffle 的結果遵循 Uniform distribution,因為沒有足夠證據推翻。
從圖表來看 shuffle 的頻率是大致符合 Uniform distribution 的。
從 include/linux/types.h
中知到 struct list_head 定義如下
list_sort_.c 中 merge 的運作參考以下流程,目標是 merge list a 與 list b,因為 merge 中不維 prev 指標,所以下圖省略
after loop 1
after loop 2
after loop 3
因為 b 為 NULL 所以進一步執行
最後回傳 head。
接著說明 list_sort
函式,部份程式碼如下
count 變數從 0 開始每回和加 1 。
程式碼第 6 行,會找尋最小為 0 的 bit,可以參考下方表格,用黃色與紅色表示。
程式碼第 10 行,會判斷剩餘的的 bits 是不是 0,非 0 則進行整並,參考下方紅色的 0 即需整並的情形,對應到 merge 欄位。
count (binary) | merge | initial pending | after merge | after insert a new node |
---|---|---|---|---|
0 (00000000) | NO | NULL | [1] | |
1 (00000001) | NO | [1] | [1, 1] | |
2 (00000010) | YES | [1, 1] | [2] | [2, 1] |
3 (00000011) | NO | [2, 1] | [2, 1, 1] | |
4 (00000100) | YES | [2, 1, 1] | [2, 2] | [2, 2, 1] |
5 (00000101) | YES | [2, 2, 1] | [4, 1] | [4, 1, 1] |
6 (00000110) | YES | [4, 1, 1] | [4, 2] | [4, 2, 1] |
7 (00000111) | NO | [4, 2, 1] | [4, 2, 1, 1] | |
8 (00001000) | YES | [4, 2, 1, 1] | [4, 2, 2] | [4, 2, 2, 1] |
9 (00001001) | YES | [4, 2, 2, 1] | [4, 4, 1] | [4, 4, 1, 1] |
10 (00001010) | YES | [4, 4, 1, 1] | [4, 4, 2] | [4, 4 ,2, 1] |
11 (00001011) | YES | [4, 4, 2, 1] | [8, 2, 1] | [8, 2, 1, 1] |
12 (00001100) | YES | [8, 2, 1, 1] | [8, 2, 2] | [8, 2, 2, 1] |
13 (00001101) | YES | [8, 2, 2, 1] | [8, 4, 1] | [8, 4, 1, 1] |
14 (00001110) | YES | [8, 4, 1, 1] | [8, 4, 2] | [8, 4, 2, 1] |
15 (00001111) | NO | [8, 4, 2, 1] | [8, 4, 2, 1, 1] | |
16 (00010000) | YES | [8, 4, 2, 1, 1] | [8, 4, 2, 2] | [8, 4, 2, 2, 1] |
取當 count 為 9 來舉例說明,初始 pending 列表如下
9 的 binary 為 00001001,因為最小的 0 出現前有一個 1 因此下方會執行 1 次
接著
然後
最後這段程式碼會在放如一個節點
接著看 merge_final
函式中這段程式碼
待處理
關於 likely 與 unlikely 函式
於 linux-6.14-rc7 的檔案 include/linux/compiler.h
可以找到其定義,在 CONFIG_TRACE_BRANCH_PROFILING 未被開啟的狀態下被定義如下
關於 CONFIG_TRACE_BRANCH_PROFILING 可以參考 kernel/trace/Kconfig
中說明
在 linux-6.14-rc7 的資料夾底下
可以在 Tracers -> Branch Profiling -> Trace likely/unlikely profile 找到說明
這個 config 是用來追蹤 likely 與 unlikely 的情形,打開此選項對效能有很大的影響
因此可以推斷平常執行時 likely 與 unlikely 定義是使用
解釋 __builtin_expect,參考 6.64 Other Built-in Functions Provided by GCC
Built-in Function: long __builtin_expect (long exp, long c)
You may use __builtin_expect to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this (-fprofile-arcs), as programmers are notoriously bad at predicting how their programs actually perform. However, there are applications in which this data is hard to collect.
可以使用這個函式來告知編譯器分支預測的資訊。
當第 c 帶入 0 代表這個分支不被預期發生,帶入 0 則代表會被預期發生,編譯器會針對這個對於產的的 assembly code 做調整。
參考下方實驗
可以觀察到 expected 的訊息都被安排到 je 或 jne 的下方。
另外文件中提到可以在編譯時加入 -fprofile-arcs
可以追蹤真實運作中每個分支實際被運作的情形,進一步查閱 3.13 Program Instrumentation Options
-fprofile-arcs
Add code so that program flow arcs are instrumented. During execution the program records how many times each branch and call is executed and how many times it is taken or returns. On targets that support constructors with priority support, profiling properly handles constructors, destructors and C++ constructors (and destructors) of classes which are used as a type of a global variable.
When the compiled program exits it saves this data to a file called auxname.gcda for each source file. The data may be used for profile-directed optimizations (-fbranch-probabilities), or for test coverage analysis (-ftest-coverage). Each object file’s auxname is generated from the name of the output file, if explicitly specified and it is not the final executable, otherwise it is the basename of the source file. In both cases any suffix is removed (e.g. foo.gcda for input file dir/foo.c, or dir/foo.gcda for output file specified as -o dir/foo.o).
Note that if a command line directly links source files, the corresponding .gcda files will be prefixed with the unsuffixed name of the output file. E.g. gcc a.c b.c -o binary would generate binary-a.gcda and binary-b.gcda files.
在編譯時加入這個選項後,運作程式時會自動產生 .gcda
檔案。
The data may be used for profile-directed optimizations (-fbranch-probabilities), or for test coverage analysis (-ftest-coverage)
(待處理)
另外也提到可以透過 gcov 工作來分析。(待處理)
select() can monitor only file descriptors numbers that are less than FD_SETSIZE (1024)
文件中提及 select 只能監視最多 1024 個 file descriptors,超過則可以用 poll(2) 或 epoll(7)。
Thus, if using select() within a loop, the sets must be
reinitialized before each call.