# 2021q1 Homework3 (fibdrv) contributed by < [shawn5141](https://github.com/Shawn5141/fibdrv) > ## 指定閱讀 [作業解說](https://hackmd.io/@sysprog/linux2022-fibdrv) [Youtube講解](https://www.youtube.com/watch?v=IfsldrCuX28) [費氏數列分析](https://hackmd.io/@sysprog/fibonacci-number) [Linux 核心設計: 檔案系統概念及實作手法](https://hackmd.io/@sysprog/linux-file-system)。 [Linux 核心設計: 中斷處理和現代架構考量](https://hackmd.io/@sysprog/linux-interrupt)。 [Linux 核心設計: Timer 及其管理機制](https://hackmd.io/@sysprog/linux-timer)。 [Linux Virtual File System](https://www.win.tue.nl/~aeb/linux/lk/lk-8.html) ## 預期目標 - [x] 撰寫適用於 Linux 核心層級的程式 - [x] 學習 ktimer, copy_to_user 一類的核心 API - [ ] 複習 C 語言 [數值系統](https://hackmd.io/@sysprog/c-numerics) 和 [bitwise](https://hackmd.io/@sysprog/c-bitwise) 操作 - [ ] 數值分析和運算改進策略 - [ ] 初探 [Linux VFS](https://www.win.tue.nl/~aeb/linux/lk/lk-8.html) - [ ] 自動測試機制 - [ ] 透過工具進行效能分析 ## 自我檢查清單 - [x] 研讀上述 `Linux 效能分析的提示` 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作 →從中也該理解為何不希望在虛擬機器中進行實驗; - [ ] 研讀上述費氏數列相關材料 (包含論文),摘錄關鍵手法,並思考 [clz/ ctz](https://en.wikipedia.org/wiki/Find_first_set) 一類的指令對 Fibonacci 數運算的幫助。請列出關鍵程式碼並解說 - [ ] 複習 C 語言 [數值系統](https://hackmd.io/@sysprog/c-numerics) 和 [bitwise operation](https://hackmd.io/@sysprog/linux2022-fibdrv#:~:text=%E8%AA%9E%E8%A8%80%20%E6%95%B8%E5%80%BC%E7%B3%BB%E7%B5%B1%20%E5%92%8C-,bitwise%20operation,-%EF%BC%8C%E6%80%9D%E8%80%83%20Fibonacci%20%E6%95%B8),思考 Fibonacci 數快速計算演算法的實作中如何減少乘法運算的成本; - [ ] 研讀 [KYG-yaya573142](https://hackmd.io/@sysprog/linux2022-fibdrv#:~:text=%E7%A0%94%E8%AE%80-,KYG%2Dyaya573142%20%E7%9A%84%E5%A0%B1%E5%91%8A,-%EF%BC%8C%E6%8C%87%E5%87%BA%E9%87%9D%E5%B0%8D%E5%A4%A7) 的報告,指出針對大數運算,有哪些加速運算和縮減記憶體操作成本的舉措? - [ ] lsmod 的輸出結果有一欄名為 `Used by`,這是 “each module’s use count and a list of referring modules”,但如何實作出來呢?模組間的相依性和實際使用次數 (reference counting) 在 Linux 核心如何追蹤呢? >搭配閱讀 [The Linux driver implementer’s API guide » Driver Basics](https://www.kernel.org/doc/html/latest/driver-api/basics.html) - [ ] 注意到 fibdrv.c 存在著 DEFINE_MUTEX, mutex_trylock, mutex_init, mutex_unlock, mutex_destroy 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題。嘗試撰寫使用 POSIX Thread 的程式碼來確認。 - [ ] 修正 Fibonacci 數計算的正確性,隨後改善 fibdrv 計算 Fibinacci 數列的執行效率,在 Linux 核心模組和使用層級去測量執行時間。 - 正確性和數值系統的使用 - 務必研讀上述 Linux 效能分析的提示 的描述,降低效能分析過程中的干擾; - 在 Linux 核心模組中,可用 ktime 系列的 API; - 在 userspace 可用 clock_gettime 相關 API; - 善用統計模型,除去極端數值,過程中應詳述你的手法 - 分別用 gnuplot 製圖,分析 Fibonacci 數列在核心計算和傳遞到 userspace 的時間開銷,單位需要用 us 或 ns (自行斟酌) - 嘗試解讀上述實驗的時間分佈,特別是隨著 Fibonacci 數列增長後,對於 Linux 核心的影響。可修改 VFS 函式,允許透過寫入 /dev/fibonacci 裝置檔案來變更不同的 Fibonacci 求值的實作程式碼。 ## 建立效能分析實驗 > 以下操作參考[k04 fibdiv](https://hackmd.io/@sysprog/linux2021-fibdrv#%E6%8E%92%E9%99%A4%E5%B9%B2%E6%93%BE%E6%95%88%E8%83%BD%E5%88%86%E6%9E%90%E7%9A%84%E5%9B%A0%E7%B4%A0), [KYG-yaya573142 2020q1 Homework2 (fibdrv)](https://hackmd.io/@KYWeng/rkGdultSU#:~:text=%E9%80%8F%E9%81%8E%E5%B7%A5%E5%85%B7%E9%80%B2%E8%A1%8C,%E5%88%86%E6%9E%90%E7%9A%84%E5%9B%A0%E7%B4%A0) <details> <summary>學習如何使用taskset</summary> #### [taskset](https://linux.die.net/man/1/taskset)來查看行程的 CPU affinity - ##### 什麼是 Affinity mask: > The CPU affinity is represented as a bitmask, with the lowest order bit corresponding to the first logical CPU and the highest order bit corresponding to the last logical CPU ```cpp= 0x00000001 is processor #0 0x00000003 is processors #0 and #1 0xFFFFFFFF is all processors (#0 through #31) ``` - ##### 使用flag `cp`: (p is pid, c is core) 查詢pid 可使用的cpu。 ```cpp= $taskset -cp 1 pid 1's current affinity list: 0-7 ``` - ##### 將行程固定在特定的 CPU 中執行: ```bash= taskset -p COREMASK PID //其中 COREMASK 就是指定的十六進位 core mask askset -cp CORELIST PID //CORELIST 為 CPU 核心列表,以逗點分隔各個核心的編號或是使用連字號指定連續的區間,例如: 0,2,5,7-10。 ``` > 在 Linux 中的使用者必須有開啟 [CAP_SYS_NICE](https://man7.org/linux/man-pages/man7/capabilities.7.html) 這個權限,才能更動行程的處理器親和性 - ##### 以特定的 CPU 執行程式: 使用 taskset 指定 CPU 核心來執行一個新的程式: 例如:以第 0 個 CPU 核心執行 vlc 則使用 ```cpp taskset 0x1 vlc // `taskset COREMASK EXECUTABLE` ``` - ##### 限定 CPU 給特定的程式使用 光是使用上述的方法,指定的CPU依然可能被其人Process使用。可以使用 `isolcpus` 這個 Linux 核心起始參數,這個參數可以讓特定的 CPU 核心在開機時就被保留下來。 > 設定的方式有兩種,一個是在開機時使用 boot loader 所提供的自訂開機參數功能,手動加入 isolcpus=cpu_id 參數,或是直接加在 [GRUB 的設定檔](http://doc.dpdk.org/spp-18.02/setup/performance_opt.html#reduce-context-switches)中,這樣 Linux 的排程器在預設的狀況下就不會將任何一般行程放在這個被保留的 CPU 核心中執行,只有那些被 taskset 特別指定的行程可使用這些 CPU 核心。 </details> ### 排除干擾效能分析的因素 1. Isolate cpu and assign task to this cpu 修改 `/etc/default/grub` 內的 `GRUB_CMDLINE_LINUX_DEFAULT`,加入 `isolcpus=7` 來指定編號 7 的核心不被排程器賦予任務,修改完成後需 `sudo update-grub` 來更新設定,重開機後即生效 (可從 `/sys/devices/system/cpu/isolated` 確認是否生效) ```shell // sudo vim /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=7" ``` 可透過以下命令確認是否設定成功 ```shell $ cat /sys/devices/system/cpu/isolated 7 ``` 接著以 [taskset](http://man7.org/linux/man-pages/man1/taskset.1.html) 指定 CPU 核心來執行程式: ```shell $ taskset -c 7 EXECUTABLE ``` 1. 暫時抑制 [address space layout randomization](https://docs.oracle.com/cd/E37670_01/E36387/html/ol_aslr_sec.html) (ASLR) ```shell $ sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" ``` 2. [設定 scaling_governor 為 performance](https://community.mellanox.com/s/article/how-to-set-cpu-scaling-governor-to-max-performance--scaling-governor-x)。準備以下 shell script,檔名為 `performance.sh`: ```shell for i in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor do echo performance > ${i} done ``` 之後再用 `$ sudo sh performance.sh` 執行 3. 針對 Intel 處理器,關閉 turbo mode ```shell $ sudo sh -c "echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo" ``` > Good refs: https://askubuntu.com/questions/37618/is-turbo-boost-working ## 量測時間的方式 ### user space 使用 clock_gettime 下的 CLOCK_MONOTONIC 來計時,CLOCK_MONOTONIC 是從特定起始時間單調遞增的計時方式。 ### kernel space 使用[htimer](https://lwn.net/Articles/167897/),放置在device read前後。 參考[隔壁同學bakudr18](https://hackmd.io/@MR-9Qa9wQLWglSAUyd6UhQ/HJ363p_Qd#%E6%B8%AC%E9%87%8F-user-space-%E8%88%87-kernel-space-%E6%99%82%E9%96%93)建立device_attribute的做法: >為了方便開啟 kernel space 計時功能,這裡在 [sysfs](https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt) 底下註冊了一個 `ktime_measure` 變數來開關計時功能。根據 [sysfs](https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt) 文件 :::success sysfs is a ram-based filesystem initially based on ramfs. It provides a means to export kernel data structures, their attributes, and the linkages between them to userspace. ::: show 是一個callback,會在讀文件時會觸發,並讀出回傳值。store則是把值寫入。透過寫入 "0" 或 "1" 來調整 ```cpp= static ssize_t ktime_measure_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "%d\n", ktime_enable); } static ssize_t ktime_measure_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { ktime_enable = (bool) (buf[0] - '0'); fib_exec = ktime_enable ? &fib_time_proxy_p : &fib_p; return count; } ``` 同時我們也建立另一個sysfs,來選擇不同的fib_method。 ```cpp= // fib_method related utils function static ssize_t fib_method_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "%d\n", ktime_enable); } static ssize_t fib_method_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int fib_method_index = (unsigned int) (buf[0] - '0'); fib_method_set(fib_method_index); return count; } ``` 在client.c 當中設定ktimer 和 fib_method。 ```cpp= #define KTIME_ENABLE "/sys/class/fibonacci/fibonacci/ktime_measure" #define FIB_METHOD "/sys/class/fibonacci/fibonacci/fib_method" int main(){ int fd_kt = open(KTIME_ENABLE, O_RDWR); if (fd_kt < 0) { perror("Failed to open sysfs"); err = 1; fd = fd_kt; goto close_fib; } int fd_fib = open(FIB_METHOD, O_RDWR); if (fd_fib < 0) { perror("Failed to open sysfs"); err = 1; fd = fd_fib; goto close_fib; } // Set ktimer write(fd_kt, "1", 2); close(fd_kt); // Set fib method write(fd_fib, "0", 2); close(fd_fib); } for (int i = 0; i <= offset; i++) { lseek(fd, i, SEEK_SET); long long start = get_nanotime(); sz = read(fd, buf, 1); long long utime = get_nanotime() - start; ssize_t kt = write(fd, NULL, 0); printf("Reading from " FIB_DEV " at offset %d, returned the sequence " "%lld. utime %lld, ktime %ld\n", i, sz, utime, kt); } ``` 可以得到下圖 ![](https://i.imgur.com/un9D2rV.png) ### copy_{from/to}_user ## Fibonacci 實作 使用不同的策略,並計算時間 #### [Vorobev 等式](https://hackmd.io/@sysprog/fibonacci-number#Vorobev-%E7%AD%89%E5%BC%8F) \begin{split} F(2k) &= F(k)[2F(k+1) - F(k)] \\ F(2k+1) &= F(k+1)^2+F(k)^2 \end{split} 參考 [pusedo code](https://hackmd.io/@sysprog/linux2021-fibdrv#:~:text=Fast_Fib(n)%0A%20%20%20%20a%20%3D%200%3B%20b%20%3D%201%3B%20%20%20%20%20%20%20//%20m%20%3D%200%0A%20%20%20%20for%20i%20%3D%20(number%20of%20binary%20digit%20in%20n)%20to%201%0A%20%20%20%20%20%20%20%20t1%20%3D%20a*(2*b%20%2D%20a)%3B%0A%20%20%20%20%20%20%20%20t2%20%3D%20b%5E2%20%2B%20a%5E2%3B%0A%20%20%20%20%20%20%20%20a%20%3D%20t1%3B%20b%20%3D%20t2%3B%20//%20m%20*%3D%202%0A%20%20%20%20%20%20%20%20if%20(current%20binary%20digit%20%3D%3D%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20t1%20%3D%20a%20%2B%20b%3B%20//%20m%2B%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20a%20%3D%20b%3B%20b%20%3D%20t1%3B%0A%20%20%20%20return%20a%3B) 可以產生以下的程式碼。 ```cpp= tatic long long fib_sequence_fdouble(unsigned int k) { long long h = 0; for (long long i = k; i; ++h, i >>= 1) ; long long a = 0; // F(0) = 0 long long b = 1; // F(1) = 1 for (long long mask = 1 << (h - 1); mask; mask >>= 1) { // Run h times! // Let j = h-i (looping from i = 1 to i = h), n_j = floor(n / 2^j) = n // >> j (n_j = n when j = 0), k = floor(n_j / 2), then a = F(k), b = // F(k+1) now. long long c = a * (2 * b - a); // F(2k) = F(k) * [ 2 * F(k+1) – F(k) ] long long d = a * a + b * b; // F(2k+1) = F(k)^2 + F(k+1)^2 if (mask & k) { // n_j is odd: k = (n_j-1)/2 => n_j = 2k + 1 a = d; // F(n_j) = F(2k + 1) b = c + d; // F(n_j + 1) = F(2k + 2) = F(2k) + F(2k + 1) } else { // n_j is even: k = n_j/2 => n_j = 2k a = c; // F(n_j) = F(2k) b = d; // F(n_j + 1) = F(2k + 1) } } return a; } ``` 再來可以使用硬體加速,直接去計算leading zero的數量。這邊讓`h=sizeof(long long) * 8 - 1 - clz(k)`,多減一是因為是要找第一個set bit。 ```cpp= static long long fib_sequence_fdouble_clz(unsigned int k) { long long h = 0; long long a = 0; // F(0) = 0 long long b = 1; // F(1) = 1 for (long long mask = 1 << (sizeof(long long) * 8 - 1 - clz(k) - 1); mask; mask >>= 1) { // Run h times! // Let j = h-i (looping from i = 1 to i = h), n_j = floor(n / 2^j) = n // >> j (n_j = n when j = 0), k = floor(n_j / 2), then a = F(k), b = // F(k+1) now. long long c = a * (2 * b - a); // F(2k) = F(k) * [ 2 * F(k+1) – F(k) ] long long d = a * a + b * b; // F(2k+1) = F(k)^2 + F(k+1)^2 if (mask & k) { // n_j is odd: k = (n_j-1)/2 => n_j = 2k + 1 a = d; // F(n_j) = F(2k + 1) b = c + d; // F(n_j + 1) = F(2k + 2) = F(2k) + F(2k + 1) } else { // n_j is even: k = n_j/2 => n_j = 2k a = c; // F(n_j) = F(2k) b = d; // F(n_j + 1) = F(2k + 1) } } return a; } ``` user space comparsion ![](https://i.imgur.com/J8jkiYt.png) ![](https://i.imgur.com/lfNdzP1.png) ![](https://i.imgur.com/3GSTdme.png) 可以發現使用clz明顯會讓kernel space的運算速度提升。 ### Page faults 不論是哪個fib method,都可已發現前幾次的runtime特別高,這應該是Page faults造成的。參考同學<>的[做法](https://hackmd.io/@MR-9Qa9wQLWglSAUyd6UhQ/HJ363p_Qd#Fibonacci-%E5%AF%A6%E4%BD%9C),可以使用[mlock](https://linux.die.net/man/2/mlockall) > lock part or all of the calling process's virtual address space into RAM, preventing that memory from being paged to the swap area ## 大數運算 ```cpp= typedef struct _bn { unsigned long long lower, upper; } bn; ``` 嘗試使用上面的結構,讓big num可以存到128 digit,需要思考怎麼變回character。 ```cpp= static inline void bn_add(bn *output, bn x, bn y) { output->upper = x.upper + y.upper; if (y.lower > ~x.lower) output->upper++; output->lower = x.lower + y.lower; } ``` ## Mutex