文章目录
- 3. 系统调用
- 3.1 用户态与内核态
- 3.2 系统调用分类
- 3.3 如何从用户态切换到内核态(系统调用举例)
- 4. 内存管理
- 4.1 内存管理是做什么的
- 4.1.1 为什么需要虚拟地址空间
- 4.1.2 使用虚拟地址访问内存有什么优势
- 4.2 常见的内存管理机制
- 4.3 分页管理
- 4.3.1 基本概念
- 4.3.2 地址转换(由逻辑地址访问内存的过程)
- 4.3.3 快表
- 4.3.3.1 为什么引入快表
- 4.3.3.2 快表的访问流程
- 4.3.4 多级页表
- 4.3.4.1 为什么引入多级页表
- 4.4 分段管理
- 4.4.1 段表
- 4.4.2 地址转换
- 4.4.3 分页和分段的对比
- 4.5 段页式管理
- 4.5.1 逻辑地址结构
- 4.5.2 段表与页表
- 4.5.3 地址转换
- 4.6 由逻辑地址访问内存的次数
- 4.7 虚拟内存
- 4.7.1 传统存储管理方式的特征及缺点
- 4.7.2 局部性原理
- 4.7.3 什么是虚拟内存
- 4.7.4 虚拟内存的特征
- 4.7.5 如何实现虚拟内存
- 4.8 页面置换算法
- 4.8.1 最佳置换算法(OPT)
- 4.8.2 先进先出置换算法(FIFO)
- 4.8.3 最近最久未使用置换算法(LRU)
- 4.8.4 最少使用置换算法(LFU)
- 4.8.5 时钟置换算法(CLOCK,NRU)
- 4.8.6 改进型的时钟置换算法
3. 系统调用
3.1 用户态与内核态
进程在系统上的运行分为两个级别,用户态和内核态。
用户态的进程可以直接读取用户程序的数据,系统态的进程可以直接访问计算机的任何资源,不受限制。
运行的应用程序基本都是运行在用户态,如果调用操作系统提供的系统态的子功能,就需要系统调用。
3.2 系统调用分类
系统调用,按功能大致分为:
- 设备管理。完成设备的请求、释放、启动等功能。
- 文件管理。完成文件的读、写、创建及删除等功能。
- 进程控制。完成进程的创建、撤销、阻塞及唤醒功能。
- 进程通信。完成进程之间的消息传递或信号传递等功能。
- 内存管理。完成内存的分配、回收,以及获取作业占用内存区大小及地址等功能
3.3 如何从用户态切换到内核态(系统调用举例)
- 用户态进程主动要求切换到内核态的系统调用
- 异常,比如缺页异常
- 外围设备的中断,比如硬盘读写操作完成。
4. 内存管理
4.1 内存管理是做什么的
-
负责内存空间的分配与回收(C++中的malloc函数申请内存,free函数释放内存)
-
从逻辑上对内存空间进行扩充(如4GB内存运行60GB大小的游戏)
-
负责进行地址转换,也就是把逻辑地址转换成相应的物理地址
-
内存保护。保证各进程在各自存储空间内运行,互不干扰。
4.1.1 为什么需要虚拟地址空间
如果没有虚拟地址空间,程序直接访问和操作的都是物理内存。这样会带来两个主要问题:
- 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易破坏操作系统,造成操作系统崩溃。
- 想要同时运行多个程序特别困难。举个例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
4.1.2 使用虚拟地址访问内存有什么优势
- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
- 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。
- 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
4.2 常见的内存管理机制
内存管理机制分为连续管理方式(指为一个用户程序分配一个连续的内存空间)和非连续管理方式(指允许一个程序使用的内存有着不相邻的分布)
-
连续分配管理方式
-
分块管理
把内存分为大小相等且固定的几个块,每个进程占用其中的一个。如果进程很小的话,会浪费大量的空间。块式管理已经被淘汰。
-
-
非连续分配管理方式
-
分页管理
把内存分为若干个很小的页面,相比分块,划分的力度更大。分页管理提高了内存利用率,减少内存碎片。
分页管理通过页表对应逻辑地址和物理地址。
分页管理虽然提高了内存利用率,但是“页”无实际意义。
-
分段管理
把内存分为几个大小不固定的、有意义的段,每个段定义了一组逻辑信息。例如,主程序段Main、子程序段X、数据段D、栈段S等。
分段管理通过段表对应逻辑地址和物理地址。
-
段页式管理
段页式管理结合了分段管理和分页管理的优点。把主存先分成若干段,每个段又分成若干页。段与段之间、段的内部(因为是页)都是离散的。
-
4.3 分页管理
4.3.1 基本概念
-
页框:将内存空间分为一个个大小相等的分区(比如每个分区4KB),每个分区就是一个“页框”,或者称为“页帧”、“内存块”、“物理块”
-
页框号:每个页框有一个编号,称为“页框号”,或者“页帧号”、“内存块号”、“物理块号”。页框号从0开始
-
页/页面:
将用户进程的地址空间也分为与页框大小相等的一个个区域,称为“页”或“页面”。每个页面也有一个编号,即“页号”,页号也是从0开始。
一个进程的最后一个页可能并没有页框那么大(这样最后一个页放在页框中会有内部碎片)。因此页框不能太大,避免产生过大的内部碎片。
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。
各个页面不必连续存放,也不必按照顺序,可以放到不相邻的页框中。
- 分页管理的虚拟地址分为两部分,一部分是存储页面号,一部分是存储偏移量。
- 如果有K位表示“页内偏移量”,则说明该系统中一个页面的大小是2^K个内存单元。
- 如果有M位表示“页号”,则说明该系统中,一个进程最多允许有2^M个页面。
- 页表
分页管理中,每个页表项由“页号”和“内存块号”组成。
4.3.2 地址转换(由逻辑地址访问内存的过程)
内存管理单元(MMU)管理着逻辑地址和物理内存的转换,其中页表(Page Table)存储着页(程序地址空间)与页框(物理内存)的映射表。
下图页表存放着16个页,这16个页要用4个比特位来进行索引定位。
例如,对于虚拟地址(0010 0000 0000 0100),前4位是存储页面号(0010)2,后12位(0000 0000 0100)是存储偏移量。
根据页面号读取页表中对应表项(2)的内容(1101)。页表项内容的前3位表示内存块号,最后一位表示是否存在于内存中(0表示不存在,1表示存在)。
拼接页表项内容的前三位(内存块号,110)和虚拟地址中的后12位(存储偏移量,0000 0000 0100),得到物理地址(110 0000 0000 0100)。
根据得到的物理地址访问内存空间。
4.3.3 快表
4.3.3.1 为什么引入快表
快表的引入,是为了加快逻辑地址(虚拟地址)到物理地址转换的速度。
在引入快表之前,由逻辑地址访问内存的过程如下:
- 根据逻辑地址的高位拿到页号
- 根据页号访问内存中的页表,根据页表的映射拿到实际的内存块号(一次访问)
- 把内存块号和逻辑地址的低位拼接,得到物理地址
- 访问物理地址对应的内存(二次访问)
这个过程是需要两次直接访问内存的,所以为了加快这个访问速度,引入了快表。
快表可以认为是一个高速缓冲存储器(Cache),内容是页表的一部分或者全部内容,和页表的功能是一样的,只不过比在内存中的页表访问速度要快很多。
由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。
4.3.3.2 快表的访问流程
被访问的内存块很可能在短时间内再次被访问,可能程序在一段时间内会多次访问同一个页表项。
所以在每次访问页表项时,先在快表里查询是否有该页表项,如果没有再去页表中查询,并把查询的页表项存入快表。
如果快表满了,就根据一些策略把其中的一些页表项淘汰掉,再把新查询的页表加进去。
总结:使用快表之后的地址转换流程如下:
- 根据虚拟地址中的页号查快表;
- 如果该页在快表中,直接从快表中读取相应的物理地址;
- 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
- 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
4.3.4 多级页表
4.3.4.1 为什么引入多级页表
多级页表主要是为了避免把全部页表一直放在内存中占用过多空间的问题,特别是那些根本就不需要的页表就不需要保留在内存中。属于时间换空间。
单级页表存在的问题:
- 页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。——多级页表解决
- 整个单级页表都会常驻内存,这是没有必要的,因为进程在一段时间内可能只要访问某几个特定的页面。——虚拟存储技术解决
两级页表,将全部的页表项分组,使得每组的页表项刚好能够装入一个内存块,让各组页表项(二级页表)可以离散地分配在各个内存块中,同时为了记录二级页表的顺序,通过一个“页目录表”来映射二级页表的页号和二级页表存放的内存块号。
两级页表的引入,使得虚拟地址的结构也要发生相应的变化:一级页号+二级页号+页内偏移量
两级页表的地址转换:
注意,采用多级页表机制时,各级页表的大小不能超过一个页面。
4.4 分段管理
4.4.1 段表
4.4.2 地址转换
段长大小是不固定的,但是段表项长度是固定的,不要混淆。
4.4.3 分页和分段的对比
- 单位
- 页是信息的物理单位
- 段是信息的逻辑单位
- 目的
- 分页的目的是为了实现离散匹配,提高内存利用率。
- 分段的目的是更好地满足用户的需求。
- 层面
- 分页仅仅是系统管理的需要,完全是系统行为,对用户是不可见的。
- 一个段通常包含着一组属于一个逻辑模块的信息,分段对用户是可见的,用户编程时需要显式地给出段名。
- 大小
- 页的大小是固定的,且由系统决定。页表由页号(可隐含)和页框号组成
- 段的长度不固定,由用户编写的程序大小决定。段表由段号(可隐含)、段长、基址组成。
- 地址转换过程
- 分段管理相比于分页管理,需要进行两次越界检查:
- 段号与段表寄存器中的段表长度比较,检查是否越界
- 根据段表中记录的段长,检查段内地址是否越界
- 分段管理相比于分页管理,需要进行两次越界检查:
- 地址空间
- 分页的用户进程地址空间是一维的,程序员只需要给出一个记忆符指出页号即可表示一个地址。
- 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
- 分段比分页更容易实现信息的共享和保护(不可更改的代码可以共享)
- 内存碎片
- 分页管理内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片
- 分段管理会产生外部碎片。如果采用“紧凑”的方式来解决外部碎片,会付出较大的时间代价。
4.5 段页式管理
4.5.1 逻辑地址结构
4.5.2 段表与页表
段页式管理中,每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。
分段管理中,每个段表项由段号、段长、基址组成。
段页式管理中以及分页管理中,每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。
Note:段页式管理中的段表项结构与分段管理中的段表项结构不同,但段页式管理中的页表项结构与分页管理中的页表项结构相同。
一个进程对应一个段表,但是一个进程有可能对应多个页表。
4.5.3 地址转换
4.6 由逻辑地址访问内存的次数
-
分页
-
单级页表
- 第一次访问内存——查内存中的页表
- 第二次访问内存——访问目标内存单元
-
多级页表(n级)
- 逐级访问页表(n次)
- 第n+1次,访问
-
-
分段
- 第一次访问内存——查内存中的段表
- 第二次访问内存——访问目标内存单元
-
段页式
- 第一次访问内存——查内存中的段表
- 第二次访问内存——查页表
- 第三次访问内存——访问目标内存单元
-
快表
- 分页管理、分段管理中,都可以引入快表,将近期访问过的页表项/段表项放到快表中。若快表命中,则仅需一次访问内存。
- 段页式管理中,也可以引入快表,将近期访问过的段号和页号作为查询快表的关键字。若快表命中,则仅需一次访问内存。
4.7 虚拟内存
4.7.1 传统存储管理方式的特征及缺点
4.7.2 局部性原理
4.7.3 什么是虚拟内存
4.7.4 虚拟内存的特征
4.7.5 如何实现虚拟内存
实现虚拟内存,操作系统需要在传统的非连续分配存储管理方式的基础上,再增加两个主要功能:
- 请求调页/请求调段——解决访问的信息不在内存时的问题
- 页面置换/分段置换——解决内存空间不够的问题
实现虚拟内存的三种主要方式为:
-
请求分页存储管理
- 请求分页存储管理建立在传统分页管理之上,为了支持虚拟内存而增加了请求调页功能和页面置换功能。
- 请求分页是目前最常用的一种实现虚拟内存的方法。
- 在作业开始运行时,仅装入当前要执行的部分页面即可运行;
- 假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统将相应的页面调入到内存。
- 如果发现在把页面调入内存时,内存已满,操作系统也可以将暂时不用的页面置换到外存中。
-
请求分段存储管理
原理和请求分页存储管理相似,不过是把页面换成段
-
请求段页式存储管理
不管是上面哪种实现方式,一般都需要:
- 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
- 缺页中断/缺段中断:如果需执行的指令或访问的数据尚未在内存(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段调入到内存,然后继续执行程序;
- 虚拟地址空间:都需要把逻辑地址转换为物理地址。
4.8 页面置换算法
请求分页存储管理中的页面置换算法。
页面的换入、换出需要磁盘IO,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率。
4.8.1 最佳置换算法(OPT)
最佳置换算法,每次选择淘汰的页面将是以后永不使用或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面,无法提前预判页面访问序列。因此,最佳页面置换算法是无法实现的。
4.8.2 先进先出置换算法(FIFO)
先进先出置换算法,每次选择淘汰的页面是最早进入内存的页面。
实现算法:把调入的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面换出即可。
队列的最大长度限制取决于系统为进程分配了多少个内存块。
4.8.3 最近最久未使用置换算法(LRU)
最近最久未使用置换算法,每次淘汰的页面是最近最久未使用的页面。
实现方法:LRU赋予每个页面一个访问字段,用访问字段记录该页面自上次被访问以来所经历的时间T。当需要淘汰一个页面时,选择现有页面中T值最大的,即最近最久未使用的页面。
4.8.4 最少使用置换算法(LFU)
最少使用页面置换算法(LFU,Least Frequently Used),选择在之前时期使用最少的页面作为淘汰页。
4.8.5 时钟置换算法(CLOCK,NRU)
时钟置换算法,又称最近未用算法(NRU,Not Recently Used),是一种性能和开销较均衡的算法。
CLOCK算法的实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位 置1。当需要淘汰一个页面时,只需检查页的访问位。如果是0,则选择该页换出;如果是1,则将其置为0,暂不换出,继续检查下一个页面。如果第一轮扫描中所有页面都是1,则将这些页面的访问位依次置0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面),因此简单的CLOCK算法选择淘汰一个页面最多会经过两轮扫描。
4.8.6 改进型的时钟置换算法
简单的时钟置换算法,仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。
只有被淘汰的页面被修改过时,才需要写回外存。
因此,改进型的时钟置换算法,除了考虑一个页面最近是否被访问过之外,还应考虑该页面是否被修改过。在其他条件都相同时,应优先淘汰没有被修改过的页面,避免I/O操作。
- 访问位:访问位=0表示最近未被访问过,访问位=1表示最近被访问过。
- 修改位:修改位=0表示页面没有被修改过,修改位=1表示页面被修改过。