# 2024q1 Homework6 (integration) contributed by < [`56han`](https://github.com/56han/lab0-c) > ## 自我檢查清單 ### 研讀 [Linux 效能分析](https://hackmd.io/@sysprog/gnu-linux-dev/https%3A%2F%2Fhackmd.io%2Fs%2FB11109rdg) **必要的設定和準備工作** kernel.perf_event_paranoid 是用來決定你在沒有 root 權限下 (Normal User) 使用 perf 時,你可以取得哪些 event data。預設值是 1 ,你可以輸入 ```shell $ cat /proc/sys/kernel/perf_event_paranoid ``` 來查看權限值。一共有四種權限值: :::info -1 - not paranoid at all 0 - disallow raw tracepoint access for unpriv 1 - disallow cpu events for unpriv 2 - disallow kernel profiling for unpriv 4 - disallow all unpriv perf event use ::: >The current paranoia levels are documented in a comment in the kernel source, in file kernel/events/core.c. 預設會使 perf 權限不足,所以要用以下方法開啟權限: ```shell $ sudo su # As Root $ sysctl -w kernel.perf_event_paranoid=-1 $ echo 0 > /proc/sys/kernel/kptr_restrict $ exit ``` 執行程式 ```shell $ gcc -std=c99 -c perf_top_example.c $ gcc perf_top_example.o -o example $ ./example & ``` 執行上述程式後,可以取得一個 pid 值,再根據 pid 輸入 ```shell $ perf top -p $pid ``` 會出現錯誤如下: :::danger Error: Couldn't create thread/CPU maps: No such process Press any key... ::: 但是直接執行 `perf top` 可以看到 example 在背景執行(第三行)。 ![image](https://hackmd.io/_uploads/ByVhyYDMA.png) :::warning 輸入 `perf top -p $pid` 會出現錯誤(No such process)的原因,我目前不清楚 ::: #### 為何不希望在虛擬機器中進行實驗? 虛擬機器通常無法完全模擬物理硬體的性能,因此在虛擬機器上運行 perf 無法獲得真實系統的性能數據。perf 需要訪問硬體性能計數器等功能,虛擬機器無法提供足夠的權限。 --- ### 閱讀〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉 **Linux Kernel Module 簡介** Linux kernel module 就是一個可以插入核心執行的一個小程式片段。使用時機比如說不同裝置需要不同的驅動程式,這時就可以**依照目前的硬體配置**動態地載入對應的模組。 **建構核心模組** 出現以下 error :::warning warning: the compiler differs from the one used to build the kernel The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0 You are using: gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0 ::: 因此需要指定正確的編譯器:在使用 make 命令時,可以明確指定使用的編譯器。例如: ```shell $ make -C /lib/modules/`uname -r`/build M=`pwd` modules CC=x86_64-linux-gnu-gcc-12 ``` 掛載核心模組後,只輸入 `dmesg` ,會出現以下錯誤 :::danger dmesg: read kernel buffer failed: Operation not permitted ::: 因此使用 `sudo dmesg` ,因為 `sudo dmesg` 才可以獲取 root 權限顯示核心訊息。 ```shell [429103.210678] Hello, world ``` 卸載模心模組後 dmesg 顯示核心訊息。 ```shell [429873.369388] Goodbye, cruel world ``` --- #### `fibdrv`: 可輸出 Fibonacci 數列的 Linux 核心模組 ```shell $ ls -l /dev/fibonacci $ cat /sys/class/fibonacci/fibonacci/dev $ cat /sys/module/fibdrv/version $ lsmod | grep fibdrv $ cat /sys/module/fibdrv/refcnt ``` ls -l: to list the files and directories with file permissions for your current location. ``` crw------- 1 root root 511, 0 五 8 21:29 /dev/fibonacci 511:0 0.1 fibdrv 12288 0 0 ``` crw: a character special file whose user class have the read, write permissions. :::warning 注意到 511 這個數字,試著對照 fibdev.c,找尋彼此的關聯? 目前尚未有想法。 ::: #### 1. 解釋 `insmod` **當我們透過 `insmod` 去載入一個核心模組時,為何 `module_init` 所設定的函式得以執行呢?** 不管是已定義 `MODULE` ,或是還沒定義 `MODULE`,因此最後每一個模組都會使用一個 `module_init()` 。 :::info `module_init` 巨集幫我們做 2 件事 1. 檢查傳入的函式,回傳值是否正確 2. 把 `init_module` 和傳入的函式關聯起來,因為 `insmod` 指令實作內部會呼叫 `init_module`。如此一來呼叫 `init_module` 就等同於呼叫我們自己寫的函式。 ::: 對 module 做初始化之前,核心先做 `layout_and_allocate` 為載入的 module 進行記憶體配置。 **像 `fibdrv.ko` 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心?** Executable and Linking Format 簡稱為 ELF,可以表示一個 executable binary file 或是 object file。由於這次實驗,`fibdrv.ko` 不是能在 shell 呼叫並執行的執行檔,因此這邊專注於解釋 ELF 檔案以 object file 的觀點。 因此我們需要透過 `insmod` 這個程式(可執行檔)來將 `fibdrv.ko` 植入核心中。kernel module 是執行在 kernel space 中,但是 `insmod fibdrv.ko` 是一個在 user space 的程序,因此在 `insmod` 中應該需要呼叫相關管理記憶體的 system call,將在 user space 中 kernel module 的資料複製到 kernel space 中。 #### 2. Linux 核心模組的符號 (symbol) 如何被 Linux 核心找到 (使用 List API) **關於 Linux 核心模組的符號** [閱讀 Linux 教程網](https://www.unixlinux.online/unixlinux/linuxjc/linuxjs/201703/91672.html) 撰寫 C 語言程式時,如果需要使用某個外部的函式,通常的做法是 #include 包含該函式原型(prototype)的頭文件,然後在程式中進行呼叫。經過編譯連接後,程序就能順利呼叫該程式。但是對於核心模組來說,這種方法並不適用,因此Linux核心提供了一種機制——**核心模組符號表**機制。 即使用 `EXPORT_SYMBOL` 標簽將模組中的函式對整個核心公開,因此導出的函式不用修改核心程式碼就可以被其他核心模組所呼叫。也就是說,使用 `EXPORT_SYMBOL` 可以將一個函式以符號的方式導出給其他模組使用。另外,也可以使用 `EXPORT_SYMBOL_GPL` 進行符號導出,但只適用於包含GPL許可權的模組。(它們定義在 `include/linux/export.h`) 在 [kernel/module/main.c](https://elixir.bootlin.com/linux/v6.8.7/source/kernel/module/main.c) 中定義 Linux 核心如何找到符號的過程,如果沒在內建的模組找到則會利用 `list_for_each_entry_rcu` 逐步走訪每個已載入的核心模組並且使用 `symsearch` 定義相關資訊,這邊會分成兩個類型的符號表是因為會需要根據當前模組的許可證是否為符合 GPL 授權條款,如果是則需要查找 GPL 的符號表,另外還要使用 `mod->state == MODULE_STATE_UNFORMED` 判斷模組的狀態是否能在被設定中,若成立則代表此模組的符號表不可使用。 ```c bool find_symbol(struct find_symbol_arg *fsa) { ... for (i = 0; i < ARRAY_SIZE(arr); i++) if (find_exported_symbol_in_section(&arr[i], NULL, fsa)) return true; list_for_each_entry_rcu(mod, &modules, list, lockdep_is_held(&module_mutex)) { struct symsearch arr[] = { { mod->syms, mod->syms + mod->num_syms, mod->crcs, NOT_GPL_ONLY }, { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms, mod->gpl_crcs, GPL_ONLY }, }; if (mod->state == MODULE_STATE_UNFORMED) continue; for (i = 0; i < ARRAY_SIZE(arr); i++) if (find_exported_symbol_in_section(&arr[i], mod, fsa)) return true; } pr_debug("Failed to find symbol %s\n", fsa->name); return false; } ``` #### 3. MODULE_LICENSE 巨集指定的授權條款又對核心有什麼影響? [閱讀 The Linux Kernel documentation](https://docs.kernel.org/process/license-rules.html) 核心根據模組的許可證類型,可以限制哪些模組可以加載到核心中。 :::info 許可證類型: “GPL”,“GPL v2”,“GPL and additional rights”,“Dual SD/GPL”,“Dual MPL/GPL”,“Proprietary” ::: **GPL 與否對於可用的符號列表有關?** * 透過 `EXPORT_SYMBOL` 導出的符號可以被包含 GPL 許可證的模組和不包含 GPL 許可證的模組呼叫。 * 透過 `EXPORT_SYMBOL_GPL` 導出的符號只能被包含 GPL 許可證的模組呼叫,否則會報錯。 #### 4. 藉由 `strace` 追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統? 指令 `sudo strace insmod fibdrv.ko` 會呼叫到 `finit_module` 又 `SYSCALL_DEFINE3` 會執行 `load_module` 這個函式。 在 Linux kernel v6.8.5 中,finit_module 系統呼叫的實作方式已變更, 呼叫 `idempotent_init_module` -> `init_module_from_file` -> `load_module` 。