本节内容包括:
1.驱动编写(驱动的基本框架);
2.编译驱动程序的前置工作;
3.把驱动程序编译成内核模块(.ko);
4.把驱动程序编译进内核(built-in);
1.实验目标
通过本实验,完成以下内容:
- 理解 Linux 驱动程序的基本结构
- 编写一个最简单的 HelloWorld 内核驱动
- 掌握内核模块方式的编译方法
- 掌握将驱动编译进内核的方法
- 学会使用insmod,rmmod,modprobe 等命令验证驱动运行结果
2.驱动程序基础与整体框架
Linux驱动本质上是运行在内核空间的一段代码。与普通用户态C程序不同,驱动程序不能直接使用C标准库中的printd,main等接口,而是需要遵循Linux内核驱动开发框架。
对于一个最简单的Linux驱动,一般要包含以下几个部分:
**(1) 模块加载函数:**当使用加载驱动模块时,内核会执行模块加载函数,完成模块加载函数中的初始化工作。
**(2)模块卸载函数:**当卸载某模块时,内核会执行模块卸载函数,完成模块卸载函数中的退出工作。
**(3)模块许可证声明:**许可证声明描述了内核模块的许可权限,如果不声明模块许可,模块在加载的时候,会收 到"内核被污染(kerneltainted)"的警告。可接受的内核模块声明许可包括"GPL""GPLv2"。
(4)模块参数,作者信息等内容。
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "HelloWorld driver init
");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "HelloWorld driver exit
");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("your_name");
MODULE_DESCRIPTION("A simple HelloWorld Linux driver");
3.驱动程序编译的前置工作
面向开发板编译驱动时,不能简单使用宿主机当前运行内核的头文件 。因为驱动模块必须与开发板当前运行的内核版本、内核配置、编译器架构 保持一致,否则容易出现模块无法加载、invalid module format、vermagic 不匹配等问题。
因此,前置工作通常包括以下内容:
3.1明确开发板信息
先在开发板上查看以下信息:
bash
uname -r
uname -m
cat /proc/version
例如:
uname -r:查看开发板当前运行的内核版本uname -m:查看开发板 CPU 架构,如arm,aarch64,armv7lcat /proc/version:查看内核编译器版本信息
这些信息决定后续要使用哪一套内核源码和交叉编译工具链。
3.2准备与开发板匹配的内核源码
编译开发板驱动时,最好使用开发板厂商提供的 Linux 内核源码,或者与板端正在运行内核严格匹配的源码树。
3.3准备交叉编译工具链
将下载的交叉编译器拷贝到Ubuntu 的/usr/local 目录下,

输入以下命令,解压交叉编译编译器压缩包:
bash
tar-vxf gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu.tar.gz
在终端输入"sudo vim /etc/profile"命令,在文件最后输入以下命令修改环境变量。
bash
export PATH=$PATH:/usr/local/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin

保存退出,在终端输入"reboot"命令重新启动Ubuntu系统,使交叉编译环境生效。
4.将驱动编译成内核模块并部署到开发板
4.1编写Makefile
编译驱动程序还需要使用Makefile文件。 Makefile 文件和源文件helloworld.c 位于同一级目录,代码如下所示:
bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu
obj-m += helloworld.o #helloworld.c 对应.o 文件的名称。名称要保持一致。
KDIR :=/home/haohao/Linux/linux_sdk/kernel #内核源码所在虚拟机ubuntu的实际路径
PWD?=$(shell pwd)
all:
make-C $(KDIR) M=$(PWD) modules #make 操作
clean:
make-C $(KDIR) M=$(PWD) clean #makeclean 操作
有了makefile以后,输入make编译驱动模块,编译完生成helloworld.ko目标文件就是我们需要的内核模块。

4.2模块的加载与卸载
将编译好的.ko文件拷贝到开发板,在Linux系统启动以后使用"insmod"命令加载驱动模块。
在加载驱动模块的时候会执行驱动入口的函数,也就是 helloworld 程序中的 helloworld_init 函 数 , 所以可以看到打印出来的字符串信息 "helloworld_init"。

如果要卸载helloworld 内核模块,可以通过"rmmod helloworld"命令来卸载驱动模块, 同理在卸载驱动模块的时候会执行驱动出口的函数,所以可以看到驱动出口函数打印出来的字符串信息"helloworld_exit"。
加载驱动模块也可以使用modprobe命令,它比insmod命令更强大,modprobe命令在加载驱动模块的时候,会同时加载该模块依赖的其他模块。
所以,如果驱动模块是以"modprobe helloworld.ko"命令加载的,卸载的时候使用"modprobe-r helloworld.ko"命令卸载。
4.3查看模块信息
在驱动模块加载之后,使用"modinfo helloworld.ko "命令可以获得模块的信息,lsmod 命令可以列出已经载入Linux内核模块。
也可以查看开发板内核日志:
bash
dmesg | tail
若驱动加载成功,通常可以看到:Helloworld driver init;
5.将驱动编译到内核
除了编译成模块,还可以将驱动直接编译进开发板内核镜像。这种方式称为 built-in。
其特点是:
- 驱动随目标板内核一起编译
- 系统启动时自动生效
- 不能像模块一样用
insmod/rmmod动态装卸 - 常用于启动阶段必须存在的驱动,或嵌入式系统固化部署场景
built-in 与模块方式的区别:
|--------------|-----------|-------------|
| 对比项 | 编译成模块方式 | 编译进内核方式 |
| 生成文件 | .ko | 内核镜像的一部分 |
| 是否可以动态加载 | 可以 | 不可以 |
| 是否需要重新编译整个内核 | 不需要 | 需要 |
| 调试便利性 | 高 | 低 |
| 部署方式 | 拷贝.ko到开发板 | 替换内核镜像/烧录系统 |
5.1使用menuconfig配置
我们使用menuconfig选择选项,将驱动编译进内核。
首先我们进入到源码kernel下的drivers下的字符型驱动下:
bash
cd kernel/drivers/char
然后建立相应的驱动文件夹,我们以helloworld驱动程序为例:
bash
mkdir hello
cd hello
然后将之前写的helloworld.c文件放到hello目录下。
(1)接着创建Kconfig文件,Kconfig文件内容如下:
bash
config HELLO
tristate "hello world"
default y
help
hello hello
tristate "hello world"
-
含义 :定义该选项的类型 和在菜单中显示的提示字符串。
-
tristate(三态):这意味着该选项有三种可能的选择:-
y(Yes):编译进内核映像(built-in)。 -
m(Module):编译为可加载的内核模块(.ko 文件)。 -
n(No):不编译,不包含该功能。
-
如果是bool则是两种选择。
(2)创建Makefile文件
makefile文件内容为:
bash
obj-$(CONFIG_HELLO)+=helloworld.o
(3)修改上级目录
接下来修改上一级目录的Kconfig文件和Makefile文件,也就是kernel/drivers/char目录。Makefile 添加下所示内容:
bash
obj-y += hello/

Kconfig添加如下所示内容:
bash
source "drivers/char/hello/Kconfig"
最后打开menuconfig 图形化配置工具,在配置界面选择helloworld驱动。把驱动编译进 Linux 内核,用 * 来表示,所以配置选项改为*。
(4)编译内核源码
退出配置页面,输入以下内容就可以编译内核源码了:
bash
make savedefconfig
cp defconfig arch/arm64/configs/rockchip_linux_defconfig
cd ../
./build.sh kernel