OS 内存小结

常用数量单位:

1 bit = 1 位 = 1 比特(即 0 或 1)

1 byte = 8 bit = 1 字节

一般而言 1 位采用的符号为 b (小写) 、1 字节采取的符号为 B (大写)

计算机的底层采用的是字节存储,也就是 8 位 (类似:0000 0101 )

它是计算机中可寻址的最小内存单元。这意味着CPU在访问内存时,不是一个个地去读/写单个的位,而是以字节为单位进行操作的。

2^10 = 1024 B = 1 KB

2^20 = 1 MB

2^30 = 1 GB

什么是内存

计算机的硬件可以区分为如下图

由于 CPU 的运行速度很快,而外存的传输速度很慢,所以需要先把数据放置到内存再被 CPU 处理

外存是持久化必不可少的物理硬件,内存中只能临时存放数据

如果一个内存能存储 4 G 的数据则代表它能存储 4*2^30 字节

即如下图 4 G 的内存有 4 * 2 的 30 次方个格子,一个格子中是一个字节

{ 注意!在 4 G 的内存的前提下,内存存储应该为 0 ~ 2^32-1 字节,也就是说,一块内存中真实存储的应该是类似于 【00000000 00000000 00000000 00000000 】这样子的数据 }

指令工作的原理

内存会被划分为多个区域,某个区域会存储需要执行的指令,有的区域将会存储需要使用的数据,以下图举例,CPU 会读取 【0001 0101】或许它的作用是相加两个数,接下来就会把【1000 0001】和【1000 0010】一起读入 CPU 然后进行相加操作(我这里省略了很多步骤,具体操作可以自行了解)

注意,这里的每个字节代表的意思是操作或数据在内存中的地址值

{ 注意!在 4 G 的内存的前提下,内存存储应该为 0 ~ 2^32-1 字节,也就是说,一块内存中真实存储的应该是类似于 【00000000 00000000 00000000 00000000 】这样子的数据 }

物理地址和逻辑地址

可以简单来说 相当于数组在内存眼里, 和数组在你眼里

数组在你眼里是连续的,从下标为 0 开始向后逐一增加

在内存眼里并不是从内存的 0 地址开始写的

在这里,你看到的就是逻辑地址,而内存条中的才是物理地址

动态运行时装入

重定位寄存器 是想要使逻辑地址转变为物理地址的必要硬件

操作系统将一个程序加载到内存会创建一个进程,进程中增加其对应的起始物理地址,CPU 想要调用某个进程时,会从 PCB 中读取对应的起始物理地址放置在重定位寄存器中

CPU 会在动态地将指令的逻辑地址转变为物理地址然后再进行操作

比如你往数组中写入数据,那么会在运行中找到对应的物理地址修改数据

进程和对应程序的关系

进程是程序的一次执行过程,是系统进行资源分配和调度的独立单位。

内存的保护

使用重定位寄存器和界地址寄存器判断进程在内存中的访问是否有越界行为

如:

某个进程的起始物理地址为 100 ( 十进制 ) 逻辑地址空间为 0 ~ 179 ( 进程占内存大小 180 字节)那么只要二者相加就产生了上下限( 100 ~ 279 ),进程只能在此上下限中运行

进程在内存的映像 - 地址为逻辑地址

内存空间的分配和回收 - 分页存储管理

注:页表的作用是为了管理虚拟内存空间

虚拟内存空间就是:每个进程认为自己可以使用的内存地址范围,由CPU的架构决定

分页存储管理使用增加分页的逻辑来防止外部碎片的产生,是典型的时间换空间

页和页框分别是什么:

页表

OS 会给每个进程建立一张页表(我的块号内容是乱写的)

与重定位寄存器相似的,进程未被执行时,页表的起始地址和页表长度会被存储在 PCB 中, 当进程被调度时,页表也会被存储在页表寄存器(PTR)中,内部会存放页表在内存中的起始地址和页表长度

想要找到页表,只需要找到进程对应的起始物理地址就可以了

在存在页表的情况下,CPU 只需要找到对应进程的页表,找到页号对应的块号,到达块号指向的内存块号

对应的页框的起始物理地址为 块号 * 4K + 页号 * 4K(页内偏移量)

需要增加偏移量是因为页之间逻辑上是连续的,物理上是离散的

例如下图,

进程 A_0 对应的页内逻辑地址是 0

页内偏移量是 0

块号是 2

那么进程 A_0 的实际物理地址是 2 * 4K + 0

进程 A_2 对应的页内逻辑地址是 2

页内偏移量是 2 * 4K

块号是 1

那么进程 A_2 的实际物理地址是 1 * 4K + 2 * 4K

页表需要占用的大小
块号

4 G 内存中会分出 2^32/2^12 = 2^20个内存块

4 G 内存中的页表需要使用 32 位来表达,其中 20 位是内存块的块号,12位是一个内存块内的偏移量

例如:

我需要执行在第 6 个内存块上的第一个步骤

00000000 00000000 01010000 00000000

黄色背景表示的是内存块

绿色背景表示的是块内偏移量

页号

由于页表是顺序存储的,页号是隐含的,所以页号理论上不占用内存空间

快表(TLB)

是 CPU 中的高速缓存,访问速度比内存更快

用来存储最近访问页表项的副本

快表的意义

更快,使用空间换时间了

例子:

访问 TLB 只需要 1us

访问 内存 需要 100us

假设 TLB 的缓存命中率为 90%

访问一个逻辑地址的平均耗时为

二级页表

页表是将进程逻辑地址和物理地址的寻址表,获取了页表中的块号,就能够找到内存中对应的页框,但页表的本身也需要存储,虽然 CPU 中拥有 页表寄存器(PTR)但是它只记录当前占用 CPU 的进程的页表位置,页表本身还是需要去内存上找的

而二级页表,顾名思义就是存储页表的页表

一级页表的缺陷

我们知道,进程都有一个对应的页表,而同一时间 CPU 可能会执行多个进程(RR),如果需要页表时去磁盘寻找,那太耗费时间了,而且也不方便页表的寻址,程序运行后,进程中会存入对应页表的地址,如果将 CPU 暂时不调用的进程对应的页表放回磁盘,那么在下一次调用时就需要耗费更多的时间,CPU 需要根据 PCB 寄存器中的 PCB 信息,磁盘中会将对应临时存储的 PCB 放入内存,那么页表也需要重新放入,那么 PCB 内部的数据也需要修改(因为页表的地址被修改了)这简直就是重新运行一个程序所需要消耗的时间了

所以在内存中为了保证地址转换的速度,操作系统会将进程正在使用的、活跃的页表常驻在物理内存中。,但是我们知道,内存中是"寸土寸金"的,本身 4G 的内存如果运行了多个进程,那可能就需要存储多张页表,一张页表需要占用一个页框(4K)我们的 CPU 会运行超级多的进程(可以打开任务管理器看看)估算为 100 个进程,如果一个进程的大小为 4 G(内存会使用虚拟存储,先简单理解为进程看自己的逻辑空间为实实在在的 4 G,不过在内存里面的物理空间就不一定了)那么需要使用的页表数量就为:4G / 4K = 2^20 个页表,而每个页表项假设占用 4 字节那么所有页表需要的大小为 100 * 4 * 2^20 = 100 * 4 M = 400 MB

100 个进程,内存中需要额外使用 400 MB 的大小来存储页表,相当于一本书,目录就占了大概 4.9%(一本 1千页的书目录就有 49 页)

为了解决这一问题,人们创造了二级页表

页表的第二个性质引发的问题

页表必须连续存放,因此当页表很大时,需要占用的连续的页框会很多

二级页表

二级页表,相当是页表的页表,目录的目录

如图所示就是二级页表的所有内容

由于我们的一级页表是连续存放的,使用页号是被隐式存储,而我们需要存储的就是块号(这与 二级页表 相同)

每一个页表都需要占据一个页框的大小,所以页表需要使用 1024 字节来存储

00000000 00000000 00000000 00000111

其中红色可以代表一级页号

黄色可以代表二级页号

蓝色可以代表页内偏移量

而这三个数据组成的二进制码就是内存对应的页框

只需要乘以 4 K 就可以找到这个页框的起始地址了

基本分段存储管理

分页是操作系统为进程提供的、面向物理内存的物理管理机制。

分段是编译器/程序员为程序提供的、面向逻辑功能的逻辑组织机制。

什么是分段

程序中的函数在被 CPU 调用成为进程后会把每个函数分开(分段)

用户编程的时候可以自己为每个函数附加段名

程序在运行的时候获取的是段号

段名是为了方便用户编程,提高代码可读性,段号是为了方便 OS 操控

段的组成

也是和页中分配大差不差,使用 32 位分两个数据:段名和段号

例如

00000000 00000000 00000000 00000000

红色代表当前的段号

黄色代表当前的段内地址(偏移量)

单有段号和段内地址是无法让 CPU 找到并执行对应代码的

所以我们需要使用"段表"来把程序和内存当中分散的段块联系起来

其中段表中的每个段表项的长度都是一样的 6B 因为段内地址占 16 位,那么最多只需要 16 位就能表示段长

相同的,一个段表中的段表项是连续存储的

当进程想要调用某个代码时,它会交给 CPU 段号和段内地址(就是进程的逻辑地址)接下来 CPU 会通过访问进程的段表然后找到对应段号存放的段基址,在段基址加上段内地址后就能得到代码对应的物理地址。

段页式管理

在段页式管理中,进程会鲜卑分段,在分页,然后放置到内存中

段页表

段表

页表

段页表

存储方式

程序先由用户分为几个段存入段表,段表当中存储页表的位置以及页表的长度,页表中则会存放分段再分页后的代码存放的内存块号

用户想要使用某一段代码时只需要输入段号和对应的段内偏移量即可,进程在被调用后会将进程中包含的段表起始地址和段表长度放置到段表寄存器中,系统会通过段表起始地址和段表长度找到对应的段表,通过段表找到段号对应的页表,并通过页表获取到对应的内存块号,内存块号 * 内存块大小 + 用户传入的段内偏移量就可以运行对应的代码(如果页号为 2 且用户想要运行的代码位于第二页中则需要系统进行额外判断)

题外

Belady 异常 -- 当为进程分配的物理块/内存块数增大时,缺页率出现不减反增的异常现象,只有先进先出(FIFO)置换算法会产生 Belady 异常

相关推荐
程序员一点8 小时前
第3章:首次启动与基础配置
操作系统·openeuler
冰冷的希望13 小时前
【系统】VMware17虚拟机安装黑苹果macOS 15.0详细步骤(保姆级)
macos·操作系统·系统·vmware·虚拟机·黑苹果
请输入蚊子1 天前
«操作系统真像还原» 第二章 编写MBR主引导记录
linux·汇编·操作系统·bochs·操作系统真像还原
七夜zippoe2 天前
NumPy高级:结构化数组与内存布局优化实战指南
python·架构·numpy·内存·视图
添砖java‘’2 天前
线程的互斥与同步
linux·c++·操作系统·线程·信息与通信
燃于AC之乐3 天前
【Linux系统编程】进程控制完全指南:从fork创建、优雅终止到进程等待的全面解析
linux·操作系统·进程控制·进程创建·进程等待·进程终止·fork函数
Trouvaille ~4 天前
【Linux】Linux线程概念与控制(四):glibc源码剖析与实现原理
linux·运维·服务器·c++·操作系统·glibc·线程控制
_OP_CHEN4 天前
【Linux系统编程】(二十四)深入 Ext2 块组内部:inode、数据块与目录的底层工作机制
linux·操作系统·文件系统·c/c++·inode·块组·数据块映射
番茄灭世神4 天前
Linux从入门到进阶第一章
linux·计算机·操作系统