所以我有一个我想了解的程序,它来自旧的考试,但是我无法掌握。我如何知道分叉的顺序以及如何更改变量?
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;
}
答案 0 :(得分:2)
对fork()
的调用既开始了新的过程,又继续了旧的过程。如果存在某种错误,它将返回一个错误值。所有错误和仅有错误是负数。这是第一个if
块检查的内容。
在新进程中,fork()
返回0。因此,递增v
和递减g
的分支仅在子进程中被调用,而不在父进程中被调用。
在原始进程中,fork()
函数返回子进程的进程标识符(PID),它是一个正整数。 (这将稍后传递给waitpid()
。因此,仅在父进程中调用分支v
并递增g
的分支,而不是子进程。
每个进程都有自己的v
和g
副本。 (这是进程和线程之间的主要区别:线程共享内存。)在现代的SMP操作系统上,将发生的事情是子进程获得父进程的内存映射的副本。但是这些引用指的是物理内存的相同页面,直到一个进程或另一个进程对其进行写入为止。发生这种情况时,将在该内存页面上创建一个副本,并且两个进程现在都将获得自己的不同副本。
现代Linux内核实现fork()
的方式,子进程将在父进程之前继续执行。这对性能产生了重大影响。大多数调用fork()
的程序都会立即有子进程调用exec()
来启动新程序。这意味着它根本不需要父代记忆的副本。 (现在有一种更新,更简单的方法可以在新进程posix_spawn()
中启动其他程序。)另一方面,父进程几乎总是保持运行并修改其内存。因此,给孩子一个机会来声明它将丢弃它继承的内存,这意味着父母无需担心为其孩子留下任何内存页面的未修改副本,并且内核也不必经历写入时复制的严密条件。
但是,实际上,任何体面的编译器都会将两个局部变量都保留在寄存器中,因此不会出现此问题。
在循环的下一次迭代中(仅在子进程终止后才发生),使用父变量的更新值生成新的子进程。每个子进程还将继续使用其从其父级继承的v
和g
的值来运行循环。
答案 1 :(得分:2)
每次对fork的调用都会使用自己的变量生成自己的进程,这些变量会在调用时进行复制(逻辑上;优化可能会在实际复制发生时更改,但不会改变结果)。
因此,当您进入循环时,v会增加到1,然后进行分叉。此时,父进程的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的结尾,退出循环并打印其结果。
这时,父级将解除阻止,再循环一次(沿途分叉另一个孩子),然后(一旦孩子完成)退出循环。