筆記補在下面~
表示你的編譯器版本與kernel開發套件所用的編譯器版本不一致,建議安裝對應的版本避免後續開發出問題。
Linux 核心模組是一段可以根據需要動態載入和卸載到核心中的程式碼。這些模組可以在不需要重新啟動的情況下加強核心的功能。
如果沒有模組,目前的方法通常是 monolithic kernels (單核),需要將新功能直接整合到核心映像中。這種方法會導致需要更大的核心,並且當需要新功能時,需要重建核心和隨後重新啟動系統。
A Linux Kernel Module is precisely defined as a code segment capable of dynamic loading and unloading within the kernel as needed.
Linux kernel module 可以在不需要重啟系統的情況下加強 kernel 的功能。在沒有 module 的情況,一般方法都是以 monolithic kernel 為主,直接將功能整合至 kernel image,但這樣在加入新功能時都可能造成子系統需要重啟和 kernel rebuild。
在核心模組的 package 裡有提供命令來手動載入:
modprobe
: 載入指定模組與模組的依賴項depmod
: 顯示模組的相依性insmod
: 掛載模組lsmod
: 檢查有哪些模組已被載入cat /proc/modules
: 用來看存了哪些模組lsmod | grep <xxxx>
: 列出特定模組的資訊那有沒有 load 和一般的程式差在哪?
在讓核心模組跑起來之前的準備:
Modversioning:若一個核心模組為了某 kernel 編譯,則這個模組在開啟別的 kernel 時不會被 load,除非目前開啟的 kernel 是支援 CONFIG_MODVERSIONS
的,但後面又說要關掉才能使 module 正常運作?求翻譯
linux-headers-6.8.0-55-generic
Skipping BTF generation for /home/chiu/develop/kernel/hello-1/hello-1.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-55-generic'
journalct1
??
pr_info() -> print
obj-y
vs. obj-m
在 Makefile 中加入 PWD := $(CURDIR)
很重要
因為 sudo 出於安全考慮會重置大部分的環境變數,包括 PWD
如果沒有這行程式碼,當執行 sudo make
時,Makefile 可能找不到正確的資料夾
$ lsmod | grep hello
是在尋找名稱中包含 "hello" 的已掛載核心模組。
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
You are using: gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
journalctl --since "1 hour ago" | grep "Hello"
掛載模組後,可以輸入以上測試,應該要可以看到 Hello world 1
同理,卸載模組後應該要看到 Goodbye world 1
如果輸入
$ dmesg
顯示
dmesg: read kernel buffer failed: Operation not permitted
可以試試看
$ sudo sysctl kernel.dmesg_restrict=0
在早期版本的 Linux 核心中,必須使用 init_module
和 cleanup_module
函式。但現在可以透過使用 module_init
和 module_exit
巨集來自定義這些函式的名稱。
__init
和 __exit 巨集
__init
巨集的功能是在模組被寫進核心(built-in)時,使 init function 在完成後被丟棄並釋放其記憶體空間。
他對 loadable modules 沒有影響:
__exit
用於模組的 cleanup function。對於built-in drivers,函式會被完全省略(因為永遠不會被卸載);對於 loadable modules 則需要保留以便卸載時執行。
module_param
巨集來設定參數。module_param
巨集本身有三個參數:
module_param_array()
),必須有一個額外的 pointer to a count variable 作為第三個參數MODULE_PARM_DESC()
巨集為參數提供描述的文字sudo insmod hello-5.ko mystring="bebop" myintarray=-1
模組始於 init_module
函式或 module_init
指定的函式
模組終於 cleanup_module
函式或 module_exit
指定的函式
每個模組必須有 entry 以及 exit function.
核心模組只能使用核心提供的函式,不能使用 standard C library。
One point to keep in mind is the difference between library functions and system calls. Library functions are higher level, run completely in user space and provide a more convenient interface for the programmer to the functions that do the real work — system calls. System calls run in kernel mode on the user’s behalf and are provided by the kernel itself.
以 printf
函式為例,實際上會調用 write
system call。
Unix:
此設計是為了讓核心維持秩序,確保 users 不會任意訪問資源。
Typically, you use a library function in user mode. The library function calls one or more system calls, and these system calls execute on the library function’s behalf, but do so in supervisor mode since they are part of the kernel itself. Once the system call completes its task, it returns and execution gets transferred back to user mode.
撰寫核心時,即使是最小的模組也會與整個核心連結,所以 The best way to deal with this is to declare all your variables as static
。
如果不想將所有變數宣告為靜態,另一個選擇是宣告一個 symbol table 並將其 register 到核心。
The kernel has its own space of memory as well. Since a module is code which can be dynamically inserted and removed in the kernel (as opposed to a semi-autonomous object), it shares the kernel’s codespace rather than having its own. Therefore, if your module segfaults, the kernel segfaults.
file_operations
結構包含 pointers to functions defined by the driver that perform various operations on the device.
提供統一的介面讓核心與 driver 之間進行溝通。
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
file
結構file 是 kernal 層級的結構,絕不會出現在 user space 中
在驅動程式的 file_operations 函式中,file 結構被作為參數傳遞,通過這個結構來維護檔案相關的狀態和操作。
向 Linux 核心註冊裝置,使其能夠透過檔案系統被存取。為裝置分配一個 major number,作為識別。
建議使用 cdev interface
步驟一:register a range of device numbers
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);
The choice between two different functions depends on whether you know the major numbers for your device.
需要動態分配 major number 時使用 alloc_chrdev_region
步驟二:initialize the data structure struct cdev for our char device and associate it with the device numbers.
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
cdev_init
初始化 cdev 結構cdev_add
新增到系統中舊的 register_chrdev
會占用 major number 下的所有 minor number
$ sudo insmod kxo.ko
[sudo] password for linmarc:
insmod: ERROR: could not insert module kxo.ko: Key was rejected by service