目录
前言:
第11章 操作系统
第4节 存储管理:寄存器+cache+内存+外存
4.1计算机存储层次模型
4.1.1 层次模型的内容
4.1.2 通用的思想和思路
4.1.3 内存抽象
4.1.3 多核并行架构
4.2 位于CPU内部的寄存器
4.3 位于SOC芯片内部的Cache (纯硬件、解决速率不匹配问题)
4.3.1 概述
4.3.2 为什么需要cache
4.3.3 多级cache存储结构
4.3.4 多级cache之间的配合工作
4.3.5 直接映射缓存(Direct mapped cache)
4.3.6 替换算法
4.3.7 更多信息
4.4 CPU指令的虚拟地址到物理内存地址的映射问题:MMU =》 多线程
4.4.1 概述
4.4.2 MMU的功能
4.4.3 MMU的优势与好处
4.4.4 虚拟地址到物理地址翻译过程
4.4.5 进程页表
4.4.6 关于TLB:页表缓冲区(Translation Lookaside Buffer)
4.4.6.1 TLB工作原理
4.4.6.2 页面置换算法
4.5 MMU + Cache协同工作
4.5.1 MMU+Cache
4.5.2MMU(Memory Management Unit)页表和页式存储管理的页表
4.6 文件系统:位于内存与外存之间的内存管理单元(代码文件到内存的映射)
引言:
基本思想:
4.6.1 页式存储管理
4.6.2 段式存储管理
(1) 概述
(2) 段式存储管理和页式存储管理比较
4.7 文件系统:文件在硬盘中的存储格式
4.8 总结
前言:
操作系统的本质就是创建一个并发的应用程序执行的环境,使得各种应用程序可以动态、共享相同的计算机物理硬件资源,计算机的三大物理资源包括:
CPU
内存
IO外设
应用程序(管理应用程序):应用程序以并发进程和线程的方式组织,所有的应用程序被抽象成了一个个的进程与线程;然后,有了进程间同步、互斥与通信、进程的优先级调度等概念。
所有的外设(管理外设资源):以统一的文件来组织,所有的应用程序通过文件的方式访问所有的外设,操作系统通过把文件映射成设备的驱动程序访问外设硬件。并以中断的手段提供异步抢占的方式临时占用计算机的资源。
所有的内存(管理内存资源):以虚拟地址的方式来组织,每个应用程序拥有0G~3G的用户地址空间和3G~4G的内核专有空间,于是了内存管理、MMU和地址映射等等概念。
内核调度程序(管理CPU资源):如何管理上述资源呢?于是就有了操作系统的调度程序!
本文就是探讨操作系统上述核心理念和原理!!!
第11章 操作系统
第4节 存储管理:寄存器+cache+内存+外存
4.1计算机存储层次模型
4.1.1 层次模型的内容
存储不仅仅包括内存,还包括寄存器、cache、外存。
4.1.2 通用的思想和思路
在上述层析模型中,寄存器离CPU的距离最近,外存离CPU最远。
离CPU越近,速度越高,容量越低,价格越高。
理CPU越远,容量越高,速度越低,价格越便宜。
在上述通用模型中, 本质是“一对多”映射,要解决如下问题:
(1)按照什么规则实现“一对多”映射 =》 算法
(2)按照什么方式实现“一对多”映射 =》 映射表
(3)命中直接获取 =》硬件
(4)不命中替换 =》 硬件
(5)N+1层数据替换N层数据时,需要使用一定的算法 =》算法。
通用处理过程
CPU执行指令
CPU通过寻址方式获取数据
如果数据在外存中,则通过内存地址获取数据
CPU自顶向下,按照某种地址映射规则,逐层访问数据下一层的数据。
地址映射规则,称为地址映射表,表的内容,可以由软件按照某种规则填写,也可以由硬件按照某种规则填写。
当数据地址空间在N层,称为命中,直接从N层获取数据
当数据不在N层时,称为不命中,从N+1层获取数据
然后用N+1层中的数据替换N层中的已有数据或空闲位置
由于N层中的容量小于N+1层中的容量,且部分内容已经被已有数据占用,因此,必须有某种替换算法,使用新的数据替换掉旧的数据。
4.1.3 内存抽象
为了更好的管理内存,操作系统将内存抽象成地址空间。
4.1.3 多核并行架构
4.2 位于CPU内部的寄存器
实际上,无论是指令还是数据,最终都会被加载到CPU内部的数据和指令寄存器中,并作为ALU运算单元的输入,这样CPU的指令才得以执行。
CPU内部的指令寄存器和数据寄存器非常少,指令寄存器就只有几个,取决于指令流水线的个数,数据寄存器也就是几十个。而程序员编写的高层代码,无论是指令的地址空间,还是数据的地址空间,高达几个G, 这么大的地址空间的程序和数据,如何复用有限的几个CPU内部寄存的呢???
这个艰巨的任务是由CPU内部的硬件控器+汇编语言+编译器+程序员共同完成的,程序员通过汇编语言指示计算机如何在内部寄存器和内存之间倒换数据,如load和save指令,或直接操作CPU内部的寄存器(只有汇编语言才具备操作CPU内部寄存器的能力)。然后经过汇编语言的编译、链接,把文本的汇编语言转换成CPU能够识别的二进制代码程序,CPU内部的硬件控制器负责取指令、解析指令、执行指令,在执行指令的过程中,通过Next指针、PC指针、跳转指令等源源不断地中外部存储器中获取指令,并执行指令,在每个执行的指令中完成大容量的内存空间与有限的CPU寄存器之间的映射,而这个映射是在汇编语言中完成的、实现的,因此,这个映射的职责,要么就是汇编语言的程序员,要么就是把C/C++等高层语言编译成汇编语言的编译器,而不是操作系统!!!!不需要操作系统本身参与。
4.3 位于SOC芯片内部的Cache (纯硬件、解决速率不匹配问题)
4.3.1 概述
汇编语言和程序员解决了大容量的内存空间与有限数量的CPU寄存器之间的映射,但他们解决不了一个问题:就是内存的访问速度远远低于CPU内部寄存器的访问速度,这导致CPU执行指令的效率极大地受到了内存访问速率的影响。如何提升CPU访问内存的速度呢?Cache技术因运而生。
Cache被安排在了CPU内部寄存器和内存之间,其CPU访问Cache速度接近CPU访问内部寄存器的速度,Cache的容量在CPU内部寄存器和内存之间,有几十K。这就带来一个新的问题:难道要让汇编语言程序员通过编码代码实现内存到Cache的数据搬移?然后再实现Cache到CPU内部寄存器的搬移?
如果这样,对汇编语言的改动有点大,对程序员的要求有点高,编程的复杂度也会极大的提升。如何在增加Cache的情况下,而不影响汇编语言的编程呢?或者说如何设计一个简易的机制,使得Cache对CPU汇编指令是透明的呢?
实际上,内存与Cache之间的地址映射和数据搬移,以及Cache到内部寄存器之间的映射与数据搬移,完成是通过硬件电路实现的,对于汇编语言完成是透明的,与汇编语言无关,更不需要操作系统的干预。
CPU的Cache机制,是如何实现该神奇的效果的呢??
4.3.2 为什么需要cache
在思考为什么需要cache之前,我们首先先来思考另一个问题:我们的程序是如何运行起来的?
我们应该知道程序是运行在 RAM之中,RAM 就是我们常说的DDR(例如: DDR3、DDR4等)。我们称之为main memory(主存)。当我们需要运行一个进程的时候,首先会从磁盘设备(例如,eMMC、UFS、SSD等)中将可执行程序load到主存中,然后开始执行。在CPU内部存在一堆的通用寄存器(register)。如果CPU需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:
CPU 从主存中读取地址A的数据到内部通用寄存器 x0(ARM64架构的通用寄存器之一)。
通用寄存器 x0 加1。
CPU 将通用寄存器 x0 的值写入主存。
我们将这个过程可以表示如下:
其实现实中,CPU通用寄存器的速度和主存之间存在着太大的差异。
两者之间的速度大致如下关系:
CPU register的速度一般小于1ns,主存的速度一般是65ns左右。速度差异近百倍。因此,上面举例的3个步骤中,步骤1和步骤3实际上速度很慢。当CPU试图从主存中load/store 操作时,由于主存的速度限制,CPU不得不等待这漫长的65ns时间。如果我们可以提升主存的速度,那么系统将会获得很大的性能提升。如今的DDR存储设备,动不动就是几个GB,容量很大。如果我们采用更快材料制作更快速度的主存,并且拥有几乎差不多的容量。其成本将会大幅度上升。我们试图提升主存的速度和容量,又期望其成本很低,这就有点难为人了。因此,我们有一种折中的方法,那就是制作一块速度极快但是容量极小的存储设备。那么其成本也不会太高。这块存储设备我们称之为cache memory。在硬件上,我们将cache放置在CPU和主存之间,作为主存数据的缓存。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。如果其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。当存在cache的时候,以上程序如何运行的例子的流程将会变成如下:
CPU和主存之间直接数据传输的方式转变成CPU和cache之间直接数据传输。cache负责和主存之间数据传输。
4.3.3 多级cache存储结构
cahe的速度在一定程度上同样影响着系统的性能。一般情况cache的速度可以达到1ns,几乎可以和CPU寄存器速度媲美。但是,这就满足人们对性能的追求了吗?并没有。当cache中没有缓存我们想要的数据的时候,依然需要漫长的等待从主存中load数据。为了进一步提升性能,引入多级cache。前面提到的cache,称之为L1 cache(第一级cache)。我们在L1 cache 后面连接L2 cache,在L2 cache 和主存之间连接L3 cache。等级越高,速度越慢,容量越大。但是速度相比较主存而言,依然很快。不同等级cache速度之间关系如下:
经过3级cache的缓冲,各级cache和主存之间的速度最萌差也逐级减小。在一个真实的系统上,各级cache之间硬件上是如何关联的呢?我们看下Cortex-A53架构上各级cache之间的硬件抽象框图如下:
在Cortex-A53架构上,L1 cache分为单独的instruction cache(ICache)和data cache(DCache)。L1 cache是CPU私有的,每个CPU都有一个L1 cache。一个cluster 内的所有CPU共享一个L2 cache,L2 cache不区分指令和数据,都可以缓存。所有cluster之间共享L3 cache。L3 cache通过总线和主存相连。
4.3.4 多级cache之间的配合工作
首先,引入两个名词概念,命中和缺失。
CPU要访问的数据在cache中有缓存,称为“命中” (hit),反之则称为“缺失” (miss)。
多级cache之间是如何配合工作的呢?我们假设现在考虑的系统只有两级cache。
当CPU试图从某地址load数据时,首先从L1 cache中查询是否命中,如果命中则把数据返回给CPU。如果L1 cache缺失,则继续从L2 cache中查找。当L2 cache命中时,数据会返回给L1 cache以及CPU。
如果L2 cache也缺失,很不幸,我们需要从主存中load数据,将数据返回给L2 cache、L1 cache及CPU。
这种多级cache的工作方式称之为inclusive cache。某一地址的数据可能存在多级缓存中。与inclusive cache对应的是exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一级。也就是说,任意地址的数据不可能同时在L1和L2 cache中缓存。
4.3.5 直接映射缓存(Direct mapped cache)
我们继续引入一些cache相关的名词。cache的大小称之为cache size,代表cache可以缓存最大数据的大小。我们将cache平均分成相等的很多块,每一个块大小称之为cache line,其大小是cache line size。例如一个64 Bytes大小的cache。如果我们将64 Bytes平均分成64块,那么cache line就是1字节,总共64行cache line。如果我们将64 Bytes平均分成8块,那么cache line就是8字节,总共8行cache line。现在的硬件设计中,一般cache line的大小是4-128 Byts。为什么没有1 byte呢?原因我们后面讨论。
这里有一点需要注意,cache line是cache和主存之间数据传输的最小单位。什么意思呢?当CPU试图load一个字节数据的时候,如果cache缺失,那么cache控制器会从主存中一次性的load cache line大小的数据到cache中。例如,cache line大小是8字节。CPU即使读取一个byte,在cache缺失后,cache会从主存中load 8字节填充整个cache line。又是因为什么呢?后面说完就懂了。
我们假设下面的讲解都是针对64 Bytes大小的cache,并且cache line大小是8字节。我们可以类似把这块cache想想成一个数组,数组总共8个元素,每个元素大小是8字节。就像下图这样。
现在我们考虑一个问题,CPU从0x0654地址读取一个字节,cache控制器是如何判断数据是否在cache中命中呢?cache大小相对于主存来说,可谓是小巫见大巫,因此cache与主存的关系是1对多的关系,一个cache地址存放多个不同的主存的地址。所以cache肯定是只能缓存主存中极小一部分数据。
我们如何根据地址在有限大小的cache中查找数据呢?
现在纯硬件(不需要软件设置映射表)采取的做法是对地址进行hash 散列(可以理解成地址取模操作)。
我们接下来看看是如何做到的?
我们一共有8行cache line,cache line大小是8 Bytes。所以
我们可以利用地址低3 bits(如上图地址蓝色部分)用来寻址8 bytes中某一字节,我们称这部分bit组合为offset。
同理,8行cache line,为了覆盖所有行。我们需要3 bits(如上图地址黄色部分)查找某一行,这部分地址部分称之为index。
现在我们知道,如果两个不同的地址,其地址的bit3-bit5如果完全一样的话,那么这两个地址经过硬件散列之后都会找到同一个cache line。所以,当我们找到cache line之后,只代表我们访问的地址对应的数据可能存在这个cache line中,但是也有可能是其他地址对应的数据。所以,我们又引入tag array区域,tag array和data array一一对应。每一个cache line都对应唯一一个tag,tag中保存的是整个地址位宽去除index和offset使用的bit剩余部分(如上图地址绿色部分)。
tag、index和offset三者组合就可以唯一确定一个地址了。因此,当我们根据地址中index位找到cache line后,取出当前cache line对应的tag,然后和地址中的tag进行比较,如果相等(异或值为0),这说明cache命中。如果不相等(异或为1),说明当前cache line存储的是其他地址的数据,这就是cache缺失。
在上述图中,我们看到tag的值是0x19,和地址中的tag部分相等,因此在本次访问会命中。由于tag的引入,因此解答了我们之前的一个疑问“为什么硬件cache line不做成一个字节?”。这样会导致硬件成本的上升,因为原本8个字节对应一个tag,现在需要8个tag,占用了很多内存。
tag也是cache的一部分,暂用cache空间的大小,需要专有的cache空间,虽然我们谈到cache size的时候并不考虑tag占用的cache空间大小。
我们可以从图中看到tag旁边还有一个valid bit,这个bit用来表示cache line中数据是否有效(例如:1代表有效;0代表无效)。当系统刚启动时,cache中的数据都应该是无效的,因为还没有缓存任何数据。cache控制器可以根据valid bit确认当前cache line数据是否有效。所以,上述比较tag确认cache line是否命中之前还会检查valid bit是否有效。只有在有效的情况下,比较tag才有意义。如果无效,直接判定cache缺失。
上面的例子中,cache size是64 Bytes并且cache line size是8 bytes。offset、index和tag分别使用3 bits、3 bits和42 bits(假设地址宽度是48 bits)。我们现在再看一个例子:512 Bytes cache size,64 Bytes cache line size。根据之前的地址划分方法,offset、index和tag分别使用6 bits、3 bits和39 bits。如下图所示。
需要强烈说明的是:Cache映射(命中与缺失两种情况),都是硬件完成的,Cache中的Tag映射表,也是由硬件在缺失情况下,自动填充的,并不需要软件的参与,因此cache的映射,完全对汇编程序是透明的,汇编程序并不需要关系在CPU寄存器和内存之间是否有cache的存在。汇编程序只需要在适当的地方打开或关闭cache即可。
直接映射缓存的优缺点:
优点:
直接映射缓存在硬件设计上会更加简单,因此成本上也会较低。
根据直接映射缓存的工作方式,我们可以画出主存地址0x00-0x88地址对应的cache分布图。
我们可以看到,
每行的大小为8个字节,整个cache为64个字节。
地址0x00-0x3f地址处对应的数据可以覆盖整个cache,
地址0x40-0x7f地址的数据也同样是覆盖整个cache,每行的大小为8个字节。
缺点:
我们现在思考一个问题,如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?
首先我们应该明白0x00、0x40、0x80地址中index部分是一样的,都是0x0。因此,这3个地址对应的cache line是同一个。所以,当我们访问0x00地址时,cache会缺失,然后数据会从主存中加载到cache中第0行cache line。当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中。同理,继续访问0x80地址,依然会cache缺失。这就相当于每次访问数据都要从主存中读取,所以cache的存在并没有对性能有什么提升。访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)。
针对这个问题,我们引入多路组相连缓存。
我们首先研究下最简单的两路组相连缓存的工作原理。
4.3.6 替换算法
cache在使用一段时间后,空间会满,新进来的数据需要替换一定的数据块。
常见的算法有:
Hash映射法:适用于直接映射缓存,按照固定地址映射算法进行替换。
先进先出算法FIFO(按第一次的时间排优先级、按资历淘汰):用一个计数器记录先进来的数据,新进来的需要替换时替换掉最先进来的数据,即计数大的数据。
最不经常使用算法LFU“Least Frequently Used”(按照命中次数排优先级、按做出贡献的大小淘汰):用计数器记录每个数据命中的次数(只有在命中时进行计数,命中++,命中的次数反应了频繁使用的次数),替换时替换命中次数最小的(淘汰贡献最小的)。
近期最少使用算法LRU“Least Recently Used”(按照没有命中的次数排优先级,按照没有做出贡献的次数淘汰):用一个计数器记录一定时间周期中一个数据没有命中的次数,如果命中计数器-1,没有命中+1,替换掉计数器大的数据。(无论命中还是没有命中,都进行计数,淘汰最近贡献最小的)。
随机算法:随机地选择一个单元来替换。
LFU 算法和 LRU 算法比较:
LFU 算法和 LRU 算法是两种常用的缓存淘汰策略,它们在缓存管理上有一些区别。
淘汰策略:LRU 算法基于最近使用的原则,淘汰最长时间没有被使用的数据。LFU 算法基于最不经常使用的原则,淘汰访问频率最低的数据。
数据访问特征:LRU 适用于数据访问具有时序特征的场景,即最近访问的数据在未来可能被再次访问。LFU 适用于数据访问频率有明显分布不均匀的场景,即某些数据被频繁访问,而其他数据很少被访问。
实现复杂度和性能:LRU 算法相对简单,只需维护一个按访问时间排序的链表即可。LFU 算法需要维护每个数据的访问频率计数器,可能会增加额外的计算开销。在实际性能方面,LRU 算法在大部分场景下表现良好,而 LFU 算法在特定分布不均匀情况下可能优于 LRU。
适用场景:LRU 算法适用于多数一般的缓存需求,可以有效地利用缓存空间。LFU 算法适用于对数据访问频率十分敏感的场景,可以保证热门数据被缓存,但可能需要更复杂的实现。
在选择使用 LRU 还是 LFU 算法时,需要根据具体的应用场景和需求来决定。如果对数据的访问频率分布比较均匀,且节省计算资源较为重要,可以选择 LRU 算法。如果对数据的访问频率不均匀且访问热度极不均衡,且能承受一定的额外计算成本,可以考虑 LFU 算法或其变体。
4.3.7 更多信息
参考:Cache的基本原理 – 知乎
4.4 CPU指令的虚拟地址到物理内存地址的映射问题:MMU =》 多线程
程序指令的虚拟地址连续,代码存在在物理内存的地址可以不连续,可以分页存储,支持程序在硬盘中的存放,支持按需加载到内存中。
4.4.1 概述
MMU(Memory Management Unit),即内存管理单元,是现代CPU架构中不可或缺的一部分。
MMU与Cache一样,位于CPU与内存之间.
MMU负责指令中的虚拟地址到物理内存的物理地址的转换,并提供硬件机制的内存访问权限检查。
虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
在 MMU 开启之前,CPU 都是通过物理地址来访问内存。程序必须连续存放在物理内存中。
在MMU 开启之后,CPU 都是通过虚拟地址来访问内存。
虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。
4.4.2 MMU的功能
虚实地址翻译:在用户访问内存时,将用户访问的虚拟地址翻译为实际的物理地址,以便CPU对实际的物理地址进行访问。
访问权限控制:可以对一些虚拟地址进行访问权限控制,以便于对用户程序的访问权限和范围进行管理,如代码段一般设置为只读,如果有用户程序对代码段进行写操作,系统会触发异常。
引申的物理内存管理:对系统的物理内存资源进行管理,为用户程序提供物理内存的申请、释放等操作接口:malloc与free
MMU是纯硬件的内存控制器,它的职责是完成CPU指令中的虚拟地址到内存总线的物理地址的映射的硬件逻辑 控制。
它的输入是:CPU指令中的虚拟地址。
它映射的依据是:TLB表。
它的输出是:内存的物理地址。
4.4.3 MMU的优势与好处
使用MMU带来的好处或者优势:
提升物理内存的利用率:
物理内存按需申请,如代码段的内存在执行时进行映射和转换,进程fork后,通过写时复制(Copy-On-Write)进行真正的物理内存分配。
解决内存管理碎片化的问题,即在系统运行一段时间后,频繁的内存申请和释放会导致内存碎片化,无法申请到一块足够大的地址连续的内存。
对内存地址的访问进行控制
如上述代码段只读权限控制,多线程的栈内存之间的空洞页隔离可以防止栈溢出后改写其他线程的栈内存,不同进程之间的地址隔离等等。
将进程的地址空间隔离
不同进程之间可以使用相同的虚拟内存地址空间,而进程间的物理内存又可以做到隔离,这保证了进程的独立性同时,又简化了地址的访问方式,如在早期32位CPU上,为了支持4G以上的物理内存,一般物理地址有36-bit(如PowerPC-604系列),但是用户的虚地址仍然使用32-bit,做法就是将用户的不同进程的32-bit虚地址在MMU转换时,转换为36-bit的物理地址,这样每个进程仍然能访问0-3G虚地址范围,将多个进程的3G空间映射到36-bit的物理内存空间中去。
4.4.4 虚拟地址到物理地址翻译过程
在上图中,如果MMU关闭,CPU指令地址,就是物理地址,而不是虚拟地址,比如uboot,就是没有使能MMU的单体、单线程程序,因此不需要使能MMU.
只有MMU使能,才会进行虚拟地址到物理地址的翻译,以支持多进程系统。
进程地址空间指的是操作系统为每个运行的进程分配的独立的、隔离的、虚拟地址空间。它是进程在内存中存储数据和执行代码的区域,进程的地址空间为进程中的所有线程共享。
进程的地址空间在一个进程中的所有线程之间是共享的。在同一个进程中的多个线程共享相同的内存地址空间。每个线程有自己的栈空间和栈指针,但它们都可以访问进程的全局变量、堆和代码段等共享的内存区域。
这种共享的地址空间使得线程之间的数据共享和通信变得更加方便。线程可以通过读写相同的内存位置来进行数据共享,避免了数据复制和额外的通信开销。但同时也需要注意线程之间的同步和互斥,以避免并发访问共享数据导致的竞态条件和数据不一致的问题。
以下是关于进程地址空间的基本信息:
虚拟地址空间:
- 虚拟地址空间是一个抽象的地址空间,每个进程都拥有自己的虚拟地址空间。
- 虚拟地址空间的大小可以根据操作系统和硬件的限制而有所不同,但通常它对每个进程来说是相对固定的。
地址空间分布:
- 进程地址空间通常可以分为几个不同的部分,如下所示:
- 代码段(Text Segment):存放进程中所有线程的可执行代码。
- 数据段(Data Segment):存放全局变量和静态变量。
- 堆(Heap):用于动态内存分配,如使用malloc和new等函数分配的内存。
- 栈(Stack):用于存储局部变量、函数调用信息和函数返回地址等。
- 进程地址空间通常可以分为几个不同的部分,如下所示:
地址空间的映射:
- 操作系统使用页表和内存管理单元(Memory Management Unit,MMU)将进程的虚拟地址转换为物理地址。
- 页表记录了虚拟地址与物理地址之间的映射关系,从而实现了虚拟地址到物理地址的转换。页表是由操作系统的进程管理模块和内存管理模块共同负责。
- 进程的程序在硬盘中是以文件的形式存放的,可以分批次导入到物理内存中,物理内存和硬件中文件的映射关系是存储管理的文件系统决定的,不是进程虚拟地址到物理地址的映射决定。
地址隔离和保护:
- 进程地址空间的主要作用是实现地址隔离和保护。
- 每个进程有自己的地址空间,进程无法直接访问其他进程的地址空间,从而确保了各个进程之间的内存隔离和安全性。
虚拟内存:
- 进程地址空间和物理内存之间的映射是通过虚拟内存系统来实现的。
- 虚拟内存系统将虚拟地址空间划分为固定大小的页面,并通过页面置换和页面调度策略将页面从磁盘交换到内存中。
进程地址空间的设计和组织可以在不同的操作系统中有所不同,但基本原理是相似的。
通过分配独立的地址空间,操作系统实现了进程之间的内存隔离,提高了系统的稳定性和安全性。
引申:
平行世界会不会就是进程?
而虫洞就是进程与进程之间的共享内存区?
操作系统就是宇宙的运行规律?
进程就是一个三维宇宙空间?
4.4.5 进程页表
例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
进程页表是操作系统中用于管理进程虚拟内存的数据结构之一。它记录了进程的虚拟地址空间中每个页面(通常以固定大小的页来划分)与物理内存页面之间的映射关系。
以下是关于进程页表的一些基本信息:
虚拟内存和物理内存:
- 虚拟内存是一个抽象的、与实际物理内存无关的地址空间,为每个进程提供了一个独立的地址范围。
- 物理内存是计算机实际的内存硬件,用来存储数据和程序。
页表的目的:
- 为了实现虚拟地址到物理地址的映射,操作系统使用页表来跟踪进程虚拟地址空间中每个页面与物理内存页面之间的映射关系。
- 进程页表是每个进程独有的,记录了进程的虚拟地址空间中每个页面对应的物理内存页面的起始地址。每个进程有自己独立的页表!!!
页表项(Page Table Entry):
- 页表项是进程页表中的条目,用于存储页面映射的相关信息,如虚拟地址、物理地址、访问权限、页面状态等。
- 每个页面在页表中对应一个页表项,页表项的大小和格式会根据操作系统的设计而有所不同。
多级页表:
- 为了有效组织和管理页表,以应对大型虚拟地址空间的情况,操作系统常采用多级页表的层次结构。
- 典型的多级页表结构包括页目录表、页表和页表页等。通过逐级查找和索引,可以快速定位和访问到所需的页表项。
页面置换和内存分配:
- 当进程需要访问的页面不在物理内存中时,会发生页面缺失(Page Fault),需要通过页面置换算法将其从磁盘装入内存。
- 进程页表还负责跟踪和管理进程所拥有的页面以及页面的状态,如页面是否在内存或者被交换到磁盘中。
通过进程页表,操作系统实现了虚拟内存的管理、地址转换和访问权限的控制,使得每个进程能够拥有独立、安全的地址空间,并且能够灵活地管理内存资源。
4.4.5.1页面映射表的内容
页面映射表(Page Mapping Table)是操作系统中用于管理虚拟内存和物理内存之间的映射关系的数据结构。它存储了虚拟内存页面和物理内存页面之间的对应关系。页面映射表的内容通常如下:
虚拟页号(Virtual Page Number):每个条目记录了虚拟内存中的页号。虚拟页号是虚拟内存地址经过分页机制后的索引。
物理页号(Physical Page Number):对应虚拟页号的物理内存中的页号。物理页号表示页面在物理内存中的位置。
页面状态位(Page Status):表示被映射的页面当前的状态,例如是否在物理内存中、是否被修改等。用于表示是否命中,1表示命中,0表示未命中。
控制位(Control Bits):包括一系列附加的控制位,用于管理和操作页面的状态,如访问位(表示是否被访问过)、修改位(表示是否被修改过)、有效位(表示该条目是否有效)等。
其他元数据:还可能包含其他与页面映射相关的元数据,如权限信息、缓存策略等。
页面映射表的作用是将虚拟内存地址和物理内存地址联系起来,使得程序可以使用虚拟内存进行地址访问,而不需要关心具体的物理内存位置。操作系统通过查询页面映射表来确定页面的映射关系,如果页面未在物理内存中,则触发页面调度机制将其加载到物理内存中。
需要注意的是,页面映射表通常是针对每个进程独立维护的,每个进程都有自己的页面映射表。这样可以保护进程的虚拟地址空间和物理地址空间的隔离,提高系统的安全性和稳定性。
总之,页面映射表是操作系统中用于管理虚拟内存和物理内存的映射关系的数据结构。它记录了虚拟页号与物理页号之间的对应关系,并存储了相关的页面状态和控制信息。通过页面映射表,操作系统可以实现虚拟内存的透明访问和页面调度。
4.4.5.2页面映射表的替换算法与控制位的关系
页面映射表中的替换算法与控制位密切相关,控制位的状态可以用于辅助替换算法的决策。
下面是替换算法和控制位之间的关系:
最近使用位/访问位(Recently Used or Reference Bit):记录一个页面是否最近被访问过。这个位可以通过硬件机制自动设置,每当有对应页表条目的访问时,这个位就会被设置为 1,操作系统根据这个位判断页面的历史使用情况。例如,最近未使用的页面可能成为被替换的候选。页面替换算法优先替换掉使用位/访问位为0的项,为0表示,最近没有人使用。
修改位(Dirty Bit):表示一个页面是否被修改过。当对页面内容进行写入操作时,这个位会被设置为 1,表示该页已经被修改。修改位的设置可以帮助操作系统决定是否需要将页面写回到磁盘中,以确保页面的一致性。同时,页面替换算法优先替换掉“修改位”为0的项,避免导致页面不一致。
在替换算法中,一般有多种选择的候选页面来进行替换。控制位的使用可以帮助操作系统判断哪些页面是最合适的替换候选。常见的替换算法和控制位的关系如下:
最近未使用算法(Least Recently Used, LRU):该算法根据最近使用位判断页面的使用情况,最近较长时间内未使用的页面可能成为替换的候选。
先进先出算法(First-In-First-Out, FIFO):该算法通常不使用控制位,只根据页面进入内存的时间顺序来进行页面替换。
时钟算法(Clock):该算法使用最近使用位作为页面是否最近被访问的依据。通过维护一个类似于时钟指针的数据结构,按照页面访问情况逐个检查页面的最近使用位,并将其中最早未使用的页面作为替换的候选。
第二次机会算法(Second-Chance):该算法使用最近使用位和修改位两个控制位。它给予被访问过的页面第二次机会,如果页面的最近使用位为 1,则将其置为 0,并继续检查下一个页面;如果最近使用位为 0,则检查修改位,如果修改位为 1,则认为该页面较重要,将其置为 0 并继续检查下一个页面;如果修改位也为 0,则认为该页面是替换的候选。
综上所述,控制位的状态可以提供额外的页面信息,辅助替换算法进行页面的选择和决策。根据控制位的状态,替换算法可以更加智能地选择合适的页面进行替换。
4.4.6 关于TLB:页表缓冲区(Translation Lookaside Buffer)
TLB(Translation Lookaside Buffer)是一种高速缓存,用于加速虚拟地址到物理地址的转换过程,常规的进程页表存放在内存中,通过内存中的页表获取指令和数据在物理内存中的实际存储位置。TLB通常是CPU内部的一部分,位于内存管理单元(Memory Management Unit,MMU)中。
以下是关于TLB的一些基本信息:
地址翻译过程:
- 当CPU执行指令时,虚拟地址需要转换为物理地址才能在内存中访问相应的数据。这个转换过程由MMU负责。
- MMU通过查找页表来找到虚拟地址与物理地址之间的映射关系,然后将转换得到的物理地址提供给内存系统。
TLB的作用:
- TLB是一个高速缓存,用于存储最近的虚拟地址到物理地址的转换结果。
- 当CPU发出访问请求时,MMU首先在TLB中进行查找,如果找到了对应的转换结果,就可以直接返回物理地址,避免了频繁地访问页表。
TLB的结构:
- TLB是一个关联存储器(associative memory),允许并行地查找多个存储单元。
- 它通常由多个缓存行组成,每个缓存行存储一个虚拟地址到物理地址的映射项(TLB entry)。
TLB命中和TLB缺失:
- 当CPU请求一个虚拟地址时,MMU首先在TLB中进行查找。如果找到了对应的虚拟地址,就称为TLB命中(TLB hit),可以直接返回物理地址。
- 如果在TLB中没有找到对应的虚拟地址,就称为TLB缺失(TLB miss),此时需要从页表中进行转换并将结果存入TLB以供之后的访问。
TLB的大小和性能:
- TLB的大小可以根据硬件设计而有所不同,有些CPU可能只有几个缓存行,而其他高端CPU可能由数百个甚至上千个缓存行组成。
- TLB的大小和命中率直接影响了地址转换的速度。较大的TLB可以容纳更多的虚拟地址到物理地址的映射项,提高了命中率和性能。
TLB在虚拟内存系统中起着重要的作用,通过减少对页表的访问次数加速了地址翻译过程,提高了系统的性能和效率。
4.4.6.1 TLB工作原理
快表,直译为旁路快表缓冲,也可以理解为页表缓冲,地址变换高速缓存。
TLB位于SOC芯片内部的专有的高速缓存中。
而页表存放在SOC芯片外部的主存中,因此程序每次访存至少需要两次:一次访存获取数据的物理地址,第二次进行地址映射,访问物理地址才获得数据。
提高访存性能的关键在于依靠页表的访问局部性。当一个转换的虚拟页号被使用时,它可能在不久的将来再次被使用到。
TLB内核本身是一种高速缓存,内存管理硬件使用它来改善虚拟地址到物理地址的转换速度。当前所有的个人桌面,笔记本和服务器处理器都使用TLB来进行虚拟地址到物理地址的映射。
使用TLB内核可以快速的找到虚拟地址指向物理地址,而不需要请求RAM内存获取虚拟地址到物理地址的映射关系。这与data cache和instruction caches有很大的相似之处。
当cpu要访问一个虚拟地址/线性地址时,CPU会首先根据虚拟地址的高20位(20是x86特定的,不同架构有不同的值)在TLB中查找。如果是表中没有相应的表项,称为TLB miss,需要通过访问慢速RAM中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB表项中,以后对同一线性地址的访问,直接从TLB表项中获取物理地址即可,称为TLB hit。
TLB表项
高速缓冲TLB内核内部存放的基本单位是页表条目,对应着RAM中存放的页表条目。页表条目的大小固定不变的,所以TLB容量越大,所能存放的页表条目越多,TLB hit的几率也越大。但是TLB容量毕竟是有限的,因此RAM页表和TLB页表条目无法做到一一对应。因此CPU收到一个线性地址,那么必须快速做两个判断:
1 所需的也表示否已经缓存在TLB内部(TLB miss或者TLB hit)
2 所需的页表在TLB的哪个条目内
为了尽量减少CPU做出这些判断所需的时间,那么就必须在TLB页表条目和内存页表条目之间的对应方式做足功夫
备注:
RAM页表中的内容是有操作系统的软件维护的,包括新建、增加、删除等操作。
TLB高速缓存的内容是由硬件完成的,并非有操作系统维护。
4.4.6.2 页面置换算法
在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。
页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。
页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。
1. 最佳:OPT, Optimal replacement algorithm
所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。
是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。
举例:一个系统为某进程分配了三个物理块,并有如下页面引用序列:
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
开始运行时,先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。
缺页中断:硬件复杂替换。
2. 最近最久未使用:LRU, Least Recently Used
虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。
因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
4,7,0,7,1,0,1,2,1,2,6
3. 最近未使用:NRU, Not Recently Used
每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:
R=0,M=0
R=0,M=1
R=1,M=0
R=1,M=1
当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。
NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。
4. 先进先出:FIFO, First In First Out
选择换出的页面是最先进入的页面。
该算法会将那些经常被访问的页面换出,导致缺页率升高。
5. 第二次机会算法
FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。
6. 时钟
Clock 第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。
4.5 MMU + Cache协同工作
4.5.1 MMU+Cache
Caches:位于MMU和物理内存之间。
4.5.2MMU(Memory Management Unit)页表和页式存储管理的页表
MMU(Memory Management Unit)页表和页式存储管理的页表有一些区别。
下面是它们之间的主要区别:
作用范围:
- MMU页表:MMU页表是在硬件层面上实现的,它负责将程序的虚拟地址转换为物理地址,并参与内存访问的过程。MMU页表是处理器内部的一个组件,用于实现虚拟内存的管理。
- 页式存储管理的页表:页式存储管理的页表是操作系统的一部分,它用于记录进程的虚拟地址空间中每个页面对应的物理地址的映射关系。页表是在操作系统层面实现的数据结构。
实现方式:
- MMU页表:MMU页表是由硬件实现的,通常作为处理器中的一个组件,用于存储和转换虚拟地址和物理地址的映射关系。它使用硬件机制实现快速的地址转换。
- 页式存储管理的页表:页式存储管理的页表是由操作系统管理的数据结构,存储在主存中。当进程切换或发生页面调度时,操作系统会根据需要更新页表。
粒度级别:
- MMU页表:MMU页表将虚拟地址和物理地址映射为较小的地址单元,例如,将虚拟页面映射到物理页面或者某一字节的物理地址。
- 页式存储管理的页表:页式存储管理的页表以页面为单位进行映射,通常是以固定大小的页作为最小的映射单位,例如,4KB或者8KB的页面。
尽管MMU页表和页式存储管理的页表在实现方式和作用范围上有所区别,但它们都是用于地址转换和管理虚拟内存的重要组成部分。通过MMU页表和页式存储管理的页表,计算机可以实现虚拟内存的管理和地址转换,为每个进程提供独立的地址空间和隔离的执行环境。
MMU(Memory Management Unit)页表和页式存储管理的页表有以下相同点和不同点:
相同点:
作用:两者都用于实现虚拟内存的管理和地址转换。它们都参与将进程的虚拟地址转换为物理地址的过程。
存储结构:MMU页表和页式存储管理的页表都是用于维护虚拟地址和物理地址之间的映射关系的数据结构。
分页机制:两者都使用分页机制将虚拟地址空间划分为固定大小的页面,并将物理内存划分为相同大小的物理页面。
地址转换:MMU页表和页式存储管理的页表都通过查找页表项实现虚拟地址到物理地址的转换。
不同点:
层次结构:MMU页表通常是多级的层次结构。例如,x86架构使用两级的页表,其中更高的级数用于访问更大的内存范围。而页式存储管理的页表通常是一级的结构。
存储位置:MMU页表存储在MMU或处理器内部,作为硬件的一部分。而页式存储管理的页表存储在操作系统管理的主存中。
更新:MMU页表通常由硬件自动管理,随着地址转换的发生自动更新。而页式存储管理的页表由操作系统负责管理和更新,例如在进程切换或页面置换时更新页表。
粒度级别:MMU页表可以实现更细粒度的地址转换,例如将虚拟页面映射到物理页面或者某一字节的物理地址。而页式存储管理的页表通常以固定大小的页面为单位进行映射,例如4KB或8KB大小的页面。
尽管MMU页表和页式存储管理的页表在实现方式、存储位置和更新方式等方面存在一些差异,但它们都是用于实现虚拟内存管理的重要组成部分,共同完成将虚拟地址转换为物理地址的任务,并提供地址空间的隔离和内存管理的功能。
4.6 文件系统:位于内存与外存之间的内存管理单元(代码文件到内存的映射)
引言:
对于单体、多进程的程序,所有的程序都一次性加载到物理内存空间中,比如VxWorks操作系统的可执行程序。然而,在Windows和Linux操作系统中,在文件系统中存在无数个可执行文件,这些可执行文件可以动态创建、执行,同时执行的可执行程序的总的代码长度往往超过物理内存的大小,甚至单个可执行程序的代码空间的大小就可以大于物理内存的大小。
操作系统如何做到这一点的呢?
比如,在实际系统中,内存的容量通常几个兆的,而硬盘高达几十个G,而硬盘终端可执行程序,其长度可以高达几个G, 甚至超过物理内存的大小。在内存中执行的可执行程序的总和可以远远超过物理内存的大小。
基本思想:
直观上理解:在Windows和Linux操作系统的可执行程序,在执行时,并不是一次性把自己的代码全部加载到物理内存空间中的,而是执行一段代码,加载到内存一段代码,避免没有执行的代码无端地占用紧张的物理内存空间大小,确保能够有更多的进程可以并发执行。
如果不支持上述的能力,这就要求所有并发执行的程序,必须一次性、全部加载到物理内存中,这就带来几个麻烦:
某个时刻,某个应用程序的大部分代码都的不到执行,无端地占用了紧张的物理内存的空间。
同时,并发执行的程序的数量极大的受到了程序大小的影响。比如物理内存16M, 可执行程序的大小8M, 10M, 则该系统最多只能执行一个程序,无法并发执行着两个程序,因为物理内存容不小同时加载这两个程序的代码。
一种可能的解决办法是:根据程序的局部性原理,每个程序在执行时,只加载执行部分的程序,比如1K, 其他程序亦然留在硬盘中,等需要执行的时候在加载到内存空间中。这样16M的内存,就可以同时执行16M/1K = 16000个程序。这种思想,称为页式存储管理和段式存储管理。 这就是本节要讨论的存储管理技术!!!
即运行可执行程序按需加载到内存中执行,而大部分暂时不执行的程序存放在硬盘中,等需要执行的时候再加载到内存中执行,这种技术充分利用了程序的空间局部性和时间局部性原理!!!
4.6.1 页式存储管理
页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间和物理磁盘都划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。
页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。
逻辑地址:CPU所生成的地址。CPU产生的逻辑地址被分为 :p (页号) 它包含每个页在物理内存中的基址,用来作为页表的索引;(页偏移),同基址相结合,用来确定送入内存设备的物理内存地址。
物理地址:内存单元所看到的地址。逻辑地址空间为2^m,且页大小为2^n,那么逻辑地址的高m-n位表示页号,低n位表示页偏移。
逻辑地址空间:由程序所生成的所有逻辑地址的集合。
物理地址空间:与逻辑地址相对应的内存中所有物理地址的集合,用户程序看不见真正的物理地址。
4.6.2 段式存储管理
(1) 概述
段式管理(segmentation),是指把一个程序分成若干个不等长的段(segment)进行存储,每个段的长度称为段长,每个段都是一个逻辑实体(logical entity),程序员需要知道并使用它。它的产生是与程序的模块化直接有关的。不同的模块,放到不到的段中,而页表是根据固定长度的页长来划分程序空间的。
段式管理是通过段表进行的,它包括段号或段名、段起点、装入位、段的长度等。此外还需要主存占用区域表、主存可用区域表。
为了进行段式管理,每道程序在系统中都有一个段(映象)表来存放该道程序各段装入主存的状况信息。段表中的每一项(对应表中的每一行)描述该道程序一个段的基本状况,由若干个字段提供。段名字段用于存放段的名称,段名一般是有其逻辑意义的,也可以转换成用段号指明。由于段号从0开始顺序编号,正好与段表中的行号对应,如2段必是段表中的第3行,这样,段表中就可不设段号(名)字段。装入位字段用来指示该段是否已经调入主存,“1”表示已装入,“0”表示未装入。在程序的执行过程中,各段的装入位随该段是否活跃而动态变化。当装入位为“1”时,地址字段用于表示该段装入主存中起始(绝对)地址,当装入位为“0”时,则无效(有时机器用它表示该段在辅存中的起始地址)。段长字段指明该段的大小,一般以字数或字节数为单位,取决于所用的编址方式。段长字段是用来判断所访问的地址是否越出段界的界限保护检查用的。访问方式字段用来标记该段允许的访问方式,如只读、可写、只能执行等,以提供段的访问方式保护。除此之外,段表中还可以根据需要设置其它的字段。段表本身也是一个段,一般常驻在主存中,也可以存在辅存中,需要时再调入主存。假设系统在主存中最多可同时有N道程序,可设N个段表基址寄存器。对应于每道程序,由基号(程序号)指明使用哪个段表基址寄存器。段表基址寄存器中的段表基址字段指向该道程序的段表在主存中的起始地址。段表长度字段指明该道程序所用段表的行数,即程序的段数。
(2) 段式存储管理和页式存储管理比较
段式存储管理和页式存储管理有以下相同和不同之处,进行比较如下:
相同点:
虚拟内存管理:两者都是为了实现虚拟内存管理而设计的方法。它们都能够将进程的虚拟地址空间映射到物理内存,并提供内存隔离和保护的功能。
内存管理单位:段式存储管理和页式存储管理都将内存划分为分散的部分。
访问权限控制:两种管理方式都提供了权限控制机制,以确保进程仅能访问其授权的段或页。
分段以及分页:两者都基于分段和分页机制,通过分段或分页表维护虚拟地址和物理地址之间的映射关系。
不同点:
存储管理单位:
- 段式存储管理:内存被划分为不同的逻辑段,每个段表示一个具有独立意义的逻辑单位,例如代码段、数据段等。每个段具有不同的长度。
- 页式存储管理:内存被划分为固定大小的页面,通常为几KB或几MB大小。所有页面的大小相同,不区分逻辑单位。
碎片问题:
- 段式存储管理:由于段的长度不一致且不连续,容易产生外部碎片,导致内存空间的浪费。
- 页式存储管理:页面大小固定,避免了外部碎片问题,但可能会产生内部碎片,即页面内部未被完全利用。
地址转换:
- 段式存储管理:需要进行两次地址转换,首先将虚拟地址转换为段内的偏移量,然后再将段内偏移量转换为物理地址。
- 页式存储管理:只需要进行一次地址转换,将虚拟地址转换为页号和页内偏移量,然后将页号转换为物理地址。
内存分配方式:
- 段式存储管理:内存分配(包括代码或数据段)以段为单位进行,适合于动态增长的数据结构或具有不确定大小的逻辑单位。
- 页式存储管理:内存分配(包括代码或数据段)以页为单位进行,适用于固定大小的数据块或需要高效访问连续内存的场景。
综上所述,段式存储管理和页式存储管理在内存管理单位、碎片问题、地址转换和内存分配方式上存在差异。选择使用哪种内存管理方式取决于应用程序的特性和需求,以及对内存利用率、访存性能和内存管理复杂度的权衡。
4.7 文件系统:文件在硬盘中的存储格式
文件系统是计算机用于组织和管理文件的一种机制,它定义了文件在硬盘中的存储格式。
常见的文件系统类型包括FAT(File Allocation Table)、NTFS(New Technology File System)、ext4等。每个文件系统都有自己的文件存储格式和相关的优化策略,以满足不同的需求和目标。
具体的文件存储格式可能因不同的文件系统而异,以下是一般的文件存储格式的概述:
文件分配类型:
- 文件系统通常使用不同的分配方式来存储文件的内容。常见的分配方式包括顺序分配、链接分配和索引分配。
顺序分配:
- 在顺序分配中,文件的内容按照顺序存储在磁盘上的连续扇区中。
- 文件系统维护一个指针来记录文件的起始扇区位置,通过顺序读取扇区来访问文件的内容。
链接分配:
- 在链接分配中,文件的内容被分割成不同大小的磁盘块,这些块可以在磁盘上的任何位置分散存储。
- 每个块中都包含了指向下一个块的指针,通过这些指针来链接所有的块以恢复文件的完整内容。
索引分配:
- 在索引分配中,文件系统使用一个专门的索引块来存储文件的索引信息。
- 索引块中包含了指向文件内容存储位置的指针。通过索引块的请求来访问文件内容。
文件属性和元数据:
- 文件系统还会为每个文件维护一些元数据,如文件名、大小、权限、创建时间等。
- 这些元数据通常以特殊的数据结构或附加的存储区域的形式存储在磁盘上。
需要注意的是,不同的文件系统可能会有不同的实现细节和特性。
4.8 总结
计算机存储体系结构指的是计算机中用于存储和访问数据的各个层次和组件的组织方式。它涵盖了从高速缓存到主存和辅助存储器等不同层次的存储设备,以及它们之间的数据传输和访问方式。以下是常见的计算机存储体系结构:
CPU内部的寄存器:
- 寄存器是位于CPU内部的最快速、容量最小的存储设备。它们用于存储指令执行过程中的数据、地址和控制信息。
- CPU寄存器的复用与Cache内存的映射:由汇编程序的编译器完成。
- MMU:实现指令中的虚拟地址到内存物理地址的映射,映射关系通过内存中的页表实现。MMU有硬件电路直接实现地址映射!!!
- TLB Cache:为了加快硬件的地址硬件,在CPU内部增加了一段cache,专门用于存放部分的虚拟地址到物理地址的映射,TLB的硬件是Cache,内存是专门存放虚拟地址到物理地址的页表,称为地址映射表Cache,这种专有存放页表的Cache,给他取个名字,称为TLB表。
高速缓存Cache:为了加快物理地址内存访问的速度,因此处于MMU和物理内存之间。
- 高速缓存是位于CPU和主存之间的快速存储器,用于提高存储器访问速度。
- 它根据局部性原理将最近被访问的数据和指令复制到缓存中,以便CPU可以更快地获取。
- 进一步分类:指令Cache + 数据Cache (进程页表Cache放在专门的TLB cache中)
主存(内存)DRAM:
- 主存是计算机中常用的存储设备,用于保存:程序指令+数据
- 主存还用于存放:每个进程独立的PCB块 + 虚拟地址到物理地址的页表
- 主存还用于存放:物理内存到硬盘的页表+段表
- 主存通过内存控制器与CPU进行数据传输,是CPU可以直接访问的存储器
辅助存储器:用于永久存储代码和数据,并通过文件系统, 如FAT, NFS等文件系统。
- 辅助存储器用于存放:文件系统本身:目录结构 + 文件名
- 辅助存储器用于存放:按分散的方式存放文件内容本身
- 文件:程序文件 + 数据文件 + 目录文件
- 辅助存储器包括磁盘、光盘和固态硬盘等用于长期存储数据和程序的设备。
- 辅助存储器与主存之间的数据传输速度通常较慢,但容量较大,可以保存大量的数据。
存储器层次结构:
- 存储器层次结构指的是通过不同存储介质和访问速度的存储器层次来提供访问速度和容量的折中。
- 存储器层次结构是由多级缓存、主存和辅助存储器组成的,每一级存储器速度逐级递减,容量逐级递增。
存储器总线和控制器:
- 存储器总线是连接CPU、主存和辅助存储器之间的数据通路,用于传输地址和数据信号。
- 存储器控制器负责管理存储器模块以及数据在存储器层次结构中的传输和访问。
计算机存储体系结构旨在根据不同存储设备的特点和性能来优化数据访问的效率和容量。通过合理设计存储层次结构和使用缓存技术,可以提高计算机的存取速度、运行效率和用户体验。
附录:内存地址的十六进制表示
下面是一些常见的地址总线长度如下(以位为单位),以及对应的内存地址范围和十六进制表示形式:
1K(10位地址总线长度):
- 内存地址范围:0x0000 – 0x03FF
- 十六进制表示:0x000 – 0x3FF
4K(12位地址总线长度)://最常见的页表的长度
- 内存地址范围:0x0000 – 0x0FFF
- 十六进制表示:0x000 – 0xFFF
1M(20位地址总线长度):
- 内存地址范围:0x000000 – 0x0FFFFF
- 十六进制表示: 0x000000 – 0x0FFFFF
4M(22位地址总线长度):
- 内存地址范围:0x000000 – 0x3FFFFF
- 十六进制表示:0x000000 – 0x3FFFFF
1G(30位地址总线长度):
- 内存地址范围:0x00000000 – 0x3FFFFFFF
- 十六进制表示:0x00000000 – 0x3FFFFFFF
4G(32位地址总线长度): //32位总线的最大地址空间
- 内存地址范围:0x00000000 – 0xFFFFFFFF
- 十六进制表示:0x00000000 – 0xFFFFFFFF
这些地址范围和十六进制表示形式是根据地址总线长度计算而来,每个地址位可以表示二进制中的一个位。根据不同的地址总线长度,内存地址范围和十六进制表示形式会有所变化。