天天看点

深入理解Linux内核学习笔记之内存寻址

一 内存地址

使用80x86微处理器需区分三种地址:

逻辑地址(段+偏移 - 机器指令中用的地址)

线性地址(即虚拟地址 32bit无符号整形, 0x00000000 - 0xFFFFFFFF)

物理地址(与从微处理器的地址引脚发送到内存总线上的电信号相对应 - 用于内存芯片级寻址)

MMU有两个硬件电路:1是分段单元,2是分页单元

1 用来逻辑地址-》线性地址

2 用来线性地址-》物理地址

多处理器系统中,总线和每个RAM芯片之间会插入一个所谓内存仲裁器(memory arbiter),就RAM芯片空闲状态来准予或延

迟CPU的访问(CPU并发访问,但RAM芯片的读写必须串行,所以需要仲裁);

单处理器系统也有内存仲裁器(单处理器系统包含DMA控制器,这是个特殊处理器,它将与CPU并发操作)。

二 硬件中的分段

Intel微处理器从80286模型开始,以两种不同的方式(实模式、保护模式)执行地址转换;

1 段选择符和段寄存器

逻辑地址的组成:段标识符(16bit 也称段选择符) + 指定段内相对地址的偏移量(32bit)

【段选择符16bit格式:15...3   2   10

                     Index    TI  RPL    Index - 索引号,TI - 指示器,RPL - 请求者特权级】

段寄存器(共6个,为cs,ss,ds,es,fs,gs)的唯一目的:存放段选择符。

程序可以复用这些寄存器(把同一个段寄存器用于不同目的),方法是:先将其值保存在内存中,用完后再恢复。

cs,ss,ds 三个有专门的用途:

cs: 代码段寄存器(指向包含程序指令的段)

ss: 栈段寄存器(指向包含当前程序栈的段)

ds: 数据段寄存器(指向包含静态数据或全局数据的段)

cs还有一个重要功能:它含有一个两bit的字段,用以指明当前CPU的特权级(CPL- Current privilege Level)。

0 - 最高优先级,3 - 最低优先级;Linux只用0级和3级,分别称之为内核态和用户态。

es,fs,gs 一般用途,可以指向任意数据段。

2 段描述符

每个段由一8字节的段描述符表示,它描述了段的特征。段描述符放在GDT(Global Desciptor Table 全局描述符表)或LDT

(Local Descriptor Table 局部描述符表)中。 GDT/LDT在内存中的地址和大小信息存放在gdtr/ldtr控制寄存器中。通常

指定义一个GDT,每个进程除了放在GDT中的段之外如果还需要创建附加的段,就可以有自己的LDT。

64bit的段描述符中:

bit 0-15 / 48-51 :LIMIT 占了两部分(LIMIT 0-15/16-19 共 20 bits)存放段中最后一个内存单元的偏移量,从而决定

段的长度。

  若G=0,则段大小在1B-1MB之间变化;若G=1,则在4KB-4GB间变化。

bit 55   :G 粒度标志:0 - 段大小以字节为单位;1 - 以4096字节(4KB)的倍数计(LIMIT值表示4KB的多少倍)。

bit 16-31 / 32-39 / 56-63 : 占了三部分空间,分别存放 BASE0-15, BASE16-23, BASE24-31 。

   BASE里放的是段的首字节的线性地址。

bit 40-43 :TYPE 段的类型及存取权限

bit 44    :S (系统标志) S=0代表这是个系统段(存储如LDT这样的关键数据结构);

            S=1 就代表是普通的代码段或数据段。

bit 45-46 :DPL (Descriptor privilege Level)描述符特权级 - 为访问这个段而要求的CPU最小优先级。

           (用于限制对这个段的存取)。DPL为0的段只有当CPL=0(内核态)时才可访问;

   DPL为3的段任何CPL值都可访问。

bit 47    : 恒为1.

bit 52    :AVL标志,可由OS使用,但被Linux忽略。

bit 53    :恒为0.

bit 54    : 数据段的B标志或代码段的D标志(系统段忽略此位),具体看Intel使用手册。

   B或D的含义在在两种情况下稍有区别,但若段偏移量的地址是32位长,基本置1;16位长,基本置0.

Linux中被广泛使用的段类型有:

代码段描述符:此段描述符(可放在GDT或LDT中)代表一个代码段;

数据段描述符,此段描述符(可放在GDT或LDT中)代表一个数据段;栈段是通过一般的数据段实现的。

任务状态段描述符(TSSD): 此段描述符代表一个任务状态段(TSS - Task State Segment),S=0 ;

 用于保存CPU寄存器的内容,只能出现在GDT中。

 根据相应的进程是否正在CPU上运行,Type字段值分别为11或9。

局部描述符表描述符(LDTD):代表包含LDT的段,此描述符只出现在GDT中;TYPE=2,S=0 。

3 快速访问段描述符

80x86处理器提供一种附加的寄存器(不可编程)供6个可编程的的段寄存器使用。

这种附加的寄存器里存的是段描述符(8 bytes),由相应的段寄存器中的段选择符来指定。

硬件控制过程:当一个段选择符被装入段寄存器,相应的段描述符就从内存装入到对应的非编程CPU寄存器。

此后,针对那个段的逻辑地址转换就不必访问主存中的GDT或LDT,处理器只需直接引用存放段描述符的CPU寄存器。仅当段寄

存器内容改变,才有必要访问GDT或LDT。

段选择符组成字段:

1 Index : 指定了放在GDT或LDT中相应段描述符的入口。

2 TI (Table Indicator) :指明段描述符在GDT中(TI=0)还是在LDT中(TI=1)。

3 RPL : 相应的段选择符装入到cs寄存器中时指示出CPU当前的特权级;

还用于在访问数据段时有选择地削弱处理器特权级。

段描述符(占8字节)在GDT或LDT中的相对地址是由Index * 8 得到的。

如:gdtr寄存器的值若为0x00020000(表示GDT在内存中这个地址处),且由段选择符指定的索引号Index为2,则相应段描述

符的地址为0x00020010 。

GDT的第一项总是设为0,这确保空段选择符的逻辑地址被认为无效,因此引起一个处理器异常。

GDT中能保存的段描述符最大数目2的13次方-1. (Index是13bit的)

4 分段单元(segmentation unit)

分段单元执行以下操作将逻辑地址转换为线性地址:

(1) 检查段选择符的TI以确定段描述符所在的Table,从而从gdtr或ldtr中得到相应Table(GDT或激活的LDT)的线性基地址

(2) 利用段选择符的Index计算段描述符的地址:gdtr或ldtr的值 + Index*8 。

(3) 线性地址 = 相应段描述符的Base字段 + 逻辑地址的偏移量 。

若有附加的不可编程寄存器,则可直接从此寄存器中取得相应的段描述符得到Base来+偏移量,(1)(2)两个步骤可省。

当段寄存器的内容改变了,才需重新执行(1)(2)步骤。

三 Linux中的分段

分段可以给每个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。Linux更喜欢使用分

页方式,这样所有进程可以使用相同的段寄存器值,即多个进程能共享同样的一组线性地址,内存管理变得简单。另外,不

同于80x86体系结构,RISC体系结构对分段支持有限,Linux又需要移植到大多数流行的处理器平台。

2.6版本的Linux只有在80x86体系结构下才需要使用分段,以下是在80x86下的Linux分段:

运行在用户态(/内核态)的所有Linux进程都使用一对相同的段来对指令和数据寻址,

即用户(/内核)代码段和用户(/内核)数据段。

四个主要的Linux段的段描述符字段的值:

段            Base         G     LIMIT     S    TYPE   DPL   D/B    P

用户代码段   0x00000000    1    0xfffff    1     10     3     1     1 

用户数据段   0x00000000    1    0xfffff    1     2      3     1     1 

内核代码段   0x00000000    1    0xfffff    1     10     0     1     1 

内核数据段   0x00000000    1    0xfffff    1     2      0     1     1 

相应的段选择符分别由__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS决定,如:

为了对内核代码段寻址,内核只需把__KERNEL_CS宏产生的值装进cs段寄存器即可。

与段相关的线性地址从0开始,达到2的32次方-1的寻址限长(偏移量是32bits)。

这意味用户态或内核态下所有进程都可以使用相同的逻辑地址。

(5.7)

所有段都有0x00000000开始,可得出:Linux下逻辑地址与线性地址一致,

即逻辑地址偏移量字段的值与相应线性地址的值总是一致的。

CPU的当前特权级(CPL)反映了进程是在用户态还是内核态,并由存放在cs寄存器中的段选择符的RPL字段指定。

只要当前特权级被改变,一些段寄存器必须相应地更新。

如CPL=3时(用户态),ds寄存器含有的段选择符应是用户数据段,CPL=0时,应含有内核数据段。

同样,ss寄存器在CPL=3时需指向一个用户数据段中的用户栈,在CPL=0时指向内核数据段中的一个内核栈。

从用户态切换到内核态时,Linux总是确保ss寄存器装有内核数据段的段选择符。

内核调用一个函数,它执行一条call汇编语言指令,该指令仅指定其逻辑地址的偏移量部分(不用再设段选择符);

因为段选择符已经隐含在cs寄存器中。

在内核态执行的段只有一种,叫内核代码段,由宏__KERNEL_CS定义,

只要当CPU切换到内核态时将__KERNEL_CS装载到cs即可。

同理适用于指向内核数据结构的指针(隐含地使用ds寄存器)和用户数据结构的指针(内核显式地使用es寄存器)。(这个

同理指不用设段选择符,仅指定偏移量。)

1 Linux GDT

每个CPU对应一个GDT, 所有GDT存放在cpu_gdt_table[]中,

这些GDT的地址和大小(用于初始化gdtr寄存器)被存放在cpu_gdt_descr[]中。

这些符号都能在kernel目录的类似arch/x86/kernel/head.S(或head_32.S/head_64.S)目录中找到。

(5.8)

每个GDT包含18个段选择符和14个空的/未使用的/保留的项。

18 = 用户/内核代码/数据段共4个 + 任务状态段1个(TSS,每个CPU一个,段选择符0x80) + 

LDT段1个(包含缺省局部描述符表)+ TLS段3个(Thread-Local Storage 局部线程存储) +

与AMP(高级电源管理)相关的段3个(APMBIOS 32-bit code/APMBIOS 16-bit code/APMBIOS data) +

与支持PnP(即插即用)功能的BIOS服务程序相关的段5个(PNPBIOS 32-bit code段1个和PNPBIOS 16-bit code段4个) +

特殊TSS段(被内核用来处理“双重错误”异常) 1个(处理一个异常引发另一个异常,产生“双重错误”)

着重看下TSS段:每个CPU都有一个TSS,每个TSS相应的线性地址空间都是内核数据段相应地址空间的一个小子集。

所有TSS顺序放在init_tss[]数组, 第n个CPU的TSS描述符的Base字段指向init_tss[]的第n个元素。

TSS段G=0,LIMIT=0xeb (TSS段长为 236 Bytes),Type=9或11,DPL=0 (只能内核态进程访问)

2 Linux LDT

大多用户态下的Linux程序不使用LDT, 这样内核定义了一个缺省的LDT供大多数进程共享;

这个LDT放在default_ldt[]中。

某些情况下,进程仍然需要创建自己的局部描述符表。

四 硬件中的分页

分页单元(paging unit)将线性地址转为物理地址,其中有一个关键任务:

把所请求的访问类型与线性地址的访问权限相比较,如果此次内存访问是无效的,就产生一个缺页异常。

为效率起见,线性地址被分成以固定长度为单位的组,称为页(page);

页内部连续的线性地址被映射到连续的物理地址;

这样,内核可以指定一个页的物理地址和其存取权限(而不用指定页所包含的全部线性地址的存取权限);

遵习惯,术语“页”既指一组线性地址,又指包含在这组地址中的数据。

分页单元把所有的RAM分成固定长度的页框(page frame, 也叫物理页),每个页框包含一个页,即页框长度=页长度。

页框是主存的一部分,是一个存储区域,而一页只是一个数据块,这个数据块可以存放在任何页框或磁盘中。

页表(page table): 把线性地址映射到物理地址的数据结构,它存放在主存中。

在启用分页单元前必须由内核对页表进行适当的初始化。

80386开始的所有x86系列处理器都支持分页(分页单元处理4KB大小的页),通过设置cr0寄存器的PG标志启用;

PG=0时,线性地址就被解释成物理地址。

1 常规分页

32位的线性地址被分为3个域:Directory(目录 高10位) + Table(页表 中10位) + Offset(偏移量 低12位);

线性地址的转换分两步,每步基于一种转换表,分别为页目录表(page directory)和页表(page table);

使用这种二级模式的目的:减少每个进程页表所需RAM的数量;

若使用一级页表,将需要2的20次方个表项来表示每个进程的页表(即使一个进程并不使用那个范围内的所有地址);

( 为什么需要2的20次方个表项的解释: 

段的限长LIMIT占20位,若G=1,则一个段最多可以有4GB的大小,4KB的2的20次方倍;

一个页是4KB大小,则一个段最多可分为2的20次方个页,一个页需要一个表项)

若每个表项占4字节,则需要4MB的RAM来存储这个页表。

二级模式通过只为进程实际使用的那些虚拟内存区请求页表来减少内存容量。

(5.9)

每个活动进程必须有一个分配给它的页目录,在进程实际需要一个页表时才给这个页表分配RAM

(即没有必要马上为该进程的所有页表都分配RAM - 疑问:所有页表?一个段一个页表?)

同一时间只有一个“页目录”表,是和正在活动的进程对应的;

正在使用的“页目录”表起始物理地址:存放在控制寄存器cr3中;

线性地址中Directory域指向“页目录”这张表中的某个目录项,而该目录项指向对应“页表”的起始物理地址;

Table域指向“页表”这张表中的某个表项,而该表项指向对应页所在页框(第一个存储空间)的物理地址;

Offset域就指向页框内的相对位置,Offset有12位,所以每个页框含2的12次方即4K字节的数据。

“页目录”表和“页表”都是最多1024项,因为Directory域和Table域都是10bit的。

可见一个“页目录”表可以寻址到 1024*1024*4KB=2的32次方个存储单元。

页目录项和页表项结构相同,均包含以下字段:

Present标志,包含页框物理地址最高20位的字段,Accessed标志,Dirty,Read/Write,User/Supervisor, PCD/PWT, Page 

Size, Global 这些标志。

Present = 1 :表示页目录项所指的页表(或页表项所指的页)就在主存中;

Present = 0 :页表或页不在主存中,这时此表项剩余bit可由OS用于自己的目的;

若执行一个地址转换所需的页目录项或页表项中Present标志被清0,

则分页单元就把该线性地址存放在控制寄存器cr2中,并产生14号异常:缺页异常。

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

每个页框容量为4KB,所以每个页框的起始物理地址必须是4096的倍数,所以页框起始物理地址的低12位均为0。

所以项里只要包含高20位即可确定页框物理地址。

注意:“页目录”表和“页”表也是事实存放在物理页框中的,

一个页框可存放4KB的字节,所以一个页框内可存放一个完整的“页目录”表或“页表”(1024项*4字节/项),

或一页完整的页数据。

Accessed:当分页单元对相应页框寻址时就设置这个标志。当选中的页被交换出去时,此标志可由OS使用。

 分页单元从不重置此标志,而必须由OS去做。

Dirty (仅应用于页表项): 当对一个页框进行写操作就设置这个标志, 其它和Accessed一样。

Read/Write : 含有页表和页的存取权限(读写或只读)。Read/Write=0,只读,否则,可读写。

User/Supervisor: 含有访问页表或页所需的特权级。

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

Page Size(仅应用于页目录项):=1 启用扩展分页功能,表示页目录项指的是2MB或4MB的页框。

Global(仅应用于页表项):此标志在Pentium Pro中引入,用来防止常用页从TLB高速缓存中刷新出去。

只有cr4寄存器的PGE(Page Global Enable)标志置位时Global才起作用。

TLB (Translation Lookaside Buffer 转换后援缓冲器 这是IBM的叫法),

有时也叫联想内存( Associative Memory), 俗称“快表”。

2 扩展分页

从Pentium模型开始,80x86微处理器引入了扩展分页(extended paging),它允许页框大小为4MB而不是4KB。

扩展分页用于把大段连续的线性地址转换成相应的物理地址,这种情况下不需要中间的“页表”来地址转换,

从而节省内存并保留TLB项。

启用扩展分页功能(Page Size=1)后, 分页单元把32位线性地址分成两个字段:

Directory (高10位) + Offset(低22位)

扩展分页和正常分页的页目录项基本相同,除了:Page Size = 1,以及

页目录项里20位物理地址字段只有高10位有意义,因为每一个物理地址都是在以4MB为边界的地方开始的。

故这个地址的低22位均为0.

设置cr4寄存器的PSE标志能使扩展分页和常规分页共存。

3 硬件保护方案

分页单元和分段单元的保护方式不同。

80x86允许一个段使用4种可能的特权级别,但与页和页表相关的特权级只有2个。

页表/页的特权由User/Supervisor标志控制,若User/Supervisor=0,则只有当CPL<3时才能对页寻址;

若User/Supervisor=1,总能对页寻址。【CPL<3, 对Linux而言,处理器处于内核态】

段的存取权限有三种:读,写,执行;页的存取权限只有两种:读,写。

(1) 常规分页举例

内核已给一个正在运行的进程分配的线性地址空间范围为:0x20000000 ~ 0x2003ffff,刚好由64页组成。

分析高10位Directory字段(十进制128):开始和结束的线性地址的Directory字段都指向进程页目录的第129项.

该目录项中必须包含分配给该进程的页表的物理地址。

若没有给该进程分配其它的线性地址,则页目录的其余1023项都填为0.(一个页目录对应一个进程)

分析中10位Table,从0-0x03f(十进制0-63),所以只有页表的前64个表项是有意义的,其余960个表项填0.

假设进程需要读线性地址0x20021406中的字节,页目录中第0x80项指向相应页表;

Table字段0x21来选择到页表的0x21项,此表项指向包含所需页的页框。

Offset字段0x406用于在目标页框中读取偏移量为0x406的物理地址中的字节。

若页表第0x21项的Present标志为0,则此页就不在主存中,

这种情况下,分页单元在线性地址转换的同时产生一个缺页异常。

无论何时,当进程试图访问0x20000000 ~ 0x2003ffff范围之外的线性地址,都将产生一个缺页异常。

因为这些页表项都填充了0,尤其是Present标志都被清0.

(2) 物理地址扩展(PAE)分页机制

CPU支持的RAM容量受连接到地址总线上的地址管脚数限制。

(早期Intel处理器使用32位物理地址,理论上可以寻址4G RAM, 

但事实上,由于用户进程线性地址空间的需要,内核不能直接对1GB以上的RAM进行寻址。疑问:?)

大型服务器需要大于4GB的RAM来同时运行数以千计的进程,所以必须扩展32位80x86结构所支持的RAM容量。

Intel把管脚数增加到36来解决这个问题,从Pentium Pro开始的所有Intel处理器寻址能力达2的36次方=64GB。

不过要引入一种新的分页机制把32位线性地址转换为36位物理地址才能使用所增加的物理地址。

从Pentium Pro开始,Intel引入物理地址扩展机制(PAE - Physical Address Extension) ;

【 另外还引入一种页大小扩展机制 - (PSE-36 Page Size Extension - Linux没有采用这种机制) 。】

激活PAE: 设置cr4控制寄存器中的PAE标志。

页目录项中的PS标志启用大尺寸页(PAE启用时为2MB,否则是4MB) .

- PS=1 ,  猜想使用PAE分页机制时一页为2MB,使用页大小扩展机制时为一页为4MB。

- PS=0 ,  一页为4KB。

(5.14)

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

64GB的RAM = 2的24次方个页框 (每个页框4KB);

页表项的物理地址字段: 20 bits -> 24 bits + 12bit标志位;

(一项需36bit,存储时要两个32bit才够,寻址是32bit的,不可能指到36bit那去)。

页表项大小:32 bits->64 bits; 一个4KB的页表只能包含512个表项(而非1024个);

PAE页表项必须包含:24 bits 物理地址 + 12 bits 标志位 = 36 bits;

引入页表新级别:页目录指针表(PDPT - Page Directory Pointer Table) = 4个表项(每项64 bits);

cr3控制寄存器:包含PDPT基地址(27 bits);

      PDPT放在RAM的前4GB中,并在32字节(2的5次方)的倍数上对齐,所以27位足以表示此表的基地址。

页目录项中PS=0时,线性地址映射到4KB的页。32位线性地址按以下方式解释:

cr3 -> PDPT , bit31-30 -> PDPT 4项中的1项,bit29-21 -> 页目录512项中的1项,

bit20-12 -> 页表512项中的1项,bit11-0 -> 4KB页中的偏移量

可以说:32位线性地址包含3级index ( 2bits+9bits+9bits )和1个偏移量(12bits)。

页目录项中PS=1,并且采取PAE分页机制(cr4中PAE设置上)线性地址映射到2MB的页。32位线性地址按以下方式解释:

cr3 -> PDPT , bit31-30 -> PDPT 4项中的1项,bit29-21 -> 页目录512项中的1项,

bit20-0 -> 2MB页中的偏移量。

总之,一旦cr3被设置,就可能寻址高达4GB RAM ( 2MB * 512 * 4) 。

(5.15)

若想对更多的RAM寻址,就必须在cr3中放置一个新值,或改变PDPT的内容。

但使用PAE的主要问题是线性地址只有32位长,这迫使内核编程人员用同一线性地址映射不同的RAM区。

PAE并没有扩大进程的线性地址空间,因为它只处理物理地址。

只有内核能够修改进程的页表,因此在用户态下运行的进程不能使用大于4GB的物理地址空间。

另外,PAE允许内核使用容量高达64GB的RAM,从而显著增加了系统中的进程数量。

(3) 64位系统中的分页

32位微处理器普遍采用两级分页,但两级分页不适用于64位系统。

64位系统线性地址是64位,假设4KB标准页(4KB=2的12次方,需12bit才够寻址),线性地址中的offset=12bit,还剩余52bit

分配给Table和Directory字段。

若我们决定仅使用64位线性地址中的48位来寻址(足够了,这个限制仍然能使我们拥有256TB的寻址空间)。

那么48-12=36位可以分配给Table和Directory字段,姑且各分18bit,则每个进程的页目录和页表都含有2的18次方项。

由于这个项太多,所以所有64位处理器的硬件分页系统都使用了额外的分页级别。

使用的级别数量取决于处理器的类型。

一些Linux所支持64位平台使用的硬件分页系统的主要特征如下:

平台名称 页大小 寻址使用的位数 分页级别数 线性地址分级

 alpha 8KB     43    3 10+10+10+13

 ia64 4KB     39      3 9+9+9+12

 ppc64 4KB     41    3 10+10+9+12

 sh64 4KB     41    3 10+10+9+12

 x86_64 4KB     48    4 9+9+9+9+12

Linux成功地提供了一种通用分页模型,它适合于绝大多数所支持的硬件分页系统。

(4) 硬件高速缓存

当今CPU的时钟频率接近几个GHZ, 而动态RAM(DRAM)芯片的存取时间是时钟周期的数百倍。

这意味着,当从RAM中取操作数或向RAM中存放结果这样的指令执行时,CPU可能等待很长时间。

为缩小CPU和RAM间速度不匹配,引入了硬件高速缓存内存(hardware cache memory)。

硬件高速缓存内存基于著名的局部性原理(locality principle), 原理既适用于程序结构也适用于数据结构。

这表明由于程序的循环结构及相关数组可以组织成线性数组,最近最常用的相邻地址在最近的将来又被用到的可能性极大。

因此,引入小而快的内存来存放最近最常用的代码和数据变得非常有意义。

80x86体系结构中引入了一个叫做行(line)的新单位。

行由几十个连续字节组成,

它们以脉冲突发模式(burst mode)在慢速DRAM和快速的用来实现高速缓存的片上静态RAM(SRAM)之间传送,

用来实现高速缓存。

高速缓存再被细分为行的子集。

在一种极端模式下,高速缓存可以是直接映射的(direct mapped),

这时主存中的一个行总是存放在高速缓存中完全相同的位置。

另一种极端模式下,高速缓存是充分关联的(fully associative), 

这意味着主存中的任意一个行可以存放在高速缓存中的任意位置。

但大多数情况下,高速缓存在某种程度上是N-路相关联的(N-way set associative),

意味着主存中的任意一个行可以存放在高速缓存N行中的任意一行中。

如:内存中的一个行可以存放到一个2路组关联高速缓存两个不同的行中。

高速缓存单元插在分页单元和主存之间。

它包含一个硬件高速缓存内存(hardware cache memory)和一个高速缓存控制器(cache controller)。

高速缓存内存存放内存中真正的行;高速缓存控制器存放一个表项数组,每个表项对应高速缓存内存中的一个行。

表项 = Tag(标签) + Flags(描述高速缓存行状态的几个标志)

标签由一些位组成:这些位让高速缓存控制器能辨别由这个行当前所映射的内存单元。

这种内存物理地址通常分三组:最高几位对应标签,中间几位对应高速缓存控制器的子集索引,最低几位对应行内偏移量。

当访问一个RAM存储单元时,CPU从物理地址中提取出子集的索引号并把子集中所有行的标签与物理地址的高几位比较。

若发现某一个行的标签与这个物理地址的高位相同,则CPU命中一个高速缓存(cache hit);否则,没有命中(cache miss)。

当命中一个高速缓存时,高速缓存控制器进行不同的操作,具体取决于存取类型。

对于读操作,控制器从高速缓存行中选择数据并送到CPU寄存器,不需要访问RAM因而节约了CPU时间。

对写操作,控制器可能采用以下两个基本策略之一,分别称为通写(write-through)和回写(write-back)。

通写:控制器既写RAM也写高速缓存行,为提高写操作的功率关闭高速缓存,

回写:只更新高速缓存行,不改变RAM的内容,提供了更快的功效。

当然,回写结束后,RAM最终必须被更新。

只有当CPU执行一条要求刷新高速缓存表项的指令或者当一个FLUSH硬件信号产生时(通常在高速缓存不命中后),

高速缓存控制器才把高速缓存行写回到RAM中。

当高速缓存没有命中时,高速缓存行被写回到内存中,若有必要的话,把正确的行从RAM中取出放到高速缓存的表项中。

多处理器系统中每个处理器都有一个单独的硬件高速缓存,因此它们需要额外的硬件电路用于保持高速缓存内容的同步。

高速缓存侦听(cache snooping)(硬件级处理,内核无需关心): 

一个CPU修改了它的硬件高速缓存,它必须检查同样的数据是否包含在其他的硬件高速缓存中;

如果是,它必须通知其他CPU用适当的值对其更新。

高速缓存技术正快速发展,如:多级片上高速缓存:L1-cache,L2-cache, L3-cache... 它们之间的一致性由硬件实现。

Linux忽略这些硬件细节并假定只有一个高速缓存。

处理器的cr0寄存器的CD标志位用来启用或禁用高速缓存电路;

处理器的cr0寄存器的NW标志位指明高速缓存是使用通写还是回写策略。

Pentium处理器高速缓存的另一个有趣的特点:

让OS把不同的高速缓存管理策略与每一个页框相关联。

为此,每个页目录项和每个页表项都包含两个标志:PCD(Page Cache Disable) 和 PWT(Page Write-through) 。

PCD标志指明:当访问包含在这个页框中的数据时,高速缓存功能必须被启用还是被禁用。

PWT标志指明:当把数据写到页框时,使用的策略是回写还是通写。

Linux清除了所有页目录项和页表项的这两个标志,结果是,对于所有页框都启用高速缓存,对写操作总是采用回写策略。

(5)转换后援缓冲器(TLB) - Translation Lookaside Buffer

80x86处理器除了包含通用硬件高速缓存外,还包含另一个称为TLB的高速缓存用于加快线性地址的转换。

当一个线性地址第一次被使用,通过慢速访问RAM中的页表计算出相应的物理地址。

同时,物理地址被存放在一个TLB表项(TLB Entry)中, 以便以后对同一个线性地址的引用可以快速地得到转换。

多处理器系统中,每个CPU都有自己的TLB,TLB中的对应项不必同步。

这是因为运行在现有CPU上的进程可以使同一线性地址与不同物理地址发生联系。  疑问:?

当CPU的cr3控制寄存器被修改时,硬件自动使本地TLB中的所有项都失效。

这是因为新的一组页表被启用而TLB指向的是旧数据。