Linux 核心模塊允許動態載入/卸載程式碼,避免單一巨大核心映像。本文從最簡單的 Hello World 切入,詳細解析 module_init/exit、MODULE_LICENSE 等巨集用法,並深入探討 printk 8 級優先權、控制台輸出門檻與動態除錯開關技巧,為驅動開發打穩基礎。
/* hello.c */
#include
#include
static int __init hello_init(void) {
printk("Hello, kernel module!\n");
return 0;
}
static void __exit hello_exit(void) {
printk("Goodbye, kernel module!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_VERSION("1.0");
# 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
# 編譯載入
make
insmod hello.ko
dmesg | tail # 觀看 printk 輸出
rmmod hello
| 屬性 | 初始化函數 | 清理函數 |
|---|---|---|
| 巨集 | module_init(fn) | module_exit(fn) |
| 調用時機 | insmod 載入時 | rmmod 卸載時 |
| 回傳值 | 0 成功、負錯誤碼失敗(載入中止) | void |
static | 建議加,限制連結可見性 | 建議加 |
__init / __exit | 載入後釋放該段記憶體(節省 RAM) | 內建核心時自動捨棄 |
| 必要性 | 必須 | 必須(無清理函數無法載入) |
MODULE_LICENSE("GPL"); // 必填,否則標記 taint
MODULE_AUTHOR("Name");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Brief");
MODULE_ALIAS("alias_name"); // modprobe 別名
| 巨集 | 數值 | 含義 |
|---|---|---|
| KERN_EMERG | 0 | 系統不可用 |
| KERN_ALERT | 1 | 需立即處理 |
| KERN_CRIT | 2 | 嚴重錯誤 |
| KERN_ERR | 3 | 錯誤 |
| KERN_WARNING | 4 | 警告(printk 預設) |
| KERN_NOTICE | 5 | 正常但重要 |
| KERN_INFO | 6 | 資訊 |
| KERN_DEBUG | 7 | 除錯訊息 |
只有優先權數值 ≤ console_loglevel 的訊息才會輸出到實體控制台。
預設值查看/修改:
cat /proc/sys/kernel/printk
# 輸出四值:console_loglevel default_message_loglevel minimum_console_loglevel default_console_loglevel
# 典型預設:7 4 1 7 → console_loglevel=7 (KERN_DEBUG 也會顯示)
# 臨時調整(需 root)
echo 8 > /proc/sys/kernel/printk # 讓所有等級含 KERN_DEBUG 皆顯示
printk("no level\n"); // 預設 KERN_WARNING (4) → 會顯示
printk(KERN_DEBUG "debug\n"); // 等級 7 → 預設 console=7 會顯示
printk("<7>debug explicit\n"); // 同 KERN_DEBUG
若 console_loglevel=6,則 KERN_DEBUG (7) 不會在控制台顯示,但永遠會寫入 /var/log/messages(由 klogd/rsyslog 收集)。
#define DEBUG_ENABLE 1
#if DEBUG_ENABLE
#define P_DEBUG(fmt, ...) printk(KERN_INFO "[%s] " fmt, __func__, ##__VA_ARGS__)
#else
#define P_DEBUG(fmt, ...) printk(KERN_DEBUG "[%s] " fmt, __func__, ##__VA_ARGS__)
#endif
static int __init test_init(void) {
P_DEBUG("Debug message %d\n", 123); // 依 DEBUG_ENABLE 決定是否進控制台
return 0;
}
編譯時切換 DEBUG_ENABLE 即可控制除錯訊息是否輸出到控制台,無需修改大量 printk 呼叫。
tainted,部份符號(如 EXPORT_SYMBOL_GPL)不可用。insmod 失敗,dmesg 顯示錯誤碼。%f 浮點,整數用 %d/%ld,指標用 %p(會自動哈希保護位址)。