哈工大李治军老师操作系统笔记【21】:段页结合的实际内存管理(Learning OS Concepts By Coding Them !)

30 篇文章 25 订阅
本文详细介绍了操作系统中的段页结合内存管理机制,包括虚拟内存的概念、重定位过程以及`fork()`函数在内存分配中的应用。通过段页映射,解决了分段和分页的冲突,实现了更有效的内存利用。同时,文章还讨论了如何建立段表和页表,以及写时复制技术在进程间的内存共享。
摘要由CSDN通过智能技术生成

0 回顾

  • 操作系统采用分段管理内存,用户也是分段
  • 操作系统对物理内存是分页管理
  • 所以这两种机制应该结合
  • 引出段页结合

1 段页结合

  • 段怎么工作? 程序在内存当中割出几个区域
  • 然后将程序中的段与区域建立几个映射,比如说把代码段放到什么区域,数据段放到什么区域
  • 这样,一个应用程序就放到了段中

在这里插入图片描述


  • 页怎么工作?
  • 把物理内存打散成一页一页固定大小的片
  • 假如现在我们的程序也是两个段,怎么把这两个段放到物理内存当中呢?
  • 也把它进行打散,比如把一段打散成两个页,然后进行映射

在这里插入图片描述


  • 怎么结合?

在这里插入图片描述


  • 可以看出,打散的那一段,就是上方段的内存中的一片

在这里插入图片描述

  • 这样就将段页进行结合了
  • 中间的那个中介内存是什么?肯定不是物理内存,但是很像物理内存,因为物理内存肯定是进行到最后一步了,这还在中间的那一步,所以引出虚拟内存的概念
  • 在地址空间中割出一段来给这个程序,给程序中的一段,这个程序中的一段就到了我这个地址上,所以再将地址空间映射到实际的物理地址,就实现了段页的结合

1.1 虚拟内存


  • 在用户眼里,虚拟内存当中的就是段
  • 在操作系统当中,把这个打散了再一页一页的放在这里
  • 所以既支持段,也支持页
    在这里插入图片描述

  • 综上,应用程序有代码段,首先在虚拟内存当中割出一块区域给这个段,然后在操作系统当中虚拟内存的段,再打散成一页一页,然后再和页关联在一起,所以现在用户的代码段已经放到了内存里
  • 疑问:为什么不能直接每一个代码段分别分页呢?而是需要虚拟内存作为桥梁呢?

分页过程输入的地址都是一个规整的32位/64位地址;而使用了段的用户代码中,所有地址都是由段地址和段内偏移组成的;把段地址和段内偏移组成的地址先转化为一个规整的32位/64位地址,把这个地址称为虚拟地址,虚拟地址所在的空间称为虚拟内存,就自然而然的引出了虚拟内存的概念
每个用户代码都有多个段,如果直接给段分页,进程的每一个段需要都维护一个页表,经过虚拟内存的中介,只需要给虚拟内存维护一个页表
分页机制已经解决了外部碎片的问题,将段首先映射到虚拟内存,让分页机制的内部碎片更少,让分页机制的空间浪费更少,假设一个程序共有2047KB,分为两个段,1个段是1025KB,一个段是1022KB;如果经过虚拟内存的中介,只需要放到两页上;如果直接给段分页,就要放到3页上

1.2 重定位


在这里插入图片描述


  • 先从csip取到虚拟地址,再根据页号和偏移进行
  • 第一次地址翻译是分段的,第二次是分页的
  • 段、页同时存在时的重定位(地址翻译)
  • 疑问:段表和页表分别是什么时候初始化的?
  • 首先,查询段表,转化为虚拟地址
  • 然后,查询页表,转化为物理地址
  • 再次提到实验6

1.3 实际的段页内存

  • 看代码,看一个实际的段、页式内存管理是怎么实现的

  • 程序放入内存,之后开始取址执行

在这里插入图片描述


1.4 fork()

  • 怎么在虚拟内存中割出一段?
  • 首先,使用之前内存分区中讲得分区适配算法,对虚拟内存进行分区,对程序的各个段进行适配,建立段表
  • 然后,对虚拟内存占用的空间进行分页,分配物理内存的页,建立页表,同时程序也就载入了内存

在这里插入图片描述


虚拟地址从高到低被划分为段号、页号、页内偏移地址,而映射过程是通过拆分出虚拟地址里高位的段号 通过该进程的段表找到该段号对应的页表的存放内存地址 之后通过虚拟地址的页号找到实际的页框号 再将实际的页框号拼接上虚拟地址低位的偏移地址,得到物理地址。

  • 整个过程分为几步?
  1. 在虚拟内存当中割一段出来
  2. 把用户代码假装放到这里来,怎么假装?建立段表,明确映射关系
  3. 在物理内存当中,找一段空闲区
  4. 建立页表,再加上真正的磁盘读写就能把代码真正转移到物理内存当中
  5. 可以用重定位来使用内存

建立段表映射 -> 建立逻辑段->找到空闲物理内存,建立逻辑段到物理内存页的映射->磁盘读文件

  • 分别用段表以及页表来记录区域是怎么对应的
  • fork()开始讲解代码
    • 之前讲过fork()的源码,可以回顾一下
    • copy_process中,调用了copy_mem
    • copy_memnr是进程的编号(第几个进程),p是PCB
      • new_data_base就是每个进程在虚拟内存中的起始地址,每个进程占64M虚拟地址空间,互不重叠,给new_data_base赋值就是在虚拟内存中分区
      • set_base就是在设置段表基址(段表中存放着每个段对应的基址),在这里的实现中,数据段和代码段是同一个段,这一步就是在建立段表
    • 图示
      • 老师说这种分割虚拟内存的方法比较弱智,但是学习起来很简单,适合入门
      • 由于所有进程的虚拟地址互不重叠,所以所有进程可以共用一套页表;但是现在的操作系统,进程的虚拟地址是会重叠的,所以每个进程应该维护自己的页表

在这里插入图片描述


1.5 建表


在这里插入图片描述
在这里插入图片描述


  • 分配内存、建页表
  • 分配虚存、建段表;分配内存、建页表
    • copy_page_tables子进程和父进程共用物理内存的页,所以子进程实际上不需要新分配内存,直接建页表即可
      • 首先明确这里是32位地址空间
      • 实参old_data_base对应形参from,表示父进程的虚拟内存的起始地址,from右移20位,和0xffc进行位与,结果赋给from_dir
        • 右移22位,得到了页目录号,再乘以4,得到顶级页目录中的索引位置(顶级页目录中的每一项占4字节),所以代码里直接写了右移20位
        • from_dir就是指向父进程的第一个二级页目录的指针,*from_dir就是父进程的第一个二级页目录
      • 实参new_data_base对应形参to,表示子进程的虚拟内存的起始地址,to右移20位,和0xffc进行位与,结果赋给to_dir
        • to_dir就是指向子进程的第一个二级页目录的指针,*to_dir就是子进程的第一个二级页目录
      • size先加上0x3fffff,然后右移22位,结果赋给size本身
        • size表示有多少个二级页目录

from先右移22得到已使用的页目录号,因为每个页目录号对应一个页目录,一个页目录是4字节,所以再乘以4(字节)就得到已使用页目录项所占据的长度,因为地址是从0开始的,这个长度也是下一目录项的起始地址

右移22位拿到了第多少项,相当于拿到了数组下标,具体元素在哪个位置要用下标✖️单个元素大小

在这里插入图片描述


  • 循环size次,给子进程的二级目录分配页
  • get_free_page()使用内嵌汇编从空闲列表中取出一页
    在这里插入图片描述

  • 按照父进程的每一个二级页目录,建立子进程的每一个二级页目录
    • mem_map[this_page]++,有点像引用计数的概念,两个虚拟内存页共享一个物理内存页
    • 这里用了写时复制的方法

在这里插入图片描述


  • 图示
    • 进程1fork得到进程2,它们的虚拟内存指向了同一块物理内存空间,一页一页对应

在这里插入图片描述


  • 段表、页表建立之后,就可以使用它们进行地址翻译了
    • 查段表、页表进行地址翻译的工作由MMU完成
    • 写时复制,进程2的*p=8,会写到新的页里面
    • 我的理解:子进程如果没有写操作,就可以以只读的方式共享父进程的内存页;如果有写操作,就会使用写时复制,产生子进程独立的内存页,并且需要修改页表

在这里插入图片描述


2 总结

下次再听一遍

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值