操作系统 内存使用与分段--10
- 如何让内存用起来?
- 那就让首先程序进入内存
- 重定位: 修改程序中的地址(是相对地址)
- 程序载入后还需要移动…
- 重定位最合适的时机 - 运行时重定位
- 整理一下思路
- 引入分段: 是将整个程序一起 载入内存中吗?
- 程序员眼中的程序
- 不是将整个程序,是将各段分别放入内存
- 这个表似曾相识… 真正故事:GDT LDT
如何让内存用起来?
- 内存使用:将程序放到内存中,PC指向开始地址
那就让首先程序进入内存
- 让程序从磁盘加载到内存中来,首先需要考虑要将程序代码安放在内存的什么位置
- 如果把入口地址直接放在0地址处,然后调用主方法main时,直接使用call 40,这样有没有问题?
首先,0地址处一般不会存放用户态程序代码,其次,call 40这里40的含义是绝对地址,指的就是内存中40的位置,绝对位置好吗?
- 假设1000地址处有空位,程序被读取到1000地址处,然后这里call 40还能理解为绝对地址吗?
- 显然,不能,相信大家都看出来了,这里40应该看做是偏移地址。
重定位: 修改程序中的地址(是相对地址)
上面分析已经知道了,对于程序地址的调用,应该采用相对地址,而非绝对地址,那么怎么把相对地址转换为绝对地址呢?
- 是么时候完成重定位? 编译时 载入时
- 编译时重定位就是在程序编译阶段对相对地址统一加上一个偏移地址,变成绝对地址,但是这样的坏处是,程序编译完毕后,程序中相关地址就变成了绝对地址,显然这样不够灵活,适合于小型,功能单一,不会变化的嵌入式系统
- 如果是载入时完成重定位,则是在挑选到一块空闲内存载入后,程序的基址就确定了,然后将程序中所有相对地址加上基址得到绝对地址。
小结:
- 编译时重定位的程序只能放在内存固定位置
- 载入时重定位的程序一旦载入内存就不能动了
程序载入后还需要移动…
内存中存放的是常驻程序,但是如果某个进程长时间阻塞,不使用CPU和内存资源,如果继续让该进程滞留在内存中,是不是有点浪费宝贵的内存资源呢?
- 可以采用交换机制,将当前阻塞的进程换出到磁盘上保存,等到要使用的时候,再将该进程从磁盘换回来
- 同一个进程换出和换进后,在内存中存放的位置是会动态变化的,如果还是采用载入时确定程序地址的方式,显然行不通,那怎么办呢?
重定位最合适的时机 - 运行时重定位
- 在运行每条指令时才完成重定位
因此每个进程都需要对应一个基地址,那么这个基地址应该存放在哪里呢?
每个进程都对应一个描述符,这个描述进程信息的数据结构被称为PCB,因此需要将base地址在程序开始运行时,将分配得到的基地址记录在PCB中。
当进程被换出,然后换入后,需要重新修改PCB中base地址的值。
整理一下思路
CPU中有好几个基址寄存器,例如: x86系统中的ds代码段基址寄存器,用来存放当前程序的基地址,当程序每遇到一个相对偏移地址时,默认都会加上ds寄存器中保存的基地址,然后得到真实的物理地址。
当进程要进行切换时,会先将基址寄存器中的值保存到当前进程PCB中,然后将下一个进程保存的相关基地址状态拍到对应基址寄存器中。
引入分段: 是将整个程序一起 载入内存中吗?
程序员眼中的程序
由若干部分(段)组成,每个段有各自的特点、用途:代码段只读,代 码/数据段不会动态增长…
- 符合用户观点: 用户可独立考虑每个段(分治)
- 怎么定位具体指令(数据): <段号, 段内偏移>
如mov [es:bx], ax
es是段基址寄存器,而bx存放的是段偏移地址。
不是将整个程序,是将各段分别放入内存
程序分段放入内存能够更加高效的提升对内存的利用,例如: 如果不进行分段,如果程序栈空间不足,需要扩展,就需要将整个程序的代码重新copy到新分配好的更大的内存空间才行。
如果进行了分段处理,因为各段不干扰,因此只需要将栈段copy到新空间即可,不需要移动整个程序。
将程序分段存放后,程序的寻址方式也就发生了变化,需要分段寻址,即每次寻址就需要当前段号加上段内偏移,才能确定真实的物理地址:
因此进程的PCB中,还需要存放一个段表,该表内记录了每个段的段号,基地址,长度和其他访问权限管理等信息。
- 当使用下面这条指令时,假设此时DS=0,表示段号为0,查段表知道,基地址为180k,[DS:100]得到真实物理地址为180K 100
mov [DS:100], �x
- 当使用下面这条指令时,假设此时CS=1,表示段号为1,查段表知道,基地址为360k,要跳转到360k 100的位置
jmpi 100, CS
这个表似曾相识… 真正故事:GDT LDT
我们可以把操作系统看做是一个进程,而操作系统这个进程关联的段表就是GDT表。
每个进程也都关联了一个段表,这个表叫做LDT表。
因为我们对程序进程了分段,所以在程序载入到内存中时,是分段载入的,因此需要一个表来记录每个段的段号和该段对应的基地址,这就有了LDT表的诞生。
因此,每个进程在初始化创建时,需要初始化好当前进程对应的LDT表,该表内部记录了当前程序的分段信息,然后将初始化好的LDT表,设置到当前进程的PCB中。
当程序执行时,需要查询LDT表,得到当前段基址,然后与偏移地址拼接得到真实的物理地址。
当进程切换时,需要切换LDT表,并将当前CPU中相关基址寄存器的值存入当前进程PCB中。