博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Linux】进程的虚拟地址空间与页表映射
阅读量:4156 次
发布时间:2019-05-25

本文共 2799 字,大约阅读时间需要 9 分钟。

【Linux】进程的虚拟地址空间与页表映射


文章目录


  我们先看一下这段代码

#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 的,那么它们在底层是如何映射的呢?


一、进程虚拟地址空间

1.1 虚拟地址

  在 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,同时修改父进程中页表的映射关系

  由下面的图可以悟出:同一个变量地址相同,其实是虚拟地址相同;同一变量内容不同,其实是被页表映射到了不同的物理内存上。

在这里插入图片描述


1.2 写时拷贝

  其实上述这种机制叫做 写时拷贝

  ● 当 fork 的时候,如果父子进程不修改数据,则页表映射关系不发生改变

  ● 当父/子进程其中一方修改数据时,为了防止导致另一方读取到的数据是错误的,所以需要在物理内存当中重新开辟一段空间,保存修改后的值,并且将该进程页表的映射关系重新指向新的物理内存。


二、页表

  我们平时在 C/C++ 中所看到的地址,全部都是虚拟地址,物理地址我们是看不到的,是由操作系统统一管理。由第1节中的两幅图可以看出,页表是虚拟地址和物理地址联系的纽带,操作系统(Operating System,简称OS)将虚拟地址通过页表转化成物理地址。

  那操作系统又是如何通过页表将虚拟地址转化成物理地址的呢?

  页表维护了页和块的关系。进程虚拟地址空间被 OS 分为一页一页的,物理内存被分成了一块一块的,其中 一页的大小 = 一块的大小 = 4096K

image-20210628221508562


2.1 分页式内存管理

  原理:用户程序的虚拟地址空间被划分成若干个固定大小的页,相应的,物理内存空间也被分成相应的若干个物理块,页和块的大小相等,可将用户程序的任意一页放在内存的任一一块,这些块不必连续,实现离散分配,减少资源碎片化。

  虚拟地址 = 页号 + 页内偏移

  物理地址 = 块的起始位置 + 页内偏移

  其中:页号 = 虚拟地址 / 块的大小

     页内偏移 = 虚拟地址 % 块的大小

     块的起始位置 = 块号 * 块的大小

     块的大小 = 4096k

  举例: 假设有一个虚拟地址是 0x 00001622(对应的十进制 5666),计算它的物理地址是多少?(其中页表的映射关系如下图所示)

image-20210629103610042

  页号 = 虚拟地址 / 块的大小 = 5666 / 4096 = 1;

  页内偏移 = 虚拟地址 % 块的大小 = 5666 % 4096 =1570;

  页号为 1 根据上图的映射关系可推断出块号是 7 ;

  块的起始位置 = 块号 * 块的大小 = 7 * 4096 = 28672;

  物理地址 = 块的起始位置 + 页内偏移 = 28672 + 1570 = 30242。

  将其转换成16进制为 0x 0000 7622。

image-20210629111556227


2.2 分段式内存管理

  原理: 用户程序的虚拟地址空间被划分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息,段的长度由相应的逻辑信息组的长度决定。存储分配时,以段为单位,段育段在内存中不可以相邻接,也实现了离散分配。

  虚拟地址 = 段号 + 段内偏移

在这里插入图片描述


2.3 段页式内存管理

  原理: 分页式能够有效的提高内存的里利用率,而分段能反映程序的逻辑结构,将分页式和分段式结合起来,就形成了段页式内存管理,在段页式内存管理中,用户的虚拟地址空间首先被划分成若干个逻辑分段,每段都有自己的段号,然后在将每段分成若干个大小相等的页。

  虚拟地址 = 段号 + 页号 + 页内偏移

image-20210629112708613

  1、通过段号找到页的起始位置;

  2、通过页的起始位置,找到对应的页表结构;

  3、通过页号,找到对应的块号,通过块号,计算出块的起始位置;

  4、块的起始位置加上页内偏移计算出物理地址。


转载地址:http://rnwxi.baihongyu.com/

你可能感兴趣的文章
从一列数中筛除尽可能少的数,使得从左往右看这些数是从小到大再从大到小...
查看>>
判断一个整数是否是回文数
查看>>
腾讯的一道面试题—不用除法求数字乘积
查看>>
素数算法
查看>>
java多线程环境单例模式实现详解
查看>>
将一个数插入到有序的数列中,插入后的数列仍然有序
查看>>
在有序的数列中查找某数,若该数在此数列中,则输出它所在的位置,否则输出no found
查看>>
阿里p8程序员四年提交6000次代码的确有功,但一次错误让人唏嘘!
查看>>
一道技术问题引起的遐想,最后得出结论技术的本质是多么的朴实!
查看>>
985硕士:非科班自学编程感觉还不如培训班出来的,硕士白读了?
查看>>
你准备写代码到多少岁?程序员们是这么回答的!
查看>>
码农:和产品对一天需求,产品经理的需求是对完了,可我代码呢?
查看>>
程序员过年回家该怎么给亲戚朋友解释自己的职业?
查看>>
技术架构师的日常工作是什么?网友:搭框架,写公共方法?
查看>>
第四章 微信飞机大战
查看>>
九度:题目1008:最短路径问题
查看>>
九度Online Judge
查看>>
九度:题目1027:欧拉回路
查看>>
九度:题目1012:畅通工程
查看>>
九度:题目1017:还是畅通工程
查看>>