在fork()系统调用中混淆

时间:2011-01-18 08:32:52

标签: c linux fork system

我使用fork()创建了父进程和子进程,并且共享一个名为“ptr”的内存地址。但由于程序的一个输出,我感到困惑:

1)地址:123456 注意:父和子的地址相同,所以预期如果一个进程改变了这个地址,那么它也应该反映另一个进程,因为地址是相同的。

2)家长:* ptr = 44

3)孩子:* ptr = 33

4)印刷价值:     父仍然保留旧值:printf(“ptr =%d”,* ptr); //输出:仍为44,exp为33     儿童打印33,预期值。 printf(“ptr =%d”,* ptr); //打印33罚款

问题1 )有谁能告诉我,价值观有何不同?虽然指针地址对父母和孩子都是一样的吗?

问题2 )我正在研究一个内存泄漏工具,它提供双重免费,错误,因为它看到父和孩子释放相同的地址。但是,正如我们所看到的,这不是双重自由的情况。如何排序这个问题?作为工具看到父级的内存地址,子级是相同的加法器吗?

P.S:请参阅以下代码段:

#include <sys/types.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
 int pid, *ptr
 ptr=(int*)malloc(sizeof(int));
 *ptr=33; // Parent keeps the data as 33, before forking.

 if(pid==0){*ptr=44;} // Child modifies data, which is ignored by parent

 // Now we print the memory address and the value both by child and parent
  if(pid==0)
  {
    printf("Child data: %u\n",*ptr);
    printf("Child address: %u\n",ptr);
  }
  if(pid>0)
  {
    printf("Parent data: %u\n",*ptr);
    printf("Parent address: %u\n",ptr);
  }
}

输出: 儿童数据:44 儿童地址:123456

父母数据:33(旧的价值怎么样?) 家长地址:123456(为什么地址相同但数据与孩子不同?)

3 个答案:

答案 0 :(得分:5)

if(pid==0){*ptr=44;} // Child modifies data, which is ignored by parent
Question1)谁能告诉我,价值观有何不同?虽然父项和子项的指针地址相同吗?

这是整个想法。它们可能具有相同的地址,但这些地址为virtual。每个进程都有自己的地址空间。 fork()所做的是创建一个新进程,并使其虚拟内存布局看起来像父进程。

有关其工作原理的一些说明,请参阅Wikipedia article on page tables及类似主题。

- (接下来是长篇) -

fork()通常会发生的情况是设置父页面和子页面的页面表,以便将页面标记为只读。当针对某个位置发生写入指令时,内核会获得page fault,CPU会在内存访问不良时生成该copy-on-write。内核将为陷阱进程分配新内存,通过操作其页表将其映射到正确的地址,将旧缓冲区复制到新分配的缓冲区并让写入继续。这称为{{3}}。这使得初始fork快速并且对于未在任一过程中写入的页面保持内存消耗。

前一段只是fork编程模型的优化。他们说早期的Unix没有这样做 - 它完整地记录了整个过程。我也听说Cygwin的fork()做了完整的副本。

但虚拟地址与内存的物理地址无关。 CPU将其用作页表的“键”,用于定义实际内存的位置。页表也可能表示页面无效,在这种情况下,内核有机会进行“修复”(执行写入时复制,从交换空间调用页面等)或者终止进程。合法无效指针访问的情况。

答案 1 :(得分:2)

你误解了内存在类Unix系统中是如何工作的:父母和孩子的记忆是独立的。如果您希望他们进行通信,您可以设置explicitly shared memory或IPC。

答案 2 :(得分:0)

即使您将内存视为具有单个地址的大缓冲区,也需要更多内容。

上面的观点对于物理内存来说已经足够了,但是现代处理器包括一个MMU芯片(Memory Management Unit),这个芯片将物理内存页面映射到虚拟内存。当给定系统上没有足够的物理内存用于运行程序时,虚拟内存还用于将(虚拟)内存地址映射到磁盘(交换)。

在用户空间(甚至是汇编程序中写入的程序)中运行C程序时,您访问的是虚拟内存,地址是虚拟内存的地址。为了使编译器和程序加载器保持简单,在现代操作系统上,每个进程都有自己独立的内存地址空间,并且地址空间彼此无关(如果每个进程都可以访问机器的整个内存空间)。当然,如果进程访问某些虚拟内存页面未映射到物理内存(或交换到磁盘),则会导致“分段错误”。

当使用fork创建进程时,父进程的内存空间在子进程中重复(即:两者的相同虚拟地址都是相同的数据)。在fork之后,当在其中一个进程中更改内存而不在另一个进程中时,它们将发生分歧。实际的机制稍微复杂一些,通常是写时复制,只要在内存页面上执行了对此页面的副本的修改,如果没有进行任何更改,则两个进程可以访问以在同一物理内存中读取。这解释了在更改子进程的父级中的值时所看到的内容:您看到在分叉之前放置了两个进程(在两个进程之间是通用的),或者如果在fork之后更改了它们,则会看到不同的值。

要使进程在彼此之间进行通信,您必须使用一些通信层(套接字,文件,管道,共享内存等)。并且不要相信使用共享内存与其他方法相比特别简单快,但事实并非如此。

顺便说一下,这是进程和线程之间的区别。每个进程都有自己的内存,而线程共享相同的内存空间。你认为对于进程(由fork创建)的真实情况对于线程来说基本上是正确的。

共享内存空间对于内核级编程基本上也是如此,但无论如何fork都不可用。