为了技术,我不会停下学习的脚步,我相信我还能走二十年。
分类:
2012-09-01 13:57:53
原文地址:深入理解Linux内核(2)---内存寻址 作者:leon_yu
1.内存地址
当使用80x86处理器时,必须区分三个地址
逻辑地址(Logical address):每个逻辑地址包括一个段和偏移,偏移指从段起始地址到实际地址的偏移。
线性地址(Linear address)(也称虚拟地址):一个32位无符号整数,可以用来表示高达4GB的地址,通常用十六进制表示,范围从0x00000000到0xffffffff
物理地址(physical address):用于在内存芯片上寻址内存单元。与从CPU地址引脚发送到内存总线的电信号相对应。
MMU通过一种称为分段单元(segmentation unit)的硬件电路吧逻辑地址转换成线性地址,接着分页单元(paging uni)的硬件电路把线性地址转换成一个物理地址。
内存仲裁器(memory arbiter):这个硬件电路插在总线和每个RAM芯片之间。他保证多处理器,或与DMA之间访问RAM的同步问题。
2.硬件中的分段
从80286开始,intel处理器以两种不同方式执行地址转换,即实模式(real mode)和保护模式(protected mode)。实模式存在原因主要是维持处理器与早期模型兼容,不让操作系统自举。
(1)段选择符和段寄存器:一个逻辑地址由两部分组成:一个段标志符和一个指定段内相对地址偏移量。
段标识符是一个16位长的字段,称为选择符(Segment Selector),偏移量是一个32位长字段。
为了快速方便地找到段选择符,处理器提供了段寄存器(存放段选择符),段寄存器有cs,ss,ds,es,fs和gs。这6个寄存器中有三个有专门用途。
Cs 代码段寄存器,指向包含程序指令的段。
Ss 栈段寄存器,指向包含当前程序栈的段。
Ds 数据段寄存器,指向包含静态数据或全局数据段
Cs寄存器还有一个重要功能,它包含一个两位字段,用以指明CPU的当前特权级(Current Privilege level,CPL),0表示最高,3代表最低优先级。Linux只用0级和3级,分别称为内核态和用户态。
(2)段描述符:
每个段由一个8字节的段描述符(Segment Descriptor)表示,它描述段的特征,放在全局描述符表(Global Descriptor Table,GDT)或局部描述符表(Local Descriptor Table,LDT)中。
通常只定义GDT,而每个进程除了存放在GDT中的段之外,如果还需创建附加的段,就可以有自己的LDT。GDT存放在gdtr控制寄存器中,当前正被使用的LDT地址和大小放在ldtr控制寄存器中。
下面几种类型的段在Linux中广泛使用。
代码段描述符:表示这个段描述符代表一个代码段,它可以放在GDT或LDT中,置S标志为1(非系统段)
数据段描述符:表示一个数据段,可以放在GDT或LDT中,置S标志为1,栈段是通过一般的数据段实现的。
任务状态段描述符(Task State Segment,TSSD):用于保存处理器寄存器的内容。
局部描述符表描述符(LDTD):只出现在GDT中。
(3)快速访问段描述符
为了加速逻辑地址到线性地址的转换,80x86提供了一个附加的不可编程的寄存器,供6个段寄存器使用,包含8个字节的段描述符。每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入对应的非编程的CPU寄存器。这样,逻辑地址到线性地址转换就不用访问主存中的GDT或LDT。除非段寄存器内容有改变时。
段选择符字段:
Index(13bit): 指定放在GDT和LDT的相应段描述符的入口。
TI(1bit):Table Indicator标志,指明段描述符是在GDT还是LDT中
RPL(2bit):请求中特权级.
能保存在GDT中的段描述符最大数是2*13方-1=8191
(4)分段单元
分段单元执行以下操作:
①先检查段选择符的TI字段,以决定段选择符在GDT还是LDT中。
②从index字段计算段描述符的地址,index值*8+gdtr/ldtr
③逻辑地址偏移量与段描述符的Base字段相加减,得到线性地址。
3.Linux中的分段
80x86微处理器的分段,鼓励程序员把他们的程序化分成逻辑上的相关的实体,如子程序或者全局与局部数据区。而Linux基本不采用分段机制。Linux更喜欢采用分页方式,因为:
①当所有进程使用相同的段寄存器值时,内存管理变得更简单。
②Linux设计目标之一,是可以移植到绝大多数流行的处理器上,RISC体系结构对分段的支持分有限。
2.6版的Linux只有在80x86才使用分段。
运行在用户态的所有Linux进程都使用一对相同的段来寻址,即用户代码段,用户数据段。
运行在内核态的所有Linux进程都使用一对相同段寻址,内核代码段,内核数据段。
四个主要Linux段的段描述符字段的值
4.硬件中的分页
分页单元(paging unit)把线性地址转换成物理地址,其中一个关键任务是把所请求的访问类型与线性地址的访问权限相比较,如果这次内存访问是无效的,就产生一个缺页异常。
把线性地址映射到物理地址的数据结构叫页表(page table),页表在主存中,在启动分页单元之前必须由内核对页表进行适当的初始化。
(1)常规分页
从80386起,intel的分页单元处理4KB的页,32位的线性地址被分成3个域:
Directory(页目录),最高10位
Table(页表),中间10位
Offset(偏移量),最低12位
线性地址的转换分两步,每一步都基于一种转换表,第一种是页目录表(page directory),第二种是页表(page table).
每个活动进程必须有一个分配给它的页目录,只有在进程实际需要一个页表时,才给该页表分配RAM。
正在使用的页目录的物理地址放在cr3中,线性地址内的Directory字段决定页目录中的目录项,Table字段决定页表中的表项,表项含有页所在页框的物理地址。Offset字段决定页框内的相对位置。
假设进程需要读线性地址0x20021406的字节,分页单元做如下处理:
①Directory字段的0x80用于选择页目录的第0x80目录项,此目录项指向和该进程的页相关的页表。
②Table字段0x21用于选择页表的第0x21表项,此表项指向包含所需页的页框
③最后,Offset字段0x406用于在目标页框中读偏移量为0x406中的字节。
(2)扩展分页
从Pentium模型开始,80x86微处理器引入扩展分页(extended paging),它允许页框大小为4MB,而不是4KB。
(3) 64位系统中的分页:
所有64位处理器的硬件分页系统都是用了额外的分页机制,Linux所支持的64位平台硬件分页系统特征,
(4)硬件高速缓存
动态RAM(DRAM)芯片的存取时间是时钟周期的数百倍,这意味着,访问RAM时,CPU可能要等待很长时间,为了缩小CPU和RAM之间的速度不匹配,引入了硬件高速缓存内存(hardware cache memory),硬件高速缓存基于著名的局部性原理(locality principle),该原理既适用程序结构也使用数据结构。
多处理器系统的每一个处理器都有一个单独的硬件告诉缓存,因此需要额外的硬件电路用于保持高速缓存内容的同步。
(5)转换后援缓冲器(TLB)
除了通用硬件高速缓存之外,80x86处理器还包含了另一个TLB(Translation Lookaside Buffer)的高速缓存用于加快线性地址的转换。
当一个线性地址第一次使用时,通过慢速访问RAM的页表计算相应的物理地址,同时物理地址被存放在一个TLB表项(TLB entry)中,以便以后对同一个线性地址的引用得到快速转换。
在多处理器系统中,每个CPU都有自己的TLB,这叫该CPU的本地TLB
5.Linux中的分页
(1)到2.6.10版本,Linux采用三级分页的模型,从2.6.11版本开始,采用了四级分页模型:
页全局目录(Page Global Directory)
页上级目录(Page Upper Directory)
页中间目录(Page Middle Directory)
页表(Page Table)
对于没有启用物理地址扩展的32位系统,两级页表已经足够了,Linux通过使“页上级目录”位和“页中间目录”位全为0,从根本上取消了页上级目录和页中间目录字段。不过这两个目录在指针序列中的位置被保留,以便同样的代码在32位和64位系统都能使用。
(2)Linux的进程处理很大程度上依赖于分页,事实上线性地址到物理地址的自动转换使下面的设计目标变得可行
①给每个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误。
②区别页(一组数据)和页框(即主存中的物理地址)之不同,这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装载不同的页框中,这就是虚拟内存机制的基本要素。
(3)进程页表
进程的线性地址空间分成两部分
从0x00000000~0xbfffffff的线性地址,无论进程运行在用户态还是内核态都可以寻址。
从0xc0000000~0xffffffff的线性地址,只有内核态进程才能访问
(4)事实上每个内核线程并不拥有自己的页表集,更确切的说,它使用一个普通进程的页表集。