LV06-02-内核模块-03-内核模块的加载

本文主要是内核模块——内核模块的加载的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、加载命令

前面我们已经拿到了ko文件,现在我们来学习一下怎么加载ko驱动。先来学习几个命令:

1. file——查看文件格式

file命令可用于查看指定的.ko文件可以用于哪种平台,一般使用格式如下:

1
file module_name.ko

输出信息中,带x86字样的适用于主机ubuntu linux,带arm字样的适用于开发板linux。

image-20241117163610305

2. dmesg——内核日志查看

dmesg命令可用于查看内核的打印的提示信息,主要是在Ubuntu中加载模块的使用后使用,我们的开发板会实时在串口终端界面打提示信息,一般使用格式如下:

1
2
sudo dmesg -c  # 清除内核已打印的信息
dmseg # 查看内核的打印信息

【注意】在主机Ubuntu中加载模块的时候需要加上sudo。

3. 模块相关的命令

3.1 insmod——加载指定模块

insmod命令可用于加载.ko文件,一般使用格式如下:

1
insmod module_name.ko

【注意】在主机Ubuntu中加载模块的时候需要加上sudo。

3.2 lsmod——查看已经加载的模块

lsmod命令可用显示已经加载的模块,一般使用格式如下:

1
lsmod module_name.ko

【注意】在主机Ubuntu中加载模块的时候不加sudo,也可以正常使用。

3.3 rmmod——卸载已经加载的模块

rmmod命令可用于卸载已经加载的模块,一般使用格式如下:

1
rmmod module_name

【注意】在主机Ubuntu中加载模块的时候需要加上sudo。

4. 加载测试

4.1 获取内核模块文件

上一节我们编译了两个内核模块文件,一个是在内核源码中编译的,另一个是在内核源码外编译的,这两个文件可以在这里找:02_module_load/module_load_test · 苏木/imx6ull-driver-demo - 码云 - 开源中国 (gitee.com)

image-20241117164516768

4.2 拷贝到根文件系统

我们前面配置的开发环境是从nfs挂载根文件系统,所以我们直接把这两个内核模块文件拷贝到根文件系统去就行了,linux系统启动后,我们就可以看到。

1
2
mkdir ~/4nfs/imx6ull_rootfs/driver_demo
cp -rvf module_load_test ~/4nfs/imx6ull_rootfs/drivers_demo/

我们启动开发板,然后看一下/driver_demo目录:

image-20241117165539155

需要注意的事这里并不是所有的移植完的根文件系统都有tree命令,有的话就能用。另外我发现好像加载有些延迟,拷贝完,linux开发板上还没有这些文件,等待一小会就好了。

4.3 模块测试

注意,两个测试模块名字一样,就不要同时加载了,一个测试完卸载后再加载另一个测试。命令都是一样的。

1
2
3
4
5
cd /drivers_demo

insmod hello_world_demo.ko # 加载模块
lsmod # 查看已加载模块
rmmod hello_world_demo # 卸载已加载的模块
image-20241117171145909

二、modprobe

还有一个很强大的命令就是modprobe,使用它,我们可以不进入驱动所在的目录。

1. 一个错误的解决

1.1 原因分析

还记得前边使用buildroot构建根文件系统的时候,有一个报错:

image-20241117171806402

这里的这个目录起始就是为modprobe这个命令准备的,这个目录中将会存放驱动文件,然后可以不进入此目录,就直接使用modprobe命令加载指定的驱动。

“/lib/modules”这个目录是通用的,不管用的什么板子、什么内核,这部分是一样的。不一样的是这个目录下的子目录,会有一个以内核版本命名的目录,这个目录才是最终存放驱动的目录,例如“/lib/modules/4.19.71”,这里要根据我们所使用的 Linux 内核版本来设置,比如 ALPHA 开发板现在用的是4.19.71 版本的 Linux 内核,因此就是“/lib/modules/4.19.71”。如果使用的其他版本内核,比如 5.14.31,那么就应该创建“/lib/modules/5.14.31”目录,否则 modprobe 命令无法加载驱动模块。

所以我们先自行在根文件系统创建这个目录,这个目录名怎么确定?我们可以看开机打印信息:

image-20241117171932832

或者制作的根文件系统支持uname命令的话也可以使用这个命令来看:

1
uname -r
image-20241117172034406

我们手动创建这个目录

1
mkdir -p /lib/modules/4.19.71-00007-g51f3cd8ec-dirty

之后就会发现这个报错就会消失,但是又会报另一个错:

image-20241117172158173

可以看出, modprobe 提示无法打开“modules.dep”这个文件,因此驱动挂载失败了。我们不用手动创建 modules.dep 这个文件,直接输入 depmod 命令即可自动生成modules.dep。

1.2 添加depmod命令

有些根文件系统可能没有 depmod 这个命令,如果没有这个命令就只能重新配置busybox了,所以我们可以进入buildroot源码(我用的是这个)中,执行以下命令打开busybox的配置窗口:

1
2
cd ~/7Linux/buildroot-2023.05.1
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- busybox-menuconfig

这里要加个sudo,不然有个地方会报没有权限,反正我的是这样的。然后就会打开busybox的配置窗口:

image-20241117172744748

我们按以下配置项路径找一下depmod的配置项:

1
2
Linux Module Utilities  ---> 
[*] depmod (27 kb)

勾选这个depmod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
BusyBox 1.36.1 Configuration
──────────────────────────────────────────────────────────────────────────────
┌──────────────────────── Linux Module Utilities ─────────────────────────┐
│ Arrow keys navigate the menu. <Enter> selects submenus --->. │
│ Highlighted letters are hotkeys. Pressing <Y> includes, <N> excludes, │
│ <M> modularizes features. Press <Esc><Esc> to exit, <?> for Help, </> │
│ for Search. Legend: [*] built-in [ ] excluded <M> module < > │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │[ ] Simplified modutils │ │
│ │[*] depmod (27 kb) │ │
│ │[*] insmod (22 kb) │ │
│ │[*] lsmod (1.9 kb) │ │
│ │[*] Pretty output │ │
│ │[ ] modinfo (24 kb) │ │
│ │[*] modprobe (28 kb) │ │
│ │[ ] Blacklist support │ │
│ │[*] rmmod (3.3 kb) │ │
│ │--- Options common to multiple modutils │ │
│ │[*] Accept module options on modprobe command line │ │
│ │[ ] Support version 2.2/2.4 Linux kernels │ │
│ │[*] Support tainted module checking with new kernels │ │
│ │[ ] Try to load module from a mmap'ed area │ │
│ │[*] Support module.aliases file │ │
│ │[*] Support module.symbols file │ │
│ │(/lib/modules) Default directory containing modules │ │
│ │(modules.dep) Default name of modules.dep │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ <Select> < Exit > < Help > │
└─────────────────────────────────────────────────────────────────────────┘

看最后两个配置项,我们应该是可以在这里自定义这个模块默认存放的目录以及modules.dep的默认文件名,不过这里还是默认好了。最后我们保存退出。然后我们执行以下命令重新编译:

1
sudo make # 在图形界面已经配置过编译工具链了

1.3 重启测试

最后重启,就会发现还在报,原来是还需要用这个命令自动生成一些文件,我们直接在终端执行depmod,就会生成下图这些文件:

1
depmod
image-20241117174639337

然后我们再重启,应该就不会有这些报错了:

2. 加载驱动

我们前边知道可以用insmod命令加载驱动,但是这个命令有一个缺点,就是驱动若是依赖其他驱动的话,就需要先加载其他依赖的驱动,然后这个驱动才能加载成功,这样是有些麻烦的,而且还需要指定驱动路径或者到驱动存放目录下才能加载。我们可以吧驱动放在上面创建的目录下:

1
cp drivers_demo/module_load_test/outside /lib/modules/4.19.71-00007-g51f3cd8ec-dirty/ -r

然后我们直接在根目录执行:

1
modprobe hello_world_demo.ko

然后会有以下信息:

image-20241117174936411

从图中可以看出,报错了,第一次并未加载成功,这个时候我们需要执行一次depmod命令重新生成一下那几个文件:

1
ls /lib/modules/`uname -r` -F
image-20241117175752872

更新完毕后重新加载就可以了:

image-20241117175015311

重新生成后,就可以成功加载驱动啦。

3. 命令说明

上面我们已经把问题解决,也体验了这个命令,接下来我们来学习一下这个命令的使用。

3.1 命令格式

我们可以直接在ubuntu下看一下man手册:

1
2
3
4
5
6
7
8
9
10
11
NAME
modprobe - Add and remove modules from the Linux Kernel

SYNOPSIS
modprobe [-v] [-V] [-C config-file] [-n] [-i] [-q] [-b] [modulename] [module parameters...]

modprobe [-r] [-v] [-n] [-i] [modulename...]

modprobe [-c]

modprobe [--dump-modversions] [filename]

modprobe会从linux内核中智能地添加或者移除模块。modprobe需要一个实时更新的 modules.dep 文件,这个文件位于这里:

1
/lib/modules/`uname -r`/modules.dep

这个文件由 depmod 命令生成。这个文件列出了每个模块还需要依赖哪些其他的模块。 modprobe利用这个文件来自动解决添加和删除模块时候的依赖关系。我们可以看一下bulidroot创建的根文件系统中这个命令的手册:

1
2
3
4
5
6
7
8
9
10
11
BusyBox v1.36.1 (2024-11-02 20:28:53 CST) multi-call binary.

Usage: modprobe [-alrqvsD] MODULE [SYMBOL=VALUE]...

-a Load multiple MODULEs
-l List (MODULE is a pattern)
-r Remove MODULE (stacks) or do autoclean
-q Quiet
-v Verbose
-s Log to syslog
-D Show dependencies

modprobe 命令在加载驱动模块的时候, 会同时加载该模块依赖的其他模块。 比如 helloworld.ko 依赖 before.ko,使用 insmod 加载的时候, 就必须先加载 before.ko, 然后在加载 helloworld.ko 才可以加载成功。 但是使用 modprobe 加载的时候, 他会自动分析模块的依赖关系, 然后将所有的依赖的模块都加载到内核当中。 比较“聪明” 。

同样, 在卸载驱动模块的时候, 如果模块存在依赖关系, 如果使用 insmod 命令, 需要手动卸载依赖的内核模块, 但是使用 modprobe 命令可以自动卸载驱动模块所依赖的其他模块。所以, 如果驱动模块是以“modprobe helloworld.ko”命令加载的, 卸载的时候使用“modprobe -r helloworld.ko” 命令卸载。但是使用 modprobe 卸载存在一个问题, 如果所依赖的模块被其他模块所使用, 这时候就不能使用 modprobe 卸载。 所以还是推荐使用 rmmod 命令来卸载。

3.1 modprobe -l

这个命令可以列出内核中所有已经或者未挂载的所有模块

1
modprobe -l
image-20241117180953845

3.2 modprobe -r

前面我们知道了怎么加载驱动,怎么卸载呢?可以用这个命令:

1
modprobe -r MODULE

移除已加载的模块,和 rmmod 功能类似。注意:模块名是不能带有后缀的,我们通过 modprobe -l 所看到的模块,都是带有 .ko.o 后缀。前面已经说了,当要卸载的模块依赖于别的模块的时候可能就会有问题,所以卸载的时候其实不是很常用这个命令,主要还是用的rmmod。

3.3 modprobe -D

这个命令是显示依赖:

1
modprobe -D MODULE
image-20241117181338149

4. 搜索目录

在Linux中,modprobe命令会在下面的路径搜索可加载的内核模块:

1
/lib/modules/`uname -r` #(内核模块所在的默认目录)

uname -r为当前系统所使用的内核版本。当我们使用modprobe命令加载一个内核模块时,系统首先会在上面的路径中搜索是否存在该模块,若存在,系统会将其加载到内存中。若以上三个路径都没有找到该模块,则系统会返回“找不到该模块”的错误。

我们可以通过modprobe配置文件来设置搜索路径。Linux中modprobe的配置文件为/etc/modprobe.conf和/etc/modprobe.d/,其中/etc/modprobe.conf为全局配置文件,而/etc/modprobe.d/为目录,其中存放的是单独的模块配置文件。

在打开的modprobe.conf文件中,我们可以设定模块搜索路径,例如:

1
install 模块名 路径

其实在前面打开depmod命令的时候,我们看到是可以直接在buildroot中配置目录的:

image-20241117182244699

这里我就没做具体尝试了,感觉目前好像也不是很重要,后面有需要再补充吧。

参考资料

modprobe 搜索路径|极客笔记 (deepinout.com)