owned this note
owned this note
Published
Linked with GitHub
# 2024q1 Homework6 (integration)
## 開發環境
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ 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): 16
On-line CPU(s) list: 0-15
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
CPU family: 6
Model: 165
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
Stepping: 5
CPU max MHz: 4800.0000
CPU min MHz: 800.0000
BogoMIPS: 5799.77
## 自我檢查清單
- [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 核心的掛載,涉及哪些些系統呼叫和子系統?
### 〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉筆記
`MODULE_LICENSE` 在 [/include/linux/module.h](https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L186) 定義如下
```c
#define MODULE_LICENSE(_license) MODULE_FILE MODULE_INFO(license, _license)
```
共有 `GPL`, `GPL v2`, `GPL and additional rights`, `Dual BSD/GPL`, `Dual MIT/GPL`, `Dual MPL/GPL` 用來標示模組為自由軟體模組,非自由則是 `Proprietary`。
這些標記的理由如下
>1. So modinfo can show license info for users wanting to vet their setup is free
>2. So the community can ignore bug reports including proprietary modules
>3. So vendors can do likewise based on their own policies
看註解時看到 `EXPORT_SYMBOL_GPL` 這個巨集,它的用途是導出給定的 symbol 給其他模組使用。但是該模組必須遵循 GPL 規範,也就是說如果模組是 `Proprietary` 則無法使用 `EXPORT_SYMBOL_GPL` 導出的 symbol
>The sole purpose is to make the 'Proprietary' flagging work and to refuse to bind symbols which are exported with EXPORT_SYMBOL_GPL when a non free module is loaded.
#### 模組掛載練習
檢查 Linux 核心版本
$ uname -r
6.5.0-28-generic
確認 linux-headers 套件已正確安裝於開發環境
$ dpkg -L linux-headers-`uname -r` | grep "/lib/modules/.*/build"
/lib/modules/6.5.0-28-generic/build
掛載核心模組
$ make -C /lib/modules/`uname -r`/build M=`pwd` modules
$ sudo insmod hello.ko
$ sudo rmmod hello
$ sudo dmesg
....
[174167.005752] Hello, world
[174246.857853] Goodbye, cruel world
考慮到以下程式碼
```c
static int hello_init(void) {
printk(KERN_INFO "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
```
在 `module_init` 巨集中會展開 `__initcall(x)` 巨集
```c
#define __initcall(fn) device_initcall(fn)
```
`device_initcall(fn)` 對應到 `__define_initcall(fn, 6)`
> `device_initcall(fn)` 定義在 [linux/init.h](https://github.com/torvalds/linux/blob/5eb4573ea63d0c83bf58fb7c243fc2c2b6966c02/include/linux/init.h#L311)
```c
#define device_initcall(fn) __define_initcall(fn, 6)
```
持續展開後會執行到以下巨集
```c
#define ____define_initcall(fn, __unused, __name, __sec) \
static initcall_t __name __used \
__attribute__((__section__(__sec))) = fn;
```
意思是 symbol : `__name` (根據定義的函數名稱決定)會註冊到 `__sec` 這個 ELF section (`__define_initcall(fn, 6)` 傳入參數為 6,對應到 : `.initcall6.init`)
並將此 symbol 指到 `fn` 這個函式
> 上述展開過程有些程式碼還沒看懂,我是以[此篇討論]([參考](https://stackoverflow.com/questions/18605653/module-init-vs-core-initcall-vs-early-initcall))幫助理解
***`initcall_t` 定義為一個 function pointer**
```c
typedef int (*initcall_t)(void);
```
透過 `insmod` 載入此核心模組時,`module_init` 會做兩件事情
1. 傳入的 function pointer,也就是 `hello_init` 是不是合法的
2. `insmod` 指令實作內部會呼叫 `init_module`,`module_init` 會將 `init_module` 與 `hello_init` 關聯起來,因此呼叫 `init_module` 就相當於執行自行定義的函式
```c
#define module_init(initfn)
...
int init_module(void) __copy(initfn) __attribute__((alias(#initfn)))
```
> initfn 為傳入的 funciton,在這邊也就是 `hello_init`。
> 透過 gcc 的 __attribute__((alias( ..))) 來將 `hello_init` 設成 `init_module` 的別名
#### 藉由 strace 追蹤 Linux 核心的掛載
```c=
$ sudo strace insmod hello.ko
execve("/usr/sbin/insmod", ["insmod", "hello.ko"], 0x7fff7b3dba78 /* 26 vars */) = 0
brk(NULL) = 0x5a65cffc8000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fff7c5e9120) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7b82f94c7000
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=61287, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 61287, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7b82f94b8000
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) = 0x7b82f93e9000
mmap(0x7b82f93f3000, 729088, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa000) = 0x7b82f93f3000
mmap(0x7b82f94a5000, 69632, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xbc000) = 0x7b82f94a5000
mmap(0x7b82f94b6000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xcc000) = 0x7b82f94b6000
close(3) = 0
...
close(3) = 0
getcwd("/home/xiang/Desktop/hello", 4096) = 26
newfstatat(AT_FDCWD, "/home/xiang/Desktop/hello/hello.ko", {st_mode=S_IFREG|0664, st_size=112368, ...}, 0) = 0
openat(AT_FDCWD, "/home/xiang/Desktop/hello/hello.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=112368, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 112368, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7b82f93a0000
finit_module(3, "", 0) = 0
munmap(0x7b82f93a0000, 112368) = 0
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
```
自上述第 28 行可以發現呼叫到 `finit_module`,其實做內部會呼叫 `idempotent_init_module` 當中的 `init_module_from_file` 再呼叫 `load_module` 以此分配記憶體並載入模組。
`load_module` 會呼叫 `simplify_symbols` -> `resolve_symbol_wait` -> `resolve_symbol_wait` 最後呼叫到 `find_symbol`,其中用到 `list_for_each_entry_rcu` 並透過 `find_exported_symbol_in_section` 進行symbol 查找。
>`load_module` 定義在 [module/main.c](https://elixir.bootlin.com/linux/v6.5/source/kernel/module/main.c#L2820)
:::info
上述目前僅於流程概念上的理解,實作原理尚待研究。
:::
- [ ] 研讀 [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 排程器互動?
### [CMWQ](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) 筆記
> CMWQ
Linux 提供 workqueue API 用來實現非同步執行 context。每個需要被非同步執行的函式,會抽象為一個 work item,並加入 workqueue 中,接著核心便會挑選合適的 "worker" thread 來處理 work item,完畢後便從 workqueue 中剔除。
在 CMWQ 之前的 workqueue 實作上,worker thread 數量和被建立的 workqueue 數量成正相關,也就是每建立新的 workqueue,系統上的 worker thread 總量便會增加。
實作上有兩種建立 worker thread 方式
* multi threaded (MT) workqueue : 每個 CPU 上會有各自的 worker thread。可分別處理不同的 work item
* single threaded (ST) workqueue: 只有單一的 worker thread 來處理 work item
MT 的缺陷為 CPU 和 workqueue 數量變多時,worker thread 數量也會增多,可能會因 worker thread 行程數量太多,將 32k 的 PID 空間佔滿 (32k PID 為可以分配行程 ID 的數量),導致其他行程無法運行。又因替每個 CPU 提供一個 context,會消耗大量資源,但 concurrency 效果卻不佳。原因是 workqueue 中的 work 會對應到特定的 worker pool 去處理,那當此 worker pool 所有 worker 被 block 住時,該 workqueue 的其他 work 便無法轉交其他 worker pool,導致剩餘 work 無法及時被處理。
ST 的缺陷則為只有一個 worker thread,因此所有的 work 都需逐一排隊執行,造成並行效果不佳。
**CMWQ 為了解決上述問題而生,並專注於以下目標**
* 維持原始 workqueue API 的相容性
* 捨棄 workqueue 獨立對應一組 worker-pools 的作法。反之是讓 workqueue 共享 per-CPU worker pools,並依照情況提供對應的並行程度,也進而減少資源浪費
* worker pool 和並行程度由系統內部處理,API使用者可以不用考慮細節
**設計方式**
會創造一個 `work_struct` 結構,裡面有 `func` 成員,用來指向要被執行的函式。當系統想非同步執行某個函式,就會透過 `work_struct` 建立對應的 `work item` 並放入 workqueue 中等待執行。worker thread 會負責從佇列中取出 work item 並執行其中的 `func`。
CMWQ 會對每個 CPU 含有兩個 worker pools,一種是處理普通的 work,另一則是處理優先等級高的。此外還有一些額外 worker-pools,它們不會被綁訂到特定 CPU,且 pools 的數量是動態的。
對於 CPU 上的 worker-pool,可以排程器做並行的管理,且每個 worker 被喚醒或是睡眠時,worker-pools 會收到通知,以此追蹤可運行的 woker 數量。CPU 上只要有多個正運行的 worker threads,worker-pools 就不會處理新的 work,當所有 worker threads 都進入睡眠狀態時,worker-pools 就會立刻排程 work 給新的 worker thread,藉此 CPU 就不會在還有 work 要處理時進入 idle。
除了會占用記憶體空間外,idle 的 worker thread 不會消耗其他資源,因此 CWMQ 會保留一個 idle 的 worker thread 一段時間。這樣若有 work,則直接拿閒置的 worker thread 處理,就不需額外創造。
:::info
教材有提到 BH (softirq) context, 目前還沒看懂。
:::
- [ ] 閱讀《[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) 在排序過程中的平均比較次數,並提供對應的數學證明;
- [ ] 解釋 `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+` 是否有速度的優勢?其弱點又是什麼?
- [ ] 解釋 [ksort](https://github.com/sysprog21/ksort) 如何運用 CMWQ 達到並行的排序;