linux fork实现方法,(转)关于linux系统如何实现fork的研究(一)

1

2 INLINE_SYSCALL (clone, 5, CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid);3

4

5 #defineINLINE_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 intINLINE_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

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

2

3 ENTRY(vector_swi)4

7 #ifdef CONFIG_CPU_V7M8 v7m_exception_entry9 #else

10 sub sp, sp, #S_FRAME_SIZE11 stmia sp, {r0 - r12} @ 将r0~r12保存到栈中12 ARM( add r8, sp, #S_PC )13 ARM( stmdb r8, {sp, lr}^) @ Calling sp, lr14 THUMB( mov r8, sp )15 THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr16 mrs r8, spsr @ called from non-FIQ mode, so ok.17 str lr, [sp, #S_PC] @ Save calling PC18 str r8, [sp, #S_PSR] @ Save CPSR19 str r0, [sp, #S_OLD_R0] @ Save OLD_R020 #endif

21 zero_fp22 alignment_trap r10, ip, __cr_alignment23 enable_irq24 ct_user_exit25 get_thread_info tsk26

27

30

31 #ifdefined(CONFIG_OABI_COMPAT)

32

33

36 #ifdef CONFIG_ARM_THUMB37 tst r8, #PSR_T_BIT38 movne r10, #0@ no thumb OABI emulation39 USER( ldreq r10, [lr, #-4] ) @ getSWI instruction40 #else

41 USER( ldr r10, [lr, #-4] ) @ getSWI instruction42 #endif

43 ARM_BE8(rev r10, r10) @ little endian instruction44

45 #elifdefined(CONFIG_AEABI)

46

47

50 #elifdefined(CONFIG_ARM_THUMB)

51

52 tst r8, #PSR_T_BIT53 addne scno, r7, #__NR_SYSCALL_BASE54 USER( ldreq scno, [lr, #-4] )55

56 #else

57

58 USER( ldr scno, [lr, #-4] ) @ 获取系统调用号59 #endif

60

61 adr tbl, sys_call_table @ tbl为r8,这里是将sys_call_table的地址(相对于此指令的偏移量)存入r862

63 #ifdefined(CONFIG_OABI_COMPAT)

64

69 bics r10, r10, #0xff000000

70 eorne scno, r10, #__NR_OABI_SYSCALL_BASE71 ldrne tbl, =sys_oabi_call_table72 #elif!defined(CONFIG_AEABI)

73 bic scno, scno, #0xff000000

74 eor scno, scno, #__NR_SYSCALL_BASE75 #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_trace83

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_OFF89 2: cmp scno, #(__ARM_NR_BASE -__NR_SYSCALL_BASE)90 eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back91 bcs arm_syscall92 mov why, #0@ no longer a real syscall93 b sys_ni_syscall @ not privatefunc94

95 #ifdefined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)

96

104 9001:105 sub lr, lr, #4

106 str lr, [sp, #S_PC]107 b ret_fast_syscall108 #endif

109 ENDPROC(vector_swi) @ 返回

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

1

2

3 CALL(sys_restart_syscall)4 CALL(sys_exit)5 CALL(sys_fork)6 CALL(sys_read)7 CALL(sys_write)8 CALL(sys_open)9 CALL(sys_close)10 CALL(sys_ni_syscall)

11 CALL(sys_creat)12 CALL(sys_link)13 CALL(sys_unlink)14 CALL(sys_execve)15 CALL(sys_chdir)16 CALL(OBSOLETE(sys_time))

17 CALL(sys_mknod)18 CALL(sys_chmod)19 CALL(sys_lchown16)20 CALL(sys_ni_syscall)

21 CALL(sys_ni_syscall)

22 CALL(sys_lseek)23 CALL(sys_getpid)24 CALL(sys_mount)25 CALL(OBSOLETE(sys_oldumount))

26 CALL(sys_setuid16)27 CALL(sys_getuid16)28 CALL(OBSOLETE(sys_stime))29 CALL(sys_ptrace)30 CALL(OBSOLETE(sys_alarm))

31 CALL(sys_ni_syscall)

32 CALL(sys_pause)33

34 ......................35 ......................36

37 CALL(sys_clone)

38 CALL(sys_setdomainname)39 CALL(sys_newuname)40 CALL(sys_ni_syscall)

41 CALL(sys_adjtimex)42 CALL(sys_mprotect)43 CALL(sys_sigprocmask)44 CALL(sys_ni_syscall)

45 CALL(sys_init_module)46 CALL(sys_delete_module)47

48 ......................49 ......................50

51 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 CALL(sys_sched_setattr)57 CALL(sys_sched_getattr)58 CALL(sys_renameat2)59 CALL(sys_seccomp)60 CALL(sys_getrandom)61 CALL(sys_memfd_create)62 CALL(sys_bpf)63 #ifndef syscalls_counted64 .equ syscalls_padding, ((NR_syscalls + 3) & ~3) -NR_syscalls65 #define syscalls_counted

66 #endif

67 .rept syscalls_padding68 CALL(sys_ni_syscall)69 .endr

CALL为一个宏,而我们使用的那一行CALL(sys_clone)配合ldrcc pc,[tbl,scno,lsl

#2]使用的结果就是把sys_clone的地址放入pc寄存器。具体我们仔细分析一下,首先先看看CALL宏展开,然后把CALL代入ldrcc,结果

就很清晰了

1

2 #defineCALL(x) .equ NR_syscalls,NR_syscalls+1

3 #include "calls.S"

4

5 .ifne NR_syscalls -__NR_syscalls6 .error "__NR_syscalls is not equal to the size of the syscall table"

7 .endif8

9

12

13 #undefCALL

14

15 #defineCALL(x) .long x

16

17 #ifdef CONFIG_FUNCTION_TRACER18

19

20

21 ldrcc pc, [tbl, scno, lsl #2]22

23

24 ldrcc pc, sys_clone(函数地址)

清楚的看出来,ldrcc最后是将sys_clone的函数地址存入了pc寄存器,而sys_clone函数内核是怎么定义的呢,如下

perl">

1

2

3

6 #ifdef __ARCH_WANT_SYS_CLONE7 #ifdef CONFIG_CLONE_BACKWARDS8 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 #elifdefined(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 #elifdefined(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

39

40 #defineSYSCALL_DEFINE0(sname) \

41 SYSCALL_METADATA(_##sname, 0); \42 asmlinkage long sys_##sname(void)43

44 #defineSYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

45 #defineSYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)

46 #defineSYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

47 #defineSYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

48 #defineSYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

49 #defineSYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

50

51 #defineSYSCALL_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函数被定义为

cpp">

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

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, inttls_val)16 {17 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);18 }

终于看到最后系统会调用do_fork函数进行操作,接下来我们看看do_fork函数

1

2

10 long do_fork(unsigned longclone_flags,11 unsigned longstack_start,12 unsigned longstack_size,13 int __user *parent_tidptr,14 int __user *child_tidptr)15 {16 struct task_struct *p;17 int trace = 0;18 longnr;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

34 p =copy_process(clone_flags, stack_start, stack_size,35 child_tidptr, NULL, trace);36

37

38 if (!IS_ERR(p)) {39

40 structcompletion vfork;41 struct pid *pid;42

43 trace_sched_process_fork(current, p);44

45

46 pid =get_task_pid(p, PIDTYPE_PID);47

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

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

78 returnnr;79 }

在do_fork函数中,首先会根据clone_flags判断是否对父进程进行了跟踪(调试使用),如果进行了函数跟踪

(还需要判断是否对子进程进行跟踪),之后调用copy_process(do_fork的核心函数,之后的文章会对它进行分析),在

copy_process中会对子进程的许多结构体和参数进行初始化(同时在fork正常情况中为什么会返回两次也是在此函数中实现的),do_fork

最后就判断是否是通过vfork创建,如果是vfork创建,则会使父进程阻塞直到子进程结束释放所占内存空间后才继续执行,最后do_fork子进程

pid。

小结

到这里,整个系统调用的入口就分析完了,其实整个流程也不算很复杂:应用层通过swi软中断进入内核---->通过系统调用表选

定目标系统调用--->执行系统调用--->返回。之后的文章我会详细分析copy_process函数,此函数中涉及相当多的知识,作为学

习linux内核的入口也是比较合适的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值