# 2024q1 Homework5 (assessment)
contributed by < `nosba0957` >
## 改進測驗題
### 第三週測驗一
計算平方根的原理為
$$
N^2 = (a_n+a_{n-1}+a_{n-2}+...+a_0)^2,\ a_m=2^m \ or \ a_m=0
$$
乘開後得到
$$
\begin{bmatrix}
a_na_n & a_{n-1}a_n & a_{n-2}a_n & ... & a_0a_n\\
a_na_{n-1} & a_{n-1}a_{n-1} & a_{n-2}a_{n-1} & ... & a_0a_{n-1}\\
a_na_{n-2} & a_{n-1}a_{n-2} & a_{n-2}a_{n-2} & ... & a_0a_{n-2}\\
\vdots & \vdots & \vdots & \ddots & \vdots\\
a_na_0 & a_{n-1}a_0 & a_{n-2}a_0 & ... & a_0a_0\\
\end{bmatrix}
$$
分成幾個部分觀察
$$
\begin{bmatrix}
a_na_n
\end{bmatrix} = a_n^2
$$
$$
\begin{bmatrix}
\ & a_{n-1}a_n \\
a_na_{n-1} & a_{n-1}a_{n-1}
\end{bmatrix} = a_{n-1}(2a_n+a_{n-1})
$$
$$
\begin{bmatrix}
\ & \ & a_{n-2}a_n \\
\ & \ & a_{n-2}a_{n-1} \\
a_na_{n-2} & a_{n-1}a_{n-2} &a_{n-2}a_{n-2}
\end{bmatrix} = a_{n-2}(2(a_{n-1}+a_n)+a_{n-2})
$$
所以可以將 $N^2$ 表示為
$$
N^2=a_n^2+a_{n-1}(2a_n+a_{n-1})+a_{n-2}(2(a_n+a_{n-1})+a_{n-2})+...+a_0(\sum_{i=1}^{n}a_i+a_0)
$$
將 $\sum_{i=m}^{n}a_i$ 定義為 $P_m$,而 $P_0=a_n+a_{n-1}+a_{n-2}+...+a_0$ 就是我們要求的平方根 $N$。所以接下來要找出所有組成平方根 $N$ 的所有 $a_m$。我們可以得知
$$
P_m = P_{m+1} + a_m
$$
其中 $a_m = 2^m \ or \ a_m=0$。找到平方根的方式就是讓 $P_m=N$,所以透過每一輪比較 $P_m^2 \leq N^2$ 來決定 $a_m$ 是否要加入到 $P_m$,也就是決定 $a_m$ 的值為 $0$ 或是 $2^m$。
$$
\begin{cases}
P_m = P_{m+1} + a_m, & \text{if } P_m^2 \leq N^2 \\
P_m = P_{m+1}, & \text{otherwise}
\end{cases}
$$
因為每輪計算 $N^2-P_m^2$ 的運算成本太高,所以改為紀錄每輪的差值 $X_m$,計算本輪差值的方式為 $X_m = X_{m+1} - Y_m$,$X_{m+1}$ 為上一輪計算完的差值。
$$
\begin{aligned}
X_m &= N^2-P_m^2 = X_{m+1} - Y_m \\
Y_m &= P_m^2-P_{m+1}^2 = 2P_{m+1}a_m+a_m^2
\end{aligned}
$$
再將 $Y_m$ 拆為 $c_m$,$d_m$
$$
\begin{aligned}
c_m&=2P_{m+1}a_m = P_{m+1}2^{m+1} \\
d_m&=a_m^2=(2^m)^2 \\
Y_m &= \begin{cases}
c_m+d_m, &\text{if }a_m=2^m\\
0, &\text{if }a_m=0
\end{cases}
\end{aligned}
$$
每輪的 $c_m$,$d_m$ 都會更新,所以試著計算 $m$ 的下一輪,$m-1$ 輪的結果
$$
\begin{aligned}
c_{m-1}&=2P_ma_{m-1} = (P_{m+1}+a_m)2^m\\
&=P_{m+1}2^m+a_m2^m \\
&=\begin{cases}
c_m/2+d_m, & \text{if }a_m=2^m \\
c_m/2, & \text{if } a_m=0 \\
\end{cases}\\ \\
d_{m-1}&=a_{m-1}^2 = (2^{m-1})^2 = d_m/4
\end{aligned}
$$
綜合上述方式,要從最大的位元開始測試,即從 $a_n$ 開始試到 $a_0$,初始情況為
+ $X_{n+1}=N^2$
+ $c_n=0$
+ $P_{n+1}=0$
在程式碼中 `z` 是 $c_m$,`m` 是 $d_m$,`x` 為 $X_m$,`b` 是 $Y_m$。
```c
for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2)
```
for 迴圈的初始情況先將 $d_m$ 也就是 `m`。首先 `(31 - __builtin_clz(x))` 可以找到最高位的 1 距離 lsb 的長度。接下來 `& ~1UL` 讓 `(31 - __builtin_clz(x))` 算出來的長度調整為偶數,如此一來 `1UL` 經過位移後,`m` 必為2的冪且有整數平方根。接下來每次迴圈都會執行 `m >>= 2`,對應到上面敘述的 $d_{m-1}=d_m/4$。
```c
int b = z + m;
z >>= 1;
if (x >= b)
x -= b, z += m;
```
迴圈內,`z >>= 1` 對應到先前敘述的 $c_{m-1}$ 的計算,先計算 $c_m/2$,是否要將 $d_m$ 加入則是由 $a_m$ 的值決定,也就是判斷 `x >= b`,成立後才執行 `z += m`,即為 $c_{m-1}=c_m/2+d_m$ 這段敘述。
#### 使用 fls 取代 `__builtin_clz`
```c
int fls(int x)
{
int result = 0;
if (x & 0xFFFF0000) {
result += 16;
x >>= 16;
}
if (x & 0xFF00) {
result += 8;
x >>= 8;
}
if (x & 0xF0) {
result += 4;
x >>= 4;
}
if (x & 0xC) {
result += 2;
x >>= 2;
}
if (x & 0x2) {
result += 1;
x >>= 1;
}
if (x & 0x1)
result += 1;
return result;
}
```
學習 [第二週測驗題](https://hackmd.io/@sysprog/linux2024-quiz2) 使用二分搜尋法找到最靠近 MSB 並且被設為 1 的位置。接下來將 `(31 - __builtin_clz(x))` 替換為 `(32 - fls(x))`。
```c
int branchless_fls(int x)
{
int result = 0;
int move = 0;
move = 16 & (~(!!(x & 0xFFFF0000)) + 1);
result += move;
x >>= move;
move = 8 & (~(!!(x & 0xFF00)) + 1);
result += move;
x >>= move;
move = 4 & (~(!!(x & 0xF0)) + 1);
result += move;
x >>= move;
move = 2 & (~(!!(x & 0xC)) + 1);
result += move;
x >>= move;
move = 1 & (~(!!(x & 0x2)) + 1);
result += move;
x >>= move;
move = 1 & (~(!!(x & 0x1)) + 1);
result += move;
x >>= move;
return result;
}
```
無分支的實作會需要另一個變數 `move` 協助運算。以第一行計算為例,透過 `!!` 將 `x & 0xFFFF0000` 的值轉為 0 或 1。
+ 若為 0,經過 bitwise NOT 運算後會得到 `0xFFFFFFFF`,再加 1 會得到 0。
+ 若為 1,經過 bitwise NOT 運算後會得到 `0xFFFFFFFE`,再加 1 得到 `0XFFFFFFFF`。
而 `0xFFFFFFFF` 就可以作為 mask 和 16 進行 bitwise AND 運算。
#### Linux 核心原始程式碼使用 int_sqrt
在 [block/blk-wbt.c](https://github.com/torvalds/linux/blob/e34cbd307477ae07c5d8a8d0bd15e65a9ddaba5c/block/blk-wbt.c#L402) 可以找到,從文件開頭可以知道主要是處理 buffered writeback throttling,在 [LWN](https://lwn.net/Articles/704739/) 有找到作者一開始嘗試的情況。大量的 background buffered writeback 會導致 foreground 活動也受影響(下面例子是用開啟 chrome)。
>When we do background buffered writeback, it should have little impact
on foreground activity. That's the definition of background activity...
But for as long as I can remember, heavy buffered writers have not
behaved like that.
:::danger
command 是「命令」,而非「指令」(instruction)
:::
一開始作者使用<s>指令</s> 製作檔案,並且嘗試開啟 chrome,發現要等到 writeback 結束 chrome 才會開啟。
```bash
$ dd if=/dev/zero of=foo bs=1M count=10k
```
這段程式碼看最後一行 `mod_timer` 可以知道是調整計時器的倒數時間。
```c
static void rwb_arm_timer(struct rq_wb *rwb)
{
unsigned long expires;
if (rwb->scale_step > 0) {
/*
* We should speed this up, using some variant of a fast
* integer inverse square root calculation. Since we only do
* this for every window expiration, it's not a huge deal,
* though.
*/
rwb->cur_win_nsec = div_u64(rwb->win_nsec << 4,
int_sqrt((rwb->scale_step + 1) << 8));
} else {
/*
* For step < 0, we don't want to increase/decrease the
* window size.
*/
rwb->cur_win_nsec = rwb->win_nsec;
}
expires = jiffies + nsecs_to_jiffies(rwb->cur_win_nsec);
mod_timer(&rwb->window_timer, expires);
}
```
### 第三週測驗二
因為 10 不能表示為二的冪,所以透過精確度的計算找到可以使用 $\frac{128}{13} \approx 9.84$ 來估算除以 10。這邊使用的方式是透過找到 $\frac{n}{8}+\frac{n}{2}+n=\frac{13}{8}n$ 後再乘上 8 即可找到 $13n$。
```c
(((n >> 3) + (n >> 1) + n) << 3)
```
接下來再除 128,可以得到 $\frac{13}{128} \approx 0.10$。
```c
q = ((((n >> 3) + (n >> 1) + n) << 3) + d0 + d1 + d2) >> 7;
```
這邊 `d0`,`d1`,`d2` 是要處理右移後被捨棄的位元。但這裡應該要對 `n` 做 bitwise AND 而不是對 `q`,因為被右移的是 `n`。
```c
d0 = q & 0b1;
d1 = q & 0b11;
d2 = q & 0b111;
```
下面是另一種表示方式,和上面方式不太一樣。首先計算 `x` 為 $\frac{3}{4}in$,但這裡不清楚為何需要 `(in | 1)`。再計算 `q` 為 $(\frac{3}{64} + \frac{3}{4})in$,而其中 $(\frac{3}{64} + \frac{3}{4}) \approx 0.80$。接下來四次執行 `q = (q >> 8) + x;` 是為了讓 `q` 逼近 $0.8in$,最後再透過 `q >> 3` 就可以得到 $0.1in$。而最後 `mod` 計算,`(q & ~0x7)` 相當於 `div << 3`,所以最後一行程式碼可以看成 `in - (8 * div + 2 * div)`。
```c
void divmod_10(uint32_t in, uint32_t *div, uint32_t *mod)
{
uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */
uint32_t q = (x >> 4) + x;
x = q;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
*div = (q >> 3);
*mod = in - ((q & ~0x7) + (*div << 1));
}
```
#### 分析佔用的 CPU 週期數
透過使用 `objdump -S` 產生組合語言,查看 intel ice lake and tiger lake 的表。以下是使用 `/` 和 `%`。
```
0000000000001149 <divmod_10>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 89 7d fc mov %edi,-0x4(%rbp)
1154: 48 89 75 f0 mov %rsi,-0x10(%rbp)
1158: 48 89 55 e8 mov %rdx,-0x18(%rbp)
115c: 8b 45 fc mov -0x4(%rbp),%eax
115f: 48 63 d0 movslq %eax,%rdx
1162: 48 69 d2 67 66 66 66 imul $0x66666667,%rdx,%rdx
1169: 48 c1 ea 20 shr $0x20,%rdx
116d: c1 fa 02 sar $0x2,%edx
1170: c1 f8 1f sar $0x1f,%eax
1173: 29 c2 sub %eax,%edx
1175: 48 8b 45 f0 mov -0x10(%rbp),%rax
1179: 89 10 mov %edx,(%rax)
117b: 8b 4d fc mov -0x4(%rbp),%ecx
117e: 48 63 c1 movslq %ecx,%rax
1181: 48 69 c0 67 66 66 66 imul $0x66666667,%rax,%rax
1188: 48 c1 e8 20 shr $0x20,%rax
118c: 89 c2 mov %eax,%edx
118e: c1 fa 02 sar $0x2,%edx
1191: 89 c8 mov %ecx,%eax
1193: c1 f8 1f sar $0x1f,%eax
1196: 29 c2 sub %eax,%edx
1198: 89 d0 mov %edx,%eax
119a: c1 e0 02 shl $0x2,%eax
119d: 01 d0 add %edx,%eax
119f: 01 c0 add %eax,%eax
11a1: 29 c1 sub %eax,%ecx
11a3: 89 ca mov %ecx,%edx
11a5: 48 8b 45 e8 mov -0x18(%rbp),%rax
11a9: 89 10 mov %edx,(%rax)
11ab: 90 nop
11ac: 5d pop %rbp
11ad: c3 retq
```
1 個 `push` 花費 3 clock cycles。5 個 `mov`(r,r) 共花費 5 clock cycles。4 個 `mov`(r,m) 共花費 8 clock cycles。5 個 `mov`(m,r) 共花費 15 clock cycles。2 個 `movslq`(r,r) 花費 2 clock cycles。2 個 `imul` 共花費 6 clock cycles。2 個 `shr` 花費 2 clock cycles。4 個 `sar` 花費 4 clock cycles。3 個 `sub`(r,r) 花費 3 clock cycles。2 個 `add`(r,r) 花費 2 clock cycles。1 個 `pop` 花費 3 clock cycles。
總共 53 個 clock cycles。
以下是改為使用 shift/add 方式的組合語言。
```
0000000000000000 <divmod_10>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 89 7d ec mov %edi,-0x14(%rbp)
b: 48 89 75 e0 mov %rsi,-0x20(%rbp)
f: 48 89 55 d8 mov %rdx,-0x28(%rbp)
13: 8b 45 ec mov -0x14(%rbp),%eax
16: 83 c8 01 or $0x1,%eax
19: 89 c2 mov %eax,%edx
1b: 8b 45 ec mov -0x14(%rbp),%eax
1e: c1 e8 02 shr $0x2,%eax
21: 29 c2 sub %eax,%edx
23: 89 d0 mov %edx,%eax
25: 89 45 f8 mov %eax,-0x8(%rbp)
28: 8b 45 f8 mov -0x8(%rbp),%eax
2b: c1 e8 04 shr $0x4,%eax
2e: 89 c2 mov %eax,%edx
30: 8b 45 f8 mov -0x8(%rbp),%eax
33: 01 d0 add %edx,%eax
35: 89 45 fc mov %eax,-0x4(%rbp)
38: 8b 45 fc mov -0x4(%rbp),%eax
3b: 89 45 f8 mov %eax,-0x8(%rbp)
3e: 8b 45 fc mov -0x4(%rbp),%eax
41: c1 e8 08 shr $0x8,%eax
44: 89 c2 mov %eax,%edx
46: 8b 45 f8 mov -0x8(%rbp),%eax
49: 01 d0 add %edx,%eax
4b: 89 45 fc mov %eax,-0x4(%rbp)
4e: 8b 45 fc mov -0x4(%rbp),%eax
51: c1 e8 08 shr $0x8,%eax
54: 89 c2 mov %eax,%edx
56: 8b 45 f8 mov -0x8(%rbp),%eax
59: 01 d0 add %edx,%eax
5b: 89 45 fc mov %eax,-0x4(%rbp)
54: 89 c2 mov %eax,%edx
56: 8b 45 f8 mov -0x8(%rbp),%eax
59: 01 d0 add %edx,%eax
5b: 89 45 fc mov %eax,-0x4(%rbp)
5e: 8b 45 fc mov -0x4(%rbp),%eax
61: c1 e8 08 shr $0x8,%eax
64: 89 c2 mov %eax,%edx
66: 8b 45 f8 mov -0x8(%rbp),%eax
69: 01 d0 add %edx,%eax
6b: 89 45 fc mov %eax,-0x4(%rbp)
6e: 8b 45 fc mov -0x4(%rbp),%eax
71: c1 e8 08 shr $0x8,%eax
74: 89 c2 mov %eax,%edx
76: 8b 45 f8 mov -0x8(%rbp),%eax
79: 01 d0 add %edx,%eax
7b: 89 45 fc mov %eax,-0x4(%rbp)
7e: 8b 45 fc mov -0x4(%rbp),%eax
81: c1 e8 03 shr $0x3,%eax
84: 89 c2 mov %eax,%edx
86: 48 8b 45 e0 mov -0x20(%rbp),%rax
8a: 89 10 mov %edx,(%rax)
8c: 8b 45 fc mov -0x4(%rbp),%eax
8f: 83 e0 f8 and $0xfffffff8,%eax
92: 89 c2 mov %eax,%edx
94: 48 8b 45 e0 mov -0x20(%rbp),%rax
98: 8b 00 mov (%rax),%eax
9a: 01 c0 add %eax,%eax
9c: 01 c2 add %eax,%edx
9e: 8b 45 ec mov -0x14(%rbp),%eax
a1: 29 d0 sub %edx,%eax
a3: 89 c2 mov %eax,%edx
a5: 48 8b 45 d8 mov -0x28(%rbp),%rax
a9: 89 10 mov %edx,(%rax)
ab: 90 nop
ac: 5d pop %rbp
ad: c3 retq
```
1 個 `push` 花費 3 clock cycles。12 個 `mov`(r,r) 花費 12 clock cycles。13 個 `mov`(m,r) 花費 26 clock cycles。21 個 `mov`(r,m) 花費 63 clock cycles。1 個 `or` 花費 1 clock cycles。7 個 `shr` 花費 7 clock cycles。8 個 `add` 花費 8 clock cycles。2 個 `sub` 花費 2 clock cycles。1 個 `and` 花費 1 clock cycles。1 個 `pop` 花費 3 clock cycles。
總共 126 clock cycles。
#### 不依賴除法指令 modulo 9
參考延伸閱讀 [Modulus without Division, a tutorial](https://homepage.cs.uiowa.edu/~dwjones/bcd/mod.shtml)。先分析 `mod 3` 的情況。
首先
> All of the above rules are actually special cases of a single general rule. Given that
a is represented in number base b
a mod m = ( (b mod m)⌊a/b⌋ + (a mod b) ) mod m
可以得知若要將 `a` 改為用 `b` 進位表示(十進位,八進位 ...)
$$
a\ mod\ m = ((b\ mod\ m)\lfloor \frac{a}{b}\rfloor + (a\ mod\ b))\ mod\ m
$$
觀察上面式子,想要實作 `% 3` 可以透過將 `a` 轉為 4 進位,因為 $(b\ mod\ m)$ 為 $(4\ mod\ 3)$ 即為 $1$。所以整式為
$$
\begin{aligned}
a\ mod\ 3 &= ((4\ mod\ 3)\lfloor \frac{a}{4} \rfloor + (a\ mod\ 4))\ mod\ 3 \\
a\ mod\ 3&=(\lfloor \frac{a}{4} \rfloor + (a\ mod\ 4))\ mod\ 3
\end{aligned}
$$
其中可以發現 $(a\ mod\ 4)$ 是取出 `a` 在四進位中的第一個 digit。而 $\lfloor \frac{a}{4} \rfloor$ 便是 `a` 將第一個 digit 移去後剩下的值。所以如果重複執行 $(\lfloor \frac{a}{4} \rfloor + (a\ mod\ 4))$ 直到 `a` 小於 4,其意思就是將 `a` 轉為四進位後,把所有 digit 相加。
```c
uint32_t mod3( uint32_t a ) {
a = (a >> 16) + (a & 0xFFFF);
a = (a >> 8) + (a & 0xFF);
a = (a >> 4) + (a & 0xF);
a = (a >> 2) + (a & 0x3);
a = (a >> 2) + (a & 0x3);
a = (a >> 2) + (a & 0x3);
if (a > 2) a = a - 3;
return a;
}
```
這個程式碼是基於上面所述以四進位來計算 `mod 3`,但計算方式不同。本來的原理是透過計算四進位的所有 digit 總和,而這個方式改為依序計算 $2^{16}=4^8$ 進位(內文為 base-$4^8$)的 digit。接下來再計算 base-$2^8(=4^4)$ 的 digit 總和,再依序算到四進位的 digit 總和。可以透過最一開始的式子證明
$$
a\ mod\ 3 = (\lfloor \frac{a}{2^{16}} \rfloor + (a\ mod\ 2^{16}))\ mod\ 3
$$
用同餘的形式表示
$$
a \equiv (\lfloor \frac{a}{2^{16}} \rfloor + (a\ mod\ 2^{16}))\ mod\ 3
$$
可以再推算
$$
(\lfloor \frac{a}{2^{16}} \rfloor + (a\ mod\ 2^{16})) \equiv (\lfloor \frac{a}{2^8} \rfloor + (a\ mod\ 2^8))\ mod\ 3
$$
所以要撰寫 `mod 9` 的方式和 `mod 3` 類似,但因 9 並非 $2^i-1$,所以需要找到某數為 $2^i-1$ 且為 9 的倍數。所以找到以 $63=9\times 7$ 為除數來取餘數。
```c
uint32_t mod9(uint32_t a) {
a = (a >> 24) + (a & 0xFFFFFF);
a = (a >> 18) + (a & 0x3FFFF);
a = (a >> 12) + (a & 0xFFF);
a = (a >> 6) + (a & 0x3F);
if (a > 62) a -= 63;
while (a > 8) {
a -= 9;
}
return a;
}
```
首先找到 63 的餘數,和尋找 3 的餘數方式相同,找 3 的餘數要先計算四進位(base-4)的所有 digit 和,而找 63 的餘數就要先找 base-64 的所有 digit 和,即為 `a = (a >> 6) + (a & 0x3F);`。接下來因為 63 的餘數範圍為 0~62,所以再對此餘數 `mod 9`,這邊是透過減法撰寫。
:::warning
TODO: 證明針對 2 的冪 +/- 1 的 modulo 運算可找到對應不依賴除法運算的實作。
:::
### 第三週測驗三
判斷 ilog2 就是找到二進位最高位的 1 的位置,版本二是透過二分搜尋法來找。版本三是透過 `__builtin_clz` 得到最靠近 MSB 的第一個 set bit 前有幾個 0,`31 - __builtin_clz` 就可以得到最靠近 MSB 的 set bit 到 LSB 共有幾個 bits,就是 ilog2 的結果。
在 [ksm.c](https://github.com/torvalds/linux/blob/7efd0a74039fb6b584be2cb91c1d0ef0bd796ee1/mm/ksm.c#L3246) 中有找到 `ilog2` 的使用。KSM 是 [Kernel Samepage Merging](https://docs.kernel.org/admin-guide/mm/ksm.html) 的縮寫,主要用來找到有相同內容的 page,將他們替代為同一份共享的 write-protected page。
```c
static void wait_while_offlining(void)
{
while (ksm_run & KSM_RUN_OFFLINE) {
mutex_unlock(&ksm_thread_mutex);
wait_on_bit(&ksm_run, ilog2(KSM_RUN_OFFLINE),
TASK_UNINTERRUPTIBLE);
mutex_lock(&ksm_thread_mutex);
}
}
```
這邊的 `ilog2` 是用來取得 `KSM_RUN_OFFLINE` 的 set bit 位置。`KSM_RUN_OFFLINE` 的值為 4,所以 `ilog2` 會得到 2。[offline](https://docs.kernel.org/admin-guide/mm/memory-hotplug.html#onlining-and-offlining-memory-blocks) 是指某記憶體區塊若要被 kernel 移除(remove),則會被標記為 `offlined`。而 `wait_on_bit` 是讓此 thread 等待直到 `ilog2(KSM_RUN_OFFLINE)` 位置的 bit 設為 0,才能繼續下一步。
## 閱讀〈因為自動飲料機而延畢的那一年〉的啟發
從這篇文章中可以看到作者在製作飲料機的過程中經歷,過程中有許多失敗和挫折,但他還是不放棄的繼續做下去,很值得我學習。製作飲料機和我們學習 Linux 核心的精神很類似,有許多細節和設計都是很重要的卻也很難注意到的。在前幾週的 Linux 核心課程中,我深深體會到自己的不足,學習到軟體開發的態度。以前寫作業都是隨便寫,能跑得出正確答案就好。而現在寫作業需要閱讀大量資料,發現自己連英文閱讀都有點問題,還要注意許多細節,連實驗次數是如何訂定的也要注意,程式語法要直接參考規格書而不是 google 搜尋。
這堂課帶給我的不僅是誠實認知到電腦科學相關知識的不足,也讓我認知到我有許多心態需要改變。老師有說過,真強者是不怕世俗的批評,但我從寫開發紀錄時就發現我不太敢寫出自己的看法,每次寫下一句話就要花很久的時間思考,原因就是怕被批評、害怕別人看法、害怕失敗。老師在第一週就有提到一句話
> 大部分的人一輩子洞察力不彰,原因之一是怕講錯被笑。想了一點點就不敢繼續也沒記錄或分享,時間都花在讀書查資料看別人怎麼想。看完就真的沒有自己的洞察了
雖然看到這句話想要努力改變,但我在第五週看完老師 code review 後,反而變得更不敢發表自己的意見,因為我發現自己雖然寫了很多作業,但根本沒有掌握細節,許多地方都是似懂非懂。所以後來更害怕自己被批評,讀了論文怕自己哪裡沒讀清楚,怕寫上去後是錯的,所以不敢寫,拖延一段時間後又責怪自己沒有付出夠多的時間寫作業。所以我認為我在這六週表現不好,但看完〈因為自動飲料機而延畢的那一年〉,我了解到自己遇到的挫折和失敗幾乎不值一提,並且增加了許多勇氣,希望能一路堅持到期末。
## 課程回顧
### Linux 核心模組運作原理
[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module) 提到在新建立的裝置檔案 `/dev/fibonacci` 的數字會不同。
```bash
$ ls -l /dev/fibonacci
$ cat /sys/class/fibonacci/fibonacci/dev
```
在 [fibdrv.c](https://github.com/sysprog21/fibdrv/blob/master/fibdrv.c#L114) 中看到註解,於是尋找 `register_chrdev` 這個函式。先在 [fs.h](https://elixir.bootlin.com/linux/latest/source/include/linux/fs.h#L2685) 中找到該函式,接下來在 [Linux Kernel API](https://docs.kernel.org/core-api/kernel-api.html?highlight=register_chrdev#c.__register_chrdev) 中看到 `major` 這個參數,當其為 0 時,會動態配置 device number。
在講解 `__MODULE_INFO` 的巨集展開時提到
> 上述巨集的定義根據 MODULE 是否有被定義,MODULE 是在此核心模組被編譯時期所定義,若此模組編譯時已內建於核心則不會被定義。繼續將上述巨集展開
:::info
不理解此處,是指 `MODULE` 在編譯時會自動新增定義嗎?是由誰新增的?
> 注意看巨集展開,你可嘗試建構 [fibdrv](https://github.com/sysprog21/fibdrv) 並留意產生的 C 檔案,觀察其內容。 :notes: jserv
:::
模組載入時可以直接使用 `init_module()` 這個函式名稱是因為原始程式碼中使用 `module_init` 巨集,展開後會透過 [GCC extension](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-alias-function-attribute) alias 連結 `init_module()` 和我們自訂的模組初始化函式。
#### 解釋 `fibdrv.ko` 是如何植入到 Linux 核心
`insmod` 會使用系統呼叫 `finit_module`,再觀察此系統呼叫的程式碼,發現會使用 [kernel_read_file](https://elixir.bootlin.com/linux/v6.9-rc1/source/kernel/module/main.c#L3149) 函式透過 file descriptor `fd` 來讀取檔案。
## 2024-05-01
[shecc](https://github.com/sysprog21/shecc)
RISC-V auipc, jalr
TODO:
1. 在 Linux 環境中,執行 shecc,並設定 Arm 和 RISC-V backend,觀察 bootstrap
2. 看報告,紀錄問題
3. https://github.com/sysprog21/shecc/blob/master/lib/c.c 產生的執行檔會用到 Linux 系統呼叫
4. Engineering a compiler 的 SSA 描述 => SSA 要解決什麼問題?什麼樣的 optimizer 可用 SSA 表達?SSA 的限制?
Known issues
* shecc 無法搭配 perf 使用