Bootstrap

RK3568驱动指南|驱动基础进阶篇-进阶6 内核运行ko文件实验——系统调用

瑞芯微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主板


进阶6 内核运行ko文件实验——系统调用

在前面的学习中,我们了解到insmod命令的功能。当我们使用insmod命令加载ko文件时,它会调用系统调用init_module或finit_module。那么,什么是系统调用呢?接下来我们来进一步探索系统调用。

6.1 什么是系统调用

系统调用(system call)是操作系统提供给应用程序的编程接口,用于访问操作系统的服务和功能。它是应用程序与操作系统之间的桥梁,允许应用程序请求操作系统执行特定的操作,例如文件操作,进程管理,网络通信等。在计算机系统中,应用程序运行在用户态下,而操作系统运行在内核态下。用户态的应用程序无法直接访问操作系统的内核功能和资源,只能通过系统调用来向操作系统发出请求。当应用程序需要执行操作系统提供的服务或访问底层资源时,它会通过系统调用接口向操作系统发送请求,并传递必要的参数。操作系统接收到请求后,会在内核态下执行相应的操作,并将结果返回给应用程序。

举个例子来说明,假设小A同学去饭店吃饭。饭店提供了菜单,厨师会根据顾客点的菜来准备食物。在这个例子中,顾客相当于上层应用程序,顾客想要吃鱼香肉丝,可以通过服务员(类比系统调用)点这道菜。服务员接收到顾客的点菜请求后,将其传达给厨师(类比操作系统)。厨师根据订单准备食物,最后将食物交给服务员,服务员再将食物送到顾客面前。

这个例子说明了上层应用程序无法直接操作底层硬件,而是通过系统调用接口与操作系统进行交互来实现对硬件的访问。系统调用是与特定的CPU架构相关的,并且与操作系统的内核版本也有关系。

回到init_module和finit_module这俩个系统调用,它们是应用程序调用的系统调用。当应用程序调用这些系统调用时,操作系统内核会执行加载和运行ko文件的操作。这样,应用程序就能通过系统调用向操作系统请求加载内核模块的功能。

6.2 系统调用的流程

以init_module为例,init_module的原型为如下所示:

#include <linux/module.h>

int init_module(void *module_image, unsigned long len, const char *param_values);

参数说明:

  • module_image:指向内核模块的内存映像的指针。
  • len:内核模块的大小(以字节为单位)。
  •  param_values:用于传递内核模块参数的字符串。

返回值:成功加载并初始化内核模块时,返回0。加载和初始化内核模块失败时,返回负数值,表示错误代码。

syscall函数是一个系统调用的包装函数,用于在C/C++程序中调用系统调用。每个系统有一个唯一的系统调用号来标识对应的函数。它的原型如下:

long syscall(long number, ...);

参数说明:

  • number:系统调用的编号。不同的系统调用有不同的编号,可以在系统调用的文档或头文件中找到相应的编号。
  • ...:可变参数,用于传递系统调用的参数。具体的参数个数和类型取决于不同的系统调用。

返回值:系统调用执行成功时,返回系统调用的结果或返回值。系统调用执行失败时,返回负数值,表示错误代码。错误代码的具体含义可以在系统调用的文档或头文件中找到相应的定义。

作用:根据系统调用号,调用相应的系统调用

那__NR_init_module 和哪个函数绑定了呢?打开include/uapi/asm-generic/unistd.h文件,找到以下代码:

/* kernel/module.c */

#define __NR_init_module 105

__SYSCALL(__NR_init_module, sys_init_module)

#define __NR_delete_module 106

__SYSCALL(__NR_delete_module, sys_delete_module)

__SYSCALL将系统调用号与sys_init_module函数绑定。这里有一个规律,在用户空间我们使用xxx函数,对应的系统调用的函数就是sys_xxx;

sys_init_module函数定义在kernel/module.c文件中。sys_init_module函数定义是一个宏定义。如下图所示

 

SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, uargs)
{
	int err;
	struct load_info info = { };

	err = may_init_module();
	if (err)
		return err;

	pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
	       umod, len, uargs);

	err = copy_module_from_user(umod, len, &info);
	if (err)
		return err;

	return load_module(&info, uargs, 0);
}

在上面的系统调用函数将用户空间的模块数据复制到内核空间,并调用load_module函数加载和初始化该模块。在load_module函数中,完成模块的加载过程,返回do_init_module函数进行模块的初始化。在do_init_module函数中,如果模块的init函数不为空,调用do_one_initcall函数执行模块的初始化函数。在do_one_initcall函数中,执行一个初始化调用函数,这个函数通常在内核初始化过程中使用,用于执行各个模块的初始化函数。

这个宏定义非常复杂,大家只要记住SYSCALL_DEFINE3(init_module, void __user *, umod,

unsigned long, len, const char __user *, uargs)等价于__se_sys_init_module函数即可。这也就解释了为什么当我调用这个系统调用的时候会调用__se_sys_init_module函数。

图6-1

;