Linux系统调用内核态分析

版权所有,转载请标明出处   All right reserved,Copyright by 徐行而至 浅唱而归


这里我们分析下linux2.6.38中ARM处理器中的系统调用,当用户程序进行系统调用时就会产生软中断,此时会跳转至异常向量表中软中断对应的入口处,下面为异常向量表(\arch\arm\kernel\entry-armv.S):

__vectors_start:
 ARM(	swi	SYS_ERROR0	)
 THUMB(	svc	#0		)
 THUMB(	nop			)
	W(b)	vector_und + stubs_offset
	W(ldr)	pc, .LCvswi + stubs_offset
	W(b)	vector_pabt + stubs_offset
	W(b)	vector_dabt + stubs_offset
	W(b)	vector_addrexcptn + stubs_offset
	W(b)	vector_irq + stubs_offset
	W(b)	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:

.LCvswi:
	.word	vector_swi

因此,代码就跳转至vector_swi系统调用入口处执行(其中stubs_offset是系统在启动的时候代码从如flash之类的存储介质中重定位到内存中产生的地址偏移),如下(\arch\arm\kernel\entry-common.S):
ENTRY(vector_swi)
	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0 - r12
 ARM(	add	r8, sp, #S_PC		)
 ARM(	stmdb	r8, {sp, lr}^		)	@ Calling sp, lr
 THUMB(	mov	r8, sp			)
 THUMB(	store_user_sp_lr r8, r10, S_SP	)	@ calling sp, lr
	mrs	r8, spsr			@ called from non-FIQ mode, so ok.
	str	lr, [sp, #S_PC]			@ Save calling PC
	str	r8, [sp, #S_PSR]		@ Save CPSR
	str	r0, [sp, #S_OLD_R0]		@ Save OLD_R0
	zero_fp

	/*
	 * Get the system call number.
	 */

#if defined(CONFIG_OABI_COMPAT)

	/*
	 * If we have CONFIG_OABI_COMPAT then we need to look at the swi
	 * value to determine if it is an EABI or an old ABI call.
	 */
#ifdef CONFIG_ARM_THUMB
	tst	r8, #PSR_T_BIT
	movne	r10, #0				@ no thumb OABI emulation
	ldreq	r10, [lr, #-4]			@ get SWI instruction
#else
	ldr	r10, [lr, #-4]			@ get SWI instruction
  A710(	and	ip, r10, #0x0f000000		@ check for SWI		)
  A710(	teq	ip, #0x0f000000						)
  A710(	bne	.Larm710bug						)
#endif
#ifdef CONFIG_CPU_ENDIAN_BE8
	rev	r10, r10			@ little endian instruction
#endif

#elif defined(CONFIG_AEABI)

	/*
	 * Pure EABI user space always put syscall number into scno (r7).
	 */
  A710(	ldr	ip, [lr, #-4]			@ get SWI instruction	)
  A710(	and	ip, ip, #0x0f000000		@ check for SWI		)
  A710(	teq	ip, #0x0f000000						)
  A710(	bne	.Larm710bug						)

#elif defined(CONFIG_ARM_THUMB)

	/* Legacy ABI only, possibly thumb mode. */
	tst	r8, #PSR_T_BIT			@ this is SPSR from save_user_regs
	addne	scno, r7, #__NR_SYSCALL_BASE	@ put OS number in
	ldreq	scno, [lr, #-4]

#else

	/* Legacy ABI only. */
	ldr	scno, [lr, #-4]			@ get SWI instruction
  A710(	and	ip, scno, #0x0f000000		@ check for SWI		)
  A710(	teq	ip, #0x0f000000						)
  A710(	bne	.Larm710bug						)

#endif

#ifdef CONFIG_ALIGNMENT_TRAP
	ldr	ip, __cr_alignment
	ldr	ip, [ip]
	mcr	p15, 0, ip, c1, c0		@ update control register
#endif
	enable_irq

	get_thread_info tsk
	adr	tbl, sys_call_table		@ load syscall table pointer

#if defined(CONFIG_OABI_COMPAT)
	/*
	 * If the swi argument is zero, this is an EABI call and we do nothing.
	 *
	 * If this is an old ABI call, get the syscall number into scno and
	 * get the old ABI syscall table address.
	 */
	bics	r10, r10, #0xff000000
	eorne	scno, r10, #__NR_OABI_SYSCALL_BASE
	ldrne	tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
	bic	scno, scno, #0xff000000		@ mask off SWI op-code
	eor	scno, scno, #__NR_SYSCALL_BASE	@ check OS number
#endif

	ldr	r10, [tsk, #TI_FLAGS]		@ check for syscall tracing
	stmdb	sp!, {r4, r5}			@ push fifth and sixth args

#ifdef CONFIG_SECCOMP
	tst	r10, #_TIF_SECCOMP
	beq	1f
	mov	r0, scno
	bl	__secure_computing	
	add	r0, sp, #S_R0 + S_OFF		@ pointer to regs
	ldmia	r0, {r0 - r3}			@ have to reload r0 - r3
1:
#endif

	tst	r10, #_TIF_SYSCALL_TRACE		@ are we tracing syscalls?
	bne	__sys_trace

	cmp	scno, #NR_syscalls		@ check upper syscall limit
	adr	lr, BSYM(ret_fast_syscall)	@ return address
	ldrcc	pc, [tbl, scno, lsl #2]		@ call sys_* routine

	add	r1, sp, #S_OFF
2:	mov	why, #0				@ no longer a real syscall
	cmp	scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
	eor	r0, scno, #__NR_SYSCALL_BASE	@ put OS number back
	bcs	arm_syscall	
	b	sys_ni_syscall			@ not private func
ENDPROC(vector_swi)

上述代码实际是获取系统调用号,然后根据调用号在系统调用跳转表中索引出实际要执行的系统调用函数入口(ldrccpc, [tbl, scno, lsl #2]@ call sys_* routine),并进入执行;tbl中存放的即为系统调用跳转表起始地址,根据EABI,OABI配置的不同会有不同的跳转表sys_call_table或sys_oabi_call_table,实际上两个跳转表相同,如下:
sys_call_table:
ENTRY(sys_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
sys_oabi_call_table:
ENTRY(sys_oabi_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
我们可以看到两个跳转表中都是calls.S这个文件中的内容

在上述代码中,我们会发现EABI很让人费解,下面就进行简单介绍:

从上面的代码我们会发现EABI,OABI,跟系统调用表(sys_call_table,sys_oabi_call_table )的选择有关

下面讲下内核是怎么处理这一问题的。 
sys_call_table,sys_oabi_call_table都是内核中系统调用的跳转表,根据配置EABI或OABI的不同,会选择不同的跳转表,并将跳转表起始地址存到tbl(r8寄存器, tbl  .req   r8)中,在系统调用跳转表中存储的是一系列系统调用函数的指针。如sys_select,在应用程序中调用select函数时会向内核传送一个系统调用号(对每个系统调用函数都是独一无二的),此时异常处理(上述代码)根据系统调用号(即系统调用跳转表的索引号)在系统调用表中(sys_call_table或sys_oabi_call_table )找到内核的实际系统调用函数sys_select,然后运行该函数。


关于EABI,OABI:

EABI (Extended ABI),说的是这样的一种新的系统调用方式

mov r7, #num 
swi 0x0

原来的系统调用方式是这样, 
swi (#num | 0x900000) (0x900000是个magic值)

也就是说原来的调用方式(Old ABI)是通过跟随在swi指令中的调用号来进行的,现在的是根据r7中的值。

现在看两个宏,一个是 
CONFIG_OABI_COMPAT 意思是说和old ABI兼容

另一个是 
CONFIG_AEABI 意思是说指定现在的方式为EABI

这两个宏可以同时配置,也可以都不配,也可以配置任何一种。

 

首先,对于old ABI,内核给出的处理是给它建立一个单独的system call table,叫sys_oabi_call_table,这样,兼容方式下就会有两个system call table, 以old ABI方式的系统调用会执行old_syscall_table表中的系统调用函数,EABI方式的系统调用会用sys_call_table中的函数指针。

配置无外乎以下4中

第一 两个宏都配置 行为就是上面说的那样

第二 只配置CONFIG_OABI_COMPAT , 那么以old ABI方式调用的会用sys_oabi_call_table,以EABI方式调用的 用sys_call_table,和1实质相同,只是情况1更加明确。

第三 只配置CONFIG_AEABI 系统中不存在 sys_oabi_call_table, 对old ABI方式调用不兼容。只能 以EABI方式调用,用sys_call_table

第四 两个都没有配置 系统默认会只允许old ABI方式,但是不存在old_syscall_table,最终会通过sys_call_table 完成函数调用



下面就进入系统调用跳转表(更准确的说应该叫索引表,sys_call_table或sys_oabi_call_table),内容在\arch\arm\kernel\calls.S中,如下(由于内容太多,只选取部分):

/* 0 */		CALL(sys_restart_syscall)
		CALL(sys_exit)
		CALL(sys_fork_wrapper)
		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_wrapper)
		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实际是宏定义,在arch/arm/kernel/entry-common.S中,如下:

	.equ NR_syscalls,0
#define CALL(x) .equ NR_syscalls,NR_syscalls+1
#include "calls.S"
#undef CALL
#define CALL(x) .long x
在定义宏CALL()的地方,我们看到calls.S已经被包含了一次,只不过在这里,不是为了建立系统调用表,而仅仅是为了获得系统的系统调用的数量,并保存在宏NR_syscalls中。在SWI异常处理中,我们也看到,是使用了这个宏NR_syscalls来检查用户空间传进的调用号是否有效;
从上面我们可以看到最后有效的宏定义是#define CALL(x) .long x,前面的只是用来计算系统调用的个数(用法很妙),因此CALL(sys_select)就是.long sys_select,也就是sys_select函数的入口地址,其他一样;

以上就根据系统用户空间的传进的系统调用号,在改变中索引出了实际系统调用函数的入口,并跳转至该函数执行;



下面我们以select系统调用进行分析,其系统调用号存放在linux/arch/arm/include/asm/unistd_32.h文件中,如下

#ifndef __ASM_ARM_UNISTD_H
#define __ASM_ARM_UNISTD_H

#define __NR_OABI_SYSCALL_BASE	0x900000

#if defined(__thumb__) || defined(__ARM_EABI__)
#define __NR_SYSCALL_BASE	0
#else
#define __NR_SYSCALL_BASE	__NR_OABI_SYSCALL_BASE
#endif

/*
 * This file contains the system call numbers.
 */
……

#define __NR__newselect			(__NR_SYSCALL_BASE+142)
……

但是,我们在内核源码中搜索 sys_select时,并未找到其函数实例,只找到其 系统调用原型如下:

/include/linux/syscalls.h

asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
			fd_set __user *exp, struct timeval __user *tvp);

那么sys_select到底在哪里定义?

这里首先给出sys_select()的定义,在select.c中(/fs/select.c)

SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
		fd_set __user *, exp, struct timeval __user *, tvp)
{
	struct timespec end_time, *to = NULL;
	struct timeval tv;
	int ret;

	if (tvp) {
		if (copy_from_user(&tv, tvp, sizeof(tv)))
			return -EFAULT;

		to = &end_time;
		if (poll_select_set_timeout(to,
				tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
				(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
			return -EINVAL;
	}

	ret = core_sys_select(n, inp, outp, exp, to);
	ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);

	return ret;
}

首先来解释一下SYSCALL_DEFINEx 中的x表示上层应用函数的参数个数,比如select()函数有5个参数,因此会对应到SYSCALL_DEFINE5。sname表示的就是上层应用程序中函数的名字,如select。


下面我们来分析SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
fd_set __user *, exp, struct timeval __user *, tvp)

SYSCALL_DEFINE5是个宏,在/include/linux/syscalls.h 中定义了如下的宏:

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

因此可分解得到如下代码:

SYSCALL_DEFINEx(5, _##select, __VA_ARGS__)

SYSCALL_DEFINEx又定义为如下宏:
#define SYSCALL_DEFINEx(x, sname, ...)				\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)


由此可进一步分解如下:
__SYSCALL_DEFINEx(5,_##select, __VA_ARGS__)

__SYSCALL_DEFINEx又定义如下:
#define __SYSCALL_DEFINEx(x, name, ...)					\
	asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))


由此再分解为如下:
asmlinkage long sys##_##select(__SC_DECL##5(__VA_ARGS__))

asmlinkage long sys_select(__SC_DECL5(__VA_ARGS__))

这样结果已经开始清晰了,对于__SC_DECL5也是宏,如下:
#define __SC_DECL1(t1, a1)	t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)
#define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)
#define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)
#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)
__SC_DECL5实际为嵌套宏,最后可分解为:t5 a5,t4 a4,t3 a3,t2 a2,t1 a1 

即int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp

也就是asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timeval __user *tvp);


最后,我们就清楚的了解了系统调用的过程,其他系统调用同理


本文参考:

http://blog.csdn.net/hongjiujing/article/details/6831192

http://www.cnblogs.com/zhuyp1015/archive/2012/05/29/2524936.html



你可能感兴趣的:(linux源码分析)