【问题标题】:Where is the source for the fork() call in Linux? [closed]Linux 中 fork() 调用的来源在哪里? [关闭]
【发布时间】:2016-04-24 14:10:33
【问题描述】:

我已经花费了相当长的时间来寻找fork() 函数的源代码。我知道fork() 完成的大部分工作都是由do_fork() 完成的,可以在kernel/fork.c 中找到。但是我想看到的是fork() 函数的源代码。

在哪里可以找到它的任何想法?我一直在浏览 GCC 和 Linux 源代码,但仍然没有找到。

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

【问题讨论】:

  • GNU libc 围绕内核 do_fork 使用的“包装器”位于 GNU libc 的源代码中。
  • @Programmer123 使用您的 linux 发行版的 package 命令下载您当前的 linux 内核和 glibc 的源代码,然后搜索
  • @arsane 我实际上已经这样做了,并且在过去的几个小时里一直在寻找,但什么也没有。
  • 你为什么要问? fork 是一个系统调用,所以从用户级应用的角度来看,它是一个基本的原子操作……
  • 你在看什么 Linux 版本?

标签: c linux gcc linux-kernel fork


【解决方案1】:

来自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

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

【讨论】:

  • 不。 glibc 调用clone() 实现fork()
  • 非常感谢。所有这些宏让像我这样的菜鸟很难阅读代码:)
  • @FUZxxl 我相信__libc_fork() 确实调用了fork() 系统调用,而不是clone(),请参阅其他答案。
【解决方案2】:

以x86平台和2.6.23 Linux内核为参考:

  • 创建test-fork.c 文件:

    #include <unistd.h>
    
    int main (void)
    {
        fork();
        return 0;
    }
    
  • 使用静态链接编译:gcc -O0 -static -Wall test-fork.c -o test-fork

  • 拆机:objdump -D -S test-fork &gt; test-fork.dis

  • 打开test-fork.dis文件并搜索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 */
    
  • 打开linux-2.6.23/arch/x86/kernel/process.c文件

  • 搜索sys_fork

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

    请注意,do_fork() 仅使用 SIGCHLD 参数调用

  • 打开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;
      }
    
  • fork 中的大部分工作由do_fork() 处理, 在kernel/fork.c 中定义。 do_fork() 执行的操作:

    • 它通过调用alloc_pid()为孩子分配一个新的PID
    • 它检查父级的ptrace 字段(即current-&gt;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结构和新进程的Kernel Mode栈,并将其地址保存在ti局部变量中
        • 它将当前进程描述符的内容复制到tsk指向的task_struct结构中,然后将tsk-&gt;thread_info设置为ti
        • 它将当前的thread_info描述符的内容复制到ti指向的结构中,然后将ti-&gt;task设置为tsk
        • 它将新进程描述符(即tsk-&gt;usage)的使用计数器设置为2,以指定进程描述符正在使用并且相应的进程处于活动状态(其状态不是EXIT_ZOMBIEEXIT_DEAD
        • 返回新进程的进程描述符指针(即tsk
      • copy_process() 然后检查是否没有超过当前用户的最大进程数(即大于 `max_threads)

    • 它通过清除或初始化task_struct 的各个字段来区分子项和父项
    • 它调用copy_flags() 来更新task_structflags 字段

      • PF_SUPERPRIV(表示任务是否使用超级用户权限)和PF_NOFREEZE 标志被清除
      • 设置了PF_FORKNOEXEC 标志(表示任务是否未调用`exec())
      • 它调用 `init_sigpending() 来清除挂起的信号
      • 根据传递给 do_fork(),copy_process() 的参数,然后复制或共享资源
      • 打开文件
      • 文件系统信息
      • 信号处理程序
      • 地址空间
      • 它调用sched_fork(),它将剩余的时间片在父母和孩子之间分割
      • 最后,它返回一个指向新子节点的指针
    • 然后,do_fork() 添加一个待处理的SIGSTOP 信号,以防设置CLONE_STOPPED 标志或必须跟踪子进程(即PT_PTRACED 标志在p-&gt;ptrace 中设置)

    • 如果没有设置CLONE_STOPPED 标志,它会调用wake_up_new_task() 函数,该函数执行以下操作:

      • 调整父母和孩子的调度参数
      • 如果子进程将与父进程在同一个 CPU 上运行,并且父进程和子进程不共享同一组页表(即CLONE_VM 标志已清除),则它会通过插入它来强制子进程在父进程之前运行在父级之前进入父级的运行队列。如果子进程刷新其地址空间并在分叉后立即执行新程序,这个简单的步骤会产生更好的性能。如果我们让父级先运行,Copy On Write 机制会导致一系列不必要的页面重复。
      • 否则,如果子进程不会在与父进程相同的 CPU 上运行,或者父进程和子进程共享同一组页表(即 CLONE_VM 标志集),它将孩子插入到父运行队列的最后一个位置
    • 否则,如果设置了CLONE_STOPPED 标志,它会将孩子置于TASK_STOPPED 状态
    • 如果正在跟踪父进程,它将子进程的 PID 存储在 currentptrace_message 字段并调用 ptrace_notify(),它实质上会停止当前进程并向其父进程发送SIGCHLD 信号。孩子的“祖父母”是跟踪父母的调试器; SIGCHLD 信号通知调试器当前已经派生了一个子节点,可以通过查看 current-&gt;ptrace_message 字段来检索其 PID。

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

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

【讨论】:

  • 非常感谢。我实际上打印了汇编代码并没有从那里得到太多。我没有想到要创建对象转储。再次感谢。
  • 请注意,此答案适用于 Linux 2.6.23;我的答案是针对 Linux 4.4 - 看起来在两个版本中定义 fork() 系统调用的方式存在显着差异。我引用的代码不在 Linux 2.6.23 中。
  • 天哪,这是一个冗长而彻底的答案。我希望我能多投票给你几次。 :)
【解决方案3】:

这是 glibc 文件的链接 fork.c

【讨论】:

    猜你喜欢
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-22
    • 1970-01-01
    相关资源
    最近更新 更多