--- tags: linux2023 --- # 2023q1 Homework3 (fibdrv) contributed by < `brianlin314` > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 12 On-line CPU(s) list: 0-11 Vendor ID: GenuineIntel Model name: 12th Gen Intel(R) Core(TM) i5-12400 CPU family: 6 Model: 151 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 Stepping: 2 CPU max MHz: 5600.0000 CPU min MHz: 800.0000 BogoMIPS: 4992.00 ``` ## 前期準備 詳閱 [Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module) 了解 Linux 核心模組掛載機制 Ubuntu 預設開啟 EFI_SECURE_BOOT_SIG_ENFORCE,使得核心模需要簽章才可掛載進入 Linux 核心,所以需將 UEFI Secure Boot 的功能關閉。 需輸入 8-16 個字元的密碼,並且重新 reboot 後,需按照系統提示的數字,輸入你密碼對應的字元。 ```shell $ sudo apt install mokutil $ sudo mokutil --disable-validation ``` Linux 核心版本需大於等於 `5.4.0` ```shell $ uname -r 5.19.0-35-generic ``` 安裝 `linux-headers` 套件,並確認是否安裝 ```shell $ sudo apt install linux-headers-`uname -r` $ dpkg -L linux-headers-5.19.0-35-generic | grep "/lib/modules" ``` 得到輸出 ``` /lib/modules /lib/modules/5.19.0-35-generic /lib/modules/5.19.0-35-generic/build ``` 檢驗目前的使用者身份,第一個命令的預期輸出為 `非root的使用者名稱`,第二個命令的預期輸出則為 `root` ```shell $ whoami $ sudo whoami ``` 安裝必要套件 ```shell $ sudo apt install util-linux strace gnuplot-nox ``` 在 Linux 核心掛載 `fibdrv.ko` 核心模組 ```shell $ sudo insmod fibdrv.ko ``` --- ## Linux 效能分析 參考 [KYG-yaya573142](https://hackmd.io/@KYWeng/rkGdultSU#%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) 所紀錄的排除干擾效能分析的因素 在多核心作業系統中,使用處理器的親和性(processor affinity) 讓行程在特定的CPU核中持續執行的好處。將行程綁定在特定的CPU核上可以減少cache miss的狀況,也可以增進執行效率。 #### 查看行程的 CPU affinity 在 Linux 系統中可以使用 taskset 命令來設定或取得行程的處理器親和性。 ```shell $ taskset -p 1 pid 1's current affinity mask: fff ``` 可以看見輸出為 `fff`,轉成二進位格式之後為 `111111111111`,表示行程可以在第 0 到第 11 個 CPU 核中執行,若位元值為 `1` 則代表該行程可在這個位元對應的 CPU 核中執行,若位元值為 `0` 則代表該行程不允許在這個位元對應的 CPU 核中執行。 也可以使用以下命令,直接輸出 CPU 核的列表: ```shell $ taskset -cp 1 pid 1's current affinity list: 0-11 ``` #### 限定 CPU 給特定的程行程使用 修改 `/etc/default/grub` 內的 `GRUB_CMDLINE_LINUX_DEFAULT`,加入 isolcpus=7 來指定編號 7 的核心不被排程器賦予任務。 修改完成後需 sudo update-grub 來更新設定,重開機後即生效,輸入以下命令可查看是否設定成功: ```shell $ taskset -cp 1 pid 1's current affinity list: 0-6,8-11 ``` ```shell $ cat /sys/devices/system/cpu/isolated 7 ``` #### 固定在特定的 CPU 中執行 ``` $ sudo taskset -c 7 ./client ``` #### 排除干擾效能分析的因素 <s> 簡單請 ChatGPT 解釋 ASLR ,見以下: 地址空間隨機化(Address Space Layout Randomization,簡稱ASLR)是一種記憶體保護機制,用於抵禦緩衝區溢出攻擊。 ASLR 會在每次運行程式時,隨機地配置程式使用的記憶體地址,使得攻擊者無法預測所要攻擊的程式的記憶體地址,從而增加了攻擊者利用程式漏洞的難度[[1]](https://zhuanlan.zhihu.com/p/58419878)。 ASLR 可以在Linux、Windows 和 macOS 等操作系統中使用[[1]](https://zhuanlan.zhihu.com/p/58419878)。在 ASLR 機制中,程式在運行時的地址空間會被隨機化,而程式的代碼段和數據段的隨機化則由PIE(Position Independent Executable)機制負責,但是只有在開啟ASLR之後,PIE才會生效[[3]](https://zhuanlan.zhihu.com/p/77370302)。 </s> :::warning 參照 LWN 的 [Kernel address space layout randomization](https://lwn.net/Articles/569635/),儘量閱讀第一手材料 :notes: jserv ::: #### 抑制 [address space layout randomization](https://en.wikipedia.org/wiki/Address_space_layout_randomization) (ASLR) ```shell $ sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" ``` #### 設定 scaling_governor 為 performance 準備以下 shell script 來設定 CPU 以最高頻率執行所有 process ```shell $ vim performance.sh for i in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor do echo performance > /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor done $ sudo sh performance.sh ``` #### 關閉 turbo mode 關閉 Intel 處理器的 turbo mode ```shell $ sudo sh -c "echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo" ``` --- ## 費式數列 參考 [你所不知道的 C 語言:遞迴呼叫篇](https://hackmd.io/@sysprog/c-recursion#Fibonacci-sequence) 的 Fibonacci sequence ### Iterative 以下是原始 `fibdrv.c` 裡實作的計算費式數列程式碼,相比於 Recursive 版本,運用 array 來儲存已計算過的子問題,能避免子問題的重複計算,時間複雜度與空間複雜度皆為 $O(n)$,尚有很大的進步空間。 ```c static long long fib_sequence(long long k) { /* FIXME: C99 variable-length array (VLA) is not allowed in Linux kernel. */ long long f[k + 2]; f[0] = 0; f[1] = 1; for (int i = 2; i <= k; i++) { f[i] = f[i - 1] + f[i - 2]; } return f[k]; } ``` ### Fast Doubling 參考 [Matrix Difference Equation for Fibonacci Sequence](https://chunminchang.github.io/blog/post/matrix-difference-equation-for-fibonacci-sequence) 與 [Calculating Fibonacci Numbers by Fast Doubling](https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling) 公式如下: $F_{2k} = F_k(2F_{k+1}-F_k)$ $F_{2k+1} = {F_{k+1}}^2+{F_k}^2$ 可以明顯看出兩式皆可由 $F_{k}$ 與 $F_{k+1}$ 推導而出 #### Fast Doubling (recursive 版本) ```c static long long fast_doubling_recursive(int k) { if (k <= 2) { return k ? 1 : 0; } int n = k / 2; long long a = fast_doubling_recursive(n); long long b = fast_doubling_recursive(n + 1); if (k & 1) { return a * a + b * b; } return a * (2 * b - a); } ``` #### Fast Doubling (iterative 版本) 實作參照以下虛擬碼進行實作: ```c Fast_Fib(n) a = 0; b = 1; // m = 0 for i = (number of binary digit in n) to 1 t1 = a*(2*b - a); t2 = b^2 + a^2; a = t1; b = t2; // m *= 2 if (current binary digit == 1) t1 = a + b; // m++ a = b; b = t1; return a; ``` 舉 $F(12)$ 為例: $12$ = $1100_2$ | i | start | 4 | 3 | 2 | 1 | result | | -------- | -------- | -------- | -------- | -------- | -------- | -------- | | n | - | **1**100 | 1**1**00 | 11**0**0 | 110**0** | - | | F(k) | F(0) | F(0*2+1) | F(1*2+1) | F(3*2) | F(6*2) | F(12) | | a |0|1|2|8|144 | 144| | b |1|1|3|13|233| - | 時間複雜度為: $O(log N)$ - 當遇到 `0` : 求出 $F_{2k}$ 和 $F_{2k+1}$ - 當遇到 `1` : 先求 $F_{2k}$ 和 $F_{2k+1}$,再求出 $F_{2k+2}$ ```c static long long fast_doubling_iterative(long long k) { if (k < 2) { return k; } long long a = 0, b = 1; for (unsigned int i = 1U << 15; i; i >>= 1) { long long t1 = a * (2 * b - a); long long t2 = b * b + a * a; if (k & i) { a = t2; b = t1 + t2; } else { a = t1; b = t2; } } return a; } ``` #### Fast Doubling (iterative with __builtin_clz()) Fast Doubling 會根據 bit 的值來進行相對應的操作,因此可以先利用 `clz` 去除數字 MSB 起算的開頭 0 位元。 ```c static long long fast_doubling_iterative_clz(long long k) { if (k < 2) { return k; } long long a = 0, b = 1; for (unsigned int i = 1U << (15 - __builtin_clz(k)); i; i >>= 1) { long long t1 = a * (2 * b - a); long long t2 = b * b + a * a; if (k & i) { a = t2; b = t1 + t2; } else { a = t1; b = t2; } } return a; } ``` ### 時間測量 #### kernel space 參照作業說明,挪用 `fib_write` 來回傳 kernel space 的執行時間,同時借用 size 參數當作切換的參數,以便於量測不同演算法的執行時間。 ```c /* write operation is skipped */ static ssize_t fib_write(struct file *file, const char *buf, size_t size, loff_t *offset) { ktime_t kt; switch (size) { case 1: kt = ktime_get(); fib_sequence(*offset); kt = ktime_sub(ktime_get(), kt); break; case 2: kt = ktime_get(); fast_doubling_recursive(*offset); kt = ktime_sub(ktime_get(), kt); break; case 3: kt = ktime_get(); fast_doubling_iterative(*offset); kt = ktime_sub(ktime_get(), kt); break; case 4: kt = ktime_get(); fast_doubling_iterative_clz(*offset); kt = ktime_sub(ktime_get(), kt); break; default: return 1; } return (ssize_t) ktime_to_ns(kt); } ``` 詳閱 [gnuplot 語法解說和示範](https://hackmd.io/@sysprog/Skwp-alOg) 來進行繪圖。 額外寫一個 `compare.c` 與 `fib_compare.gp` 來量測時間。 - write(fd, buf, 1) 回傳 iterative 的執行時間 - write(fd, buf, 2) 回傳 fast_doubling_recursive 的執行時間 - write(fd, buf, 3) 回傳 fast_doubling_iterative 的執行時間 - write(fd, buf, 4) 回傳 fast_doubling_iterative_clz 的執行時間 ```c int main() { char write_buf[] = "testing writing"; int offset = 100; /* TODO: try test something bigger than the limit */ int fd = open(FIB_DEV, O_RDWR); if (fd < 0) { perror("Failed to open character device"); exit(1); } for (int i = 0; i <= offset; i++) { long long t1, t2, t3, t4; lseek(fd, i, SEEK_SET); /*iterative*/ t1 = write(fd, write_buf, 1); /*fast_doubling_recursive*/ t2 = write(fd, write_buf, 2); /*fast_doubling_iterative*/ t3 = write(fd, write_buf, 3); /*fast_doubling_iterative_clz*/ t4 = write(fd, write_buf, 4); printf("%lld %lld %lld %lld\n", t1, t2, t3, t4); } close(fd); return 0; } ``` ``` set title "Fibonacci Compare" set xlabel "F(n)" set ylabel "time (ns)" set terminal png enhanced font "Verdana,12" set output "Fib_Compare.png" set xtics 0 ,10 ,100 set key left set grid plot [:][:]'test' using 1 with linespoints linewidth 2 title "iterative", \ "" using 2 with linespoints linewidth 2 title "fast doubling recursive", \ "" using 3 with linespoints linewidth 2 title "fast doubling w/o clz", \ "" using 4 with linespoints linewidth 2 title "fast doubling w/ clz", \ ``` 並執行以下命令 ```shell $ make load $ sudo taskset -c 7 ./compare > output.txt $ gnuplot scripts/fib_compare.gp ``` ![](https://i.imgur.com/ef47CUG.png) --- ### 大數運算 詳讀 [初步支援大數運算](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-d#%E5%88%9D%E6%AD%A5%E6%94%AF%E6%8F%B4%E5%A4%A7%E6%95%B8%E9%81%8B%E7%AE%97) #### `bn` 結構 引入長度可變動的數值表示法,動態配置不同大小的記憶體來呈現更大範圍的整數,將此資料結構定義在 `bn.h` 中。 ```c /* number[size - 1] = msb, number[0] = lsb */ typedef struct _bn { unsigned int *number; unsigned int size; int sign; } bn; ``` - `number` - 指向儲存的數值,之後會以 array 的形式來取用 - `size` - 配置的記憶體大小,單位為 sizeof(unsigned int) - `sign` - 0 為正數、1 為負數 #### `bn_to_string` 大數沒辦法直接以數值的形式列出,這裡改用字串來呈現,轉換的部分利用 ASCII 的特性並根據 fast doubling 的邏輯來「組合」出 10 進位字串。 以下詳細探討該程式碼,並理解其運作: ```c= /* * output bn to decimal string * Note: the returned string should be freed with kfree() */ char *bn_to_string(bn src) { // log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322 size_t len = (8 * sizeof(int) * src.size) / 3 + 2 + src.sign; char *s = kmalloc(len, GFP_KERNEL); char *p = s; memset(s, '0', len - 1); s[len - 1] = '\0'; for (int i = src.size - 1; i >= 0; i--) { for (unsigned int d = 1U << 31; d; d >>= 1) { /* binary -> decimal string */ int carry = !!(d & src.number[i]); for (int j = len - 2; j >= 0; j--) { s[j] += s[j] - '0' + carry; // double it carry = (s[j] > '9'); if (carry) s[j] -= 10; } } } // skip leading zero while (p[0] == '0' && p[1] != '\0') { p++; } if (src.sign) *(--p) = '-'; memmove(s, p, strlen(p) + 1); return s; } ``` 第 8 行: `len` 用於儲存將 `bn` 轉換為十進制字串所需的最大字串長度(Bytes)。 第 9 行: `kmalloc` 是一個動態記憶體配置函式,用於在 Linux 核心中配置記憶體空間。 :::warning 注意用語! :notes: jserv :::