内存寻址

内存寻址


内存地址


使用用80x86微处理器时,必須区分以下三种不同的地址

  1. 逻辑地址包含在机器语言指令中用来指定一个操作数或一条指令的地址。这种寻址有80x86著名的分段结构中得到表现,它促使MS-DOSwindows程序员把程序分成若干段。每个逻辑地址都是由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。

  2. 线性地址是一个32位的无符号整数,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制表示,值的范围从0x000000000xffffffff

  3. 物理地址用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址同32位或36位无符号整数表示。


内存控制单元通过一种为分段单元的硬件电路把一个逻辑地址转换成线性地址;接着,第二个称为分页单元的硬件电路把线性地址转换成物理地址。(见图21)


在多处理器系统中,所有CPU都共享同一内存;这意味着RAM芯片可以由独立的CPU并发地访问。因为在RAM芯片上读或写操作必须串行地执行,因此一种所谓内存仲裁器的硬件电路插在总线和每个RAM芯片之间。其作用是如果其中一个RAM芯片空闲,就准予一个CPU访问,如果该芯片忙于为另一个处理器提出的请求服务,就廷迟这个CPU访问。即使在单处理器上也使用内存仲裁器,因为单处理器系统中包括一个叫做DMA控制器的特殊处理器,而DMA控制器与CPU并发操作。在多处理器系统的情况下,因为仲裁器有多个输入端口,所以其结构更加复杂。例如,双pentium在每个芯片的入口维持一个两端口仲裁器,并在试图使用公用总线前请求两个CPU交换同步信息。从编程观点看,因为仲裁器由硬件电路管理,因此它是隐藏的。


硬件中的分段


80286模型开始,Intel微处理器以两种不同的方式执行地址转换,这两种方式分别称为实模式和保护模式。实模式存在主要原因是要维持处理器与早期模型兼容,并让操作系统自举。


段选择符和段寄存器


一个逻辑地址由两部分组成:一个段标识符和一个指定段内相对地址的偏移量。段标识符是一个16位长的字段,称为段选择符如图2-2所示,而偏移量是一个32位长的字段。


为了快速方便地找到段选择符,处理器提供段寄存器,段寄存器的唯一目的是存放段选择符。这些段寄存器称为cs,ss,ds,es,fsgs。尽管只有6个段寄存器,但程序可以把同一个段寄存器用于不同的目的,方法是先将其值保存在内存中,用完后再恢复


6个寄存器中3个有专门的用途:

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

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

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


其它3个段寄存器作一般用途,可以指向任意的数据段。


cs寄存器还有一个很重要的功能:它含有一个两位的字段,用以指明CPU的当前特权级。值为0代表最高优先级,而值为3代表最低优先级。Linux只有0级和3级,分别称之为内核态和用户态。


段描述符


每个段由一个8字节的段段描述符表示,它描述了段的特征。段描述符放在全局描述符表或局部描述符表中。


通常只定义一个GDT,而每个进程除了存放在GDT中的段之外如果还需要创建附加的段,就可以有自己的LDTGDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正被使用的LDT地址和大小放在ldtr控制寄存器中。


2-3阐明了段描述符的格式:表2-1解释了图中各个字段的含义


有几种不同类型的段以及和它们对应的段描述符。下面列出了Linux中被广泛使用的类型:


代码段描述符

表示这个段描述符代表一个代码段,它可以放在GDTLDT中。该描述符置S标志为1

数据段描述符

表示这个段描述符代表一个数据段,它可以放在GDTLDT中,该描述符置S标志为1

任务状态段描述符

表示这个段描述符代表一个任务状态段,也就是说这个段用于保存处理器寄存器的内容。它只能出现在GDT中,根据相应的进程是否正CPU上运行,其Type字段的值分别为119。这个描述符的S标志置为0

局部描述符表描述符

表示这个段描述符代表一个包含LDT的段,它只出现在GDT中。相应的Type字段的值加2S标志置为0


快速访问段描述符


逻辑地址由16位段选择符和32位偏移量组成,段寄存器仅仅存放段选择符。


为了加速逻辑地址到线性地址的转换,80x86处理提供一种附加的非编程的寄存器,供6个可编程的段寄存器使用。每一个非编程的寄存器含有8个字节的段描述符,由相应的段寄存器中的段选择符来指定。每当一个段选择符被装入段寄存器时,相应的段描述符由内存到对应的非编程CPU寄存器。从那时起,针对那个寄存器的逻辑地址转换就可以不访问主存中的GDTLDT,处理器只需直接引用存放段描述符中CPU寄存器即可。仅当段寄存器的内容改变时,才有必要访问GDTLDT(2-4)


由于一个段描述符是8字节长,因此它在GDTLDT内的相对地址是由段选择符的最高13位值乘以8得到的。例如:如果GDT0x00020000且由段选择符所指定的索引号为2,那么相应的段描述符地址是0x00020000+ (2 * 8),


GDT第一项总是设为0。这就确保空段选择符的逻辑地址会被认为是无效的,因此引起一个处理器异常。能够保存在GDT中的段描述符的最大数目是8191


分段单元


2-5详细显示了一个逻辑地址是怎样转换成相应的线性地址的。分段单元执行以下操作:

  1. 先检查段选择符的TI字段,以决定段描述符保存在哪一个描述表中。TI字段指明描述符是在GDT中还是在激活的LDT中。

  2. 从段选择符的index字段计算段描述符的地址,index字段的值乘以8,这个结果与gdtrldtr寄存器中的内容相加。

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


请注意,有了与段寄存器相关的不可编程寄存器,只有当段寄存器的内容被改变时才需要执行前两个操作。


Linux中的分段


80x86微处理器中的分段鼓励程序员把他们的程序化成逻辑上相关的实体,例如子程序或者全局与局部数据区。然而,Linux以很有限的方式使用分段。实际上,分段和分页在某种程度上有点多余,因为它们都可以划分进程的物理空间:分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。与分段相比,Linux更喜欢使用分页方式,因为:


  1. 当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址。

  2. Linux设计目标之一是可以把它移植到绝大多数流行的处理器平台上。然而,RISC体系结构对分段的支持很有限。


2.6版的Linux只有在80x86结构下才需要使用分段。


运行在用户态的所有Linux进程都使用一对相同的段来对指令和数据寻址。这个两个段就是所谓的用户代码段和用户数据段。类似地,运行在内核态的所有Linux进程都使用一个相同的段对指令和数据寻址:它们分别叫做内核代码段和内核数据段。表2-3显示了这四个重要段的段描述字段的值。


相应的段选择符由宏__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS分别定义。例如,为了对内核代码段寻址,内核只需要把__KERNEL_CS宏产生的值装进cs段寄存器即可。


注意,与段相关的线性地址从0开始,达到2321的寻址限长。这就意味着在用户态或内核态下的所有进程可以使用相同的逻辑地址。


所有段都从0x00000000开始,这可以得出另一个重要结论,那就是在Linux下逻辑地址与线性地址是一致的,即逻辑地址的偏移量字段的值与相应的线性地址的值总是一致的。


如前所述,CPU的当前特权级反映了进程是在用户态还是内核态,并由存放在cs寄存器中的段选择符的RPL字段指定。只要当前特权级被改变,一些段寄存器必须相应地更新。例如,当CPL3时,ds寄存器必须含有用户数据段的段描述符,而当CPL=0时,ds寄存器必须含有内核数据段的段选择符。


类似的情况也出现在ss寄存器中。当CPL3时,它必须指向一个用户数据段中用户栈,当CPL0时,它必须指向内核段中的一个内核栈。当从用户态切换到内核态时,Linux总是确保ss寄存器装有内核数据段的段的段选择符。


当对指向指令或者数据结构的指针进行保存时,内核根本不需要为其设置逻辑地址的段选择符,因为cs寄存器就含有当前的段选择符。例如,当内核调用一个函数时,它执行一条call汇编语言指令,该指令仅指定其逻辑地址的偏移部分,而段选择符不用设置,它已经隐含在cs寄存器中了,因为“在内核态执行”的段只有一种,叫做代码段,由宏__KERNEL_CS定义,所以只要当CPU切换到内核态时将__KERNEL_CS装载入cs就足够了。同样的首也适用于指向内核数据结构的指针。


除了刚才描述的4个段以外,Linux还使用了其它几个专门的段。


LinuxGDT


在单处理器系统中只有一个GDT,而在多处理器系统中每个CPU对应一个GDT。所有的GDT都存放在cpu_gdt_table数组中,而所有GDT的地址和它们的大小被存放在cpu_gdt_descr数组中。


2-6GDT的布局示意图。每个GDT包含18个段描述符和14个空的,未使用的,或保留的项,插入未使用的项的目的是为了使经常一起访问的描述符能够处于同一个32字节的硬件高速缓存行中。


每一个GDT中包含的18个段描述符指向下列的段:

  1. 用户态和内核态下的代码段和数据段共4

  2. 任务状态段,每个处理器有1。每个TSS相应的线性地址空间都是内核数据段相应线性地址空间的一个小子集。所有的任务状态段都顺序地存放在init_tss数组中;值得特别说明的是,第NCPUTSS描述符的Base字段指向init_tssN个元素。G标志被清0,而Limit字段置为0xeb,因为TSS段是236字节长。Type字段置为911,且DPL置为0,因为不允许用户态下的进程访问TSS段。

  3. 1个包括缺省局部描述符表的段,这个段通常是被所有进程共享的段

  4. 3个局部线程存储段:这种机制允许多线程应用程序使用最多3个局部于线程的数据段。系统调用set_thread_area()get_thread_area()分别下在执行的进程创建和撤销一个TLS段。

  5. 与高级电源管理相关的3个段:由于BIOS代码使用段,所以当LinuxAPM驱动程序调用BIOS函数来获取或者设置APM设备的状态时,就可以使用自定义的代码段和数据段。

  6. 与支持即敀插即用功能的BIOS服务程序相关的5个段:在前一种情况下,就像前述与AMP相关的3个段的情况一样,由于BIOS例程使用段,所以当LinuxPnP设备驱动程序调用BIOS函数来检测PnP设备使用的资源时,就可以使用自定义的代码段和数据段。

  7. 被内核用来处理“双重错误”异常的特殊TSS段。


如前所述,系统中每个处理器都有一个GDT副本。除了少数几种情况以外,所有GDT的副本都存放相同的表项。首先,每个处理器都有它自己的TSS段,因此其对应的GDT项不同。其次,GDT中只有少数项可能依赖于CPU正在执行的进程。最后,在一些情况下,处理器可能临时修改GDT副本里的某个项。


LinuxLDT


大多数用户态下的Linux程序不使用局部描述符表,这样内核就定义了一个缺省的LDT供大多数进程共享。缺省的局部描述符表存放在default_ldt数组中。它包含5个项,但内核仅仅有效地使用了其中的两个项:用于iBCS执行文件的调用门和Solaris/x86可执行文件的调用门。调用门是80x86微处理器提供的一种机制,用于在调用预定义函数时改变CPU的特权段。


在一些情况下,进程仍然需要创建自己的局部描述符表。这对有些应用程序很有用,像wine那样的程序,它们执行面向段的微软windows应用程序。modify_ldt()系统调用允许进程创建自己的局部描述符。


任何被modify_ldt()创建的自定义局部描述符表仍然需要它自己的段。当处理器开始执行拥有自定义局部描述符表的进程时,该CPUGDT副本中的LDT表项相应地就被修改了。


用户态下的程序同样也利用modify_ldt()来分配新的段,但内核从不使用这些段,它也不需要了解相应的段描述符,因为这些段描述符被包含在进程自定义的局部描述符表中了。


硬件中的分页


分页单元把线性地址转换成物理地址。其中的一个关键会务是把所请求的访问类型与线性地址的访问权限相比较,如果这次内存访问是无效的,就产生一个缺页异常。


为了效率起见,线性地址被分成以固定长度为单位的组,称为页。页内部连续的线性地址被映射到连续的物理地址中。这样,内核可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。


分页单元把所有的RAM分成固定长度的页框。每一个页框包含一个页,也就是一个页框的长度与一个页的长度一致。页框是主存的一部分,因此也是一个存储区域。区分一页和一个页框是很重要的,前者是一个数据块,可以存放任何页框或磁盘中。


把线性地址映射到物理地址的数据结构称为页表。页表存放在主存中,并在启用分页单元之前由内核对页表进行适当的初始化。


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


常规分页


80386起,Intel处理器的分页单元处理器4KB的页。


32位的线性地址被分成3个域:


Directory(目录)

最高10

Table(页表)

中间10

Offset(偏移量)

最低12


线性地址的转换分两步完成,每一步都基于一种转换表,第一种转换表称为页目录表,第二种转换表称为页表。


使用这种二级模式的目的在于减少每个进程页表所需RAM的数量。如果使用简单的一级页表,那将需要高达220次方个表页来表示每个进程的页表,即使一个进程并不使用那个范围内的所有地址。二级模式通过只为进程实际使用的那些虚拟内存区请求页表来减少内存容量。


每个活动进程必须有一个分配给它的页目录。不过,没有必要很快为进程的所有页表都分配RAM。只有在进程实际需要一个页表时才给该页表分配RAM会更为有效率。


正在使用的页目录的物理地址存放在控制寄存器cr3中。线性地址内的Directory字段决定页目录中的目录项,而目录项指向适当的页表。地址的Table字段依次又决定页表中的表项,而表项含有页所在页框的物理地址。Offset字段决定页框的相对位置(如图2-7)。由于它是12位长,帮每一页含有4096字节的数据。


Directory字段和Table字段都是10位长,因此页目录和页表都可以多达1024项。那么一个页目录可以寻址到高达102410242092232次方个存储单元,这和你对32位地址所期望的一样。


页目录项和页表项有同样的结构,每项都包含下面的字段:


Present标志

如果被置为1,所指的页就在主存中;如果为0,则这一页不在主存中,些时这个表项剩余的位可由操作系统用于自己的目的。如果执行一个地址转换所需要页表项目录项中present标志被清0,那么分页单元就把该线性地址存放在cr2中,并产生14号异常:缺页异常。


包含页框物理地址的最高20位的字段

由于每一个页框有4KB的容量,它的物理地址必须是4096的倍数,因此物理地址的最低12总是为0。如果这个字段指向一个页目录,相应的页框就包含有一个页表;如果它指向一个页表。相应的页框就含有一页数据。


Accessed标志

每当分布单元对相应页框进行寻址时就设置这个标志。当选中的页被交换出去时,这一标志就可以由操作系统使用。分页单元从来不重置这个标志,而是必须由操作系统去做。


Dirty标志

只应用于页表项中,每当对一个页框进行写操作时就设置这个标志。与Accessed标志一样,当选中的页被交换出去时,这一标志就可以由操作系统使用。分页单元从来不智重置这个标志。


Read/Write标志

含有页或页表的存取权限。

User/Supervisor标志

含有访问页或页表所需的特权级。

PCDPWT标志

控制硬件高速缓存处理页或页表的方式。

PageSize标志

只应用于页目录项。如果设置为1,则页目录项指的是2MB4MB的页框。


扩展分页

pentium模型开始,80x86微处理器引入了扩展分页,它允许页框大小为4MB而不是4KB。扩展分页用于把大段连续的线性地址转换成相应的物理地址,在这种情况下,内核可以不用中间页表进行地址转换。


正如前面所述,通过设置页目录项的PageSize标志启用扩展分页功能。在这种情况下,分页单元把32位线性地址分成两个字段:

Directory

最高10

offset

剩下22


扩展分页与正常分页的页目录基本相同,除了:

  1. PageSize标志必須被设置

  2. 20位物理地址字段只有最高10位是有意义的。这是因为每一个物理地址都是在以4MB为边界的地方开始的,故这个地址的最低22位为0

通过设置cr4处理器寄存器的PSE标志能使扩展分页和正常分页共存。


硬件保护方案


分页单元和分段单元的保护方案不同。尽管80x86处理器允许一个段使用4种可能的特权级别,但与页和页表相关的特权级只有两个,因为特权由前面“常规分页”一节中所提到的User/Ssupervisor标志所控制。若这个标志为0,只有当CPL小于3时才能对页寻址;若该标志为1,则总能对页寻址。


此外,与段的3种存取权限不同的是,页的存取只有两种(读写)。如果页目录或页表的Read/Write标志等于0,说明相应的页表或页是只读的,否则是可读写的。


常规分页举例


假定内核已给一个正在运行的进程分配的线性地址空间范围是0x200000000x2003ffff。这个空间正好是64页组成。我们不必关心包含这些页的页框的物理地址,事实上,其中的一些页甚至可能不在主存中。只关注页表项中剩余的字段。


让我们从分配给进程的线性地址的最高10开始。这两个地址都以2开头后面跟着0,因此高10有相同的值即0x080或十进制128。因此,这两个地址的directory字段都指向进程页目录的第129项。相应的目录项中必须包含分配给该进程的页表的物理地址(2-9)。如果没有给这个进程分配其它的线性地址,则页目录的其它1023项都填为0


中间的10位的值范围从00x03f。因而只有页表的前64个表项是有意义的,其它960个表项都填0


假设进程需要读线性地址0x20021406中的字节。这个地址由分页单元按下面的方法处理:

  1. Directory字段的0x80用于选择页目录的第0x80项,此目录项指向和该进程的页相关的页表。

  2. Table字段0x21用于选择页表的第0x21表项,此表项指向包含所需页的页框。

  3. 最后,offset字段0x406用于在目标页框中读偏移量为0x406中的字节。


如果页表第0x21表项的present标志为0,则此页不在主存中,在这种情况下,分页单元在线性地址转换的同时产生一缺页异常。无论何时,当进程试图访问限定在0x200000000x2003ffff范围之外的线性地址是,都将产生一个缺页异常,因为这些页表项都填充了0,特别是它们的present标志都被清0


物理地址扩展分页机制


处理器所支持的RAM容量受连接到地址总线上的地址管脚数限制。早期Intel处理器从80386Pentium使用32位物理地址。从理论上讲,这样的的系统上可以安装高达4GBRAM,而实际上,由于用户进程线性地址空间的需要,内核不能直接对1GB以上的RAM进行寻址。


然而,大型服务器需要大于4GBRAM来同时运行数以千计的进程,近几年这对Intel造成了压力,所以必須扩展3280x86结构所支持的RAM容量。


Intel通过在它的处理器上把管脚数从32增加到36已经满足了这些需求。从PentiumPro开始。Intel所有处理器现在寻址能力达64GB。不过,只有引入一种的分页机制把32位线性地址转换到36位物理地址才能使用所增加的物理地址。


pentiumPro处理器开始,Intel引入一种叫做物理地址扩展的机制。另外一种叫做页大小扩展的机制在Pentium3处理器中引入,但是Linux并没有使用这种机制。


通过设置cr4控制寄存器中的物理地址扩展标志激活PAE。页目录中的页大小标志ps启用大尺寸页。


Intel为了支持PAE已经改变了分页机制。


  1. 64GBRAM被分为了224次方个页框,页表项的物理地址字段从20位扩展到24位。因为PAE页表项必须包含12个标志位和24个物理地址。总数之和为36。页表项大小从32位变成64们增加了一倍。结果,一个4KB的页表包含512个表页不是1024个表项。

  2. 引入一个叫做页目录指针表的页表新级别,它由464位表项组成。

  3. cr3控制寄存器包含一个27位的页目录指针表基地址字段。因为PDPT存放在RAM的前4GB中,并在32字节的倍数上对齐,因此27位足以表示这种表的基地址。

  4. 当把线性地址映射到4KB的页时,32位线性地址按下列方式解释:

    cr3

    指向一个PDPT

    3130

    指向PDPT4个项中的一个

    2921

    指向嶡目录中512个项中的一个

    2012

    1. 指向页表中512项中一个

110

4KB页中的偏移量

总之,一旦cr3被设置,就可能寻址高达4GBRAM,如果我们希望对更多的RAM寻址,就必须在cr3中放置一个新值,或改变PDPT的内容。然而,使用PAE的主要问题是线必地址仍然是32位长。


硬件高速缓存


当今的微处理器时钟频率接近几个GHZ,而动态RAM芯片的存取时间是时钟周期的数百倍。这意味着,当从RAM中取操作数或向RAM中存放结果这样的指令执行是,CPU可能等待很长时间。


为了缩小CPURAM之间的速度不匹配,引入了硬件高速缓存内存。硬件高速缓存基于著名的局部性原理,该原理既适用程序结构和适用数据结构。这表明由于程序的循环结构及相关数组可以组织成线性数组,最近最常用的相邻在最近的将来又被用到的可能性极大。因此,引入小而快的内存来存放最近最常使用的代码和数据变得很有意义。为此,80x86体系结构中引入了一个叫行的新单元。行由几十个连续的字节组成,它们以脉冲突发模式在慢速DRAM和快速的用来实现高速缓存的片上静态RAM之间传送,用来实现高速缓存。


高速缓存再被细分为行的子集。在一种极端的情况下,高速缓存可以是直接映射的,这时主存中的一个行总是存放在高速缓存中完全相同的位置。在另一种极端情况下,高速缓存是充分关联的,这意味着主存中的任意一个行可以存放在高速缓存中的任意位置。但是大多数高速缓存在某种程序上是N路相关联的,意味着主存中的任意一个行可以存放在高速缓存N行中的任意一行中。例如,内存中的一个行可以存放到一个2路组关联高速缓存两个不同的行中。


转换后援缓冲器


除了通过硬件高速缓存之外,80x86处理器还包含了另一个称为转换后援缓冲器的高速缓存用于加快线性地址的转换。当一个线性地址第一次使用时,通过慢速访问RAM中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB表项中,以便以后对同一个线性地址的引用可以快速地行到转换。


Linux中的分页


Linux采用了一种同时适用于32位和64位系统的普通分页模型。正像前面“64位系统中的分页”一节所解释的那样,两级页表对32位系统来说已经足够了,但64位系统需要更多数量的分页级别。直到2.6.10版本,Linux采用三级分页模型。从2.6.11版本开始,采用了四级分页模型。图2-12中提示的4种页表分别被为:

  1. 页全局目录

  2. 页上级目录

  3. 页中间目录

  4. 页表


页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每一个页表项指向一个页框。线性地址因此被分成五个部分。图2-12没有显示位数,因为每一部分的大小与具体的计算机体系结构有关。


对于没有启用物理地址扩展的32位系统。两级页表已经足够了。Linux通过使“页上级目录”位和“页中间目录”位全为0,从根本上取消了页上级目录和页中间目录字段。不过,页上级目录和页中间在指针序列中的位置被保留,以便同样的代码在32位系统和64位系统下都能使用。内核为页上级目录和同中间目录保留一个位置,这是通过把它们的页目录数设置为1,并把这两个目录项映射到页全局目录的一个适当的目录项而实现的。


启用了物理地址扩展的32位系统使用了三级页表。Linux的页全局目录对应80x86的页目录指针,取消了页上级目录,页中间目录对应80x86的页目录,Linux的页对应80x86的页表。


最后,64位系统使用三级还是四级分页取决于硬件对线性地址的位的划分(见表24)


Linux的进程处理很大程序上依赖于分页。事实上,线性地址到物理地址的自动转换使下面的设计目录就得可行:

  1. 给每一个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误。

  2. 区别页和页框之不同。这就允许放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装在不同的页框中。这就是虚拟内存机制的基本要素。


每一个进程有它自己的页全局目录和自己的页表集。当发生进程切换时,Linuxcr3控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行进程的描述符装入cr3寄存器中。因此,当新进程重新开始在CPU上执行时,分页单元指向一组正确的页表。


把线性地址映射到物理地址虽然有点复杂,但现在已经成了一种机械的任务。


线性地址字段


下列宏简化了页表处理:


PAGE_SHIFT

指定Offset字段的位数;当用于80x86处理时,它产生的值为12。由于页内所有地址都必须能放到Offset字段中。因此80x86系统的页的大小是4096个字节。PAGE_SHIFT的值为12,可以看作以2为低的页大小的对数。这个宏由PAGE_SIZE使用以返回页的大小。最后,PAGE_MASK宏产生的值为0xfffff000,用以屏蔽offset字段的所有位。


PMD_SHIFT


指定线性地址的Offset字段和Table字段的总位数;换句话说,是页中间目录可以映射的区域大小的对数。PMD_SIZE宏用于计算页中间目录的一个单独表项所映射的区域大小,也就是一个页表的大小。PMD_MASK宏用于屏蔽OFFSET字段与TABLE字段的所有位。

PAE被禁用时,PDM_SHIFT产生的什为22PMD_SIZE产生的值为4MBPMD_MASK产生的值为0xffc00000。相反,当PAE被激活时,PMD_SIZE产生的值为2MPMD_MASK产生的值为0xffe00000


大型页不使用最后一级页表,所以产生大型尺寸的LARGE_PAGE_SIZE宏等于PMD_SIZE,而在大型页地址中用于屏蔽Offset字段和Table字段的所有位的LARGE_PAGE_MASK宏,就等于PMD_MASK


PUD_SHIFT


指定页上级目录项能映射的区域大小的对数。PUD_SIZE宏用于计算页全局目录中的一个单独项所能映射的区域大小。PUD_MASK宏用于屏蔽Offset字段、Table字段。MiddleAir字段等。

80x86处理上,PUD_SHIFT总是等价于PMD_SHIFT,而PUD_SIZE则等于4M2M


PGDIR_SHIFT


确定页全局目录能映射的区域大小的对象。PGDIR_SIZE宏用于计算页全局目录中一个单独表项能映射区域的大小。PGDIR_MASK宏用于屏蔽OffsetTable,等一些字段。

PAE被禁止时,PGDIR_SHIFT产生的值为22PGDIR_SIZE产生的值为4M,以及PGDIR_MASK产生的值为0xffc00000。相反,当PAE被激活时,PGDIR_SHIFT产生的值为30PGDIR_SIZE产生的值为1GB


PTRS_PER_PTEPTRS_PER_PMDPTRS_PER_PUDPTRS_PER_PGD


用于计算页表,页中间目录,页上级目录和页全局目录表中表项的个数。当PAE被禁止时,它们产生的值为别为1024111024。当PAE被激活时,产生的值分别为51251214


页表处理


pte_t,pmd_t,pud_tpgd_t分别描述页表项、页中间目录项、页上级目录和页全局目录项的格式。当PAE被激活时它们都是64位的数据类型。否则都是32位数据类型,它表示与一个单独表项相关的保护标志。


五个类型转换宏(__pte,__pmd,__pud,__pgd__pgprot)把一个无符号整数转换成所需要类型。另外的五个类型转换成(pte_val,pmd_val,pud_val,pgd_valpgprot_val)执行相反的转换,即把上面提到的四种特殊的类型转换成一个无符号整数。


内核还提供了许多宏和函数用于读或修改页表表项:

  1. 如果相应的表项值为0,那么,宏pte_none,pmd_none,pud_nonepgd_none产生的值为1,否则产生的值为0

  2. pte_clear,pmd_clear,pud_clear,pgd_clear清除相应页表的一个表项,由此禁止进程使用由该页表项映射的线性地址。ptep_get_and_clear()函数清除一个页表项并返回前一个值。

  3. set_pte,set_pmd,set_pudset_pgd向一个页表项中写入指定的值。set_pte_atomicset_pte的作用相同,但是当PAE被激活时它同样能保证64位的值被原子地写入。

  4. 如果AB两个页表项指向同一个页并且指定相同的访问优先级,那么pte_same(A,B)返回1,否则返回0

  5. 如果页中间目录项e指向一个大型页,那么pmd_large返回1,否则返回0


pmd_bad由函数使用并通过输入参数传递来检查页中间目录项。如果目录项指向一个不能使用的页表,也就是说,如果至少出现以下条件中的一个,则这宏产生的值为1

  1. 页不在主存中

  2. 页只允许读访问

  3. Acessed或者Dirty被清除。


pud_bad宏和pgd_bad宏总是产生0。没有定义pte_bad宏,因为页表项引用一个不在主存中的页,一个不可写的页或一个根本无法访问的页都是合法的。


如果一个页表项的Present标志或者pagesize标志等于1,则pte_present宏产生的值为1,否则为0。前面讲过页表项的Pagesize标志对微处理器的分页单元来讲没有意义,然而,对于当前在主存中却又没有读、写或执行权限的页,内核将其PresentPageSize分别标记为01。这样,任何试图对此类页的访问都会引起一个缺页异常。因为页的present标志清0,而内核可能通过检查Pagesize的值来检测到产生异常并不是因为缺页。


如果相应表项的present标志等于1,也就是说,如果对应的页或页表被载入主存,pmd_present宏产生的值为1pud_presetn宏和pgd_present宏产生的值总为1


25中列出的函数用来查询页表项中任意一个标志的当前值;除了pte_file()外,其它函数只有在pte_present返回1的时候。才能正常返回页表项中任意一个标志。


26列出的另一组函数用于设置页表项中和标志的值。


27对页表项进行操作,它们把一个页地址和一组保护标志组合成页表项,或者执行相反的操作,从一个页表项中提出页地址。


当使用两级页表时,创建或删除一个页中间目录项是不重要的。页中间目录仅含有一个指向下属页表的目录项。所以,页中间目录项只是页全局目录中的一项而已。然而当处理页表时,创建一个页表可能很复杂,因为包含页表项的那个页表可能就不存在。在这样的情况下,有必要分配一个新页框,把它填写为0,并把这个表项加入。


如果PAE被激活,内核使用三级页表。当内核创建一个新的页全局目录时,同时也分配四个相应的页中间目录;只有当父页全局目录被释放时,这四个页中间目录才以释放。


当使用两级或三级分页时,页上级目录项总是被映射为页全局目录中的一个单独项。


28中列出的函数描述是针对80x86体系结构的。


物理内存布局


在初始化阶段,内核必須建立一个物理地址映射来指定哪些物理地址范围对内核可用而哪些不可用。

内核将下列页框记为保留:

  1. 在不可用的物理地址范围内的页框

  2. 含有内核代码和已初始化的数据结构的页框


保留页框的页绝不能被动态分配或交换到磁盘上。


一般来说,Linux内核安装在RAM中物理地址0x00100000开始地方,也就是说,从第二个MB开始。所需页框总数依赖于内核的配置方案:典型的配置所得到的内核可以被安装在小于3MRAM中。


为什么内核没有安装在RAM第一个MB开始的地方呢?因为PC体系结构有几个独特的地方必须考虑到。劎例如:

  1. 页框0BIOS使用,存放加电自检期间检查到的系统硬件配置。

  2. 物理地址从0x000a00000x000fffff的范围通常留给BIOS例程,并且映射ISA图形卡上的内部内存。这个区域就是所有IMB兼容PC上从640KB1MB之间著名的洞

  3. 第一个MB内的其它页框可能由特定计算机模型保留。


在启动过程的早期阶段,内核询问BIOS并了解物理内存的大小。在新近的教育处机中,内核也调用BIOS过程建立一组物理地址范围和其对应的内存类型。


随后,内核执行machine_specific_memory_setup()函数,该函数建立物理地址映(见表2-9)射。当然,如果这张表是可获取的,那是内核在BIOS列表的基础上构建的;否则,内核按保守的缺省设置构建这张表:从0x9f0x100号的所有页框都标记为保留。


2-9显示了具有128MRAM计算机的典型配置。从0x07ff00000x07ff2fff的物理地址范围中存有加电自检阶段由BIOS写入的系统硬件设备信息;在初始化阶段,内核把这些信息拷贝到一个合适的内核数据结构中,然后认为这些页框是可用的。相反,从0x07ff30000x07ffffff的物理地址范围被映射到硬件设备的ROM芯片。从0xffff0000开始的物理地址范围标记为保留,因为它由硬件映射到BIOSROM芯片。注意BIOS也许不提供一些物理地址范围的信息。


内核可能不会见到BIOS报告的所有物理内存:例如,如果未使用PAE支持来编译,即使有更大的物理内存可供使用,内核也只能寻址4GB大小的RAMsetup_memory()函数在machine_specific_memory_setup()执行后被调用:它分析物理内存区域表并初始化一些变量业描述内核的物理内存布局,这些变量如表2-10所示。


为了避免把内核装入一组不连续的页框里面,Linux更愿意跳过RAM的第一个MB。明确地说,LinuxPC体系结构未保留的页框来动态存放所分配的页。


2-13显示Linux怎样填充前3MBRAM。假定内核需要小于3MBRAM


符号_text对应于物理地址0x00100000,表示内核代码第一个字节的地址。内核代码的结束位置由另外一个类似的符号_etext表示。内核数据分为两组:初始化过的数据和和没有初始化的数据。初始化过的数据在_etext后开始,在_edata处结束。紧接着是未初始化的数据并以_end结束。


进程的页表


进程的线性地址空间分成两部分:


  1. 0x000000000xbfffffff的线性地址,无论进程运行在用户态还是在内核态都可以寻址。

  2. 0xc00000000xffffffff的线性地址,只有内核态的进程才能寻址。


当进程运行在用户态时,它产生的线性地址小于0xc0000000;当进程运行在内核态时,它执行内核代码,所产生的地址大于等于0xc0000000。但是,在一些情况下,内核为了检索或存放数据必须访问用户态线性地址空间。


PAGE_OFFSET产生的值为0xc0000000,这就是进程在线性地址空间中的偏移量,也是内核生存空间的开始之处。


页全局目录的第一部分表项映射的线性地址小于0xc0000000,具体大小依赖于特定进程。


内核页表


内核维持着一组自己使用的页表,驻留在所谓的主内核页全局目录中。系统初始化后,这组页表还从未被任何进程或任何内核线程直接使用;更确切地说,主内核页全局目录的最高目录项部分作为参考模型,为系统中每个普通进程对应的页全局目录项提供参考模型。


内核初始化自己的页表。这个过程分为两个阶段。事实上,内核映像刚刚被装入内存后,CPU仍然运行于实模式,所以分页功能没有被启用。


第一个阶段,内核创建一个有限的地址空间,包括内核的代码段和数据段、初始页表和用于存放动态数据结构的共128KB大小的空间。这个最小限度的地址空间仅够内核状入RAM和对初始化的核心数据结构。


第二个阶段,内核充分利用剩余的RAM并适当地建立分页表。


临时内核页表


临时页全局目录是在内核编译过程中静态地初始化的,而临时页表是由startup_32()汇编语言函数初始化的。


临时页全局目录放在swapper_pg_dir变量中。临时页表在pg0变量处开始存放,紧接在内核未初始化的数据段后面。为简单起见,我们假定内核使用的段、临时页表和128KB的内存范围能容纳于RAM8MB空间里。为了映射RAM8MB的空间,需要用到两个页表。


分页第一个阶段的目录是允许在实模式下和保护模式下都能很容易地对这8MB寻址。因些,内核必須创建一个映射,把从0x000000000x007fffff的线性地址和从0xc00000000xc07fffff的线性地址映射到从0x000000000x007fffff的物理地址。换句话说,内核在初始化的第一个阶段,可以通过与物理地址相同的线性地址或者通过从0xc0000000开始的8MB线性地址对RAM的前8MB进行寻址


内核通过把swapper_pg_dir所有项都填充为0来创建期望的映射,不过,010x3000x301这四项除外;后两项包含了从0xc00000000xc07fffff间的所有线性地址。010x3000x301按以下方式初始化:

  1. 0项和0x300项的地址字段置为pg0的物理地址,而1项和0x301项的地址字段置为紧随pg0后的页框的物理地址。

  2. 把这四个项中的PresentReand/WriteUser/Supervisor标志置位。

  3. 把这四个项中的AccessedDirytPCDPWDPageSize标志清0

汇编语言函数startup_32()也启用分页单元,通过向cr3控制寄存器装入swpper_pg_dir的地址及设置cr0控制寄存器的PG标志来达到这一目的。下面是等价的代码片段:


RAM小于896MB时的最终内核页表


由内核页表所提供的最终映射必须把从0xc0000000开始的线性地址转化为从0开始的物理地址。


__pa用于把从PAGE_OFFSET开始的线性地址转换成相应的物理地址,而宏__va做相反的转化。


主内核页全局目录仍然保存在swapper_pg_dir变量中。它由page_init()函数初始化。该函数进行如下操作:

  1. 调用pagetable_init()适当地建立页表项。

  2. swapper_pg_dir的物理地址写cr3控制寄存器中。

  3. 如果CPU支持PAE并且如果内核编译时支持PAE,则将cr4控制寄存器的PAE标志置位。

  4. 调用__flush_tlb_all()使TLB的所有项无效。

pagetable_init()执行的操作既依赖于现有RAM的容量,也依赖于CPU模型。计算机有小于896MBRAM32位物理地址足以对所有可用RAM进行寻址,因而没有必要激活PAE机制。


我们假定CPU是支持4MB页和“全局”TLB表项的最新80x86微处理器。注意如果页全局目录项对应的是0xc0000000之上的线性地址,则把所有这些项的User/Supervisor标志清0。由此拒绝用户态进程访问内核地址空间。还要注意PageSize被置位使得内核可能通过使用大型而对RAM进行寻址。


startup_32()函数创建的物理内存前8MB的恒等映射用来完成内核的初始化阶段。当这种映射不再必要是,内核调用zap_low_mappings()函数清除对应的页表项。


RAM大小在896MB4096MB之间时的最终内核页表


在这种情况下,并把RAM全部映射到内核地址空间。Linux在初始化阶段可以做的最好的事把一个具有896MBRAM窗口映射到内核线性空间。如果一个程序需要对现在RAM的其余部分寻址,寻就必须把某些其它的线性地址间隔映射到所需的RAM。这意味着修改一些页表的值。


RAM大于4096MB时的最终内核页表


如果RAM大于4GB计算机的内核页表初始化;更确切地说,要处理以下发生的情况:

  1. CPU模型支持物理地址扩展

  2. RAM容量大于4GB

  3. 内核以PAE支持来编译

尽管PAE处理36位物理地址,但是线性地址依然是32位地址,如前所述,Linux映射一个896MBRAM窗口到内核线性地址空间,剩余RAM留着不映射,并由动态重映射来处理。


而全局目录中的前三项与用户线性地址空间相对应,内核用一个空页的地址对这三项进行初始化。第四项用页中间目录中的前448项用RAM896MB的物理地址填充。


注意,支持PAE的所有CPU模型也支持大型2MB页和全局页。正如前一种情况一样,只要可能,Linux使用大型页来减少页表数。


然后页全局目录的第四项被拷贝到第一项中,这样好为线性地址空间的前896MB中的低物理内存映射做镜像。为了完成对SMP系统的初始化,这个映射是必需的:当这个映射不再必要时,内核通过调用zap_low_mapping()函数来清除对应的页表项。


固定映射的线性地址


我们看到内核线性地址第四个GB的初始部分映射系统的物理内存。但是,至少128M的线性地址总是留作他用,因为内核使用这些线性地址实现非连续内存分配和固定映射的线性地址。


非连续内存分配仅仅是动态分配和释放内存页的一种特殊方式。


固定映射的线性地址基本上是一种类似于0xffffc000这样的常量线性地址,其对应的物理地址不必等于线性地址送去0xc0000000,而是可以以任意方式建立。因此,每个固定映射的线性地址都映射一个物理内存的页框。其实主是使用固定映射的线性地址来代替指针变量,因为这些变量的值从不改变。


固定映射的线性地址概念上类似于对RAM896MB映射的线性地址。不过,固定映射的线性地址可以映射任何物理地址,而由第4GB初始化的线性地址所建立的映射是线性的。


就指针变量而言,固定映射的线性地址更有效。事实上间接引用一个立即常量地址要多一次内存访问。此外,在间接引用一个指针变量之前对其值进行检查是一个良好的编程习惯。


每个固定映射的线性地址都定义于enumfixed_address数据结构中的整型索引来表示:


每个固定映射的线性地址都存放在线性地址第四个GB的低端。fix_to_virt()函数计算从给定索引开始的常量线性地址:


假定某个内核函数调用fix_to_virt(FIX_IO_APIC_BASE_0).因为该函数声明为“inline”,所以C编译程序不调用fix_to_virt(),而是仅仅把它的代码插入到调用函数中。此外,运行时从不对这个索引值执行检查。事实上,FIX_IO_APIC_BASE_0是个等于3的常量,因此编译程序可以去掉if语句,因为它的条件在编译时为假。相反,如果条件为址,或者fix_to_virt()参数不是一个常量,则编译程序在连接阶段产生一个错误。


为了把一个物理地址与固定映射的线性地址关联起来,内核使用set_fixma(idx,phys)set_fixmap_nocache(idx,phys)宏。这两个函数都把fix_to_virt(idx)线性地址对应一个页表项初始化为物理地址phys;不过,第二个函数也把页表项的PCD标志置位,因此,当访问这个页框中的数据时禁用硬件高速缓存。反过来,clear_fixmap(idx)用来撤消固定映射线性地址idx和物理地址之间的连接。


处理硬件高速缓存和TLB


内存寻址的最后一个主题是关于内核如果使用硬件高速缓存来达到最佳效果。硬件高速缓存和转换后援缓冲器在提高现代计算机体系结构的性能上扮演着重要的角色。采用一些技术来减少高速缓存和TLB的未命中次数。


处理硬件高速缓存


如前所述,硬件高速缓存是通过高速缓存行寻址的。L1_CACHE_BYTES宏产生以字节为单位的高速缓存行的大小。


为了使高速缓存的命中率达到最优化,内核在下列决策中考虑体系结构:

  1. 一个数据结构中最常使用的字段放在该数据结构内的低偏移部分,以便它们能够处于高速缓存的同一行中。

  2. 当为一大组数据结构分配空间时,内核试图把它们都存放在内存中,以便所有高速缓存行按同一方式使用。

80x86微处理器自动处理高速缓存的同步,所以应用于这种处理器的Linux内核并不处理任何硬件高速缓存的刷新。不过内核却为不能同步高速缓存的处理器提供了高速缓存刷新接口。


处理TLB


处理器不能自动同步它们自己的TLB高速缓存,因为决定线性地址和物理地址之间映射何时不再有效的是内核,而不是硬件。


Linux2.6提供了几种在合适时机应当运用的TLB刷新方法,这取决于页表更换的类型。


尽管普通Linux内核提供了丰富的TLB方法,但通常每个微处理器都提供了更受限制的一组使TLB无效的汇编语言指令。在这个方面,一个更为灵活的硬件平台就是SunUltraSPARC.与之相比,Intel微处理器只提供了两种使TLB无效的技术:

  1. 在向cr3寄存器写入值时所有Pentium处理器自动刷新相对于非全局页的TLB表项。

  2. pentiumPro及以后的处理器中,invlpg汇编语言指令使映射指定线性地址的单个TLB表项无效。

2-12列出了采用这种硬件技术的 Linux宏;这些宏是实现独立于系统的方法的基本要点。


注意表2-12中没有flush_tlb_pgtables方法:在80x86系统中,当页表与父页表解除链接时什么也不需要做,所以实现这个方法的函数为空。


独立于体系结构的使TLB无效的方法非常简单地扩展到了多处理器系统,在一个CPU上运行的函数发送一个处理器间中断给其它的CPU来强制它们执行适当的函数使TLB无效。


一般来说,任何进程切换都会暗示着更换活动页表集。相对于过期页表,本地TLB表项必须被刷新:这个过程在内核把新页全局目录的地址写入cr3控制寄存器时会自动完成。不过内核在下列情况下将避免TLB被刷新:

  1. 当两个使用相同页表集的普通进程之间执行进程切换时。

  2. 当在一个普通进程一个内核线程执行进程切换时。事实上,内核线程并不拥用自己的页表集;更确切地说,它们使用刚在CPU上执行过的普通进程的页表集。

除了进程切换以外,还有其它几种情况下内核需要刷新TLB中的一些表项。例如,当内核为某个用户态进程分配页框并将它的物理地址存入页表项,它必须刷新与相应线性地址对应的任何本地TLB表项。在多处理器系统中,如果有多个CPU在使用相同的页表集,那么内核还必须刷新这些CPU上使用相同页表的TLB表项。


为了避免多处理器系统上无用的TLB刷新,内核使用一种叫做懒惰TLB模式的技术。其基本思想是,如果几个CPU正在使用相同的页表,而且必须对这些CPU上的一个TLB表项刷新,那么,在一些情况下,正在运行内核线程的那些CPU上的刷新就可以廷迟。


事实上,每个内核线程并不拥有自己的页表集;更确切的说,它使用一个普通进程的页表集。不过,没有必要使用一个用户态线性地址对应的TLB表项无效,因为内核线程不访问内核态地址空间。


当某个CPU开始运行一个内核线程时,内核把它置为懒惰TLB模式。当发出清除TLB表项的请求时,处理懒惰TLB模式的每个CPU都不刷新相应的表项;但是,CPU记住它的当前进程正运行在一组页表上,而这组页表的TLB表项对用户态地址是无效的。只要处于懒惰TLB模式的CPU用一个不同的页表集切换到一个普通进程,硬件就自动刷新TLB表项,同时内核把CPU设置为非懒惰TLB模式。然而,如果处理懒惰TLB模式的CPU切换到进程与刚才运行的内核拥有相同的页表集,那么,任何使TLB无效的廷迟操作必须由内核有效地实施;这种使TLB无效的“懒惰”操作可以通过刷新CPU的所有非全局TLB项来有效地获取。


为了实现懒惰TLB模式,需要一些额外的数据结构。cpu_tlbstate变量是一个具有NR_CPUS个结构的静态数组,这个结构有两个字段,一个是指向当前进程内存描述符的active_mm字段,一个是具有两个状态值的state字段:TLBSTATE_OKTLBSTATE_LYZY。此外,每个内存描述符中包含一个cpu_vm_mask字段,该字段存放的是CPU下标;只有当内存描述符属于当前运行的一个进程时这个字段才有意义。


当一个CPU开始执行内核线程是,内核把该CPUcpu_tlbstate元素的state字段置为TLBSTATE_LAZY;此外,活动内存描述符的cpu_vm_mask字段存放系统中所有CPU的下标。对于与给定页表集相关的所有CPUTLB表项,当另外一个CPU想使这些表项我效时,该CPU就把一个处理器间中断发送给下标处于对应内存描述符的cpu_vm_mask字段中的那些CPU


CPU接受到一个与TLB刷新相关的处理器中断,并验证它影响了当前进程的页表集时,它就检查它的cpu_tlbstate元素的state字段是否等于TLBSTATE_LAZY。如果等于,内核就拒绝使TLB表项无效,并从内存描述符的cpu_vm_mask字段删除该CPU下标。这有两种结果:


  1. 只要CPU还处于懒惰TLB模式,它将不接受其它与TLB刷新相关的处理器间中断。

  2. 如果CPU切换到另一个进程,而这个进程与刚被替换的内核线程使用相同的页表集。那么内核调用__flush_tlb()使该CPU的所有非全局TLB表项有效。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内存工作原理 1.内存寻址 首先,内存从CPU获得查找某个数据的指令,然后再找出存取资料的位置时(这个动作称为“寻址”),它先定出横坐标(也就是“列地址”)再定出纵坐标(也就是“行地址”),这就好像在地图上画个十字标记一样,非常准确地定出这个地方。对于电脑系统而言,找出这个地方时还必须确定是否位置正确,因此电脑还必须判读该地址的信号,横坐标有横坐标的信号(也就是RAS信号,Row Address Strobe)纵坐标有纵坐标的信号(也就是CAS信号,Column Address Strobe),最后再进行读或写的动作。因此,内存在读写时至少必须有五个步骤:分别是画个十字(内有定地址两个操作以及判读地址两个信号,共四个操作)以及或读或写的操作,才能完成内存的存取操作。 2.内存传输 为了储存资料,或者是从内存内部读取资料,CPU都会为这些读取或写入的资料编上地址(也就是我们所说的十字寻址方式),这个时候,CPU会通过地址总线(Address Bus)将地址送到内存,然后数据总线(Data Bus)就会把对应的正确数据送往微处理器,传回去给CPU使用。 3.存取时间 所谓存取时间,指的是CPU读或写内存内资料的过程时间,也称为总线循环(bus cycle)。以读取为例,从CPU发出指令给内存时,便会要求内存取用特定地址的特定资料,内存响应CPU后便会将CPU所需要的资料送给CPU,一直到CPU收到数据为止,便成为一个读取的流程。因此,这整个过程简单地说便是CPU给出读取指令,内存回复指令,并丢出资料给CPU的过程。我们常说的6ns(纳秒,秒-9)就是指上述的过程所花费的时间,而ns便是计算运算过程的时间单位。我们平时习惯用存取时间的倒数来表示速度,比如6ns的内存实际频率为1/6ns=166MHz(如果是DDR就标DDR333,DDR2就标DDR2 667)。 4.内存延迟 内存的延迟时间(也就是所谓的潜伏期,从FSB到DRAM)等于下列时间的综合:FSB同主板芯片组之间的延迟时间(±1个时钟周期),芯片组同DRAM之间的延迟时间(±1个时钟周期),RAS到CAS延迟时间:RAS(2-3个时钟周期,用于决定正确的行地址),CAS延迟时间 (2-3时钟周期,用于决定正确的列地址),另外还需要1个时钟周期来传送数据,数据从DRAM输出缓存通过芯片组到CPU的延迟时间(±2个时钟周期)。一般的说明内存延迟涉及四个参数CAS(Column Address Strobe 行地址控制器)延迟,RAS(Row Address Strobe列地址控制器)-to-CAS延迟,RAS Precharge(RAS预冲电压)延迟,Act-to-Precharge(相对于时钟下沿的数据读取时间)延迟。其中CAS延迟比较重要,它反映了内存从接受指令到完成传输结果的过程中的延迟。大家平时见到的数据3—3—3—6中,第一参数就是CAS延迟(CL=3)。当然,延迟越小速度越快。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值