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 <sys/linux-syscalls.h>
.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_<exception> (parentPC) and spsr_<exception>
@ (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
………………………省略号………………………………………………
#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
#ifdefined(CONFIG_OABI_COMPAT)
#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)
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)
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
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)
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:
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)。