OS内核(xv6)探究-分段&分页

分段与分页

分段和分页是现代操作系统管理和使用内存的方式,一般需要操作系统(软件)配合CPU(硬件)一起来实现分段和分页的功能。我的理解,分段的主要目的是为了实现对内存的保护,即操作系统的保护模式;分页的主要目的是为了提高对内存的利用率,减少内存碎片

分段是程序员能在开发过程中够感知到的部分,例如我们常说的代码段(TEXT段),数据段(DATA段),堆和栈都是段的概念,还有CS,DS,SS等寄存器也是分段机制相关的寄存器。

分页则是上层应用开发的程序员通常感知不到的部分,我们在开发上层应用时,面对的是逻辑地址空间,分页功能对我们来说是透明的。但这也不是说了解分页机制对我们来说没有必要的,例如抖音基于二进制重排的启动优化就是基于对分页技术的理解和应用。如果你不了解分页机制,自然也不会了解缺页中断,不了解缺页中断,自然也就不能输出二进制重排的优化方案。

虚拟地址空间

在了解分段和分页之前,我们得先了解一下进程的虚拟地址空间。每个进程在执行的时候,它都会面对一个虚拟地址空间,为什么叫做虚拟地址空间呢?因为这些地址确实是虚拟的,并且是完整的,巨大的(远超实际物理内存)。虚拟内存会被划分成两个大的部分,内核虚拟空间和用户虚拟空间,我们写的代码运行在用户空间,当我们需要一些特殊操作的时候(例如读写磁盘),会通过系统调用进入到内核空间。而在用户地址空间,空间又会被划分成一些,不同段会放不同类型的数据,有不同的访问权限和使用方式。

我们在日常开发中,只需要了解进程虚拟地址空间的结构就可以了。但虚拟内存究竟是怎么和物理内存对应上的呢?分页机制就在这里发挥了重要作用。

虚拟内存主要的几个作用:

  1. 保护每个进程的地址空间,防止互相影响
  2. 优化内存结构,简化内存管理
  3. 当内存不够用的时候,可以利用外存空间扩展内存空间(这又是另一种虚拟内存的概念)

分段

分段的历史

分段机制是一个逐渐演化而成的机制,最早引入分段机制是为了扩展内存寻址空间。1971年11月15日,Intel 推出世界第一块个人微型处理器 4004(4位处理器)。随后又推出了 8080(8 位处理器)。那时候访问内存就是直接使用内存的体物理地址,那时候还没有段的概念。

段的概念是起源于 8086,这个16位处理器。限于当时的技术背景和经济,寄存器只有16位,但地址总线却有20位。那16的位的寄存器如何能访问20位的地址?这也就是引入段的概念,让CPU通过使用两个寄存器,并使用:**「段基地址(在段寄存器中)+段内偏移(在另一个寄存器中)」**的方式来访问内存。

再具体一点的计算规则是:段基地址左移4位(就是乘16)再加上段内偏移,这样得到的就是20位的地址。比如现在的要访问的内存地址是0x05808,那么段基地址可以是0x0580,偏移量就是0x0008。

分段与保护模式

有了分段机制之后,大佬们又在分段机制的基础之上赋予了其一种新的保护机制,即计算机的**【保护模式】**(与之对应的是之前的实模式)。

保护模式的核心就是赋予各个段以各种权限,例如有的段是可执行段,有的段是可读可写段,有的段是只读段等。当程序试图向一段只读段内存写入数据的时候,就会触发段异常,例如栈溢出就是一种典型的段异常(程序访问了超越栈段的内存)。

分段机制的实现

网上有很多分段机制详情的介绍,我这里就不再多介绍。段机制的实现有以下几个核心点:

  1. GDT(全局描述符表):GDT记录了一些段的主要信息,这写段是全局的。每个段由一个描述符表示,包括:段属性,段基地址,段界限(段范围)。GDT是一段内存(数组结构),它只能由处于内核态的操作系统设置,以保证安全性。
  2. LDT(局部描述符表):和GDT对应的,这些段是局部的(每个进程私有的)
3. 段寄存器:段寄存器CPU中的一些专用寄存器,负责存放用于寻址段描述符的数据(选择子)。典型的段寄存器包括:CS(代码段段寄存器),DS(数据段段寄存器),SS(栈段段寄存器)等。 4. selector(选择子):段寄存器中存放的是选择子,选择子主要是指明了段描述符在GDT或LDT中的索引号(因为GDT和LDT是数组结构的),从而取到段描述符。
5. 硬件寻址:「段基地址+段内偏移」= 逻辑地址,这个计算过程是由硬件完成的,所以速度很快(还有不可见的描述符缓存表用于加速)。软件(操作系统)只负责设置GDT和LDT。

分页

上文也提到了,分页功能对上层开发人员是透明的。操作系统负责管理分页,默默地在底层把逻辑地址转化成物理地址(也是配合CPU一同完成的)。因为其对上层开发人员的不可见性,所以对于不开发操作系统内核的同学,大概了解其原理即可。

个人认知到的分页的主要作用:

  1. 提高对内存的利用率,减少内存碎片
  2. 方便构建虚拟内存地址空间
分页与映射

操作系统为了更高效、合理的管理内存,会以页为最小单位来分配和回收内存,通常一个页的大小是4KB。

一个进程的虚拟地址空间是由连续的N个页组成的,并且这个N很大,让进程以为自己有很多的内存可以使用,但实际上操作系统只会给进程分配它当时所需的内存,当内存不够用的时候,才会再继续给进程分配更多的页。

进程的虚拟地址空间上的页是由连续的,但是这些页在实际物理内存上则可能不是连续的(绝大多数情况是不连续的)因此就存在一个虚拟页和物理页的映射关系。

页表

那么如何管理虚拟页和实际物理页的对应关系呢?这里用到了一个数据结构:页表。

简化的描述:一个虚拟地址由【页号】和【页内偏移地址组成】,当需要访问一个内存地址时,CPU先拿着页号去页表中查找(因为是硬件计算,所以速度很快),页表中存在虚拟页的页号和对应的物理页的起始地址的对应关系。找到物理页的起始地址后,再加上页内偏移地址就是真实的物理地址值了。

可以看到页的逻辑还是比较简单易懂的(但真实实现是很复杂的,参考最后的大图)

多级页表

虽然页表很好用,但是存在一个问题:页表也是占用内存的,并且如果只用单个页表来维护映射关系的话,这个单页表占用的内存是极大的。

所以大佬们又设计出了多级页表的方案,多级页表的核心思想就是【页表的延迟创建】,即有些页表不必一开始就创建出来,当有需要的时候再创建。多级页表的具体细节这里也不再多介绍了(网上或者操作系统书籍介绍的很清楚)。或者可以参考文章最后的大图。

缺页

上文也说道,虚拟地址空间让进程以为自己有很多的内存可以使用,但实际上操作系统只会给进程分配它当时所需的内存,当内存不够用的时候,才会再继续给进程分配更多的页,这是一种情况。那么还有一种情况是,操作系统在加载程序可执行文件的时候,并不会一股脑全部加载进来,而是先加载目前正需要的一部分,目标不需要的部分还安静的躺在外存上,当真正用到的时候再被加载进来。

当进程的实际内存不够用的时候,或者进程访问了还没有被物理页映射的虚拟页的时候,就会触发一个缺页中断,缺页中断由操作系统负责处理。处理流程也很好理解,操作系统会找到一个空闲的物理页,把这个物理页和虚拟页建立一个映射关系,把这个映射关系存在页表当中。

段页结合

我们现在的内存访问方式就是段页结合式的方式。下面这个图可以概括的解释段页式的工作模式。

  1. 虚拟地址(或者叫逻辑地址)由段基地址+偏移地址组成,例如:data段基地址+偏移地址 = (可能是)data段某个静态变量的地址
  2. 虚拟地址在分段的机制下被转换为一个线性地址,线性地址即进程虚拟地址空间中的地址
  3. 线性地址在分页的急之下被映射到某个具体物理地址上

总结

最后总结一下我理解的段页式内存的优势:

  1. 分段提供了保护机制
  2. 分段的程序可读性更好,也有利于编译器工作
  3. 分页提高内存利用率,减少内存碎片
  4. 分页为虚拟地址空间机制提供了基础,虚拟地址空间是操作系统的一个核心机制
  5. 分页为swap技术提供了基础

超级大图

最后放一个分页的超级大图,仅供娱乐~😁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值