About Kernel Module¶
Kernel Module Information¶
Định nghĩa¶
Kernel Module là một đoạn mã có thể được nạp vào hoặc gỡ khỏi Kernel theo yêu cầu. Kernel module giúp mở rộng chức năng cho kernel mà không cần phải khởi động lại hệ thống. Device Driver là một loại kernel module. Nếu không có kernel module, mỗi khi cần thêm một chức năng vào kernel, người phát triển sẽ phải build lại kernel image rồi khởi động lại hệ thống.
Có hai dạng Kernel module: built-in và loadable module. Trong đó built-in module được tích hợp vào Kernel Image và được khởi tạo trong quá trình khởi động kernel. Ngược lại, loadable module có thể được người dùng nạp vào hệ thống sau khi kernel đã khởi động thành công.
Việc một module là built-in hay loadable được quyết định bởi Kconfig
(kiểu tristate): y là built-in, m là loadable module, n là không
build.
Loadable Kernel Module (LKM) được nạp vào hệ thống bằng các câu lệnh
như modprobe hoặc insmod.
module_init & module_exit¶
Kernel module có thể được hiểu là một chương trình chạy trong Kernel Space, nhưng không giống như chương trình bình thường, kernel module không bắt đầu bằng hàm "main". Module cũng không thể dùng thư viện C chuẩn (libc), chỉ có thể gọi các hàm kernel cung cấp.
Macro module_init() và module_exit() (định nghĩa trong
linux/module.h) được sử dụng để đăng ký hàm khởi tạo và hàm hủy cho
kernel module. Nội dung của chúng khác nhau tùy theo module là built-in
hay LKM:
- Built-in:
module_init(fn)được quy về__initcall(fn), tương đươngdevice_initcall(fn)(các macro*_initcallđược định nghĩa tronglinux/init.h). Hàmfnsẽ được gọi trong quá trình khởi động kernel, theo thứ tự initcall level.module_exitkhông có tác dụng vì built-in module không bao giờ bị gỡ. - LKM:
module_init(fn)tạo aliasinit_modulechofn, cònmodule_exit(fn)tạo aliascleanup_module. Đây là các entry point mà kernel gọi khiinsmod/rmmod(thông qua syscallfinit_module/delete_module).
Hàm init trả về 0 nếu thành công; trả về giá trị âm (ví dụ -ENOMEM)
thì module sẽ không được nạp. Hàm init/exit nên gắn __init/__exit để
kernel giải phóng bộ nhớ của hàm init sau khi chạy xong.
Ví dụ: hello world¶
hello.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
pr_info("hello: module loaded\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("hello: module unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple hello world kernel module");
Makefile:
obj-m += hello.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Note
Dòng lệnh trong Makefile phải thụt bằng ký tự Tab. Cần cài
kernel headers tương ứng với kernel đang chạy (ví dụ trên Debian/Ubuntu:
apt install linux-headers-$(uname -r)). Khi cross-compile, truyền
thêm ARCH= và CROSS_COMPILE=, và đặt KDIR trỏ tới source/build tree
của kernel đích.
Build và thử nghiệm:
make
sudo insmod hello.ko
lsmod | grep hello
dmesg | tail
modinfo hello.ko
sudo rmmod hello
dmesg | tail
MODULE_LICENSE là bắt buộc nên khai báo đúng. Nếu module không có
license tương thích GPL, kernel sẽ bị đánh dấu tainted và module không
dùng được các symbol EXPORT_SYMBOL_GPL.
Compile a kernel module¶
Để build một kernel module, Linux Kernel sử dụng một hệ thống gọi là
kbuild. Với module nằm ngoài source tree (out-of-tree) như ví dụ trên,
obj-m liệt kê các module cần build, và make -C <kernel build dir>
M=<module dir> modules sẽ gọi kbuild của kernel để build chúng. Nếu
module gồm nhiều file source, dùng <tên>-objs := a.o b.o.
Chi tiết xem trong Kernel Documentation:
Documentation/kbuild/modules.rst.
In log trong kernel¶
Phương pháp debug cơ bản và phổ biến nhất khi phát triển một chương
trình là in log ra màn hình hoặc file. Kernel module sử dụng printk
thay vì printf như khi phát triển ứng dụng User space. Log được ghi
vào kernel ring buffer và đọc bằng lệnh dmesg.
Các hàm in log thường dùng trong kernel module:
- printk(): cách dùng tương tự
printf(), có thể kèm mức log nhưKERN_INFO,KERN_ERR. - pr_*: họ hàm in log theo mức (
pr_info,pr_err,pr_debug...). Nên dùng thay choprintktrần. - dev_*: họ hàm in log gắn với một
struct device, tự thêm tên device vào log.
Khi cần bật/tắt các log pr_debug/dev_dbg lúc runtime, xem
Dynamic debug.
Nạp và gỡ LKM¶
Để nạp LKM vào hệ thống có thể dùng insmod, gỡ bằng rmmod, và liệt
kê bằng lsmod. Ngoài ra có thể dùng modprobe. Khác với insmod
(nhận đường dẫn tới file .ko), modprobe tìm module theo tên trong
/lib/modules/$(uname -r)/ và tự nạp cả các module phụ thuộc dựa trên
modules.dep (được tạo bởi depmod). Cấu hình cho modprobe nằm trong
/etc/modprobe.d/. Xem thêm
modprobe.
Khi nạp module có thể truyền thêm parameters, ví dụ:
sudo insmod hello.ko name=value. Module khai báo parameter bằng macro
module_param(); giá trị hiện tại của parameter có thể xem trong
/sys/module/<tên module>/parameters/.
lsmod đọc thông tin từ /proc/modules; người dùng cũng có thể đọc
trực tiếp file này để biết thông tin của các module đang chạy. Khi một
module được nạp, thông tin về module cũng xuất hiện trong sysfs tại
/sys/module/<tên module>/.
Tham khảo¶
- The Linux Kernel Module Programming Guide (bản cập nhật cho kernel 5.x/6.x).
- Writing a Linux Kernel Module - Derek Molloy.
- Kernel module - ArchWiki.
- Dynamic debug howto.