目录
引言:想必大家在学习c语言的时候都看到过,这样的一张图片,再c语言的学习时候呢,我们都知道局部变量是存在栈区的,动态开辟的内存是存在堆区的,常量字符串是存在字符常量区的等等,但是这样图你是否是真的了解了呢,我们动态开辟的内存是不是真的就在内存中开辟一块的空间呢?
1.虚拟地址
这段代码,我们用fork创建了一个子进程,父进程和子进程会同时执行printf
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
printf("pid:%d path:%p\n",pid,&pid);
return 0;
}
执行结果
我们神奇的发现,pid一个变量竟然有两个值,而且这两货的地址还是相同的,由这个现象可以得出2个结论
(1)变量的内容不同,所以这两个pid绝对不是一个pid。
(2) 地址不同,这个地址绝对不是内存中真实的地址。
解释:(1)在linux下这种地址叫做虚拟地址
(2) c/c++中的地址一概是虚拟地址,真实的物理地址是由操作系统统一管理,用户看不到,这也是一种对操作系统的保护。
2.页表
简单理解:页表可以分为两块,左边存储着虚拟地址,右边存着物理地址,页表把虚拟的地址经过映射,映射到物理地址。
虚拟地址------>映射关系------>物理地址
3.总结
有了上面的知识储备,我们也就能解释,为什么一个变量有两个值,地址是相同的问题。
虽然这两变量的虚拟地址是相同的,但是别忘了,映射关系可以不同,修改一下映射关系,子进程pid地址就可以映射到物理地址的其他位置。
下图可以非常清楚的表示
4.拓展知识细节
1.在地址空间,栈和堆的大小,其实是由个整形变量维护的,就像画了两条线,这两条线之间就是堆或者栈的大小。
2.当在虚拟地址空间动态开辟内存时,不会直接为其分配物理内存,使用时会引发缺页中断,这个时候操作系统介入,为其分配物理内存。
3.fork()创建子进程,首先会以父进程为模板,创建task_struct,然后指向父进程的地址空间,当子进程要对变量修改时会发生写时拷贝。
4.fork()之后代码子进程和父进程共享
5.虚拟地址到物理地址的转换,是cpu中一个叫MMU的寄存器做的