计算机操作系统——(第四章) 存储器管理/内存管理

目录

Previously on OS...

存储器的层次结构

操作系统的内存管理

程序运行的步骤

地址绑定

Previously on Experiment

内核的编译

gdb调试【1】

Stopping and Continuing(停止并继续)

Examining the Stack(检查堆栈)

Source and Machine Code(源代码和机器代码)

中断机制

Now...

连续内存分配(Contiguous Memory Allocation)

内存保护机制(Memory Protection)

内存分配机制(Memory Allocation)

动态存储分配(dynamic storage-allocation)

动态分区分配中的数据结构

碎片问题(Fragmentation Problem)

分页(Paging)

参考

[1]GDB调试

[2]操作系统中的自陷Trap(software interrupt)/异常Exception、中断Interrupt

[3]调度器Scheduler和分派器Dispatcher

Translate a logical address to a physical address(将逻辑地址转换为物理地址)


Previously on OS...

存储器的层次结构

register-high speed cache-main memory-fixed disk-removable storage

(寄存器高速缓存—主内存—固定磁盘—可移动存储)

操作系统的内存管理

可执行存储器(寄存器+主存)的分配、回收以及提供在存储层次间数据移动的管理机制。

程序运行的步骤

  1. compile(编译)【c-obj】
  2. link(链接) 【modules+objs】
  3. load(载入) 【allocate memory(分配内存)】

地址绑定

编译时-绝对代码/装入时-可重定位代码/运行时-(base+limit)(基础+限制)

逻辑地址(CPU生成的地址)和物理地址(MMU内存地址寄存器中的地址)

虚拟地址在程序运行时的地址绑定机制生成的逻辑地址。

Virtual address(虚拟地址)---》MMU --》Physical address(物理地址)

base/relocation register + limit register ----》 address space mapping(地址空间映射)

动态装入(使用时装入) ---》动态链接和共享库(Dynamically linked libraries-动态链接库,DLLs)

Previously on Experiment

内核的编译

gdb调试【1】

Stopping and Continuing(停止并继续)

break(中断)/continue(继续)/finish(完成)/step(步骤)

Examining the Stack(检查堆栈)

backtrace(回溯)/info register(信息寄存器)/info stack(信息栈)/info frame(信息帧)

Source and Machine Code(源代码和机器代码)

 disassemble(拆卸)/info line(信息线)

中断机制

Now...

连续内存分配(Contiguous Memory Allocation)

  • 主存要保证操作系统和用户进程的使用。通常分为两个分区:分别供操作系统和用户进程使用。
  • 操作系统使用高位内存地址(high memory addresses)还是低位内存地址(low memory addresses)取决于很多因素,比如中断向量(interrupt vector)的位置。
  • Windows和Linux使用高位内存地址。
  • 在连续内存分配策略中,每个进程包含在单一的内存中(single section of memory),多个进程的内存段是连续的。
  • 如何使各进程拥有独立空间?(保护内存)
  • 大部分时候内存中同时驻留多个用户进程,如何为即将装入内存的进程分配可用内存?(分配内存)

内存保护机制(Memory Protection)

  • 防止进程访问不属于它的内存空间。limit register + relocation register
  • 当CPU访问逻辑地址时,先与界限寄存器的地址比较,如果超出许可的界限,则触发系统自陷【2】。如果是合法地址,则与重定位寄存器的地址相加,得到真实的物理地址。

  • 当CPU调度器(CPu - Scheduler) 准备挑选一个进程进入运行状态时,分派器(dispatcher)也载入了重定位寄存器和界限寄存器的内容,作为上下文切换的一部分【3】。
  • 在进程访问内存地址时,我们可以比较CPU生成的逻辑地址与界限寄存器的内容,从而避免内存的非法访问

内存分配机制(Memory Allocation)

  •  将进程指派到内存中的可变大小的分区,每个分区只包含一个进程。可变分区(variable - partition)方案下,OS维护了一张数据表,记录了哪些内存是可用的,被看作一大块可用的孔(hole)
  • 正常情况:当进程载入系统时,OS评估每个进程请求的内存和可用内存,决定分配给谁、分配多少内存。当进程获得了空间,进程得以进入内存并运行。当进程结束时,释放占用的内存,并给其他进程使用。

  •  如果没有足够的空间分配给进程呢?
  1.   拒绝进程执行,返回错误信息。
  2.   将进程放入等待队列,后续有进程释放内存后,OS检查等待队列,再为队列中的进程分配空间。
  • 通常,可用内存块由一个孔集合(a set of holes)组成,孔的大小不一,分布在整个内存中。当进程需要内存是,系统查找孔集合中是否有合适的孔。如果一个孔太大,可以将它分为两个部分,其中一个孔提供给进程,另一个孔重新放入孔集合中。
  • 这个过程是动态存储分配问题(dynamic storage-allocation problem)的一个特例,即根据一组空闲孔来分配大小为n的请求。可以使用首次适应(first-fit)最佳适应(best-fit)最差适应(worst-fit)等算法。

动态存储分配(dynamic storage-allocation)

首次适应(first fit):分配第一-次找到的能。满足要求孔。检索孔列表,可以从头开始,或者从上次的FF搜索结束的位置开始。找到第一一个符合要求的孔时,结束检索。
最佳适应(best fit):分配能满足要求的最小的孔。如果孔列表没有排序,则必须检索整个孔列表。
最差适应(worst fit):分配最大的孔。如果孔列表没有排序,则必须检索整个孔列表。总是提供剩余孔中,最大的孔。 

动态分区分配中的数据结构

(1) 空闲分区表:每个空闲分区占一个表目,表目中包括分区号、分区大小和分区起始地址等;

 (2) 空闲分区链:实现对空闲分区的分配和链接,在每个分区的起始位置设置一些分配的信息、链接各分区的前向指针,分区尾部设置后向指针。尾部也可以重复设置状态位和分区大小表目。状态位为1时表示分区已分配,0表示空闲。

碎片问题(Fragmentation Problem)

  • 首次适应和最佳适应都有外部碎片(external fragmentation)问题。随着进程加载到内存或从内存中移除,空闲内存空间被分为小的片段。当总的可用内存之和可以满足请求,但空间并不连续时,就会导致外部碎片问题:存储被分成了大量的小孔。 '
  • 在最坏情况下,每两个进程之间就有空闲(或浪费的)块。如果这些内存是一整块,那么可以再运行多个进程。
  • 不管是first-fit还是best-fit都会引起碎片总量的增加。另外一个因素是从空闲分区的哪一端分配。
  • 碎片也可以是内部的。假设一个孔有18464字节,而进程只需要18462字节。如果按需求分配,剩余2字节。记录2字节孔的开销比孔自身大很多。
  • 解决办法:将物理内存划分为固定大小的块,根据块大小分配内存单元。此时为进程分配的内存略大于进程请求的大小。
  • 分配的大小与请求大小之差称为内部碎片(internal fragmentation),即在分区内部未使用的内存。
  • 解决外部碎片的办法之一:紧缩(compaction)/紧凑。通过整理内存中的内容,使可用内存集中到一起。
  • 如果重定位是静态的,并且在汇编时或加载时进行的,不能使用紧缩。只用重定位时动态的,并且是在运行时进行的,才能使用紧缩。如果地址被动态重定位,可以首先移动程序和数据,然后再根据新的基地址的值来改变基址寄存器。如果使用紧缩,还要评估开销。最简单的合并算法是将所有进程移到内存的一端,将所有的孔移到内存的另一端。
  • 另一种解决外部碎片的办法:允许逻辑地址空间不连续,允许进程被分配到任何可用的物理地址之上。
  • 这种策略称为分页(paging),大多数计算机系统使用此类策略。
  • 当需要管理数据块时,碎片问题都会发生。

分页(Paging)

  • 避免了外部碎片和紧缩。在服务器/大型机和移动设备中广泛使用。
  • 通过OS和硬件的协作来实现分页机制。
  • 基本方法:把 物理内存 分为固定大小的块,称为/页帧(frames),把 逻辑内存 分为同等大小的块,称为/页面(pages)。
  • 当需要执行一个进程时,它的页从文件系统或者备份存储等源处,载入到任何可用的内存帧之内。这样,逻辑地址空间与物理地址空间完全分离,即使物理内存小于2^64 ,进程在逻辑上仍然可以使用2^64的地址空间。
  • 由CPU生成的每个地址(逻辑地址)分为两部分:页号p(page number)页偏移d(page offset。页号作为页表(page table)的索引。

  •  注意:教材中使用页号P位偏移量W

logical address --> physical address
page number--> frame address(page table)
frame address + page offset -->  physical address

  •  页表包含了物理内存中每个帧的基础地址,偏移量指相关帧里的位移/位置。
  • 所以,物理地址是由基础地址和页偏移量来定义的。

  • MMU将CPU生成的逻辑地址转换成物理地址的步骤:
  • 1. 将页号p提取出来,作为页表的索引;
  • 2. 将页表中对应的帧号f提取出来;
  • 3. 使用帧号替换掉逻辑地址中的页号。
  • 由于偏移量d没有发生变化,无需替换,帧号和偏移量组成了物理地址 。
  • 页的大小是2的幂,是由硬件定义的,在不同的计算机架构上,每页的大小可以从
    4KB到1GB。
  • 如果逻辑地址空间是2m,页大小是2n字节,那么高阶m-n位的数字指定页号,n个低位数字指定页偏移量。逻辑地址如下如所示:

  •  逻辑地址地址空间大小为:24,页大小为22,即:n=2, m=4。使用4字节的页,32字节(8个页)的物理内存。
  • 逻辑地址0是页0偏移量0。从页表可知,页0的帧地址是5,所以逻辑地址0映射为物理地址是20【=5*4+0】
  • 逻辑地址3的物理地址为多少?23=5*4+3
  • 逻辑地址4的物理地址为多少?24=6*4+0

  • 维护页记录需要开销。页越大开销越低。
  • 典型的页大小为4KB或8KB。Windows 10支持从4KB到2MB的页大小。
  • 当系统准备执行进程时,按页检查进程的大小。进程的每一页都需要一个帧。如果进程需要n页,那么内存中至少要有n个帧。每个页号及其对应的帧号,都被记录到页表中。 
  • 分配内存前后 的空闲帧

  •  分页使得程序员把内存看作单一的空间。而实际上,用户程序被离散的分布在物理内存中;而且内存中可以同时存在多个用户程序。地址映射是由操作系统控制的,对程序员不可见。
  • 操作系统记录了物理内存的分配细节,哪些帧已分配,哪些帧已被使用,总共有多少帧等等。这些信息保存在帧表(frame table)数据结构中。对于每个物理页面帧,都有一个入口,指明了该帧是空闲或被哪个/哪些进程所占有。
  • 操作系统不仅维护指令计数器和寄存器的内容,也为每个进程维护一个页表的副本,用作将逻辑地址转换为物理地址。当一个进程分配到CPU时,分派器也根据该副本来定义硬件页表。因此,分页增加了上下文切换的时间开销

参考

[1]GDB调试

  • 当程序调用某个函数时,会产生关于该调用的信息。包括程序调用的地址,调用的参数,局部变量等。这些信息保存在一个数据块,称为栈帧Stack Frame。这些栈帧在一个内存区域,称为调用栈Call Stack

  • 当程序停止时,GDB提供了一系列的命令查看栈的信息。如backtrace、info stack、info frame可以查看当前执行的栈、栈帧信息。

  • GDB 入门教程 |Menci Ol Blog

  • GDB调试教程:1小时玩转Linux gdb命令 (biancheng.net)

[2]操作系统中的自陷Trap(software interrupt)/异常Exception中断Interrupt

  • 在运行进程时,CPU的执行过程为:读取指令,更新程序计数器,执行指令,重复以上步骤。但是有时侯下一个指令需要通过内核来执行,即将用户程序的控制权转交给内核,以提升指令的执行权限

  • 自陷通常是一个由用户进程引起的异常(Exception),是导致CPU指令不能继续执行的异常,是处理机指令内部的事件,比如除0、访问无效内存地址等。或用户进程在执行系统调用也可以引发陷阱。自陷是一种调用系统功能/执行权限提升的常见手段。当进程执行自陷时,由自陷指令(trap instruction)保存用户代码的状态,切换到内核态,然后分派给对应的内核功能并进行处理,如执行系统调用或服务等。自陷也称为软件中断。异常不能被屏蔽,一旦发生应立即处理。

  • 中断:也称外中断。指来自处理机执行指令以外的事件发生。由软件或硬件发出的信号。硬件和软件都产生这些信号,被称为硬件和软件中断。I/O结束中断、时钟中断。不同的中断具有不同的优先级。在处理高级中断时,低级中断可以被临时屏蔽。中断是异步发生的,并且可能随时发生

[3]调度器Scheduler和分派器Dispatcher

调度器:决定让哪个进程使用CPU。

  1. 长程调度/作业调度:决定多少进程留在主存,剩下的留在外存。

  2. 中程调度:当进程在运行时需要I/O(运行阻塞),或I/O完成需要CPU时(阻塞就绪),由中程调度完成切换。

  3. 短程调度:CPU调度器。从就绪队列中选择进程以分配CPU。

分派器:调度器完成工作之后,由分派器执行以下工作:修改进程的状态或进程所在的队列。包括:

  1. 切换上下文;

  2. 切换到用户模式;

  3. 跳转到合适的地址(恢复点)以继续执行程序。

  • 由于涉及到上下文切换,分派器要尽可能快速地执行。分派器的工作时间称为分派延迟(dispatch latency)。

Translate a logical address to a physical address(将逻辑地址转换为物理地址)

The following outlines the steps taken by the MMU to translate a logical address generated by the CPU to a physical address:
1. Extract the page number p and use it as an index into the page table.
2. Extract the corresponding frame number f  from the page table.
3. Replace the page number p in the logical address with the frame number f .
As the offset d does not change, it is not replaced, and the frame number and offset now comprise the physical address.

下面概述了 MMU 将 CPU 生成的逻辑地址转换为物理地址所执行的步骤:

1. 提取页码 p 并将其用作页表中的索引。

2. 从页表中抽取相应的帧编号 f。

3. 将逻辑地址中的页码 p 替换为帧号 f 。

由于偏移量 d 不会更改,因此不会替换它,并且帧编号和偏移量现在包含物理地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

钢铁大侠.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值