# 2020q1 Homework4 (kcalc)
contributed by < `ambersun1234` >
## 環境
```shell
$ uname -a
Linux 4.15.0-74-generic #83~16.04.1-Ubuntu SMP Wed Dec 18 04:56:23 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
```
## 作業說明
+ [作業說明](https://hackmd.io/@sysprog/linux2020-kcalc#-%E9%A0%90%E6%9C%9F%E7%9B%AE%E6%A8%99)
## 作業要求
### 自我檢查清單
* 找出至少 2 個 Linux 核心中使用定點數的案例,搭配程式碼解說
* 提示: 參照以下文章:
* [Linux Kernel Load Average 計算分析
](http://brytonlee.github.io/blog/2014/05/07/linux-kernel-load-average-calc/)
* [Load average explained](https://wiki.nix-pro.com/view/Load_average_explained)
* `MathEx` 如何解析給定字串,從而分離出變數和對應數學表示法呢?
* 在 `MathEx` 中,`nop` 一類使用者自訂的函數如何實作呢?
* 是否發現 `MathEx` 對於負數的除法結果不正確、乘法和加法的 overflow 沒有做處理,且缺乏對減法的 underflow 處理機制呢?你預計如何克服?
### 作業要求
* 回答上述「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳
:::warning
:warning: 如果你在 2020 年 3 月 27 日前,已從 GitHub [sysprog21/kcalc](https://github.com/sysprog21/kcalc) 進行 fork,請依據 [Alternatives to forking into the same account](https://github.community/t5/Support-Protips/Alternatives-to-forking-into-the-same-account/ba-p/7428) 一文,對舊的 repository 做對應處置,然後重新 fork
:::
* 在 GitHub 上 fork [kcalc](https://github.com/sysprog21/kcalc),目標是將 [fibdrv 作業](https://hackmd.io/@sysprog/linux2020-fibdrv)的成果透過 livepatch,整合進 kcalc。過程中應一併完成以下:
* 觀察原始程式碼的 `expression.[ch]` 裡頭 `vec` 相關的程式碼,將 [quiz4](https://hackmd.io/@sysprog/linux2020-quiz4) 使用到的技巧予以應用,提出對空間效率更好的實作;
* 在 `main.c` 提供自行定義的 `fib()` 函式,一開始僅有不完整實作 (可直接回傳 `0`,一如 `nop()` 所為),但在 livepatch 端 (即 `livepatch-calc.c`) 就該有正確的 Fibonacci 數求值實作 $\to$ 不用考慮大數運算,但仍要留意有效數值範圍和運算效率;
* 修正現有 kcalc 內部數值運算的錯誤,並留意到不合法的記憶體操作;
* 指出原本 fixed-point 實作的限制,觀察 Linux 核心所用的 fixed-point 運算程式碼,提出針對 kcalc 的改進方案 $\to$ 避免直接用 `int` 型態描述,可透過 `typedef` 來定義 [binary32_t](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) 一類的衍生型態;
* 原有的測試程式不會檢驗輸出數值,應予以擴充,得以自動和預先輸入的期望數值進行比對;
## 自我檢查清單
+ 找出至少 2 個 Linux 核心中使用定點數的案例,搭配程式碼解說
+ c.f. Linux Kernel 浮點運算
+ 浮點數計算相當的耗費資源,而且對 FPU 需要手動操作,對於整體的開銷來說是非常大的
+ 且浮點數運算並非那麼的長使用
+ 因此不建議在 kernel 中使用浮點數運算
+ c.f. 定點數,浮點數
+ 舉例來說,`500`是一個定點數,在`3精度`的表示下,浮點數為 `0.5`
+ 精度丟失 :arrow_right: 浮點數`1.5005`轉換為`3精度`定點數的答案為`1500`
+ 四則運算
+ `+, -` :arrow_right: 定點數 2500 + 定點數 2500 = 定點數 5000,轉換為浮點數(3精度)後 0.25 + 0.25 = 0.5
+ `*` :arrow_right: 定點數 2500 * 定點數 2500 = 定點數 625000,轉換為浮點數 0.25 * 0.25 = 0.0625
+ ==需進行修正==, 乘法 :arrow_right: 除以 $進制^{精度}$, 除法 :arrow_right: 乘以 $進制^{精度}$
+ [linux/loadavg.h](https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L18) 裡面定義了一系列計算系統 load average 的定點數常數,
```c
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ+1) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
```
kernel 計算 load average 的方式是 $S_t = \alpha * X_{t-1} + (1 - \alpha) * S_{t-1}$,最後計算完成後,需將定點數轉換為浮點數
```c
static inline unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
unsigned long newload;
newload = load * exp + active * (FIXED_1 - exp);
if (active >= load)
newload += FIXED_1-1;
return newload / FIXED_1;
}
```
+ `MathEx` 如何解析給定字串,從而分離出變數和對應數學表示法呢?
+ MathEX 根據輸入的值,會建立 expression。在 [MathEX/expression.c](https://github.com/jserv/MathEX/blob/master/expression.c#L480) 裡面,定義了一系列的操作;
+ 比如說 expression `x = 40, add(2, x)`,MathEX 會建立三個 vector(expression, string, value),根據輸入的字串分別判斷並存入 vector 中
+ `expr_var`, `expr_fuct` 的方法都是採用判斷 `字串長度` 以及 `名稱是否相等` 為依據
+ 依照這些方法即可分離出變數,表示法以及其他
+ 在 `MathEx` 中,`nop` 一類使用者自訂的函數如何實作呢?
+ 給定的範例程式中,[MathEX/test-simple.c](https://github.com/jserv/MathEX/blob/master/test-simple.c) 中展示了基本的自訂函數操作
```c
/* Custom function that returns the sum of its two arguments */
static float add(struct expr_func *f, vec_expr_t args, void *c) {
float a = expr_eval(&vec_nth(&args, 0));
float b = expr_eval(&vec_nth(&args, 1));
return a + b;
}
```
+ 在經由簡單的確認後(長度以及名稱),在將其的 expression 加入到 vector 中 [MathEX/expression.c](https://github.com/jserv/MathEX/blob/master/expression.c#L666) 並視為一般表示式,也就是在 compile 時期(這裡指的是表示式轉換時期)將自定義函數直接整合。在後續的計算呼叫中,就是直接操作已經處理過後的表示式了
```c
} else {
struct expr_func *f = expr_func(funcs, str.s, str.n);
struct expr bound_func = expr_init();
bound_func.type = OP_FUNC;
bound_func.param.func.f = f;
bound_func.param.func.args = arg.args;
if (f->ctxsz > 0) {
void *p = calloc(1, f->ctxsz);
if (!p) {
goto cleanup; /* allocation failed */
}
bound_func.param.func.context = p;
}
vec_push(&es, bound_func);
}
```
+ 是否發現 `MathEx` 對於負數的除法結果不正確、乘法和加法的 overflow 沒有做處理,且缺乏對減法的 underflow 處理機制呢?你預計如何克服?
# 實做
+ 觀察原始程式碼的 `expression.[ch]` 裡頭 `vec` 相關的程式碼,將 [quiz4](https://hackmd.io/@sysprog/linux2020-quiz4) 使用到的技巧予以應用,提出對空間效率更好的實作;
+ `NEXT_POWER_OF_2`
+ 給定整數 n,求大於 n 且最小的 $2^m$
```c
#define NEXT_POWER_OF_2(s) \
(s&(s-1)) == 0 \
? s \
: (size_t) 1 << (64 - __builtin_clzl(s)) /* next power of 2 */
```
###### tags: `linux2020`