# 2024q1 Homework6 (integration) contributed by < `mesohandsome` > ## 開發環境 ``` $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 12 On-line CPU(s) list: 0-11 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 151 Model name: 12th Gen Intel(R) Core(TM) i5-12400 Stepping: 2 CPU MHz: 2500.000 CPU max MHz: 4400.0000 CPU min MHz: 800.0000 BogoMIPS: 4992.00 Virtualization: VT-x L1d cache: 288 KiB L1i cache: 192 KiB L2 cache: 7.5 MiB L3 cache: 18 MiB NUMA node0 CPU(s): 0-11 ``` ## 閱讀〈Linux 核心模組運作原理〉 編譯用命令 ```bash # make make -C /lib/modules/`uname -r`/build/ M=`pwd` modules # make clean make -C /lib/modules/`uname -r`/build/ M=`pwd` clean ``` 什麼是 Character / Block Device Driver? > A character device typically transfers data to and from a user application — they behave like pipes or serial ports, instantly reading or writing the byte data in a character-by-character stream. They provide the framework for many typical drivers, such as those that are required for interfacing to serial communications, video capture, and audio devices. > > The main alternative to a character device is a block device. Block devices behave in a similar fashion to regular files, allowing a buffered array of cached data to be viewed or manipulated with operations such as reads, writes, and seeks. Both device types can be accessed through device files that are attached to the file system tree. ### `insmod` 使用 `man` 在 Manual page 中可以看到關於 `insmod` 的簡述。 > insmod - Simple program to insert a module into the Linux Kernel 在撰寫核心模組時,會使用巨集 `module_init(init_function_name)`,讓核心找到寫好的 init 函式,以 `int init_module(void) __attribute__((alias(#initfn)));` 將`init_module` 取一個別名,也就是我們的 init 函式名稱,因此呼叫 `init_module` 等同於呼叫自己寫的函式。 而後透過 `sudo strace insmod hello.ko` 觀察發現,一開始 `insmod` 會開啟 `fibdrv.ko`,並得到該檔案的 fd,最後會執行 `finit_module(3, "", 0)`,當中會透過 `load_module()` 呼叫我們的 init 函式並初始化。 ### 使用 List API 找到核心模組的符號 在 [/kernel/module.c](https://elixir.bootlin.com/linux/v4.18/source/kernel/module.c) 中: ```c static struct module *mod_find(unsigned long addr) { struct module *mod; list_for_each_entry_rcu(mod, &modules, list) { if (within_module(addr, mod)) return mod; } return NULL; } ``` ### MODULE_LICENSE 授權條款影響 如果模組使用了 `MODULE_LICENSE("GPL")` 或類似的 GPL 授權,這樣的模組可以無限制的訪問核心中所有標記為 EXPORT_SYMBOL_GPL 的符號。 使用 `Proprietary` 的 non-GPL 的話,就不能用標記為 EXPORT_SYMBOL_GPL 的符號了。 ### strace ``` execve("/usr/sbin/insmod", ["insmod", "hello.ko"], 0x7ffdebbff978 /* 25 vars */) = 0 brk(NULL) = 0x55f7c552a000 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffdcba62cf0) = -1 EINVAL (Invalid argument) 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 fstat(3, {st_mode=S_IFREG|0644, st_size=75375, ...}) = 0 mmap(NULL, 75375, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4410ae2000 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@B\0\0\0\0\0\0"..., 832) = 832 ... openat(AT_FDCWD, "/home/scream/linux2024/workspace/hello/hello.ko", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1", 6) = 6 lseek(3, 0, SEEK_SET) = 0 fstat(3, {st_mode=S_IFREG|0664, st_size=215504, ...}) = 0 mmap(NULL, 215504, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f44104e2000 finit_module(3, "", 0) = 0 munmap(0x7f44104e2000, 215504) = 0 close(3) = 0 exit_group(0) = ? ``` `open()`, `read()`, `write()`, `mmap()` ## 閱讀《The Linux Kernel Module Programming Guide》(LKMPG) ### 疑惑 kernel 會自動分配 major number 給 device driver,那 device driver 要怎麼用 minor number 判斷目前的裝置為何?