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
交叉编译/使用源码
见另文。
Makefile编写
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
- 目标设置
若编译成模块,添加到obj-m; 若编译进内核,则添加到obj-y。
obj-m += $(MOD_NAME).o
- 指定访问内核的入口
KDIR := /lib/modules/$(shell uname -r)/build
/lib/modules/$(uname -r)目录下有两个链接source和build,Linux规定外部模块必须通过此链接访问内核的构建环境和源码。
build指向本地kernel头文件和构建树(顶层Makefile),提供了完整的构建环境,包括:
.configModule.symversinclude/generated/scripts/makefile注意如果要交叉编译或者从源码编译,要保证先编译一次内核(或者其中一部分),能提供上述文件才可以。
.config是内核配置,就是在SDK配置时的menuconfig中产出的,或者各类厂商提供的defconfig。
Module.symvers是模块符号表,用于解析printk,gpio_request,i2c_transfer这些内核符号。
- 指定当前路径
PWD := $(shell pwd)
- 指定编译动作
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");
- 头文件引用
#include <linux/module.h>
#include <linux/init.h>
一个最基本的模块需要的头文件:
linux/module.h提供了一系列模块初始化所需的标志。linux/init.h为模块和内核初始化阶段提供宏和标记,例如指定init函数放在哪个端,标记哪些数据随初始化创建和释放,等等
- 初始化回调
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); //设置模块的卸载回调
- 设置模块元信息
这些信息会在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: 驱动已卸载