接上一篇:从零看懂驱动框架 + 设备分类 + 最简模板
上一篇我们拿到了最简内核驱动 .c 源码,但是内核驱动不能用gcc直接编译,必须借助内核源码树进行编译。本篇讲解驱动专用Makefile语法、编译步骤、insmod/rmmod/lsmod/modinfo/modprobe全套命令,新手照着敲即可跑通实验。
一、为什么驱动不能直接 gcc xxx.c?
普通应用程序:链接glibc用户态库,gcc直接编译。
内核驱动:运行在内核空间,要调用内核导出的函数、头文件,必须使用内核本身的编译体系,不能链接用户态C库。
重点:编译驱动必须要有对应版本的内核源码/内核头文件,内核版本必须和运行开发板/虚拟机的内核版本严格匹配,否则会出现版本魔数校验失败,模块加载报错。
二、驱动专用Makefile完整模板
新建文件名字就叫 Makefile,注意大小写M大写,不要后缀,和驱动c文件放在同一个目录。
假设驱动源码文件名叫 mydrv.c
# 判断是否传入KERNELDIR,没有就使用本机内核路径
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
# 当前源码所在目录
PWD := $(shell pwd)
# 要编译生成的模块名,对应 mydrv.c → mydrv.ko
obj-m += mydrv.o
# make的默认目标
all:
make -C $(KERNELDIR) M=$(PWD) modules
# make clean 清理编译产物
clean:
make -C $(KERNELDIR) M=$(PWD) clean
Makefile逐行小白讲解
- ?=:如果变量没有被外部赋值,才赋值;交叉编译的时候可以外部传入指定内核路径覆盖。
- $(shell uname -r):shell命令,获取当前正在运行的内核版本号。
- /lib/modules/xxx/build:Ubuntu虚拟机本地编译,指向内核头文件软链接;做开发板交叉编译时这里改成开发板内核源码绝对路径。
PWD := $(shell pwd)
:= 立即赋值,获取当前源码文件夹路径。
obj-m += mydrv.o
核心!驱动Makefile最关键一行
obj‑m:表示把这个文件编译成可动态加载的内核模块(.ko)
obj‑y:编译进内核镜像zImage,开机就内置,不能rmmod卸载。
mydrv.o 对应源码 mydrv.c,会自动生成 mydrv.ko,不要写ko!
- -C $(KERNELDIR):切换工作目录到内核源码目录,执行内核顶层Makefile
- M=$(PWD):告诉内核编译系统,外部模块源码在当前PWD目录
- modules:内核Makefile目标,专门编译外部内核模块
✅编译操作
终端进入源码目录执行
make
编译成功后目录生成:mydrv.ko 就是我们的内核模块文件。
⚠️交叉编译小提示(开发板场景)
# 示例,传入交叉编译器+开发板内核路径
make KERNELDIR=/home/xxx/linux-5.4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
编译产物说明:
- *.ko:真正内核模块,拷贝到开发板使用
- *.mod.c:内核自动生成中间文件
- *.o:编译目标文件
- modules.order、Module.symvers:模块符号表,多个模块依赖时需要
三、内核模块全套操作命令(必考实操)
注意:大部分模块加载卸载操作需要root权限,Ubuntu加sudo;开发板一般默认root。
1. lsmod 列出当前已经加载的内核模块
lsmod
作用:查看系统哪些ko已经载入内核,第一列模块名,第二列占用内存,第三列被哪些其他模块依赖。
2. insmod 手动加载模块
sudo insmod mydrv.ko
直接指定ko文件路径加载。
缺点:不会自动解决模块依赖,如果本模块依赖别的模块,insmod会报错。
查看打印输出,驱动里面printk打印,用dmesg看:
dmesg
# 实时追踪打印
dmesg -w
printk不会直接输出终端,存在内核环形缓冲区,要用dmesg查看日志。
3. modprobe 智能加载模块
sudo modprobe mydrv
- 不需要写.ko后缀,只写模块名字
- 会自动查找模块路径,自动加载依赖的其他模块
前提条件:ko需要拷贝到系统模块目录 /lib/modules/$(uname -r)/,执行depmod更新模块依赖数据库,modprobe才能识别。
简单理解:insmod简单粗暴;modprobe智能处理依赖。
4. rmmod 卸载模块
sudo rmmod mydrv
只写模块名字,不要加.ko后缀,执行驱动的__exit退出函数。
注意:模块正在被程序占用的时候rmmod会失败。
5. modinfo 查看模块信息
modinfo mydrv.ko
读取ko内部信息:作者、协议、描述、模块参数、版本,就是我们代码里MODULE_LICENSE、MODULE_AUTHOR这些宏生成的信息。
6. depmod 更新模块依赖数据库(配合modprobe使用)
sudo depmod
把新拷贝的ko注册进系统模块列表,modprobe才能找到这个模块。
📝完整实操流程示例(虚拟机)
#1 编译
make
#2 加载模块
sudo insmod mydrv.ko
#3 看内核打印
dmesg
#4 查看已加载模块
lsmod
#5 查看模块信息
modinfo mydrv.ko
#6 卸载模块
sudo rmmod mydrv
#7 清理编译产物
make clean
四、常见踩坑点(小白高频报错)
version magic does not match
原因:编译用的内核头文件版本 和 运行内核版本不一致。虚拟机不要随便升级内核;开发板编译驱动必须使用和烧录内核一模一样的源码。
dmesg里面出现tainted kernel内核污染警告,部分内核函数调用直接失败。必须写GPL。
Ubuntu安装内核头文件:
sudo apt install linux-headers-$(uname -r)
模块正在被使用,有应用程序打开设备,要先关闭应用再卸载。
ko没有放到系统模块目录,没有执行depmod,改用insmod指定ko完整路径临时测试。
五、本篇知识点总结
|命令|功能|
| —- | —- |
|lsmod|列出已加载内核模块|
|insmod xxx.ko|直接加载ko,不处理依赖|
|modprobe xxx|智能加载,自动处理依赖|
|rmmod xxx|卸载模块,不加ko后缀|
|modinfo xxx.ko|查看模块元信息|
|dmesg|查看printk内核打印|
|make clean|清理驱动编译产物|
本篇为专栏入门免费章节,完整专栏包含三大基础驱动设备(面向入门)、内核机制原理讲解(面向面试)、多套驱动框架配套源码(面向工作)等内容,后续进阶内容仅专栏订阅用户可见,点击专栏主页订阅解锁全部教程。
网硕互联帮助中心


评论前必须登录!
注册