# 2024q1 Homework1 (lab0)
contributed by < `stevendd543` >
### Reviewed by `SuNsHiNe-75`
- 你的「開發環境」沒有呈現出,作業有規定。
- 請把完整程式碼移除,如要討論才將要討論之部分「重點列出」。
- 數字或英文等字元,兩邊應要有空格來隔開。
- 注意標點符號的使用,有些地方都沒有「句號」。
- 字如「在/再」、「做/作」應分清楚。
> 你的洞見呢?與其談「心法」,不如設想「如果我是你」,我會怎麼做,示範一次給同學看,而且檢討自己哪裡沒做好,從而呼應原本學員的不足處。
> 清楚標示學員的 git commits 和敘述的不足處,予以檢討。
> [軟體工程師要學會說故事](https://ruddyblog.wordpress.com/2016/06/18/),本作業要求學員從良性詳盡的批評開始。繼續檢討!
> :notes: jserv
### 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
CPU family: 6
Model: 158
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 9
CPU max MHz: 3800.0000
CPU min MHz: 800.0000
BogoMIPS: 5599.85
```
## 指定的佇列操作
#### Free and Malloc
```c
static inline void q_release_element(element *e)
{
test_free(e->value);
test_free(e);
}
```
在 queue.h 中我發現了一個函式 `q_release_elements` 裡面使用了 `test_free` 而非 `free` ,一開始還有個疑問是為何 `q_release_element` 要先將 value 釋放掉記憶體在釋放掉 list ,後來仔細看 `element` 這個結構後才發現原來當初 value 的型態是 *char**,因此在**動態**建立 `element` 這個結構時,也同時動態配置了 value 空間,因此在釋放空間的時候,如果沒有將 value 釋放將會產生錯誤。
```c
struct list_head *q_new()
{
struct list_head *q_head=malloc(sizeof(struct list_head));
/*return queue head address if successfully allocate memory else return NULL*/
if(q_head){
INIT_LIST_HEAD(q_head);
return q_head;
}
return NULL;
}
```
:::danger
function call 是「函式呼叫」,其權限沒有大到可以「調用」你。
:::
在 `INIT_LIST_HEAD` 註解中提到,一個沒被初始化的節點,有可能會被其他函式呼叫 ,並使用 `list_del` 將其刪除,這樣的結果是不安全,雖然配置記憶體空間給了`q_head`,但是結構中的鏈結串列沒有使用 `INIT_LIST_HEAD` 初始化的話會造成[解引用空指標](https://learn.snyk.io/lesson/null-dereference/)。
:::danger
1. 留意[資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
2. 改進漢語表達
:::
<!-- 此處外加了考慮記憶體配置是否成功,且`e`與`v`不該放置同一判斷是否配置成功,若其中之一有配置,另外一個沒有配置到記憶體,會造成記憶體釋放失敗造成問題。 -->
#### strcpy and strncpy
:::danger
store 的翻譯是「儲存」,而非「存儲」,本處針對者是存取記憶體的邊界議題。
:::
strcpy() 與 memcpy() 皆有可能會造成儲存 溢位要小心使用 !
兩者差異在於複製的型態限制不一樣,但不像 `strdup` 在配置空間時會返還配置失敗的訊息,而會直接覆蓋原始資料。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || list_empty(head))
return NULL;
element_t *e = container_of(head->next,element_t,list); \
// `head->prev` for q_remove_tail
list_del_init(head->next);
if(sp)
strncpy(sp,e->value,bufsize-1);
strcpy(sp+bufsize-1,"\0");
return e;
}
```
這裡嘗試了使用`strncpy`來複製字串,透過 bufsize-1 限制了複製位元組的次數,可以避免掉存儲溢位,但要注意要預留最後給空白字元進行字串中止 `strcpy(sp+bufsize-1,"\0")`
其餘操作像是 q_delete_dup、q_swap、q_reverseK、q_ascend、 q_descend 等等程式碼詳見 [Github: lab0-c/queue.c](https://github.com/stevendd543/lab0-c)
<!-- https://hackmd.io/879ySuWfSpuswOG7h6DyvQ?view -->
[likely](https://meetonfriday.com/posts/cecba4ef/)
### list_sort
==名詞解釋==
* count : 透過二進位記錄 pending 的節點數量來控制合併操作
* pending list : 儲存等待合併的串列
**合併過程**
```
Each time we increment "count", we set one bit (bit k) and clear
bits k-1 .. 0. Each time this happens (except the very first time
for each bit, when count increments to 2^k), we merge two lists of
size 2^k into one list of size 2^(k+1).
```
此處擷取 list_sort.c 說明了 merge 兩個 list 的時機,
每當我們增加 count 時,發生第 k-bit 且 0~k-1-bit 都設為0時進行 merge
**合併條件**
```
This merge happens exactly when the count reaches an odd multiple of
2^k, which is when we have 2^k elements pending in smaller lists,
so it's safe to merge away two lists of size 2^k.
```
裡面提到了當 count 也就是紀錄 pending list 的節點數量,剛好是 $2^k$ 的奇數倍,就進行合併,但我覺得這裡一開始看相當不值觀,因為 $2^k$ 的奇數倍換句話說就是 $2^k$ 的偶數倍 1、2、4、8... 不進行合併,那再搭配表看看
|count decimal|count binary|merge|
| -------- | -------- | -------- |
0|0000 |No|
1|0001 |No|
2|0010 |Yes|
3|0011 |No|
4|0100 |Yes|
5|0101 |Yes|
明顯的搭不起來。
:::danger
無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利);
:::
<!--
$x$代表任意的bits組合,$y$代表任意非全0的bits組合,兩者都可以說成是count
以下兩位數分別為第k-bit與{k-1}-bit
$0\overbrace{01..11}^\text{k 個} = 00x$ (狀態 0) : 小於$2^k$長度的pending elements有$x$個
$0\overbrace{11..11}^\text{k 個} = 01x$ (狀態 1) : 小於$2^k$長度的pending elements有$2^{k-1} +x$個
到這裡可以發現每個bit為1代表者有$2^{bit位置}$個pending的節點元素
$1\overbrace{01..11}^\text{k 個} = x10x$ (狀態 2) : 小於$2^k$長度的pending elements有$2^k+x$個
$1\overbrace{11..11}^\text{k 個} = x11x$ (狀態 3) : 小於$2^k$長度的pending elements有$2^{k-1} +x$個 -->
<!-- 在前面其實提到兩個 list 若要被 merge 的話,大小比例在 2:1 時可以有較低的比較次數。那以合併方式我分成兩種,一個是**早合併**另一個是**晚合併**,fully-eager bottom-up merge sort 就是屬於早合併,出現兩個 $2^k$ 長度的 list 時馬上合併,那晚合併就顯然是先不合併,等到出現第三個 $2^k$ 長度的 list 在做合併 -->
#### 以圖來解釋 list_sort 程式碼
:::danger
改進你的漢語表達。
:::
後來我直接去了解搭不起來的原因,主要是 count 的變化時間點與剛剛的程式碼註解有稍微一點落差,在理解上比較容易搞混,因此我按照原本程式碼圖解了 pending list 、 merge 、 bits 的個別用意,以下為擷取部分[lib/list_sort.c 程式碼](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)。
```c
for (bits = count; bits & 1; bits >>= 1)
tail = &(*tail)->prev;
/* Do the indicated merge */
if (likely(bits)) {
struct list_head *a = *tail, *b = a->prev;
a = merge(priv, cmp, b, a);
/* Install the merged result in place of the inputs */
a->prev = b->prev;
*tail = a;
}
```
假設目前 `bits = count` 計算到 0100~2~,且目前 pending list 如下圖,表示剛剛加入一個長度為 1 的元素後,就會有兩個一樣大小的要準備合併,現在 pendlist 狀況長這樣。
```graphviz
digraph{
rankdir=LR
"pending list"[shape=box]
"a 長度 2"[shape=box]
"b 長度 1"[shape=box]
"c 長度 1"[shape=box]
"a 長度 2"->"pending list"
"b 長度 1"->"a 長度 2"
"c 長度 1"->"b 長度 1" [color =blue]
}
```
合併後再加入一個長度為 1。然後這個時候 count 變成 0101~2~,在上面有提到說除了第一次 flip外,如果第 k 個 bit 被轉成 1 且 0~k-1 bits 都因為進位變 0代表有長度 2^k^ 的 list 要做合併,那此處就是 a 與 b進行合併,且 count + 1 變成 0110~2~。
```graphviz
digraph{
rankdir=LR
"pending list"[shape=box]
"a 長度 2"[shape=box][color =red]
"b 長度 2"[shape=box] [color =red]
"c 長度 1"[shape=box]
"a 長度 2"->"pending list"
"b 長度 2"->"a 長度 2"
"c 長度 1"->"b 長度 2" [color =blue]
}
```
合併後再加一個進來變成下圖。那這邊 count + 1 後變成 0111~2~ 此處同上第 0 bit 進位了,所以 2^0^ 長度要進行合併。
```graphviz
digraph{
rankdir=LR
"pending list"[shape=box]
"a 長度 4"[shape=box]
"b 長度 1"[shape=box] [color =red]
"c 長度 1"[shape=box][color =red]
"a 長度 4"->"pending list"
"b 長度 1"->"a 長度 4"
"c 長度 1"->"b 長度 1" [color =blue]
}
```
合併且加入元素變成,可以注意到 0111~2~ 中每個 1都代表長度為 2^k^ 各有一個。
```graphviz
digraph{
rankdir=LR
"pending list"[shape=box]
"a 長度 4"[shape=box]
"b 長度 2"[shape=box]
"c 長度 1"[shape=box]
"a 長度 4"->"pending list"
"b 長度 2"->"a 長度 4"
"c 長度 1"->"b 長度 2" [color =blue]
}
```
同理可推 1000~2~ 因為是第一次轉成 1 所以不做任何合併。
#### 總整理所有規則可以簡單分成以下三點 :
* 每次 +1 必定會有一個節點加入 pending list 等待被合併。
* 當第 k 個 bit 由 0 變成 1 且不是第一次發生,代表有 2^k^ 長度的 list 在 pending list 中要被合併。
* count = 2^k^-1 時代表有 k 個不同長度的 list 在 pending list 中。
:::danger
明確標示 GitHub 帳號名稱及出處。
:::
有了上面的圖在看整理圖表([來源 : kdnvt](https://hackmd.io/byNYeoGvQv-a9TNDc2CKFQ?view))就可以理解整個設計了。
|count decimal|count binary|merge|before merge|after merge
| -------- | -------- | -------- |---|---|
0|0000 |No| NULL| X
1|0001 |No| 1 |X
2|0010 |Yes| 1-1 |2
3|0011 |No| 1-2 |X
4|0100 |Yes| 1-1-2 |2-2
5|0101 |Yes| 1-2-2 |1-4
6|0110 |Yes| 1-1-4 |2-4
7|0111 |No| 1-2-4 |X
8|1000 |Yes| 1-1-2-4 |2-2-4
9|1001 |Yes| 1-2-2-4 |1-4-4
10|1010 |Yes | 1-1-4-4| 2-4-4
11|1011 |Yes | 1-2-4-4| 1-2-8
12|1100 |Yes| 1-1-2-8| 2-2-8
13|1101 |Yes | 1-2-2-8 |1-4-8
14|1110|Yes | 1-1-4-8 |2-4-8
15|1111 |No | 1-2-4-8 |X
16|10000 |Yes| 1-1-2-4-8| 2-2-4-8
:::warning
回到第一手材料,閱讀作業說明所及的三篇論文。
:::
---
### 將 list_sort 引入 lab0-c 專案
參考 [chiangkd](https://hackmd.io/duPg38XOTL-XKa7_h0Ielw?view#%E7%A0%94%E8%AE%80%E8%AB%96%E6%96%87%E4%B8%A6%E6%94%B9%E9%80%B2-dudect)
- 複製 list_sort.[ch] 到專案底下
- 新增 cmp 函式在 list_sort.c
- 修改 Makefile 指令新增 list_sort 目標檔
- 根據 cppcheck 報錯修改程式碼
perf 效能測試,參見 [perf 原理和實務](https://hackmd.io/@sysprog/gnu-linux-dev/https%3A%2F%2Fhackmd.io%2Fs%2FB11109rdg)
1. 用此命令檢查權限,若非 -1
```
$ cat /proc/sys/kernel/perf_event_paranoid
```
2. 透過以下命令設定權限
```
$ sudo sh -c "echo -1 > /proc/sys/kernel/perf_event_paranoid"
```
3. (optional)perf 指令加入 Makefile
```
export cycle ?= 10
perf: qtest
perf stat --repeat $(cycle) -e cache-misses,cache-references,instructions,cycles ./qtest -f traces/trace-sort.cmd
```
---
#### Instructions and cycles 執行結果 : Delta time
| 資料量 |q_sort | list_sort |
|------|------|--------|
|500,000|0.45|0.406|
|1,000,000|0.987|0.859|
#### cache 執行結果 : Delta time
| 資料量 |q_sort | list_sort |difference|
|------|------|--------|-------|
|cache-misses|126,801,501|107,195,973|19,605,528|
|cache-references|210,171,035|171,331,504|38,839,531|
可以從結果看出 list_sort 對 cache 相當友善!
:::success
TODO : 改進 q_sort
:::
---
### Valgrind + 自動測試程式
**Valgrind 簡介**
valgrind 是個在使用者層級 (user space) 對程式在執行時期的行為提供動態分析的系統軟體框架,具備多種工具,可以用來追蹤及分析程式效能。
* [memcheck](https://valgrind.org/docs/manual/mc-manual.html) : 用於偵測記憶體錯誤,例如存取非法記憶體位置、使用未初始化的值等等。
* [massif](https://valgrind.org/docs/manual/ms-manual.html) : 他是一個 head profiler,用來量測 heap 可使用空間。
* [chachegrind](https://valgrind.org/docs/manual/cg-manual.html) : 能夠精確地量測程式執行的指令數。
<詳細功能可參閱超連結>
valgrind 會自行維護一份暫存器與記憶體的副本來安全地使用並測試,因為他屬於 dynamic Binary Instrumentation (DBI) 框架,因此會操作機械碼來進行分析,但這過程會經過轉譯成 VEX 中間表示法 (intermediate representation,簡稱 IR,是種虛擬的指令集架構),並插入分析工具來進行檢測。
---
### 研讀論文〈Dude, is my code constant time?〉
論文網址: [<Dude, is my code constant time?>](https://eprint.iacr.org/2016/1123.pdf)
#### Introduction
##### 歷史回顧
Timing attacks 是一種 side-channel 實體攻擊手法,攻擊者主要是基於不同的 logical operation,來量測每個 opertaion 執行時間推敲出輸入資料的資訊
為了解決人工解查耗時的問題,現今研究提出了由**動態分析工具**`Valgrind`延伸而來的`ctgrind`去追蹤 secret-dependant paths 或者 memory accesses。也有人利用**靜態指令分析**去分析部分程式碼是否為常數執行時間
嵌入硬體過程繁雜,難以建模實現。
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
##### 本篇論文提出貢獻
* dudect 偵測程式碼執行時間是否為常數執行時間
* 專注在執行時間量測的統計分析,非靜態程式碼分析,可規避硬體建模上的困難
What is timing variability ?
---
#### 流程
**step 1. Measure execution time**
根據兩組不同資料,執行 leakage detection test 去統計執行時間,因為有多種 leakage detection test 常見的手法有兩種 **fix** 與 **random**
* Classes definition
- fix : 輸入資料固定為 constant value,fixed value 有可能可以用來觸發極端案例
- random : 輸入資料隨機產生
* Cycle counters
- CPUs 提供精確的執行時間量測
* Environmental conditions
- 為了減少環境差異影響 ,每次量測前都會對應到一個隨機的類別
**step 2. Apply post-processing**
在做統計分析前會先做以下一些處理
* Cropping : 典型的時間分布屬於正偏斜,也就是大部分時間分布都落在較短時間,這是因為許多程序會被作業系統或者其他事件給中斷,因此很容易可以根據設定閾值去切割量測的範圍
![image alt](https://hackmd.io/_uploads/rykpk1an6.png =250x250) Right skewed = Postively skewed
* Higher-order preprocessing : 根據統計分析結果可以應用 higher-order 的預處理,像是 centered product 模仿了 higher-order DPA 攻擊
**Step 3. Apply statistical test**
去驗證 「*兩個時間分布是相等*」 的虛無假設
* T-test
* Non-parametric test
#### dudect 程式碼
:::danger
指出論文和程式碼之間的差異。
:::
從[dudect/src/dudect.h](https://github.com/oreparaz/dudect/blob/master/src/dudect.h#L419)開始分析
```c
static int64_t percentile(int64_t *a_sorted, double which, size_t size)
```
從參數名稱`int64_t *a_sorted`知道輸入資料必須是經過排序,輸入資料要從另一個函式`prepare_percentiles`取得
```c
static void prepare_percentiles(dudect_ctx_t *ctx) {
qsort(ctx->exec_times, ctx->config->number_measurements, sizeof(int64_t), (int (*)(const void *, const void *))cmp);
for (size_t i = 0; i < DUDECT_NUMBER_PERCENTILES; i++) {
ctx->percentiles[i] = percentile(
ctx->exec_times, 1 - (pow(0.5, 10 * (double)(i + 1) / DUDECT_NUMBER_PERCENTILES)),
ctx->config->number_measurements);
}
}
```
這兩個函式在 $1-0.5^{10*(i+1)/100}$ 控制抽樣的比例
* 論文提到根據經驗較低的 cycle count tail 比較不會受資料相依(**noise 中文待查**)的影響去切割量測(measurements),當大於某個 percentile-p 時將其去除,此操作可以提升偵測速度
---
#### Simlation
如何透過以實驗而非理論分析來驗證時間複雜度
解釋 Student's t-test 以及實作
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
Student's t-test : 先介紹什麼是**假說檢定**,主要功能是透過現有的數據去支持特定假設的方法,因此會有兩個假說 *Null hypothesis* $H0$ 以及對立的假說 *Alternative hypothesis* $H1$,根據結果來推論真正的母體。再來就是 Student's t-test,它是用來檢定兩組資料的母體平均數是否有顯著差異,但再進行檢定前必須滿足三個前提假設,[獨立性、常態性、變異數同質性](https://highscope.ch.ntu.edu.tw/wordpress/?p=69817),才會去進行 Student's t-test 倘若兩組資料變異數不具同質性,才會使用 Welch’s t-test 來檢定會更具可靠性
來自 [lab0-c/dudect/ttest.c](https://github.com/sysprog21/lab0-c/blob/master/dudect/ttest.c) 實作 Welch’s t-test 分成三組函數
* `void t_push(t_context_t *ctx, double x, uint8_t class)` : 裡面有計算變異數 Welford’s 方法
* `double t_compute(t_context_t *ctx)` : 計算在變異數不同下的 t 統計量
* `void t_init(t_context_t *ctx)` : 初始化所有統計變數
將 [dudect/src/dudect.h](https://github.com/oreparaz/dudect/blob/master/src/dudect.h#L205) 中的 percentile 引入 lab0-c,新增在[commit : da59019](https://github.com/sysprog21/lab0-c/commit/da59019b59072549540adf9352fcf78c0f1a13c0)
```c
ctx->percentiles[i] = percentile(
exec_times,
1 - (pow(0.5, 10 * (double))(i + 1) /DUDECT_NUMBER_PERCENTILES)),
N_MEASURES);
```
可以知道 `percentiles[i]` 由小至大由 $1-0.5^{10*(i+1)/100}$ (下圖為變化曲線) 加權設置不同的閾值來限制 measurements ,但仔細看曲線可以發現所有的`percentiles`並不全然相異,等於從時間統計中抽樣出來的值會重複在 larger time
![image alt](https://hackmd.io/_uploads/Hynm0gzaT.png =400x300)
* [traces/trace-17-complexity.cmd](https://github.com/sysprog21/lab0-c/blob/master/traces/trace-17-complexity.cmd) : 提供模擬參數
* [dudect/fixture.c](https://github.com/sysprog21/lab0-c/blob/master/dudect/fixture.c) : 多次給定執行函式測量執行時間,並且執行 Welch's t-test 來檢定是否為 constant time,
---
## Code review
> 錄影 : [Amazing Code Reviews](https://www.youtube.com/watch?v=ly86Wq_E18o)
有時候我們會像單細胞生物一樣 PR 與 merge,但事實上這樣很容易會有程式問題,因此作者強調著重在 code review 的工程上會讓團隊生產更有效率
* Build the right thing : 越早 PR 越有它的價值,==can show direction==、==minimize re-work==,因此要保持開放的心態去接受批評。
* Build the thing right : 每次 PR 盡可能控制程式碼行數 200-300 行,使得別人在 code review 時不會看得眼花撩亂,但也不要太小無法了解整體的想法。1 PR == 1 Concern 一個 PR 只關注一件事情,萬一程式碼出錯不必逐一偵錯。
* Build it fast : 篩選 PR,避免在 review 下面過多的討論
* Build it toghether
---
## Tic-tac-toe
**前提** : 在 monte-calro 中大量使用 *浮點數* 計算,當我們無法接受浮點數帶來的計算負擔時,就需要使 *定點數* 來解決。
其實一開始直觀的想法,浮點數與定點數都屬於小數,為什麼定點數就能比浮點數還低的計算開銷,參考了[作業講解](https://hackmd.io/@sysprog/linux2024-ttt#-Linux-%E6%A0%B8%E5%BF%83%E7%9A%84%E6%B5%AE%E9%BB%9E%E6%95%B8%E9%81%8B%E7%AE%9)中提到,kernel 會多了 trap 以及 initiate 這兩步驟,那定點數呢?
> When using floating-point instructions kernel normally catches a trap and then initiates the transition from integer to floating point mode.
定點數雖然是小數,但他不屬於 IEEE 754 的浮點數規範,不必處裡浮動的小數點,在某些精度要求不高的地方,定點數可以比浮點數更有效率,也要特別注意浮點數與定點數的表示方式是有差異的,在這裡很容易搞混。
以下簡單的整理作業提到的定點數運算範例
```c
static unsigned long fixed_power_int(unsigned long x,
unsigned int frac_bits,
unsigned int n)
{
unsigned long result = 1UL << frac_bits;
if (n) {
for (;;) {
if (n & 1) {
result *= x;
result += 1UL << (frac_bits - 1);
result >>= frac_bits;
}
n >>= 1;
if (!n)
break;
x *= x;
x += 1UL << (frac_bits - 1);
x >>= frac_bits;
}
}
return result;
}
```
本範例的精確度取決於 `frac_bits` ,也就是當你固定小數位置,後面表示小數的位數有多少個 bits。
`result = 1UL << frac_bits` 這段程式碼的用意主要是在於,將其結果初始化成定點數的形式,因為日後的計算都是以定點數做操作。
再來迴圈內的所有乘法都屬於定點數乘法,因此經過乘法後小數點偏移,因此要做一次反操作成規定的定點數位置。
### 整合網頁伺服器
line editing library 是在 CLI 中提供給使用者功能的 library,linenoise 就是其中一種輕型的應用。以下來自[官方 github](https://github.com/antirez/linenoise) 提供的用途
* Single and multi line editing mode with the usual key bindings implemented.
* History handling.
* Completion.
* Hints (suggestions at the right of the prompt as you type).
* Multiplexing mode, with prompt hiding/restoring for asynchronous output.
我從 `do_web` 中發現當執行 web 命令後 use_lineboise 會被設為 false 也就是會將 linenoise 關閉,那這個會如何影響 web 呢? 先回去看一下 linenoise。
#### linenoise.c
`linenoise.c/linenoise` 這個函式主要用來檢查終端的基本能力。
首先會透過 `isatty` 檢查是否連上 terminal ,因為每個 terminal 都會有一個唯一的 device file 儲存在 dev/ 下,也可透過 [`tty`](https://support.microsoft.com/zh-tw/office/%E4%BB%80%E9%BA%BC%E6%98%AF-tty-2c46b431-6681-43e6-b8a5-cf3886e3a53d) 命令將當前終端的名稱顯示出來。如果 terminal 支援但無法解析輸入命令就會執行 `line_raw` 切換到 *raw mode* 讀取命令,使用者不管有沒有 enter 命令都會被完整 `read`。
>[Terminal mode](https://en.wikipedia.org/wiki/Terminal_mode)
假設使用者在 terminal 中輸入 “ABC<Backspace>D”
>1. cooked mode :在資料傳進程式之前會先做預處理,變成 ABD
>2. raw mode :不做任何的特殊字轉換就將其送入程式,變成 ABC<Backspace>D
在 `console.c` 中如果 `linenoise` 被關閉後,只會執行 `cmd_select` 而不會去透過 `linenoise` 處理終端輸入的命令。
不過目前還是沒辦法理解作業中提到的「當 read 阻塞時,便無法接收 web 傳來的資訊」,我有去參考 [vax-r](https://hackmd.io/@vax-r/linux2024-homework1#%E6%95%B4%E5%90%88%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8) 將 `cmd_select` 中的 `char *cmdline = readline()` 將其改成 `linenoise(prompt)` 去處理輸入的命令,但會發現只能在 web 成功完成命令,在終端卻無法同時使用,這裡給的結論是 **read 被阻塞**,不過我覺得不夠清楚理解(TODO:解釋為何無法同時操作)。