# Linux 核心專題: 重做 lab0
> 執行人: bclegend
> [專題解說影片](https://www.youtube.com/watch?v=FopiIjY-zgo)
### Reviewed by `LindaTing0106`
> 參照 dudect 中函式 update_statistics() 會放棄前 10 個量測值,而不是從頭開始計算。因此在 lab0-c 中也做了對應的更改,即在進行統計分析時,同樣放棄前 10 個量測值。
dudect 中捨棄前 10 個量測值的理由為何?
### Reviewed by `Booker-Chen`
在 dudect 原始碼中的 `prepare_percentiles` 函式是將百分位數存進 `ctx->percentiles` 這個陣列,但你是把百分位數存進 `ctx->exec_times` 這個陣列,可以解釋為甚麼做這樣的改動嗎 ?
### Reviewed by `SuNsHiNe-75`
> [commit ebc7886](https://github.com/sysprog21/lab0-c/commit/ebc78862d7bc8b3b680eccbd3d3439b53b15f84f)
[commit 805c765](https://github.com/sysprog21/lab0-c/commit/805c765d730d66726b5058401d4b6e4b88270312)
你的 Commit message 可以寫得更好,參見 [How to Write a Git Commit Message](https://cbea.ms/git-commit/),大原則如「內文每行最多 72 字」等需特別注意。
> 收到,已更正 [Comparing changes](https://github.com/sysprog21/lab0-c/compare/master...bclegend:lab0-c:master)
### Reviewed by `lintin528`
在佇列操作的程式碼實作中,可以將 `commit` 的重點部分放到簡記裡,比對文字說明的效果會更好。
## TODO: 重做 lab0 並彙整學員成果
> 務必依循作業規範,執行所有要求之任務
> 善用 git rebase 操作,在最新的 [sysprog21/lab0-c](https://github.com/sysprog21/lab0-c) 開發
### 佇列操作的程式碼實作
#### q_new
> [commit 1195f8a](https://github.com/sysprog21/lab0-c/commit/1195f8aa7fe77169ea438ba2d5441f843269f7e4)
:::danger
$\to$ [commit 6bb8bbf](https://github.com/bclegend/lab0-c/commit/6bb8bbfadb3e79d7139cf8dfb690f6891b52fd78):
> Implementation of q_new
> In q_new, we have to check the memory whether has been allocated correctly after we called malloc.
不是清晰的訊息,因為:
1. 標題的 `q_new` 多餘,本修改著重在鏈結串列的建立,沒必要寫出函式名稱,應描述其英語的操作
2. 內文也不用提 `q_new`,git 總是會追蹤變更的檔案內容,你該強調配置記憶體要注意什麼、在什麼時候可釋放已配置的空間,和定義「建立新的佇列」和「建立新的節點」有什麼差別
3. 之所以要寫好 git commit message,就是讓你的想法和相關舉措得以清晰地記錄下來,從而便於日後的協作。並非機械性地對程式碼變更進行逐行解釋
> <s> 收到,已改進 [commit 2cf0d43](https://github.com/sysprog21/lab0-c/commit/2cf0d439b3fd5a7a0bc61ea2d2e4df86781ecfe7) </s>
> :warning: 你沒有改進!務必依循 [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) 的規範,再次 rebase!
:::
> 已修正 [commit 1195f8a](https://github.com/sysprog21/lab0-c/commit/1195f8aa7fe77169ea438ba2d5441f843269f7e4)
宣告一個新的佇列並將 `list_head` 大小的空間分配給新的佇列。
由於 `malloc` 依據 C99 規格書 7.20.3 中所敘述,可能會配置失敗。
> If the space cannot be allocated, a null pointer is returned.
因此利用 `if(!q_empty)` 來判斷該佇列是否成功分配。
將佇列結構中指向下一個以及前一個節點的指標 `prev` 和 `next` 宣告在該新佇列之中。
:::danger
Peter Hutterer 在 [On commit messages](https://who-t.blogspot.com/2009/12/on-commit-messages.html) 說得很精闢:
> 「重新了解一段程式碼更動的脈絡很浪費腦力。雖然這件事情沒辦法完全避免,但是我們可以盡量[降低](https://www.osnews.com/story/19266/wtfsm/)這件事情的複雜度。Commit messages 正可以做到這點,而**我們可以從 commit message 看出一個開發者是否為一位好的合作對象**。」
一個專案是否能長期且成功地運作 (撇除其他影響的因素),取決於它的可維護性,而在這件事上沒有其他工具比專案本身的開發軌跡更為強大。因此花時間學習如何撰寫與維護專案的開發紀錄,是件值得投資的事。一開始你可能會覺得麻煩,但它很快就會變成一種習慣,甚至能成為你之所以感到自豪及具備生產力的因素。
Chris Beams 在 [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) 一文 (繁體中文翻譯: [如何寫一個 Git Commit Message](https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/)) 提出,好的 Git commit message 應符合七條規則:
1. 用一行空白行分隔標題與內容
2. 限制標題最多只有 50 字元
3. 標題開頭要大寫
4. 標題不以句點結尾
5. 以祈使句撰寫標題
6. 內文每行最多 72 字
7. 用內文解釋 what 以及 why vs. how
:::
#### q_free
> [commit d3d5e80](https://github.com/sysprog21/lab0-c/commit/d3d5e808697f7b744cc332d91b7ef017e3fc05e0)
:::danger
改進你的漢語表達。
:::
首先需要確認佇列是否存在。接著宣告指標 `entry` 用於指向當前的節點和指標 `safe` 用於指向下一個節點。利用 `list_for_each_entry_safe` 遍歷佇列中的每個節點,使用 `list_del` 刪除節點與佇列的連結,並使用 `q_release_element` 釋放該節點的記憶體。最後,在刪除完所有節點後,釋放 head 節點的記憶體。
#### q_insert_head / q_insert_tail
> [commit 8c8f841](https://github.com/sysprog21/lab0-c/commit/8c8f84102ac928583f9a50639c248f9afc40617a)
為了符合 linux 的編寫風格,在佇列的頭部或尾端插入新節點可以使用 `list_add` 和 `list_add_tail` 函式。
首先需要確認佇列是否存在。接著宣告並分配一個新的 `element`,並確認分配是否成功。然後使用 strdup 將目標字元指標 s 複製到新的 `element->value` 中,並確認是否成功複製。最後,根據需要使用 `list_add` 將新節點插入到佇列的頭部,或者使用 `list_add_tail` 將新節點插入到佇列的尾端。
```shell
$ man strdup
```
> The `strdup()` function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with `malloc(3)`, and can be freed with `free(3)`.
利用 `list_add` 或是 `list_add_tail` 將 `element` 加入到佇列之中,並回傳 `true` 回報 `element` 已經加入佇列之中。
#### q_remove_head / q_remove_tail
> [commit dc52bfc](https://github.com/sysprog21/lab0-c/commit/dc52bfcccb5cee04c29f384b8ed27c11988f7e08)
:::danger
改進你的漢語表達。
:::
首先,需要確認佇列是否存在且不為空。接著宣告一個新的指標 `node`,將 `head->next` 或 `head->prev` 指派給 `node`。`q_remove_head` 函式使用 `head->next` 來指定目標節點,而 `q_remove_tail` 函式使用 `head->prev` 來指定目標節點。
然後宣告一個 `element_t` 的指標 `element`,並將 `node` 的進入點指派給 `element`。接著,將該節點中的字串根據設定的長度 `bufsize` 使用 strncpy 複製到字元指標 `sp` 中,並在字串的最後加入 `\0`。最後,移除並釋放該節點。
```shell
$ man strdup
```
> The strndup() function is similar (with `strdup`) , but copies at most n bytes. If s is longer than n, only n bytes are copied, and a terminating null byte ('\0') is added.
使用函式 `list_del` 刪除該節點在佇列中的連結。
#### q_size
> [commit 53e8039](https://github.com/sysprog21/lab0-c/commit/53e803927f6f7fbf88e348887756f378ab908593)
:::danger
注意用語:
* access 是「存取」,而非「訪問」
:::
首先需要確認佇列是否存在。然後宣告一個整數變數 `count` 並將其初始化為 0。接著使用 `list_for_each` 函式遍歷每一個節點,每遍歷一個節點就將 count 加 1。這樣可以計算出在佇列中的節點數量。
#### q_delete_mid
> [commit 05f80bf](https://github.com/sysprog21/lab0-c/commit/05f80bf8926419aecdff3c7c6f3573c5a2c97be4)
:::danger
改進你的漢語表達。
:::
首先確認佇列是否存在。接著宣告兩個指標 `move_head` 和 `move_tail`,將 `head` 指派給這兩個指標。在迴圈中,同時將 `move_head` 移動到下一個節點 `next` 以及將 `move_tail` 移動到前一個節點 `prev`。當 `move_head->next` 和 `move_tail->prev` 指向同一個位置,或者 `move_head->next` 指向 `move_tail` 時,離開迴圈。
接下來,將 move_head 移動到下一個節點,然後刪除並釋放該節點。這樣就能達成我們所期望的效果,即刪除佇列的中間節點。
#### q_delete_dup
> [commit eecb514](https://github.com/sysprog21/lab0-c/commit/eecb5141f9f01b978457671874f9b6a1f115a5c0)
:::danger
改進你的漢語表達。
:::
根據 [wanghanchi](https://hackmd.io/@wanghanchi/linux2023-lab0#q_merge) 的筆記中提到的,`q_delete_dup` 函式是針對已經排序過的佇列進行刪除重複字串的節點。因此,在實作時,我們需要比較每個節點與下一個節點的字串,如果發現重複則刪除該節點。
用函式 `list_for_each_entry_safe` 可以組成巢狀迴圈來比對每個節點中的字串值是否一致,若一致則刪除該節點。
#### q_swap
> [commit 00b33dd](https://github.com/sysprog21/lab0-c/commit/00b33dd3c5bc7729dcae8a365e85e02656790855)
:::danger
改進你的漢語表達。
:::
要將佇列中的節點從頭到尾進行兩兩交換,首先需要確認佇列是否存在。然後宣告一個新的指標 `node`,並利用函式 `list_for_each` 讓該指標經過佇列中的每個節點。在經過每個節點時,將該節點與其所指向的下一節點進行交換。
為了避免將佇列的頭部和尾端節點進行交換,我們需要在程式碼中加入以下判斷式:如果 `node` 或 `node->next` 為空,則跳過交換操作。這樣可以確保只進行預期的節點交換。
```c
if (node->next == head)
break;
```
#### q_reverse/q_reverseK
> [commit fad95c1](https://github.com/sysprog21/lab0-c/commit/fad95c14869db6eb52d091b39733d904f1dc8789)
將佇列中的所有節點進行反向操作時,使用 `list_for_each_safe` 函式會將指標 `safe` 指向 `node->next`。由於 `list_for_each_safe` 函式支持在迭代過程中移除節點的操作,因此我們選擇此函式來實現反向操作。
首先,我們需要確認佇列是否存在。然後宣告兩個指標 `node` 和 `safe`,在調用 `list_for_each_safe` 函式時,將 `node` 指向 `head->next`,並將 safe 指向 `node->next`。
接著,在每次迭代中,將 `safe` 指向 `node->next`,然後對調 `node` 中前後節點的指向。由於迭代結束後 `head` 節點沒有被修改,因此最後需要將 `head` 的前後指標進行對調。
#### q_descend / q_ascend
> [commit 69ca1d1](https://github.com/sysprog21/lab0-c/commit/69ca1d1ab418e0629097af663c82184d63c11f04)
![git commit](https://hackmd.io/_uploads/SyuB2G5LC.png)
:::danger
務必使用 git rebase,而非 git merge!
一旦正確使用 git rebase,就不會有 "Merge branch 'sysprog21:master' into master" 的訊息。
唯有掌握細節,方可挑戰更大的目標。你若無法從錯誤中學習,憑感覺持續盲目投入,只是徒勞。
> 收到,已更正
> ![git rebase](https://hackmd.io/_uploads/Sk-ux8nU0.png)
:::
:::danger
詳閱〈[Git 教學和 GitHub 設定指引](https://hackmd.io/@sysprog/git-with-github)〉(含解說錄影),使用 git 命令的 `fetch` 操作取得最新的 [sysprog21/lab0-c](https://github.com/sysprog21/lab0-c) 變更,並用 `rebase` 操作讓自己提交的 commit 得以在 [sysprog21/lab0-c](https://github.com/sysprog21/lab0-c) 最新的變革之上。注意:你可能會遇到若干衝突,你需要自行排除。過程中,確保符合〈[How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)〉規範,你或許會需要用 `git rebase -i` 命令來改進,最後用 `git push --force` (注意: 操作務必謹慎) 公開發佈於 GitHub。
> `git rebase` 示意: ![image](https://hackmd.io/_uploads/HkkxHWE6T.png =80%x)
:::
### 研讀 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 並完成 q_sort
:::danger
改進你的漢語表達。
:::
list_sort.c 是 Linux 核心中實作的 merge sort 檔案。在執行 `list_sort` 函式時,會先將環狀雙向鏈結串列轉換為 null-terminated 的單向鏈結串列,並在排序結束後使用 `merge_final` 函式將其轉換回環狀雙向鏈結串列。
```c
__attribute__((nonnull(2,3,4)))
```
> **5.25 Declaring Attributes of Functions**
> The keyword `__attribute__` allows you to specify special attributes when making a declaration.
根據 [A GNU Manual](https://gcc.gnu.org/onlinedocs/gcc-3.4.3/gcc/Function-Attributes.html) 中所敘述在函式宣告時,檢查在位置 `(2,3,4)` 輸入的參數是否為 `non-null` 的指標。
### 閱讀論文 〈[ Dude, is my code constant time? ](https://eprint.iacr.org/2016/1123.pdf)〉 並改進 dudect
論文中提到,評估一個實作是否以恆定時間運行並非易事,即使是在理論時間複雜度為 O(1) 的實作中也是可能存在 timing leakage 。
現有的幾種檢測變時性代碼的工具大多需要某種方式的硬件建模,然而這非常困難,因為 CPU 製造商對 CPU 內部運作原理的 <s>信息</s> 公開很少,且這種行為可能會因微碼更新而改變。因此論文中介紹了一種名為 dudect 的工具,用於檢測某段代碼是否在給定平台上以恆定時間運行。
論文中解釋了 time leakage 檢測測試的實作,首先測量兩個不同輸入數據類別的執行時間,然後檢查這兩個時間分佈是否在統計上存在顯著差異。在進行執行時間的量測時,leakage 檢測技術針對兩個不同輸入數據類別進行測量。其中,一種被廣泛應用的測試方法是 fix-vs-random 測試。在這種測試中,第一類輸入數據被固定為一個常數值,而第二類輸入數據則每次測量時隨機選擇。而根據論文中所描述,以上兩組測試數據所得到的時間分佈應該是相等的。
:::danger
探討更多該論文的議題,不要只做摘要!
:::
#### 實驗方法
量測方法:
使用 fix-vs-random 的方法進行量測。具體來說,將第一組輸入設定為固定值,而第二組輸入則會根據每次的量測隨機設定。
後處理:
在進行統計分析之前,需要對每次的量測結果進行後處理。由於大部分的量測值集中在較小的執行時間,但也有部分量測值出現特別高的執行時間,這可能導致時間分佈呈現正偏態。因此,需要對結果進行篩選或取捨,以確保統計分析的準確性。
:::danger
一旦進行後處理,是否會影響統計模型?
:::
>會,根據論文 〈[ Dude, is my code constant time? ](https://eprint.iacr.org/2016/1123.pdf)〉 中所描述,若少了對 t-test 資料的**前**處理,則 t-test 將只能檢測 first-order timing information leakage
>
>A simple statistical test to use and implement is Welch’s t-test. This statistic tests for equivalence of means, and hence failure of this test trivially implies that there is a ==first-order timing information leakage==. Note that when a t-test is coupled with cropping pre-processing, one is no longer testing only for equality of means ==but also for higher order== statistical moments since the cropping is a non-linear transform.
>〈[ Dude, is my code constant time? ](https://eprint.iacr.org/2016/1123.pdf)〉
:::danger
翻閱統計學教科書,確認上述說法。
:::
統計測試:
使用 [Welch's t-test](https://en.wikipedia.org/wiki/Welch%27s_t-test) 來測試兩組數據的時間分佈是否相等(例如,平均數是否相同)。如果檢驗結果顯示不相等,則可能意味著存在時間洩漏。
:::danger
先做數學分析,不要急著寫程式。
:::
#### 數學分析
#### 實作
>[commit f5c709b](https://github.com/bclegend/lab0-c/commit/f5c709b5f6698d313a15a2a3960bb294982edf3b)
在 lab0 的實作中加入 leakage detection test 來測試程式是否為 constant time 。
閱讀 [dudect](https://github.com/oreparaz/dudect/blob/master/src/dudect.h) 程式碼可以發現,函式 `dudect_main` 與 `lab0-c/dudect/fixture.c` 中的函式 `doit` 相對應,因此在函式 `doit` 中加入函式 `prepare_percentiles`
```diff
bool ret = measure(before_ticks, after_ticks, input_data, mode);
differentiate(exec_times, before_ticks, after_ticks);
+ prepare_percentiles(exec_times);
update_statistics(exec_times, classes);
ret &= report();
```
:::danger
區分「函數」和「函式」,見: https://hackmd.io/@sysprog/it-vocabulary
:::
函式 `prepare_percentiles` 主要操作的是使用函式 `qsort` <s>函數</s> 對 `exec_times `進行排序,迭代每個測量點,計算其對應的百分位數,並將結果儲存 <s>存儲</s> 回 `exec_times` <s>數組</s> 陣列中。
:::danger
注意用語!
:::
```c
static int cmp(const int64_t *a, const int64_t *b)
{
if (*a == *b)
return 0;
return (*a > *b) ? 1 : -1;
}
static int64_t percentile(int64_t *a_sorted, double which, size_t size)
{
size_t array_position = (size_t) ((double) size * (double) which);
assert(array_position < size);
return a_sorted[array_position];
}
/*
* set different thresholds for cropping measurements.
* the exponential tendency is meant to approximately match
* the measurements distribution, but there's not more science
* than that.
* Reference : https://github.com/oreparaz/dudect/blob/master/src/dudect.h
*/
static void prepare_percentiles(int64_t *exec_times)
{
qsort(exec_times, N_MEASURES, sizeof(int64_t),
(int (*)(const void *, const void *)) cmp);
for (size_t i = 0; i < N_MEASURES; i++) {
exec_times[i] = percentile(
exec_times, 1 - (pow(0.5, 10 * (double) (i + 1) / N_MEASURES)),
N_MEASURES);
}
}
```
參照 `dudect` 中函式 `update_statistics()` 會放棄前 10 個量測值,而不是從頭開始計算。因此在 lab0-c 中也做了對應的更改,即在進行統計分析時,同樣放棄前 10 個量測值。
:::danger
探討此操作在統計的意義。
:::
```diff
static void update_statistics(const int64_t *exec_times, uint8_t *classes)
{
- for (size_t i = 0; i < N_MEASURES; i++) {
+ for (size_t i = 10; i < N_MEASURES; i++) {
```
### 利用 valgrind 分析記憶體使用情況
### 整合網頁伺服器
:::danger
務必依循作業規範,執行所有要求之任務。
注意細節!
整個學期過去,我看不到你的成長,為何如此?說好的翻身呢?
:::