Skip to content

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 đương device_initcall(fn) (các macro *_initcall được định nghĩa trong linux/init.h). Hàm fn sẽ được gọi trong quá trình khởi động kernel, theo thứ tự initcall level. module_exit không có tác dụng vì built-in module không bao giờ bị gỡ.
  • LKM: module_init(fn) tạo alias init_module cho fn, còn module_exit(fn) tạo alias cleanup_module. Đây là các entry point mà kernel gọi khi insmod/rmmod (thông qua syscall finit_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 cho printk trầ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