云计算百科
云计算领域专业知识百科平台

【Linux 驱动小白教程】二、驱动Makefile编写 + 模块操作全套命令实操

接上一篇:从零看懂驱动框架 + 设备分类 + 最简模板
上一篇我们拿到了最简内核驱动 .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逐行小白讲解

  • KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    • ?=:如果变量没有被外部赋值,才赋值;交叉编译的时候可以外部传入指定内核路径覆盖。
    • $(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!

  • make -C $(KERNELDIR) M=$(PWD) modules
    • -C $(KERNELDIR):切换工作目录到内核源码目录,执行内核顶层Makefile
    • M=$(PWD):告诉内核编译系统,外部模块源码在当前PWD目录
    • modules:内核Makefile目标,专门编译外部内核模块
  • clean:清理编译出来的.ko、.o、.mod.c等中间文件。
  • ✅编译操作

    终端进入源码目录执行

    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
  • 原因:编译用的内核头文件版本 和 运行内核版本不一致。虚拟机不要随便升级内核;开发板编译驱动必须使用和烧录内核一模一样的源码。

  • MODULE_LICENSE("GPL")忘记写
  • dmesg里面出现tainted kernel内核污染警告,部分内核函数调用直接失败。必须写GPL。

  • make报错找不到内核头文件
    Ubuntu安装内核头文件:
  • sudo apt install linux-headers-$(uname -r)

  • rmmod提示module is in use
  • 模块正在被使用,有应用程序打开设备,要先关闭应用再卸载。

  • modprobe找不到模块
  • ko没有放到系统模块目录,没有执行depmod,改用insmod指定ko完整路径临时测试。

    五、本篇知识点总结

  • 驱动不能gcc直接编译,依靠内核Makefile编译体系;obj‑m += xxx.o编译生成ko模块。
  • Makefile关键参数:‑C切换内核目录,M=指定外部模块源码目录。
  • 命令速记:
    |命令|功能|
    | —- | —- |
    |lsmod|列出已加载内核模块|
    |insmod xxx.ko|直接加载ko,不处理依赖|
    |modprobe xxx|智能加载,自动处理依赖|
    |rmmod xxx|卸载模块,不加ko后缀|
    |modinfo xxx.ko|查看模块元信息|
    |dmesg|查看printk内核打印|
    |make clean|清理驱动编译产物|
  • 本篇为专栏入门免费章节,完整专栏包含三大基础驱动设备(面向入门)、内核机制原理讲解(面向面试)、多套驱动框架配套源码(面向工作)等内容,后续进阶内容仅专栏订阅用户可见,点击专栏主页订阅解锁全部教程。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux 驱动小白教程】二、驱动Makefile编写 + 模块操作全套命令实操
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!