操作系统:内存使用与分段
内存的使用方式以及问题
程序如何存放在内存中
内存作为计算机的基本组成部分,用来存储程序(指令和数据)
内存单元按字节编址、寻址,程序装入到内存后,
PC 指向程序开始地址,依次取指执行
对于多个存在于内存中的程序,它们在同一时刻必须占用不同的内存
指令的标号即是该条指令在程序起止位置中的偏移,是一种数字量表示的逻辑地址(相对地址,相对于程序开始位置)
_main1: _main2:
mov [100], 0 mov [100], 10
call _func ; call 40 call _some_func ; call 40
... ...
ret ret
(40)_func: (40)_some_func:
mov ecx,10 ...
... ret
ret
若上面两段独立的程序同时存在于内存中,他们访问的编号为100的应当是不同的内存单元
同理 call 跳转到的应当是逻辑地址相对于各自程序的起始地址偏移后的位置
如上图,call _func 应当即为 call 1040,同理 call _some_func 即跳转到地址 2040 处执行
那么在什么时候以何种方式将程序中的逻辑地址变换为内存中实际的物理地址?
这个问题即是重定位的概念
重定位
重定位是指将指令中的逻辑地址转换为内存中实际的物理地址的过程
-
编译时重定位
编译时重定位的程序只能放在内存中的固定位置,而编译结束后的内存使用情况不一定
与编译时的相同,因此这种重定位方式有很大的局限性。必须保证装入该程序时,这段程序要
使用的地址是可用的。
编译时进行重定位后,装入过程不需要有额外开销,因此效率较高
编译时重定位一般用在可以保证一段程序固定地装入某段内存中的嵌入式系统中 -
载入时重定位(静态重定位)
程序在装入内存时,将指令中的相对地址加上装入的内存段的基址作为绝对地址,载入时
重定位的程序一旦载入内存后就不可以再移动位置。不利于程序在内存中的移动(交换,swap)。 -
运行时重定位(动态重定位)
运行时重定位的程序,装入内存的仍是逻辑地址,在实际访问时进行重定位,即在进程 PCB 中
保存程序段的基址,实际访问时进行地址翻译(由基地址与逻辑偏移计算出物理地址),无妨交换
另一方面,由于每次在内存中寻址都需要进行计算,因此效率十分关键,所以由硬件 MMU(内存
管理单元)完成地址翻译的工作
交换
为了更好地支持多进程,当内存空闲空间不足时,有选择地将某些进程保存到硬盘上(换出)
将腾出的空间交给当前需要运行的进程使用,即将要运行的进程换入到内存
假如进程2处于阻塞状态,而用户运行了新的程序
那么操作系统将进程2换出,换入进程5
当进程2又需要运行时,在换入到内存中合适的位置(比如恰好进程4结束
那么进程2的基址就发生了改变,如果程序在编译时重定位或使用静态重定位
那么交换导致进程2的指令中地址出现错误,所以说 动态重定位 使进程可以在内存中移动
另一种交换是交换部分进程(而不是整个进程),以降低交换时间。通常与虚拟内存一起工作。
内存的分段管理
上面讲述了内存的使用方式,以及每个进程中的指令的地址如何对应到实际的内存
实际上,更多时候进程不是作为一个连续的整体装入内存的
进程由多个部分(段)组成,每个段有各自的特点和用途
比如代码段是只读的,代码段、数据段不会动态增长,而堆栈段可能要动态增长
因为各个段性质的不同,当所有段作为一个整体看待时就会有所不便,若堆栈段的空闲区域不足,
就需要为整个进程分配一块更大的内存,移动整个进程,然后满足堆栈段的增长需要
不如按照程序本身特点,将进程分段管理,满足每个段的需要
建立段表,描述段的信息,包括段号、段基址、段限长,段类别等等
可以单独移动、扩大某个段,只需要维护好段表
汇编语言中,就有明确的段的定义与使用
如mov [100], %eax
,这里 100 隐含的就是以DS为基址,即mov [DS:100], %eax
以及jmp 100
转移到当前段偏移100B处,以及jmp CS:IP
等等
上面的段表,即为LDT,每个进程可以维护一个LDT表作为进程段表
操作系统维护 GDT,每个 LDT 的入口可以作为GDT的一个表项,LDTR寄存器保存当前LDT的地址
进程切换时,切换PCB,包括切换指令序列(CS:IP)与映射表(LDT)
那么当内存在分段管理时,建立一个进程需要按程序所分的段(编译时确定)建立其段表,即初始化LDT
并将LDT与PCB关联起来,然后在内存中找到一块合适空闲区域装入程序
2020/1/7