android基于Socket的系统调用实现
声明:该文件为本人原创,如转载修改及使用其中图片,请注明出处及原作者。
Author:lanbo(高兆成)
E-mail:[email protected]
如有任何疑问可留言或E-mail
系统调用就是用户空间应用程序和内核提供的服务之间的接口。服务是由linux内核提供的,无法直接调用。因此必须使用一个进程来跨越用户空间和内核之间的界限。
今天我们就将从用户层通过socket来分析linux下的系统调用的实现过程。
通过该文章读者可熟悉系统调用的实现
以wpa_supplicant中driver_wext中的socket为例来分析:
要想通过wext与kernel沟通wpa_supplicant中是在wpa_driver_wext_init函数中通过创建socket来实现
Socket定义在bionic/libc/arch-arm/syscalls/socket.S中如下:
#include
.text
.type socket, #function
.globl socket
.align 4
.fnstart
socket:
.save {r4, r7}
stmfd sp!, {r4, r7}
ldr r7, =__NR_socket//此处将__NR_socket放入到ARM R7中
swi #0//调用系统中断
ldmfd sp!, {r4, r7}
movs r0, r0
bxpl lr
b __set_syscall_errno
.fnend
__NR_socket 在几个文件中都有定义,我就不确认是调用的kernel/arch/arm/include/asm/Unistd.h还是
ndk/build/platforms/android-8/arch-arm/usr/include/sys/Linux-syscalls.h
#if !defined__ASM_ARM_UNISTD_H && !defined __ASM_I386_UNISTD_H
#if defined__arm__ && !defined __ARM_EABI__ && !defined __thumb__
# define __NR_SYSCALL_BASE 0x900000
#else
# define __NR_SYSCALL_BASE 0
#endif
………………………省略号……………….
#define __NR_socket (__NR_SYSCALL_BASE +281)
如上调用了swi(软中断) ,接下来我们看看中断向量实现。
在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:
V=0:0x00000000~0x0000001C
V=1:0xffff0000~0xffff001C
arch/arm/mm/proc-arm920.S中
.section".text.init", #alloc, #execinstr
__arm920_setup:
……orr r0, r0,#0x2100 @ ..1. ...1 ..11 ...1
//bit13=1 中断向量表基址为0xFFFF0000。R0的值将被付给CP15的C1.
中断向量在early_trap_init中定义,调用顺序如下:
start_kernel(kernel/init/main.c)==> setup_arch(kernel/arch/arm/kernel/Setup.c)==>early_trap_init(kernel/arch/arm/kernel/Traps.c)
early_trap_init部分代码如下:
unsigned longvectors = CONFIG_VECTORS_BASE;//定义中断向量起始地址
//#defineCONFIG_VECTORS_BASE 0xffff0000定义在kernel/include/linux/Autoconf.h
extern char__stubs_start[], __stubs_end[];
extern char__vectors_start[], __vectors_end[];
//如下做中断向量的搬移动作,为保护模式准备,如上调用swi后PC指针会指向vectors +address 0x00000008。
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);
其中__stubs_start[], __stubs_end[],__vectors_start[], __vectors_end[]在kernel/arch/arm/kernel/dntry-armv.S中定义,我们来分析看看如何实现。
.macro vector_stub,name, mode, correction=0//此处定义了一个vector_stub宏定义
.align 5
vector_\name:
.if \correction
sub lr,lr, #\correction
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@
stmia sp,{r0, lr} @ save r0, lr
mrs lr,spsr
str lr,[sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0,cpsr
eor r0,r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf,r0
@
@ the branch table must immediatelyfollow this code
@
and lr,lr, #0x0f
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0,sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc,lr @ branch tohandler in SVC mode
ENDPROC(vector_\name)
………………………………省略号………………………………………………….
.LCvswi:
.word vector_swi//系统调用向量
.globl __stubs_end
__stubs_end:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__vectors_start:
ARM( swi SYS_ERROR0 ) //复位指令
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset//未定义异常时,CPU将执行这条指令
W(ldr) pc, .LCvswi + stubs_offset//swi异常
W(b) vector_pabt + stubs_offset//指令预取中止
W(b) vector_dabt + stubs_offset//数据访问中止
W(b) vector_addrexcptn + stubs_offset//没有用到
W(b) vector_irq + stubs_offset//irq中断异常
W(b) vector_fiq + stubs_offset//fiq 快速中断异常
.globl __vectors_end
__vectors_end:
至于为何要加stubs_offset,请参考如下解释,我也没分析(引用于网上)
__stubs_end 至 __stubs_start之间是异常处理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pabt、vector_irq、vector_fiq都在它们中间。
stubs_offset值如下:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
stubs_offset是如何确定的呢?
当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断 向量中的中断入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) +vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的 vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的。
如上我们有看到有“vector_swi”(系统调用向量),其定义处为kernel/arch/arm/kernel/entry-common.S
其部分内容为:
.equ NR_syscalls,0
#defineCALL(x) .equ NR_syscalls,NR_syscalls+1
#include"calls.S"
#undefCALL
#defineCALL(x) .long x
………………………省略号………………………………………………
/*=============================================================================
* SWI handler
*-----------------------------------------------------------------------------
*/
/* If we're optimising for StrongARMthe resulting code won't
run on an ARM7 and we can save a couple of instructions.
--pb*/
#ifdefCONFIG_CPU_ARM710
#defineA710(code...) code
.Larm710bug:
ldmia sp,{r0 - lr}^ @ Getcalling r0 - lr
mov r0,r0
add sp,sp, #S_FRAME_SIZE
subs pc,lr, #4
#else
#defineA710(code...)
#endif
.align 5
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_lrr8, r10, S_SP ) @ calling sp, lr
mrs r8,spsr @ called fromnon-FIQ mode, so ok.
str lr,[sp, #S_PC] @ Savecalling PC
str r8,[sp, #S_PSR] @ Save CPSR
str r0,[sp, #S_OLD_R0] @ SaveOLD_R0
zero_fp
/*
* Get the system call number.
*/
#ifdefined(CONFIG_OABI_COMPAT)
/*
* If we have CONFIG_OABI_COMPAT then we needto look at the swi
* value to determine if it is an EABI or anold ABI call.
*/
#ifdefCONFIG_ARM_THUMB
tst r8,#PSR_T_BIT
movne r10,#0 @ nothumb OABI emulation
ldreq r10,[lr, #-4] @ getSWI instruction
#else
ldr r10,[lr, #-4] @ getSWI instruction
A710( and ip, r10, #0x0f000000 @ check for SWI )
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#endif
#ifdefCONFIG_CPU_ENDIAN_BE8
rev r10,r10 @ little endianinstruction
#endif
#elifdefined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscallnumber 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 )
#elifdefined(CONFIG_ARM_THUMB)
/* Legacy ABI only, possibly thumbmode. */
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 SWIinstruction
A710( and ip, scno, #0x0f000000 @ check for SWI )
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#endif
#ifdefCONFIG_ALIGNMENT_TRAP
ldr ip,__cr_alignment
ldr ip,[ip]
mcr p15,0, ip, c1, c0 @ update controlregister
#endif
enable_irq
get_thread_info tsk
adr tbl, sys_call_table @load syscall table pointer
ldr ip,[tsk, #TI_FLAGS] @ check forsyscall tracing
#ifdefined(CONFIG_OABI_COMPAT)
/*
* If the swi argument is zero, this is an EABIcall and we do nothing.
*
* If this is an old ABI call, get the syscallnumber 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 SWIop-code
eor scno,scno, #__NR_SYSCALL_BASE @ check OS number
#endif
stmdb sp!,{r4, r5} @ pushfifth and sixth args
tst ip,#_TIF_SYSCALL_TRACE @ arewe tracing syscalls?
bne __sys_trace
cmp scno,#NR_syscalls @ check uppersyscall limit
adr lr,BSYM(ret_fast_syscall) @ returnaddress
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
add r1,sp, #S_OFF
2: mov why,#0 @ nolonger a real syscall
cmp scno,#(__ARM_NR_BASE - __NR_SYSCALL_BASE)
eor r0,scno, #__NR_SYSCALL_BASE @ put OSnumber back
bcs arm_syscall
b sys_ni_syscall @ not private func
ENDPROC(vector_swi)
……………………………省略号……………………………………………………….
.type sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
还记得之前分析时系统调用入栈的__NR_socket (__NR_SYSCALL_BASE + 281)吗?在如上代码中再将其转换成对应的偏移值再通过调用sys_call_table+偏移调用指定系统调用。而在sys_call_table中先调将calls.S给include进来,这样我们通过偏移可得出我们系统调用对应的function 为CALL(sys_socket),其中CALL(X)如上也有定义。
sys_socket系统调用在kernel/include/linux/Syscalls.h中,
asmlinkage longsys_socket(int, int, int);
sys_socket原型如下:
kernel/net/Socket.c
SYSCALL_DEFINE3(socket,int, family, int, type, int, protocol)
{
retval = sock_create(family, type,protocol, &sock);//主要作用是创建socket,暂且先不深入了。
if (retval < 0)
goto out;
retval = sock_map_fd(sock, flags &(O_CLOEXEC | O_NONBLOCK));//该函数创建文件描述符并与socket关联。
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor8) Not kernel problem. */
return retval;//返回文件描述符
int sock_map_fd(struct socket *sock, int flags)
{
structfile *newfile;
intfd = sock_alloc_fd(&newfile, flags);// 结构分配一个空闲的文件描述符
if(likely(fd >= 0)) {
interr = sock_attach_fd(sock, newfile, flags);//关联文件描述符与socket
if(unlikely(err < 0)) {
put_filp(newfile);
put_unused_fd(fd);
returnerr;
}
fd_install(fd,newfile);
}
returnfd;
}
static int sock_attach_fd(struct socket*sock, struct file *file, int flags)
sock->file = file;//socket的file指针指向文件描述符
init_file(file, sock_mnt, dentry,FMODE_READ | FMODE_WRITE,
&socket_file_ops);//提供文件描述符接口,在文章“WEXTdriver的执行过程实现”会用到。
file->private_data =sock;//文件描述符的private_data指向socket
int init_file(struct file *file, struct vfsmount *mnt, struct dentry *dentry,
fmode_t mode, const struct file_operations *fop)
{
int error = 0;
file->f_path.dentry = dentry;
file->f_path.mnt = mntget(mnt);
file->f_mapping = dentry->d_inode->i_mapping;
file->f_mode = mode;
file->f_op = fop;以后上层调用ioctl等都会用到该接口。
至此终于能够执行到socket的系统调用了,以后我们再分析上层是如何调用到网络驱动的(wpa_supplicant及iwpriv)。