2016-01-19 14 views
4

我一直花费相当多的时间试图找到fork()函数的源代码。我知道fork()完成的大部分工作是由do_fork()完成的,可以在kernel/fork.c中找到。但是我想看到的是fork()函数的源代码。Linux中fork()调用的来源在哪里?

任何想法,它可以找到?我一直在通过GCC和Linux源代码,但仍然无法找到它。

编辑:我试图找到我的系统正在使用的确切实现。正如在评论和Link中提到的那样,它显然在glibc的一些包装中。任何想法,在glibc我可以找到包装。我已经彻底搜索,但无法找到它的定义。

+0

GNU libc围绕内核do_fork使用的“包装器”来自GNU libc。 – alk

+1

@ Programmer123用你的linux发行版的package命令下载你当前的linux内核&glibc的源代码,然后搜索 –

+0

@arsane我已经做到了这一点,并且在过去的几个小时里一直在四处搜寻,但没有任何结果。 – Programmer123

回答

13

以作为参考x86平台和2.6。23 Linux内核:

  • 创建test-fork.c文件:gcc -O0 -static -Wall test-fork.c -o test-fork

  • 拆解:objdump -D -S test-fork > test-fork.dis

  • 打开test-fork.dis文件和

    #include <unistd.h> 
    
    int main (void) 
    { 
        fork(); 
        return 0; 
    } 
    
  • 静态链接编译搜索fork

     fork(); 
    80481f4:  e8 63 55 00 00   call 804d75c <__libc_fork> 
         return 0; 
    80481f9:  b8 00 00 00 00   mov $0x0,%eax 
    } 
    80481fe:  c9      leave 
    80481ff:  c3      ret  
    
  • 然后搜索__libc_fork

    0804d75c <__libc_fork>: 
    804d75c:  55      push %ebp 
    804d75d:  b8 00 00 00 00   mov $0x0,%eax 
    804d762:  89 e5     mov %esp,%ebp 
    804d764:  53      push %ebx 
    804d765:  83 ec 04    sub $0x4,%esp 
    804d768:  85 c0     test %eax,%eax 
    804d76a:  74 12     je  804d77e <__libc_fork+0x22> 
    804d76c:  c7 04 24 80 e0 0a 08 movl $0x80ae080,(%esp) 
    804d773:  e8 88 28 fb f7   call 0 <_init-0x80480d4> 
    804d778:  83 c4 04    add $0x4,%esp 
    804d77b:  5b      pop %ebx 
    804d77c:  5d      pop %ebp 
    804d77d:  c3      ret  
    804d77e:  b8 02 00 00 00   mov $0x2,%eax 
    804d783:  cd 80     int $0x80 
    804d785:  3d 00 f0 ff ff   cmp $0xfffff000,%eax 
    804d78a:  89 c3     mov %eax,%ebx 
    804d78c:  77 08     ja  804d796 <__libc_fork+0x3a> 
    804d78e:  89 d8     mov %ebx,%eax 
    804d790:  83 c4 04    add $0x4,%esp 
    804d793:  5b      pop %ebx 
    804d794:  5d      pop %ebp 
    804d795:  c3      ret  
    

    注意,在这个特定的硬件/内核fork系统调用号2

  • 相关下载Linux内核的副本:wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.23.tar.bz2

  • 打开linux-2.6.23/arch/x86/kernel/syscall_table_32.S文件

  • 注意,系统调用号2关联到

    sys_fork: 
        .long sys\_fork /* 2 */ 
    
  • 打开​​文件

  • 搜索sys_fork

    asmlinkage int sys_fork(struct pt_regs regs) 
        { 
          return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL); 
        } 
    

    注意do_fork()仅与称为参数

  • 打开linux-2.6.23/kernel/fork.c文件。这里是do_fork()定义!

  • do_fork()然后调用copy_process()

    /* 
        * Ok, this is the main fork-routine. 
        * 
        * It copies the process, and if successful kick-starts 
        * it and waits for it to finish using the VM if required. 
        */ 
        long do_fork(unsigned long clone_flags, 
           unsigned long stack_start, 
           struct pt_regs *regs, 
           unsigned long stack_size, 
           int __user *parent_tidptr, 
           int __user *child_tidptr) 
        { 
          struct task_struct *p; 
          int trace = 0; 
          struct pid *pid = alloc_pid(); 
          long nr; 
    
          if (!pid) 
            return -EAGAIN; 
          nr = pid->nr; 
          if (unlikely(current->ptrace)) { 
            trace = fork_traceflag (clone_flags); 
            if (trace) 
              clone_flags |= CLONE_PTRACE; 
          } 
    
          p = copy_process(clone_flags, stack_start, regs, stack_size, \ 
              parent_tidptr, child_tidptr, pid); 
    
    
         /* 
         * Do this prior waking up the new thread - the thread 
         * pointer might get invalid after that point, 
         * if the thread exits quickly. 
         */ 
         if (!IS_ERR(p)) { 
           struct completion vfork; 
    
           if (clone_flags & CLONE_VFORK) { 
             p->vfork_done = &vfork; 
             init_completion(&vfork); 
           } 
    
           if ((p->ptrace & PT_PTRACED) || \ 
            (clone_flags & CLONE_STOPPED)) { 
             /* 
             * We'll start up with an immediate SIGSTOP. 
             */ 
             sigaddset(&p->pending.signal, SIGSTOP); 
             set_tsk_thread_flag(p, TIF_SIGPENDING); 
           } 
    
           if (!(clone_flags & CLONE_STOPPED)) 
             wake_up_new_task(p, clone_flags); 
           else 
             p->state = TASK_STOPPED; 
    
           if (unlikely (trace)) { 
             current->ptrace_message = nr; 
             ptrace_notify ((trace << 8) | SIGTRAP); 
           } 
    
           if (clone_flags & CLONE_VFORK) { 
              freezer_do_not_count(); 
              wait_for_completion(&vfork); 
              freezer_count(); 
              if (unlikely (current->ptrace & \ 
                 PT_TRACE_VFORK_DONE)) { 
                current->ptrace_message = nr; 
                ptrace_notify \ 
                ((PTRACE_EVENT_VFORK_DONE << 8) | \ 
                 SIGTRAP); 
              } 
            } 
          } else { 
            free_pid(pid); 
            nr = PTR_ERR(p); 
          } 
          return nr; 
        } 
    
  • 在分叉的大部分工作是由do_fork()处理,在kernel/fork.c定义 。通过do_fork()执行的操作:通过调用alloc_pid()

  • 它检查父的ptrace字段

    • 它分配一个新的PID为子(即current->ptrace
      • 如果不是零,父进程正在被另一个进程跟踪
    • 它调用copy_process(),其中规定了对孩子的要求的过程描述符和任何其他内核数据结构执行

      • 它的参数是相同的do_fork()加上孩子的PID
      • 它检查是否在clone_flags参数传递的标志是兼容
      • 它通过调用security_task_create()security_task_alloc()
      • 它要求新工艺dup_task_struct()它创建新的内核堆栈,thread_infotask_struct结构进行额外的安全检查。

        • 新的值是相同的那些当前任务
        • 的此时孩子,父进程的描述是一致
        • 它执行alloc_task_struct()宏来得到一个task_struct结构,新工艺,并保存其地址在tsk局部变量中。
        • 它执行alloc_thread_info宏来得到一个免费的存储区域来存储thread_info结构和新工艺的内核模式堆栈,并保存其在ti局部变量
        • 它复制地址当前的进程描述符的内容进入task_struct结构指向tsk,然后设置tsk->thread_infoti
        • 它会将当前的thread_info描述符的内容到结构指向ti,然后设置ti->tasktsk
        • 它设置新进程描述符的使用计数器(即, tsk->usage)〜图2来指定进程描述符是在使用中,且对应的处理是活的(它的状态是不EXIT_ZOMBIEEXIT_DEAD
        • 它返回新进程的进程描述符指针(即tsk
      • copy_process()然后检查是否没有超过当前用户的最大进程量(即,比`更大max_threads的)

    • 它通过清除或initilizing的task_struct
    • 它调用copy_flags()各个领域,以更新task_struct

      • 的的flags领域区分从父子PF_SUPERPRIV(表示任务是否使用超级用户权限)和PF_NOFREEZE标志被清除
      • PF_FORKNOEXEC标志(其表示如果一个任务还没有名为`EXEC())被设置
      • 它调用`init_sigpending(),该清除挂起信号
      • 根据传递到do_fork(), copy_process参数()`然后重复或股资源
      • 打开文件
      • 文件系统信息
      • 信号处理
      • 地址空间
      • 它调用sched_fork()其将父母之间的剩余时间片和儿童
      • 最后,它返回一个指向新的子
    • 然后,do_fork()以防CLONE_STOPPED标志被设置增加了一个未决SIGSTOP信号或子进程必须跟踪(即所述PT_PTRACED标志在p->ptrace设置)

    • 如果未设置CLONE_STOPPED标志,它调用wake_up_new_task()功能,其执行以下操作:

      • 它调整母公司和两者的调度参数孩子
      • 如果孩子将相同的CPU作为父母和父母和孩子在跑不共享同一套页表(即CLONE_VM标志清零),它然后将其插入迫使孩子家长之前运行父母的跑步权在帕尔之前ENT。如果孩子在分岔后立即刷新地址空间并执行一个新程序,这个简单的步骤会产生更好的性能。如果我们让父代首先运行,那么机制会产生一系列不必要的页面重复。
      • 否则,如果孩子不会与父母在同一CPU上运行,或者父母和孩子共享相同的一组页面表(即设置了 CLONE_VM标志),它会将孩子插入父母的运行队列
    • 否则,如果CLONE_STOPPED标志设置,它使孩子在TASK_STOPPED状态
    • 如果父进程正被跟踪,它在 的ptrace_message字段存储的子进程的PID current并调用 ptrace_notify(),它基本上停止当前进程,并向其父节点发送一个SIGCHLD信号。孩子的“祖父母”是跟踪父母的调试者; SIGCHLD信号通知调试器当前已经分叉了一个孩子,可以通过查看current->ptrace_message字段来检索其PID。

    • 如果指定了CLONE_VFORK标志,它插入父进程处于等待队列并暂停它,直到子释放其存储器地址空间(即,直到子或者终止或者执行一个新的程序)

  • 它通过返回孩子的PID终止。
+0

非常感谢你。实际上我打印出来的汇编代码并没有从那里得到太多。没有想到创建对象转储。再次感谢。 – Programmer123

+0

请注意,这个答案适用于Linux 2.6.23;我的答案是针对Linux 4.4--它看起来像是在这两个版本中定义fork()''syscall的方式有很大的不同。我引用的代码不在Linux 2.6.23中。 –

+0

我的天啊,这是一个漫长而彻底的答案。我希望我能多投几倍。 :) –

4

http://lxr.free-electrons.com/source/kernel/fork.c#L1787这是Linux 4.4:

1787 #ifdef __ARCH_WANT_SYS_FORK 
1788 SYSCALL_DEFINE0(fork) 
1789 { 
1790 #ifdef CONFIG_MMU 
1791   return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0); 
1792 #else 
1793   /* can not support in nommu mode */ 
1794   return -EINVAL; 
1795 #endif 
1796 } 
1797 #endif 

我相信这是它定义了系统调用叉。在Linux下,我相信glibc fork()函数直接调用这个系统调用,而不用做任何事情。

+1

没有。 glibc调用'clone()'来实现'fork()'。 – fuz

+0

辉煌谢谢。所有这些宏使像我这样的noobes真的难以阅读代码:) – Programmer123

+0

@FUZxxl我相信''__libc_fork()''确实会调用fork()''系统调用,而不是''clone()''看到其他答案。 –