Linux内核与驱动:2.驱动基础(编译驱动)

本节内容包括:

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 formatvermagic 不匹配等问题。

因此,前置工作通常包括以下内容:

3.1明确开发板信息

先在开发板上查看以下信息:

bash 复制代码
uname -r

uname -m

cat /proc/version

例如:

  • uname -r:查看开发板当前运行的内核版本
  • uname -m:查看开发板 CPU 架构,如 arm, aarch64, armv7l
  • cat /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
相关推荐
电商API_180079052472 小时前
API分享:获取淘宝商品价格|详情|主图|sku信息
开发语言·c++·人工智能·数据分析
Mariooooooooooo2 小时前
个人5070离线安装nvidia显卡驱动
linux
龙泉寺天下行走2 小时前
记一次windows SSH无法免密登录Linux的处理
linux·运维·ssh
极客老王说Agent2 小时前
适合IT运维人员进行服务器监控和故障预警的Agent有哪些?2026智能运维全攻略
运维·服务器·人工智能·ai·chatgpt
kainx2 小时前
华为RH1288 V2服务器风扇异常狂转iBMC的管理网口无法连上查看硬件告警-通过ESXi启用shell安装ipmitool修改iBMC网络配置
linux·运维·服务器·网络·esxi·vmware
vx-bot5556662 小时前
企业微信ipad协议的日志追踪与异常监控体系
服务器·企业微信·ipad
AI浩2 小时前
第 12 章:命令行高级参考 —— 自动化与工程化的基石
运维·服务器·自动化
羊小蜜.2 小时前
C++17: map & multimap—— 键值映射容器
开发语言·c++·stl
i建模2 小时前
Ubuntu 中使用 LVM(逻辑卷管理)挂载磁盘
linux·运维·ubuntu