请稍侯

ARM Linux系统调用

14 November 2022

ARM Linux系统调用

Linux系统调用是系统提供的从用户空间进入内核空间的方式。每一种系统调用在内核都实现了其对应功能。 在应用层我们看到的是open(),read(),write()等由C库封装好的接口,这些接口都对应了一个内核函数sys_xxx() [sys_open(),sys_read(),sys_write()]。下面开始分析C库函数是怎么调用到内核函数sys_xxx()。

先提出问题:

1、如何进入内核?

2、内核如何知道是发生了哪一种系统调用?

3、进入内核做了哪些操作?

现在开始解答这些问题:

1、C库函数通过软中断 swi 指令进入内核。(函数参数保存在r0->r3寄存器,如果参数超过4个,将超过的部分参数压入栈中)。

2、每一个系统调用都对应一个固定的系统调用号,在调用swi中断之前,C库函数会把系统调用号存入r7寄存器,当通过swi中断进入内核后,将r7寄存器中的系统调用号取出,通过这个系统调用号就能找到对应的处理函数sys_xxx()。

3.当 C库函数执行swi 指令后会自动的跳到内核设置好的中断向量表0x08处执行中断处理。中断向量表在内核启动时由start_kernel()调用early_trap_init() [路径:/arch/arm/kernel/trap.c] 将中断向量表拷贝到中断跳转地址0xFFFF0000(或0x00000000),当中断发生时,CPU会自动的跳转到这个地址执行中断处理。因为swi 指令会偏移0x80,所以实际的跳转地址就是 0xFFFF0000 + 0x08.

void __init early_trap_init(void *vectors_base)
{
	unsigned long vectors = (unsigned long)vectors_base;
	extern char __stubs_start[], __stubs_end[];
	extern char __vectors_start[], __vectors_end[];
	unsigned i;

	vectors_page = vectors_base;

	for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
		((u32 *)vectors_base)[i] = 0xe7fddef1;

    /* 将中断向量表拷贝到中断跳转地址,在arm中 中断跳转地址为0xFFFF0000 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

	kuser_init(vectors_base);

	flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
	modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

__vectors_start 和__vectors_end 定义在链接脚本 vmlinux.lds中。

	__vectors_start = .;
	.vectors 0 : AT(__vectors_start) {
		*(.vectors)
	}
	. = __vectors_start + SIZEOF(.vectors);
	__vectors_end = .;

	__stubs_start = .;
	.stubs 0x1000 : AT(__stubs_start) {
		*(.stubs)
	}
	. = __stubs_start + SIZEOF(.stubs);
	__stubs_end = .;

中断向量表的内容位于 /arch/arm/kernel/entry-armv.S中,在中断向量表中偏移0x08就是跳转到W(ldr) pc, __vectors_start + 0x1000 这个地址。这句话表明执行地址为__vectors_start + 0x1000

__vectors_start:
	W(b)	vector_rst
	W(b)	vector_und
	W(ldr)	pc, __vectors_start + 0x1000
	W(b)	vector_pabt
	W(b)	vector_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_irq
	W(b)	vector_fiq

从前面的连接脚本vmlinux.lds或者early_trap_init()都能发现__vectors_start 偏移 0x1000这个地址指向__stubs_start这个标号 , __stubs_start代码位于/arch/arm/kernel/entry-armv.S。

__stubs_start:
	@ This must be the first word
	.word	vector_swi

现在我们知道代码将进入vector_swi [/arc/arm/kernel/entry-common.S] 执行。 vector_swi [/arc/arm/kernel/entry-common.S] 的主要事情就是,保存中断现场,获取系统调用号,根据系统调用号找到对应的sys_xxx(),然后恢复中断现场。

	.type	sys_call_table, #object
ENTRY(sys_call_table)
/* calls.S在编译时展开, ENTRY(sys_call_table) 会跳转到对应的标号地址,进入sys_xxx()*/
#include "calls.S"

/* 略... */
ENTRY(vector_swi)

    /* 
     * 以上内容 略 。。。
     * 备份用户空间进程行下文 
     */
    addne	scno, r7, #__NR_SYSCALL_BASE  /* r7中的系统调用号保存到scno */	
    
     USER(	ldreq	scno, [lr, #-4]		)

#else
	/* Legacy ABI only. */
     USER(	ldr	scno, [lr, #-4]		)	@ get SWI instruction
#endif

	adr	tbl, sys_call_table		@ load syscall table pointer
    /* 略... */
local_restart:
    /*
     * 以上内容 略 。。
     */
	cmp	scno, #NR_syscalls		     /* 检查系统调用号 */
	adr	lr, BSYM(ret_fast_syscall)	 /* 保存返回地址 */
    ldrcc	pc, [tbl, scno, lsl #2]	 /* PC指针跳转到ENTRY(sys_call_table) + (scno * 4)执行sys_xxx()*/

calls.S 内容如下所示 [/arch/arm/kernel],到这里我们就调用到了C库函数对应的系统调用函数sys_xxx()。

/* 0 */		CALL(sys_restart_syscall)
		CALL(sys_exit)
		CALL(sys_fork)
		CALL(sys_read)
		CALL(sys_write)
/* 5 */		CALL(sys_open)
		CALL(sys_close)
		CALL(sys_ni_syscall)		/* was sys_waitpid */
		CALL(sys_creat)
		CALL(sys_link)
/* 10 */	CALL(sys_unlink)
		CALL(sys_execve)
		CALL(sys_chdir)
		CALL(OBSOLETE(sys_time))	/* used by libc4 */
		CALL(sys_mknod)
/* 15 */	CALL(sys_chmod)
		CALL(sys_lchown16)
		CALL(sys_ni_syscall)		/* was sys_break */
		CALL(sys_ni_syscall)		/* was sys_stat */
		CALL(sys_lseek)
/* 20 */	CALL(sys_getpid)
		CALL(sys_mount)
		CALL(OBSOLETE(sys_oldumount))	/* used by libc4 */
		CALL(sys_setuid16)
		CALL(sys_getuid16)
...
...
...

本作品由本站发表,未经授权,禁止转载。