本文基于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);
}