本文共 2799 字,大约阅读时间需要 9 分钟。
我们先看一下这段代码
#include#include int g_val=10;int main(){ pid_t pid = fork(); if(pid < 0) { //创建子进程失败 perror("fork error\n"); return 0; } else if(pid == 0) { //子进程 printf("i am child g_val=[%d] pid=[%d] ppid=[%d]\n",g_val,getpid(),getppid()); } else { //父进程 printf("i am father g_val=[%d] pid=[%d] ppid=[%d]\n",g_val,getpid(),getppid()); } return 0;}
编译运行以下,可以看到父进程的 PID 是15474,子进程的 PID 是15475。两个进程所打印的 g_val 都是10。
那么子进程是拷贝父进程的 PCB 的,那么它们在底层是如何映射的呢?
在 fork 创建子进程时,它不仅仅拷贝了父进程的 PCB,与此同时还拷贝了父进程的页表映射关系,它们的关系就像下图一样。子进程和父进程通过页表映射到同一块物理内存当中,都映射到 g_val 且值都为10。
也就意味着,当 fork 完成之后,父进程之前的数据,会被父/子进程通过页表结构映射到同一块物理内存上。
如果,我们在父/子进程中修改了 g_val 的值,此时对应页表的映射就会发生改变。比如,将父进程的 g_val 的值修改为 20。
#include#include int g_val=10;int main(){ pid_t pid = fork(); if(pid < 0) { //创建子进程失败 perror("fork error\n"); return 0; } else if(pid == 0) { //子进程 printf("i am child g_val=[%d] pid=[%d] ppid=[%d]\n",g_val,getpid(),getppid()); } else { //父进程 g_val += 10; printf("i am father g_val=[%d] pid=[%d] ppid=[%d]\n",g_val,getpid(),getppid()); } return 0;}
那么,操作系统会在物理内存中为父进程重新开辟一段空间,保存父进程修改之后的值 g_val = 20,同时修改父进程中页表的映射关系。
由下面的图可以悟出:同一个变量地址相同,其实是虚拟地址相同;同一变量内容不同,其实是被页表映射到了不同的物理内存上。
其实上述这种机制叫做 写时拷贝。
● 当 fork 的时候,如果父子进程不修改数据,则页表映射关系不发生改变
● 当父/子进程其中一方修改数据时,为了防止导致另一方读取到的数据是错误的,所以需要在物理内存当中重新开辟一段空间,保存修改后的值,并且将该进程页表的映射关系重新指向新的物理内存。
我们平时在 C/C++ 中所看到的地址,全部都是虚拟地址,物理地址我们是看不到的,是由操作系统统一管理。由第1节中的两幅图可以看出,页表是虚拟地址和物理地址联系的纽带,操作系统(Operating System,简称OS)将虚拟地址通过页表转化成物理地址。
那操作系统又是如何通过页表将虚拟地址转化成物理地址的呢?
页表维护了页和块的关系。进程虚拟地址空间被 OS 分为一页一页的,物理内存被分成了一块一块的,其中 一页的大小 = 一块的大小 = 4096K。
原理:用户程序的虚拟地址空间被划分成若干个固定大小的页,相应的,物理内存空间也被分成相应的若干个物理块,页和块的大小相等,可将用户程序的任意一页放在内存的任一一块,这些块不必连续,实现离散分配,减少资源碎片化。
虚拟地址 = 页号 + 页内偏移
物理地址 = 块的起始位置 + 页内偏移
其中:页号 = 虚拟地址 / 块的大小
页内偏移 = 虚拟地址 % 块的大小
块的起始位置 = 块号 * 块的大小
块的大小 = 4096k
举例: 假设有一个虚拟地址是 0x 00001622(对应的十进制 5666),计算它的物理地址是多少?(其中页表的映射关系如下图所示)
页号 = 虚拟地址 / 块的大小 = 5666 / 4096 = 1;
页内偏移 = 虚拟地址 % 块的大小 = 5666 % 4096 =1570;
页号为 1 根据上图的映射关系可推断出块号是 7 ;
块的起始位置 = 块号 * 块的大小 = 7 * 4096 = 28672;
物理地址 = 块的起始位置 + 页内偏移 = 28672 + 1570 = 30242。
将其转换成16进制为 0x 0000 7622。
原理: 用户程序的虚拟地址空间被划分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息,段的长度由相应的逻辑信息组的长度决定。存储分配时,以段为单位,段育段在内存中不可以相邻接,也实现了离散分配。
虚拟地址 = 段号 + 段内偏移
原理: 分页式能够有效的提高内存的里利用率,而分段能反映程序的逻辑结构,将分页式和分段式结合起来,就形成了段页式内存管理,在段页式内存管理中,用户的虚拟地址空间首先被划分成若干个逻辑分段,每段都有自己的段号,然后在将每段分成若干个大小相等的页。
虚拟地址 = 段号 + 页号 + 页内偏移
1、通过段号找到页的起始位置;
2、通过页的起始位置,找到对应的页表结构;
3、通过页号,找到对应的块号,通过块号,计算出块的起始位置;
4、块的起始位置加上页内偏移计算出物理地址。
转载地址:http://rnwxi.baihongyu.com/