owned this note
owned this note
Published
Linked with GitHub
# 2024q1 Homework6 (integration)
contributed by < `HotMercury` >
:::danger
注意細節!要更新 HackMD 筆記的標題。
:::
## 閱讀 linux 核心模組教材
[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)
ubuntu linux 預設開啟 `EFI_SECURE_BOOT_SIG_ENFORCE` 當使用第三方 kernel module 時,所以有幾種方法可以解決。
- Disable Secure Boot in UEFI (BIOS) settings
- An alternative way is to disable Secure Boot using mokutil
```bash
$ sudo apt install mokutil
$ sudo mokutil --sb-state
$ sudo mokutil --disable-validation
```
這裡嘗試理解 [UEFI Secure Boot](https://wiki.debian.org/SecureBoot)
What is UEFI Secure Boot?
UEFI is unified extensiblt firmware interface 更多細節可以參考 man page,UEFI secure boot,用來避免在 boot process 時載入惡意程式,大部分的 x86 hardware 都有 microsoft key 但我們還是可以透過 disable or 更改 sign key,所以 SB 並不是微軟帝國為了防止其他 OS 的手段。
:::danger
改進漢語表達。
:::
[kernel module 前置作業](https://sysprog21.github.io/lkmpg/#headers)
Install the header files for the kernel
```bash
sudo apt-get update
apt-cache search linux-headers-`uname -r`
sudo apt-get install kmod linux-headers-`uname -r`
dpkg -L linux-headers-`uname -r` | grep "/lib/modules/.*/build"
```
[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/#headers) 提供安裝的 example,我認為是可以改成以下方式比較容易跟著做,我想嘗試貢獻。
```diff
- sudo apt-get install kmod linux-headers-5.4.0-80-generic
+ sudo apt-get install kmod linux-headers-`uname -r`
```
### 小試身手 hello module
- MODULE_LICENSE
They are defined within [include/linux/module.h](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/module.h)
> This exists for several reasons
> 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
- init
- exit
- makefile
如果直接在 make file 裡面使用 `PWD := &(CURDIR)` 可能會有錯誤
> In the sudoers security policy, env_reset is enabled by default, which restricts environment variables.
```bash
make -C /lib/modules/`uname -r`/build M=`pwd` modules
```
掛載
```
sudo insmod hello.ko
```
### hello.ko insmod 流程
hello.ko
```
strace -o strace.log insmod hello.ko
```
## fibdrv
`make check` 後會有以下的 warning,跟據 [ISO C90 forbids variable length array](https://stackoverflow.com/questions/10234288/iso-c90-forbids-variable-length-array) 使用 dynamic allocate,因為在 kernel 中所以使用 `kmalloc`
```
warning: ISO C90 forbids variable length array ‘f’ [-Wvla]
31 | long long f[k + 2];
```
```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];
long long *f = kmalloc((k + 2) * sizeof(long long), GFP_KERNEL);
if (!f) {
pr_info(KERN_ALERT "Failed to allocate memory\n");
return -ENOMEM;
}
f[0] = 0;
f[1] = 1;
for (int i = 2; i <= k; i++) {
f[i] = f[i - 1] + f[i - 2];
}
long long result = f[k];
kfree(f);
return result;
}
```
### macro 展開
kernel module 會用到的 macro,而這些 macro 是怎麼讓 kernel 知道的,以及 insmod 對應的 linux 內部操作有什麼關西?
- MODULE_LICENSE
- MODULE_AUTHOR
- MODULE_DESCRIPTION
接下來的 code 接來自 linux v6.8 且假設我的 code 是 `MODULE_AUTHOR("Jay")`
[include/linux/module.h](https://elixir.bootlin.com/linux/v6.8/source/include/linux/module.h#L236)
```c
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
```
展開成 `MODULE_INFO(author, "Jay")`
```c
/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
```
展開成 `__MODULE_INFO(author, author, "jay")` 為什麼需要傳入兩個 tag 是為了統一界面?
:::danger
你做實驗,看能否通過編譯。
:::
`__section` : GCC extension determine the storage location of the object tagged with it.
`__stringify` : `#define __stringify_1(x...) #x`
[include/linux/moduleparam.h](https://elixir.bootlin.com/linux/v6.8/source/include/linux/moduleparam.h#L23)
```c
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \
__used __section(".modinfo") __aligned(1) \
= __MODULE_INFO_PREFIX __stringify(tag) "=" info
```
探討 `__UNIQUE_ID(author)`,所以展開會變成 `__UNIQUE_ID_author0`
`__COUNTER__` 根據 [The C Preprocessor](https://gcc.gnu.org/onlinedocs/gcc-9.1.0/cpp.pdf) This macro expands to sequential integral values starting from 0
> Care must be taken to ensure that `__COUNTER__` is not expanded prior to inclusion of precompiled headers which use it. Otherwise, the precompiled headers will not be used
:::warning
對照 [Linux 核心原始程式碼巨集: max, min](https://hackmd.io/@sysprog/linux-macro-minmax)
:::
[include/linux/compiler.h](https://elixir.bootlin.com/linux/v6.8/source/include/linux/compiler.h#L180)
```c!
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)
```
[include/linux/compiler_type.h](https://elixir.bootlin.com/linux/v6.8/source/include/linux/compiler_types.h#L83)
```c!
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
```
所以最後的展開為以下
```c
static const char __UNIQUE_ID_author0[] \
__used __attribute__((section(".modinfo"), unused, aligned(1))) \
= "author=Jay"
```
接下來透過 objdump 觀察
> objdump - display information from object files
```
$ objdump -s fibdrv.ko | head -130
Contents of section .modinfo:
0000 76657273 696f6e3d 302e3100 64657363 version=0.1.desc
0010 72697074 696f6e3d 4669626f 6e616363 ription=Fibonacc
0020 6920656e 67696e65 20647269 76657200 i engine driver.
0030 61757468 6f723d4a 6179006c 6963656e author=Jay.licen
0040 73653d44 75616c20 4d49542f 47504c00 se=Dual MIT/GPL.
0050 73726376 65727369 6f6e3d38 38353230 srcversion=88520
0060 37394236 44374236 32354643 46303130 79B6D7B625FCF010
0070 44370064 6570656e 64733d00 72657470 D7.depends=.retp
0080 6f6c696e 653d5900 6e616d65 3d666962 oline=Y.name=fib
0090 64727600 7665726d 61676963 3d352e31 drv.vermagic=5.1
00a0 352e302d 3130312d 67656e65 72696320 5.0-101-generic
00b0 534d5020 6d6f645f 756e6c6f 6164206d SMP mod_unload m
00c0 6f647665 7273696f 6e732000 odversions .
```
### insmod 流程
閱讀《[Linux Device Driver 3/e](https://static.lwn.net/images/pdf/LDD3/ch02.pdf)》 Initialization and Shutdown
initialization functions 通常會以 static 的方式,因為通常只被用來註冊使用。
> The module loader drops the initialization function
after the module is loaded, making its memory available for other uses.
:::danger
避免過多的中英語詞彙交錯
:::
建構 Linux 核心模組所用到的巨集 <s>kernel module 的 macro</s> 會增加 <s>specail section</s> ?? 因此可以找到對應的 module initialization function.
至於 module function 會被如何呼叫
> Modules can register many different types of facilities, including different kinds of devices, filesystems, cryptographic transforms, and more. For each facility, there is a specific kernel function that accomplishes this registration. The arguments passed to the kernel registration functions are usually pointers to data structures describing the new facility and the name of the facility being registered. The data structure usually contains point
## CMWQ
閱讀 [workqueue](https://www.kernel.org/doc/html/latest/core-api/workqueue.html)
非同步的執行上下文通常會使用 workqueue API,所以可以想像成一個代工廠,將任務交給工廠,如果沒有任務,工人就可以去放假。
原本設計的 multi-thread 是一個 core 一個 thread,而 single thread 則是共用一個,每個 workqueue 會維護一個 work pool,以上不管是 MT or ST 都沒有足夠好的 concurrency,因此接下來要探討 Concurrency Managed Workqueue
:::info
這裡提到 default 32k PID 是什麼意思
:::
> The kernel grew a lot of MT wq users over the years and with the number of CPU cores continuously rising, some systems saturated the default 32k PID space just booting up.
**Concurrency Managed Workqueue**
*goal*
- 相容原本的 workqueue API
- per-CPU 維護統一的 work pools 且共享於所有的 wq,提供彈性的 concurrency 機制,減少資源浪費
- 隱藏使用者一些機制
**Design**
要使用 wq 時會先初始化 work item,指向 function 並放入 work queue。
接下來搭配 [simrupt.c](https://github.com/sysprog21/simrupt/blob/main/simrupt.c#L319) 來理解 wq API
### simrupt.c 導讀
是一個 kernel module device driver 用來做 deffered work
- `simrupt_init`
首先用 [kfifo_alloc](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kfifo_alloc) 分配一個 fifo buffer,以下為 fifo 展開的樣子(type 為初始化參數)。
```c
struct {
union {
struct __kfifo kfifo;
type *type;
const datatype *const_type;
char (*rectype)[0];
type *ptr;
typr *ptr_const;
}
type buf[0]
}
```
接著透過 `alloc_chrdev_region` 註冊 [Character devices](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html),`cdev_init` 初始化 struct cdev 以及對應的 operations。
- `alloc_workqueue` alloc a wq
@name : wq 的名字
@flags : 可以設定 wq 的模式
- WQ_UNBOUND
```c!
/* Create the workqueue */
simrupt_workqueue = alloc_workqueue("simruptd", WQ_UNBOUND, WQ_MAX_ACTIVE);
if (!simrupt_workqueue) {
vfree(fast_buf.buf);
device_destroy(simrupt_class, dev_id);
class_destroy(simrupt_class);
ret = -ENOMEM;
goto error_cdev;
}
```