Linux 10分钟让你掌握虚拟地址--写时拷贝技术

程序地址空间

在这里插入图片描述
地址:对内存单元的编号
程序是不占用内存的,运行起来的程序才会被加载到内存,才会占用空间。所以程序地址空间也叫做进程地址空间

我们先来看一下代码:
zone.c

  #include <stdio.h>
  #include <unistd.h>
  
  int gval = 1;
  int main()
  {
      pid_t pid = 0;
  
      pid = fork();
      if (pid == 0)//子进程
      {
          printf("child gval:%d\n", gval);
      }
      else if (pid > 0)
      {
          printf("parent gval:%d\n", gval);
      }
      return 0;
  }

执行后:
在这里插入图片描述
不难发现,我们的父进程与子进程输出的变量都是同一个变量gval,值都为1。
但是我们在子进程中修改gval的值再让父进程执行,看他们的值会不会一样,并且再打印他们变量的地址。

 #include <stdio.h>
  #include <unistd.h>
  
  int gval = 1;
  int main()
  {
      pid_t pid = 0;
  
      pid = fork();
      if (pid == 0)//子进程
      {
      	  gval= 100;
          printf("child gval:%d,&gavl-%p \n", gval,&gval);
      }
      else if (pid > 0)
      {
      	  sleep(3);
          printf("parent gval:%d,&gavl-%p \n", gval,&gval);
      }
      return 0;
  }

在这里插入图片描述

我们都知道,只要地址相同,数据一定也是相同的,一块内存数据不可能存在两个值。但是上面的结果却是地址相同,打印的数据不同。这是不是出错了呢?

实际上进程中访问的地址都是虚拟地址,而我们所说的程序地址空间实际上就是一个进程的虚拟地址空间。

虚拟地址空间是进程的虚拟地址空间,是操作系统为每个进程对于内存空间的虚拟描述,在linux下是一个结构体mm_struct

虚拟地址空间–操作系统为一个进程描述的虚拟的、连续的、完整的、线性的地址空间

而为什么不让进程直接去访问物理内存呢?
1、进程中代码数据的使用都是连续的地址,若直接使用连续的物理内存会造成内存的浪费
2、如果一个进程中存在一个野指针,会访问到另一个进程中的数据。直接访问物理内存会因为缺乏内存访问控制导致进程的不安全

物理地址是我们用户看不到的,我们所看到的地址0x601044就是一个虚拟地址,但是我们真正存放的那个数据是存放到物理内存当中的。那我们如何通过虚拟地址访问到对应的物理地址呢?–页表,通过虚拟地址映射到页表中,再通过映射到页表中的位置再映射到物理内存中,从而找到相对应的数据。

页表功能:保存了虚拟地址和物理地址之间的映射关系,提供内存访问控制

在这里插入图片描述
虽然父子进程的虚拟内存地址是一样的,但是他们在物理内存存放的数据的地址是不一样的。通过各自的虚拟地址和页表,映射到不同的物理内存中,从而找到各自的数据

作用:进程使用虚拟内存,通过页表映射物理内存,可以实现进程中数据在物理内存上的离散存储。这样子大大提高了内存的利用率。在页表中可以直接对某个地址设置访问权限标志位,让这个地址成为只读,从而实现对内存的访问控制,让进程更加安全,提高了进程的独立性,保证了各个进程的稳定性。从而避免了进程在物理内存上存在的问题

写时拷贝

但是我们要知道一个问题,子进程在复制父进程的时候也复制了父进程的页表,那页表相同,虚拟内存地址也相同,映射的物理地址为什么不同呢?
:Linux下的fork()函数利用了写时拷贝技术,子进程复制了父进程的页表,进程“读”操作的时候也能访问到同一个数据,那是因为他们映射到的物理内存上都是一块相同的空间。但是当有一个进程要进行“写”操作时,系统会在物理内存中复制出一块内存,然后将数据写进去,进行“写”操作的进程通过虚拟地址访问时就不是访问之前的那个数据,而是访问进行“写”操作后复制过来并写入数据的那块数据----写时拷贝技术:两个进程一开始指向同一块空间,等有进程进行“写”操作时候,再给进行“写”操作的进程重新开辟空间,目的就是提高子进程创建效率,保证进程的独立性

父进程创建子进程,子进程复制父进程的信息都是可读的,父子进程访问的都是同一块物理内存,只有当有进程进行写操作时,才会给子进程开辟新的内存空间来保存数据这也保证了进程之间的独立性。
在这里插入图片描述

页表如何实现通过虚拟地址访问物理地址—MMU
内存管理方式:分页式内存管理 / 分段式内存管理 / 段页式内存管理

分页式内存管理

分页式内存管理的虚拟地址组成:页号 + 页内偏移
页号:页表中表项的编号
页内偏移:具体一个变量首地址相较于内存起始位置的偏移量
虚拟地址通过页号找到页表中相对应的页号,再用页表内的页号对应的块号再映射到物理地址中的块号,然后再用虚拟地址的页内偏移找到唯一的物理地址
在这里插入图片描述
优点:将物理内存块进行分块管理,通过页表映射实现数据在物理内存上的离散式存储,提高内存的利用率
公式物理内存块号 * 页面大小 + 虚拟地址偏移量 = 物理地址

假设物理内存大小为4G,页的大小为4096字节,页数为4G / 4096 = 2^20,意味着页号占了虚拟地址的高20位,低12位就是页内偏移。

用虚拟地址(逻辑地址)计算物理地址(十进制 & 十六进制)

分段式内存管理

地址组成:段号 + 段内偏移
原理和分页式差不多,通过段表找到段号,再用段表对应的物理段起始地址找和段内偏移到物理地址
在这里插入图片描述
优点:使程序员对内存管理更加方便,以及实现程序和数据的共享和保护

段页式内存管理

地址组成:段号 + 段内页号 + 页内偏移
在每一个分段内,又采用分页式管理。两者相结合就是段页式内存管理
通过地址中的段号,找到段表项,通过段表项中段内页表其实地址找到自己的也报,通过地址中的段内页号,在这个页表中找到页表项,通过页表项中的物理块号和页内偏移最终得出物理地址

磁盘分区有两种:交换分区 / 文件系统分区
当内存不够用的时候,操作系统则会根据一定的算法,将某块内存中的数据保存到磁盘的交换分区中,腾出这块内存加载新的数据

何时为发生缺页中断?:进程中每个页表都存放虚拟地址和物理地址的映射关系,如果虚拟页面的物理内存中的数据被交换出去,保存到了交换分区,则将触发缺页中断,重新从交换分区将数据交换回来。
什么样的数据在内存不够用的时候应该被交换出去?
1、LRU算法–最近最久未使用的数据
2、LFU算法–最少使用的数据
3、Clock算法–与LRU算法近似

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteShirtI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值