# 2024q1 Homework6 (integration)
contributed by < `wu81177` >
## Linux 核心模組執行
完成編譯核心模組之後,即可使用 `insmod` 將 .ko 掛載,以 `hello` 模組為例:
```shell
$ sudo insmod hello.ko
```
可以查看 hello.ko 的 modinfo
```shell
$ modinfo hello.ko
filename: /home/bohan/linux2024/hello/hello.ko
license: Dual BSD/GPL
srcversion: ACE9EABF0105E78508E4360
depends:
retpoline: Y
name: hello
vermagic: 6.5.0-21-generic SMP preempt mod_unload modversions
```
`dmesg` 來自於 "display message" 的縮寫,可顯示核心訊息, `hello` 模組會使用 `printk` 印出 Hello, world ,可以用 dmesg 查看
```shell
$ sudo dmesg | grep Hello
[sudo] password for bohan:
[173341.575538] Hello, world
```
若要卸載模組則使用 `rmmod`
```shell
$ sudo rmmod hello
```
使用 `strace` 可以看到 `insmod` 過程中使用的系統呼叫
:::spoiler
```shell
$ sudo strace insmod hello.ko
[sudo] password for bohan:
execve("/usr/sbin/insmod", ["insmod", "hello.ko"], 0x7ffd096049f8 /* 27 vars */) = 0
brk(NULL) = 0x55cc2ae49000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fff8216c4c0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbba3594000
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=61431, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 61431, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbba3585000
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) = 0x7fbba34b6000
mmap(0x7fbba34c0000, 729088, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa000) = 0x7fbba34c0000
mmap(0x7fbba3572000, 69632, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xbc000) = 0x7fbba3572000
mmap(0x7fbba3583000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xcc000) = 0x7fbba3583000
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) = 0x7fbba348b000
mmap(0x7fbba348e000, 110592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7fbba348e000
mmap(0x7fbba34a9000, 45056, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e000) = 0x7fbba34a9000
mmap(0x7fbba34b4000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7fbba34b4000
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) = 0x7fbba3000000
mmap(0x7fbba30b2000, 2486272, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb2000) = 0x7fbba30b2000
mmap(0x7fbba3311000, 860160, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x311000) = 0x7fbba3311000
mmap(0x7fbba33e3000, 385024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3e2000) = 0x7fbba33e3000
mmap(0x7fbba3441000, 9408, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fbba3441000
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\302\211\332Pq\2439\235\350\223\322\257\201\326\243\f"..., 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) = 0x7fbba2c00000
mprotect(0x7fbba2c28000, 2023424, PROT_NONE) = 0
mmap(0x7fbba2c28000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7fbba2c28000
mmap(0x7fbba2dbd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7fbba2dbd000
mmap(0x7fbba2e16000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7fbba2e16000
mmap(0x7fbba2e1c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fbba2e1c000
close(3) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbba3489000
arch_prctl(ARCH_SET_FS, 0x7fbba348a000) = 0
set_tid_address(0x7fbba348a2d0) = 28250
set_robust_list(0x7fbba348a2e0, 24) = 0
rseq(0x7fbba348a9a0, 0x20, 0, 0x53053053) = 0
mprotect(0x7fbba2e16000, 16384, PROT_READ) = 0
mprotect(0x7fbba33e3000, 372736, PROT_READ) = 0
mprotect(0x7fbba34b4000, 4096, PROT_READ) = 0
mprotect(0x7fbba3583000, 4096, PROT_READ) = 0
mprotect(0x55cc292ed000, 8192, PROT_READ) = 0
mprotect(0x7fbba35ce000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7fbba3585000, 61431) = 0
getrandom("\x5d\x0e\x00\x4f\x66\xfc\x07\x0a", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x55cc2ae49000
brk(0x55cc2ae6a000) = 0x55cc2ae6a000
uname({sysname="Linux", nodename="bohan-930XDB-931XDB-930XDY", ...}) = 0
openat(AT_FDCWD, "/lib/modules/6.5.0-21-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) = 118
read(3, "", 3977) = 0
close(3) = 0
getcwd("/home/bohan/linux2024/hello", 4096) = 28
newfstatat(AT_FDCWD, "/home/bohan/linux2024/hello/hello.ko", {st_mode=S_IFREG|0664, st_size=112376, ...}, 0) = 0
openat(AT_FDCWD, "/home/bohan/linux2024/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=112376, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 112376, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbba346d000
finit_module(3, "", 0) = 0
munmap(0x7fbba346d000, 112376) = 0
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
```
:::
裡面包含
execve:執行指令 /usr/sbin/insmod hello.ko。
brk:設置堆的結束地址。
arch_prctl:嘗試設置進程的架構相關參數,但由於提供的參數無效而失敗。
mmap:在記憶體中分配一塊空間。
access:嘗試訪問檔案 /etc/ld.so.preload。
openat:打開檔案 /etc/ld.so.cache 以及一系列動態連結庫檔案。
newfstatat:獲取檔案狀態資訊。
read:從檔案中讀取內容。
close:關閉檔案描述符。
finit_module:載入模組。
munmap:釋放記憶體映射。
exit_group:執行緒退出。
## 閱讀 [< The Linux Kernel Module Programming Guide >](https://sysprog21.github.io/lkmpg/#the-init-and-exit-macros)
### 5.6 Device Drivers
使用以下命令可查看設備檔案:
```shell=
bohan@bohan-930XDB-931XDB-930XDY:~/Desktop$ ls -l /dev
total 0
crw------- 1 root root 10, 117 May 3 10:59 acpi_thermal_rel
crw-r--r-- 1 root root 10, 235 May 3 10:59 autofs
drwxr-xr-x 2 root root 540 May 3 10:59 block
```
以第一列 `crw------- 1 root root 10, 117 May 3 10:59 acpi_thermal_rel` 為例, `c` 表示他是字元設備檔, `rw-------` 在描述讀寫權限, `1` 代表 hard link 數,第一個 `root` 表示檔案的擁有者,第二個 `root` 則表示檔案的所屬使用者組,10, 117 是檔案的主要和次要設備號,May 3 10:59 表示檔案的最後修改時間, `acpi_thermal_rel` 是檔案名稱。
主設備號代表使用哪個驅動程式來存取硬體。每個驅動程式都被分配了一個唯一的主編號;所有具有相同主設備號碼的設備檔案都由同一個驅動程式控制,次設備號則是來區分驅動程式控制的各種硬體。
設備檔案可通過 mknod 命令創建,要創建一個新的字符設備,名稱為 coffee,主要/次要編號為 12 和 2,可執行
```shell=
mknod /dev/coffee c 12 2
```
### 6.1 The file_operations Structure
`/include/linux/fs.h` 中定義了`file_operations` 這個結構,每個欄位紀錄著驅動程式中函式指標,方便給核心調用
:::spoiler
```clike
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
```
:::
有兩種賦值寫法:
```clike
struct file_operations fops = {
read: device_read,
write: device_write,
open: device_open,
release: device_release
};
```
```clike
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
```
前者為 GCC 提供的寫法,後者是 C99 的寫法,後者比較通用
### 6.2 The file structure
每個設備在核心中都由一個檔案結構來表示,定義在 `include/linux/fs.h` 中,不會出現在用戶空間的程式中,它代表的是一個抽象打開的「檔案」,而不是硬碟上的檔案。
一個 struct file 的實例通常被命名為 filp ,或被稱為 struct file 。
### 6.3 Registering A Device
字符設備通常通過設備檔案訪問,通常位於 /dev ,可以使用 `/include/linux/fs.h` 定義的 `register_chrdev` 將設備註冊到內核中
```clike
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
```
`unsigned int major` 是請求的主編號,`const char *name` 是設備的名稱,它將出現在 `/proc/devices` 中, `struct file_operations *fops` 是指向驅動程序的 `file_operations` 表的指針,返回負值表示註冊失敗,而這裡不用傳遞次要編號,因為內核不關心次要編號,只有驅動程序使用它。
選擇主編號最簡單的方法是查看 `Documentation/admin-guide/devices.txt` ,然後選擇一個未使用的編號,但這樣不確定選擇的編號是否會被後來分配,應該要求內核分配一個動態主要編號。
如果將主要編號設為 0 傳遞給 `register_chrdev` ,返回值是動態分配的主要編號,但這樣無法提前創建設備文件,因為不知道主要編號是多少。
有幾種方法可以解決這個問題。首先,驅動程序本身可以印出新分配的編號,然後手動創建設備文件。
其次,新註冊的設備將在 `/proc/devices` 中有一個條目,可以手動創建設備文件或編寫一個 shell 腳本來讀取文件並創建設備文件。
第三種方法是在成功註冊後,我們的驅動程序使用 `device_create` 函式創建設備文件,在 `cleanup_module` 調用期間使用 `device_destroy` 。
然而 `register_chrdev()` 將佔用與給定主要編號相關聯的一系列次要編號。減少字符設備註冊浪費的推薦方法是使用 `cdev` 接口。
新接口將字符設備註冊分為兩個不同的步驟。
首先,註冊一系列設備號碼,可以透過 `register_chrdev_region` 或 `alloc_chrdev_region` 完成。
```clike
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
```
如果知道設備的主要編號,則使用 `register_chrdev_region`,如果要分配動態分配的主要編號,則使用 `alloc_chrdev_region` 。
再來,初始化 `struct cdev` 以用於字符設備並將其與設備號碼連結。
```clike
struct cdev *my_dev = cdev_alloc();
my_cdev->ops = &my_fops;
```
但常見的用法是將 `struct cdev` 嵌入到自己的特定於設備的結構中。在這種情況下,使用 `cdev_init` 進行初始化。
```clike
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
```
完成初始化後將字符設備加入系統中
```clike!
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
```
### 6.4 Unregistering A Device
核心模組不能隨便被移除,因為一些進程可能正在使用它, `cat /proc/modules` 或 `sudo lsmod` 可以查看使用的進程數。如果不是零 rmmod 將失敗。
在 `cleanup_module` 中不需要檢查計數器,因為系統調用 `sys_delete_module` 會檢查,該系統調用定義在 `include/linux/syscalls.h` 中。不應直接使用這個計數器,但是在 `include/linux/module.h` 中有一些函數可以用來增加、減少和顯示這個計數器:
* try_module_get(THIS_MODULE):增加當前模塊的引用計數。
* module_put(THIS_MODULE):減少當前模塊的引用計數。
* module_refcount(THIS_MODULE):返回當前模塊的引用計數值。
### 7 The /proc File System
`/proc` 是用來向進程報告訊息,在一開始是用來存放進程訊息,也是名稱由來
文章以 `procfs3.c` 模組來示範示範如何使用 `/proc` file
首先在 `procfs1_init` 中用 `proc_create` 創建 helloworld 檔案,執行命令 `cat /proc/helloworld` 後會因為 `.proc_read = procfile_read, ` 去呼叫 `procfile_read` 來讀取,其中使用 `copy_to_user` 把字移到 buffer 後印出
### 7.1 The proc_ops Structure
`proc_ops` 在新版本中取代 `file_operations` 來進行對 `/proc` 的操作,可以提高效能
### 7.2 Read and Write a /proc File
資料寫入 `/proc` 需要把資料引入核心中,所以需要使用 `copy_from_user` 或 `get_user` 函數
`procfs2.c` 例子中用 `copy_from_user` 將資料寫入 buffer
### 8 sysfs: Interacting with your module
`hello-sysfs.c` 範例在 `static int __init mymodule_init(void)` 中建立 kobject
```clike
mymodule = kobject_create_and_add("mymodule", kernel_kobj);
if (!mymodule)
return -ENOMEM;
```
然後在裡面建立 myvariable 檔案
```clike
error = sysfs_create_file(mymodule, &myvariable_attribute.attr);
```
其中 `myvariable_attribute` 使用了 `__ATTR` 巨集去連結
```clike
static struct kobj_attribute myvariable_attribute =
__ATTR(myvariable, 0660, myvariable_show, (void *)myvariable_store);
```
這樣就能 show 或 store 裡面的資料
## ksort: 處理並行排序的 Linux 核心模組
### 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ 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): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
CPU family: 6
Model: 140
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 1
CPU max MHz: 4700.0000
CPU min MHz: 400.0000
BogoMIPS: 5606.40
```
### 開發紀錄
首先我修改 `user.c` 使它可以選擇所使用的排序演算法
> [commit 9a9d720](https://github.com/sysprog21/ksort/commit/9a9d720561f441f909518b83c0995e450428bf52)
```clike=
const char algorithm = '1'; // 1. Timsort 2.pdqsort 3.libsort
ssize_t written = write(fd, &algorithm, sizeof(algorithm));
if (written < 0) {
perror("Failed to write control command");
goto error;
}
```
其中 1, 2, 3分別代表 Timsort、 Pattern Defeating Quicksort (pdqsort) 及 Linux 核心 lib/sort.c,寫進裝置檔案 /dev/sort