一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
分段
上文提出的base-bound内存模型虽然达到了功能要求,但是地址空间中heap和stack中间大片的内存无法被灵活使用,我们需要一种更灵活的地址转移技术,也就是segmentation。
它的基本思想就是,在地址空间的每个逻辑段(代码区、堆栈区、堆区)上都有一对base-bound。目的是将每个段放在物理内存的不同部分,这样就不需要预留那么多空间给地址空间了。
上图右半部分是segmentation的内存模型,segmentation将进程的code、stack、heap在物理内存上分开放置,分为三个不同的段,并给每一段都配置一对base-bound registers,当进程发出一个地址请求时,地址经由处理变为offset,也就是相对于其地址空间三个不同分段的的偏移量,再发送到bounds确认偏移量没有超出范围,之后与base相加/减得到在物理内存上的实际地址。
我们以一个实际的例子来看segmentation的工作流程,系统切换到某一个进程时,将其三个段的寄存器值填入,举例来说,可以是如下图所示,之后进程中的每一个内存访问都会通过这些寄存器分到不同的物理地址上。
segmentation fault就是机器对非法地址的访问引起的trap,这一种错误在分段这个思想出现后就一直保留至今
更加举例的来说,对于某一个地址空间的地址访问,我们可以将看作如下图所示,前两位是用于区分在作用在哪一个段上,后面是相对于寄存器值的偏移量。
segmentation的大致过程如上,但是还有许多细节,硬件怎样确定发过来的偏移量是相对于那一段的?stack段地址空间向上填充需要相减(而不是相加)是怎样做的?这些由于篇幅掠过。
segmentation分段处理的方式还带来了另一个好处,不同进程的code段、stack段、heap段都可以实现一定程度的共享。在有序的前提下,不同进程共用一段代码,一个进程访问另一个进程的数据,这些操作可以大大增加的内存的利用率。具体的,可以共享的段会有**保护位(protection bit)**标识
segmentation分段处理的方式也有一个难以解决的问题,不同进程的不同段在物理内存中稀疏的分布导致空余空间小而散,难以利用,这些空间被称为external fragmentation外部碎片。 转化前后直观来看可以像下面这样
注意一般不会压到右边这样密集,因为这样的话已有进程几乎不能申请新的空间。
操作系统可以通过移动段位置来整理空间,但是这是一个trade-off的问题,空间紧凑则已有进程申请新空间则比较困难,空间稀疏则新进程申请空间十分麻烦。目前没有最优方案,一般可以通过一些智能算法或者定期压缩段来缓解这个问题。
segmentation解决了许多问题,利用上了潜在的巨大的内存空格键,实现了更深一步的虚拟化,它的实现也相对简单,硬件只需要根据接收的信息找到内存中各个分段的地址,无需关心分段的含义是什么。
但是它仍然不够灵活,不能支持足够稀疏的地址空间(外部碎片太多),比如说,进程堆空间必须是连续的,假如它很大的话,不能够放进那些松散的空余空间中,哪怕它们的总和远大于这个堆空间所占的体积。