【问题标题】:Child Processes memory allocation and the purpose of reaping child processes子进程内存分配和收获子进程的目的
【发布时间】:2018-11-14 10:07:35
【问题描述】:

我是进程的初学者,仍在努力理解获取子进程和为其分配内存的目的,所以我的问题是:

第一季度。为什么Linux没有自动收割子进程的机制,我的意思是所有子进程一旦完成就会像垃圾回收一样被终止并删除,这样用户就不需要使用waitpid手动收割子进程了?

第二季度。我的教科书说,当用户使用 fork() 创建新的子进程时,子进程会获得父进程的文本、数据和 bss 段、堆和用户堆栈的相同(但单独)副本。那么这是否意味着父母的相同大小的内存地址也被分配给孩子,并且孩子的内存内容与其父母完全相同?如果是这样,假设我们创建了大量的子进程,堆栈是不是很容易溢出?

第三季度。可以说下图是父进程

您可以看到以红色突出显示的用户堆栈是父堆栈。那么如果父程序使用fork(),当它执行fork()函数时,子进程的栈是如何分配的呢?是当前栈旁边的子栈

【问题讨论】:

  • 对于 Q1:考虑一个父进程派生一个子进程。当子进程终止时,操作系统会继续存储子进程的返回状态信息,以便父​​进程调用waitpid时可以得到返回状态。只有父进程调用waitpid,操作系统才能获取子进程。
  • @MFisherKDX 那么获取子进程的目的仅仅是为了获取子进程的返回状态吗?
  • 在这里阅读:Zombie Process

标签: c linux process operating-system


【解决方案1】:

第一季度。为什么Linux没有自动收割子进程的机制,我的意思是所有子进程一旦完成就会像垃圾回收一样被终止并删除,这样用户就不需要使用waitpid手动收割子进程了?

确实如此。只需将 SIGCHLD 设置为忽略,您的孩子就会为您收割。

第二季度。我的教科书说,当用户使用 fork() 创建新的子进程时,子进程会获得父进程的文本、数据和 bss 段、堆和用户堆栈的相同(但独立)副本。那么这是否意味着父母的相同大小的内存地址也分配给了孩子,并且孩子的内存内容与其父母完全相同?

这是一个实现细节。大多数现代系统只是在两个进程之间共享内存。

如果是这样,假设我们创建了大量的子进程,堆栈是不是很容易溢出?

没有。当您在特定进程的内存视图中用完地址空间时,堆栈溢出。由于每个fork 都会创建一个新进程,因此您不会在任何特定进程中耗尽地址空间。

第三季度。可以说下图是父进程在此处输入图像描述 ...您可以看到以红色突出显示的用户堆栈是父堆栈。那么如果父程序使用fork(),当它执行fork()函数时,子进程的栈是如何分配的呢?子栈在当前栈旁边吗?

子进程的堆栈在另一个进程中。因此,如果这是父进程段的地址空间,则子进程的堆栈根本不在其中。子进程从父地址空间的副本开始——通常共享实际内存页面,至少在任一进程尝试修改它们(取消共享它们)之前。

【讨论】:

【解决方案2】:

虽然 David Schwartz already answered 提出了问题,但我想谈谈基本情况。所以,不要认为这是一个答案,而是一个扩展评论。

问题是,显示的图像不能很好地表示普通用户空间应用程序在当前计算机和操作系统上看到的地址空间。

每个进程都有自己的地址空间。这是使用virtual memory 实现的;从虚拟地址到实际硬件访问的映射,用于用户空间进程不可见的所有意图和目的。 (虚拟内存不是每个地址的,而是使用称为pages 的小块。在所有当前架构上,每个页面的大小都是两个字节的幂。212 = 4096 很常见,但其他也使用了 216 = 65536 和 221 = 2097152 等尺寸。)

即使您使用共享内存,它也可以驻留在不同进程的不同虚拟地址。 (这也是你不能真正在共享内存中使用指针的原因。)

当一个进程被 fork() 时,虚拟内存被克隆。 (它本身并没有真正复制,因为这会浪费资源。通常操作系统内核使用一种称为copy-on-write 的技术,因此实际相同的物理 RAM 用于两者使用的内存/所有进程,直到其中一个进程修改了它们的“副本”;此时操作系统内核分离受影响的页面,复制其内容,然后允许修改继续进行。)

这意味着在 fork() 之后,父进程和子进程的堆栈将位于完全相同的虚拟地址。

唯一的限制是有多少实际可用的 RAM。实际上,操作系统内核也可以将当前未使用的文件移动到swap or paging file;但是如果很快就需要这些页面,则会减慢机器的运行速度。至少在 Linux 上,二进制文件和库也直接映射到它们各自的文件——这就是为什么你不能在使用它们时修改可执行文件的原因——所以除非修改了代码的 RAM 副本,否则它们倾向于不使用交换/分页文件。

在大多数情况下,一些虚拟内存范围是为操作系统内核保留的。它确实意味着内核内存对用户空间可见,或以任何方式可访问;这只是一种确保在向或从用户空间进程传输数据时,操作系统内核可以使用用户空间虚拟内存地址,而不是将它们与自己的内部地址混合的一种方式。本质上,操作系统内核不会创建任何虚拟内存映射到它自己使用的地址,对于任何用户空间进程,以简化自己的工作。

Linux 上一个有趣的细节是,通常,新线程的默认堆栈大小相当大,在 32 位 x86 上为 8 MiB(8,388,608 字节)。除非您设置较小的堆栈,否则进程可以创建的线程数受可用虚拟内存的限制。每个用户空间进程可以使用较低的 3 GiB,或 32 位 x86 上 3,221,225,472 以下的虚拟内存地址;你最多可以容纳 384 个 8 MiB 堆栈。如果您考虑标准库等,通常在这些系统上,一个进程可以在虚拟内存耗尽之前创建大约 300 个线程。如果您使用小得多的堆栈,例如 65536 字节,那么即使在 32 位 x86 上,一个进程也可以创建数千个线程。请记住,问题在于可用的虚拟内存地址空间不足,而不是内存本身。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-19
    • 2012-03-10
    • 2020-11-28
    • 2011-10-11
    相关资源
    最近更新 更多