【问题标题】:Understanding fork() order in C理解 C 中的 fork() 顺序
【发布时间】:2019-05-11 21:32:06
【问题描述】:

所以我有这个程序我想了解,它来自一个旧考试,但我无法掌握它。我如何知道分叉的顺序以及变量如何更改?

static int g = -1;

int main(int argc, char *argv[])
{
    int v = 0;
    pid_t p;

    while (v++ < 6)
        if ((p = fork()) < 0) {
            perror("fork error");
            return 1;
        } else if (p == 0) {
            v++;
            g--;
        } else {
            g++;
            v+=3;
        if (waitpid(p, NULL, 0) != p) {
            perror("waitpid error");
            return 1;
        }
    }
    printf("mypid = %d parentpid = %d p = %d v = %d g = %d\n",
    getpid(), getppid(), p, v, g);
    return 0;
}

【问题讨论】:

  • 如果缩进一致,可能会更容易阅读。在while 下对齐的} 实际上属于} else { 块。这并不可怕,但也不对。我希望在整个循环体周围看到一组额外的大括号,但从技术上讲,它不是必需的。
  • 简答:没有顺序。
  • @JonathanLeffler 我直接从考试中复制了它,我猜他们这样做只是为了混淆。

标签: c unix fork posix


【解决方案1】:

对 fork 的每次调用都会使用自己的变量生成自己的进程,这些变量会在调用时被复制(从逻辑上讲,优化可能会在实际复制发生时发生变化,但不会以会改变结果的方式发生变化)。

所以当你进入循环时,v 会递增到 1,然后你 fork。此时父进程有 g=-1, v=1, p= 而新子进程有 g=-1, v=1, p=0。然后父级进入 else 情况,将 g 增加到 0 并将 v 增加到 4,然后等待子级完成,而子级进入“else if (p == 0)”,将 v 增加到 2,减少 g到-2,然后再次循环。

从那里,希望您现在获得了足够的信息来遵循逻辑,因为接下来的两个子进程被分叉,完成循环并打印它们各自的结果。当他们这样做时,第一个孩子也会以 v=6 到达其 waitpid 的末尾,退出循环并打印其结果。

此时,父级将解除阻塞,再绕循环一次(沿途再分叉一个子级),然后(一旦该子级完成)退出循环。

【讨论】:

    【解决方案2】:

    fork() 的调用会启动一个新进程并继续旧进程。如果有某种错误,它会返回一个错误值。所有错误,只有错误是负数。这是第一个 if 块检查的内容。

    在新进程中,fork() 返回 0。因此,递增 v 并递减 g 的分支仅在子进程中调用,而不在父进程中调用。

    在原进程中,fork()函数返回子进程的进程标识符(PID),为正整数。 (这稍后将传递给waitpid()。因此,递减v 并递增g 的分支只在父进程中调用,而不是子进程。

    每个进程都有自己的vg 副本。 (这是进程和线程之间的主要区别:线程共享内存。)在现代 SMP 操作系统上,子进程会获得父进程内存映射的副本。但这些指的是相同的物理内存页面,直到一个进程或另一个进程写入它们。发生这种情况时,会对该内存页进行复制,并且两个进程现在都会获得自己的不同副本。

    现代 Linux 内核实现 fork() 的方式,子进程将在父进程之前继续。这对性能产生了重大影响。大多数调用fork() 的程序会立即让子进程调用exec() 来启动一个新程序。这意味着它根本不需要它的父内存副本。 (现在有一种更新、更简单的方法可以在新进程中启动不同的程序,posix_spawn()。)另一方面,父进程几乎总是保持运行并修改其内存。因此,让孩子有机会声明它将丢弃它继承的内存意味着父级无需担心为其子级留下任何内存页面的未修改副本,内核也不必经过写时复制的复杂性。

    但实际上,任何体面的编译器都会将两个局部变量都保存在寄存器中,因此不会出现此问题。

    在循环的下一次迭代中,仅在​​子进程终止后发生,使用父变量的更新值生成一个新的子进程。每个子进程还使用从其父进程继承的相同值 vg 继续运行循环。

    【讨论】:

    • 子进程首先运行还有另一个原因——父进程在再次循环之前等待子进程死亡。
    • @JonathanLeffler 也是如此,但在父母甚至有机会等待它死亡之前,孩子也会(在某些操作系统上)运行。
    猜你喜欢
    • 1970-01-01
    • 2014-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    相关资源
    最近更新 更多