4. 第一个驱动:Hello World 模块
终于到了动手写代码的时候了。说实话,很多初学者在搭建好环境后,反而不知道从哪里下手。我个人的建议是——先写一个最简单的模块,跑通加载、卸载、看日志的完整流程。这个 Hello World 虽然简单,但它能帮你验证整个工具链是否正常。
4.1 模块代码:hello.c
先看代码,再解释。你创建一个 hello.c,内容如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello World from Zynq!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, Zynq World!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World module for Zynq");
这里有几个关键点,我当年第一次写的时候也踩过坑:
- module_init / module_exit:这是内核模块的入口和出口宏。加载模块时调用
hello_init,卸载时调用hello_exit。 - printk:内核空间的打印函数,不是 printf。它把信息输出到内核日志缓冲区,而不是终端。
- KERN_INFO:日志级别宏,表示这是一条普通信息。还有 KERN_ERR、KERN_DEBUG 等。
- __init / __exit:这两个标记告诉内核:初始化函数在运行后可以释放内存,退出函数在模块卸载时使用。
小技巧: 我习惯在 printk 里加上模块名前缀,比如
"hello: ",这样在内核日志里一眼就能找到自己的输出。
4.2 Makefile:构建模块
模块不能像普通程序那样用 gcc 直接编译。你需要内核源码树和交叉编译工具链。Makefile 如下:
obj-m := hello.o
KDIR := /path/to/your/linux-xlnx
CROSS_COMPILE := arm-linux-gnueabihf-
CC := $(CROSS_COMPILE)gcc
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
解释一下:
- obj-m := hello.o:告诉内核构建系统,把 hello.c 编译成一个可加载模块。
- KDIR:指向你之前编译好的内核源码树路径。我一般用绝对路径,避免麻烦。
- CROSS_COMPILE:交叉编译工具链前缀。Zynq 是 ARM 架构,所以用 arm-linux-gnueabihf-。
- -C $(KDIR):切换到内核源码目录执行 make。
- M=$(PWD):指定模块源码在当前目录。
注意: 我曾经因为 KDIR 指向了错误的内核版本,导致编译出来的模块加载时直接报
Invalid module format。一定要确保 KDIR 和你板子上运行的内核是同一个版本。
4.3 编译与加载
在终端执行:
make
如果一切正常,你会看到 hello.ko 生成。把这个文件拷贝到 Zynq 板子上,然后:
insmod hello.ko
没有输出?很正常。模块加载成功不会在终端打印东西。你需要查看内核日志:
dmesg | tail
你应该能看到:
[ 12.345678] Hello World from Zynq!
卸载模块:
rmmod hello
dmesg | tail
输出:
[ 15.123456] Goodbye, Zynq World!
核心流程: 编写代码 → Makefile 编译 → insmod 加载 → dmesg 查看日志 → rmmod 卸载。这个循环贯穿整个驱动开发过程。
4.4 查看内核日志的几种方式
| 命令/工具 | 说明 | 常用场景 |
|---|---|---|
dmesg |
显示内核环形缓冲区所有日志 | 查看最近日志,配合 tail 使用 |
dmesg -w |
实时等待新日志(类似 tail -f) | 调试时实时观察模块输出 |
cat /var/log/kern.log |
读取持久化的内核日志文件 | 查看历史日志,系统重启后仍可查 |
journalctl -k |
systemd 系统下的内核日志查看 | 现代发行版,支持过滤和搜索 |
我个人最常用的是 dmesg -w。开一个终端窗口跑着它,然后另一个窗口加载模块,日志实时刷新,非常直观。
4.5 模块加载与卸载的完整流程
下面这张图展示了从编译到卸载的完整链路:
4.6 常见问题与避坑
- insmod 报错 "Invalid module format":内核版本不匹配。检查 KDIR 是否指向正确的内核源码。
- insmod 报错 "Operation not permitted":权限问题。用 root 执行,或者加 sudo。
- dmesg 看不到输出:可能是日志级别被过滤了。试试
dmesg -n 7降低控制台日志级别。 - rmmod 报错 "Module is in use":有其他进程或模块依赖它。用
lsmod查看引用计数。
曾经踩过的坑: 我有一回在 Makefile 里忘了加
CROSS_COMPILE,结果编译出来的是 x86 的 .ko 文件。放到 Zynq 板子上加载,直接报 wrong architecture。所以编译前一定要确认工具链。
4.7 小结
Hello World 模块虽然简单,但它验证了整个开发链路的正确性:
- 交叉编译工具链是否正常
- 内核源码树是否匹配
- 模块加载/卸载机制是否工作
- 内核日志输出是否可见
这个流程你以后会反复使用。每写一个新模块,都是先跑通这个循环,再往里加功能。嗯,基础打牢了,后面的事情就顺了。