先简单说明一下逻辑地址,虚拟地址,物理地址这三个地址的理解,然后再举个例子,结合实际运行程序的时候深入理解地址概念。
1.逻辑地址:
概念上的地址。通常在机器语言指令中来指定操作数或者指令的地址。(补充:机器语言就是计算机能够直接识别和执行的指令,即二进制形式)。
每一个逻辑地址,包括段地址segment和段内偏移量offset,即逻辑地址=【16位的段标识:20位的偏移offset】
MS-DOS或windows把不同的程序划分为独立的逻辑段。逻辑地址在80*86分段结构中表现得很具体。
为什么会有逻辑地址这个概念?逻辑地址可以理解为分段结构意义上的地址。因为windows或者早起的intel为了让每个程序都只有自己的独立空间,所以就将每个程序作为一个单独的逻辑单元,也就是分段单元。在这个逻辑单元内的意义上的地址就是逻辑地址了。
2.虚拟地址(线性地址):
32位的无符号整数,地址范围:0x00000000 到0xffffffff。
在二级页表结构中,虚拟地址=【10位的页目录:10位的页表:12位的偏移量】
在一级页表结构中,虚拟地址=【1位的页表:22位的偏移量】
为什么会有虚拟地址?虚拟地址其实就是分页结构对应的地址。因为linux kernel将内存划分为大小相等的页,方便内存管理和分配。在分段结构中,每个段的大小不一定相等,不便于管理和分配。
3. 物理地址:
实际存在的地址,用于内存芯片级的内存单位寻址。它们与微处理器的地址引脚发送到内存总线上的电信号相对应。
物理地址由32位或者36位无符号整数表示。(硬件上地址总线为36位,高四位和低32位)。
4. 逻辑地址转换为线性地址:
(1) 逻辑地址=[16位的段标识:32位offset]
(2)段标识存储在段寄存器中,段标识=[13位index,1位flag,2位的cpu level].若flag=1,表示段描述符存储在GDT,否则在LDT。
(3)如果段描述符存储在GDT中,则从GDT控制器读取出GDT(全局描述表)的首地址。GDT每个元素是8字节的段描述符,因此,可计算出段标识指向的段描述符地址为:GDT首地址+8*13位的index. 如果在LDT中,计算过程同理。
(4)计算出段标识指向的段描述符地址后,内核将该段描述符从内存读取出来,存放在CPU控制的寄存器中。
(5)段描述符位8个字节大小,存放了段的属性,包括基地址,段长度,等等。
(6)那么,虚拟地址=基地址+32位的offset.
整个转换过程,是由一个硬件电路计算的,效率很高。相当于由段标识映射到段描述符,段描述符中有该段的基地址,基地址+offset就是虚拟地址了。
5.线性地址转换为物理地址:
对于一级页表结构来说, 地址计算过程如下:
(1)页表在内存中的地址存储在cr3寄存器中,我们标记为页表的物理地址;
(2)线性地址的高10位为页目录索引。 页目录的每一项存储了该页的物理地址。所以页的物理地址=(页表的物理地址 + 页目录索引)所指向的页的物理地址。
(3)页的物理地址+偏移量offset 就是该线性地址对应的物理地址。
6.小结:
用户程序经过编译连接后生成由机器语言组成的可执行程序,该可执行程序中的地址是逻辑上的逻辑地址。
当CPU执行可执行程序的指令时,会由一个硬件电路将逻辑地址转换为虚拟地址,再经过一个硬件电路将虚拟地址转换为物理地址。然后CPU就直接从该物理地址指向的内存单元去读数据。
为什么先要转换为虚拟地址呢?是因为逻辑地址是段结构意义上的地址,而内存会被划分为页结构。也就是说,操作系统展现出来的地址是由页地址和页内偏移量组成,而这个就是虚拟地址。至于这个页结构上的虚拟地址如何转换为物理地址,是有硬件电路去计算的。内核不用去管。
因此,可以这样理解, 操作系统把硬件上的物理地址屏蔽掉,抽象出虚拟地址。这样对于程序来说,处于内核层面上的虚拟地址空间,程序不用管内存的物理地址是否够用。