每個 Linux 驅動開發者的第一課,都是編寫一個最簡單的核心模組——「Hello World」。本文不僅給出標準範例,更詳細拆解模組生命週期、Makefile 編譯機制、核心符號表、模組參數與授權聲明等核心概念,助你建立扎實的驅動開發基石。
#include
#include
MODULE_LICENSE("Dual BSD/GPL"); // 授權聲明,影響核心符號可見性
static int __init hello_init(void) {
printk(KERN_ALERT "Hello, Kernel World!\n");
return 0; // 非 0 表示初始化失敗,模組不會被載入
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "Goodbye, Kernel World!\n");
}
module_init(hello_init); // 模組載入入口
module_exit(hello_exit); // 模組卸載入口
__init:標記初始化函式,核心載入模組後會釋放該函式佔用的記憶體(.init.text 區段)。__exit:標記清理函式,若核心配置 CONFIG_MODULE_UNLOAD 未開啟,該函式會被編譯器優化移除。module_init/exit:註冊模組入口/出口點,展開為對應區段的函式指標。驅動模組編譯需依賴核心原始碼樹的 Kbuild 系統。以下為 ARM 交叉編譯環境範例:
# 核心原始碼樹路徑(已配置並編譯過的)
KERNELDIR := /home/user/linux-5.10
# 當前模組源碼目錄
PWD := $(shell pwd)
# 交叉編譯工具鏈前綴
CROSS_COMPILE := arm-linux-gnueabihf-
CC := $(CROSS_COMPILE)gcc
# 目標模組名稱(hello.c -> hello.ko)
obj-m := hello.o
# 若模組由多個 .c 組成:
# obj-m := mydrv.o
# mydrv-objs := file1.o file2.o
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
install:
cp hello.ko /path/to/rootfs/lib/modules/$(shell uname -r)/extra/
depmod -a
KERNELDIR:指向核心源碼樹頂層,內含 Makefile 與 scripts/。M=$(PWD):告知核心建置系統模組源碼位置(外部模組模式)。obj-m:指定生成可載入模組(.ko),對應 .c 檔名。CROSS_COMPILE:交叉編譯工具鏈前綴,make 會自動拼接 gcc, ld, strip 等工具。# 編譯
$ make
# 輸出 hello.ko
# 部署至目標板(NFS/SCP/USB)
$ scp hello.ko root@target:/lib/modules/$(uname -r)/extra/
# 目標板載入
root@target:~# insmod hello.ko
Hello, Kernel World!
# 查看已載入模組
root@target:~# lsmod | grep hello
hello 16384 0
# 卸載模組
root@target:~# rmmod hello
Goodbye, Kernel World!
init 申請的資源(記憶體、IRQ、I/O 區域、中斷),必須在 exit 中完整釋放,否則資源洩漏需重開機恢復。__kmalloc, __init_waitqueue_head 通常為內部實現,介面不穩定,優先使用公開 API。kernel_fpu_begin/end),極度不建議在核心使用浮點。MODULE_LICENSE("GPL") 才能使用 EXPORT_SYMBOL_GPL 導出的符號(如 mutex_lock)。EXPORT_SYMBOL(name) 或 EXPORT_SYMBOL_GPL(name),必須置於全域變數區。module_param(name, type, perm) 允許載入時傳參(insmod mod.ko param=value)。<linux/init.h> (init/exit 宏) + <linux/module.h> (核心模組核心定義)。掌握模組編譯、載入、參數傳遞、日誌輸出、資源管理後,下一步即是註冊字元裝置(cdev_add)、實現 file_operations(open/read/write/ioctl)、申請 I/O 記憶體與中斷號,逐步構建完整的字元裝置驅動。每一行核心代碼都在邊界上行走,謹慎、測試、再測試,是驅動開發者的基本功。