关于Linux系统如何实现fork的研究

fork函数是用于在linux系统中创建进程所使用,而最近看了看一个fork()调用是怎么从应用到glibc,最后到内核中实现的,这片文章就聊聊最近对这方面研究的收获吧。我们主要聊聊从glibc库进入内核,再从内核出来的情景,而从应用到glibc这部分本片文章就不详细说明了。为了方便期间,我们的硬件平台为arm,linux内核为3.18.3,glibc库版本为2.20,可从下载源码。

Glibc到kernel

我们设定硬件平台为arm,glibc库版本为2.20,因为不同的CPU体系结构中,glibc库通过系统调用进入kernel库的方法是不一样的。当glibc准备进入kernel时,流程如下 


 1 /* glibc最后会调用到一个INLINE_SYSCALL宏,参数如下 */
 2 INLINE_SYSCALL (clone, 5, CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid);
 3
 4  /* INLINE_SYSCALL的宏定义如下,可以看出在INLINE_SYSCALL宏中又使用到了INTERNAL_SYSCALL宏,而INTERNAL_SYSCALL宏最终会调用INTERNAL_SYSCALL_RAW */
 5 #define INLINE_SYSCALL(name, nr, args...) \
 6  ({ unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args); \
 7      if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) \
 8        { \
 9      __set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, )); \
10      _sys_result = (unsigned int) -1; \
11        } \
12      (int) _sys_result; })
13
14  /* 为了方便大家理解,将此宏写为伪代码形式 */
15  int INLINE_SYSCALL (name, nr, args...)
16  {
17    unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args);
18
19    if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) {
20        __set_error (INTERNAL_SYSCALL_ERRNO (_sys_result, ));
21        _sys_result = (unsigned int) -1;
22    }
23    return (int)_sys_result;
24  }
25
26 /* 这里我们不需要看INTERNAL_SYSCALL宏,只需要看其最终调用的INTERNAL_SYSCALL_RAW宏,需要注意的是,INTERNAL_SYSCALL调用INTERNAL_SYSCALL_RAW时,通过SYS_ify(name)宏将name转为了系统调用号
27  * name: 120(通过SYS_ify(name)宏已经将clone转为了系统调用号120)
28  * err: NULL
29  * nr: 5
30  * args[0]: CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD
31  * args[1]: NULL
32  * args[2]: NULL
33  * args[3]: NULL
34  * args[4]: &THREAD_SELF->tid
35  */
36  # define INTERNAL_SYSCALL_RAW(name, err, nr, args...)    \
37  ({    \
38        register int _a1 asm ("r0"), _nr asm ("r7");    \
39        LOAD_ARGS_##nr (args)    \
40        _nr = name;    \
41        asm volatile ("swi    0x0    @ syscall " #name    \
42    : "=r" (_a1)    \
43    : "r" (_nr) ASM_ARGS_##nr    \
44    : "memory");    \
45        _a1; })
46  #endif

INTERNAL_SYSCALL_RAW实现的结果就是将args[0]存到了r0...args[4]存到了r4中,并将name(120)绑定到r7寄存器。然后通过swi  0x0指令进行了软中断。0x0是一个24位的立即数,用于软中断执行程序判断执行什么操作。当执行这条指令时,CPU会跳转至中断向量表的软中断指令处,执行该处保存的调用函数,而在函数中会根据swi后面的24位立即数(在我们的例子中是0x0)执行不同操作。在这时候CPU已经处于保护模式,陷入内核中。现在进入到linux内核中后,具体看此时内核是怎么操作的吧。

1 /* 源文件地址: 内核目录/arch/arm/kernel/entry-common.S */
  2
  3 ENTRY(vector_swi)
  4    /*
  5      * 保存现场
  6      */
  7 #ifdef CONFIG_CPU_V7M
  8    v7m_exception_entry
  9 #else
 10    sub    sp, sp, #S_FRAME_SIZE
 11    stmia    sp, {r0 - r12}            @ 将r0~r12保存到栈中
 12  ARM(    add    r8, sp, #S_PC        )
 13  ARM(    stmdb    r8, {sp, lr}^        )    @ Calling sp, lr
 14  THUMB(    mov    r8, sp            )
 15  THUMB(    store_user_sp_lr r8, r10, S_SP    )    @ calling sp, lr
 16    mrs    r8, spsr            @ called from non-FIQ mode, so ok.
 17    str    lr, [sp, #S_PC]            @ Save calling PC
 18    str    r8, [sp, #S_PSR]        @ Save CPSR
 19    str    r0, [sp, #S_OLD_R0]        @ Save OLD_R0
 20 #endif
 21    zero_fp
 22    alignment_trap r10, ip, __cr_alignment
 23    enable_irq
 24    ct_user_exit
 25    get_thread_info tsk
 26
 27    /*
 28      * 以下代码根据不同arm体系结构获取系统调用号
 29      */
 30
 31 #if defined(CONFIG_OABI_COMPAT)
 32
 33    /*
 34      * 如果内核配置了OABI兼容选项,会先判断是否为THUMB,以下为THUMB情况(我们分析的时候可以忽略这段,一般情况是不走这一段的)
 35      */
 36 #ifdef CONFIG_ARM_THUMB
 37    tst    r8, #PSR_T_BIT
 38    movne    r10, #0                @ no thumb OABI emulation
 39  USER(    ldreq    r10, [lr, #-4]        )    @ get SWI instruction
 40 #else
 41  USER(    ldr    r10, [lr, #-4]        )    @ get SWI instruction
 42 #endif
 43  ARM_BE8(rev    r10, r10)            @ little endian instruction
 44
 45 #elif defined(CONFIG_AEABI)
 46
 47    /*
 48      * 我们主要看这里,EABI将系统调用号保存在r7中
 49      */
 50 #elif defined(CONFIG_ARM_THUMB)
 51    /* 先判断是否为THUMB模式 */
 52    tst    r8, #PSR_T_BIT           
 53    addne    scno, r7, #__NR_SYSCALL_BASE   
 54  USER(    ldreq    scno, [lr, #-4]        )
 55
 56 #else
 57    /* EABI模式 */
 58  USER(    ldr    scno, [lr, #-4]        )    @ 获取系统调用号
 59 #endif
 60
 61    adr    tbl, sys_call_table        @ tbl为r8,这里是将sys_call_table的地址(相对于此指令的偏移量)存入r8
 62
 63 #if defined(CONFIG_OABI_COMPAT)
 64    /*
 65      * 在EABI体系中,如果swi跟着的立即数为0,这段代码不做处理,而如果是old abi体系,则根据系统调用号调用old abi体系的系统调用表(sys_oabi_call_table)
 66      * 其实说白了,在EABI体系中,系统调用时使用swi 0x0进行软中断,r7寄存器保存系统调用号
 67      * 而old abi体系中,是通过swi (系统调用号|magic)进行调用的
 68      */
 69    bics    r10, r10, #0xff000000
 70    eorne    scno, r10, #__NR_OABI_SYSCALL_BASE
 71    ldrne    tbl, =sys_oabi_call_table
 72 #elif !defined(CONFIG_AEABI)
 73    bic    scno, scno, #0xff000000       
 74    eor    scno, scno, #__NR_SYSCALL_BASE   
 75 #endif
 76
 77 local_restart:
 78    ldr    r10, [tsk, #TI_FLAGS]        @ 检查系统调用跟踪
 79    stmdb    {r4, r5}            @ 将第5和第6个参数压入栈
 80
 81    tst    r10, #_TIF_SYSCALL_WORK        @ 判断是否在跟踪系统调用
 82    bne    __sys_trace
 83
 84    cmp    scno, #NR_syscalls        @ 检测系统调用号是否在范围内,NR_syscalls保存系统调用总数
 85    adr    lr, BSYM(ret_fast_syscall)    @ 将返回地址保存到lr寄存器中,lr寄存器是用于函数返回的。
 86    ldrcc    pc, [tbl, scno, lsl #2]        @ 调用相应系统调用例程,tbl(r8)保存着系统调用表(sys_call_table)地址,scno(r7)保存着系统调用号120,这里就转到相应的处理例程上了。
 87
 88    add    r1, sp, #S_OFF
 89 2:    cmp    scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
 90    eor    r0, scno, #__NR_SYSCALL_BASE    @ put OS number back
 91    bcs    arm_syscall
 92    mov    why, #0                @ no longer a real syscall
 93    b    sys_ni_syscall            @ not private func
 94
 95 #if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
 96    /*
 97      * We failed to handle a fault trying to access the page
 98      * containing the swi instruction, but we're not really in a
 99      * position to return -EFAULT. Instead, return back to the
100      * instruction and re-enter the user fault handling path trying
101      * to page it in. This will likely result in sending SEGV to the
102      * current task.
103      */
104 9001:
105    sub    lr, lr, #4
106    str    lr, [sp, #S_PC]
107    b    ret_fast_syscall
108 #endif
109 ENDPROC(vector_swi)            @ 返回

好的,终于跳转到了系统调用表,现在我们看看系统调用表是怎么样的一个形式

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/b33989c2b19c44d30b132667ae89ee74.html