---
# System prepended metadata

title: 2024q1 Homework6 (integration)

---

# 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
