在* nix系统中,使用fork()系统调用创建进程。例如,考虑init进程创建另一个进程。首先它自己分叉并创建一个具有init等上下文的进程。只有在调用exec()时,这个子进程才会成为一个新进程。那么为什么需要中间步骤(创建具有与父相同的上下文的子项)?这不是浪费时间和资源,因为我们正在创建一个上下文(消耗时间和浪费内存),然后过度编写它?
为什么这没有实现为分配空闲内存区域然后调用exec()?这样可以节省时间和资源吗?
答案 0 :(得分:3)
通过中间步骤,您可以在子进程中设置共享资源,而无需外部程序了解它。规范示例是构建管道:
// read output of "ls"
// (error checking omitted for brevity)
int pipe_fd[2];
pipe(&pipe_fd);
if (fork() == 0) { // child:
close(pipe_fd[0]); // we don't want to read from the pipe
dup2(pipe_fd[1], 1); // redirect stdout to the write end of the pipe
execlp("ls", "ls", (char *) NULL);
_exit(127); // in case exec fails
}
// parent:
close(pipe_fd[1]);
fp = fdopen(pipe_fd[0], "r");
while (!feof(fp)) {
char line[256];
fgets(line, sizeof line, fp);
...
}
注意如何在fork
和exec
之间在子项中完成标准输出到管道的重定向。当然,对于这个简单的情况,可以有一个spawning API,只要给出适当的参数,就可以自动执行此操作。但是fork()
设计允许对子进程中的每进程资源进行任意操作 - 可以关闭不需要的文件描述符,修改每进程限制,删除权限,操纵信号掩码等等。如果没有fork()
,产生过程的API最终会变得非常胖或者非常有用。实际上,产生竞争操作系统的过程产生的过程通常介于两者之间。
至于内存的浪费,使用copy on write技术可以避免。 fork()
不会为子进程分配新内存,而是将子进程指向父进程的内存,并指示只有在页面被写入时才创建页面副本。这使fork()
不仅内存效率高,而且速度快,因为它只需要复制“目录”。
答案 1 :(得分:2)
这是一个古老的抱怨。很多人首先要求为什么fork()
?,通常他们会建议一个从头开始创建新流程并在其中运行程序的操作。此操作称为spawn().
他们总是说,那会不会更快?
事实上,除了Unix系列之外的每个系统都采用“spawn”方式。只有Unix基于fork()
和exec().
但有趣的是,Unix一直比其他全功能系统快得多。它总是处理更多的用户和负载。
多年来Unix的发展速度更快。 Fork()不再真正复制地址空间,它只是使用一种名为copy-on-write.的技术共享它(一个名为vfork()
的非常古老的叉优化也仍然存在。)
答案 2 :(得分:0)
我不知道 init 进程在分叉方面是如何在内核上运行的,但是为了回答你为什么需要调用 fork 然后 exec 只是因为一旦你执行就没有回头路了。
如果查看文档here,它实质上需要生成一个新进程( fork 调用),以便父进程恢复控制并且等待让它完成或作为守护进程坐下。
答案 3 :(得分:0)
只有在调用exec()时,这个子进程才会成为新进程 过程
不是真的。在fork之后,您已经拥有了新的进程,即使与其父进程没有太大的不同。在某些情况下,没有exec需要遵循fork。
那么为什么是中间步骤(创建一个相同的孩子 作为父母的上下文)需要吗?
一个原因是因为它是创造整个shebang的有效方式。克隆通常不如从头开始创建。
这不是浪费时间和资源,因为我们正在创造一个 上下文(消耗时间和浪费记忆),然后过度写作?
由于使用了写入机制的复制,因此大多数此资源都是虚拟的,这不是浪费时间和资源。此外,说明创建的上下文被覆盖是不正确的。没有任何东西可以改写,因为事实上并没有真正写出来。这是COW的重点。 “仅”进程地址空间(代码,堆和堆栈)被替换,而不是被覆盖。部分或全部保留了许多进程上下文,包括环境,文件描述符,优先级,忽略的信号,当前和根目录,限制,各种掩码,处理器绑定,特权以及进程地址空间异常的其他一些内容。