首页 文章

在fork()系统调用中混淆

提问于
浏览
1

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

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

2)父母:* ptr = 44

3)孩子:* ptr = 33

4)打印值:父级仍保留旧值:printf(“ptr =%d”,* ptr); //输出:仍为44,exp为33子项打印33,为期望值 . printf(“ptr =%d”,* ptr); //打印33罚款

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

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

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
    if(pid==0){*ptr=44;} // Child modifies data, which is ignored by parent
    

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

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

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

    • (长期以下) -

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

    前一段只是fork编程模型的优化 . 他们说早期的Unix并没有听说Cygwin的 fork() 做了完整的副本 .

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

  • 5

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

  • 2

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

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

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

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

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

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

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

相关问题