學習 Linux 核心模組與設備驅動,同樣從「Hello World」開始。本文示範三種在核心空間輸出訊息的方式:
printk() 寫入核心日誌緩衝區(dmesg 可見)/proc 虛擬檔案系統(用戶空間 cat /proc/hello_world 讀取)/dev 字元設備檔案(用戶空間 cat /dev/hello 讀取)並完整說明編譯環境建置、Makefile 撰寫、模組載入/卸載、udev 規則設定等實務流程。
$ sudo apt-get install build-essential linux-headers-$(uname -r)
$ sudo dnf install kernel-devel-$(uname -r) gcc make
$ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz
$ tar xf linux-6.6.tar.xz
$ cd linux-6.6
$ make defconfig
$ make modules_prepare
$ sudo ln -s $(pwd) /lib/modules/$(uname -r)/build
注意:模組必須對應正在運行的核心版本編譯,否則 insmod 會報錯 Invalid module format。
#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void) {
printk(KERN_INFO "Hello, world!\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Hello World via printk");
MODULE_VERSION("0.1");
obj-m := hello_printk.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
$ make
$ sudo insmod hello_printk.ko
$ dmesg -T | tail -2
[Oct 10 10:00:00] Hello, world!
[Oct 10 10:00:05] Goodbye, world!
$ sudo rmmod hello_printk
/proc 是虛擬檔案系統,讀取時由核心回呼函數產生資料,寫入時由核心處理資料。適合「單一數值/字串」的輸出入。
#include <linux/proc_fs.h>
static int hello_read_proc(char *buf, char **start, off_t off,
int count, int *eof, void *data) {
char *msg = "Hello, world!\n";
int len = strlen(msg);
if (off > 0) return 0; // 已讀完
if (count < len) return -EINVAL;
memcpy(buf, msg, len);
*eof = 1;
return len;
}
static int __init hello_init(void) {
if (!create_proc_read_entry("hello_world", 0, NULL, hello_read_proc, NULL))
return -ENOMEM;
return 0;
}
static void __exit hello_exit(void) {
remove_proc_entry("hello_world", NULL);
}
$ sudo insmod hello_proc.ko
$ cat /proc/hello_world
Hello, world!
現代驅動使用 miscdevice 簡化字元設備註冊,搭配 udev 自動建立 /dev/hello 節點。
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
static ssize_t hello_read(struct file *f, char __user *buf,
size_t count, loff_t *ppos) {
char *msg = "Hello, world!\n";
int len = strlen(msg);
if (*ppos != 0) return 0;
if (count < len) return -EINVAL;
if (copy_to_user(buf, msg, len)) return -EFAULT;
*ppos = len;
return len;
}
static const struct file_operations hello_fops = {
.owner = THIS_MODULE,
.read = hello_read,
};
static struct miscdevice hello_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello",
.fops = &hello_fops,
};
static int __init hello_init(void) {
return misc_register(&hello_dev);
}
static void __exit hello_exit(void) {
misc_deregister(&hello_dev);
}
建立 /etc/udev/rules.d/90-hello.rules:
KERNEL=="hello", SYMLINK+="hello_world", MODE="0444"
KERNEL=="hello":比對 /sys/class/misc/helloSYMLINK+="hello_world":額外建立 /dev/hello_world 連結MODE="0444":全域可讀$ sudo udevadm control --reload
$ sudo rmmod hello_dev && sudo insmod hello_dev.ko
$ ls -l /dev/hello*
cr--r--r-- 1 root root 10, 57 Oct 10 10:00 /dev/hello
lrwxrwxrwx 1 root root 11 Oct 10 10:00 /dev/hello_world -> hello
$ cat /dev/hello_world
Hello, world!
| 特性 | printk | /proc | /dev (misc) |
|---|---|---|---|
| 適用場景 | 除錯、核心事件紀錄 | 單一數值/狀態輸出、參數調整 | 資料流、硬體存取、權限控制 |
| 用戶空間介面 | dmesg / /var/log/kern.log | cat /proc/xxx | open/read/write/ioctl /dev/xxx |
| 雙向通訊 | 否 | 是(支援 write) | 是 |
| 權限控制 | 僅 root 可看 dmesg | 預設 root,可改權限 | 透過 udev 規則彈性設定 |
| 複雜度 | 極低 | 低 | 中(需 file_operations) |
file_operations:open、write、ioctl、mmap、poll。kobject/sysfs 暴露屬性給用戶空間。mutex、spinlock、wait_queue。platform_device、DT (Device Tree) 綁定硬體資源。Documentation/ 目錄。核心模組開發的鐵律:任何在核心空間跑的代碼,一行錯誤都可能 panic 整台機器。請在虛擬機或測試機上實驗,隨時備份重要資料。