fork 和 exec 是如何工作的?

Pri*_*riB 17 fork exec

我没有太多经验,只是试图参与他们如何从用户级别解释到硬件的过程。

因此,当从 shell 触发命令时,fork()继承它exec()的子进程并将子进程加载到内存中并执行。

  1. 如果子进程包含了父进程(也就是原始进程)的所有属性,那么这个子进程需要什么?原始进程也可能已加载到内存中。
  2. 这个forkexec概念是否适用于 UNIX 中的所有可执行程序?也喜欢shell脚本还是只喜欢命令?它是否也适用于 shell 内置命令?
  3. 如果我将执行命令/脚本,何时使用复制概念?

很抱歉一次问了很多问题,但是当我想到任何命令执行时,所有这些问题都会立刻浮现在我的脑海中。

psu*_*usi 22

因此,当从 shell 触发命令时,fork() 继承它的子进程,然后 exec() 将子进程加载到内存并执行。

不完全的。 fork()克隆当前进程,创建一个相同的子进程。 exec()将新程序加载到当前进程中,替换现有程序。

我的问题是:

如果子进程包含父进程(即原始进程)的所有属性,那么这个子进程需要什么?原始进程也可能已加载到内存中。

需要是因为父进程还不想终止;它希望一个新进程在继续执行的同时启动并执行某些操作。

这个 fork 和 exec 概念是否适用于 UNIX 中的所有可执行程序?像 shell 脚本一样还是仅适用于命令?它是否也适用于 shell 内置命令?

对于外部命令,shell 执行 afork()以便命令在新进程中运行。内置函数直接由 shell 运行。另一个值得注意的命令是exec,它exec()无需先fork()ing 就将shell 告诉外部程序。这意味着外壳本身被新程序替换,因此该程序退出时不再返回。如果您说, exec true,/bin/true则将替换您的外壳,并立即退出,不再在终端中运行任何内容,因此它将关闭。

如果我将执行命令/脚本,何时使用写时复制概念?

回到石器时代,fork()实际上不得不将调用进程中的所有内存复制到新进程中。写时复制是一种优化,其中设置了页表,以便两个进程开始共享所有相同的内存,并且只有在需要时才复制由任一进程写入的页面。

  • “对于几乎每个命令,shell 都会执行 fork() 以便命令在新进程中运行。如果该命令是内置命令,那么子进程不需要 exec() 一个单独的程序。” 很好的答案,但这部分应该编辑。运行内置函数时,shell 不会分叉。它直接在活动的 shell 进程中运行那些。这是像 `cd` 或 `read` 这样的内置函数可以工作的唯一方法。没有分叉也使得内置命令比外部命令快得多。 (4认同)

Bru*_*ger 6

  1. 对于某些程序,子进程做一件事(从串行端口读取,写入终端),而父进程继续做其他事情(从终端读取,写入串行端口)。另一个经典示例是子进程对正在发生的任何长时间运行的计算进行检查点。大多数情况下,子进程会进行一些设置,例如更改目录、重置信号处理程序或重置文件描述符,然后调用execve()以使用不同的代码覆盖自身。
  2. fork()并且exec()确实适用于所有可执行文件 - 事实上,与 argc 和 argv 一起,管道、fork 和 exec 是 Unix 与其他操作系统的区别。fork()存在一些专业化或泛化,例如 BSD 的vfork()、Plan 9 的rfork()和 Linux 的clone(),但主要内容保持不变。
  3. “写时复制”并没有真正显示给用户,它更多的是一种优化创建子进程以及执行过程中的技术。调用堆栈和堆(使用malloc(),甚至静态或全局范围变量分配的内存)可能是“写入时复制”。当一个子进程被创建时fork()调用,内核将设置子进程与父进程具有与堆和堆栈完全相同的内存页面。如果硬件(内存管理单元)检测到堆或堆栈的写入,内核将获得一个新的内存物理页面,将父页面复制到新页面中,并将该新页面映射到子进程的堆栈或堆中。这构成了一种优化,因为内核在设置页面映射上花费的时间比为子进程完全复制堆栈和堆所花费的时间要少。


小智 5

如果子进程包含了父进程(也就是原始进程)的所有属性,那么这个子进程需要什么?原始进程也可能已加载到内存中。

通过查看必须在严格的内存限制下工作并且一次在内存/地址空间中只有一个执行进程的最早的 Unix 实现,可以非常形象地回答这个问题。

多任务是通过将一个进程交换到磁盘并交换一个不同的进程来实现的。

现在fork系统调用几乎相同:它将一个进程换出到磁盘,但不是换入另一个进程,而是给内存中的副本另一个进程 ID 并返回给它。exec毕竟,这是这个过程决定只进入另一个可执行文件的合适时机。

fork+exec因此实际上并没有产生明显的生成开销:无论如何你必须将你的进程换出到磁盘,并且无论如何你在可用的内存位置都有旧的进程映像。

随着可用内存和内存管理单元数量的增加以及多个内存中的进程,分叉的最初可以忽略不计的成本对于某些架构来说变得更加麻烦:因此vfork诞生了。