# F04: kcalc contributed by < `afcidk` > * [F04: kcalc](https://hackmd.io/s/SyC9V0gUE) ## 自我檢查清單 ### 1. 解釋浮點運算在 Linux 核心中為何需要特別對待,以及 context switch 的過程中,涉及到 FPU/SIMD context,該注意什麼? #### Lazy FP state restore [CVE-2018-3665](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-3665),是存在於 Intel Core CPU 中,因為 speculative execution(推測執行)技術中的一些缺陷加上特定作業系統中對 FPU(Floating point unint)進行 context switch 所產生的漏洞,允許一個本地端的程式洩漏其他程序的 FPU 暫存器內容。 #### Mechanism Lazy FP state leak 的原理是透過 FPU 的 lazy state switching 機制達成。因為 FPU 和 SIMD 暫存器不一定會在每個 task 被使用到,因此我們可以不需要使用到 FPU 的任務時直接將 FPU 設為不可使用,而不更改裡面的內容。 然而,在現今的[亂序執行](https://en.wikipedia.org/wiki/Out-of-order_execution) CPU 中,lazy state switching 裡會設定的 "FPU not available" 可能沒辦法馬上被偵測到,導致我們在 B process,但是仍然可以存取到 A process 的 FPU 暫存器。 因為上述原因,雖然我們在 kernel mode 裡頭仍然可以使用浮點運算,但是這不僅會拖慢執行速度,還需要手動儲存/取回相關的暫存器,因此才會不建議在 kernel 裏面使用到浮點運算。 #### 浮點運算時間差異比較 我寫了一個簡單的 kernel module,測試在單純的浮點數運算以及整數運算花費的時間差異。 相關的程式碼可以參考[這裡](https://gist.github.com/afcidk/7178ef368d98ee4b49c7a1acc3704303) ![](https://i.imgur.com/Rpe3DnR.png) ### 2. 在給定的 calc.c 檔案中,和 fibdrv 一樣有 character device,但註冊用的 kernel API 不同 (register_chrdev vs. alloc_chrdev_region),能否解釋其差異和適用場合呢? 在 [Ch03 Char Drivers](http://static.lwn.net/images/pdf/LDD3/ch03.pdf) 可以看到下面這段話 > If you dig through much driver code in the 2.6 kernel, you may notice that quite a few char drivers do not use the cdev interface that we have just described. What you are seeing is older code that has not yet been upgraded to the 2.6 interface. Since that code works as it is, this upgrade may not happen for a long time. For completeness, we describe the older char device registration interface, but new code should not use it; this mechanism will likely go away in a future kernel. 大意是說在 linux kernel 2.6 版以前,只有 `register_chrdev` 可以使用,但是官方建議我們用新的 API (`alloc_chrdev_region`),因為在日後的版本這個機制可能會消失。 從 function prototype 我們可以看出,這兩個函式的差異會在有沒有把 file_operations 的空間跟著配置進去,還有 `dev_t` 的使用。 ```clike int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); ``` #### `dev_t` 型態 從 [linux/types.h](https://elixir.bootlin.com/linux/v4.15/source/include/linux/types.h#L16) 中可以看到,其實 `dev_t` 是由 unsigned int (32 bits) 定義來的。雖然他看起來和一般的無號整數相同,但是他內部的資料格式實際上儲存了 major/minor number。 在使用上,我們可以使用 `MAJOR(dev_t dev)` 和 `MINOR(dev_t dev)` 來取得某個 `dev_t` 型態的 major/minor number,以及 `MKDEV(int major, int minor)` 把資料放到 `dev_t` 變數裡頭。 #### Major/Minor number Major number 代表的是比較大範圍的 device type,而 minor number 則是代表 device type 底下,特定的 device。因此,我們可以知道 `major:minor` 的組合必須要是獨一無二,同一個 device 被兩個 driver 控制顯然不合邏輯。 #### 適用場合 > For completeness, we describe the older char device registration interface, but new code should not use it; this mechanism will likely go away in a future kernel. 不太確定這兩者的使用場合差異,不過從這裡看來,也許是為了相容性才會使用舊的寫法。 ### 3. 在 scripts/test.sh 檔案中,有一道命令為 sudo chmod 0666,這個作用為何?對於我們測試有何幫助?能否對 fibdrv 建立的 /dev/fibonacci device file 也套用類似修改呢?另外,請解釋 device file 在核心及使用者層級的功能 我們先觀察一下 `/dev/calc` 的各種權限 ```shell $ ls -l /dev/calc crw------- 1 root root 240, 0 三 14 14:28 /dev/calc ``` 看得出來,目前的權限只允許 owner 執行 read/write 動作。之所以要把權限改成 `666` 是因為我們希望任何使用者都可以對 device 讀寫而不需要 `sudo` 權限。 device file(VFS 層)允許 user space 和 kernel space 相互溝通,在實作上我們需要定義出 syscall 對應的 driver method。 ![](https://i.imgur.com/RGnZqz8.png) 圖片取自 [Embedded Sense](https://msreekan.com/2015/04/24/linux-storage-cache/) ### 4. 在 calc.c 檔案中,用到 copy_to_user 這個 kernel API,其作用為何?本例使用該 API 做了什麼事?若是資料量增長,是否會有效能的嚴重衝擊呢? [copy_to_user](https://elixir.bootlin.com/linux/v4.15/source/include/linux/uaccess.h#L151) 是在 kernel 中讓我們可以取得 userspace 傳送過來資料的一個 API。在 `calc.c` 中,我們將輸入的 `buffer` 複製給 `message`,回傳值是尚未複製的字元數量(意思是只要回傳值不為 0 就代表 `copy_to_user` 失敗)。 ### 8. 在 MathEx 原始程式碼的 expression.\[ch\] 裡頭 vec 相關的程式碼,主要做什麼事?有沒有發現類似 list 使用到的技巧呢? vec 相關的程式碼主要是提供我們一個比較簡單的介面來操控 vec 結構。vec 的結構由一個指向 T 的指標 buf、整數型態的 len 以及整數型態的 cap 組成。 與 list 相同,vec 將常使用到的操作以巨集的方式包裝起來讓後續可以寫出較簡潔的程式碼時又不會多了使用 function call 的時間成本。 ### 10. 如果要將使用者層級的 C 語言程式,搬到 Linux 核心作為核心模組 (LKM),該注意哪些議題呢?請舉例說明 當我們在編譯 LKM 時,`__KERNEL__` 會被定義出來,這可以把 kernel space 和 user space 需要的功能區分開來。 在把 mathex 功能搬到 kernel space 時,遇到的另一個議題會是 malloc。 malloc 讓我們在 user space 分配空間。而 kmalloc 與 vmalloc 則會在 kernel space 使用到。 kmalloc 和 vmalloc 的主要差別在於分配的空間大小,以一個 page 為基準。 kmalloc 使用 `get_free_pages` 來低於一個 page 的空間,得到的會是實體記憶體位址,速度比 vmalloc 快一些。 vmalloc 在我們要求大於一個 page 的空間時使用,他會分配一些 page,再把那些空間 map 到虛擬記憶體中給我們使用。 ## 整合 Mathex 到 calc.c (LKM 形式) ### Fixed point 實作浮點運算 我將所有會使用到浮點數的地方都改為 32 bit integer 使用 fixed-point 來計算。 實作裏面,我將 lsb 4 bit 作為 exponent,msb 28 bit 作為 mantissa,將大部份 Mathex 的功能 port 到 kernel space 運算。 沒有改到的地方是 user function 的功能,因為這個部份需要我們傳入 pointer to function。但是我們沒辦法將寫好的 function **傳給 kernel module** 使用。所以這個功能就沒有多加實作。