# 2019q1 Homework2 (kcalc) contributed by < `rebvivi` > ###### tags: `linux2019` * [F04: kcalc](https://hackmd.io/s/SyC9V0gUE#F04-kcalc) Reviewed by <`jserv`> 1. 提及 `if (0.1 + 0.2 == 0.3)` 判斷式不成立,但沒有說到浮點數值運算的解決方法,請提出; (考慮到計算誤差) 2. 尚未用 Linux 程式碼解釋 `Lazy context switch` 原理和運作機制; 3. 探討 `register_chrdev` vs. `alloc_chrdev_region` 時,僅列出 API 差異,但沒有詳細探討應用場景和考量點的落差 4. 下方提到「如果使用`copy_to_user`時,要複製的資料量太大,就會讓 buffer 產生 overflow,導致效能降低」,那解決方案為何?請舉出 Linux 核心的案例 (最好列出對應的 Git commits) 5. 下方提到的 `fixed point arithmetic`,實際可完整表達的數值範圍為何?既然精準度下降了,那麼我們更需要知道衝擊,可參考 CS:APP 第 2 章的分析方法 --- ## 作業要求 - 回答「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳 - 在 GitHub 上 fork kcalc,主要目標是將 MathEx 整合到 calc.c 中 (作為 LKM 的形式),過程中需要一併完成以下: * 將 MathEx 的浮點數運算換為 fixed point,應該先在使用者層級驗證,然後再搬到 Linux 核心中。可斟酌移除 MathEx 裡頭的功能,但需要充分解釋; * 量化 MathEx 在核心的執行時間,搭配 fibdrv 撰寫的工具程式使用; * 設計 micro-benchmarking 實驗,用以驗證 MathEx 移植到 Linux 核心後的表現; * 嘗試解讀上述時間分佈,特別是隨著 Fibonacci 數列增長後,對於 Linux 核心的影響; * 改善 MathEx 的執行效率; * 請善用 perf 一類地效能分析工具; ## 自我檢查清單 1. 解釋浮點運算在 Linux 核心中為何需要特別對待,以及 context switch 的過程中,涉及到 FPU/SIMD context,該注意什麼? * 提示: 參照 Lazy FP state restore 和上方參考資料 * 應該撰寫對應包含浮點運算的 Linux 核心模組,實際編譯和測試 2. 在給定的 `calc.c` 檔案中,和 fibdrv 一樣有 character device,但註冊用的 kernel API 不同 (`register_chrdev` vs. `alloc_chrdev_region`),能否解釋其差異和適用場合呢? 3. 在 `scripts/test.sh` 檔案中,有一道命令為 `sudo chmod 0666`,這個作用為何?對於我們測試有何幫助?能否對 fibdrv 建立的 `/dev/fibonacci` device file 也套用類似修改呢?另外,請解釋 device file 在核心及使用者層級的功能 4. 在 `calc.c` 檔案中,用到 `copy_to_user` 這個 kernel API,其作用為何?本例使用該 API 做了什麼事?若是資料量增長,是否會有效能的嚴重衝擊呢? 5. 找出至少 3 個 Linux 核心中使用定點數的案例,搭配程式碼解說 * 提示: 參照 Linux Kernel Load Average 計算分析 , What Every Computer Scientist Should Know About Floating-Point Arithmetic, Load average explained 6. 是否知曉 `MathEx` 如何解析給定字串,從而分離出變數和對應數學表示法呢? 7. 如何對 `MathEx` 進行效能分析?能否找出相似的 math expression 來分析比較呢? * 提示: 參照 muparserSSE - A Math Expression Compiler 8. 在 `MathEx` 原始程式碼的 `expression.[ch] `裡頭 `vec` 相關的程式碼,主要做什麼事?有沒有發現類似 list 使用到的技巧呢? * 提示: 參照 `mathex/test-unit.c` 的測試項目 9. 解釋 `MathEx` 一類的 math expression 在真實世界有哪些應用?甚至,是否在 Linux 核心就存在類似的程式碼? * 提示: 參照 A thorough introduction to eBPF 10. 如果要將使用者層級的 C 語言程式,搬到 Linux 核心作為核心模組 (LKM),該注意哪些議題呢?請舉例說明 * 提示: 注意 `__KERNEL__` 巨集的定義, kmalloc 的使用, vmalloc 的使用 (以及後兩者的差異) 11. fixed point arithmetic for RT-Linux 的運作原理為何?給定的程式碼已經存在超過 20 年,很多細節已有出入,可否嘗試移植到 Linux `v4.15+` 呢? ### 1. 解釋浮點運算在 Linux 核心中為何需要特別對待,以及 context switch 的過程中,涉及到 FPU/SIMD context,該注意什麼?提示: 參照 Lazy FP state restore 和上方參考資料。應該撰寫對應包含浮點運算的 Linux 核心模組,實際編譯和測試 - 進行浮點數運算的時候,往往會遇到許多問題,例如: * 輸入與儲存的值不一定精確,可能原因: * 輸入值本身就不精確 * 儲存浮點數用的記憶體有限 * 不同底數之間的轉換不精確 * 輸入與儲存產生 overflow * 計算的結果會有誤差: 使用浮點數進行運算時會有上述產生的許多誤差,這些誤差經過反覆而且大量運算之後很有可能會蔓延到其它所在,從而產生不可收拾的後果 - 浮點數容易犯的錯誤:直接用 == 或 != 比較兩個浮點數。 >以下的判斷式是不成立的 ```cpp if (0.1 + 0.2 == 0.3) ``` >二進位表示的浮點數並沒有辦法精確儲存 0.1、0.2、0.3 這些十進位實數,只能以最接近的浮點數表示,所以會和原本的數值有微小的誤差 >三個各自帶有誤差的數字要碰巧讓整個等式成立,是非常困難的 - `Floating Point Unit(FPU)`:專用於浮點運算的處理器 - `Single Instruction Multiple Data (SIMD)` : 同時對多個數據執行同一條CPU指令,達到平行運算的目的 - `Lazy context switch`:硬體會先 disable FPU,一直等到 FP 指令被執行的時候才 enable CPU >每次 context switch 的時候如果 CPU 和 FPU 都要做 context switch 會使系統的 overloading 增大,因為存取 FPU 的 hardware context 需要更多的 CPU time - 在 context switch 的過程中,如果涉及到 FPU context ,會執行`Lazy context switch`,也就是僅在需要時保存並還原 FPU ,但這可能會面臨一些安全性的問題: * 可以利用 Lazy FP 狀態還原功能,潛在允許一個 process 從另一個 process 推斷數據 * 推斷到的數據也可能包括已加密操作的信息,這個安全漏洞會影響到CPU的預測執行推測執行機制 ### 2. 在給定的 `calc.c` 檔案中,和 fibdrv 一樣有 character device,但註冊用的 kernel API 不同 (`register_chrdev` vs. `alloc_chrdev_region`),能否解釋其差異和適用場合呢? - 為了將 device file 與 driver 連起來,driver 必須向 kernel 註冊 major number - `register_chrdev` : 在 kernel 中註冊字元型的 driver - 將 driver 自己註冊到 kernel 的 VFS 層,註冊時所要呼叫的函數根據裝置類型的不同而不同 - 將 driver 註冊至 kernel 的動作必須在 init_module() 裡實作 - 註冊的動作是寫在 init_module() 裡,因此當使用者執行 insmod 載入 driver時, register_chrdev() 便會執行 >`major` : 欲請求的 major number >>如果 major 是0,表示動態的分配給此 driver 一個 major number,如果 major 不是0,表示 driver 向系統申請 major number >> >`name` : driver 的名稱 `fops` : 指向 kernel 定義的 file_operations 的 struct 中 ```clike int register_chrdev (unsigned int major, const char *name, struct file_operations *fops); ``` - `alloc_chrdev_region` : 動態取得 major number ,在 kernel 中註冊字元型的 driver - 依 driver 名稱用來取得 major number,並從指定的起點開始預留指定數目的 minor number >`dev`: 僅供輸出的 parameter,當配置成功,這個 parameter 持有配置的 device number `firstminor`: 請求的 first minor number ,通常為 0 `count`: minor number 個數 `name`: driver 的名稱 ```clike int alloc_chrdev_region (dev_t *dev, unsigned int firstminor, unsigned int count, char *name); ``` - `register_chrdev` 除了請求 major number 以外,還一併將 0~255 的 minor number 註冊好,並自動分配了新的 cdev structure , `alloc_chrdev_region` 的 cdev stucture 成員則可自行增加 - 如果我們提前知道 device driver 的 major number,那麼就用`register_chrdev`,但是如果不知道就使用動態申請 device number ### 3. 在 `scripts/test.sh` 檔案中,有一道命令為 `sudo chmod 0666`,這個作用為何?對於我們測試有何幫助?能否對 fibdrv 建立的 `/dev/fibonacci` device file 也套用類似修改呢?另外,請解釋 device file 在核心及使用者層級的功能 - 在 `scripts/test.sh`中有一道命令: >`chmod`:控制用戶對檔案的權限的命令 >以下命令代表每個人對 $CALC_DEV 都有讀和寫的權限 ```cpp sudo chmod 0666 $CALC_DEV ``` - `device file`: - `device driver` 與 user 的重要溝通橋梁,我們透過 `device file` 與實體硬體溝通 - 都有兩個數字分別是 `major number` 與 `minor number`,用來區別不同的 `device driver` 與個別的硬體 - `major number` 相同,代表他們使用一樣的 `device driver` - 不同的 `minor number`,則連結到不同的硬體 - User 以 open() 打開 device file 後: - 可以透過 write() 將資料傳給 driver - 可以透過 read() 從 driver 取得資料 - 處理完畢後,再以 close() 關閉 device file。 ### 4. 在 `calc.c` 檔案中,用到 `copy_to_user` 這個 kernel API,其作用為何?本例使用該 API 做了什麼事?若是資料量增長,是否會有效能的嚴重衝擊呢? - `copy_to_user` : kernel space 的內容複製到 user space >`to`:destination address in user space `from`:source address in kernel space `n`: number of bytes to copy 返回不能被複制的 bytes,如果完全複製成功則返回0 ```clike unsigned long copy_to_user (void *to, const void *from, unsigned long n); ``` >將 kernel space 裡的 message ,長度為 size_of_message 的 char type array 複製到 user space 的 buffer 所指的 address ```clike error_count = copy_to_user(buffer, message, size_of_message); ``` - 如果使用`copy_to_user`時,要複製的資料量太大,就會讓 buffer 產生 overflow,導致效能降低 - 如果 user 讀取到錯誤的資料,可能導致程式崩潰 - 如果後面的記憶體存放著應用程式的資料,則會導致其他功能讀取到錯誤的資料,因此工作不正常,也可能導致程式崩潰。 ### 5. 找出至少 3 個 Linux 核心中使用定點數的案例,搭配程式碼解說。提示: 參照 Linux Kernel Load Average 計算分析。What Every Computer Scientist Should Know About Floating-Point Arithmetic, Load average explained ### 6. 是否知曉 `MathEx` 如何解析給定字串,從而分離出變數和對應數學表示法呢? ### 7. 如何對 `MathEx` 進行效能分析?能否找出相似的 math expression 來分析比較呢?提示: 參照 muparserSSE - A Math Expression Compiler ### 8. 在 `MathEx` 原始程式碼的 `expression.[ch] `裡頭 `vec` 相關的程式碼,主要做什麼事?有沒有發現類似 list 使用到的技巧呢?提示: 參照 `mathex/test-unit.c` 的測試項目 ### 9. 解釋 `MathEx` 一類的 math expression 在真實世界有哪些應用?甚至,是否在 Linux 核心就存在類似的程式碼?提示: 參照 A thorough introduction to eBPF ### 10. 如果要將使用者層級的 C 語言程式,搬到 Linux 核心作為核心模組 (LKM),該注意哪些議題呢?請舉例說明。提示: 注意 `__KERNEL__` 巨集的定義, kmalloc 的使用, vmalloc 的使用 (以及後兩者的差異) - `__KERNEL__`:user space process 需要訪問 kernel header file ,但 kernel header file 中的某些資訊僅適用於 kernel,所以我們在 #ifdef __KERNEL __ /#endif 中封裝一些語句可確保 user space process 不會看到這些語句 ```cpp /* only for userspace compatibility */ #ifndef __KERNEL__ /* IP6 Hooks */ /* After promisc drops, checksum checks. */ #define NF_IP6_PRE_ROUTING 0 /* If the packet is destined for this box. */ #define NF_IP6_LOCAL_IN 1 /* If the packet is destined for another interface. */ #define NF_IP6_FORWARD 2 /* Packets coming from a local process. */ #define NF_IP6_LOCAL_OUT 3 /* Packets about to hit the wire. */ #define NF_IP6_POST_ROUTING 4 #define NF_IP6_NUMHOOKS 5 #endif /* ! __KERNEL__ */ ``` - `kmalloc`:在 kernel 中 allocate memory - `kmalloc`是 kernel 中最常用的一種 allocate memory 的方式,它通過呼叫`kmem_cache_alloc`函數來實現 - `kmalloc` 一次可配置的容量上限是128KBytes - `kmalloc` 能夠配置的記憶體容量是有上限的,因為配置出來的空間是連續的==實體==記憶體 >`size_t size`:how many bytes of memory are required >`gfp_t flags`:the type of memory to allocate ```clike void * kmalloc (size_t size,gfp_t flags); ``` - `vmalloc`:在 virtual space allocate 一段連續的 memory - `vmalloc` 所得到的每一頁的記憶體都是個別呼叫`alloc_page`所得來的 - `vmalloc`對一次能分配的記憶體大小沒有明確限制 - 配置出來的是連續==虛擬==位址的實體記憶體, 不一定是連續的, 而只是被核心是為一段連續的位址範圍 -`vmalloc`速度比`kmalloc`慢 >`unsigned long size`:allocation size Allocate enough pages to cover size from the page level allocator and map them into contiguous kernel virtual space. ```clike void * vmalloc (unsigned long size); ``` - `malloc`是 allocate user 的 memory ,所以當移到 kernel 之後應該改用 `kmalloc` 或是 `vmalloc` ### 11. fixed point arithmetic for RT-Linux 的運作原理為何?給定的程式碼已經存在超過 20 年,很多細節已有出入,可否嘗試移植到 Linux `v4.15+` 呢? - `fixed point number`:一種實數資料類型,小數點永遠固定在定點,利用 integer 運算模擬 floating-point 運算 * 1-bit 的 sign bit * 17-bits 的 integral part * integral part 的 range : -131072~131071 * 14-bits 的 fractional part * `fixed point number`的 accuracy : 2^-14^,four digits after the decimal point - `rt_fix`:`fixed point number`的基本類型,佔 32-bits word 的空間 * 初始化`rt_fix`之後,integral part 和 fractional part 固定了 * 運作方式為左對齊,為了保持 fractional part 的 precision - 使用 `fixed point arithmetic` 目的為避免在 kernel space 使用 FPU,因為 floating-point 運算需要花費較多的時間,雖然會降低 precision,卻可以提升運算速度