Chinaunix首页 | 论坛 | 博客
  • 博客访问: 652362
  • 博文数量: 128
  • 博客积分: 4385
  • 博客等级: 上校
  • 技术积分: 1546
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-22 14:05
文章分类

全部博文(128)

文章存档

2012年(2)

2011年(51)

2010年(75)

分类: LINUX

2011-03-09 15:31:29

03-内存寻址 - part1:基本概念

Linux内核阅读笔 2010-09-01 22:06:40 阅读69 评论0   字号: 

引言

前面所作的工作可以前纳入准备中,系统启动只是作为“猪屁股”起指导方向的作用,对它的描述 还不算完整。计划在这篇笔记中写内存寻址和内存管理的,实际上简单一点说,内存管理模块包括内存寻址、内核如何给自己分配主存以及怎样给进程分配线性地 址。这几天的兴致不是很高,有点将就应付的意思,但说话得算啊,偷点懒把三部分分割开来,这里就描述内存寻址。

其实我很想尽快将保护模式的概念描述出来,由于保护模式是一个操作系统高层次的概念,要想将其陈述清楚,就跳不过特权级转换、存储、中断和异常、I/O体系结构的一些内容。我没有屈服、我只是knee down。

理想与现实之间存在差距,我们所要做的就是看我们能做什么来弥补这个差距。

在之前的操作系统课程中,已经了解过诸如段页式管理、逻辑地址、物理地址和线性地址等概念,但由于没有Andrew那样有耐心的大师的指导,没有关于这些概念在具体实现中的形象认识。本文描述80x86 CPU如何进行芯片级的内存寻址以及Linux如何利用寻址硬件的。

 

内存地址

内存地址(memory address)可以用来作为访问内存单元(处理器的最小可寻址单位——字/字节)的一种方式,在使用80x86 CPU时(或者在IA-32体系结构下),有三种不同的地址:

逻辑地址(logical address)

在机器语言指令中用来指定一个操作数或一条指令的地址。在80x86分段结构中(IA-32 体系结构中),将程序分为若干段,每一个逻辑地址有一个段(segment)和偏移量(offset/displacement)组成,偏移量说明 了从段开始的地方到实际地址之间的距离。

具体来说,IA-32体系结构中实地址模式中,逻辑地址由16位的段基址和16位的段内偏移组成;保护模式下,逻辑地址由段选择子寄存器中的16位段选择子和32位段内偏移量组成。

线性地址(linear address)(也被称为虚拟地址virtual address)

一个32位无符号整数,用来表示高达4GB的地址(也就是42 9496 7296个内存单元)。线性地址常用十六进制数字表示,值的范围从0x0000 0000到0xffff ffff。

物理地址(physical address)

用于内存芯片级内存单元寻址。物理地址与从CPU的地址引脚发送到内存总线上的电信号想对应。物理地址有32位或36位无符号整数表示(使用PAE:Physical Address Extension物理地址扩展模式时,使用36位无符号整数表示)。

 

基于IA-32体系结构的现在操作系统中(采用了保护模式并开启分页机制),访问内存单元就 需要MMU(Memory Management Unit,内存管理单元)。MMU通过一个分段单元(segmentation unit)的硬件电路把一个逻辑地址转换成线性地址;另一个分页单元(paging unit)的硬件电路把线性地址成一个物理地址,见图03-1.

 

03-内存寻址-未完 - Marco - 扒皮周的窝

 

图03-1 逻辑地址转换

而在大多数RISC(Reduced Instruction-Set Computing,精简指令集计算)架构下,由于其中没有分段和逻辑地址的概念,只需要进行一次线性地址到物理地址的转换。

内存仲裁器(memory arbiter)

不管在多处理器还是单处理器系统中,由于RAM芯片上的读或写操作必须串行的进行,就需要一 种称为内存仲裁器的硬件电路插在总线和每个RAM芯片之间,以满足对RAM芯片的并发访问。【总线的概念会在I/O体系结构中描述】在多处理器系统中,内 存仲裁器的作用是如果某个RAM芯片空闲,就允许一个处理器访问,如果该芯片正忙于为另一个处理器提出的请求工作,就延迟这个处理器的访问。在单处理器系 统中,存在一个DMA(Direct Memory Access,直接内存访问)控制器的特殊处理器,DMA控制器需要与CPU并发操作。从编程角度来说,仲裁器是由硬件电路管理,因此是透明的。

 

IA-32

从80286模型开始,Intel CPU以两种不同的方式执行地址转换,这两种方式分别称为实模式(real mode)和保护模式(protected mode)。下面简单的描述一下Intel x86系列处理器的发展历史。

Intel早期的8086/8088处理器之具有是地址模式工作方式,不能有效的支持现代多任务操作系统需要的虚拟内存管理功能。

从80286开始(1982),Intel实现保护模式,这个处理器认为16位,但在保护模式下可以寻址16MB的内存空间。

1985年,32位的80386开发成功,实现了从16位处理器到32位处理器的飞跃。

从80386开始,Intel CPU的型号有80486、Pentium等,体系结构没有本质的变化,此体系结构常被为IA-32体系结构(Intel Architecture- 32 bit)。

80386位保证向上兼容,保留了以前的16位段寄存器,不得不在分段机制的基础上实现保护 模式。可以这样说,IA-32体系结构中的分段机制很大程度上是产品兼容的结果,另外只用分页机制就能实现有效的内存保护机制,几乎所有的RISC的处理 器之采用分页机制实现内存保护。这从一定程度上说明了一些问题,如Linux的内存管理尽可能绕过IA-32体系结构中的分段机制,只在绕不过去的时候才 使用。

 

硬件分段

图03-2从总体上显示了逻辑地址是如何转换成相应的线性地址,这主要是分段单元(segmentation unit)的工作。       

                                                                 

03-内存寻址-未完 - Marco - 扒皮周的窝

                                                  

图03-2 逻辑地址转换成线性地址

分段单元执行以下操作:

1.       先检查段选择符的TI字段,决定段描述符保存在那一个描述符表中。图03-2中gdt、ldt分别表示GDT(Global Descripteor Table,全局描述符表)、LDT(Local Descriptor Table,局部描述符表)。TI字段指明描述符是在GDT(此时分段单元从gdtr寄存去中得到GDT的线性基址)还是LDT中(此时分段单元从 ldtr寄存器中得到LDT的线性基址)。

2.       从段选择符的index字段计算段描述符的的地址:index字段的值乘以8,再与gdtr或ldtr寄存器中的值相加。

3.       把逻辑地址的偏移量offset与段描述符Base字段的值相加得到线性地址。

注意,由于与段寄存器相关的不可编程寄存器的存在,所以只有当段寄存器的内容改变时才需要执行前两个步骤。

 

下面分别对上面3个步骤出现的实体进行描述。

段寄存器和段选择符

一个逻辑地址由两部分组成:一个段标识符和一个指定段内相对地址的偏移量。段标识符是16位的字段(不管在实模式还是保护模式下),又被称作段选择符(Segment Selector),如图03-3所示,其字段含义见【4】p45。

03-内存寻址-未完 - Marco - 扒皮周的窝

图03-3段选择符格式

 

为快速找到段选择符,处理器提供了段寄存器,这也是段寄存器唯一的目的。有6个寄存器:cs、ss、ds、es、fs、gs。其中,

cs 代码段寄存器,指向包含程序指令的段。

ss 堆栈寄存器,指向包含当前程序栈的段。

ds 数据段寄存器,指向包含静态数据或者全局数据段。

其他3个寄存器可以指向任意的数据段。由于程序采用先将寄存器中的值保存到内存中,用完后恢复的方法,可以将同一个段寄存器用于不同的用途。

cs寄存器中有个两位的字段,用来指明CPU的当前特权级(CPL,Current Privilege Level)。CPL值为0表示最高优先级,为3表示最低优先级。Linux只用0级和3级,分别表示内核态和用户态。

段描述符

每个段用一个8字节(64位)的段描述符表示。段描述符描述段的特征,存放在GDT(全局描 述符表)或者LDT(局部描述符表)中。通常只需定义一个GDT,如果进程除了存放在GDT中的段之外还无需要创建附加的段,就可以有自己的LDT。 GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正被使用的LDT地址和大小存放在ldtr控制寄存器中。

段描述符字段的含义见【4】p43,下面是Linux采用的段及其对应的段描述符:

代码段描述符

这个段描述符代表一个代码段,可以放在GDT或者LDT中。属非系统段。【系统段存储诸如LDT这样关键的数据结构】

数据段描述符

这个段描述符代表一个数据段,可以存放在GDT或者LDT中。也属于非系统段,栈段可以通过一般的数据段实现。

任务状态段描述符(TSSD)

这个段描述符代表一个任务状态段(Task State Segment,TSS),这个段是80x86体系结构中一个特殊的段类型,用于存放硬件上下文,即保存处理器寄存器中的内容。只能存放于GDT中,属系统段。

局部描述符表描述符(LDTD)

这个段描述符代表一个包含LDT的段,只出现在GDT中,属系统段。

访问段描述符

为加速逻辑地址到线性地址的砖汉,80x86处理器提供了一个附加的非编程的寄存器,供6个 可编程的段寄存器使用,而它本身不能被程序员设置。每当一个段选择符被转入段寄存器,相应的段描述符(64位)就有内存装入到对应的非编程寄存器中。只有 段寄存器中的内容改变时,才有必要访问GDT或LDT。如图03-4所示:

 

03-内存寻址-未完 - Marco - 扒皮周的窝

 

 图03-4 快速访问段描述符

一个段描述符有8字节长,其在描述符表(GDT/LDT)中的相对地址(或者说偏移量)是由 段选择符的最高13位的值(13位索引号)乘以8得到。GDT的第一项总是设为0,这样保证空选择符的逻辑地址是无效的,会引起一个处理器异常。所以能保 存在GDT中的段描述符最大数目是8191( )。

 

硬件分页

常规分页

图03-5显示了未进行物理地址扩展(PAE)的80x86处理器的分页机制,从前面可知这是分页单元(paging unit)完成的任务。

 

03-内存寻址-未完 - Marco - 扒皮周的窝

 图03-5 80x86处理器分页

下面对图中出现的概念进行解释。

从80386开始,80x86处理器都支持分页,通过设置cr0寄存器的PG标志启用,当PG=0时,线性地址就被解释成物理地址。

线性地址常被分成固定长度的组,这就是页(page),页内部连续的线性地址被映射到连续的物理地址。分页单元的一个关键任务是把所请求的内存访问类型同线性地址的访问权限相比较,如果这个内存访问无效的话会产生一个缺页异常。【中断和异常、内存管理、进程地址空间】

页既指一组线性地址,又指包含在这组地址中的数据。由于地址映射的存在,内核只需要指定一个页的物理地址和其存取权限,而不需要指定页所包含的所有线性地址的存取权限。

分页单元将所有RAM分成固定长度的页框(page frame)(也成为物理页)。每个页框包含一个页。页框是主存的一部分,为一存储区域;页只是一个数据块,可以存放在页框或磁盘中。

将线性地址映射到物理地址的数据结构成为页表(page table)。页表存放在主存中,在启用分页单元之前必须有内核对页表进行适当的初始化。

从80386开始,Intel处理器的分页单元处理4KB的页,从图03-5可以看出32位的线性地址被分为3个域:directory(目录,最高10位)、table(页表,中间10位)、offset(偏移量,最低12位)。4KB= B。

线性地址的转换分两步完成,每一步都基于一种转换表。第一种转换表称为页目录表(page directory),第二种成为页表(page tabel)。这种二级模式通过只为进程实际使用的那些虚拟内存区请求页表,来减少每个进程页所需RAM的数量。

每个活动进程必须有一个页目录,但为效率起见,只有在进程实际需要一个页表时在给该页表分配RAM。正在使用的页目录的物理地址存放在cr3寄存器中。

cr0、cr3都是控制寄存器,见表03-6。

 

控制寄存器

描述

cr0

控制操作模式和处理器状态的系统标志

cr1

当前没有使用

cr2

内存页面错误信息

cr3

内存页面目录信息

cr4

支持处理器特性和说明处理器特性能力的标志

表03-6 控制寄存器(IA-32中32位)

不能直接访问(或修改)控制寄存器中的值,可以将控制寄存器中的数据传送给通用寄存器(或改动通用寄存器中的数据,在传送给控制寄存器)。

页目录项和页表项包含的字段见【4】p52。

物理地址扩展(PAE)

从Pentium模型开始,80x86微处理器引入了扩展分页(见图03-7),允许页框的 大小为4MB而不是4KB。扩展分页可以将大段连续的线性地址转换成相应的物理地址,这时内核可以不用中间页表进行地址转换,从而节省内存并保留 TLB(Translation Lookaside Buffer, 转换后援缓冲器)项。

03-内存寻址 - part1:基本概念 - Marco - 扒皮周的窝

  扩展分页与常规分页的页目录项只有两个不同:Page Size标志要置位;采用4MB页框时,包含页框物理地址最高20位的字段中只有最高10位有效。可以通过设置cr4寄存器的PSE标志使扩展分页和常规分页共存。

处理器所支持的RAM容量受连接到地址总线上的地址管脚数限制。从Pentium Pro开始,Intel所有处理器寻址能力达 。这时需要引入新的分页机制把32位线性地址转换为36位物理地址,从Pentium Pro开始,Intel引入了一种叫物理地址扩展(Physical Address Extension, PAE)的机制。(Linux没有采用Pentium III处理器中引入的页大小扩展(Page Size Extension)机制,至于还有没有其他扩展机制我不清楚。)

可以设置cr4寄存器的PAE标志激活PAE;设置页目录项中的PS标志页框大小,在PAE启动时为2MB。

硬件高速缓存

现在微处理时钟频率是几GHz,而DRAM芯片的存储时间是时钟周期的数百倍,为了缩小 CPU和RAM之间的速度不匹配,引入基于局部性原理(locality principle)的硬件高速缓存内存(hardware cache memory)。其主要目标是引入小而快的内存来存放最近最常使用的代码和数据。80x86体系结构引入了一个新单位——行。行由几十个连续的字节组成, 它们以脉冲突发模式在慢速DRAM和快速的用来实现高速缓存的SRAM之间传送,用来实现高速缓存。

如图03-8所示,高速缓存单元位于分页单元和主存之间。它由一个硬件高速缓存内存和一个高 速缓存控制器组成。硬件高速缓存内存中存放内存中真正的行。高速缓存控制器存放一个表项数组,每个表项对应高速缓存中的一行,每个表项由一个标签和描述高 速缓存行转台的几个标志组成。这个标签由一些位组成,高速缓存控制器通过这些位能够找到由这个行当前所映射的内存单元。这样内存物理地址分为3部分:最高 几位对应标签、中间几位对应高速缓存控制器的子集索引、最低几位对应行内的偏移地址。

03-内存寻址 - part1:基本概念 - Marco - 扒皮周的窝

为判断高速缓存命中与否,CPU从物理地址中提取出子集的索引号,并把物理地址的高几位同子集中所有行的标签进行比较,如果找到一行的标签与这个物理地址的高几位相同,则CPU命中一个高速缓存(cache hit),否则没有命中(cache miss)。

命中一个高速缓存时,高速缓存控制器根据存取类型进行不同的操作。对于读操作,控制器从高速 缓存行中提取数据并传送到CPU寄存器中。对于写操作,控制器有两种基本的策略:通写(write through)和回写(write back)。通写中控制器关闭高速缓存,既写RAM又写高速缓存;回写中控制器只更新高速缓存行,不改变RAM的内容,只有CPU执行要求刷新高速缓存表 项的指令或当FLUSH硬件信号产生时,高速缓存控制器才将高速缓存写回到RAM中。

没有命中高速缓存时,高速缓存行被写回到RAM中,另外如果有必要的话,把正确的行从RAM中提取出来存放到高速缓存的表项中。

多处理器系统中,每个处理器都有一个独立的硬件高速缓存,以及额外的硬件电路用于保持高速缓存的同步。更新同步的活动常被叫做高速缓存侦听(cache snooping),由于这属于硬件级处理活动,内核不需要进行处理。

TLB(Translation Lookaside Buffer)

除通用的硬件高速缓存外,80x86处理器有另一个TLB的高速缓存,可以加快线性地址的转换。一个线性地址第一次被使用时,通过访问RAM中的页表计算出相应的物理地址,同时将物理地址存放在一个TLB表项中,以便以后对同一线性地址的访问可以快速得到。

多处理器系统中,每个CPU都有自己的TLB(本地TLB)。由于当前CPU上的进程可以使用同一线性地址与不同的物理地址进行关联,所以TLB中的对应项不需要同步。

阅读(5109) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~