Try   HackMD

2024q1 Homework6 (integration)

contributed by < 56han >

自我檢查清單

研讀 Linux 效能分析

必要的設定和準備工作
kernel.perf_event_paranoid 是用來決定你在沒有 root 權限下 (Normal User) 使用 perf 時,你可以取得哪些 event data。預設值是 1 ,你可以輸入

$ cat /proc/sys/kernel/perf_event_paranoid

來查看權限值。一共有四種權限值:

-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 權限不足,所以要用以下方法開啟權限:

$ sudo su # As Root
$ sysctl -w kernel.perf_event_paranoid=-1
$ echo 0 > /proc/sys/kernel/kptr_restrict
$ exit

執行程式

$ gcc -std=c99 -c perf_top_example.c
$ gcc perf_top_example.o -o example
$ ./example &

執行上述程式後,可以取得一個 pid 值,再根據 pid 輸入

$ perf top -p $pid

會出現錯誤如下:

Error:
Couldn't create thread/CPU maps: No such process

Press any key

但是直接執行 perf top 可以看到 example 在背景執行(第三行)。

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

輸入 perf top -p $pid 會出現錯誤(No such process)的原因,我目前不清楚

為何不希望在虛擬機器中進行實驗?

虛擬機器通常無法完全模擬物理硬體的性能,因此在虛擬機器上運行 perf 無法獲得真實系統的性能數據。perf 需要訪問硬體性能計數器等功能,虛擬機器無法提供足夠的權限。


閱讀〈Linux 核心模組運作原理

Linux Kernel Module 簡介
Linux kernel module 就是一個可以插入核心執行的一個小程式片段。使用時機比如說不同裝置需要不同的驅動程式,這時就可以依照目前的硬體配置動態地載入對應的模組。

建構核心模組
出現以下 error

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 命令時,可以明確指定使用的編譯器。例如:

$ make -C /lib/modules/`uname -r`/build M=`pwd` modules CC=x86_64-linux-gnu-gcc-12

掛載核心模組後,只輸入 dmesg ,會出現以下錯誤

dmesg: read kernel buffer failed: Operation not permitted

因此使用 sudo dmesg ,因為 sudo dmesg 才可以獲取 root 權限顯示核心訊息。

[429103.210678] Hello, world

卸載模心模組後 dmesg 顯示核心訊息。

[429873.369388] Goodbye, cruel world

fibdrv: 可輸出 Fibonacci 數列的 Linux 核心模組

$ 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.

注意到 511 這個數字,試著對照 fibdev.c,找尋彼此的關聯?
目前尚未有想法。

1. 解釋 insmod

當我們透過 insmod 去載入一個核心模組時,為何 module_init 所設定的函式得以執行呢?
不管是已定義 MODULE ,或是還沒定義 MODULE,因此最後每一個模組都會使用一個 module_init()

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 教程網
撰寫 C 語言程式時,如果需要使用某個外部的函式,通常的做法是 #include 包含該函式原型(prototype)的頭文件,然後在程式中進行呼叫。經過編譯連接後,程序就能順利呼叫該程式。但是對於核心模組來說,這種方法並不適用,因此Linux核心提供了一種機制——核心模組符號表機制。
即使用 EXPORT_SYMBOL 標簽將模組中的函式對整個核心公開,因此導出的函式不用修改核心程式碼就可以被其他核心模組所呼叫。也就是說,使用 EXPORT_SYMBOL 可以將一個函式以符號的方式導出給其他模組使用。另外,也可以使用 EXPORT_SYMBOL_GPL 進行符號導出,但只適用於包含GPL許可權的模組。(它們定義在 include/linux/export.h

kernel/module/main.c 中定義 Linux 核心如何找到符號的過程,如果沒在內建的模組找到則會利用 list_for_each_entry_rcu 逐步走訪每個已載入的核心模組並且使用 symsearch 定義相關資訊,這邊會分成兩個類型的符號表是因為會需要根據當前模組的許可證是否為符合 GPL 授權條款,如果是則需要查找 GPL 的符號表,另外還要使用 mod->state == MODULE_STATE_UNFORMED 判斷模組的狀態是否能在被設定中,若成立則代表此模組的符號表不可使用。

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
核心根據模組的許可證類型,可以限制哪些模組可以加載到核心中。

許可證類型:
“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_moduleSYSCALL_DEFINE3 會執行 load_module 這個函式。
在 Linux kernel v6.8.5 中,finit_module 系統呼叫的實作方式已變更, 呼叫 idempotent_init_module -> init_module_from_file -> load_module