作者:罗宇哲,中国科学院软件研究所智能软件研究中心
上一期中我们介绍了工作队列相关的关键函数,这一期我们将介绍ARM Linux内核中的系统调用。
一、ARM Linux内核中的系统调用
在ARM Linux内核中,系统调用是一种特殊的异常,通常被归于同步异常的范畴,这是因为它是通过SVC指令触发的。我们在第二十七期中提到过,同步异常是由正在运行的指令或指令运行的结果造成的异常。
SVC指令在ARMv8体系中被归于异常处理类指令,该指令能允许用户程序调用内核,其格式如下[1]:
异常处理程序可以从异常症状寄存器(Exception Syndrome Register,ESR)中得到SVC指令使用的立即数。从异常返回则可以使用ERET指令,该指令从SPSR寄存器中恢复处理器状态PSTATE,而且将返回到ELR寄存器保存的返回地址上。以上过程的具体情况我们在第三十期中提到过。
系统调用是操作系统内核为用户程序提供系统服务的接口,操作系统将一些需要在内核态运行的公共服务通过系统调用封装并提供给应用程序。用户程序在使用系统调用时将陷入内核态,并调用系统用的处理函数。不同的系统调用有不同的编号,它们被称为系统调用号。ARM64处理器中使用SVC指令触发系统调用的约定如下[2]:
- 64位用户程序使用寄存器x8传递系统调用号,32位用户程序使用寄存器x7传递系统调用号;
- 使用寄存器x0-x6传递系统调用所需参数,最多可传递7个参数;
- 系统调用执行完后,用寄存器x0存放返回值。
二、系统调用的定义
ARM Linux内核中使用SYSCALL_DEFINEn(…)宏来定义一个系统调用,其中n为非负整数,表示后面括号中参数的数目。以内核中用于向进程发送信号的kill系统调用为例,其定义代码在openeuler/kernel/blob/kernel-4.19/kernel/signal.c文件中可以找到:
SYSCALL_DEFINE的相关宏定义可以在openeuler/kernel/blob/kernel-4.19/include/linux/syscalls.h中找到(以下相关汇编代码在同一文件中):
在C语言宏定义中##被解释成分隔与连接一个符号,符号可以是一个变量,也就是说“##name”被编译器理解为两段:“”和“name”,而name和前面的输入参数可以匹配,所以“##name”实际上是将输入参数name前面加了“”,例如输入“kill”就会变成“_kill”。__VA_ARGS__关键字等价于可变参数列表…中省略的内容。
SYS