天天看點

android基于Socket的系統調用實作

android基于Socket的系統調用實作

聲明:該檔案為本人原創,如轉載修改及使用其中圖檔,請注明出處及原作者。

Author:lanbo(高兆成)

E-mail:[email protected]

如有任何疑問可留言或E-mail

系統調用就是使用者空間應用程式和核心提供的服務之間的接口。服務是由linux核心提供的,無法直接調用。是以必須使用一個程序來跨越使用者空間和核心之間的界限。

今天我們就将從使用者層通過socket來分析linux下的系統調用的實作過程。

通過該文章讀者可熟悉系統調用的實作

android基于Socket的系統調用實作

以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)。

繼續閱讀