第四十二期-ARM Linux内核的系统调用(2)

作者:罗宇哲,中国科学院软件研究所智能软件研究中心

上一期中我们介绍了ARM Linux内核中的系统调用和定义系统调用的流程,这一期我们将介绍系统调用的执行过程。

一、ARM Linux内核中系统调用的执行过程

在第二十九期和第三十期中,我们介绍过ARM Linux内核中的异常向量表和异常处理的一般流程。系统调用属于同步异常的范畴,因此遵循异常处理的一般流程。ARM Linux 内核的异常向量表在/kernel-4.19/arch/arm64/kernel/entry.S文件中:
第四十二期-ARM Linux内核的系统调用(2)_第1张图片
该表从上到下共有四组,系统调用则属于第三组和第四组:

  • 第三组为64位应用程序在异常级别EL0生成的异常。例如64位用户态程序发生系统调用,处理器从异常级别EL0切换到异常级别EL1,并且使用aarch64执行状态处理异常;
  • 第四组为32位应用程序在异常级别EL0生成的异常。例如32位用户态程序发生系统调用,处理器从异常级别EL0切换到异常级别EL1,并且使用aarch32执行状态处理异常;

两组中的sync项即为同步异常(包括系统调用)的入口。以64位应用程序的系统调用为例,通过sync入口程序跳转到el0_sync处(这是因为kernel_ventry宏会在跳转的目标label前加入el字符串和输入的异常级别),el0_sync入口代码在entry.S文件中:

/*

* EL0 mode handlers.

*/

.align 6

el0_sync:

kernel_entry 0

mrs x25, esr_el1 // read the syndrome register

lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class

cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state

b.eq el0_svc

cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0

b.eq el0_da

cmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0

b.eq el0_ia

cmp x24, #ESR_ELx_EC_FP_ASIMD // FP/ASIMD access

b.eq el0_fpsimd_acc

cmp x24, #ESR_ELx_EC_SVE // SVE access

b.eq el0_sve_acc

cmp x24, #ESR_ELx_EC_FP_EXC64 // FP/ASIMD exception

b.eq el0_fpsimd_exc

cmp x24, #ESR_ELx_EC_SYS64 // configurable trap

b.eq el0_sys

cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception

b.eq el0_sp

cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception

b.eq el0_pc

cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL0

b.eq el0_undef

cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0

b.ge el0_dbg

b el0_inv

我们在第三十期介绍过,发生异常的原因会被保存在ESR寄存器中,该寄存器中的值被移位后与各种异常情况的标号值相比较,如果该值等于#ESR_ELx_EC_SVC64说明该同步异常是由SVC指令触发的(系统调用也由该指令触发),程序跳转到el0_svc处。el0_svc处的代码在entry.S文件中:
第四十二期-ARM Linux内核的系统调用(2)_第2张图片
它跳转到了el0_svc_handler处,其代码在openeuler/kernel/blob/kernel-4.19/arch/arm64/kernel/syscall.c中:
第四十二期-ARM Linux内核的系统调用(2)_第3张图片
asmlinkage表示该C函数可以在汇编语言中被调用。该函数调用了el0_svc_common()函数,其代码在syscall.c文件中:

static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,

const syscall_fn_t syscall_table[])

{

unsigned long flags = current_thread_info()->flags;

regs->orig_x0 = regs->regs[0];//x0中保存了栈指针sp

regs->syscallno = scno;//保存系统调用号到pt_regs结构体

cortex_a76_erratum_1463225_svc_handler();

local_daif_restore(DAIF_PROCCTX);

user_exit();

if (has_syscall_work(flags)) {//检查_TIF_SYSCALL_WORK位,如果为1

/* set default errno for user-issued syscall(-1) */

if (scno == NO_SYSCALL)

regs->regs[0] = -ENOSYS;

scno =
syscall_trace_enter(regs);//用于跟踪系统调用的情况,将系统调用进入时的信息写入审计上下文中,与安全审计功能有关

if (scno == NO_SYSCALL)

goto trace_exit;

}

invoke_syscall(regs, scno, sc_nr, syscall_table);//执行系统调用

……

trace_exit:

syscall_trace_exit(regs);//用于跟踪系统调用的情况,将系统调用退出时的信息写入审计上下文中,与安全审计功能有关

}

从输入参数看,该函数的输入有保存系统调用发生时的寄存器状态的pt_regs结构体、系统调用号(被保存在寄存器x8中)、系统调用总数__NR_syscalls(294)和系统调用表。该函数调用invoke_syscall()函数执行系统调用,invoke_syscall()函数代码在syscall.c文件中:

static void invoke_syscall(struct pt_regs *regs, unsigned int scno,

unsigned int sc_nr,

const syscall_fn_t syscall_table[])

{

long ret;

if (scno < sc_nr) {//系统调用号小于系统调用总数

syscall_fn_t syscall_fn;

syscall_fn = syscall_table[array_index_nospec(scno,
sc_nr)];//从系统调用表中得到系统调用处理函数

ret = __invoke_syscall(regs, syscall_fn);//调用系统调用处理函数

} else {

ret = do_ni_syscall(regs, scno);//处理未识别系统调用

}

regs->regs[0] = ret;//x0寄存器保存系统调用返回值

}

如果输入的系统调用号小于系统调用的总数,那么从系统调用表中找到对应的系统调用处理函数,并调用__invoke_syscall()函数处理系统调用。如果系统调用号(从0开始)大于或等于系统调用总数,那么为非法情形,调用do_ni_syscall()函数。array_index_nospec()宏用于确认scno不会超过sc_nr。__invoke_syscall()函数调用系统调用处理函数,其代码在syscall.c文件中:
第四十二期-ARM Linux内核的系统调用(2)_第4张图片
do_ni_syscall()处理那些未识别的系统调用,其代码在syscall.c文件中:

static long do_ni_syscall(struct pt_regs *regs, int scno)

{

#ifdef CONFIG_COMPAT

long ret;

if (is_compat_task())
{//在openeuler/kernel/blob/kernel-4.19/include/linux/compat.h文件中该条件被定义为0

ret = compat_arm_syscall(regs, scno);//处理所有未识别的系统调用

if (ret != -ENOSYS)

return ret;

}

#endif

return sys_ni_syscall();//返回错误值

}

do_ni_syscall()调用sys_ni_syscall()函数,该函数直接返回错误值,其代码在openeuler/kernel/blob/kernel-4.19/kernel/sys_ni.c文件中:
第四十二期-ARM Linux内核的系统调用(2)_第5张图片
错误值ENOSYS的含义为未定义函数。

二、结语

本期我们介绍了ARM Linux内核中的系统调用的执行流程,下一期我们将尝试向ARM
Linux内核中增加一个系统调用。

参考文献

  1. 《Linux内核深度解析》,余华兵著,2019

你可能感兴趣的:(第四十二期-ARM Linux内核的系统调用(2))