搭建网站架构是什么意思,手机海报制作app,如何用微信小程序做网站,wordpress搬家菜单《操作系统导论》第16章读书笔记#xff1a;分段 —— 杭州 2024-03-31 夜 文章目录 《操作系统导论》第16章读书笔记#xff1a;分段0.前言1.分段#xff1a;泛化的基址/界限2.我们引用哪个段#xff1f;3.栈怎么办4.支持共享5.细粒度与粗粒度的分段、操作系统支持6.小结7…《操作系统导论》第16章读书笔记分段 —— 杭州 2024-03-31 夜 文章目录 《操作系统导论》第16章读书笔记分段0.前言1.分段泛化的基址/界限2.我们引用哪个段3.栈怎么办4.支持共享5.细粒度与粗粒度的分段、操作系统支持6.小结7.补充笔记地址空间和进程空间的关系8.补充笔记一个CPU有几个基址寄存器和界限寄存器一个CPU有几个地址空间和进程空间一个CPU有几个MMU 0.前言
到目前为止我们一直假设将所有进程的地址空间完整地加载到内存中。利用基址和界限寄存器操作系统很容易将不同进程重定位到不同的物理内存区域。但是对于这些内存区域你可能已经注意到一件有趣的事栈和堆之间有一大块“空闲”空间。
从图 16.1 中可知如果我们将整个地址空间放入物理内存那么栈和堆之间的空间并没有被进程使用却依然占用了实际的物理内存。因此简单的通过基址寄存器和界限寄存器实现的虚拟内存很浪费。另外如果剩余物理内存无法提供连续区域来放置完整的地址空间进程便无法运行。这种基址加界限的方式看来并不像我们期望的那样灵活。因此
关键问题怎样支持大地址空间 怎样支持大地址空间同时栈和堆之间可能有大量空闲空间在之前的例子里地址空间非常小所以这种浪费并不明显。但设想一个 32 位4GB的地址空间通常的程序只会使用几兆的内存但需要整个地址空间都放在内存中。 1.分段泛化的基址/界限 分段这个想法很简单在MMU 中引入不止一个基址和界限寄存器对而是给地址空间内的每个逻辑段segment一对。一个段只是地址空间里的一个连续定长的区域在典型的地址空间里有3个逻辑不同的段代码、栈和堆。分段的机制使得操作系统能够将不同的段放到不同的物理内存区域从而避免了虚拟地址空间中的未使用部分占用物理内存。 稀疏地址空间sparse address spaces如图16.2所示64KB的物理内存中放置了3个段为操作系统保留16KB。从图中可以看到只有已用的内存才在物理内存中分配空间因此可以容纳巨大的地址空间其中包含大量未使用的地址空间有时又称为稀疏地址空间sparse address spaces。 段错误段错误指的是在支持分段的机器上发生了非法的内存访问。有趣的是即使在不支持分段的机器上 这个术语依然保留。 段异常segmentation violation或段错误segmentation fault如果我们试图访问非法的地址例如7KB它超出了堆的边界呢你可以想象发生的情况硬件会发现该地址越界因此陷入操作系统很可能导致终止出错进程。这就是每个C 程序员都感到恐慌的术语的来源。
// 假设基址寄存器值为 32KB即 32768 字节界限寄存器值为 2KB即 2048 字节// 虚拟地址转换为物理地址的函数
function 虚拟地址转物理地址(虚拟地址, 基址寄存器, 界限寄存器) {// 越界检查if (虚拟地址 界限寄存器) {抛出异常(段错误); // 越界访问} else {// 计算物理地址并返回物理地址 虚拟地址 基址寄存器;return 物理地址;}
}// 示例 1: 虚拟地址 100 的转换
物理地址 虚拟地址转物理地址(100, 32768, 2048);
// 物理地址应该是 32868// 示例 2: 堆中的地址 4200 的转换
// 假设堆基址寄存器值为 34KB即 34816 字节界限寄存器值为 2KB
物理地址 虚拟地址转物理地址(4200 - 4096, 34816, 2048); // 偏移量是 4200 - 4096
// 物理地址应该是 34920// 越界检查示例
try {虚拟地址转物理地址(4400, 32768, 2048); // 假设这是一个非法地址因为它超出了界限
} catch (异常 e) {打印(异常: e); // 捕获到越界异常
}2.我们引用哪个段
硬件在地址转换时使用段寄存器。它如何知道段内的偏移量以及地址引用了哪个段一种常见的方式有时称为显式explicit方式就是用虚拟地址的开头几位来标识不同的段。在我们的例子中如果前两位是00硬件就知道这是属于代码段的地址因此使用代码段的基址和界限来重定位到正确的物理地址。如果前两位是01则是堆地址对应地使用堆的基址和界限。前两位01告诉硬件我们引用哪个段。剩下的12 位是段内偏移0000 0110 1000即十六进制0x068 或十进制104。因此硬件就用前两位来决定使用哪个段寄存器然后用后12位作为段内偏移。偏移量与基址寄存器相加硬件就得到了最终的物理地址。请注意偏移量也简化了对段边界的判断。我们只要检查偏移量是否小于界限大于界限的为非法地址。上面使用两位来区分段但实际只有3个段代码、堆、栈因此有一个段的地址空间被浪费。因此有些系统中会将堆和栈当作同一个段因此只需要一位来做标识。硬件还有其他方法来决定特定地址在哪个段。在隐式implicit方式中硬件通过地址产生的方式来确定段。例如如果地址由程序计数器产生即它是指令获取那么地址在代码段。如果基于栈或基址指针它一定在栈段。其他地址则在堆段。如果基址和界限放在数组中每个段一项为了获得需要的物理地址硬件会做下面这样的事
1 // 获取14位虚拟地址VA的前2位
2 Segment (VirtualAddress SEG_MASK) SEG_SHIFT
3
4 // 现在获取偏移量
5 Offset VirtualAddress OFFSET_MASK
6
7 // 如果偏移量超出了段的界限
8 if (Offset Bounds[Segment])
9 RaiseException(PROTECTION_FAULT) // 抛出保护故障异常
10 else
11 PhysAddr Base[Segment] Offset // 计算物理地址
12 Register AccessMemory(PhysAddr) // 从物理地址访问内存并将数据存入寄存器3.栈怎么办
它反向增长地址转换必须有所不同。需要一点硬件支持。除了基址和界限外硬件还需要知道段的增长方向用一位区分比如1代表自小而大增长0 反之。例程1:
# 定义段寄存器的值这里仅以栈的信息为例
# 假设栈的基址为28KB大小为2KB
SEGMENT_BASE_STACK 28 * 1024 # 栈段的基址28KB转换为字节
SEGMENT_SIZE_STACK 2 * 1024 # 栈段的大小2KB转换为字节# 虚拟地址转换函数
def convert_stack_virtual_to_physical(virtual_address):# 如果虚拟地址不在栈的界限内触发保护故障异常if virtual_address SEGMENT_SIZE_STACK:raise Exception(保护故障虚拟地址超出了栈的界限)# 计算物理地址基址 虚拟地址偏移量physical_address SEGMENT_BASE_STACK virtual_addressreturn physical_address# 示例对栈内的虚拟地址进行转换
try:# 假设要转换的栈虚拟地址为1500字节偏移量virtual_address 1500# 调用函数进行转换physical_address convert_stack_virtual_to_physical(virtual_address)# 打印转换后的物理地址print(物理地址, physical_address)
except Exception as e:# 捕获并打印异常信息print(str(e))例程2:
// 假设每个段有一个基址Base和界限Limit寄存器以及一个反向增长标志GrowDown// 段寄存器的数组每个段一个寄存器包含基址、界限和反向增长标志
SegmentRegisters [{ Base: 32KB, Limit: 2KB, GrowDown: false }, // 代码段{ Base: 34KB, Limit: 2KB, GrowDown: false }, // 堆段{ Base: 28KB, Limit: 2KB, GrowDown: true } // 栈段
]// 虚拟地址转换为物理地址的函数
function 虚拟地址转物理地址(虚拟地址, 段寄存器) {// 计算段内偏移量偏移量 虚拟地址 % 段的大小// 根据段是否反向增长调整偏移量if (段寄存器.GrowDown) {偏移量 段寄存器.Limit - 偏移量 - 1}// 越界检查if (偏移量 0 || 偏移量 段寄存器.Limit) {抛出异常(段错误); // 越界访问} else {// 计算物理地址并返回物理地址 段寄存器.Base 偏移量return 物理地址}
}// 示例: 虚拟地址 100 的转换假设在代码段
物理地址 虚拟地址转物理地址(100, SegmentRegisters[代码段])
// 物理地址应该是 100 32KB 32868// 示例: 虚拟地址 4200 的转换假设在堆段
物理地址 虚拟地址转物理地址(4200 - 4KB, SegmentRegisters[堆段])
// 物理地址应该是 104 34KB 34920// 越界检查示例
try {虚拟地址转物理地址(4400, SegmentRegisters[堆段]) // 假设这是一个非法地址因为它超出了界限
} catch (异常 e) {打印(异常: e) // 捕获到越界异常
}4.支持共享
具体来说要节省内存有时候在地址空间之间共享share某些内存段是有用的。尤其是代码共享很常见今天的系统仍然在使用。保护位protection bit为了支持共享需要一些额外的硬件支持这就是保护位protection bit。基本为每个段增加了几个位标识程序是否能够读写该段或执行其中的代码。通过将代码段标记为只读同样的代码可以被多个进程共享而不用担心破坏隔离。虽然每个进程都认为自己独占这块内存但操作系统秘密地共享了内存进程不能修改这些内存所以假象得以保持。表16.3 展示了一个例子是硬件和操作系统记录的额外信息。可以看到代码段的权限是可读和可执行因此物理内存中的一个段可以映射到多个虚拟地址空间。有了保护位前面描述的硬件算法也必须改变。除了检查虚拟地址是否越界硬件还需要检查特定访问是否允许。如果用户进程试图写入只读段或从非执行段执行指令硬件会触发异常让操作系统来处理出错进程。 5.细粒度与粗粒度的分段、操作系统支持 到目前为止我们的例子大多针对只有很少的几个段的系统即代码、栈、堆。我们可以认为这种分段是粗粒度的coarse-grained因为它将地址空间分成较大的、粗粒度的块。但是一些早期系统如Multics[CV65,DD68]更灵活允许将地址空间划分为大量较小的段这被称为细粒度fine-grained分段。 支持许多段需要进一步的硬件支持并在内存中保存某种段表segment table。这种段表通常支持创建非常多的段因此系统使用段的方式可以比之前讨论的方式更灵活。例如像Burroughs B5000 这样的早期机器可以支持成千上万的段有了操作系统和硬件的支持编译器可以将代码段和数据段划分为许多不同的部分[RK68]。当时的考虑是通过更细粒度的段操作系统可以更好地了解哪些段在使用哪些没有从而可以更高效地利用内存。 系统运行时地址空间中的不同段被重定位到物理内存中。与我们之前介绍的整个地址空间只有一个基址/界限寄存器对的方式相比大量节省了物理内存。具体来说栈和堆之间没有使用的区域就不需要再分配物理内存让我们能将更多地址空间放进物理内存。 然而分段也带来了一些新的问题。我们先介绍必须关注的操作系统新问题。第一个是老问题操作系统在上下文切换时应该做什么你可能已经猜到了各个段寄存器中的内容必须保存和恢复。显然每个进程都有自己独立的虚拟地址空间操作系统必须在进程运行前确保这些寄存器被正确地赋值。 第二个问题更重要即管理物理内存的空闲空间。新的地址空间被创建时操作系统需要在物理内存中为它的段找到空间。之前我们假设所有的地址空间大小相同物理内存可以被认为是一些槽块进程可以放进去。现在每个进程都有一些段每个段的大小也可能不同。 一般会遇到的问题是物理内存很快充满了许多空闲空间的小洞因而很难分配给新的段或扩大已有的段。这种问题被称为·外部碎片external fragmentation[R69]如图16.3左边所示。 在这个例子中一个进程需要分配一个20KB 的段。当前有24KB 空闲但并不连续是3个不相邻的块。因此操作系统无法满足这个20KB 的请求。 该问题的一种解决方案是紧凑compact物理内存重新安排原有的段。例如操作系统先终止运行的进程将它们的数据复制到连续的内存区域中去改变它们的段寄存器中的值指向新的物理地址从而得到了足够大的连续空闲空间。这样做操作系统能让新的内存分配请求成功。但是内存紧凑成本很高因为拷贝段是内存密集型的一般会占用大量的处理器时间。图16.3右边是紧凑后的物理内存。 一种更简单的做法是利用空闲列表管理算法试图保留大的内存块用于分配。相关的算法可能有成百上千种包括传统的最优匹配best-fit从空闲链表中找最接近需要分配空间的空闲块返回、最坏匹配worst-fit、首次匹配first-fit以及像伙伴算法buddy algorithm[K68]这样更复杂的算法。Wilson等人做过一个很好的调查[W95]如果你想对这些算法了解更多可以从它开始或者等到第17章我们将介绍一些基本知识。但遗憾的是无论算法多么精妙都无法完全消除外部碎片因此好的算法只是试图减小它。 6.小结
分段解决了一些问题帮助我们实现了更高效的虚拟内存。不只是动态重定位通过避免地址空间的逻辑段之间的大量潜在的内存浪费分段能更好地支持稀疏地址空间。它还很快因为分段要求的算法很容易很适合硬件完成地址转换的开销极小。分段还有一个附加的好处代码共享。如果代码放在独立的段中这样的段就可能被多个运行的程序共享。
但我们已经知道在内存中分配不同大小的段会导致一些问题我们希望克服。首先是我们上面讨论的外部碎片。由于段的大小不同空闲内存被割裂成各种奇怪的大小因此满足内存分配请求可能会很难。用户可以尝试采用聪明的算法[W95]或定期紧凑内存但问题很根本难以避免。
第二个问题也许更重要分段还是不足以支持更一般化的稀疏地址空间。例如如果有一个很大但是稀疏的堆都在一个逻辑段中整个堆仍然必须完整地加载到内存中。换言之如果使用地址空间的方式不能很好地匹配底层分段的设计目标分段就不能很好地工作。因此我们需要找到新的解决方案。你准备好了吗 7.补充笔记地址空间和进程空间的关系
地址空间和进程空间是两个相关但不同的概念它们在操作系统中以不同的方式被管理和使用。 地址空间 地址空间通常指的是一个处理器可以寻址的全部内存范围。在32位系统中地址空间通常是4GB2的32次方在64位系统中则远远大于此数值。地址空间包括了所有可能的地址这些地址可以是物理内存地址也可以是虚拟内存地址。 物理地址空间是指CPU通过其地址引脚直接访问的内存RAM、ROM、映射的I/O等的范围。虚拟地址空间是指通过虚拟内存管理操作系统提供给应用程序和进程的一套地址这些地址会通过MMU映射到物理地址空间中的实际位置。 进程空间 进程空间有时称为进程的虚拟地址空间是指分配给单个进程的内存区域它是进程可见的地址空间。每个进程都有自己的独立的虚拟地址空间操作系统和MMU负责将此虚拟地址空间映射到物理地址空间上。这意味着两个不同的进程可以使用相同的虚拟地址指向不同的物理内存或者映射到相同的物理地址但是拥有不同的权限和属性。
地址空间和进程空间的关系 隔离性每个进程拥有独立的虚拟地址空间这样即使它们的虚拟地址相同也不会相互影响因为这些地址映射到物理地址空间的不同部分。这提供了良好的安全性和稳定性因为进程不能直接访问其他进程的内存。 映射操作系统的内存管理系统负责将进程的虚拟地址空间映射到物理地址空间。这通常通过页表完成页表存储了虚拟地址到物理地址的映射信息。 共享尽管每个进程有自己独立的虚拟地址空间但是有时候不同的进程需要共享内存。操作系统可以将同一块物理内存映射到多个进程的虚拟地址空间中这通常用于进程间通信或共享库的映射。
因此地址空间是处理器级别的内存寻址概念而进程空间是操作系统级别的内存隔离和管理概念。操作系统通过虚拟内存管理技术为每个进程创建了一种错觉使其认为自己拥有连续的、独立的内存区域而实际上它们是在共享同一个物理地址空间。
特性地址空间进程空间定义CPU可以寻址的全部内存范围包括物理和虚拟内存。分配给单个进程的虚拟内存区域是进程可见和可操作的内存范围。类型物理地址空间和虚拟地址空间。每个进程的虚拟地址空间。用途CPU访问内存和其他资源的能力。隔离进程确保进程内存访问的独立性和安全性。管理由CPU和MMU负责物理和虚拟地址的映射。由操作系统负责管理确保每个进程有独立的地址空间。隔离性无物理地址空间是共享的。高每个进程的虚拟地址空间彼此隔离。共享物理内存可以被多个进程共享如通过共享内存。虚拟地址空间通常是独立的但可以有映射到相同物理内存的部分。映射关系虚拟地址空间到物理地址空间的映射由页表或段表管理。进程的虚拟地址空间到物理地址空间的映射通过操作系统的内存管理器控制。大小取决于CPU架构32位、64位等。取决于操作系统和系统配置但通常每个进程的虚拟空间都有一个上限。多样性通常一个系统中物理和虚拟地址空间是统一的。每个进程的进程空间可以不同取决于其内存需求和操作系统的分配。
8.补充笔记一个CPU有几个基址寄存器和界限寄存器一个CPU有几个地址空间和进程空间一个CPU有几个MMU
CPU的基址寄存器和界限寄存器的数量地址空间和进程空间的数量以及MMU内存管理单元的数量这些都不是固定的而是取决于具体的CPU设计和它的用途。 基址寄存器和界限寄存器 基址寄存器和界限寄存器主要用于分段内存管理。在分段内存管理系统中每个段(segment)通过一对基址base和界限limit寄存器来描述。这样的寄存器对的数量取决于CPU的架构和设计。例如较旧的x86架构的处理器可能只有几个用于分段的基址和界限寄存器如CS, DS, ES, FS, GS, SS等而更现代或者专用的处理器可能有更多或根本不使用分段内存管理。 地址空间和进程空间 一个CPU通常管理一个地址空间即它可以通过其地址线访问的全部内存范围。但这个地址空间可以通过不同的机制如分页或分段被映射到多个进程空间。每个进程空间可以想象成一个独立的地址空间它是操作系统通过虚拟内存管理功能提供给单个进程的内存视图。理论上一个操作系统可以支持任意多个进程空间但实际上这受限于系统的物理内存、虚拟内存限制和操作系统设计。 MMU内存管理单元 大多数现代CPU会有一个内存管理单元MMU。MMU负责虚拟地址到物理地址的转换并且通常支持分页或分段机制。一般情况下每个CPU核心会有一个MMU。在多核心处理器中每个核心可能有自己的MMU或者有些设计可能共享一个MMU。
在讨论具体的CPU时需要参考该CPU的技术手册或架构文档来获取准确的寄存器和硬件特性信息。