我使用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 回答
这是整个想法 . 它们可能具有相同的地址,但这些地址是virtual . 每个进程都有自己的地址空间 .
fork()
所做的是创建一个新进程,并使其虚拟内存布局看起来像父进程 .有关其工作原理的一些说明,请参阅Wikipedia article on page tables和类似主题 .
通常在
fork()
处发生的是设置父页面和子页面的页面表,以便将页面标记为只读 . 当针对某个位置发生写入指令时,内核会获得page fault,CPU会在内存访问不良时生成 . 内核将为陷阱进程分配新内存,通过操作其页表将其映射到正确的地址,将旧缓冲区复制到新分配的缓冲区并让写入继续 . 这叫做copy-on-write . 这使得初始fork快速并且对于未在任一进程中写入的页面保持内存消耗 .前一段只是fork编程模型的优化 . 他们说早期的Unix并没有听说Cygwin的
fork()
做了完整的副本 .但虚拟地址与内存的物理地址无关 . CPU将其用作页表的“键”,用于定义实际内存的位置 . 页表也可能表示页面无效,在这种情况下,内核有机会进行“修复”(执行写入时复制,从交换空间调用页面等)或者终止进程 . 合法无效指针访问的情况 .
你误解了内存在类Unix系统中是如何工作的:父母和孩子的记忆是独立的 . 如果您希望它们进行通信,您可以设置explicitly shared memory或IPC .
即使您将内存视为具有单个地址的大缓冲区,也有更多内容 .
上面的观点对于物理内存来说已经足够了,但是现代处理器包括一个MMU芯片(Memory Management Unit),这个芯片将物理内存页面映射到虚拟内存 . 当给定系统上没有足够的物理内存用于运行程序时,虚拟内存还用于将(虚拟)内存地址映射到磁盘(交换) .
在用户空间中运行C程序(甚至是在汇编程序中编写的程序)时,您访问的是虚拟内存,地址是虚拟内存的地址 . 为了使编译器和程序加载器保持简单,在现代操作系统上,每个进程都有自己独立的内存地址空间,并且地址空间彼此无关(如果每个进程都可以访问机器的整个内存空间) . 当然,如果进程访问某些虚拟内存页面未映射到物理内存(或交换到磁盘),则会导致“分段错误” .
当使用fork创建进程时,父进程的内存空间在子进程中重复(即:两者的相同虚拟地址都是相同的数据) . 在fork之后,当其中一个内存发生变化时,它们会发散这个过程而不是另一个 . 实际的机制稍微复杂一些,通常是写时复制,只要在内存页面上执行了对此页面的副本的修改,如果没有进行任何更改,则两个进程可以访问以在同一物理内存中读取 . 这解释了在更改子进程的父级中的值时所看到的内容:您看到在分叉之前放置了两个进程(两个进程之间是通用的),或者如果在fork之后更改了它们,则会看到不同的值 .
要使进程在彼此之间进行通信,您必须使用一些通信层(套接字,文件,管道,共享内存等) . 并且不要相信使用共享内存与其他方法相比特别简单快,但事实并非如此 .
顺便说一下,这是进程和线程之间的区别 . 每个进程都有自己的内存,而线程共享相同的内存空间 . 您认为对于进程(由fork创建)的真实情况对于线程来说基本上是正确的 .
共享内存空间对于内核级编程基本上也是如此,但是无论如何fork都不可用 .