天天看點

深入了解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指向的是舊資料。

繼續閱讀