# 2024q1 Homework6 (integration)
contributed by < [`hungyuhang`](https://github.com/hungyuhang) >
## 自我檢查清單
- [x] 研讀前述 ==Linux 效能分析== 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作 $\to$ 從中也該理解為何不希望在虛擬機器中進行實驗;
- [x] 閱讀〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉並對照 Linux 核心原始程式碼 (v6.1+),解釋 `insmod` 後,Linux 核心模組的符號 (symbol) 如何被 Linux 核心找到 (使用 List API)、`MODULE_LICENSE` 巨集指定的授權條款又對核心有什麼影響 (GPL 與否對於可用的符號列表有關),以及藉由 [strace](https://man7.org/linux/man-pages/man1/strace.1.html) 追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統?
- [ ] 閱讀《[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)》(LKMPG) 並解釋 [simrupt](https://github.com/sysprog21/simrupt) 程式碼裡頭的 mutex lock 的使用方式,並探討能否改寫為 [lock-free](https://hackmd.io/@sysprog/concurrency-lockfree);
- [ ] 探討 Timsort, Pattern Defeating Quicksort (pdqsort) 及 Linux 核心 [lib/sort.c](https://github.com/torvalds/linux/blob/master/lib/sort.c) 在排序過程中的平均比較次數,並提供對應的數學證明;
- [ ] 研讀 [CMWQ](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) (Concurrency Managed Workqueue) 文件,對照 [simrupt](https://github.com/sysprog21/simrupt) 專案的執行表現,留意到 worker-pools 類型可指定 "Bound" 來分配及限制特定 worker 執行於指定的 CPU,Linux 核心如何做到?CMWQ 關聯的 worker thread 又如何與 CPU 排程器互動?
- [ ] 解釋 `xoroshiro128+` 的原理 (對照〈[Scrambled Linear Pseudorandom Number Generators](https://arxiv.org/pdf/1805.01407.pdf)〉論文),並利用 [ksort](https://github.com/sysprog21/ksort) 提供的 `xoro` 核心模組,比較 Linux 核心內建的 `/dev/random` 及 `/dev/urandom` 的速度,說明 `xoroshiro128+` 是否有速度的優勢?其弱點又是什麼?
(https://blog.cruciferslab.net/?p=599)
- [ ] 解釋 [ksort](https://github.com/sysprog21/ksort) 如何運用 CMWQ 達到並行的排序;
## 自我檢查清單 - 筆記
### 閱讀〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉筆記
#### 製作 hello world 核心模組
檢查 Linux 核心版本:
```
$ uname -r
6.5.0-28-generic
```
在用 make 命令編譯 hello 核心模組時,雖然可以成功編譯,但終端機出現下列敘述:
```
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 產生的 hello.ko 核心模組用 `insmod` 掛載後,可以透過 `dmesg` 命令看到 hello.ko 發出的核心訊息:
```
[ 3849.955965] Hello, world
```
使用 `rmmod` 卸載 hello 核心模組,並且用 `dmesg` 命令查看核心訊息:
```
[ 3849.955965] Hello, world
[ 4260.552598] Goodbye, cruel world
```
#### 解釋 `insmod`
`insmod` 會呼叫 [`finit_module`](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3177) 這個 system call ,而 `finit_module` 會透過下列程式呼叫去執行 fibdrv.c 中被 `module_init` 宏所設定的函式:
- 呼叫 `idempotent_init_module` ([kernel/module/main.c#L3159](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3159))
- 在上方函式裡,呼叫 `init_module_from_file` ([kernel/module/main.c#L3131](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3131))
- 在上方函式裡,呼叫 `load_module` ([kernel/module/main.c#L2836](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L2836))
- 在上方函式裡,呼叫 `do_init_module` ([kernel/module/main.c#L2509](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L2509))
- 在上方函式裡,呼叫 `do_one_initcall` ([init/main.c#L1234](https://elixir.bootlin.com/linux/latest/source/init/main.c#L1234))
- 最後,這個函式會叫 `module_init` 宏當中設定的函式(在這個例子中就是 `init_fib_dev` )
同時,`insmod` 也需要將 user space 中 kernel module 的資料複製到 kernel space 中,這個操作是在 [`init_module_from_file`](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3131) 這個函式所做的:
```c
static int init_module_from_file(struct file *f, const char __user * uargs, int flags)
{
struct load_info info = { };
void *buf = NULL;
int len;
len = kernel_read_file(f, 0, &buf, INT_MAX, NULL, READING_MODULE);
if (len < 0) {
mod_stat_inc(&failed_kreads);
return len;
}
// ...
return load_module(&info, uargs, flags);
}
```
#### Linux 核心模組的符號 (symbol) 如何被 Linux 核心找到 (使用 List API)
Linux 核心會維護一份鏈節串列 ,串列內的每一個節點都是一個核心模組的資訊(包含 symbol )。這份鏈節串列可以使用 /proc/modules 這份文件來取得。
當使用 terminal 的時候,我們可以使用 [lsmod](https://man7.org/linux/man-pages/man8/lsmod.8.html) 這個工具程式來列出 /proc/modules 裡面所有紀錄的核心模組。
但如果我們想要在程式碼裡面去取用上述鏈節串列的內容,就需要仰賴 `THIS_MODULE` 物件。每個核心模組在程式碼中都會有一個相對應的 `THIS_MODULE` 物件,這個物件的資料型態為 `struct module` ,其定義可以在 [include/linux/module.h](https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L402) 中被找到:
```c
struct module {
enum module_state state;
/* Member of list of modules */
struct list_head list;
/* Unique handle for this module */
char name[MODULE_NAME_LEN];
// ...
#ifdef CONFIG_DYNAMIC_DEBUG_CORE
struct _ddebug_info dyndbg_info;
#endif
} ____cacheline_aligned __randomize_layout;
```
要拿到該鏈節串列,只要使用 `&THIS_MODULE->list` 就可以取得該鏈節串列的 list head ,接下來就可以使用 List API 來對這個鏈節串列進行操作了。
參考: [Linux Rootkits Part 5: Hiding Kernel Modules from Userspace](https://xcellerator.github.io/posts/linux_rootkits_05/)
#### `MODULE_LICENSE` 巨集指定的授權條款又對核心有什麼影響 (GPL 與否對於可用的符號列表有關)
如果將沒有 GPL License 的核心模組加載到 Linux 核心的話, Linux 核心會將自己標記為「受污染的」 (tainted) ([參考](https://docs.kernel.org/admin-guide/tainted-kernels.html))。以下是比較細節的流程:
當使用 `insmod` 來掛載核心模組時, `insmod` 會執行下列呼叫:
- 呼叫 `finit_module` system call ([kernel/module/main.c#L3177](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3177))
- 在上方函式裡,呼叫 `idempotent_init_module` ([kernel/module/main.c#L3159](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3159))
- 在上方函式裡,呼叫 `init_module_from_file` ([kernel/module/main.c#L3131](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L3131))
- 在上方函式裡,呼叫 `load_module` ([kernel/module/main.c#L2836](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L2836))
- 在上方函式裡,呼叫 `module_augment_kernel_taints` ([kernel/module/main.c#L2035](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L2035))
- 在上方函式裡,呼叫 `module_license_taint_check` ([kernel/module/main.c#L1582](https://elixir.bootlin.com/linux/latest/source/kernel/module/main.c#L1582))
當呼叫 `module_license_taint_check` 的時候, Linux 就會檢查核心模組的 License 並且做出相對應的動作。以下為 `module_license_taint_check` 的程式碼:
```c
static void module_license_taint_check(struct module *mod, const char *license)
{
if (!license)
license = "unspecified";
if (!license_is_gpl_compatible(license)) {
if (!test_taint(TAINT_PROPRIETARY_MODULE))
pr_warn("%s: module license '%s' taints kernel.\n",
mod->name, license);
add_taint_module(mod, TAINT_PROPRIETARY_MODULE,
LOCKDEP_NOW_UNRELIABLE);
}
}
```
程式碼會先透過 `license_is_gpl_compatible` 函式來檢查欲載入的核心模組是否為 GPL compatible ([Reference](https://docs.kernel.org/process/license-rules.html#id1)) ,如果不是 GPL compatible 的話, Linux 就會用 `add_taint_module` 函式將該模組標記為「受污染的」。
#### 藉由 [strace](https://man7.org/linux/man-pages/man1/strace.1.html) 追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統?
- 大量使用 openat 跟 close 系統呼叫來開啟或是關閉檔案。
- 使用 read 跟 newfstat 來讀取檔案內容或是資訊。
- 使用 mmap 來建立虛擬的記憶體。
以下是利用 strace 追蹤執行 `insmod fibdrv.ko` 的完整結果:
```shell
$ sudo strace insmod fibdrv.ko
execve("/usr/sbin/insmod", ["insmod", "fibdrv.ko"], 0x7ffee1a7da18 /* 27 vars */) = 0
brk(NULL) = 0x5c026a554000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc0d1f94c0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76452f960000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=88687, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 88687, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76452f94a000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libzstd.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=841808, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 843832, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76452f87b000
mmap(0x76452f885000, 729088, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa000) = 0x76452f885000
mmap(0x76452f937000, 69632, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xbc000) = 0x76452f937000
mmap(0x76452f948000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xcc000) = 0x76452f948000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/liblzma.so.5", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=170456, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 172296, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76452f850000
mmap(0x76452f853000, 110592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x76452f853000
mmap(0x76452f86e000, 45056, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e000) = 0x76452f86e000
mmap(0x76452f879000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x76452f879000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libcrypto.so.3", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=4455728, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 4469952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76452f400000
mmap(0x76452f4b2000, 2486272, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb2000) = 0x76452f4b2000
mmap(0x76452f711000, 860160, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x311000) = 0x76452f711000
mmap(0x76452f7e3000, 385024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3e2000) = 0x76452f7e3000
mmap(0x76452f841000, 9408, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76452f841000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\226 \25\252\235\23<l\274\3731\3540\5\226\327"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76452f000000
mprotect(0x76452f028000, 2023424, PROT_NONE) = 0
mmap(0x76452f028000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x76452f028000
mmap(0x76452f1bd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x76452f1bd000
mmap(0x76452f216000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x76452f216000
mmap(0x76452f21c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76452f21c000
close(3) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76452f84e000
arch_prctl(ARCH_SET_FS, 0x76452f84ec40) = 0
set_tid_address(0x76452f84ef10) = 9765
set_robust_list(0x76452f84ef20, 24) = 0
rseq(0x76452f84f5e0, 0x20, 0, 0x53053053) = 0
mprotect(0x76452f216000, 16384, PROT_READ) = 0
mprotect(0x76452f7e3000, 372736, PROT_READ) = 0
mprotect(0x76452f879000, 4096, PROT_READ) = 0
mprotect(0x76452f948000, 4096, PROT_READ) = 0
mprotect(0x5c0268f48000, 8192, PROT_READ) = 0
mprotect(0x76452f99a000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x76452f94a000, 88687) = 0
getrandom("\xbb\xcd\xcf\xef\xb3\x85\x16\x61", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x5c026a554000
brk(0x5c026a575000) = 0x5c026a575000
uname({sysname="Linux", nodename="hungyuhang-ZenBook-UX434FQ-UX434FQ", ...}) = 0
openat(AT_FDCWD, "/lib/modules/6.5.0-28-generic/modules.softdep", O_RDONLY|O_CLOEXEC) = 3
fcntl(3, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE)
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=1991, ...}, AT_EMPTY_PATH) = 0
read(3, "# Soft dependencies extracted fr"..., 4096) = 1991
read(3, "", 4096) = 0
close(3) = 0
openat(AT_FDCWD, "/proc/cmdline", O_RDONLY|O_CLOEXEC) = 3
read(3, "BOOT_IMAGE=/boot/vmlinuz-6.5.0-2"..., 4095) = 105
read(3, "", 3990) = 0
close(3) = 0
getcwd("/home/hungyuhang/linux2024/hw6/fibdrv", 4096) = 38
newfstatat(AT_FDCWD, "/home/hungyuhang/linux2024/hw6/fibdrv/fibdrv.ko", {st_mode=S_IFREG|0664, st_size=274824, ...}, 0) = 0
openat(AT_FDCWD, "/home/hungyuhang/linux2024/hw6/fibdrv/fibdrv.ko", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1", 6) = 6
lseek(3, 0, SEEK_SET) = 0
newfstatat(3, "", {st_mode=S_IFREG|0664, st_size=274824, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 274824, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76452f3bc000
finit_module(3, "", 0) = 0
munmap(0x76452f3bc000, 274824) = 0
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
```