目录
实践原理
用户空间可用通过系统调用可以将一个或多个不同类型的数组传递给内核空间。对于
数组参数,用户空间必须通过指针的方式传到内核空间,由于这样的方式导致
内核空间不能直接访问用户空间的数据,会导致内核非法访问。因此对于这种情况,
内核首先将用户空间的数据拷贝到内核空间,这样才能安全使用数据。内核也可以
将内核数组数据传递给用户空间,此时也不能直接传,也需要通过安全的拷贝
操作才能完整任务。用户空间的系统调用部分传递的是一个指针,因此内核向
用户空间传递数组时,可以使用安全的拷贝函数将数组放置到用户空间指针
对应的位置。数组可以是整形数组、字符数组,以及结构体数组等。用户空间
使用格式如下:
struct BiscuitOS_node {
char buffer[32];
int nr;
};
int main(void)
{
struct BiscuitOS_node nodes[8] = { { .buffer = "Struct" } };
char buffer[64] = "String";
int int_array[64] = { 23, 45, 67, 81 };
int ret;
/*
* sys_hello_BiscuitOS: Array paramenter
* kernel:
* SYSCALL_DEFINE3(hello_BiscuitOS,
* int __user *, int_array,
* char __user *, char_array,
* struct BiscuitOS_node __user *, node_array);
*/
ret = syscall(__NR_hello_BiscuitOS,
int_array,
buffer,
nodes);
return 0;
}
内核空间系统调用部分接收上面的参数时,可以使用如下格式:
struct BiscuitOS_node {
char buffer[32];
int nr;
};
SYSCALL_DEFINE3(hello_BiscuitOS,
int __user *, int_array,
char __user *, buffer,
struct BiscuitOS_node __user *, nodes)
{
....
}
从上面的定义可以出,只要是从用户空间传递的指针参数,都需要添加
“__user” 进行强制说明,这是内核检测机制使用的符号,不影响函数的实际
功能。从上述也看到不论是整形数组,还是数据结构数组都需要指针方式传递。
对于系统调用传递多个整形参数的方法,开发者可以参考如下文档:
对于系统调用的返回值,内核会返回一个整形值,至于整形值的含义,开发者
可以根据需求进行返回.
实践准备
BiscuitOS 目前支持 6 大平台进行实践,本文以 ARM32 为例子进行讲解,如果
开发者需要在其他平台实践,可以参考下面文档进行实践:
本实践基于 ARM32 架构,因此在实践之前需要准备一个 ARM32 架构的运行
平台,开发者可以在 BiscuitOS 进行实践,如果还没有搭建 BiscuitOS
ARM32 实践环境的开发者,可以参考如下文档进行搭建:
开发环境搭建完毕之后,可以继续下面的内容,如果开发者不想采用
BiscuitOS 提供的开发环境,可以继续参考下面的内容在开发者使用
的环境中进行实践。(推荐使用 BiscuitOS 开发环境)。搭建完毕之后,
使用如下命令:
cdBiscuitOS/
make linux-5.0-arm32_defconfig
make
上图显示了 ARM32 实践环境的位置,以及相关的 README.md 文档,开发者
可以参考 README.md 的内容搭建一个运行在 QEMU 上的 ARM32 Linux 开发
环境:
添加用户空间实现
BiscuitOS 提供了一套完整的系统调用编译系统,开发者可以使用下面步骤部署一个
简单的用户空间调用接口文件,BiscuitOS 并可以对该文件进行交叉编译,安装,
打包和目标系统上运行的功能,节省了很多开发时间。如果开发者不想使用这套
编译机制,可以参考下面的内容进行移植。开发者首先获得用户空间系统调用
基础源码,如下:
cdBiscuitOS
make linux-5.0-arm32_defconfig
make menuconfig
选择并进入 “[*] Package —>”
选择 “[*] strace” 和 “[*] System Call” 并进入 “[*] System Call —>”
选择并进入 “[*] sys_hello_BiscuitOS —>”
选择 “[*] Syscall paramenter: Array —>” 保存配置并退出.
接下来执行下面的命令部署用户空间系统调用程序部署:
cdBiscuitOS
make
执行完毕后,终端输出相关的信息, 接下来进入源码位置,使用如下命令:
cdBiscuitOS/output/linux-5.0-arm32/package/SYSCALL_Array_common-0.0.1
这个目录就是用于部署用户空间系统调用程序,开发者继续使用命令:
cdBiscuitOS/output/linux-5.0-arm32/package/SYSCALL_Array_common-0.0.1
make prepare
make download
执行上面的命令之后,BiscuitOS 自动部署了程序所需的所有文件,如下:
tree
上图中,main.c 与用户空间系统调用相关的源码,
“SYSCALL_Array_common-0.0.1/Makefile” 是 main.c 交叉编译的逻辑。
“SYSCALL_Array_common-0.0.1/BiscuitOS_syscall.c” 文件是新系统调用
内核实现。因此对于用户空间的系统调用,开发者只需关注 main.c, 内容如下:
根据在内核中创建的入口,这里定义了入口宏的值为 400,一定要与内核定义
的入口值相呼应. 在上图的程序中,定义了多个类型的字符串变量。然后将这些
变量通过 “syscall()” 函数触发系统调用,传入的第一个参数是
“__NR_hello_BiscuitOS”, 其为系统调用号,该值为 400. 传入的第二个参数
是一个 int 型数组,第三个传入的是一个字符数组。最后一个传入的是一个结构体
数组。如果系统调用执行成功,那么三个数组都会接收从内核拷贝到用户
空间的数据,最后将其打印。源码准备好之后,接下来是交叉编译源码并打包
到 rootfs 里,最后在 ARM32 上运行。使用如下命令:
cdBiscuitOS/output/linux-5.0-arm32/package/SYSCALL_Array_common-0.0.1
make
make installmake pack
添加内核系统调用入口
ARM32 架构提供了便捷的方法在内核中添加一个新的系统调用入口。
开发者修改内核源码下 “arch/arm/tools/syscall.tbl” 文件,在
该文件的底部添加信息如下:
如上面内容所示,在文件最后一行添加了名为 hello_BiscuitOS 的
系统调用,400 代表系统调用号,hello_BiscuitOS 为系统调用的
名字,sys_hello_BiscuitOS 为系统调用在内核的实现。至此系统
号已经添加完毕。
添加内核实现
添加完系统号之后,需要在内核中添加系统调用的具体实现。开发者
可以参考下面的例子进行添加。本例子在内核源码 “fs/” 目录下添加
一个名为 BiscuitOS_syscall.c 的文件,如下:
cp -rfa BiscuitOS/output/linux-5.0-arm32/package/SYSCALL_Array_common-0.0.1/BiscuitOS_syscall.c BiscuitOS/output/linux-5.0-arm32/linux/linux/fs/
cdBiscuitOS/output/linux-5.0-arm32/linux/linux/fs
vi BiscuitOS_syscall.c
由于要介绍从用户空间传递的 3 个参数,因此使用 SYSCALL_DEFINE3 宏来定义,
与用户空间相对应,第一个参数是系统调用的名字 “hello_BiscuitOS”。第二个
参数是 int 数组,第三个是 char 指针,第四个是 struct BiscuitOS 指针,
函数定义了一个整形数组、一个字符数组和三个字符串常量。函数接收这些参数之后,
使用 copy_from_user() 函数从用户空间拷贝三个指针对应的数组到函数里面的数组。
拷贝完毕之后,打印数组的值。接着函数调用 copy_to_user() 函数将内核定义的数组
数组拷贝到用户空间。准备好源码之后,接着修改内核源码 “fs/Kconfig” 文
件,添加如下内容:
cdBiscuitOS/output/linux-5.0-arm32/linux/linux/fs
vi Kconfig
接着修改内核源码 “fs/Makefile” 文件,添加内容如下:
cdBiscuitOS/output/linux-5.0-arm32/linux/linux/fs
vi Makefile
接着是配置内核,将 BiscuitOS_syscall.c 文件加入内核编译树,如下:
cdBiscuitOS/output/linux-5.0-arm32/linux/linux/
make menuconfig ARCH=arm
选择并进入 “File systems —>”
选择 “[*] BiscuitOS syscall hello” 并保存内核配置。
接着重新编译内核。
cdBiscuitOS/output/linux-5.0-arm32/linux/linux/
make ARCH=arm CROSS_COMPILE=BiscuitOS/output/linux-5.0-arm32/arm-linux-gnueabi/arm-linux-gnueabi/bin/arm-linux-gnueabi- -j4
编译内核中会打印相关的信息如下图:
从上面的编译信息可以看出,之前的修改已经生效。编译系统调用相关的脚本
自动为hello_BiscuitOS 生成了相关的系统调用,
运行系统调用
在一切准备好之后,最后一步就是在 ARM32 上运行系统调用,参考下面
命令进行运行:
cdBiscuitOS/output/linux-5.0-arm32/
./RunBiscuitOS.sh
从运行结果可以看出,内核接收到用户空间传递下来数组,用户空间也成功接收
到来自内核的数组,并打印出来。可以使用 strace 工具查看具体的系统调用过
程,如下:
~ #
~ # strace SYSCALL_Array_common-0.0.1
从 strace 打印的消息可以看出 “syscall_0x190()” 正好程序里产生的系统调用.
附录
捐赠一下吧 🙂