linux 系统调用(二)源码分析

本文基于linux3.10内核

1 应用层处理

在Linux下系统调用是用软中断实现的,下面以一个简单的open例子简要分析一下应用层的open是如何调用到内核中的sys_open的。在glibc库中,通过封装例程(Wrapper Routine)将API和系统调用关联起来。API是头文件中所定义的函数接口,而位于glibc中的封装例程则是对该API对应功能的具体实现。事实上,我们知道接口open()所要完成的功能是通过系统调用open()完成的,因此封装例程要做的工作就是先将接口open()中的参数复制到相应的寄存器中,然后引发一个异常,从而系统进入内核区执行sys_open(),最后当系统调用执行完毕后,封装例程还要将错误码返回到应用程序中。

先来看一下下面这段程序:

int main(){
        int fd;
        fd=open(".",O_RDWR);
}

然后对该程序进行静态编译,并且反汇编:

arm-linux-gcc -o test main.c --static
arm-linux-objdump -D test
000082f0 <main>:
    82f0:	e92d4800 	push	{fp, lr}
    82f4:	e28db004 	add	fp, sp, #4	; 0x4
    82f8:	e24dd010 	sub	sp, sp, #16	; 0x10
    82fc:	ebffffd5 	bl	8258 <test>
    8300:	e1a03000 	mov	r3, r0
    8304:	e50b300c 	str	r3, [fp, #-12]
    8308:	e59f002c 	ldr	r0, [pc, #44]	; 833c <main+0x4c>
    830c:	e3a01002 	mov	r1, #2	; 0x2
    8310:	eb002e82 	bl	13d20 <__libc_open>
    8314:	e1a03000 	mov	r3, r0
    8318:	e50b3008 	str	r3, [fp, #-8]
    831c:	e59f301c 	ldr	r3, [pc, #28]	; 8340 <main+0x50>

可以看到在main 函数中,调用了__libc_open函数,再看一下__libc_open:

00013d20 <__libc_open>:
   13d20:	e51fc028 	ldr	ip, [pc, #-40]	; 13d00 <___fxstat64+0x50>
   13d24:	e79fc00c 	ldr	ip, [pc, ip]
   13d28:	e33c0000 	teq	ip, #0	; 0x0
   13d2c:	1a000006 	bne	13d4c <__libc_open+0x2c>
   13d30:	e1a0c007 	mov	ip, r7
   13d34:	e3a07005 	mov	r7, #5	; 0x5    //open的系统调用号5
   13d38:	ef000000 	svc	0x00000000   //产生软中断
   13d3c:	e1a0700c 	mov	r7, ip
   13d40:	e3700a01 	cmn	r0, #4096	; 0x1000
   13d44:	312fff1e 	bxcc	lr
   13d48:	ea0008b0 	b	16010 <__syscall_error>

__libc_open的实现应该在glib封装函数中,以r7作为传递系统调用号的寄存器,并且使用svc    0x00000000命令陷入内核,这种处理方式说明使用的是比较新的EABI方式的系统调用。

注:好多资料上说陷入软中断是使用SWI指令,ARM官网解释:

SVC 超级用户调用。
语法:

SVC{cond} #immed

其中:

cond

    是一个可选的条件代码(请参阅条件执行)。
immed

    是一个表达式,其取值为以下范围内的一个整数:

        在 ARM 指令中为 0 到 224–1(24 位值)

        在 16 位 Thumb 指令中为 0-255(8 位值)。

用法

SVC 指令会引发一个异常。 这意味着处理器模式会更改为超级用户模式,CPSR 会保存到超级用户模式 SPSR,并且执行会跳转到 SVC 向量(请参阅《开发指南》中的第 6 章 处理处理器异常)。

处理器会忽略 immed。 但异常处理程序会获取它,借以确定所请求的服务。
Note

作为 ARM 汇编语言开发成果的一部分,SWI 指令已重命名为 SVC。 在此版本的 RVCT 中,SWI 指令反汇编为 SVC,并提供注释以指明这是以前的 SWI。
条件标记

此指令不更改标记。
体系结构

此 ARM 指令可用于所有版本的 ARM 体系结构。

 中断向量表的初始化参考如下博文:

linux3.10 中断处理过程(一)中断初始化及irq处理详解(汇编部分)_oqqYuJi12345678的博客-CSDN博客

上面从反汇编来推测libc对于linux 系统调用的封装方式,下面摘录一下网上其他网友分析的libc源码的具体实现形式,具体情况估计是由于libc版本的原因跟反汇编看到的有些差异,不过基本思想是一致的!

通常情况下,我们写的用户空间应用程序都是通过封装的C lib来调用系统调用的。以0.9.30版uClibc中的open为例,来追踪一下这个封装的函数是如何一步一步的调用系统调用的。在include/fcntl.h中有定义:

# define open open64

open实际上只是open64的一个别名而已。

在libc/sysdeps/linux/common/open64.c中可以看到:

extern __typeof(open64) __libc_open64;

extern __typeof(open) __libc_open;

可见open64也只不过是__libc_open64的别名,而__libc_open64函数在同一个文件中定义:

libc_hidden_proto(__libc_open64)
int __libc_open64 (const char *file,int oflag, ...)
{
   mode_t mode = 0;
   if (oflag & O_CREAT)
   {
   va_listarg;
   va_start(arg, oflag);
   mode= va_arg (arg, mode_t);
   va_end(arg);
   }
   return __libc_open(file, oflag | O_LARGEFILE, mode);
}
libc_hidden_def(__libc_open64)

最终__libc_open64又调用了__libc_open函数,这个函数在文件libc/sysdeps/linux/common/open.c中定义:

libc_hidden_proto(__libc_open)
int __libc_open(const char *file, intoflag, ...)
{
   mode_tmode = 0;
   if(oflag & O_CREAT) {
      va_listarg;
      va_start(arg, oflag);
      mode= va_arg (arg, mode_t);
      va_end (arg);
   }
   return__syscall_open(file, oflag, mode);
}
libc_hidden_def(__libc_open)

这个函数,也是仅仅根据打开标志oflag的值,来判断是否有第三个参数,若由,则获得其值。之后,便用获得的参数来调用__syscall_open(file,oflag, mode)

__syscall_open在同一个文件中定义:

static __inline__ _syscall3(int,__syscall_open, const char *, file,
      int,flags, __kernel_mode_t, mode)

在文件libc/sysdeps/linux/arm/bits/syscalls.h文件中可以看到:

#undef _syscall3
#define_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3arg3) \
{ \
    return (type) (INLINE_SYSCALL(name, 3,arg1, arg2, arg3)); \
}

这个宏实际上完成定义一个函数的工作,宏的第一个参数是函数的返回值类型,第二个参数是函数名,之后的参数就如同它们的参数名所表明的那样,分别是函数的参数类型及参数名。__syscall_open实际上为:
 

int __syscall_open (const char * file,intflags, __kernel_mode_t mode)
{
    return (int) (INLINE_SYSCALL(__syscall_open,3, file, flags, mode));
}

INLINE_SYSCALL为同一个文件中定义的宏:

#undef INLINE_SYSCALL
#define INLINE_SYSCALL(name, nr,args...)            \
 ({ unsigned int _inline_sys_result = INTERNAL_SYSCALL (name, , nr,args);   \
    if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_inline_sys_result, ),0))  \
      {                        \
        __set_errno (INTERNAL_SYSCALL_ERRNO(_inline_sys_result, ));    \
        _inline_sys_result = (unsigned int) -1;          \
      }                        \
    (int) _inline_sys_result; })

INLINE_SYSCALL宏中最值得注意的是INTERNAL_SYSCALL,其定义如下:

#undef INTERNAL_SYSCALL
#if !defined(__thumb__)
#if defined(__ARM_EABI__)
#define INTERNAL_SYSCALL(name, err, nr,args...)        \
 ({unsigned int __sys_result;                 \
    {                          \
      register int _a1 __asm__ ("r0"), _nr __asm__ ("r7");    \
      LOAD_ARGS_##nr (args)                \
      _nr = SYS_ify(name);                 \
      __asm__ __volatile__ ("swi  0x0   @ syscall " #name  \
              : "=r" (_a1)            \
              : "r" (_nr) ASM_ARGS_##nr        \
              : "memory");            \
          __sys_result = _a1;               \
    }                          \
    (int) __sys_result; })
#else /* defined(__ARM_EABI__) */
#define INTERNAL_SYSCALL(name, err, nr,args...)        \
 ({ unsigned int __sys_result;                \
    {                          \
      register int _a1 __asm__ ("a1");               \
      LOAD_ARGS_##nr (args)                \
      __asm__ __volatile__ ("swi  %1 @ syscall " #name  \
           : "=r" (_a1)               \
           : "i" (SYS_ify(name))ASM_ARGS_##nr    \
           : "memory");               \
      __sys_result = _a1;                  \
    }                          \
    (int) __sys_result; })
#endif

这里也将同文件中的LOAD_ARGS宏的定义贴出来:

#define LOAD_ARGS_0()
#define ASM_ARGS_0
#define LOAD_ARGS_1(a1)           \
 _a1 = (int) (a1);            \
 LOAD_ARGS_0 ()
#define ASM_ARGS_1 ASM_ARGS_0, "r" (_a1)
#define LOAD_ARGS_2(a1, a2)       \
 register int _a2 __asm__ ("a2") = (int) (a2);   \
 LOAD_ARGS_1 (a1)
#define ASM_ARGS_2 ASM_ARGS_1, "r" (_a2)
#define LOAD_ARGS_3(a1, a2, a3)         \
 register int _a3 __asm__ ("a3") = (int) (a3);   \
 LOAD_ARGS_2 (a1, a2)

这几个宏用来在寄存器中加载相应的参数,参数传递的方式和普通的C函数也没有什么太大的区别,同样都是将参数列表中的参数依次放入寄存器r0、r1、r2、r3…中。

上面的SYS_ify(name)宏,是用来获得系统调用号的。

#define SYS_ify(syscall_name)  (__NR_##syscall_name)

也就是__NR___syscall_open,在libc/sysdeps/linux/common/open.c中可以看到这个宏的定义:

#define __NR___syscall_open __NR_open

__NR_open在内核代码的头文件中有定义。

在这里我们忽略定义__thumb__的情况,而假设我们编译出来的库函数使用的都是ARM指令集。在上面的代码中,我们看到,根据是否定义宏__ARM_EABI__INTERNAL_SYSCALL会被展开为两种不同的版本。关于这一点,与应用二进制接口ABI有关,不同的ABI,则会有不同的传递系统调用号的方法。对于比较新的EABI,则在r7寄存器保存系统调用号,通过swi   0x0来陷入内核。否则,通过swi指令的24位立即数参数来传递系统调用号。后面还会有内核中关于这个问题的更详细的说明。

同时这两种调用方式的系统调用号也是存在这区别的,在内核的文件arch/arm/inclue/asm/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_restart_syscall      (__NR_SYSCALL_BASE+  0)
#define __NR_exit        (__NR_SYSCALL_BASE+  1)
#define __NR_fork        (__NR_SYSCALL_BASE+  2)
#define __NR_read        (__NR_SYSCALL_BASE+  3)
#define __NR_write       (__NR_SYSCALL_BASE+  4)
#define __NR_open        (__NR_SYSCALL_BASE+  5)

最终INLINE_SYSCALL展开如下:

#if defined(__ARM_EABI__)
#define INTERNAL_SYSCALL(name, err, nr,args...)        \
 ({unsigned int __sys_result;                 \
    {                          \
      register int _a1 __asm__ ("r0"), _nr __asm__ ("r7");    \
      register int _a3 __asm__ ("a3") = (int) (a3); \
      register int _a2 __asm__ ("a2") = (int) (a2);\
      _nr = __NR_open;             \    \
      __asm__ __volatile__ ("swi  0x0   @ syscall " #name  \
              : "=r" (_a1)            \
              : "r" (_nr) ,"r" (_a1),"r" (_a2),"r" (_a3)      \
              : "memory");            \
          __sys_result = _a1;               \
    }                          \
    (int) __sys_result; })
#else /* defined(__ARM_EABI__) */
#define INTERNAL_SYSCALL(name, err, nr,args...)        \
 ({ unsigned int __sys_result;                \
    {                          \
      register int _a1 __asm__ ("a1");               \
      register int _a3 __asm__ ("a3") = (int) (a3);
      register int _a2 __asm__ ("a2") = (int) (a2);              \
      __asm__ __volatile__ ("swi  %1 @ syscall " #name  \
           : "=r" (_a1)               \
           : "i" (__NR_open),"r" (_a1),"r" (_a2),"r" (_a3)    \
           : "memory");               \
      __sys_result = _a1;                  \
    }                          \
    (int) __sys_result; })
#endif

上面代码参考自该博文:ARM Linux系统调用的原理_hongjiujing的博客-CSDN博客

2 内核层处理

linux异常处理过程如下图所示:

2.1 系统调用陷入

linux内核的中断向量表如下所示:

__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

当使用svc 指令陷入内核以后,执行的中断向量函数就是这条:W(ldr)    pc, .LCvswi + stubs_offset

具体看一下vector_swi的实现,源码在arch/arm/kernel/entry-common.S中

ENTRY(vector_swi)
	sub	sp, sp, #S_FRAME_SIZE   //陷入内核以后,sp已经切换成svc特权模式下的栈了,先把该栈顶向下移动S_FRAME_SIZE个单位,为保留操作做准备
	stmia	sp, {r0 - r12}			@ Calling r0 - r12  //把r0 ~ r12寄存器放入栈中,ia是increase after的意思,r0放在栈顶的位置,不更新sp
 ARM(	add	r8, sp, #S_PC		)  //移动r8到sp+#S_PC的位置
 ARM(	stmdb	r8, {sp, lr}^		)	@ Calling sp, lr  //指令中的“^”符号表示访问user mode的寄存器,db”是decrement before,该指令把用户态的sp,和lr放到栈中偏移#S_PC+4的位置
	mrs	r8, spsr		//读取用户态的cpsr	@ called from non-FIQ mode, so ok.
	str	lr, [sp, #S_PC]		//把返回地址放入栈中偏移#S_PC的位置	@ Save calling PC
	str	r8, [sp, #S_PSR]	//保存	用户态的cpsr@ Save CPSR
	str	r0, [sp, #S_OLD_R0]	//保存旧的r0,后面会用到该寄存器	@ Save OLD_R0
	zero_fp
------------------------------------------(1)
	/*
	 * 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.
	 */
//当定义了老的OABI传参模式,则从返回地址-4,得到原来那条svc 陷入指令
	ldr	r10, [lr, #-4]			@ get SWI instruction
#ifdef CONFIG_CPU_ENDIAN_BE8
	rev	r10, r10			@ little endian instruction
#endif

#elif defined(CONFIG_AEABI)
//如果定义了AEABI,则系统调用号在r7中,这边什么都不做
	/*
	 * Pure EABI user space always put syscall number into scno (r7).
	 */

	/* Legacy ABI only. */
#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
#endif
#ifdef CONFIG_ALIGNMENT_TRAP
	ldr	ip, __cr_alignment
	ldr	ip, [ip]
	mcr	p15, 0, ip, c1, c0		@ update control register
#endif
	enable_irq  //陷入异常以后cpu 默认禁止中断,这边开启中断
	ct_user_exit

	get_thread_info tsk  //tsk是r9 寄存器的别名,把thread_info放入r9寄存器
-----------------------------------(2)
	adr	tbl, sys_call_table		@ load syscall table pointer//tbl是r8寄存器的别名,把系统调用表放入r8寄存器中
------------------------------------(3)

#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  //OABI 方式从svc指令中取出系统调用号,放入scno中
	ldrne	tbl, =sys_oabi_call_table //OABI用的系统调用表更新为sys_oabi_call_table,其实起始地址和sys_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

local_restart:
	ldr	r10, [tsk, #TI_FLAGS]    //取出thread_info中的TI_FLAGS放入r10中,该位跟是否需要调度有关@ check for syscall tracing
	stmdb	sp!, {r4, r5}		//把r4,r5压入栈,!符号表示同时更新sp的值,这个时候的sp的值是刚陷入内核的时候的sp-#S_FRAME_SIZE-8	@ push fifth and sixth args
这两个寄存器中保存着系统调用第五个和第六个参数(如果存在)
-----------------------------------------------------(4)

	tst	r10, #_TIF_SYSCALL_WORK		@ 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  //利用系统调用表加系统调用号的偏移,跳转到对应的系统调用函数进行处理
      -------------------------------------------(5)
	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)

内核定义了如下寄存器别名:

scno    .req    r7        @ syscall number
tbl    .req    r8        @ syscall table pointer
why    .req    r8        @ Linux syscall (!= 0)
tsk    .req    r9        @ current thread_info

(1)主要是保留现场做的处理,上面处理完以后,sp的形式如下:

高地址
---------------------------------------------------陷入内核时sp的位置
S_OLD_R0,	
S_PSR,	
S_PC,	
S_LR,	
S_SP,	
S_IP,	
S_FP,	
S_R10,	
S_R9,	
S_R8,	
S_R7,	
S_R6,	
S_R5,	
S_R4,	
S_R3,	
S_R2
S_R1
S_R0	
----------------------------------------------------sp保存完寄存器的地址
低地址

(2)get_thread_info tsk

get_thread_info是个宏定义:

.macro	get_thread_info, rd
mov	\rd, sp, lsr #13
mov	\rd, \rd, lsl #13
.endm

linux thread_info内核栈是在同一块内存中,一般设置为8K,所以把内核栈8K字节对齐以后,就能获取到thread_info的地址,并把该地址放入tsk寄存器中

(3)adr    tbl, sys_call_table

看一下系统调用表是如何定义的,在arch/arm/kernel/entry-common.S中:

.type	sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"

主要是calls.S这个头文件,比较有意思的是,在entry-common.S文件前面的部分,有这样一段代码:

.equ NR_syscalls,0
#define CALL(x) .equ NR_syscalls,NR_syscalls+1
#include "calls.S"

/*
 * Ensure that the system call table is equal to __NR_syscalls,
 * which is the value the rest of the system sees
 */
.ifne NR_syscalls - __NR_syscalls
.error "__NR_syscalls is not equal to the size of the syscall table"
.endif

#undef CALL
#define CALL(x) .long x

先定义了CALL(x)为NR_syscalls,NR_syscalls+1,然后#include "calls.S",calls.S中的内容如下:

/* 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)
。。。。。。。。。。。。。。。。。。。。。。。。。。。。

所以第一次#include "calls.S"时只是计算了最大的系统调用数,并把该值赋值给NR_syscalls,然后重新定义了CALL(x) .long x,在后面的sys_call_table中#include "calls.S"的时候,才真正去初始化了系统调用表。

(4)stmdb    sp!, {r4, r5}

做完4以后,sp的状态如下:

高地址
---------------------------------------------------陷入内核时sp的位置
S_OLD_R0,	
S_PSR,	
S_PC,	
S_LR,	
S_SP,	
S_IP,	
S_FP,	
S_R10,	
S_R9,	
S_R8,	
S_R7,	
S_R6,	
S_R5,	
S_R4,	
S_R3,	
S_R2
S_R1
S_R0	
R4
R5
----------------------------------------------------sp现在的位置
低地址

(5)ldrcc    pc, [tbl, scno, lsl #2]   

去执行具体的系统调用函数

2.2系统调用返回

系统调用处理 完以后,从ret_fast_syscall返回:

ret_fast_syscall:
 UNWIND(.fnstart	)
 UNWIND(.cantunwind	)
	disable_irq			//关闭中断	@ disable interrupts
	ldr	r1, [tsk, #TI_FLAGS]  //从thread info中获取#TI_FLAGS,
	tst	r1, #_TIF_WORK_MASK  //返回user之前,_TIF_WORK_MASK 中的位如果有值,则需要去处理相关任务
	bne	fast_work_pending   //先不返回,去处理pending的相关事务,比如系统调度
	asm_trace_hardirqs_on

	/* perform architecture specific actions before user return */
	arch_ret_to_user r1, lr    //这两个命令不是重点
	ct_user_enter

	restore_user_regs fast = 1, offset = S_OFF //恢复用户寄存器,并返回用户态
---------------------------------------(1)
 UNWIND(.fnend		)

/*
 * Ok, we need to do extra processing, enter the slow path.
 */
fast_work_pending:   //penging相关的处理,暂时不分析
	str	r0, [sp, #S_R0+S_OFF]!		@ returned r0
work_pending:
	mov	r0, sp				@ 'regs'
	mov	r2, why				@ 'syscall'
	bl	do_work_pending
	cmp	r0, #0
	beq	no_work_pending
	movlt	scno, #(__NR_restart_syscall - __NR_SYSCALL_BASE)
	ldmia	sp, {r0 - r6}			@ have to reload r0 - r6
	b	local_restart			@ ... and off we go

(1)restore_user_regs fast = 1, offset = S_OFF

S_OFF的值为8,返回的代码处理都在该宏里面:

.macro	restore_user_regs, fast = 0, offset = 0
	ldr	r1, [sp, #\offset + S_PSR]	//之前sp的位置偏移了8,所以这边要偏移要8+S_PSR才能找到原来存放用户态cpsr的位置@ get calling cpsr
	ldr	lr, [sp, #\offset + S_PC]!	//获取pc,并且更新了sp的位置到S_PC@ get pc
	msr	spsr_cxsf, r1		//把用户态cpsr放入svc 模式的spsr中,为返回用户态做准备@ save in spsr_svc
#if defined(CONFIG_CPU_V6)
	strex	r1, r2, [sp]			@ clear the exclusive monitor
#elif defined(CONFIG_CPU_32v6K)
	clrex					@ clear the exclusive monitor
#endif
	.if	\fast
	ldmdb	sp, {r1 - lr}^		//fast为1,走这边,恢复用户态寄存器	@ get calling r1 - lr
	.else
	ldmdb	sp, {r0 - lr}^			@ get calling r0 - lr
	.endif
	mov	r0, r0				@ ARMv5T and earlier require a nop
						@ after ldm {}^
	add	sp, sp, #S_FRAME_SIZE - S_PC  //设置内核态sp的位置为进入内核态时候的位置,所以每次进入内核态,内核栈总是干净的
	movs	pc, lr		//把lr赋值给pc,执行陷入内核的下一条指令,同时s符号会把svc模式下的spsr自动压入cpsr,实现模式切换,进入用户态		@ return & move spsr_svc into cpsr
	.endm

2.3 具体内核系统调用函数

以sys_open为例,介绍一下内核的系统调用函数。

上面在陷入内核以后,具体的系统调用处理函数则为:sys_open。

内核并没有直接定义sys_open。

该函数声明如下:

asmlinkage long sys_open(const char __user *filename,
                int flags, int mode);

在内核源码中直接搜索sys_open,无法搜到它的实现代码,实际上它是在fs/open.c中实现的:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

其中SYSCALL_DEFINE3是一个宏:

syscalls.h (include\linux)

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

SYSCALL_DEFINEx也是一个宏:

syscalls.h (include\linux)

#define SYSCALL_DEFINEx(x, sname, ...)                \
    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

__SYSCALL_DEFINEx仍然是个宏:

syscalls.h (include\linux)

#define __SYSCALL_DEFINEx(x, name, ...)                    \
    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

所以展开后的结果就是:

asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__))

其中,__SC_DECL3定义如下:

syscalls.h (include\linux)

#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__)

所以最终的结果如下:

asmlinkage long sys_open(const char __user *filename, int flags, int mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值