(下面所讨论的是基于单CPU模式,对应的CPU体系结构为80x86)对应的代码是基于2.6版
内存地址
三种不同的地址:逻辑地址,线性地址,物理地址
逻辑地址->线性地址->物理地址
对于这三个地址的转换是通过内存控制单元(MMU)所使用的分段单元硬件电路来实现的。
硬件中的分段
硬件中分段的起初功能主要依靠段选择符来实现,而段寄存器是段选择符的装载器,所以,在进行硬件分段的使用时,避免不了对段寄存器的使用。每个段寄存器中有16位,高十三位为相应段在GDT(全局描述符表)中的偏移量,最后三位分别为TI(表指示器)和RPL(请求特权级,指定了CPU的当前特权级),TI即指定是存放在GDT中,还是存放在LDT中;RPL即表明当前段的请求特权级。
每个段选择符对应着一个段描述符,每个段描述符为8个字节,可以发现,对于十三位的偏移值,最大值为8192即GDT中最多只能存放8192个段描述符,但是因为GDT中的第一个不用,所以实际上能使用的就是8191个段描述符。当GDT使用完时所需要的内存容量为64KB.对于8位的段描述符,大体上可以分为下面的几个字段:
----------------------------------------------------
Base字段 -即包含段的首字节的线性地址(总共有32位)
G字段 -即粒度标志,如果这个位清0,那么这个段的大小就以字节为单位,否则就以4096字节的倍数为单位
Limit字段 -存放段中最后一个内存单元的偏移量,从而决定段的长度。可以发现,这个字段是20位,但是代表的空间大小需要根据G字段来决定,如果G被设置为0,则一个段的大小在1个字节到1MB之间变化(2^20),否则,将在4KB到4GB(2^20*4096)之间变化。
S字段 -系统标志;如果被清0,表明这是一个系统段,存储诸如LDT这样的关键数据结构,否则是一个普通的代码段或者是数据段。
Type字段 -根据相应的进程是否在CPU中运行来设置这个字段的值。
DPL字段 -描述符特权级,表示为访问这个段而要求的CPU最小的优先级,这个字段通常会和段寄存器中的RPL字段相互作用
P字段 -用来标志该字段是否在主存中,如果等于0,就不在主存中,否则就在主存中
D或B字段 -代码段或者数据段的标志
AVL字段 -Linux忽略掉了这个字段使用
----------------------------------------------------
在每次进行内存寻址的时候,为了不让CPU次次都访问GDT(因为CPU访问GDT是比较耗时的),于是引入了一种附加的非编程的寄存器,相关的知识可以参阅其他的详细资料。
如果只是知道了段选择符,是不可能得到GDT中相应的段描述符的,因为段寄存器中的段选择符仅仅是GDT中的索引,这时就需要引入一个指定GDT基地址的辅助工具,而这个工具就是gdtr,即GDT对应的寄存器,如果想要了解这个寄存器,相关的硬件资料中一般都会给予详细的介绍,所以这里就不再阐述。这就是硬件中的分段的简要介绍。
linux中的分段
Linux以非常有限的方式使用分段,2.6版的Linux只有在8下086结构下才需要使用分段。
运行在用户态下的所有linux进程都使用一对相同的段来对指令和数据寻址。这两个段就是所谓的用户代码段和用户数据段,类似地,运行在内核态的所有linux进程都使用一对相同的段对指令和数据寻址,它们分别叫做内核代码段和内核数据段。这些段的段选择符号分别由__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS分别定义。如果想要对对应的段进行寻址,直接将相应的宏装入段寄存器即可。这些宏的定义代码如下:
54 #define GDT_ENTRY_DEFAULT_USER_CS 14
55 #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
56
57 #define GDT_ENTRY_DEFAULT_USER_DS 15
58 #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
59
60 #define GDT_ENTRY_KERNEL_BASE 12
61
62 #define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
63 #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
64
65 #define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
66 #define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
linux中的分段是为分页做一定准备的。
硬件中的分页
硬件中的分页机制是为了提高效率而引入的一项重要的措施。分页机制将线性地址分为以固定长度为单位的组(4KB),每一组称为一页。由于页的引入,同时又引入了一个与页相关的概念-页框。页框和页区分起来很简单的,将内存分为一个个固定长度的部分,这个对应的部分就是所谓的页框,而页就是装载进页框中的对象。
现在给了一个线性地址,可以将这个线性地址分为三个部分:目录部分,占用10位;页表部分,占用10位;偏移部分,占用12位。前两个部分可以分别看作是一个转换表,当给出了一个线性地址时,需要经过这两个转换表来实现物理地址的转换。每个转换表中表项均有着相同的结构,无论是目录部分,还是页表部分。可以发现,如果使用分页机制,因为转换表中的每一项仅仅占有4字节,所以两个转换表总共占用了2*4*1024=8KB,这对于4GB这个容量数字来说是非常小的,但是如果上了MB单位制,那么相对来说,是相当大的。这是引入分页机制的一个原因。
当给定一个线性地址后,经过MMU的处理,就可以得到相应的物理地址(当然,这里指的是正常工作状态下).目录表中的每一项均用来作为页表指针,即指定了相对应的页表,而页表又是作为具体页指针来使用的。1)对于一线性地址,取最高10位,这个数据同GDT与段选择符前13位的功能类似,也是作为偏移值使用,即用来指定对应的项的序列。2)那么类似于gdtr功能的实现又是由谁来实现呢?这就是cr3,cr3控制寄存器存放了正在使用的页目录的物理地址。得到目录项后,对应的页表项就由页目录项中的指定页表对应页框物理地址最高20位的字段来实现。这时,MMU转移到对应的页表项中,3)同样的,获取指定页框物理地址最高20位的字段,再用这个值加上线性地址的最低12位,即偏移字段,所得到的值就是对应的物理地址。
上面介绍的仅仅是简单的线性地址映射操作。
上面介绍的分页机制中将页框的大小设置为4KB,但是从Pentium模型开始,80x86处理器引入了扩展分页,所允许的页框大小为4MB而不是4KB。而这个机制的实现是通过设置cr4处理器寄存器的Page Size标志来实现的。相关的具体内容请参见Intel文档。
阅读(1335) | 评论(0) | 转发(0) |