# 2020q1 Homework4 (kcalc)
contributed by < `StevenChen8759` >
###### tags: `linux2020`
> [作業說明 - kcalc](https://hackmd.io/@sysprog/linux2020-kcalc#-%E9%A0%90%E6%9C%9F%E7%9B%AE%E6%A8%99)
> [作業繳交區](https://hackmd.io/@sysprog/linux2020-homework4)
> [Github](https://github.com/StevenChen8759/kcalc)
> [color=lightgreen]
# :computer: 開發記錄
## :keyboard: 基本運作原理分析
* 核心模組 `calc.ko` 在啟動時會向 Kernel 註冊裝置,可在 `/dev` 資料夾下找到裝置 `calc` ,透過檔案裝置讀寫的方式實作運算式輸入與結果輸出,可在 `main.c` 中找到相關操作函式的實作。
* `calc.ko` 整合了 `expression.[ch]`,基於 Marco Defined Vector 提供運算式剖析與運算式計算功能,在 `main.c` 中的 `calc()` 函式可找到相對應的函式呼叫。
* 在使用者定義的 `nop()` 函式上採用了 `Livepatching` 的技巧,在 `main.c` 中會預設呼叫 `user_func_fib()` 函式,在 Livepatching 模組插入核心後,將會改成呼叫 `livepatch_nop()` 函式,如此即可在不卸載 `calc.ko` 核心模組的情況下,達成抽換 callback 的功能。
## :keyboard: 實作 LivePatched 費氏數列計算函式
* 首先,我們先在 `main.c` 中仿照 `nop()` 相關函式的宣告,在 `main.c` 中實作使用者函式 `fib()` 的呼叫介面。
```cpp=135
static struct expr_func user_funcs[] = {
{"nop", user_func_nop, user_func_nop_cleanup, 0},
{"fib", user_func_fib, user_func_fib_cleanup, 0},
{NULL, NULL, NULL, 0},
};
```
```cpp=119
noinline void user_func_fib_cleanup(struct expr_func *f, void *c)
{
(void) f;
(void) c;
}
noinline int user_func_fib(struct expr_func *f, vec_expr_t args, void *c)
{
(void) args;
(void) c;
pr_info("user_func_fib");
return 77777;
}
```
* 在變數 `user_funcs` 中,包含了使用者定義函式的名稱與 callback 等資訊,在函式 `calc()` 中會一併將此參數輸入 `expression.[ch]` 的函式 `expr_create()` 中,並回傳包含使用者定義函式 `fib()` 的運算式剖析器。在剖析器產生後,呼叫 `expr_eval()` 函式,以求得輸入函式的結果。
```cpp=141
static void calc(void)
{
struct expr_var_list vars = {0};
struct expr *e = expr_create(message, strlen(message), &vars, user_funcs);
if (!e) {
pr_alert("Syntax error");
return;
}
result = expr_eval(e);
pr_info("Result: %d\n", result);
expr_destroy(e, &vars);
}
```
* 在 `livepatch-calc.c` 中,我們同樣仿照 `nop()` 的 livepatching 的相關函式,宣告 `fib()` 的 livepatching 介面。
```cpp=124
static struct klp_func funcs[] = {
{
.old_name = "user_func_nop",
.new_func = livepatch_nop,
},
{
.old_name = "user_func_nop_cleanup",
.new_func = livepatch_nop_cleanup,
},
{
.old_name = "user_func_fib",
.new_func = livepatch_fib,
},
{
.old_name = "user_func_fib_cleanup",
.new_func = livepatch_fib_cleanup,
},
{},
};
```
```cpp=87
void livepatch_fib_cleanup(struct expr_func *f, void *c)
{
(void) f;
(void) c;
}
int livepatch_fib(struct expr_func *f, vec_expr_t args, void *c)
{
(void) args;
(void) c;
pr_info("function fib is now patched, address of args: %p\n", &args);
if (args.len == 0) {
pr_err("No any parameter input...");
return -1;
}
if (args.buf[0].type != 25) { // For marco OP_CONST
pr_err("Input argument for fib() error...");
return -1;
}
struct expr x = vec_pop(&args);
int ipn = GET_NUM_LP(x.param.num.value),
cnt = GET_FRAC_LP(x.param.num.value);
while (cnt--)
ipn *= 10;
pr_info("%d, %d, %d, %d\n", x.param.num.value, ipn, cnt,
FP2INT_LP(ipn, cnt));
return FP2INT_LP(fibn(ipn), 0);
}
```
* 在 `expression.[ch]` 中,數值的表示方式採用了 [MathEX 自定義的浮點數型別](https://hackmd.io/@sysprog/linux2020-kcalc#kcalc-%E5%B0%87-MathEx-%E6%95%B4%E5%90%88%E9%80%B2-Linux-%E6%A0%B8%E5%BF%83%E6%A8%A1%E7%B5%84),在此採用 `int` 型別儲存,LSB 4 bit 存放 exponent,剩下 28 bit 則存放 mantissa,因此在 Livepatching 端的 `fib()` 函式會將已轉換的數值還原成整數,再導入 `fibdrv` 中,基於 `Fast Doubling` 方法的費氏數列計算函式 `fibn()` 計算出費氏數後,再轉換成自定義的浮點數型別並回傳之。
* 其中,因為 `expression.[ch]` 中的函式`GET_NUM`、`GET_FRAC` 以及 `FP2INT` 在 `livepatching-calc.ko` 編譯時並未連接 `expression.o`,因此會跳出 `Symbol NOT Found` 的警告,並在嘗試 `insmod` 時失敗,在此暫時以我們自行在 Livepatching 中定義的函式取代之 (應有更佳的做法),參考以下的實作:
```cpp=11
#define GET_NUM_LP(n) ((n) >> 4)
```
```cpp=18
int GET_FRAC_LP(int n)
{
int x = n & 15;
if (x & 8)
return -(((~x) & 15) + 1);
return x;
}
int FP2INT_LP(int n, int d)
{
/* Tailed zero counting */
while (n && n % 10 == 0) {
++d;
n /= 10;
}
if (d == -1) {
n *= 10;
--d;
}
return ((n << 4) | (d & 15));
}
/* Fibonacci Number calculating via fast doubling */
int fibn(int n)
{
int t0 = 1, t1 = 1; // For F[n], F[n+1]
int t3 = 1, t4 = 0; // For F[2n], F[2n+1]
int i = 1;
if (n <= 0)
return 0;
else if (n > 40) // Limitation for fixed point number
return -1;
while (i < n) {
if ((i << 1) <= n) {
t4 = t1 * t1 + t0 * t0;
t3 = t0 * (2 * t1 - t0);
t0 = t3;
t1 = t4;
i = i << 1;
} else {
t0 = t3;
t3 = t4;
t4 = t0 + t4;
i++;
}
}
return t3;
}
```
* 修改測試程式並執行測試,輸出結果如下:
```shell
$ make check FIBN=10
make -C /lib/modules/4.15.0-96-generic/build M=/home/stch/linux2020/kcalc modules
...
scripts/test.sh
filename: /home/stch/linux2020/kcalc/calc.ko
version: 0.1
description: Math expression evaluation
author: National Cheng Kung University, Taiwan
license: Dual MIT/GPL
srcversion: 90FFBFE01F8E40CA140EE46
depends:
retpoline: Y
name: calc
vermagic: 4.15.0-96-generic SMP mod_unload
...
Testing nop() ...
-.10000000000000000000
Testing fib() ...
48610
livepatch was applied
Testing nop() ...
40
[ 6477.032543] calc: Received 6 -> nop()
[ 6477.032558] livepatch_calc: function nop is now patched
[ 6477.032560] calc: Result: 640
[ 6477.032568] calc: Device successfully closed
[ 6477.036158] calc: size: 4 result: 640
[ 6477.036279] calc: Device successfully closed
Testing fib(10) ...
55
[ 6477.152202] livepatch_calc: function fib is now patched
[ 6477.152206] livepatch_calc: 17, 10, -1, 16
[ 6477.152208] calc: Result: 880
[ 6477.152218] calc: Device successfully closed
[ 6477.155352] calc: size: 4 result: 880
[ 6477.155875] calc: Device successfully closed
Disabling livepatch...
Complete
```
* 目前因為變數儲存空間的限制,僅能計算 F[0] ~ F[40] 項,超過範圍會回傳 0 or -1。
# :bulb: 問題與討論
## :arrow_forward: `MathEx` 中會造成計算錯誤部分的改善
* 在程式測試時,發現 `fib()` 函式輸入空值進行 `vec_pop()` 造成記憶體存取錯誤,原因是未檢查 vector 內的元素數量而使程式存取到非法的記憶體位址。我們將 `livepatch-calc.c` 中 `livepatch_fib()` 函式內實現的元素數量檢查部分移除,並在程式測試時輸入空的參數 `fib()` 函式內,即可重現此錯誤:
```shell
$ make check
make -C /lib/modules/4.15.0-96-generic/build M=/home/stch/linux2020/kcalc modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-96-generic'
CC [M] /home/stch/linux2020/kcalc/livepatch-calc.o
Building modules, stage 2.
MODPOST 2 modules
CC /home/stch/linux2020/kcalc/livepatch-calc.mod.o
LD [M] /home/stch/linux2020/kcalc/livepatch-calc.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-96-generic'
scripts/test.sh
[sudo] password for stch:
filename: /home/stch/linux2020/kcalc/calc.ko
version: 0.1
description: Math expression evaluation
author: National Cheng Kung University, Taiwan
license: Dual MIT/GPL
srcversion: 90FFBFE01F8E40CA140EE46
depends:
retpoline: Y
name: calc
vermagic: 4.15.0-96-generic SMP mod_unload
...
Testing nop() ...
-.10000000000000000000
Testing fib() ...
48610
livepatch was applied
Testing nop() ...
40
[ 7802.137687] calc: Received 6 -> nop()
[ 7802.137701] livepatch_calc: function nop is now patched
[ 7802.137703] calc: Result: 640
[ 7802.137710] calc: Device successfully closed
[ 7802.140320] calc: size: 4 result: 640
[ 7802.140409] calc: Device successfully closed
Testing fib() ...
Makefile:40: recipe for target 'check' failed
make: *** [check] Killed
$ dmesg
...
[ 7802.193924] livepatch_calc: function fib is now patched
[ 7802.193955] BUG: unable to handle kernel NULL pointer dereference at 0000000000000000
[ 7802.193973] IP: livepatch_fib+0x20/0x230 [livepatch_calc]
[ 7802.193977] PGD 8000000012b9b067 P4D 8000000012b9b067 PUD d829067 PMD 0
[ 7802.193986] Oops: 0000 [#1] SMP PTI
...
[ 7802.195082] RIP: livepatch_fib+0x20/0x230 [livepatch_calc] RSP: ffffac948299fe20
[ 7802.195084] CR2: 0000000000000000
[ 7802.195091] ---[ end trace bf22f4e274b1adf4 ]--
```
* 因此,我們應仿效呼叫 `malloc()` 時的記憶體檢查方式,在進行 `vec_pop()` 時,皆須檢查對應 Vector 的 `len` 值是否為0,如此即可避免錯誤的記憶體存取
>
## :arrow_forward: Linux Kernel 中的定點數使用案例
* 參考[先前的 `kcalc` 作業說明](https://hackmd.io/@sysprog/SyC9V0gUE?type=view#%E9%87%8D%E6%96%B0%E5%AD%B8%E7%BF%92%E6%B5%AE%E9%BB%9E%E9%81%8B%E7%AE%97%E5%92%8C%E5%AE%9A%E9%BB%9E%E6%93%8D%E4%BD%9C),Linux Kernel 的 CFS (Completely Fair Scheduler) 使用了定點數的技巧。
* 參閱 [`linux/include/linux/sched.h`](https://github.com/torvalds/linux/blob/master/include/linux/sched.h),可發現 Linux Kernel 中的定點數使用了 10 bits 作為小數,並使用剩餘位元表示整數。
```cpp=311
/*
* Integer metrics need fixed point arithmetic, e.g., sched/fair
* has a few: load, load_avg, util_avg, freq, and capacity.
*
* We define a basic fixed point arithmetic range, and then formalize
* all these metrics based on that basic range.
*/
# define SCHED_FIXEDPOINT_SHIFT 10
# define SCHED_FIXEDPOINT_SCALE (1L << SCHED_FIXEDPOINT_SHIFT)
/* Increase resolution of cpu_capacity calculations */
# define SCHED_CAPACITY_SHIFT SCHED_FIXEDPOINT_SHIFT
# define SCHED_CAPACITY_SCALE (1L << SCHED_CAPACITY_SHIFT)
```
* 除此之外,[`linux/kernel/sched/fair.c`](https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c) 中也使用了定點數的技巧,參考以下的說明:
```cpp
/*
* The exact cpuload calculated at every tick would be:
*
* load' = (1 - 1/2^i) * load + (1/2^i) * cur_load
*
* If a CPU misses updates for n ticks (as it was idle) and update gets
* called on the n+1-th tick when CPU may be busy, then we have:
*
* load_n = (1 - 1/2^i)^n * load_0
* load_n+1 = (1 - 1/2^i) * load_n + (1/2^i) * cur_load
*
* decay_load_missed() below does efficient calculation of
*
* load' = (1 - 1/2^i)^n * load
*
* Because x^(n+m) := x^n * x^m we can decompose any x^n in power-of-2 factors.
* This allows us to precompute the above in said factors, thereby allowing the
* reduction of an arbitrary n in O(log_2 n) steps. (See also
* fixed_power_int())
*
* The calculation is approximated on a 128 point scale.
*/
```