linux内存寻址

内存寻址定义

所谓内存寻址,就是 cpu 接受到指令后,需要从内存中取得相应数据。但是内存中的数据都是有对应地址的,如何通过地址拿到的地址,来获取相应地址段上的数据。

所谓地址在操作系统中分为逻辑地址,虚拟地址,线性地址和物理地址。

1. 逻辑地址

逻辑地址就是机器码指令用到的地址。机器指令码中用到的地址都是逻辑地址。目前这个地址是由 16 位段选择符和 32 位偏移量来表示的(CS:EIP 段选择符:段内偏移量)。

2. 虚拟地址

虚拟地址就是逻辑地址的段内偏移量。所以逻辑地址 = 段选择符:虚拟地址。我们正常代码中拿到的地址就是虚拟地址,比如:

1
int *p = (int*)malloc(sizeof(int));

3. 线性地址

是一个 32 位无符号整数,是由逻辑地址经过段页式转换而来的。我们常说的进程的地址空间,所谓的地址指的就是线性地址。

4. 物理地址

是内存芯片中的物理地址,是存放数据的实际地址。是由逻辑地址转换而来的。最终是由这个地址来定位到内存空间。在页表转换时,这里存的不是真正物理地址,是物理内存块编号。例如一个内存块大小为 4K,那第 0 块地址若为 0x50000000,第一块就为 0x50001000。

最后给一张地址变换的图:

0

x86 段页式内存管理机制

逻辑地址转换为物理地址需要经历两个过程:

1. 段式内存管理:逻辑地址 => 线性地址

逻辑地址是(selector:offset)的形式,selector 可以为代码段或者数据段。

1

如用 selector 去 GDT 全局描述符表(假定 TI = 0)拿到段基址 segment base address,之后再加上 offset,就得到了线性地址。这个过程,被称为段式内存管理。对于表指示器 Table Indicator 来讲,决定了去哪种表寻找描述符。全局描述符放在 GDT(每个 CPU 有一个)里面,进程自己的放在 LDT 里面。

分段的目的主要有两个:

  • 使操作系统可以访问大于地址总线的内存,如 32 位地址总线可以访问 大于 4G 的内存。
  • 权限控制,将每个段设置权限位,让不同程序可以访问不同段。

2. 页式内存管理:线性地址 => 物理地址

线性地址结构如下图:

2

线性地址切成三段,用前两段分别作为索引去 Page Directory 和 Page Table 里查表,会先得到一个页目录表项,再得到一个页表项(Page Table Entry),那里面的值就是一个物理内存块的起始地址(其实就是是物理内存编号),把它加上 线性地址 切分之后第三段的页内偏移就得到了最终的物理地址。我们把这个过程称作页式内存管理

Linux 段页式管理做法

Linux 认为靠页式管理就能完成内核所需的功能了,段式太麻烦了。关又关不掉,因为是硬件那里做的,所以只能略施小计:所有段的 segment base address 都设置为 0!这样就不分段了,所有段内空间和线性空间重合。

逻辑地址 == 虚拟地址 == 线性地址