瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(驱动基础进阶篇_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
进阶4 内核是如何运行ko文件的_insmod
我们加载驱动一直以来使用的都是insmod命令,那这个insmod命令是哪里来的呢,我们能不能自己实现一个insmod命令呢,带着疑问,让我们进入本节课程的学习吧。
4.1 insmod源码分析
在开发板的各种系统中,insmod命令都默认存在了,而命令在本质上也是一个可执行程序,也有着对应的源码,不管是何种系统,insmod命令的源码是相同的(可能会因为版本不同,存在细微的差别),在“iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\51_module\01_busybox源码”目录下提供了busybox源码,如下图所示:
图 4-1
然后将busybox源码拷贝到虚拟机Ubuntu上并解压,解压完成如下图所示:
图 4-2
其中insmod的源码为busybox-1.34.1/modutils/insmod.c,该源码的主要内容如下所示:
int insmod_main(int argc UNUSED_PARAM, char **argv)
{
char *filename;
int rc;
// 如果是2.4风格的insmod,解析命令行选项
IF_FEATURE_2_4_MODULES(
getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
argv += optind - 1;
);
// 获取模块文件名
filename = *++argv;
if (!filename)
bb_show_usage();
// 调用bb_init_module函数加载模块
rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
// 如果加载失败,显示错误信息
if (rc)
bb_error_msg("can't insert '%s': %s", filename, moderror(rc));
return rc;
}
其中最重要的是第18行的bb_init_module函数,正是通过该函数加载的内核模块。该函数定义在busybox-1.34.1/modutils/modutils.c文件中,具体内容如下所示:
int FAST_FUNC bb_init_module(const char *filename, const char *options)
{
size_t image_size;
char *image;
int rc;
bool mmaped;
if (!options)
options = "";
// TODO: 审查 bb_init_module_24 以符合错误代码约定
#if ENABLE_FEATURE_2_4_MODULES
if (get_linux_version_code() < KERNEL_VERSION(2,6,0))
return bb_init_module_24(filename, options);
#endif
// 首先尝试使用finit_module函数加载模块
// 如果finit_module函数可用,则尝试使用它加载模块
#ifdef __NR_finit_module
{
// 打开模块文件
int fd = open(filename, O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
// 调用finit_module函数加载模块,返回值为0表示加载成功
rc = finit_module(fd, options, 0) != 0;
close(fd);
if (rc == 0)
return rc;
}
}
#endif
// 如果finit_module函数不可用,或者调用finit_module失败,则使用其他方式加载模块
// 初始化image_size为INT_MAX-4095,作为映射模块的内存大小
image_size = INT_MAX - 4095;
mmaped = 0;
// 尝试将模块文件映射到内存中
image = try_to_mmap_module(filename, &image_size);
if (image) {
mmaped = 1;
} else {
errno = ENOMEM;
image = xmalloc_open_zipped_read_close(filename, &image_size);
if (!image)
return -errno;
}
errno = 0;
// 调用init_module函数加载模块,将模块的映像和大小以及选项传递给它
init_module(image, image_size, options);
rc = errno;
if (mmaped)
munmap(image, image_size);
else
free(image);
return rc;
}
在该函数中有着两种模块加载方式,19-31行为第一种使用finit_module系统调用的加载方式,33-57行为第二种使用init_module系统调用的加载方式,默认情况会先使用第一种方式进行模块的加载,如果加载失败了才会使用第二种方式进行加载。两种加载方式的最终实现效果是相同的,而为了加深大家对insmod的理解,我们将根据该函数的内容编写C程序,分别使用第一种方法和第二种方法来加载相应的ko文件。
4.2 实验程序的编写
由于章节篇幅的原因,本章节只会实现第一种方法,在下个章节将会实现第二种方法。
4.2.1 驱动程序编写
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\51_module\02_module。
编写完成的helloworld.c代码如下所示。
#include <linux/module.h>
#include <linux/kernel.h>
static int __init helloworld_init(void) //驱动入口函数
{
dump_stack();
return 0;
}
static void __exit helloworld_exit(void) //驱动出口函数
{
printk(KERN_EMERG "helloworld_exit\r\n");
}
module_init(helloworld_init); //注册入口函数
module_exit(helloworld_exit); //注册出口函数
MODULE_LICENSE("GPL v2"); //同意 GPL 开源协议
MODULE_AUTHOR("topeet"); //作者信息
该驱动程序就是最简单的helloworld驱动程序,只是在第6行添加了dump_stack()函数,用来在内核中输出当前调用堆栈信息,在这里使用该函数的目的是验证在app中的finit_module系统调用是否生效。
4.2.2 编写测试 APP
本应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\51_module\03_app。
编写完成的应用程序app.c代码如下所示:
#include <stdio.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#define finit_module(fd, uargs, flags) syscall(__NR_finit_module, fd, uargs, flags)
int main(int argc, char **argv)
{
int fd; // 文件描述符
int ret; // 返回值
fd = open(argv[1], O_RDONLY | O_CLOEXEC); // 打开文件,以只读方式打开并设置O_CLOEXEC标志
if (fd < 0) { // 打开文件失败
printf("open error\n");
return -1;
}
ret = finit_module(fd, "", 0); // 调用finit_module系统调用加载模块
return ret; // 返回加载结果
}
该应用程序的重点在21行,使用finit_module系统调用加载模块到内核。
4.3 运行测试
4.3.1 编译驱动程序
在上一小节中的helloworld.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += helloworld.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放helloworld.c和Makefile文件目录下,如下图(图4-3)所示:
图 4-3
然后使用命令“make”进行驱动的编译,编译完成如下图(图4-4)所示:
图 4-4
编译完生成platform_led.ko目标文件,如下图(图4-5)所示:
图 4-5
至此驱动模块就编译成功了。
4.3.2 编译应用程序
下面进行应用程序编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图(图4-6)所示:
aarch64-linux-gnu-gcc app.c -o app
图 4-6
下面进行驱动程序的测试。
4.3.2 运行测试
本次测试要使用上面编译的helloworld.ko驱动文件和app可执行文件,所以需要先将两个要用到的文件拷贝到开发板上。
开发板启动之后,使用以下命令加载helloworld.ko驱动,如下图所示:
./app helloworld.ko
图 4-7
dump_stack函数会打印调用的一系列函数,这些函数的具体调用已经在进阶第一章讲解过了,这里的重点为红色框中的倒数第二行,调用了finit_module函数加载了helloworld驱动。
然后继续使用以下命令查看驱动的加载情况,打印如下图(4-8)所示:
lsmod
图 48
可以看到helloworld驱动成功加载上了,最后可以使用以下命令进行驱动的卸载,如下图(图4-9)所示:
rmmod
图 4-9
至此,内核是如何运行KO文件的_insmod实验就完成了。