【问题标题】:Confusing in fork( ) system call混淆 fork() 系统调用
【发布时间】:2011-06-10 22:51:32
【问题描述】:

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

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

2) 父级:*ptr=44

3) 孩子:*ptr=33

4) 打印值: 父级仍然保留旧值: printf("ptr = %d",*ptr); // 输出:仍然是 44,exp 是 33 Child 打印 33,即预期值。 printf("ptr = %d",*ptr);//打印33个罚款

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

Question2) 我正在开发一个内存泄漏工具,它提供双重释放,错误,因为它看到父级和子级释放相同的地址。但是,正如我们所见,这不是双重免费的情况。如何排序这个问题?由于该工具看到的父母和孩子的内存地址是相同的地址吗?

P.S: 请看下面的代码 sn-p:

#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(为什么地址相同但数据与子不同?)

【问题讨论】:

  • 父子在fork()之后是独立的,意味着他们不能看到彼此的数据。如果您希望他们共享所有数据,请考虑改用线程。
  • 关于您的问题 2:该工具如何查看两个免费的?如果它在进程上下文中运行,它只能看到一个。只要再次调用了一个malloc,同一地址上的多个免费都是有效的!所以只看地址不会让你走得太远......

标签: c linux fork system


【解决方案1】:

即使您将内存视为具有单独地址的大型缓冲区,它还有更多内容。

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

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

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

要使进程相互通信,您必须使用一些通信层(套接字、文件、管道、共享内存等)。并且不要相信使用共享内存与其他方法相比特别简单甚至更快,这是不正确的。

顺便说一下,这就是进程和线程之间的区别。每个进程都有自己的内存,而线程共享相同的内存空间。您认为适用于进程(由 fork 创建)的内容基本上适用于线程。

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

【讨论】:

  • 访问交换的内存会导致页面错误。如果虚拟内存页面没有正确的访问权限,则会发生分段错误。
  • @Frank:基本上是的,但信号永远不会进入 C 程序。它被从磁盘重新加载数据的操作系统拦截并且它是不可见的。只有当页面既没有映射到物理内存也没有映射到磁盘空间时,C 程序才会收到段错误。
【解决方案2】:
if(pid==0){*ptr=44;} // Child modifies data, which is ignored by parent
问题1) 谁能告诉我,这些值有何不同?虽然父子的指针地址是一样的?

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

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

-- (后面很长)--

fork() 通常发生的情况是父子页表都设置为将页面标记为只读。当某个位置发生写入指令时,内核会得到一个page fault,CPU 会在内存访问错误时生成它。内核将为被困进程分配新内存,通过操作页表将其映射到正确的地址,将旧缓冲区复制到新分配的缓冲区,然后继续写入。这称为copy-on-write。这使得初始分叉更快,并降低了两个进程都没有写入的页面的内存消耗。

上一段只是对fork编程模型的优化。他们说早期的 Unix 并没有这样做——它对整个过程进行了完整的内存复制。我还听说 Cygwin 的 fork() 做了一个完整的副本。

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

【讨论】:

    【解决方案3】:

    您误解了在类 Unix 系统中内存是如何工作的:父子内存是独立的。如果你想让他们通信,你可以设置explicitly shared memory,或者IPC。

    【讨论】:

    • 如果是独立的,那地址怎么一样呢?
    • @kingsmasher1:你可能想了解一下“虚拟内存”。
    • 我知道虚拟内存的概念,能否具体一点。
    • @kingsmasher1:如果知道另一个进程的地址空间中的数据地址就可以覆盖另一个进程的变量,那么您为什么认为有人会费心利用高级漏洞来破坏特权?您可以逐个查看地址,试图找到您需要覆盖的数据,这将花费一小部分时间。
    • 你永远不能访问另一个进程的内存。即使你已经从那个过程中分叉了。想想会带来的安全噩梦!所有内存引用都在进程上下文中给出,也就是说,地址 123456 的内容是特定于进程的。共享内存的唯一方法是明确地这样做并创建一个共享内存区域。但要小心,如果内存是共享的,你可能不得不引入锁定,因为对于非原始数据类型可能会出现并发问题!
    猜你喜欢
    • 2016-04-16
    • 2012-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-30
    • 2012-02-02
    • 2016-01-13
    相关资源
    最近更新 更多