软件工程实践 Blog5

软件工程实践 Blog5

2021SC@SDUSC


学习模块:计算机系统 —— 笔记 05
主要内容:储备系统原理的知识,自底向上的学习路线


006 程序内存地址的转换

实际场景:
设想一下,如果一台计算机的内存中只运行一个程序 A,这种方式正好用前面 CPU 的实模式来运行,因为程序 A 的地址在链接时就可以确定,例如从内存地址 0x8000 开始,每次运行程序 A 都装入内存 0x8000 地址处开始运行,没有其它程序干扰。
现在改变一下,内存中又放一道程序 B,程序 A 和程序 B 各自运行一秒钟,如此循环,直到其中之一结束。这个新场景下就会产生一些问题,当然这里我们只关心内存相关的这几个核心问题:

  1. 谁来保证程序 A 跟程序 B 没有内存地址的冲突?换句话说,就是程序 A、B 各自放在什么内存地址,这个问题是由 A、B 程序协商,还是由操作系统决定。
  2. 怎样保证程序 A 跟程序 B 不会互相读写各自的内存空间?
    这个问题相对简单,用保护模式就能解决。
  3. 如何解决内存容量问题?程序 A 和程序 B,在不断开发迭代中程序代码占用的空间会越来越大,导致内存装不下。
  4. 还要考虑一个扩展后的复杂情况,如果不只程序 A、B,还可能有程序 C、D、E、F、G……它们分别由不同的公司开发,而每台计算机的内存容量不同。这时候,又对我们的内存方案有怎样的影响呢?

要想完美地解决以上最核心的 4 个问题,一个较好的方案是:让所有的程序都各自享有一个从 0 开始到最大地址的空间,这个地址空间是独立的,是该程序私有的,其它程序既看不到,也不能访问该地址空间,这个地址空间和其它程序无关,和具体的计算机也无关。
这个方案就是虚拟地址


虚拟地址
正如其名,这个地址是虚拟的,自然而然地和具体环境进行了解耦,这个环境包括系统软件环境和硬件环境

虚拟地址是逻辑上存在的一个数据值,比如 0~100 就有 101 个整数值,这个 0~100 的区间就可以说是一个虚拟地址空间,该虚拟地址空间有 101 个地址。

以最开始 Hello World 作为例子,使用 objdump 工具反汇编一下 Hello World 二进制文件,就会得到如下的代码片段:

00000000000004e8 <_init>:
 4e8:  48 83 ec 08            sub    $0x8,%rsp
 4ec:  48 8b 05 f5 0a 20 00   mov    0x200af5(%rip),%rax        # 200fe8 <__gmon_start__>
 4f3:  48 85 c0               test   %rax,%rax
 4f6:  74 02                  je     4fa <_init+0x12>
 4f8:  ff d0                  callq  *%rax
 4fa:  48 83 c4 08            add    $0x8,%rsp
 4fe:  c3                     retq 

上述代码中,左边第一列数据就是虚拟地址,第三列中是程序指令,如:“mov 0x200af5(%rip),%rax,je 4fa,callq *%rax”指令中的数据都是虚拟地址。

事实上,所有的应用程序开始的部分都是这样的。
这正是因为每个应用程序的虚拟地址空间都是相同且独立的。

那么这个地址是由谁产生的呢?

答案是链接器
其实我们开发软件经过编译步骤后,就需要链接成可执行文件才可以运行,而链接器的主要工作就是把多个代码模块组装在一起,并解决模块之间的引用,即处理程序代码间的地址引用,形成程序运行的静态内存空间视图。只不过这个地址是虚拟而统一的,而根据操作系统的不同,这个虚拟地址空间的定义也许不同,应用软件开发人员无需关心,由开发工具链给自动处理了。由于这虚拟地址是独立且统一的,所以各个公司开发的各个应用完全不用担心自己的内存空间被占用和改写。

物理地址
虽然虚拟地址解决了很多问题,但是虚拟地址只是逻辑上存在的地址,无法作用于硬件电路的,程序装进内存中想要执行,就需要和内存打交道,从内存中取得指令和数据。
而内存只认一种地址,那就是物理地址。
什么是物理地址呢?物理地址在逻辑上也是一个数据,只不过这个数据会被地址译码器等电子器件变成电子信号,放在地址总线上,地址总线电子信号的各种组合就可以选择到内存的储存单元了。但是地址总线上的信号(即物理地址),也可以选择到别的设备中的储存单元,如显卡中的显存、I/O 设备中的寄存器、网卡上的网络帧缓存器。不过如果不做特别说明,我们说的物理地址就是指选择内存单元的地址。

虚拟地址到物理地址的转换

虚拟地址必须转换成物理地址,这样程序才能正常执行。
要转换就必须要转换机构,它相当于一个函数:p=f(v),输入虚拟地址 v,输出物理地址 p。
那么要怎么实现这个函数呢?
用软件方式实现太低效,用硬件实现没有灵活性,最终就用了软硬件结合的方式实现,它就是 MMU(内存管理单元)。MMU 可以接受软件给出的地址对应关系数据,进行地址转换。我们先来看看逻辑上的 MMU 工作原理框架图。如下图所示:

地址转换
上图中展示了 MMU 通过地址关系转换表,将 0x80000~0x84000 的虚拟地址空间转换成 0x10000~0x14000 的物理地址空间,而地址关系转换表本身则是放物理内存中的。

如果在地址关系转换表中,这样来存放:一个虚拟地址对应一个物理地址。那么问题来了,32 位地址空间下,4GB 虚拟地址的地址关系转换表就会把整个 32 位物理地址空间用完,这显然不行。
要是结合前面的保护模式下分段方式呢,地址关系转换表中存放:一个虚拟段基址对应一个物理段基址,这样看似可以,但是因为段长度各不相同,所以依然不可取。
综合刚才的分析,系统设计者最后采用一个折中的方案,即把虚拟地址空间和物理地址空间都分成同等大小的块,也称为页,按照虚拟页和物理页进行转换。根据软件配置不同,这个页的大小可以设置为 4KB、2MB、4MB、1GB,这样就进入了现代内存管理模式——分页模型

分页模型
一个虚拟页可以对应到一个物理页,由于页大小一经配置就是固定的,所以在地址关系转换表中,只要存放虚拟页地址对应的物理页地址就行了。

MMU
MMU 即内存管理单元,是用硬件电路逻辑实现的一个地址转换器件,它负责接受虚拟地址和地址关系转换表,以及输出物理地址。

根据实现方式的不同,MMU 可以是独立的芯片,也可以是集成在其它芯片内部的,比如集成在 CPU 内部,x86、ARM 系列的 CPU 就是将 MMU 集成在 CPU 核心中的。SUN 公司的 CPU 是将独立的 MMU 芯片卡在总线上的,有一夫当关的架势。

下面我们只研究 x86 CPU 中的 MMU。x86 CPU 要想开启 MMU,就必须先开启保护模式或者长模式,实模式下是不能开启 MMU 的。由于保护模式的内存模型是分段模型,它并不适合于 MMU 的分页模型,所以我们要使用保护模式的平坦模式,这样就绕过了分段模型。这个平坦模型和长模式下忽略段基址和段长度是异曲同工的。地址产生的过程如下所示:
MMU
上图中,程序代码中的虚拟地址,经过 CPU 的分段机制产生了线性地址,平坦模式和长模式下线性地址和虚拟地址是相等的。如果不开启 MMU,在保护模式下可以关闭 MMU,这个线性地址就是物理地址。因为长模式下的分段弱化了地址空间的隔离,所以开启 MMU 是必须要做的,开启 MMU 才能访问内存地址空间。

MMU 页表
页表描述了虚拟地址到物理地址的转换关系,也可以说是虚拟页到物理页的映射关系,所以称为页表。
为了增加灵活性和节约物理内存空间(因为页表是放在物理内存中的),所以页表中并不存放虚拟地址和物理地址的对应关系,只存放物理页面的地址,MMU 以虚拟地址为索引去查表返回物理页面地址,而且页表是分级的,总体分为三个部分:一个顶级页目录,多个中级页目录,最后才是页表,逻辑结构图如下:
多级页表
从上面可以看出,一个虚拟地址被分成从左至右四个位段。第一个位段索引顶级页目录中一个项,该项指向一个中级页目录,然后用第二个位段去索引中级页目录中的一个项,该项指向一个页目录,再用第三个位段去索引页目录中的项,该项指向一个物理页地址,最后用第四个位段作该物理页内的偏移去访问物理内存。这就是 MMU 的工作流程。

保护模式下的分页

此处笔记掠过部分详细的地址解析。

开启 MMU

要使用分页模式就必先开启 MMU,但是开启 MMU 的前提是 CPU 进入保护模式或者长模式,开启 CPU 这两种模式的方法,我们在Blog 4,下面我们就来开启 MMU,步骤如下:

  1. 使 CPU 进入保护模式或者长模式。
  2. 准备好页表数据,这包含顶级页目录,中间层页目录,页表,假定我们已经编写了代码,在物理内存中生成了这些数据。
  3. 把顶级页目录的物理内存地址赋值给 CR3 寄存器。
mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax
  1. 设置 CPU 的 CR0 的 PE 位为 1,这样就开启了 MMU。
;开启 保护模式和分页模式
mov eax, cr0
bts eax, 0    ;CR0.PE =1
bts eax, 31   ;CR0.P = 1
mov cr0, eax 

MMU 地址转换失败

MMU 的主要功能是根据页表数据把虚拟地址转换成物理地址,但有没有可能转换失败?绝对有可能,例如,页表项中的数据为空,用户程序访问了超级管理者的页面,向只读页面中写入数据。这些都会导致 MMU 地址转换失败。MMU 地址转换失败了怎么办呢?失败了既不能放行,也不是 reset,MMU 执行的操作如下:
1.MMU 停止转换地址。
2.MMU 把转换失败的虚拟地址写入 CPU 的 CR2 寄存器。
3.MMU 触发 CPU 的 14 号中断,使 CPU 停止执行当前指令。
4.CPU 开始执行 14 号中断的处理代码,代码会检查原因,处理好页表数据返回。
5.CPU 中断返回继续执行 MMU 地址转换失败时的指令。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当你进入软件工程专业的大二阶段,你将开始接触更加深入和专业的课程内容。以下是一些建议,帮助你在大二阶段更好地学习软件工程: 1. 扎的编程基础:在大二阶段,你将会学习更多的编程语言和技术。因此,建议你在大一期间要打好编程基础,包括掌握常见的编程语言(如C++、Java、Python等),熟悉基本的数据结构和算法。 2. 学习软件开发流程:了解软件开发的整个流程是非常重要的。学习软件需求分析、设计、编码、测试和维护等各个环节,了解不同的开发方法论(如敏捷开发、瀑布模型等),并学会使用相应的工具和技术。 3. 实践项目开发:通过参与际的项目开发,可以提升自己的实践能力和团队协作能力。可以尝试参加一些校内或校外的项目竞赛,或者自己组织小团队进行项目开发。 4. 深入学习数据库:数据库是软件工程非常重要的一部分,建议你深入学习数据库的原理和常用的数据库管理系统(如MySQL、Oracle等),掌握SQL语言和数据库设计的基本技能。 5. 学习软件工程工具:掌握一些常用的软件工程工具,如版本控制工具(如Git)、项目管理工具(如JIRA)、集成开发环境(如Eclipse、Visual Studio等)等,这些工具在际开发非常有用。 6. 多参加实践课程和习:大二阶段可以尽量多参加与软件工程相关的实践课程和习机会,通过实践来巩固所学知识,并且积累际项目经验。 7. 拓宽视野:除了专业课程,也要关注软件工程领域的最新动态和技术趋势。可以通过阅读相关的书籍、论文、博客,参加技术交流会议等方式来拓宽自己的视野。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值