02嵌入式 Linux 编写第一个内核驱动 (Hello World)
编写第一个内核驱动 (Hello World)
1. 核心概念:什么是内核模块 (.ko)?
- 裸机开发:你写好代码,编译成固件,烧进去。如果要修改一个 LED 的闪烁频率,你必须重新编译整个工程,重新烧录。
- Linux 内核:内核非常庞大。如果你想加一个新硬件驱动,难道要重新编译几百兆的内核吗?不需要!
- 内核模块 (Kernel Module):Linux 允许你把一部分代码编译成独立的
.ko文件。- 动态加载 (
insmod):系统运行时,把这个文件插进内核里,驱动立马生效。 - 动态卸载 (
rmmod):不用了就拔出来,释放内存。 - 这就像给电脑插拔 USB 鼠标一样方便,不需要重启系统。
- 动态加载 (
2. 准备工作
我们要创建一个独立的目录来存放驱动代码。不要混在内核源码或 U-Boot 源码里。
在 Ubuntu 终端执行:
cd ~/Embedded_study |
3. 第一步:编写驱动源码 (hello_drv.c)
使用 nano hello_drv.c 创建文件,输入以下代码。
(请仔细阅读注释,每一行都是新知识)
|
4. 第二步:编写 Makefile (构建规则)
内核模块的编译非常特殊,它必须依赖 Linux 内核的源码树。因为驱动里调用的 printk 等函数,定义都在内核源码里。
创建 Makefile 文件 (注意文件名 M 大写):nano Makefile
填入以下内容 (注意 Tab 缩进):
# 1. 指定内核源码路径 |
注意:
make命令下面那两行(make -C ...),必须以 Tab 键开头,不能用空格!
5. 第三步:编译驱动
在 driver_hello 目录下执行:
make |
预期结果:
如果不报错,你会看到生成了一个 hello_drv.ko 文件。
这就是我们的“驱动插件”!
6. 第四步:植入 SD 卡
我们要把这个 .ko 文件放到 SD 卡的 RootFS 分区里,这样启动后的 Linux 才能访问到它。
(这是对之前 SD 卡挂载操作的复习)
cd ~/Embedded_study |
7. 第五步:上机验证 (见证内核空间)
启动 QEMU:
cd ~/Embedded_study/uboot_study/u-boot |
U-Boot 启动命令 (还是老样子,如果没改 bootcmd 就手动输):
# 从 SD 卡加载 |
进入 Linux Shell 后:
检查驱动文件在不在
cd /root
ls应该能看到
hello_drv.ko加载驱动 (insmod)
insmod hello_drv.ko
- 现象:你应该立刻看到屏幕打印出
[Driver] Hello World! I am entering Kernel Space!。 - 如果没有直接打印,输入
dmesg查看内核日志缓冲区。
- 现象:你应该立刻看到屏幕打印出
查看已加载模块 (lsmod)
lsmod
- 现象:列表中应该有
hello_drv。
- 现象:列表中应该有
卸载驱动 (rmmod)
rmmod hello_drv
- 现象:屏幕打印出
[Driver] Goodbye! ...。
- 现象:屏幕打印出
📝 知识点总结 (为什么这么做?)
User Space vs Kernel Space:
- 之前的
lemon_app(printf) 运行在用户空间。它崩溃了只影响它自己。 - 现在的
hello_drv(printk) 运行在内核空间。它拥有最高权限。**如果在这个代码里写个死循环或者空指针访问,整个系统(包括所有 APP)都会立刻死机。**这就是为什么驱动开发难。
- 之前的
Makefile 的魔法:
- 为什么编译驱动需要内核源码?因为
.ko本质上是内核的一部分,它必须知道内核里的数据结构(如struct module)长什么样。如果内核源码变了,驱动必须重编。
- 为什么编译驱动需要内核源码?因为
printk:
- 为什么不用 printf?因为 printf 是 C 标准库 (
glibc/uclibc) 提供的,内核里没有标准库!内核只能用自己实现的printk。
- 为什么不用 printf?因为 printf 是 C 标准库 (
🚀 你的作业
请完成上述步骤,并截图:
- 编译成功:在 Ubuntu 下
ls -l hello_drv.ko的结果。 - 运行成功:在 QEMU 里执行
insmod和rmmod时的打印信息。
这一步跑通,你就掌握了**“如何向运行中的 Linux 植入代码”**的核心技能。这通常是驱动开发工程师 80% 的日常工作流。