一、系统调用
在现代多任务操作系统上会同时有很多进程对硬件进行访问,然而计算机的硬件资源是有限的,所有这些操作都必须由操作系统来控制
1、Linux系统调用
Linux通过系统调用限制用户空间对硬件设备和其他资源的访问,相当于在用户空间和硬件设备之间抽象出来一个中间层,对硬件具体类型进行屏蔽。主要作用就是为了保证系统稳定,避免用户应用程序肆意操作,导致崩溃
2、系统调用形式
asmlinkage long sys_getpid(void)
asmlinkage限定词是一个编译指令,通知编译器不要将函数参数放到CPU寄存器中,仅从栈中提取该函数的参数,每个系统调用都需要这个限定词
3、系统调用号
每个系统调用都有一个系统调用号,内核将所有注册过的系统调用和系统调用号存储在sys_call_table中
二、系统调用处理程序
1、指定恰当的系统调用
在x86-64中,系统调用号是通过eax寄存器传递给内核的,用户空间需要将调用号放入eax寄存器
2、参数传递
函数参数通过ebx、ecx、edx、esi、edi寄存器传递,如果参数超过5个,所有参数在用户地址空间的指针会存储在一个单独的寄存器中
3、参数验证
a.指针指向用户空间,进程不能让内核去读内核空间的数据
b.指针指向当前进程的地址空间,不能让内核去读其他进程的数据
c.进程不能绕过内存可读、可写、可执行的访问限制
三、系统调用上下文
在Linux中,系统调用运行于内核空间,是用户空间访问内核空间的唯一手段,除异常和中断外,是内核唯一的合法入口。内核在执行系统时处于进程上下文,可以休眠和被抢占,新的进程也可能会抢占当前进程并使用同样的系统调用,故必须保证系统调用是可重入的
系统调用是通过软中断实现的
四、系统调用实现
1、sys_getpid()
在arm架构中,系统调用号(syscall id)位于下面的文件中
include/uapi/asm-generic/unistd.h
打开文件搜索,可以看到,sys_getpid系统调用号为172,但不同架构的系统调用号不同,最好是通过SYS_getpid宏作为参数传递,方便移植在不同架构中
#define __NR_getpid 172
__SYSCALL(__NR_getpid, sys_getpid)
编写应用程序直接调用系统调用获得pid,与getpid()获取的结果进行对比
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <string.h>
#include <errno.h>
int main()
{
int pit_syscall = -1;
pit_syscall = syscall(SYS_getpid);
printf("syscall:[%d], syscall_id[%d]\n", pit_syscall, SYS_getpid);
if (pit_syscall == -1) {
printf("Error:%s\n", strerror(errno));
}
printf("getpid:[%d]\n", getpid());
return 0;
}
aarch64-linux-gnu-gcc sys_test.c -o sys_test
在arm64设备运行,可以看到下面结果,表明调用成功
# ./sys_test
syscall:[1154], syscall_id[172]
getpid:[1154]
2、添加自己的syscall
(1)添加自定义处理函数
在sys.c文件中添加下面函数
kernel/sys.c
SYSCALL_DEFINE0(call_test)
{
printk("Hello World, sys_call success!\n");
return 0;
}
注:可以参照其他syscall写法,如果传递1个参数,就使用SYSCALL_DEFINE1,以此类推
(2)头文件声明
声明位于syscalls.h文件
include/linux/syscalls.h
asmlinkage long sys_call_test(void);
(3)定义系统调用号
对于ARM架构,系统调用号文件位于
include/uapi/asm-generic/unistd.h
+#define __NR_call_test 451
+__SYSCALL(__NR_call_test, sys_call_test)
+
#undef __NR_syscalls
-#define __NR_syscalls 451
+#define __NR_syscalls 452
注意:需要将__NR_syscalls 也同步增加
(4)修改系统调用表
添加我们自己的系统调用到系统调用表
arch/x86/entry/syscalls/syscall_64.tbl
449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node
+451 common call_test sys_call_test
(5)C程序测试
按上述方法修改后,编译并运行内核,我是在QEMU虚拟机中运行的,可以参照QEMU这篇博客搭建运行环境
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <string.h>
#include <errno.h>
int main()
{
int ret = -1;
ret = syscall(451);
printf("syscall result[%d]\n", ret);
if (ret == -1) {
printf("Error:%s\n", strerror(errno));
return ret;
}
printf("my system call test success!\n");
return 0;
}
aarch64-linux-gnu-gcc sys_test.c -o sys_test
运行结果如下,可以看到内核printk出了我们自己编写的syscall
# ./sys_test
[ 2523.918842] Hello World, sys_call success!
syscall result[0]
my system call test success!
【参考博客】
[1] Linux内核设计与实现
[4] Linux操作系统实验 —— 增加系统调用_linux编写一个新的系统调用函数-CSDN博客