一、概述
内存:存取速度快、存储容量大和成本低(理想情况)
1、存储分层结构
Cache:高速缓存,多由硬件控制,可以暂存内存数据,速度比内存快,容量有限
内存:OS存储管理主战场,速度快,容量大,但信息断电消失,不能长久保存大容量数据
外存:磁盘等可以永久保存信息的大容量存储介质,成本也较低
2、要解决的问题
2.1 存储保护
内存是以字节或字作为基本存储单元进行编址的,每个存储单元对应一个地址,由这些连续的地址就构成了存储空间,程序计数器PC所真正指向的位置
OS将内存分为系统区和用户区,系统区用于存放OS,用户的程序和数据只能放入用户区,每个用户进程在内存都有自己的存储空间,不允许用户进程读写不属于自己进程的内存单元
为防止地址越界,硬件提供一对寄存器:
- 基址寄存器:存进程的起始地址
- 限长度寄存器:存进程的长度
2.2 地址重定位
地址重定位:程序需要运行,需要将处于外存的程序装入内存(在外存可装入模块中的偏移地址编程变成内存的地址),让程序计数器PC指向开始地址
- 逻辑地址(相对地址,虚地址):不能用逻辑地址直接在内存中读取信息;
- 物理地址(绝对地址,实地址):PC所指向的内存中的地址;
- 地址重定位(地址变换,地址映射):将逻辑地址变换为物理地址
- 编译阶段:将程序翻译成机器语言的目标模块,不分配内存,只根据各种符号声明时的类型去占位,完成从符号名到目标模块内偏移地址的转换;
- 链接阶段:程序编译后的形成的各个目标模块通过链接程序形成可装入模块;
- 装入阶段:将可装入模块加载进内存;
程序的链接
- 静态链接:将目标模块和库函数链接成一个完整的可装入模块,形成整个程序的连续逻辑地址;
- 装入时动态链接:对编译形成的一组模块边装入、边链接,各模块独立分开装入内存的不同位置,便于修改,便于共享;
- 运行时动态链接:边运行、边链接,不会运行到的模块(如错误处理)不必装入,节省内存
程序的装入
- 绝对装入方式:编译程序产生绝对地址的目标代码,将可装入模块放入内存指定的位置;(嵌入式系统)
- 可重定位装入方式(静态重定位):装入阶段一次性的完成地址变换,程序装入时,根据其在内存中的起始地址(基地址),将其中所有的逻辑地址转变为物理地址
- 动态运行时装入方式:程序在内存中的位置可以改变,边运行,边定位,程序运行时,根据其在内存中的起始地址(基地址),将其中所有的逻辑地址动态计算出物理地址
2.3 内存扩充
- 物理扩充:受条件限制
- 逻辑扩充:编程可用的地址空间(虚拟)远远大于存储空间
二、内存连续分配方式
内存连续分配:为一个用户程序分配一块不小于指定大小的连续物理内存空间
1、单一连续分配
单一连续分配:出现在批处理时代,系统区通常在低地址部分,用户区在任一时刻只连续的存放一道用户作业,优点是简单,易于管理,但仅适用于单用户、单任务的OS,而且容易造成内存浪费
2、分区式连续分配
分区式连续分配:用户区被分成一些大小相等或不等的部分供用户进程使用
2.1 固定分区分配
固定分区分配:一个用户进程只占据一个分区,当程序要装入到内存运行时,若有合适大小的空闲分区则装入内存,否则等待;OS可通过分区表或分区链表来实现存储管理
- 分区大小限制了进程大小,分区个数限制了并发进程个数
- 内部碎片浪费内存空间
2.2 可变分区分配
可变分区分配:当进程装入内存时,为进程分配指定大小的分区,OS通过可变分区表和空闲区链表来管理分区
- 首次适配算法(First Fit):空闲分区按地址递增的顺序排列,时间性能较好,但随着低地址分区不断划分,会产生较多小分区(外部碎片),影响内存的利用率和查找开销
- 循环首次适配算法(Next Fit):在首次适配的基础上,从上次内存分配之后的分区开始查找可用分区
- 最佳适配算法(Best Fit):空闲分区按容量递增的顺序排列,会形成较多的外部碎片,集中在空闲分区表的起始部分的小分区会降低性能
- 最差适配算法(Worst Fit):空闲分区容量递减的顺序排列,但较大的空闲分区不被保留
2.2 可变分区回收(分区合并)
- 上邻接:回收分区与低地址空闲区相邻
- 下邻接:回收分区与高地址空闲区邻接
- 上、下均邻接
- 上、下均不邻接:需要在分区表中增加一个新回收的分区信息
三、分页存储管理
1、地址转换
MMU(存储管理单元):把虚拟地址转化为物理地址
2、地址空间划分
在逻辑地址空间划分成大小相等的区域,称为页;
在物理地址空间划分成大小相等的区域,称为块
逻辑地址结构:页号 + 页内偏移量(x86页面大小是4kB)
页表:存放逻辑地址的页号与物理空间的块号之间的关系(OS负责填写这个页表)
基本地址变换结构
页表寄存器(页表始址 p0 ,页表长度 l)
四、快表和两级页表
1、通过页表访问内存数据
通过页表访问内存数据:两次访问内存(效率低)
- ① 访问内存中的页表,通过页表取得对应内存物理块的块号
- ② 访问内存中的数据
2、快表(联想寄存器)
快表TLB:将最近频繁访问的页表项放在快表中
命中率:快表中成功查找到所需页表项的比率(快表命中率可达90%以上)
访问内存的有效时间(EAT):从进程发出指定逻辑地址的访问请求,到最终从实际物理内存单元取出数据的时间
利用快表进行地址变换
3、两级页表
只保存用到的页表项,解决页表占用存储空间的问题
外层页表:记录各个内层页表所在物理块号的外层页表
内层页表:保存一部分页表
- 对大页表进行分页
- 解决大页表占用大的连续存储空间的问题,提高了空间效率
- 增加了访存时间,可通过快表进行补偿
五、分段存储管理
1、满足用户的需求:
- 方便编程:用户在访问数据时可以根据逻辑关系指出,逻辑地址由段号 + 段内地址构成
- 信息共享:程序和数据的共享是以信息的逻辑单位为基础的,而分页系统中的页只是存放信息的物理单位块,段却是信息的逻辑单位(共享段)
- 信息保护:程序分段保存,各段保持自己的特点
- 动态增长:程序每段分别装入内存(高效利用内存)
- 动态链接:动态链接以段为单位进行链接
2、分段存储管理基本原理
内存分配:以段为单位分配内存,每个段在内存中占据连续的空间,但各段之间可以不连续存放(首次适配、最佳适配、最差适配)
内存空间划分:内存空间被动态的划分为若干个长度不等的物理段,每个物理段由起始地址和长度确定
内存空间管理:记录空闲区起始地址和长度
段表:每个进程一个段表,放在内存中,在进程PCB中会保存段表的起始地址和段表项的个数
3、地址变换
4、分页和分段的主要区别
- 页是物理单位,分页是为了消减内存的碎片以提高内存的利用率,仅仅是系统需要;
段是逻辑单位,分段是为了更好的满足用户需要 - 页的大小固定且由系统确定,分页由硬件实现
段的长度不固定,由编译时根据程序信息来划分 - 分页的逻辑地址空间是一维的线性空间,标识地址时,只需给出一个逻辑地址
分段的逻辑地址空间是二维的,标识地址时,必须给出段号和段内地址
5、段页式存储管理
满足用户要求,提高内存利用率
基本原理:
- 用户程序划分:按段的逻辑关系进行划分
- 内存划分:按页面进行划分
- 内存分配:对用户程序的每个段,在内存中以页为单位进行分配
逻辑地址:
六、虚拟存储器
虚拟存储器:具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储器系统,其逻辑容量系统由内存容量和外存容量之和所决定(实际容量由地址寄存器决定),其运行速度接近于内存速度,而每位成本却又接近于外存
1、为什么引入虚拟存储器?
作业必须全部放入内存后方可运行
- 如果作业大于内存的容量
- 如果有大量作业要求运行,内存不足以容纳
从逻辑上对内存进行虚拟扩充,由硬件和操作系统合作实现虚拟扩充
2、常规存储管理方式
- 一次性装入
- 运行时的驻留性
3、程序局部性原理
- 时间局部性:指令被执行,在不久可能再被执行
- 空间局部性:存储单元被使用,一定时间内临近单元可能被使用
4、虚拟存储器的基本工作情况
- 只把部分程序放到内存中,其他部分留在外存
- 需要时在内存与外存之间动态对换
以处理机时间和外存空间来换取内存空间的资源转换技术
- 离散性:分页、分段
- 多次性:多次将部分调入
- 交换性:暂时不执行时允许换出,需要时换入
- 虚拟性:逻辑(虚)上扩充了内存物理(实)容量
5、虚拟存储器的实现方式
请求分页方式
请求分段方式
硬件支持:地址转换机制
- 请求分页(段)的页(段)表机构
- 缺页(段)中断处理机构
- 请求分页(段)的地址变换机构
OS:管理内存和外存间页面或段的请求调入和置换
七、请求分页存储管理
1、 请求分页的页表机制
- 状态位:表示该页是在内存还是在外存
- 访问位:记录该页在一段时间内被访问的次数
- 修改位:查看此页是否在内存中被修改过(如修改,必须将该页重写回外存上)
- 外存地址:该页在外存上的地址(通常是物理块号)
2、 缺页中断处理机构
缺页中断处理过程:页表状态位 -> 产生缺页中断 -> 操作系统 -> 缺页中断处理程序 -> 页表外存地址 -> 调入页面
和中断的区别:
- 缺页中断在指令执行期间产生和处理中断信号;
CPU则通常是在一条指令执行完后才去查看有无中断请求 - 一条指令在执行期间可能产生多次缺页中断
3、 地址变换机构
4、页面内存分配和调入策略
内存分配中OS提供的支持:
- 请求调页时,把所需的页从外存调入内存
- 置换时,将内存的某些页调至外存
内存分配问题:
- 进程正常运行所需的最少物理块?
与计算机硬件结构有关,主要取决于指令的格式、功能和寻址方式 - 每个进程分配的物理块数是固定的吗?
通常有固定分配局部置换、可变分配局部置换、可变分配全局置换 - 每个进程分配的物理块数依据是什么?
平均分配、按比例分配
页面调入
- 何时调入:预调入、请求调入
- 何时调入:对换区、文件区
- 怎样调入:内存满、内存空
5、影响缺页率的因素
- 分配给进程的物理块数
- 页面本身的大小
- 程序的编制方法
- 页面置换算法
八、页面置换算法
1、页面置换算法的概念
页面置换算法:请求分页存储管理系统中,当需要调入不在内存的页面,但内存已无空闲空间时,从内存选择换出页面的算法。置换算法的好坏直接影响到系统的性能
选择置换页的原则:好的页面置换算法应具有低的页面更换频率(缺页率)
2、常见页面置换算法
2.1 最佳置换算法(OPT)
淘汰永不使用或最长时间内不再被访问的页,无法实现,只能用它做评价标准
2.2 先进先出置换算法(FIFO)
- 简单易行
- 没有考虑访问频度的差别,不能保证经常访问的页不被淘汰
异常:抖动现象(刚淘汰的页面又要用到,重新装入内存要淘汰页面,被淘汰的页面又需要访问)
2.3 最近最久未使用置换算法(LRU)
淘汰在最近最久未使用的页面
硬件支持
- 特殊栈:保存当前在内存的各页面号,当前访问页号始终保持在栈顶,栈底就是最近最久未使用的页号
- 移位寄存器:为每个在内存的页面配置一个移位寄存器,当访问某页时将相应的寄存器最高位置1,每隔一段时间将寄存器右移1位
2.4 简单的CLOCK置换算法
(循环队列)每页设置一个访问位A,当某页被访问时,其访问位A被置为1
在循环检查时,如果页的访问位A=0,则淘汰该页,若A=1,则重新将A置为0,暂不换出而给该页第二次驻留内存的机会
3、Linux中虚拟内存的统计
vmstat用来展示虚拟内存的统计情况,如 vmstat 5 5 表示在5秒内进行5次采样
swpd:使用虚拟内存大小
free:可用内存大小
buff、cache:缓冲缓存的内存大小
swap:每秒从交换区写到内存的大小和写入交换区的内存大小
九、可执行文件与进程虚拟地址空间
1、Linux进程虚拟地址空间(x64)
实际的64位系统只使用了48位,可寻址的地址空间是2^48个字节,256TB,Linux使用其中的128TB作为内核空间,128TB作为用户空间,内核空间与用户空间之间是一个巨大的空洞(这部分区域不使用)
用户空间内存布局:保留区、代码段、数据段、BSS段、堆、内存映射段、栈
-
一个可执行程序包含二进制指令(映射到代码段中)和待处理的数据(映射到数据段和BSS段中),初始化过的变量保存在Data段上,没有初始化过的变量保存在BSS段上,Data段和BSS段都属于静态存储区,静态存储区用来保存全局变量和静态变量
-
堆中用来保存使用malloc申请分配的内存
-
内存映射段用来映射动态链接库的文件
-
栈中保存自动变量(如main中定义的局部变量,函数的调用关系等)
2、Linux实验
2.1 进程地址空间布局
每一行都对应着一个vma,用vm_area_struct结构体来描述,通过双链表进行组织,每一个vma都有起始地址、结束地址、权限(r:可读 x:可执行 w:可写 p:私有,不被共享)
未标出文件名和路径属于匿名映射
OS装载可执行文件时并不关心段中存放哪些内容,关心的是这些段的权限
Segment:段
Section(.data .bss等):节
readelf命令可查看节的信息
链接器在链接时会将相同属性的Section放在同一空间,合并为一个Segment
readelf命令可查看段(程序头)的信息
只有LOAD类型的Segment需要被映射,Segment里面包含着许多Section
2.2 ELF文件装载过程
OS装载时会将可执行文件中的Segment映射到虚拟地址空间中对应的VMA中
为什么没有BSS段的VMA?
在实际映射过程中,分配给Data段的内存空间大于自身的大小,多余的空间分配给BSS段,然后将BSS段全部填充为0,这样做的好处是并不需要为BSS段设立一个专门的Segment来映射,BSS段合并到Data段中映射
2.3 程序运行结果
ELF可执行文件中有3个Segment(可读可执行、只读、可读可写),需要关注的是4个Section(.text .rodata .data .bss),这四个Section与程序相关,其他部分在程序装载起辅助作用
- .text:存放程序可执行二进制代码
- .rodata:只读数据,存放被const修饰的全局变量
- .data:保存全局变量和静态变量,存放已初始化过的
- .BSS:保存全局变量和静态变量,存放未初始化过的
在实际运行过程中,.text .rodata .data .bss的地址空间是相对固定的,但堆空间和栈空间的地址是会变动的
- A:未初始化的全局变量,存放在.bss上
- B:初始化为0(BSS段会全部填充为0),效果与A(默认初始化为0)相同,存放在.bss上
- C:初始化非0的全局变量,存放在.data中
- D、E、F:static修饰,只是表示作用域是在本文件内,不影响内存分布情况(与A、B、C相同)
- G、H:const修饰,变量内容不能改变,分配在.rodata
- a、b、c:自动变量,分配在栈上
- d、e、f:static修饰,静态变量,静态存储区(与A、B、C相同)
- g:const修饰,动态变量,分配在栈上
- char1:局部字符数组,自动变量,分配在栈上
- *cptr:字符串指针,保存字符串地址,自动变量,分配在栈上(常量的存储是以ASCII码进行存储)
- *heap:malloc申请地址空间,分配在堆上