慢行者
分类: linux
2013-09-04 16:39:58
内存 是 linux 内核所管理的最重要的资源之一;
内存管理子系统 是操作系统中最重要的部分之一.
对于立志从事内核开发的工程师来说,熟悉linux 的内存管理系统非常重要.
物理地址是指出现在 cpu 地址总线上的 寻址物理内存的地址信号,
是地址变换的最终结果;
线性地址又名 虚拟地址,在 32 位 cpu 架构下,可以表示 4g 的地址空间,
用 16进制 表示就是 0x00000000 到 0xffffffff.
程序代码经过编译后,在汇编程序中使用的地址;
cpu 要将一个 逻辑地址 转换为 物理地址,
需要两步:
首先 cpu 利用 段式内存管理 单元,将逻辑地址 转换成线性地址;
再利用 页式内存管理 单元,把 线性地址 最终转换为 物理地址.
16 位 cpu 内部拥有 20 位的地址线,它的寻址范围就是 2 的 20 次方,也就是 1m 的内存空间.
但是 16 位 cpu 用于存放地址 寄存器 ( ip , sp ......)只有 16位,因此只能访问 65536 个存储单元,64k.
为了能够访问 1m 的内存空间,cpu 就采用了 内存分段 的管理模式,并在 cpu 内部加入了段寄存器.
16 位 cpu 把 1m 内存空间分为若干个逻辑段,
每个逻辑段的要求如下:
1,逻辑段的起始地址 ( 段地址 ) 必须是 16 的倍数,即 最后 4 个 二进制位必须全为 0.
2,逻辑段 的最大容量为 64 k.
由于段地址 必须是 16 的倍数,所以值的一般形式 为 xxxx0h , 即 前 16 位 二进制是变化的,后 4 位 是固定的.
鉴于段地址的这种特性,可以只保存前 16 位 二进制来保存整个段基地址,
所以每次使用时要用段寄存器左移补 4 个 0 ( 乘以16 ) ,来得到实际的段地址.
在确定了某个存储单元所属的段后,只是知道了该存储单元所属的范围 ( 段地址 -> 段地址 65536 ),
如果想确定 该内存单元的具体位置,还必须知道 该单元在内存的偏移,有了段地址 和偏移量,
就可以唯一的 确定内存单元在 存储器中的具体位置.
逻辑地址 = 段基地址 段内偏移量
由 逻辑地址 得到 物理地址 的公式为:
pa = 段寄存器的值 * 16 逻辑地址
段寄存器是为了对内存进行分段管理而增加的,
16位 cpu 有四个段寄存器,
用于代码段的访问, cs 指向 存放程序的段基址, ip 指向下条要执行的指令在 cs 段的偏移量,
用这两个 寄存器就可以得到一个内存物理地址,该地址存放着一条要执行的指令.
用于堆栈段的访问, ss 指向栈顶,
可以通过 ss 和 sp 两个寄存器直接访问栈顶单元的内存物理位置.
用于数据的访问. ds 中的值左移四位得到数据段起始地址,再加上 bx 中的偏移量,
得到一个存储单元 的物理地址.
用于附加段的访问. es 中的值在 左移四位 得到 附加段起始地址,再加上 bx 中的偏移量,
得到一个存储单元的 物理地址.
32 位 pc 的内存管理仍然采用 " 分段 " 的管理模式,逻辑地址同样由段地址和偏移量两部分组成,
32 位 pc 的内存管理和 16 位 pc 的内存管理有 相同 和不同之处,因为 32位 pc 采用了两种不同的工作模式:
实模式 和 保护模式.
在实模式下, 32 位 cpu 的内存管理 与 16 位 cpu 是一致的.
段基地址长达 32 位,每个 段的最大容量可达 4g ,段寄存器的值是段地址的 “ 选择器 ” ( selector ),
用该 “选择器” 从内存中得到一个 32 位的段地址,存储单元的物理地址就是 该段地址加上 段内偏移量,
这与 32 位 cpu 的物理地址计算方式完全不同.
其值在不同的模式下具有不同的意义:
1,在实模式下:
段寄存器的值 * 16 就是 段地址.
2,在保护模式下:
段寄存器的值是一个选择器,间接指出一个 32 位 的段地址.
从管理 和效率 的角度出发,线性地址 被分为固定长度的组,称为 页 ( page ),
例如: 32 位的机器,线性地址最大可为 4g ,如果用 4k 为一个页 来划分,
这样整个线性地址就被划分为 2 的 20 次方个页.
另一类 “ 页 ” ,称之为 物理页,或者是 页框、页帧.
分页单元把所有的 物理内存 也划分为固定的长度的管理单位,
它的长度一般与线性地址页是相同的.
linux 内核 的设计并没有全部采用 intel 所提供的段机制,
仅仅是 有限度的 使用了分段机制,这不仅简化了 linux 内核的设计,
而且 为把 linux 移植到其他平台创造了条件,因为 很多的 risc 处理器并不支持段机制.
所有段的基地址均为0
由此可以得出,每个段的逻辑地址空间范围 为 0 - 4 gb,
因为每个段的基地址为 0 ,因此,逻辑地址 与 线性地址保持一致 ( 即 逻辑地址 的偏移量 字段的值 与 线性地址的值 总是 相同的 ),
在 linux 中所提到的 逻辑地址 和线性地址 ( 虚拟地址 ),可以认为是 一致的.
看来 , linux 巧妙的把 段机制给绕过去了,而 完全利用了分页机制.