您的位置:首页 > 运维架构 > Linux

浅析基于ARM的Linux下的系统调用的实现

2014-07-30 16:09 260 查看
在Linux下系统调用是用软中断实现的,下面以一个简单的open例子简要分析一下应用层的open是如何调用到内核中的sys_open的。

t8.c

#include<stdio.h>

[code]#include<sys/types.h>
#include<sys/stat.h>

#include<fcntl.h>


intmain(intargc,constchar*argv[])

{

intfd;


fd=open(".",O_RDWR);


close(fd);

return0;

}

[/code]

这里需要注意的是:open是C库提供的库函数,并不是系统调用,系统调用时在内核空间的,应用空间无法直接调用。在《Linux内核设计与实现》中说:要访问系统调用(在Linux中常称作syscall),通常通过C库中定义的函数调用来进行。

将t8.c进行静态编译,然后反汇编,看一下是如何调用open的?


arm-linux-gcct8.c--static

[code]arm-linux-objdump-Da.out>a.dis
[/code]

下面我们截取a.dis中的一部分进行说明:

......

[code]00008228<main>:
8228:e92d4800push{fp,lr}

822c:e28db004addfp,sp,#4;0x4

8230:e24dd010subsp,sp,#16;0x10

8234:e50b0010strr0,[fp,#-16]

8238:e50b1014strr1,[fp,#-20]

823c:e59f0028ldrr0,[pc,#40];826c<main+0x44>

8240:e3a01002movr1,#2;0x2;#defineO_RDWR00000002

8244:eb002e7dbl13c40<__libc_open>

8248:e1a03000movr3,r0

824c:e50b3008strr3,[fp,#-8]

8250:e51b0008ldrr0,[fp,#-8]

8254:eb002e9dbl13cd0<__libc_close>

8258:e3a03000movr3,#0;0x0

825c:e1a00003movr0,r3

8260:e24bd004subsp,fp,#4;0x4

8264:e8bd4800pop{fp,lr}

8268:e12fff1ebxlr

826c:00064b8c.word0x00064b8c
......

00013c40<__libc_open>:

13c40:e51fc028ldrip,[pc,#-40];13c20<___fxstat64+0x50>

13c44:e79fc00cldrip,[pc,ip]

13c48:e33c0000teqip,#0;0x0

13c4c:1a000006bne13c6c<__libc_open+0x2c>

13c50:e1a0c007movip,r7

13c54:e3a07005movr7,#5;0x5


#在arch/arm/include/asm/unistd.h中:#define__NR_open(__NR_SYSCALL_BASE+5)

其中,__NR_OABI_SYSCALL_BASE是0



13c58:ef000000svc0x00000000#产生软中断
13c5c:e1a0700cmovr7,ip

13c60:e3700a01cmnr0,#4096;0x1000

13c64:312fff1ebxcclr

13c68:ea0008d4b15fc0<__syscall_error>
......

[/code]

通过上面的代码注释,可以看到,系统调用sys_open的系统调用号是5,将系统调用号存放到寄存器R7当中,然后应用程序通过svc0x00000000产生软中断,陷入内核空间。

也许会好奇,ARM软中断不是用SWI吗,这里怎么变成了SVC了,请看下面一段话,是从ARM官网copy的:


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体系结构。



在基于ARM的Linux中,异常向量表已经被放置在了0xFFFF0000这个位置。这个过程的完成:

start_kernel--->setup_arch--->early_trap_init


void__initearly_trap_init(void)

[code]{
unsignedlongvectors=CONFIG_VECTORS_BASE;//就是0xFFFF0000

externchar__stubs_start[],__stubs_end[];

externchar__vectors_start[],__vectors_end[];

externchar__kuser_helper_start[],__kuser_helper_end[];

intkuser_sz=__kuser_helper_end-__kuser_helper_start;

/*

*Copythevectors,stubsandkuserhelpers(inentry-armv.S)

*intothevectorpage,mappedat0xffff0000,andensurethese

*arevisibletotheinstructionstream.

*/

memcpy((void*)vectors,__vectors_start,__vectors_end-__vectors_start);

memcpy((void*)vectors+0x200,__stubs_start,__stubs_end-__stubs_start);

memcpy((void*)vectors+0x1000-kuser_sz,__kuser_helper_start,kuser_sz);

/*

*Copysignalreturnhandlersintothevectorpage,and

*setsigreturntobeapointertothese.

*/

memcpy((void*)KERN_SIGRETURN_CODE,sigreturn_codes,

sizeof(sigreturn_codes));


flush_icache_range(vectors,vectors+PAGE_SIZE);

modify_domain(DOMAIN_USER,DOMAIN_CLIENT);

}

[/code]

关于上面这个函数的详细解释,参见:

/article/5054908.html

把异常中断向量表的位置设置为0xffff0000的话,需要修改协处理器CP15的寄存器C1的第13位,将其设置为1。以Tq2440的提供的内核2.6.30.4为例看一下:

arch/arm/kernel/head.S


adrlr,__enable_mmu@return(PIC)address

[code]addpc,r10,#PROCINFO_INITFUNC
[/code]

其中,PROCINFO_INITFUNC的值是16,r10的值是__arm920_proc_info的地址:

__arm920_proc_info:

[code].long0x41009200
.long0xff00fff0

.longPMD_TYPE_SECT|\

PMD_SECT_BUFFERABLE|\

PMD_SECT_CACHEABLE|\

PMD_BIT4|\

PMD_SECT_AP_WRITE|\

PMD_SECT_AP_READ

.longPMD_TYPE_SECT|\

PMD_BIT4|\

PMD_SECT_AP_WRITE|\

PMD_SECT_AP_READ

b__arm920_setup

.longcpu_arch_name

.longcpu_elf_name
......

.size__arm920_proc_info,.-__arm920_proc_info

[/code]

看一下__arm920_setup的实现(proc-arm920.S(arch\arm\mm)):


.type__arm920_setup,#function

[code]__arm920_setup:
movr0,#0

mcrp15,0,r0,c7,c7@invalidateI,Dcachesonv4

mcrp15,0,r0,c7,c10,4@drainwritebufferonv4

#ifdefCONFIG_MMU

mcrp15,0,r0,c8,c7@invalidateI,DTLBsonv4

#endif

adrr5,arm920_crval

ldmiar5,{r5,r6}@参看以下下面的arm920_crval的实现,本句话执行完后r5和r6分别为:0x3f3f和0x3135

mrcp15,0,r0,c1,c0@getcontrolregisterv4获取协处理器p15的寄存器才c1


bicr0,r0,r5

orrr0,r0,r6@我们只关注第13位,这里将r0的第13位设置为了1

movpc,lr
.size__arm920_setup,.-__arm920_setup

/*

*R

*.RVIZFRSBLDPWCAM

*..110001..110101

*

*/

.typearm920_crval,#object

arm920_crval:

crvalclear=0x00003f3f,mmuset=0x00003135,ucset=0x00001130

[/code]

在看一下crval的实现(proc-macros.S(arch\arm\mm)):


.macrocrval,clear,mmuset,ucset

[code]#ifdefCONFIG_MMU
.word\clear

.word\mmuset

#else

.word\clear

.word\ucset

#endif

.endm

[/code]

在__arm920_setup中执行完movpc,lr后,便跳入了下面的语句:

__enable_mmu:

[code]#ifdefCONFIG_ALIGNMENT_TRAP
orrr0,r0,#CR_A

#else

bicr0,r0,#CR_A

#endif

#ifdefCONFIG_CPU_DCACHE_DISABLE

bicr0,r0,#CR_C

#endif

#ifdefCONFIG_CPU_BPREDICT_DISABLE

bicr0,r0,#CR_Z

#endif

#ifdefCONFIG_CPU_ICACHE_DISABLE

bicr0,r0,#CR_I

#endif

movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|\

domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|\

domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|\

domain_val(DOMAIN_IO,DOMAIN_CLIENT))

mcrp15,0,r5,c3,c0,0@loaddomainaccessregister

mcrp15,0,r4,c2,c0,0@loadpagetablepointer

b__turn_mmu_on

ENDPROC(__enable_mmu)

[/code]

看一下__turn_mmu_on的实现(head.S(arch\arm\kernel)):


.align5

[code]__turn_mmu_on:
movr0,r0

mcrp15,0,r0,c1,c0,0@writecontrolreg

mrcp15,0,r3,c0,c0,0@readidreg

movr3,r3

movr3,r3

movpc,r13

ENDPROC(__turn_mmu_on)

[/code]

在__turn_mmu_on中,将寄存器r0的值写到了cp15协处理器的寄存器C1中。到这里便完成了将异常中断向量表的位置放到了0xffff0000.

说完异常向量表的位置,接下来看看软中断的实现。

ARM提供的中断类型:





ARM的异常处理模型:





entry-armv.S(arch\arm\kernel)

.LCvswi:

[code]
.wordvector_swi

.globl__stubs_end

__stubs_end:


.equstubs_offset,__vectors_start+0x200-__stubs_start


.globl__vectors_start

__vectors_start:

swiSYS_ERROR0

bvector_und+stubs_offset

ldrpc,.LCvswi+stubs_offset@发生软中断后先跳到这里

bvector_pabt+stubs_offset

bvector_dabt+stubs_offset

bvector_addrexcptn+stubs_offset

bvector_irq+stubs_offset

bvector_fiq+stubs_offset


.globl__vectors_end

__vectors_end:


.data


.globlcr_alignment

.globlcr_no_alignment

cr_alignment:

.space4

cr_no_alignment:

.space4

[/code]

接下来看一下vector_swi的实现,根据实际的宏定义进行了简化

ENTRY(vector_swi)

[code]subsp,sp,#S_FRAME_SIZE
stmiasp,{r0-r12}@Callingr0-r12

addr8,sp,#S_PC

stmdbr8,{sp,lr}^@Callingsp,lr

mrsr8,spsr@calledfromnon-FIQmode,sook.

strlr,[sp,#S_PC]@SavecallingPC

strr8,[sp,#S_PSR]@SaveCPSR

strr0,[sp,#S_OLD_R0]@SaveOLD_R0

zero_fp

/*

*Getthesystemcallnumber.

*/

/*

*IfwehaveCONFIG_OABI_COMPATthenweneedtolookattheswi

*valuetodetermineifitisanEABIoranoldABIcall.

*/

ldrr10,[lr,#-4]


@getSWIinstructionr10中存放的就是引起软中断的那条指令的机器码

发生软中断的时候,系统自动将PC-4存放到了lr寄存器,由于是三级流水,

并且是ARM状态,还需要减4才能得到发生软中断的那条指令的机器码所在的地址



A710(andip,r10,#0x0f000000@checkforSWI)

A710(teqip,#0x0f000000)

A710(bne.Larm710bug)


ldrip,__cr_alignment

ldrip,[ip]

mcrp15,0,ip,c1,c0@updatecontrolregister

enable_irq@在发生中断的时候,相应的中断线在在所有CPU上都会被屏蔽掉



get_thread_infotsk@参看下面的介绍
adrtbl,sys_call_table

@loadsyscalltablepointer此时tbl(r8)中存放的就是sys_call_table的起始地址

ldrip,[tsk,#TI_FLAGS]@checkforsyscalltracing

/*

*Iftheswiargumentiszero,thisisanEABIcallandwedonothing.

*

*IfthisisanoldABIcall,getthesyscallnumberintoscnoand

*gettheoldABIsyscalltableaddress.

*/

bicsr10,r10,#0xff000000

eornescno,r10,#__NR_OABI_SYSCALL_BASE

ldr

ne
tbl,=sys_oabi_call_table


stmdbsp!,{r4,r5}@pushfifthandsixthargs

tstip,#_TIF_SYSCALL_TRACE@arewetracingsyscalls?

bne__sys_trace


cmpscno,#NR_syscalls@checkuppersyscalllimit

adrlr,ret_fast_syscall@returnaddress


ldrccpc,[tbl,scno,lsl#2]@callsys_*routine

addr1,sp,#S_OFF

2:movwhy,#0@nolongerarealsyscall

cmpscno,#(__ARM_NR_BASE-__NR_SYSCALL_BASE)

eorr0,scno,#__NR_SYSCALL_BASE@putOSnumberback

bcsarm_syscall

bsys_ni_syscall@notprivatefunc

ENDPROC(vector_swi)

[/code]

entry-common.S(arch\arm\kernel下面是entry-header.S(arch\arm\kernel)的部分内容:

/*

[code]*Thesearetheregistersusedinthesyscallhandler,andallowusto
*haveintheoryupto7argumentstoafunction-r0tor6.

*

*r7isreservedforthesystemcallnumberforthumbmode.

*

*Notethattbl==whyisintentional.

*

*Wemustsetatleast"tsk"and"why"whencallingret_with_reschedule.

*/

scno.reqr7@syscallnumber

tbl.reqr8@syscalltablepointer

why.reqr8@Linuxsyscall(!=0)

tsk.reqr9@currentthread_info

[/code]

.req是伪汇编,以scno.reqr7为例,表示scno是寄存器r7的别名。

get_thread_infotsk

其中,tsk是寄存器r9的别名,get_thread_info是一个宏定义,如下:


.macroget_thread_info,rd

[code]mov\rd,sp,lsr#13
mov\rd,\rd,lsl#13

.endm

[/code]

即:将sp进行8KB对齐后的值赋给寄存器r9,什么意思?

这个就涉及到Linux的内核栈了。Linux为每个进程都分配了一个8KB的内核栈,在内核栈的尾端存放有关于这个进程的structtherad_info结构:


structthread_info{

[code]unsignedlongflags;/*lowlevelflags*/
intpreempt_count;/*0=>preemptable,<0=>bug*/

mm_segment_taddr_limit;/*addresslimit*/

structtask_struct*task;/*maintaskstructure*/

structexec_domain*exec_domain;/*executiondomain*/

__u32cpu;/*cpu*/

__u32cpu_domain;/*cpudomain*/

structcpu_context_savecpu_context;/*cpucontext*/

__u32syscall;/*syscallnumber*/

__u8used_cp[16];/*threadusedcopro*/

unsignedlongtp_value;

structcrunch_statecrunchstate;

unionfp_statefpstate__attribute__((aligned(8)));

unionvfp_statevfpstate;

#ifdefCONFIG_ARM_THUMBEE

unsignedlongthumbee_state;/*ThumbEEHandlerBaseregister*/

#endif

structrestart_blockrestart_block;

};

[/code]

通过上面的操作,寄存器r9中就是这个进程的thread_info结构的起始地址。

sys_call_table

entry-common.S(arch\arm\kernel)


.typesys_call_table,#object

[code]ENTRY(sys_call_table)
#include"calls.S"

#undefABI

#undefOBSOLETE

[/code]

其中,calls.S的内容如下:

/*

[code]*linux/arch/arm/kernel/calls.S
*

*Copyright(C)1995-2005RussellKing

*

*Thisprogramisfreesoftware;youcanredistributeitand/ormodify

*itunderthetermsoftheGNUGeneralPublicLicenseversion2as

*publishedbytheFreeSoftwareFoundation.

*

*Thisfileisincludedthriceinentry-common.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)/*wassys_waitpid*/

CALL(sys_creat)

CALL(sys_link)
/*10*/CALL(sys_unlink)

CALL(sys_execve_wrapper)

CALL(sys_chdir)

CALL(OBSOLETE(sys_time))/*usedbylibc4*/

CALL(sys_mknod)
......
/*355*/CALL(sys_signalfd4)

CALL(sys_eventfd2)

CALL(sys_epoll_create1)

CALL(sys_dup3)

CALL(sys_pipe2)
/*360*/CALL(sys_inotify_init1)

CALL(sys_preadv)

CALL(sys_pwritev)

#ifndefsyscalls_counted

.equsyscalls_padding,((NR_syscalls+3)&~3)-NR_syscalls

#definesyscalls_counted

#endif

.reptsyscalls_padding

CALL(sys_ni_syscall)

.endr

[/code]

关于这个部分的更多介绍参见:

/article/5054905.html

bicsr10,r10,#0xff000000

执行这个操作的时候,r10中存放的是SWIinstruction,在我们的例子中就是(a.dis):





即:r10为0xEF000000

显然,bics这条指令下面的两个语句由于条件不成立,无法获得执行。这条指令的作用是获得系统调用号

可以参考这个手册,看一下svc执行的格式:

http://files.cnblogs.com/pengdonglin137/DUI0203IC_rvct_developer_guide.pdf





可以看到,[23:0]存放的就是svc指令后面的那个立即数,也即系统调用号。

不过需要注意的是:我们这里并没有这样做,我们的做法是(a.dis中可以看到):





使用的是svc0,后面跟的并不是系统调用号,而是0,这里把系统调用号存放在了寄存器r7中(a.dis中):





可以看到,由于使用的sys_open系统调用,所以把它的系统调用号5存放到了寄存器r7当中

ldrccpc,[tbl,scno,lsl#2]@callsys_*routine

这里的scno是就是寄存器r7的别名,它的值是sys_open的系统调用号5,由于在calls.S中每个系统调用标号占用4个字节,所以这个将scno的值乘以4然后再加上tbl,tbl是系统调用表sys_call_table的基地址。然后就跳入开始执行sys_open了。

asmlinkagelongsys_open(constchar__user*filename,
intflags,intmode);


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


SYSCALL_DEFINE3(open,constchar__user*,filename,int,flags,int,mode)

[code]{
longret;


if(force_o_largefile())

flags|=O_LARGEFILE;


ret=do_sys_open(AT_FDCWD,filename,flags,mode);
/*avoidREGPARMbreakageonx86:*/

asmlinkage_protect(3,ret,filename,flags,mode);

returnret;

}

[/code]

其中SYSCALL_DEFINE3是一个宏:

syscalls.h(include\linux)

#defineSYSCALL_DEFINE3(name,...)SYSCALL_DEFINEx(3,_##name,__VA_ARGS__)


SYSCALL_DEFINEx也是一个宏:

syscalls.h(include\linux)

#defineSYSCALL_DEFINEx(x,sname,...)\
__SYSCALL_DEFINEx(x,sname,__VA_ARGS__)


__SYSCALL_DEFINEx仍然是个宏:

syscalls.h(include\linux)

#define__SYSCALL_DEFINEx(x,name,...)\
asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__))


所以展开后的结果就是:

asmlinkagelongsys_open(__SC_DECL3(__VA_ARGS__))


其中,__SC_DECL3定义如下:

syscalls.h(include\linux)


#define__SC_DECL1(t1,a1)t1a1

[code]#define__SC_DECL2(t2,a2,...)t2a2,__SC_DECL1(__VA_ARGS__)
#define__SC_DECL3(t3,a3,...)t3a3,__SC_DECL2(__VA_ARGS__)

[/code]

所以最终的结果如下:


asmlinkagelongsys_open(constchar__user*filename,intflags,intmode)

[code]{
longret;


if(force_o_largefile())

flags|=O_LARGEFILE;


ret=do_sys_open(AT_FDCWD,filename,flags,mode);
/*avoidREGPARMbreakageonx86:*/

asmlinkage_protect(3,ret,filename,flags,mode);

returnret;


}

[/code]

关于sys_open本身的实现这里就不深入分析了。

接下来看一下返回。

adrlr,ret_fast_syscall@returnaddress

当sys_open中return后,便跳入ret_fast_syscall处开始执行:

/*

[code]*Thisisthefastsyscallreturnpath.Wedoaslittleas
*possiblehere,andthisincludessavingr0backintotheSVC

*stack.

*/

ret_fast_syscall:

UNWIND(.fnstart)

UNWIND(.cantunwind)

disable_irq@disableinterrupts


ldrr1,[tsk,#TI_FLAGS]@将thread_info中的flags成员存放到r1中
tstr1,#_TIF_WORK_MASK

bnefast_work_pending

/*performarchitecturespecificactionsbeforeuserreturn*/

arch_ret_to_userr1,lr


@fast_restore_user_regs

ldrr1,[sp,#S_OFF+S_PSR]@getcallingcpsr

ldrlr,[sp,#S_OFF+S_PC]!@getpc

msrspsr_cxsf,r1@saveinspsr_svc

ldmdbsp,{r1-lr}^@getcallingr1-lr

movr0,r0

addsp,sp,#S_FRAME_SIZE-S_PC

movspc,lr@return&movespsr_svcintocpsr

UNWIND(.fnend)

/*

*Ok,weneedtodoextraprocessing,entertheslowpath.

*/

fast_work_pending:

strr0,[sp,#S_R0+S_OFF]!@returnedr0

work_pending:

tstr1,#_TIF_NEED_RESCHED@判断是否需要进行进程调度

bnework_resched

tstr1,#_TIF_SIGPENDING

beqno_work_pending

movr0,sp@'regs'

movr2,why@'syscall'

bldo_notify_resume

bret_slow_syscall@Checkworkagain



work_resched:
blschedule
/*

*"slow"syscallreturnpath."why"tellsusifthiswasarealsyscall.

*/

ENTRY(ret_to_user)

ret_slow_syscall:

disable_irq@disableinterrupts

ldrr1,[tsk,#TI_FLAGS]

tstr1,#_TIF_WORK_MASK

bnework_pending

no_work_pending:
/*performarchitecturespecificactionsbeforeuserreturn*/

arch_ret_to_userr1,lr


@slow_restore_user_regs

ldrr1,[sp,#S_PSR]@getcallingcpsr

ldrlr,[sp,#S_PC]!@getpc

msrspsr_cxsf,r1@saveinspsr_svc

ldmdbsp,{r0-lr}^@getcallingr0-lr

movr0,r0

addsp,sp,#S_FRAME_SIZE-S_PC

movspc,lr@return&movespsr_svcintocpsr

ENDPROC(ret_to_user)

[/code]

在返回的时候要看是否要进行进程调用。





先分析到这里。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
章节导航