Linux驱动 - 编译一个模块

系列 - Linux驱动初试

本次测试全程在树莓派(raspberryPi 4B,64位)上本地进行,没有交叉编译。

安装环境依赖工具

sudo apt update
sudo apt install git bc bison flex libssl-dev make

在后续Makefile中,将KDIR设置为KDIR := /lib/modules/$(shell uname -r)/build

见另文。


MOD_NAME := myFirstMod

obj-m += $(MOD_NAME).o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    make -C $(KDIR) M=$(PWD) modules
clean:
    make -C $(KDIR) M=$(PWD) clean


  1. 目标设置
    若编译成模块,添加到obj-m; 若编译进内核,则添加到obj-y
obj-m += $(MOD_NAME).o
  1. 指定访问内核的入口
KDIR := /lib/modules/$(shell uname -r)/build

/lib/modules/$(uname -r)目录下有两个链接sourcebuild,Linux规定外部模块必须通过此链接访问内核的构建环境和源码。

build指向本地kernel头文件和构建树(顶层Makefile),提供了完整的构建环境,包括:

  • .config
  • Module.symvers
  • include/generated/
  • scripts/
  • makefile
    注意
    如果要交叉编译或者从源码编译,要保证先编译一次内核(或者其中一部分),能提供上述文件才可以。

.config是内核配置,就是在SDK配置时的menuconfig中产出的,或者各类厂商提供的defconfig

Module.symvers是模块符号表,用于解析printk,gpio_request,i2c_transfer这些内核符号。

  1. 指定当前路径
PWD := $(shell pwd)
  1. 指定编译动作
all:
    make -C $(KDIR) M=$(PWD) modules
clean:
    make -C $(KDIR) M=$(PWD) clean
  • -C表示改变运行目录到内核源码,因为构建模块需要内核的kbuild系统。等价于先cd到KDIR然后执行make命令
  • M=指定编译一个外部的模块的目录。
  • modules 表示目标是编译模块,内核的Makefile中有预定义module目标对象。对应的目标会用$(M)作为参数执行一系列编译动作: 1.进入此目录,2.查找该目录中obj-m变量,3.编译这些源文件
#include <linux/module.h>
#include <linux/init.h>

static int __init virdev_init(void)
{
    printk(KERN_INFO "virdev: 驱动已加载\n");
    return 0;
}

static void __exit virdev_exit(void)
{
    printk(KERN_INFO "virdev: 驱动已卸载\n");
}

module_init(virdev_init);    //设置模块的初始化回调
module_exit(virdev_exit);    //设置模块的卸载回调
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KurehaTian");
MODULE_DESCRIPTION("Description");
MODULE_VERSION("1.0");


  1. 头文件引用
#include <linux/module.h>
#include <linux/init.h>

一个最基本的模块需要的头文件:

  • linux/module.h提供了一系列模块初始化所需的标志。
  • linux/init.h 为模块和内核初始化阶段提供宏和标记,例如指定init函数放在哪个端,标记哪些数据随初始化创建和释放,等等
  1. 初始化回调
static int __init virdev_init(void)
{
    printk(KERN_INFO "virdev: 驱动已加载\n");
    return 0;
}
  1. 卸载回调
static void __exit virdev_exit(void)
{
    printk(KERN_INFO "virdev: 驱动已卸载\n");
}
  1. 设置模块回调
module_init(virdev_init);    //设置模块的初始化回调
module_exit(virdev_exit);    //设置模块的卸载回调
  1. 设置模块元信息

这些信息会在modinfo查询出来。

注意
由于GPL协议的传染性,GPL协议的模块只能被GPL协议模块依赖。

MODULE_LICENSE("GPL");
MODULE_AUTHOR("KurehaTian");
MODULE_DESCRIPTION("Description");
MODULE_VERSION("1.0");

Makefile和上面的代码(myFirstMod.c)放在同一目录中,在此执行make命令即可构建模块,产出myFirstMod.ko
加载模块:

$sudo insmod myFirstMod.ko

卸载模块:

$sudo rmmod myFirstMod.ko

查看模块信息:

$sudo modinfo

输出结果如下:

filename:       /home/kurehatian/00_loadable_module/myFirstMod.ko
version:        1.0
description:    Description
author:         KurehaTian
license:        GPL
srcversion:     6B75477C4A72524F4813A30
depends:        
name:           myFirstMod
vermagic:       6.12.47+rpt-rpi-v8 SMP preempt mod_unload modversions aarch64

使用dmesg指令可查看内核日志,加载卸载模块时打印的内容。

[94574.004431] virdev: 驱动已加载
[94675.205580] virdev: 驱动已卸载