# 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