栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【哈工大李治军操作系统听课笔记】L20 内存的使用与分段

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【哈工大李治军操作系统听课笔记】L20 内存的使用与分段

1.取址,执行带来的问题

用一个实际的例子来引出问题,现在有一个程序在编译成指令序列后存储在磁盘上,接下来这段程序从磁盘读入到内存中,设置PC指针开始执行程序,其第一个指令为”Call 40”。这条指令会执行PC=40,即让PC指针去地址为40处读取下一条指令。这个例子里下一条指令为”MOV 1,[300]”。

要使得这个程序运行正确,那么”MOV 1,[300]”这条指令就必须放在地址为40的地方。那么也就意味着这个程序的起始地址必须是物理内存0处。但是我们知道物理内存0处存放的是操作系统。就算没有操作系统存放在那里,如果并发执行两个同样的程序,我们不可能将两个程序都放在物理地址0处。 这就出现了问题。

2.程序重定位

实际上一个程序在编译成为可执行程序的时候,用到的都是从0开始的相对地址。如”Call 40”中的40实际上是一个逻辑地址而非真实的地址。一个程序在载入到内存中后,操作系统会给其安排任意一段物理内存插入,例如之前的例子中的程序就可以被插入在物理内存1000的地方。此时就需要进行程序重定位(program relocation),记录程序被插入在内存中的起始地址,然后将逻辑地址40+起始地址1000得到指令的真实地址,在这里是1040。


3.程序重定位的时机

目前的主流操作系统重定位发生的时机都是在运行时,准确地说实在执行这条指令时才会将逻辑地址变为物理地址。

之所以不放在编译的时候,是因为程序在编译时无法预知自己将被载入到的内存位置。
而不放在载入内存的时候进行重定位是因为进程可能会因为长时间阻塞而被系统暂时换出到磁盘上以释放内存空间。等过段时间进程可以执行了再找到内存空闲的地方换入。因此进程在子载入到内存的时候仍然有可能在内存移动,不能在载入时就重定位。

由于只有在程序运行到当前指令时才会进行重定位,为了提高指令执行效率,我们设置了一个用于翻译地址的硬件,即存储管理部件(MMU)。MMU中有一个寄存器用于保存进程的起始地址。在运行到该指令的时候就会将指令中的逻辑地址和起始地址相加得出真正的物理地址,这个过程被称为地址转换。

但是由于MMU寄存器只有一个,在多进程视图下不可能保存多个进程的起始地址。实际上进程的起始地址保存在其对应的进程PCB中。在执行进程1的操作时,操作系统会将进程1的PCB中保存的基址赋给MMU的寄存器。这样,只要PCB中的基址信息正确,不管进程移动到内存中的哪个位置,进程切换机制都会让进程中的指令正确执行。

之前说过,进程切换分为两部分,指令流切换以及地址空间切换。指令流切换就是指操作系统根据PCB进程内核栈切换,用户栈切换,PC指针切换等等,而地址空间切换就是指这里的MMU寄存器切换。

4.分段的概念

我们知道一个程序实际上是由若干段组成的,例如数据段,代码段,栈段等等。因此实际上一个程序的结构应该如下:

因此程序要被载入内存时,应该是多个段分别载入内存之中**(注意,各个段不用相邻,有可能一个段在内存2000位置,另一个在内存4000位置)**。如果整个程序载入内存,需要记录一个基址,而现在将多个段分别载入内存,就需要记录每个段的基址,这多个基址会形成一个表,这就是著名的段表。

因此操作系统在遇到带有逻辑地址的指令的时候,会首先找到当前指令的段号信息,通过查找段表确定段基址,最后计算出实际地址。

真正的指令是段号+段内偏移构成,如图中的mov[es:bx],ax。es就是段号,bx就是段内偏移。

在操作系统这个进程中,段表被称为GDT表,而在一般的进程中,段表被称为LDT表。这个表存储在进程对应的PCB中。

那么内存分段的完整故事就是:一个程序在被装入内存的过程中,首先是这个程序的不同的段分别在内存中找到一块空闲的空间,把这些空闲的地方的基址分别写到LDT表中。等到LDT表初始化完成后,LDT表就会被复制到该进程对应的PCB中。这下一个程序就真正载入到内存中,可以进行取址执行了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/926142.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号