_do_fork()如何返回两个不同的PID(一个用于父进程,一个用于子进程)

时间:2022-11-03 17:00:45

I was looking at the _do_fork() function () trying to understand how fork() returns the child PID for the parent process and 0 on the child process.

我正在查看_do_fork()函数(),试图理解fork()如何返回父进程的子PID和子进程的0。

I think that nr contains the PID of the child process (that will be returned to the caller process), but I can't see how it is able to return 0 to the child process.

我认为nr包含子进程的PID(将返回到调用者进程),但我看不出它如何能够将0返回到子进程。

The answer How does fork() know when to return 0? says that the return value is passed on the stack created for the new process, but (besides not really understanding it) I can't find that in the code.

答案fork()如何知道何时返回0?说返回值是在为新进程创建的堆栈上传递的,但是(除了没有真正理解它)我在代码中找不到它。

So, where the return value of 0 is set for the child process?

那么,为子进程设置返回值0的位置是什么?

The code of the _do_fork() function is copied below:

_do_fork()函数的代码复制如下:

long _do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr,
          unsigned long tls)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    /*
     * Determine whether and which event to report to ptracer.  When
     * called from kernel_thread or CLONE_UNTRACED is explicitly
     * requested, no event is reported; otherwise, report if the event
     * for the type of forking is enabled.
     */
    if (!(clone_flags & CLONE_UNTRACED)) {
        if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK;
        else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;
        else
            trace = PTRACE_EVENT_FORK;

        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }

    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
    add_latent_entropy();
    /*
     * 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;
        struct pid *pid;

        trace_sched_process_fork(current, p);

        pid = get_task_pid(p, PIDTYPE_PID);
        nr = pid_vnr(pid);

        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);

        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }

        wake_up_new_task(p);

        /* forking complete and child started to run, tell ptracer */
        if (unlikely(trace))
            ptrace_event_pid(trace, pid);

        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
        }

        put_pid(pid);
    } else {
        nr = PTR_ERR(p);
    }
    return nr;
}

1 个解决方案

#1


9  

You have correctly identified how the new process id is returned to the parent, with return nr. But you will never actually see a return 0 anywhere since this code is executed on the parent thread. This code is not for the new process that is created.

您已正确识别新进程ID如何返回到父进程,返回nr。但是,由于此代码在父线程上执行,因此您永远不会在任何地方看到返回0。此代码不适用于创建的新进程。

Now let us examine the _do_fork function.

现在让我们来看看_do_fork函数。

...
}
p = copy_process(clone_flags, stack_start, stack_size,
         child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
add_latent_entropy();
...

This is where all the magic happens. When you call copy_process , it internally calls copy_thread which is a target specific code. This function is responsible for coping the thread related data structures.

这就是所有魔法发生的地方。当您调用copy_process时,它会在内部调用copy_thread,这是一个特定于目标的代码。该函数负责处理与线程相关的数据结构。

Now say we have the target as X86_64 with the calling convention that the return value is returned in the %rax register. This function then copies 0 into %rax and copies the value of return_from_fork address to %rip(the instruction pointer).

现在说我们将目标作为X86_64,其调用约定是在%rax寄存器中返回返回值。然后,此函数将0复制到%rax中,并将return_from_fork地址的值复制到%rip(指令指针)。

On other platforms the ABI might require the return value to go on the stack. In that case 0 is placed on the stack. copy_thread is target specific but copy_process is not.

在其他平台上,ABI可能要求返回值进入堆栈。在那种情况下,0被放置在堆栈上。 copy_thread是特定于目标但不是copy_process。

This is the implementation of copy_thread for X86_64. You can see around line number 160 the sp registers being set. And at line 182 you can see %ax (which is a subregister of %rax) being set to 0.

这是X86_64的copy_thread的实现。您可以在第160行周围看到正在设置的sp寄存器。在第182行,您可以看到%ax(%rax的子寄存器)被设置为0。

I hope this clears some confusion.

我希望这可以解决一些困惑。

#1


9  

You have correctly identified how the new process id is returned to the parent, with return nr. But you will never actually see a return 0 anywhere since this code is executed on the parent thread. This code is not for the new process that is created.

您已正确识别新进程ID如何返回到父进程,返回nr。但是,由于此代码在父线程上执行,因此您永远不会在任何地方看到返回0。此代码不适用于创建的新进程。

Now let us examine the _do_fork function.

现在让我们来看看_do_fork函数。

...
}
p = copy_process(clone_flags, stack_start, stack_size,
         child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
add_latent_entropy();
...

This is where all the magic happens. When you call copy_process , it internally calls copy_thread which is a target specific code. This function is responsible for coping the thread related data structures.

这就是所有魔法发生的地方。当您调用copy_process时,它会在内部调用copy_thread,这是一个特定于目标的代码。该函数负责处理与线程相关的数据结构。

Now say we have the target as X86_64 with the calling convention that the return value is returned in the %rax register. This function then copies 0 into %rax and copies the value of return_from_fork address to %rip(the instruction pointer).

现在说我们将目标作为X86_64,其调用约定是在%rax寄存器中返回返回值。然后,此函数将0复制到%rax中,并将return_from_fork地址的值复制到%rip(指令指针)。

On other platforms the ABI might require the return value to go on the stack. In that case 0 is placed on the stack. copy_thread is target specific but copy_process is not.

在其他平台上,ABI可能要求返回值进入堆栈。在那种情况下,0被放置在堆栈上。 copy_thread是特定于目标但不是copy_process。

This is the implementation of copy_thread for X86_64. You can see around line number 160 the sp registers being set. And at line 182 you can see %ax (which is a subregister of %rax) being set to 0.

这是X86_64的copy_thread的实现。您可以在第160行周围看到正在设置的sp寄存器。在第182行,您可以看到%ax(%rax的子寄存器)被设置为0。

I hope this clears some confusion.

我希望这可以解决一些困惑。