关于linux系统如何实现fork的研究(一)
引言
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指令进行了软中断(其实就是将PC寄存器设置为异常向量表的0x0位置),陷入内核。此时linux已经处于内核态(CPU处于管理模式),而swi后面的0x0是用来做什么的呢,它是一个24位的立即数,用于指定执行异常向量表中的函数。好的,这时候通过软中断进入到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) @ 返回
好的,终于跳转到了系统调用表,现在我们看看系统调用表是怎么样的一个形式
1 /* 文件地址: linux内核目录/arch/arm/kernel/calls.S */ 2 3 /* 0 */ CALL(sys_restart_syscall) 4 CALL(sys_exit) 5 CALL(sys_fork) 6 CALL(sys_read) 7 CALL(sys_write) 8 /* 5 */ CALL(sys_open) 9 CALL(sys_close) 10 CALL(sys_ni_syscall) /* was sys_waitpid */ 11 CALL(sys_creat) 12 CALL(sys_link) 13 /* 10 */ CALL(sys_unlink) 14 CALL(sys_execve) 15 CALL(sys_chdir) 16 CALL(OBSOLETE(sys_time)) /* used by libc4 */ 17 CALL(sys_mknod) 18 /* 15 */ CALL(sys_chmod) 19 CALL(sys_lchown16) 20 CALL(sys_ni_syscall) /* was sys_break */ 21 CALL(sys_ni_syscall) /* was sys_stat */ 22 CALL(sys_lseek) 23 /* 20 */ CALL(sys_getpid) 24 CALL(sys_mount) 25 CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */ 26 CALL(sys_setuid16) 27 CALL(sys_getuid16) 28 /* 25 */ CALL(OBSOLETE(sys_stime)) 29 CALL(sys_ptrace) 30 CALL(OBSOLETE(sys_alarm)) /* used by libc4 */ 31 CALL(sys_ni_syscall) /* was sys_fstat */ 32 CALL(sys_pause) 33 34 ...................... 35 ...................... 36 37 /* 120 */ CALL(sys_clone) /* 120在此,之前传进来的系统调用号120进入内核后会到这 */ 38 CALL(sys_setdomainname) 39 CALL(sys_newuname) 40 CALL(sys_ni_syscall) /* modify_ldt */ 41 CALL(sys_adjtimex) 42 /* 125 */ CALL(sys_mprotect) 43 CALL(sys_sigprocmask) 44 CALL(sys_ni_syscall) /* was sys_create_module */ 45 CALL(sys_init_module) 46 CALL(sys_delete_module) 47 48 ...................... 49 ...................... 50 51 /* 375 */ CALL(sys_setns) 52 CALL(sys_process_vm_readv) 53 CALL(sys_process_vm_writev) 54 CALL(sys_kcmp) 55 CALL(sys_finit_module) 56 /* 380 */ CALL(sys_sched_setattr) 57 CALL(sys_sched_getattr) 58 CALL(sys_renameat2) 59 CALL(sys_seccomp) 60 CALL(sys_getrandom) 61 /* 385 */ CALL(sys_memfd_create) 62 CALL(sys_bpf) 63 #ifndef syscalls_counted 64 .equ syscalls_padding, ((NR_syscalls + 3) & ~3) - NR_syscalls 65 #define syscalls_counted 66 #endif 67 .rept syscalls_padding 68 CALL(sys_ni_syscall) 69 .endr
CALL为一个宏,而我们使用的那一行CALL(sys_clone)配合ldrcc pc,[tbl,scno,lsl #2]使用的结果就是把sys_clone的地址放入pc寄存器。具体我们仔细分析一下,首先先看看CALL宏展开,然后把CALL代入ldrcc,结果就很清晰了
1 /* CALL(x)宏展开 */ 2 #define CALL(x) .equ NR_syscalls,NR_syscalls+1 3 #include "calls.S" 4 5 .ifne NR_syscalls - __NR_syscalls 6 .error "__NR_syscalls is not equal to the size of the syscall table" 7 .endif 8 9 /* 主要是后面这一段, 10 * 上面一段主要用于统计系统调用数量,并将数量保存到NR_syscalls中,具体实现说明可以参考http://www.tuicool.com/articles/QFj6zq 11 */ 12 13 #undef CALL 14 /* 其实就是生成一个数为x,相当于.long sys_clone,因为sys_clone是函数名,所以.long生成的是sys_clone函数名对应的地址 */ 15 #define CALL(x) .long x 16 17 #ifdef CONFIG_FUNCTION_TRACER 18 19 20 /* 配合ldrcc一起看,原来ldrcc是这样 */ 21 ldrcc pc, [tbl, scno, lsl #2] 22 23 /* 把CALL(x)代入ldrcc,最后是这样 */ 24 ldrcc pc, sys_clone(函数地址)
清楚的看出来,ldrcc最后是将sys_clone的函数地址存入了pc寄存器,而sys_clone函数内核是怎么定义的呢,如下
1 /* 文件地址: linux内核目录/kernel/Fork.c */ 2 3 /* 以下代码根据不同的内核配置定义了不同的clone函数 4 * 其最终都调用的do_fork函数,我们先看看SYSCALL_DEFINE是怎么实现的吧,实现在此代码片段后面 5 */ 6 #ifdef __ARCH_WANT_SYS_CLONE 7 #ifdef CONFIG_CLONE_BACKWARDS 8 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 9 int __user *, parent_tidptr, 10 int, tls_val, 11 int __user *, child_tidptr) 12 #elif defined(CONFIG_CLONE_BACKWARDS2) 13 SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags, 14 int __user *, parent_tidptr, 15 int __user *, child_tidptr, 16 int, tls_val) 17 #elif defined(CONFIG_CLONE_BACKWARDS3) 18 SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp, 19 int, stack_size, 20 int __user *, parent_tidptr, 21 int __user *, child_tidptr, 22 int, tls_val) 23 #else 24 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 25 int __user *, parent_tidptr, 26 int __user *, child_tidptr, 27 int, tls_val) 28 #endif 29 { 30 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); 31 } 32 33 34 /************************************************ 35 * 我是代码分界线 36 ************************************************/ 37 38 /* 文件地址: linux内核目录/include/linux.h */ 39 40 #define SYSCALL_DEFINE0(sname) 41 SYSCALL_METADATA(_##sname, 0); 42 asmlinkage long sys_##sname(void) 43 44 #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) 45 #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) 46 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) 47 #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) 48 #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) 49 #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) 50 51 #define SYSCALL_DEFINEx(x, sname, ...) 52 SYSCALL_METADATA(sname, x, __VA_ARGS__) 53 __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) 54 55 #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__) 56 #define __SYSCALL_DEFINEx
可以看出系统调用是使用SYSCALL_DEFINEx进行定义的,以我们的例子,实际上最后clone函数被定义为
1 /* 展开前 */ 2 3 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 4 int __user *, parent_tidptr, 5 int __user *, child_tidptr, 6 int, tls_val) 7 #endif 8 { 9 /* 应用层默认fork参数(CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid) */ 10 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); 11 } 12 13 /* 展开后 */ 14 15 asmlinkage long sys_clone (unsigned long clone_flags, unsigned long newsp, int __user * parent_tidptr, int __user * child_tidptr, int tls_val) 16 { 17 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); 18 }
终于看到最后系统会调用do_fork函数进行操作,接下来我们看看do_fork函数
1 /* 应用层的fork最后会通过sys_clone系统调用调用到此函数 */ 2 /* 应用层默认fork参数(CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid) 3 * clone_flags: CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD 4 * stack_start: NULL 5 * stack_size: NULL 6 * parent_tidptr: NULL 7 * child_tidptr: &THREAD_SELF->tid 8 * pid: NULL 9 */ 10 long do_fork(unsigned long clone_flags, 11 unsigned long stack_start, 12 unsigned long stack_size, 13 int __user *parent_tidptr, 14 int __user *child_tidptr) 15 { 16 struct task_struct *p; 17 int trace = 0; 18 long nr; 19 20 /* 判断是否进行跟踪 */ 21 if (!(clone_flags & CLONE_UNTRACED)) { 22 if (clone_flags & CLONE_VFORK) 23 trace = PTRACE_EVENT_VFORK; 24 else if ((clone_flags & CSIGNAL) != SIGCHLD) 25 trace = PTRACE_EVENT_CLONE; 26 else 27 trace = PTRACE_EVENT_FORK; 28 29 if (likely(!ptrace_event_enabled(current, trace))) 30 trace = 0; 31 } 32 33 /* 调用copy_process进行初始化,返回初始化好的struct task_struct结构体,当我们调用fork时返回两次的原因也是在这个函数当中,下回分析 */ 34 p = copy_process(clone_flags, stack_start, stack_size, 35 child_tidptr, NULL, trace); 36 37 38 if (!IS_ERR(p)) { 39 /* 创建成功 */ 40 struct completion vfork; 41 struct pid *pid; 42 43 trace_sched_process_fork(current, p); 44 45 /* 获取子进程PID */ 46 pid = get_task_pid(p, PIDTYPE_PID); 47 /* 返回子进程pid所属的命名空间所看到的局部PID */ 48 nr = pid_vnr(pid); 49 50 if (clone_flags & CLONE_PARENT_SETTID) 51 put_user(nr, parent_tidptr); 52 53 if (clone_flags & CLONE_VFORK) { 54 p->vfork_done = &vfork; 55 init_completion(&vfork); 56 get_task_struct(p); 57 } 58 59 /* 唤醒进程 */ 60 wake_up_new_task(p); 61 62 /* 跟踪才会用到 */ 63 if (unlikely(trace)) 64 ptrace_event_pid(trace, pid); 65 66 /* 如果是vfork调用,则在此等待vfork的进程结束 */ 67 if (clone_flags & CLONE_VFORK) { 68 if (!wait_for_vfork_done(p, &vfork)) 69 ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid); 70 } 71 72 put_pid(pid); 73 } else { 74 /* 创建失败 */ 75 nr = PTR_ERR(p); 76 } 77 /* 返回新进程PID(新进程在这会返回0) */ 78 return nr; 79 }
在do_fork函数中,首先会根据clone_flags判断是否对父进程进行了跟踪(调试使用),如果进行了函数跟踪(还需要判断是否对子进程进行跟踪),之后调用copy_process(do_fork的核心函数,之后的文章会对它进行分析),在copy_process中会对子进程的许多结构体和参数进行初始化(同时在fork正常情况中为什么会返回两次也是在此函数中实现的),do_fork最后就判断是否是通过vfork创建,如果是vfork创建,则会使父进程阻塞直到子进程结束释放所占内存空间后才继续执行,最后do_fork子进程pid。
小结
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。