Linux内核深度解析之中断、异常和系统调用——系统调用

系统调用

系统调用是内核给用户程序提供的编程接口。用户程序调用系统调用,通常使用glibc库针对单个系统调用封装的函数。如果glibc库没有针对某个系统调用封装的函数,用户程序可以使用通用的封装函数syscall():

#define _GNC_SOURCE
#include <unistd.h>
#include <sys/syscall.h>        /* 定义SYS_xxx */

long syscall(long number, ...);

参数number是系统调用号,后面是传递给系统调用的参数。

返回值0表示成功,返回值-1表示错误,错误号存储在变量errno中。

例如,应用程序使用系统调用fork()创建子进程,有两种调用办法:

(1)ret = fork();

(2)ret = syscall(SYS_fork);

ARM64处理器提供的系统调用指令是svc,调用约定如下:

(1)64位应用程序使用寄存器x8传递系统调用号,32位应用程序使用寄存器x7传递系统调用号

(2)使用寄存器x0~x6最多可以传递7个参数

(3)当系统调用执行完的时候,使用寄存器x0存放返回值。

1. 定义系统调用

Linux内核使用宏SYSCALL_DEFINE定义系统调用,以创建子进程的系统调用fork为例:

kernel/fork.c
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
	/* can not support in nommu mode */
	return -EINVAL;
#endif
}

把宏“SYACALL_DEFINE0(fork)”展开以后是:

asmlinkage long sys_fork(void)

“SYSCALL_DEFINE”后面的数字表示系统调用的参数个数,“SYSCALL_DEFINE0”表示系统调用没有参数,“SYSCALL_DEFINE6”表示系统调用有6个参数,如果参数超过6个,使用宏“SYSCALL_DEFINEx”。头文件“include/linux/syscalls.h”定义了这些宏。

“asmlinkage”表示这个C语言函数可以被汇编代码调用。如果使用C++编译器,“asmlinkage”被定义为extern "C";如果使用C编译器,“asmlinkage”是空的宏。

系统调用的函数名称以“sys_”开头。

需要在系统调用表中保存系统调用号和处理函数的映射关系,ARM64架构定义的系统调用表sys_call_table如下:

arch/arm64/kernel/sys.c
#undef __SYSCALL
#define __SYSCALL(nr, sym)	asmlinkage long __arm64_##sym(const struct pt_regs *);
#include <asm/unistd.h>

#undef __SYSCALL
#define __SYSCALL(nr, sym)	[nr] = __arm64_##sym,

const syscall_fn_t sys_call_table[__NR_syscalls] = {
	[0 ... __NR_syscalls - 1] = __arm64_sys_ni_syscall,
#include <asm/unistd.h>
};

对于ARM64架构,头文件“asm/unistd.h”是“arch/arm64/include/asm/unistd.h”。

arch/arm64/include/asm/unistd.h
#include <uapi/asm/unistd.h>

arch/arm64/include/uapi/asm/unistd.h
#include <asm-generic/unistd.h>

arch/arm64/include/asm-generic/unistd.h
#include <uapi/asm-generic/unistd.h>

arch/arm64/include/uapi/asm-generic/unistd.h
#define __NR_io_setup 0        /* 系统调用号0 */
__SC_COMP(__NR_io_setup, sys_io_setup, compat_sys_io_setup)
...
#define __NR_rseq 293        /* 系统调用号293 */
__SYSCALL(__NR_rseq, sys_rseq)

#undef __NR_syscalls
#define __NR_syscalls 294

2. 执行系统调用

ARM64处理器把系统调用划分到同步异常,在异常级别1的异常向量表中,系统调用的入口有两个:

(1)如果64位应用程序执行系统调用指令svc,系统调用的入口是el0_sync;

(2)如果32位应用程序执行系统调用指令svc,系统调用的入口是el0_sync_compat。

el0_sync的代码如下:

arch/arm64/entry.S
el0_sync:
	kernel_entry 0		// 把所有通用寄存器的值保存在当前进程的内核栈中
	mrs	x25, esr_el1			// 读取异常症状寄存器esr_el1
	lsr	x24, x25, #ESR_ELx_EC_SHIFT	// 解析出异常症状寄存器的异常类别字段
	cmp	x24, #ESR_ELx_EC_SVC64		// 如果异常类别是系统调用,跳转到el0_svc
	b.eq	el0_svc

el0_svc负责执行系统调用,其代码如下:

arch/arm64/entry.S
el0_svc:
	mov	x0, sp
	bl	el0_svc_handler
	b	ret_to_user
ENDPROC(el0_svc)

el0_svc_handler执行系统调用,其代码如下:

arch/arm64/kernel/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];
	regs->syscallno = scno;

	cortex_a76_erratum_1463225_svc_handler();
	local_daif_restore(DAIF_PROCCTX);
	user_exit();

	if (has_syscall_work(flags)) {
		/* 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);

	/*
	 * The tracing status may have changed under our feet, so we have to
	 * check again. However, if we were tracing entry, then we always trace
	 * exit regardless, as the old entry assembly did.
	 */
	if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
		local_daif_mask();
		flags = current_thread_info()->flags;
		if (!has_syscall_work(flags)) {
			/*
			 * We're off to userspace, where interrupts are
			 * always enabled after we restore the flags from
			 * the SPSR.
			 */
			trace_hardirqs_on();
			return;
		}
		local_daif_restore(DAIF_PROCCTX);
	}

trace_exit:
	syscall_trace_exit(regs);
}

asmlinkage void el0_svc_handler(struct pt_regs *regs)
{
	sve_user_discard();
	el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table);
}

ret_to_user从内核空间返回用户空间,其代码定义如下:

arch/arm64/entry.S
/*
 * Ok, we need to do extra processing, enter the slow path.
 */
work_pending:
	mov	x0, sp				// 寄存器x0存放第一个参数regs,寄存器x1存放第二个参数task_struct.thread_info.flags */
	bl	do_notify_resume
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on		// 在用户空间执行时开启中断
#endif
	ldr	x1, [tsk, #TSK_TI_FLAGS]	// 重新检查单步执行
	b	finish_ret_to_user
/*
 * "slow" syscall return path.
 */
ret_to_user:
	disable_daif		/* 禁止中断 */
	ldr	x1, [tsk, #TSK_TI_FLAGS]
	and	x2, x1, #_TIF_WORK_MASK
	cbnz	x2, work_pending
finish_ret_to_user:
	enable_step_tsk x1, x2
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
	bl	stackleak_erase
#endif
	kernel_exit 0
ENDPROC(ret_to_user)

work_pending调用函数do_notify_resume,函数do_notify_resume的代码如下:

arch/arm64/kernel/signal.c
asmlinkage void do_notify_resume(struct pt_regs *regs,
				 unsigned long thread_flags)
{
	/*
	 * The assembly code enters us with IRQs off, but it hasn't
	 * informed the tracing code of that for efficiency reasons.
	 * Update the trace code with the current status.
	 */
	trace_hardirqs_off();

	do {
		/* Check valid user FS if needed */
		addr_limit_user_check();

		if (thread_flags & _TIF_NEED_RESCHED) {		/* 如果当前进程的thread_info.flags设置 */
			/* Unmask Debug and SError for the next task */
			local_daif_restore(DAIF_PROCCTX_NOIRQ);		/* 了标志位_TIF_NEED_RESCHED, */
													/* 那么以调度进程 */
			schedule();
		} else {
			local_daif_restore(DAIF_PROCCTX);

			if (thread_flags & _TIF_UPROBE)		/* 如果设置了标志位_TIF_UPROBE,调用函数 */
				uprobe_notify_resume(regs);		/* uprobe_notify_resume()处理 */

			if (thread_flags & _TIF_SIGPENDING)		/* 如果设置了标志位_TIF_SIGPENDING,调用 */
				do_signal(regs);		/* 函数do_signal()处理信号 */

			if (thread_flags & _TIF_NOTIFY_RESUME) {		/* 如果设置了标志位_TIF_NOTIFY_RESUME, */
				clear_thread_flag(TIF_NOTIFY_RESUME);		/* 那么调用函数tracehook_notify_resume(), */
				tracehook_notify_resume(regs);		/* 执行返回用户模式之前的回调函数 */
				rseq_handle_notify_resume(NULL, regs);
			}

			if (thread_flags & _TIF_FOREIGN_FPSTATE)		/* 如果设置了标志_TIF_FOREIGN_FPSTATE, */
				fpsimd_restore_current_state();		/* 那么恢复浮点寄存器 */
		}

		local_daif_mask();
		thread_flags = READ_ONCE(current_thread_info()->flags);
	} while (thread_flags & _TIF_WORK_MASK);
}

猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/106891526