《奔跑吧 Linux核心》之處理器體系結構

非同步社群發表於2019-02-28

本文摘自人民郵電出版社非同步社群《奔跑吧Linux核心》

第1章 處理器體系結構

京東購書:item.jd.com/12152745.ht…
試讀地址:www.epubit.com.cn/book/detail…
本章思考題
1.請簡述精簡指令集RISC和複雜指令集CISC的區別。
2.請簡述數值0x12345678在大小端位元組序處理器的儲存器中的儲存方式。
3.請簡述在你所熟悉的處理器(比如雙核Cortex-A9)中一條儲存讀寫指令的執行全過程。
4.請簡述記憶體屏障(memory barrier)產生的原因。
5.ARM有幾條memory barrier的指令?分別有什麼區別?
6.請簡述cache的工作方式。
7.cache的對映方式有full-associative(全關聯)、direct-mapping(直接對映)和set-associative(組相聯)3種方式,請簡述它們之間的區別。為什麼現代的處理器都使用組相聯的cache對映方式?
8.在一個32KB的4路組相聯的cache中,其中cache line為32Byte,請畫出這個cache的cache line、way和set的示意圖。
9.ARM9處理器的Data Cache組織方式使用的VIVT,即虛擬Index虛擬Tag,而在Cortex-A7處理器中使用PIPT,即物理Index物理Tag,請簡述PIPT比VIVT有什麼優勢?
10.請畫出在二級頁表架構中虛擬地址到實體地址查詢頁表的過程。
11.在多核處理器中,cache的一致性是如何實現的?請簡述MESI協議的含義。
12.cache在Linux核心中有哪些應用?
13.請簡述ARM big.LITTLE架構,包括匯流排連線和cache管理等。
14.cache coherency和memory consistency有什麼區別?
15.請簡述cache的write back有哪些策略。
16.請簡述cache line的替換策略。
17.多程式間頻繁切換對TLB有什麼影響?現代的處理器是如何面對這個問題的?
18.請簡述NUMA架構的特點。
19.ARM從Cortex系列開始效能有了質的飛越,比如Cortex-A8/A15/A53/A72,請說說Cortex系列在晶片設計方面做了哪些重大改進?

Linux 4.x核心已經支援幾十種的處理器體系結構,目前市面上最流行的兩種體系結構是x86和ARM。x86體系結構以Intel公司的PC和伺服器市場為主導,ARM體系結構則是以ARM公司為主導的晶片公司佔領了移動手持裝置等市場。本書重點講述Linux核心的設計與實現,但是離開了處理器體系結構,就猶如空中樓閣,畢竟作業系統只是為處理器服務的一種軟體而已。目前大部分的Linux核心書籍都是基於x86架構的,但是國內還是有相當多的開發者採用ARM處理器來進行開發產品,比如手機、IoT裝置、嵌入式裝置等。因此本書基於ARM體系結構來講述Linux核心的設計與實現。
關於ARM體系結構,ARM公司的官方文件已經有很多詳細資料,其中描述ARMv7-A和ARMv8-A架構的手冊包括:

  • <ARM Architecture Reference Manual, ARMv7-A and ARMv7-R edition>
  • <ARM Architecture Reference Manual, ARMv8, for ARMv8-A architecture profile>
  • 另外還有一本非常棒的官方資料,講述ARM Coxtex系統處理器程式設計技巧:

  • <ARM Coxtex-A Series Programmer’s Guide, version 4.0>
  • <ARM Coxtex-A Series Programmer’s Guide for ARMv8-A, version 1.0>
  • 讀者可以從ARM官方網站中下載到上述4本資料[1]。本書的重點集中在Linux核心本身,不會用過多的篇幅來介紹ARM體系結構的細節,因此本章以快問快答的方式來介紹一些ARM體系結構相關的問題。

    可能有些讀者對ARM處理器的命名感到疑惑。ARM公司除了提供處理器IP和配套工具以外,主要還是定義了一系列的ARM相容指令集來構建整個ARM的軟體生態系統。從ARMv4指令集開始為國人所熟悉,相容ARMv4指令集的處理器架構有ARM7-TDMI,典型處理器是三星的S3C44B0X。相容ARMv5指令集的處理器架構有ARM920T,典型處理器是三星的S3C2440,有些讀者還買過基於S3C2440的開發板。相容ARMv6指令集的處理器架構有ARM11 MPCore。到了ARMv7指令集,處理器系列以Cortex命名,又分成A、R和M系列,通常A系列針對大型嵌入式系統(例如手機),R系列針對實時性系統,M系列針對微控制器市場。Cortex-A7和Coxtex-A9處理器是前幾年手機的主流配置。Coxtex-A系列處理器面市後,由於處理效能的大幅提高以及傑出功耗控制,使得手機和平板電腦市場迅猛發展。另外一些新的應用需求正在醞釀,比如大記憶體、虛擬化、安全特性(Trustzone[2]),以及更好的能效比(大小核)等。虛擬化和安全特性在ARMv7上已經實現,但是大記憶體的支援顯得有點捉襟見肘,雖然可以通過LPAE(Large Physical Address Extensions)技術支援40位的實體地址空間,但是由於32位的處理器最高支援4GB的虛擬地址空間,因此不適合虛擬記憶體需求巨大的應用。於是ARM公司設計了一個全新的指令集,即ARMv8-A指令集,支援64位指令集,並且保持向前相容ARMv7-A指令集。因此定義AArch64和AArch32兩套執行環境分別來執行64位和32位指令集,軟體可以動態切換執行環境。為了行文方便,在本書中AArch64也稱為ARM64,AArch32也稱為ARM32。

    1.請簡述精簡指令集RISC和複雜指令集CISC的區別。

    20世紀70年代,IBM的John Cocke研究發現,處理器提供的大量指令集和複雜定址方式並不會被編譯器生成的程式碼用到:20%的簡單指令經常被用到,佔程式總指令數的80%,而指令集裡其餘80%的複雜指令很少被用到,只佔程式總指令數的20%。基於這種思想,將指令集和處理器進行重新設計,在新的設計中只保留了常用的簡單指令,這樣處理器不需要浪費太多的電晶體去做那些很複雜又很少使用的複雜指令。通常,簡單指令大部分時間都能在一個cycle內完成,基於這種思想的指令集叫作RISC(Reduced Instruction Set Computer)指令集,以前的指令集叫作CISC(Complex Instruction Set Computer)指令集。
    IBM和加州大學伯克利分校的David Patterson以及史丹佛大學的John Hennessy是RISC研究的先驅。Power處理器來自IBM,ARM/SPARC處理器受到伯克利RISC的影響,MIPS來自史丹佛。當下還在使用的最出名的CISC指令集是Intel/AMD的x86指令集。

    RISC處理器通過更合理的微架構在效能上超越了當時傳統的CISC處理器,在最初的較量中,Intel處理器敗下陣來,伺服器市場的處理器大部分被RISC陣營佔據。Intel的David Papworth和他的同事一起設計了Pentium Pro處理器,x86指令集被解碼成類似RISC指令的微操作指令(micro-operations,簡稱uops),以後執行的過程採用RISC核心的方式。CISC這個古老的架構通過巧妙的設計,又一次煥發生機,Intel的x86處理器的效能逐漸超過同期的RISC處理器,搶佔了伺服器市場,導致其他的處理器廠商只能向低功耗或者嵌入式方向發展。

    RISC和CISC都是時代的產物,RISC在很多思想上更為先進。Intel的CSIC指令集也憑藉向前相容這一利器,打敗所有的RISC廠商,包括DEC、SUN、Motorola和IBM,一統PC和伺服器領域。不過最近在手機移動業務方面,以ARM為首的廠商佔得先機。

    2.請簡述數值0x12345678在大小端位元組序處理器的儲存器中的儲存方式。

    在計算機系統中是以位元組為單位的,每個地址單元都對應著一個位元組,一個位元組為8個位元位。但在32位處理器中,C語言中除了8位元的char型別之外,還有16位元的short型,32bit的int型。另外,對於位數大於8位的處理器,例如16位或者32位的處理器,由於暫存器寬度大於一個位元組,那麼必然存在著如何安排多個位元組的問題,因此導致了大端儲存模式(Big-endian)和小端儲存模式(Little-endian)。例如一個16位元的short型變數X,在記憶體中的地址為0x0010,X的值為0x1122,那麼0x11為高位元組,0x22為低位元組。對於大端模式,就將0x11放在低地址中;0x22放在高地址中。小端模式則剛好相反。很多的ARM處理器預設使用小端模式,有些ARM處理器還可以由硬體來選擇是大端模式還是小端模式。Cortex-A系列的處理器可以通過軟體來配置大小端模式。大小端模式是在處理器Load/Store 訪問記憶體時用於描述暫存器的位元組順序和記憶體中的位元組順序之間的關係。

    大端模式:指資料的高位元組儲存在記憶體的低地址中,而資料的低位元組儲存在記憶體的高地址中。例如:

    記憶體檢視:

    0000430: 1234 5678 0100 1800 53ef 0100 0100 0000
    0000440: c7b6 1100 0000 3400 0000 0000 0100 ffff複製程式碼

    在大端模式下,前32位應該這樣讀:12 34 56 78。

    因此,大端模式下地址的增長順序與值的增長順序相同。

    小端模式:指資料的高位元組儲存在記憶體的高地址中,而資料的低位元組儲存在記憶體的低地址中。例如:

    記憶體檢視:

    0000430: 7856 3412 0100 1800 53ef 0100 0100 0000
    0000440: c7b6 1100 0000 3400 0000 0000 0100 ffff複製程式碼

    在小端模式下,前32位應該這樣讀:12 34 56 78。

    因此,小端模式下地址的增長順序與值的增長順序相反。

    如何檢查處理器是大端模式還是小端模式?聯合體Union的存放順序是所有成員都從低地址開始存放的,利用該特性可以輕鬆獲取CPU對記憶體採用大端模式還是小端模式讀寫。

    int checkCPU(void)
    {
    union w
    {
    int a;
    char b;
    } c;
    c.a = 1;
    return (c.b == 1);
    }複製程式碼

    如果輸出結果是true,則是小端模式,否則是大端模式。

    3.請簡述在你所熟悉的處理器(比如雙核Cortex-A9)中一條儲存讀寫指令的執行全過程。

    經典處理器架構的流水線是五級流水線:取指、譯碼、發射、執行和寫回。

    現代處理器在設計上都採用了超標量體系結構(Superscalar Architecture)和亂序執行(out-of-order)技術,極大地提高了處理器計算能力。超標量技術能夠在一個時鐘週期內執行多個指令,實現指令級的並行,有效提高了ILP(Instruction Level Parallelism)指令級的並行效率,同時也增加了整個cache和memory層次結構的實現難度。

    一條儲存讀寫指令的執行全過程很難用一句話來回答。在一個支援超標量和亂序執行技術的處理器當中,一條儲存讀寫指令的執行過程被分解為若干步驟。指令首先進入流水線(pipeline)的前端(Front-End),包括預取(fetch)和譯碼(decode),經過分發(dispatch)和排程(scheduler)後進入執行單元,最後提交執行結果。所有的指令採用順序方式(In-Order)通過前端,並採用亂序的方式(Out-of-Order,OOO)進行發射,然後亂序執行,最後用順序方式提交結果,並將最終結果更新到LSQ(Load-Store Queue)部件。LSQ部件是指令流水線的一個執行部件,可以理解為儲存子系統的最高層,其上接收來自CPU的儲存器指令,其下連線著儲存器子系統。其主要功能是將來自CPU的儲存器請求傳送到儲存器子系統,並處理其下儲存器子系統的應答資料和訊息。

    很多程式設計師對亂序執行的理解有誤差。對於一串給定的指令序列,為了提高效率,處理器會找出非真正資料依賴和地址依賴的指令,讓它們並行執行。但是在提交執行結果時,是按照指令次序的。總的來說,順序提交指令,亂序執行,最後順序提交結果。例如有兩條沒有資料依賴的資料指令,後面那條指令的讀資料先被返回,它的結果也不能先寫回到最終暫存器,而是必須等到前一條指令完成之後才可以。

    對於讀指令,當處理器在等待資料從快取或者記憶體返回時,它處於什麼狀態呢?是等在那不動,還是繼續執行別的指令?對於亂序執行的處理器,可以執行後面的指令;對於順序執行的處理器,會使流水線停頓,直到讀取的資料返回。

    如圖1.1所示,在x86微處理器經典架構中,儲存指令從L1指令cache中讀取指令,L1指令cache會做指令載入、指令預取、指令預解碼,以及分支預測。然後進入Fetch & Decode單元,會把指令解碼成macro-ops微操作指令,然後由Dispatch部件分發到Integer Unit或者FloatPoint Unit。Integer Unit由Integer Scheduler和Execution Unit組成,Execution Unit包含算術邏輯單元(arithmetic-logic unit,ALU)和地址生成單元(address generation unit,AGU),在ALU計算完成之後進入AGU,計算有效地址完畢後,將結果傳送到LSQ部件。LSQ部件首先根據處理器系統要求的記憶體一致性(memory consistency)模型確定訪問時序,另外LSQ還需要處理儲存器指令間的依賴關係,最後LSQ需要準備L1 cache使用的地址,包括有效地址的計算和虛實地址轉換,將地址傳送到L1 Data Cache中。

    圖1.1 x86微處理器經典架構圖

    如圖1.2所示,在ARM Cortex-A9處理器中,儲存指令首先通過主儲存器或者L2 cache載入到L1指令cache中。在指令預取階段(instruction prefetch stage),主要是做指令預取和分支預測,然後指令通過Instruction Queue佇列被送到解碼器進行指令的解碼工作。解碼器(decode)支援兩路解碼,可以同時解碼兩條指令。在暫存器重名階段(Register rename stage)會做暫存器重新命名,避免機器指令不必要的順序化操作,提高處理器的指令級並行能力。在指令分發階段(Dispatch stage),這裡支援4路猜測發射和亂序執行(Out-of-Order Multi-Issue with Speculation),然後在執行單元(ALU/MUL/FPU/NEON)中亂序執行。儲存指令會計算有效地址併發射到記憶體系統中的LSU部件(Load Store Unit),最終LSU部件會去訪問L1資料cache。在ARM中,只有cacheable的記憶體地址才需要訪問cache。

    圖1.2 Cortex-A9結構框圖[3]

    在多處理器環境下,還需要考慮Cache的一致性問題。L1和L2 Cache控制器需要保證cache的一致性,在Cortex-A9中cache的一致性是由MESI協議來實現的。Cortex-A9處理器內建了L1 Cache模組,由SCU(Snoop Control Unit)單元來實現Cache的一致性管理。L2 Cache需要外接晶片(例如PL310)。在最糟糕情況下需要訪問主儲存器,並將資料重新傳遞給LSQ,完成一次儲存器讀寫的全過程。

    這裡涉及計算機體系結構中的眾多術語,比較晦澀難懂,現在對部分術語做簡單解釋。

    • 超標量體系結構(Superscalar Architecture):早期的單發射結構微處理器的流水線設計目標是做到每個週期能平均執行一條指令,但這一目標不能滿足處理器效能增長的要求,為了提高處理器的效能,要求處理器具有每個週期能發射執行多條指令的能力。因此超標量體系結構是描述一種微處理器設計理念,它能夠在一個時鐘週期執行多個指令。
    • 亂序執行(Out-of-order Execution):指CPU採用了允許將多條指令不按程式規定的順序分開傳送給各相應電路單元處理的技術,避免處理器在計算物件不可獲取時的等待,從而導致流水線停頓。
    • 暫存器重新命名(Register Rename):現代處理器的一種技術,用來避免機器指令或者微操作的不必要的順序化執行,從而提高處理器的指令級並行的能力。它在亂序執行的流水線中有兩個作用,一是消除指令之間的暫存器讀後寫相關(Write-after-Read,WAR)和寫後寫相關(Write-after-Write,WAW);二是當指令執行發生例外或者轉移指令猜測錯誤而取消後面的指令時,可用來保證現場的精確。其思路為當一條指令寫一個結果暫存器時不直接寫到這個結果暫存器,而是先寫到一箇中間暫存器過渡,當這條指令提交時再寫到結果暫存器中。
    • 分支預測(Branch Predictor):當處理一個分支指令時,有可能會產生跳轉,從而打斷流水線指令的處理,因為處理器無法確定該指令的下一條指令,直到分支指令執行完畢。流水線越長,處理器等待時間便越長,分支預測技術就是為了解決這一問題而出現的。因此,分支預測是處理器在程式分支指令執行前預測其結果的一種機制。在ARM中,使用全域性分支預測器,該預測器由轉移目標緩衝器(Branch Target Buffer,BTB)、全域性歷史緩衝器(Global History Buffer,GHB)、MicroBTB,以及Return Stack組成。
    • 指令譯碼器(Instruction Decode):指令由操作碼和地址碼組成。操作碼錶示要執行的操作性質,即執行什麼操作;地址碼是操作碼執行時的操作物件的地址。計算機執行一條指定的指令時,必須首先分析這條指令的操作碼是什麼,以決定操作的性質和方法,然後才能控制計算機其他各部件協同完成指令表達的功能,這個分析工作由譯碼器來完成。例如,Cortex-A57可以支援3路譯碼器,即同時執行3條指令譯碼,而Cortex-A9處理器只能同時譯碼2條指令。
    • 排程單元(Dispatch):排程器負責把指令或微操作指令派發到相應的執行單元去執行,例如,Cortex-A9處理器的排程器單元有4個介面和執行單元連線,因此每個週期可以同時派發4條指令。
    • ALU算術邏輯單元:ALU是處理器的執行單元,主要是進行算術運算,邏輯運算和關係運算的部件。
    • LSQ/LSU部件(Load Store Queue/Unit):LSQ部件是指令流水線的一個執行部件,其主要功能是將來自CPU的儲存器請求傳送到儲存器子系統,並處理其下儲存器子系統的應答資料和訊息。

    4.請簡述記憶體屏障(memory barrier)產生的原因。

    程式在執行時的實際記憶體訪問順序和程式程式碼編寫的訪問順序不一致,會導致記憶體亂序訪問。記憶體亂序訪問的出現是為了提高程式執行時的效能。記憶體亂序訪問主要發生在如下兩個階段。

    (1)編譯時,編譯器優化導致記憶體亂序訪問。

    (2)執行時,多CPU間互動引起的記憶體亂序訪問。

    編譯器會把符合人類思考的邏輯程式碼(例如C語言)翻譯成CPU運算規則的彙編指令,編譯器瞭解底層CPU的思維邏輯,因此它會在翻譯成彙編時進行優化。例如記憶體訪問指令的重新排序,提高指令級並行效率。然而,這些優化可能會違背程式設計師原始的程式碼邏輯,導致發生一些錯誤。編譯時的亂序訪問可以通過volatile關鍵字來規避。

    #define barrier() asm volatile ("" ::: "memory")複製程式碼

    barrier()函式告訴編譯器,不要為了效能優化而將這些程式碼重排。

    由於現代處理器普遍採用超標量技術、亂序發射以及亂序執行等技術來提高指令級並行的效率,因此指令的執行序列在處理器的流水線中有可能被打亂,與程式程式碼編寫時序列的不一致。另外現代處理器採用多級儲存結構,如何保證處理器對儲存子系統訪問的正確性也是一大挑戰。

    例如,在一個系統中含有n個處理器P1~Pn,假設每個處理器包含Si個儲存器操作,那麼從全域性來看可能的儲存器訪問序列有多種組合。為了保證記憶體訪問的一致性,需要按照某種規則來選出合適的組合,這個規則叫做記憶體一致性模型(Memory Consistency Model)。這個規則需要保證正確性的前提,同時也要保證多處理器訪問較高的並行度。

    在一個單核處理器系統中,訪問記憶體的正確性比較簡單。每次儲存器讀操作所獲得的結果是最近寫入的結果,但是在多處理器併發訪問儲存器的情況下就很難保證其正確性了。我們很容易想到使用一個全域性時間比例部件(Global Time Scale)來決定儲存器訪問時序,從而判斷最近訪問的資料。這種記憶體一致性訪問模型是嚴格一致性(Strict Consistency)記憶體模型,也稱為Atomic Consistency。全域性時間比例方法實現的代價比較大,那麼退而求其次,採用每一個處理器的本地時間比例部件(Local Time Scale)的方法來確定最新資料的方法被稱為順序一致性記憶體模型(Sequential Consistency)。處理器一致性記憶體模型(Processor Consistency)是進一步弱化,僅要求來自同一個處理器的寫操作具有一致性的訪問即可。

    以上這些記憶體一致性模型是針對儲存器讀寫指令展開的,還有一類目前廣泛使用的模型,這些模型使用記憶體同步指令,也稱為記憶體屏障指令。在這種模型下,儲存器訪問指令被分成資料指令和同步指令兩大類,弱一致性記憶體模型(weak consistency)就是基於這種思想的。

    1986年,Dubois等發表的論文描述了弱一致性記憶體模型的定義。

    • 對同步變數的訪問是順序一致的。
    • 在所有之前的寫操作完成之前,不能訪問同步變數。
    • 在所有之前同步變數的訪問完成之前,不能訪問(讀或者寫)資料。

    弱一致性記憶體模型要求同步訪問是順序一致的,在一個同步訪問可以被執行之前,所有之前的資料訪問必須完成。在一個正常的資料訪問可以被執行之前,所有之前的同步訪問必須完成。這實質上把一致性問題留給了程式設計師來決定。

    ARM的Cortex-A系列處理器實現弱一致性記憶體模型,同時也提供了3條記憶體屏障指令。

    5.ARM有幾條memory barrier的指令?分別有什麼區別?

    從ARMv7指令集開始,ARM提供3條記憶體屏障指令。

    (1)資料儲存屏障(Data Memory Barrier,DMB)

    資料儲存器隔離。DMB指令保證:僅當所有在它前面的儲存器訪問操作都執行完畢後,才提交(commit)在它後面的存取訪問操作指令。當位於此指令前的所有記憶體訪問均完成時,DMB指令才會完成。

    (2)資料同步屏障(Data synchronization Barrier,DSB)

    資料同步隔離。比DMB要嚴格一些,僅當所有在它前面的儲存訪問操作指令都執行完畢後,才會執行在它後面的指令,即任何指令都要等待DSB前面的儲存訪問完成。位於此指令前的所有快取,如分支預測和TLB(Translation Look-aside Buffer)維護操作全部完成。

    (3)指令同步屏障(Instruction synchronization Barrier,ISB)

    指令同步隔離。它最嚴格,沖洗流水線(Flush Pipeline)和預取buffers(pretcLbuffers)後,才會從cache或者記憶體中預取ISB指令之後的指令。ISB通常用來保證上下文切換的效果,例如更改ASID(Address Space Identifier)、TLB維護操作和C15暫存器的修改等。

    記憶體屏障指令的使用例子如下。

    例1:假設有兩個CPU核A和B,同時訪問Addr1和Addr2地址。

    Core A:
    STR R0, [Addr1]
    LDR R1, [Addr2]

    Core B:
    STR R2, [Addr2]
    LDR R3, [Addr1]複製程式碼

    對於上面程式碼片段,沒有任何的同步措施。對於Core A、暫存器R1、Core B和暫存器R3,可能得到如下4種不同的結果。

    • A得到舊的值,B也得到舊的值。
    • A得到舊的值,B得到新的值。
    • A得到新的值,B得到舊的值。
    • A得到新的值,B得到新的值。

    例2:假設Core A寫入新資料到Msg地址,Core B需要判斷flag標誌後才讀入新資料。

    Core A:
    STR R0, [Msg] @ 寫新資料到Msg地址
    STR R1, [Flag] @ Flag標誌新資料可以讀

    Core B:
    Poll_loop:
    LDR R1, [Flag]
    CMP R1,#0 @ 判斷flag有沒有置位
    BEQ Poll_loop
    LDR R0, [Msg] @ 讀取新資料複製程式碼

    在上面的程式碼片段中,Core B可能讀不到最新的資料,因為Core B可能因為亂序執行的原因先讀入Msg,然後讀取Flag。在弱一致性記憶體模型中,處理器不知道Msg和Flag存在資料依賴性,所以程式設計師必須使用記憶體屏障指令來顯式地告訴處理器這兩個變數有資料依賴關係。Core A需要在兩個儲存指令之間插入DMB指令來保證兩個store儲存指令的執行順序。Core B需要在“LDR R0, [Msg]”之前插入DMB指令來保證直到Flag置位才讀入Msg。

    例3:在一個裝置驅動中,寫入一個命令到一個外設暫存器中,然後等待狀態的變化。

    STR R0, [Addr]        @ 寫一個命令到外設暫存器
    DSB
    Poll_loop:
    LDR R1, [Flag]
    CMP R1,#0 @ 等待狀態暫存器的變化
    BEQ Poll_loop複製程式碼

    在STR儲存指令之後插入DSB指令,強制讓寫命令完成,然後執行讀取Flag的判斷迴圈。

    6.請簡述cache的工作方式。

    處理器訪問主儲存器使用地址編碼方式。cache也使用類似的地址編碼方式,因此處理器使用這些編碼地址可以訪問各級cache。如圖1.3所示,是一個經典的cache架構圖。

    圖1.3 經典cache架構

    處理器在訪問儲存器時,會把地址同時傳遞給TLB(Translation Lookaside Buffer)和cache。TLB是一個用於儲存虛擬地址到實體地址轉換的小快取,處理器先使用EPN(effective page number)在TLB中進行查詢最終的RPN(Real Page Number)。如果這期間發生TLB miss,將會帶來一系列嚴重的系統懲罰,處理器需要查詢頁表。假設這裡TLB Hit,此時很快獲得合適的RPN,並得到相應的實體地址(Physical Address,PA)。

    同時,處理器通過cache編碼地址的索引域(Cache Line Index)可以很快找到相應的cache line組。但是這裡的cache block的資料不一定是處理器所需要的,因此有必要進行一些檢查,將cache line中存放的地址和通過虛實地址轉換得到的實體地址進行比較。如果相同並且狀態位匹配,那麼就會發生cache命中(Cache Hit),那麼處理器經過位元組選擇和偏移(Byte Select and Align)部件,最終就可以獲取所需要的資料。如果發生cache miss,處理器需要用實體地址進一步訪問主儲存器來獲得最終資料,資料也會填充到相應的cache line中。上述描述的是VIPT(virtual Index phg sical Tag)的cache組織方式,將會在問題9中詳細介紹。

    如圖1.4所示,是cache的基本的結構圖。

    圖1.4 cache結構圖

    • cache地址編碼:處理器訪問cache時的地址編碼,分成3個部分,分別是偏移域(Offset)、索引域(Index)和標記域(Tag)。
    • Cache Line:cache中最小的訪問單元,包含一小段主儲存器中的資料,常見的cache line大小是32Byte或64Byte等。
    • 索引域(Index):cache地址編碼的一部分,用於索引和查詢是在cache中的哪一行。
    • 組(Set):相同索引域的cache line組成一個組。
    • 路(Way):在組相聯的cache中,cache被分成大小相同的幾個塊。
    • 標記(Tag):cache地址編碼的一部分,用於判斷cache line存放的資料是否和處理器想要的一致。

    7.cache的對映方式有full-associative(全關聯)、direct-mapping(直接對映)和set-associative(組相聯)3種方式,請簡述它們之間的區別。為什麼現代的處理器都使用組相聯的cache對映方式?

    (1)直接對映(Direct-mapping)

    根據每個組(set)的快取記憶體行數,cache可以分成不同的類。當每個組只有一行cache line時,稱為直接對映快取記憶體。

    如圖1.5所示,下面用一個簡單小巧的cache來說明,這個cache只有4行cache line,每行有4個字(word,一個字是4個Byte),共64 Byte。這個cache控制器可以使用兩個位元位(bits[3:2])來選擇cache line中的字,以及使用另外兩個位元位(bits[5:4])作為索引(Index),選擇4個cache line中的一個,其餘的位元位用於儲存標記值(Tag)。

    在這個cache中查詢,當索引域和標記域的值和查詢的地址相等,並且有效位顯示這個cache line包含有效資料時,則發生cache命中,那麼可以使用偏移域來定址cache line中的資料。如果cache line包含有效資料,但是標記域是其他地址的值,那麼這個cache line需要被替換。因此,在這個cache中,主儲存器中所有bit [5:4]相同值的地址都會對映到同一個cache line中,並且同一時刻只有一個cache line,因為cache line被頻繁換入換出,會導致嚴重的cache顛簸(cache thrashing)。

    圖1.5 直接眏射的cache和cache地址

    假設在下面的程式碼片段中,result、data1和data2分別指向0x00、0x40和0x80地址,它們都會使用同一個cache line。

    void add_array(int data1, int data2, int *result, int size)
    {
    int i;
    for (i=0 ; i<size ; i++) {
    result[i] = data1[i] + data2[i];
    }
    }複製程式碼

    • 當第一次讀data1即0x40地址時,因為不在cache裡面,所以讀取從0x40到0x4f地址的資料填充到cache line中。
    • 當讀data2即0x80地址的資料時,資料不在cache line中,需要把從0x80到0x8f地址的資料填充到cache line中,因為地址0x80和0x40對映到同一個cache line,所以cache line發生替換操作。
    • result寫入到0x00地址時,同樣發生了cache line替換操作。
    • 所以這個程式碼片段發生嚴重的cache顛簸,效能會很糟糕。

    (2)組相聯(set associative)

    為了解決直接對映快取記憶體中的cache顛簸問題,組相聯的cache結構在現代處理器中得到廣泛應用。

    如圖1.6所示,下面以一個2路組相聯的cache為例,每個路(way)包括4個cache line,那麼每個組(set)有兩個cache line可以提供cache line替換。

    圖1.6 2路組相聯的對映關係

    地址0x00、0x40或者0x80的資料可以對映到同一個組中任意一個cache line。當cache line要發生替換操作時,就有50%的概率可以不被替換,從而減小了cache顛簸。

    8.在一個32KB的4路組相聯的cache中,其中cache line為32Byte,請畫出這個cache的cache line、way和set的示意圖。

    在Cortex-A7和Cortex-A9的處理器上可以看到32KB 大小的4路組相聯cache。下面來分析這個cache的結構圖。

    cache的總大小為32KB,並且是4路(way),所以每一路的大小為8KB:

    way_size = 32 / 4 = 8(KB)

    cache Line的大小為32Byte,所以每一路包含的cache line數量為:

    num_cache_line = 8KB/32B = 256

    所以在cache編碼地址Address中,bit[4:0]用於選擇cache line中的資料,其中bit [4:2]可以用於定址8個字,bit [1:0]可以用於定址每個字中的位元組。bit [12:5]用於索引(Index)選擇每一路上cache line,其餘的bit [31:13]用作標記位(Tag),如圖1.7所示。

    9.ARM9處理器的Data Cache組織方式使用的VIVT,即虛擬Index虛擬Tag,而在Cortex-A7處理器中使用PIPT,即物理Index物理Tag,請簡述PIPT比VIVT有什麼優勢?

    處理器在進行儲存器訪問時,處理器訪問地址是虛擬地址(virtual address,VA),經過TLB和MMU的對映,最終變成了實體地址(physical address,PA)。那麼查詢cache組是用虛擬地址,還是實體地址的索引域(Index)呢?當找到cache組時,我們是用虛擬地址,還是實體地址的標記域(Tag)來匹配cache line呢?

    cache可以設計成通過虛擬地址或者實體地址來訪問,這個在處理器設計時就確定下來了,並且對cache的管理有很大的影響。cache可以分成如下3類。

    • VIVT(Virtual Index Virtual Tag):使用虛擬地址索引域和虛擬地址的標記域。
    • VIPT(Virtual Index Physical Tag):使用虛擬地址索引域和實體地址的標記域。

    圖1.7 32KB 4路組相聯cache結構圖

    • PIPT(Physical Index Physical Tag):使用實體地址索引域和實體地址的標記域。

    在早期的ARM處理器中(比如ARM9處理器)採用VIVT的方式,不用經過MMU的翻譯,直接使用虛擬地址的索引域和標記域來查詢cache line,這種方式會導致快取記憶體別名(cache alias)問題。例如一個實體地址的內容可以出現在多個cache line中,當系統改變了虛擬地址到實體地址對映時,需要清洗(clean)和無效(invalidate)這些cache,導致系統效能下降。

    ARM11系列處理器採用VIPT方式,即處理器輸出的虛擬地址同時會傳送到TLB/MMU單元進行地址翻譯,以及在cache中進行索引和查詢cache組。這樣cache和TLB/MMU可以同時工作,當TLB/MMU完成地址翻譯後,再用物理標記域來匹配cache line。採用VIPT方式的好處之一是在多工作業系統中,修改了虛擬地址到實體地址對映關係,不需要把相應的cache進行無效(invalidate)操作。

    ARM Cortex-A系列處理器的資料cache開始採用PIPT的方式。對於PIPT方式,索引域和標記域都採用實體地址,cache中只有一個cache組與之對應,不會產生快取記憶體別名的問題。PIPT的方式在晶片設計裡的邏輯比VIPT要複雜得多。

    採用VIPT方式也有可能導致快取記憶體別名的問題。在VIPT中,使用虛擬地址的索引域來查詢cache組,這時有可能導致多個cache組對映到同一個實體地址上。以Linux kernel為例,它是以4KB大小為一個頁面進行管理的,那麼對於一個頁來說,虛擬地址和實體地址的低12bit(bit [11:0])是一樣的。因此,不同的虛擬地址對映到同一個實體地址,這些虛擬頁面的低12位是一樣的。如果索引域位於bit [11:0]範圍內,那麼就不會發生快取記憶體別名。例如,cache line是32Byte,那麼資料偏移域offset佔5bit,有128個cache組,那麼索引域佔7bit,這種情況下剛好不會發生別名。另外,對於ARM Cortex-A系列處理器來說,cache總大小是可以在晶片整合中配置的。如表1.1所示,列舉出了Cortex-A系列處理器的cache配置情況。

    表1.1 ARM處理器的cache概況

     

    Cortex-A7

    Cortex-A9

    Cortex-A15

    Cortex-A53

    資料快取實現方式

    PIPT

    PIPT

    PIPT

    PIPT

    指令快取實現方式

    VIPT

    VIPT

    PIPT

    VIPT

    L1資料快取大小

    8KB~64KB

    16KB/32KB/64KB

    32KB

    8KB~64KB

    L1資料快取結構

    4路組相聯

    4路組相聯

    2路組相聯

    4路組相聯

    L2快取大小

    128KB~1MB

    External

    512KB~4MB

    128KB~2MB

    L2快取結構

    8路組相聯

    External

    16路組相聯

    16路組相聯

    10.請畫出在二級頁表架構中虛擬地址到實體地址查詢頁表的過程。

    如圖1.8所示,ARM處理器的記憶體管理單元(Memory Management Unit, MMU)包括TLB和Table Walk Unit兩個部件。TLB是一塊快取記憶體,用於快取頁錶轉換的結果,從而減少記憶體訪問的時間。一個完整的頁表翻譯和查詢的過程叫作頁表查詢(Translation table walk),頁表查詢的過程由硬體自動完成,但是頁表的維護需要軟體來完成。頁表查詢是一個相對耗時的過程,理想的狀態下是TLB裡存有頁表相關資訊。當TLB Miss時,才會去查詢頁表,並且開始讀入頁表的內容。

    圖1.8 ARM記憶體管理架構

    (1)ARMv7-A架構的頁表

    ARMv7-A架構支援安全擴充套件(Security Extensions),其中Cortex-A15開始支援大實體地址擴充套件(Large Physical Address Extension,LPAE)和虛擬化擴充套件,使得MMU的實現比以前的ARM處理器要複雜得多。

    如圖1.9所示,如果使能了安全擴充套件,ARMv7-A處理器分成安全世界(Secure World)和非安全世界(Non-secure World,也稱為Normal World)。

    圖1.9 ARMv7-A架構的執行模式和特權

    如果處理器使能了虛擬化擴充套件,那麼處理器會在非安全世界中增加一個Hyp模式。

    在非安全世界中,執行特權被劃分為PL0、PL1和PL2。

    • PL0等級:這個特權等級執行在使用者模式(User Mode),用於執行使用者程式,它是沒有系統特權的,比如沒有許可權訪問處理器內部的硬體資源。
    • PL1等級:這個等級包括ARMv6架構中的System模式、SVC模式、FIQ模式、IRQ模式、Undef模式,以及Abort模式。Linux核心執行在PL1等級,應用程式執行在PL0等級。如果使能了安全擴充套件,那麼安全模式裡有一個Monitor模式也是執行在secure PL1等級,管理安全世界和非安全世界的狀態轉換。
    • PL2等級:如果使能了虛擬化擴充套件,那麼超級管理程式(Hypervisor)就執行這個等級,它執行在Hyp模式,管理GuestOS之間的切換。

    當處理器使能了虛擬化擴充套件,MMU的工作會變得更復雜。我們這裡只討論處理器沒有使能安全擴充套件和虛擬化擴充套件的情況。ARMv7處理器的二級頁表根據最終頁的大小可以分為如下4種情況。

    • 超級大段(SuperSection):支援16MB大小的超級大塊。
    • 段(section):支援1MB大小的段。
    • 大頁面(Large page):支援64KB大小的大頁。
    • 頁面(page):4KB的頁,Linux核心預設使用4KB的頁。

    如果只需要支援超級大段和段對映,那麼只需要一級頁表即可。如果要支援4KB頁面或64KB大頁對映,那麼需要用到二級頁表。不同大小的對映,一級或二級頁表中的頁表項的內容也不一樣。如圖1.10所示,以4KB頁的對映為例。

    圖1.10 ARMv7-A二級頁表查詢過程

    當TLB Miss時,處理器查詢頁表的過程如下。

    • 處理器根據頁表基地址控制暫存器TTBCR和虛擬地址來判斷使用哪個頁表基地址暫存器,是TTBR0還是TTBR1。頁表基地址暫存器中存放著一級頁表的基地址。
    • 處理器根據虛擬地址的bit[31:20]作為索引值,在一級頁表中找到頁表項,一級頁表一共有4096個頁表項。
    • 第一級頁表的表項中存放有二級頁表的物理基地址。處理器根據虛擬地址的bit[19:12]作為索引值,在二級頁表中找到相應的頁表項,二級頁表有256個頁表項。
    • 二級頁表的頁表項裡存放有4KB頁的物理基地址,因此處理器就完成了頁表的查詢和翻譯工作。

    如圖 1.11 所示的4KB對映的一級頁表的表項,bit[1:0]表示是一個頁對映的表項,bit[31:10]指向二級頁表的物理基地址。

    圖1.11 4KB對映的一級頁表的表項

    如圖1.12所示的4KB對映的二級頁表的表項,bit[31:12]指向4KB大小的頁面的物理基地址。

    圖1.12 4KB對映的二級頁表的表項

    (2)ARMv8-A架構的頁表

    ARMv8-A架構開始支援64bit作業系統。從ARMv8-A架構的處理器可以同時支援64bit和32bit應用程式,為了相容ARMv7-A指令集,從架構上定義了AArch64架構和AArch32架構。

    AArch64架構和ARMv7-A架構一樣支援安全擴充套件和虛擬化擴充套件。安全擴充套件把ARM的世界分成了安全世界和非安全世界。AArch64架構的異常等級(Exception Levels)確定其執行特權級別,類似ARMv7架構中特權等級,如圖1.13所示。

    • EL0:使用者特權,用於執行普通使用者程式。
    • EL1:系統特權,通常用於執行作業系統。
    • EL2:執行虛擬化擴充套件的Hypervisor。
    • EL3:執行安全世界中的Secure Monitor。

    在AArch64架構中的MMU支援單一階段的地址頁錶轉換,同樣也支援虛擬化擴充套件中的兩階段的頁錶轉換。

    • 單一階段頁表:虛擬地址(VA)翻譯成實體地址(PA)。
    • 兩階段頁表(虛擬化擴充套件):

    圖1.13 AArch64架構的異常等級

    階段1——虛擬地址翻譯成中間實體地址(Intermediate Physical Address,IPA)。

    階段2——中間實體地址IPA翻譯成最終實體地址PA。

    在AArch64架構中,因為地址匯流排頻寬最多48位,所以虛擬地址VA被劃分為兩個空間,每個空間最大支援256TB。

    • 低位的虛擬地址空間位於0x0000_0000_0000_0000到0x0000_FFFF_FFFF_FFFF。如果虛擬地址最高位bit63等於0,那麼就使用這個虛擬地址空間,並且使用TTBR0 (Translation Table Base Register)來存放頁表的基地址。
    • 高位的虛擬地址空間位於0xFFFF_0000_0000_0000到0xFFFF_FFFF_FFFF_FFFF。 如果虛擬地址最高位bit63等於1,那麼就使用這個虛擬地址空間,並且使用TTBR1來存放頁表的基地址。

    如圖1.14所示,AArch64架構處理地址對映圖,其中頁面是4KB的小頁面。AArch64架構中的頁表支援如下特性。

    圖1.14 AArch64架構地址對映圖(4KB頁)

    • 最多可以支援4級頁表。
    • 輸入地址最大有效位寬48bit。
    • 輸出地址最大有效位寬48bit。
    • 翻譯的最小粒度可以是4KB、16KB或64KB。

    11.在多核處理器中,cache的一致性是如何實現的?請簡述MESI協議的含義。

    快取記憶體一致性(cache coherency)產生的原因是在一個處理器系統中不同CPU核上的資料cache和記憶體可能具有同一個資料的多個副本,在僅有一個CPU核的系統中不存在一致性問題。維護cache一致性的關鍵是跟蹤每一個cache line的狀態,並根據處理器的讀寫操作和匯流排上的相應傳輸來更新cache line在不同CPU核上的資料cache中的狀態,從而維護cache一致性。cache一致性有軟體和硬體兩種方式,有的處理器架構提供顯式操作cache的指令,例如PowerPC,不過現在大多數處理器架構採用硬體方式來維護。在處理器中通過cache一致性協議來實現,這些協議維護一個有限狀態機(Finite State Machine,FSM),根據儲存器讀寫指令或匯流排上的傳輸,進行狀態遷移和相應的cache操作來保證cache一致性,不需要軟體介入。

    cache一致性協議主要有兩大類別,一類是監聽協議(Snooping Protocol),每個cache都要被監聽或者監聽其他cache的匯流排活動;另外一類是目錄協議(Directory Protocol),全域性統一管理cache狀態。

    1983年,James Goodman提出Write-Once匯流排監聽協議,後來演變成目前最流行的MESI協議。匯流排監聽協議依賴於這樣的事實,即所有的匯流排傳輸事務對於系統內所有的其他單元是可見的,因為匯流排是一個基於廣播通訊的介質,因而可以由每個處理器的cache來進行監聽。這些年來人們已經提出了數十種協議,這些協議基本上都是write-once協議的變種。不同的協議需要不同的通訊量,要求太多的通訊量會浪費匯流排頻寬,使匯流排爭用變多,留下來給其他部件使用的頻寬就減少。因此,晶片設計人員嘗試將保持一致性的協議所需要的匯流排通訊量減少到最小,或者嘗試優化某些頻繁執行的操作。

    目前,ARM或x86等處理器廣泛使用類似MESI協議來維護cache一致性。MESI協議的得名源於該協議使用的修改態(Modified)、獨佔態(Exclusive)、共享態(Shared)和失效態(Invalid)這4個狀態。cache line中的狀態必須是上述4種狀態中的一種。MESI協議還有一些變種,例如MOESI協議等,部分的ARMv7-A和ARMv8-A處理器使用該變種。

    cache line中有兩個標誌:dirty和valid。它們很好地描述了cache和記憶體之間的資料關係,例如資料是否有效、資料是否被修改過。在MESI協議中,每個cache line有4個狀態,可用2bit來表示。

    如表1.2和表1.3所示,分別是MESI協議4個狀態的說明和MESI協議各個狀態的轉換關係。

    表1.2 MESI協議定義

    狀態

    描述

    M(修改態)

    這行資料有效,資料被修改,和記憶體中的資料不一致,資料只存在本cache中

    E(獨佔態)

    這行資料有效,資料和記憶體中資料一致,資料只存在於本cache中

    S(共享態)

    這行資料有效,資料和記憶體中資料一致,多個cache有這個資料副本

    I(無效態)

    這行資料無效

    表1.3 MESI狀態說明

    當前狀態 操作 響應 遷移狀態
    修改態M 匯流排讀 Flush該cache line到記憶體,以便其他CPU可以訪問到最新的內容,狀態變成S態 S
    匯流排寫 Flush該cache line到記憶體,然後其他CPU修改cache line,因此本cache line執行清空資料操作,狀態變成I態 I
    處理器讀 本地處理器讀該cache line,狀態不變 M
    處理器寫 本地處理器寫該cache line,狀態不變 M
    獨佔態E 匯流排讀 獨佔狀態的cache line是乾淨的,因此狀態變成S S
    匯流排寫 資料被修改,該cache line不能再使用了,狀態變成I I
    本地讀 從該cache line中取資料,狀態不變 E
    本地寫 修改該cache line資料,狀態變成M M
    共享態S 匯流排讀 狀態不變 S
    匯流排寫 資料被修改,該cache line不能再使用了,狀態變成I I
    本地讀 狀態不變 S
    本地寫 修改了該cache line資料,狀態變成M;其他核上共享的cache line的狀態變成I M
    無效態I 匯流排讀 狀態不變 I
    匯流排寫 狀態不變 I
    本地讀 ● 如果cache miss,則從記憶體中取資料,cache line變成E;

    ● 如果其他cache有這份資料,且狀態為M,則將資料更新到記憶體,本cache再從記憶體中取資料,兩個cache line的狀態都為S;

    ● 如果其他cache有這份資料,且狀態是S或E,本cache從記憶體中取資料,這些cache line都變成S

    E/S
    本地寫 ● 如果cache miss,從記憶體中取資料,在cache中修改,狀態變成M;

    ● 如果其他cache有這份資料,且狀態為M,則要先將資料更新到記憶體,其他cache line狀態變成I,然後修改本cache line的內容

    M

    • 修改和獨佔狀態的cache line,資料都是獨有的,不同點在於修改狀態的資料是髒的,和記憶體不一致,而獨佔態的資料是乾淨的和記憶體一致。擁有修改態的cache line會在某個合適的時候把該cache line寫回記憶體中,其後的狀態變成共享態。
    • 共享狀態的cache line,資料和其他cache共享,只有乾淨的資料才能被多個cache共享。
    • I的狀態表示這個cache line無效。
    • MOESI協議增加了一個O(Owned)狀態,並在MESI協議的基礎上重新定義了S狀態,而E、M和I狀態與MESI協議的對應狀態相同。

      • O位。O位為1,表示在當前cache 行中包含的資料是當前處理器系統最新的資料複製,而且在其他CPU中可能具有該cache行的副本,狀態為S。如果主儲存器的資料在多個CPU的cache中都具有副本時,有且僅有一個CPU的Cache行狀態為O,其他CPU的cache行狀態只能為S。與MESI協議中的S狀態不同,狀態為O的cache行中的資料與儲存器中的資料並不一致。
      • S位。在MOESI協議中,S狀態的定義發生了細微的變化。當一個cache行狀態為S時,其包含的資料並不一定與儲存器一致。如果在其他CPU的cache中不存在狀態為O的副本時,該cache行中的資料與儲存器一致;如果在其他CPU的cache中存在狀態為O的副本時,cache行中的資料與儲存器不一致。

      12.cache在Linux核心中有哪些應用?

      cache line的空間都很小,一般也就32 Byte。CPU的cache是線性排列的,也就是說一個32 Byte的cache line與32 Byte的地址對齊,另外相鄰的地址會在不同的cache line中錯開,這裡是指32n的相鄰地址。

      cache在linux核心中有很多巧妙的應用,讀者可以在閱讀本書後面章節遇到類似的情況時細細體會,暫時先總結歸納如下。

      (1)核心中常用的資料結構通常是和L1 cache對齊的。例如,mm_struct、fs_cache等資料結構使用“SLAB_HWCACHE_ALIGN”標誌位來建立slab快取描述符,見proc_caches_init()函式。

      (2)一些常用的資料結構在定義時就約定資料結構以L1 Cache對齊,使用“_cacheline_internodealigned_in_smp”和“_cacheline_aligned_in_smp”等巨集來定義資料結構,例如struct zone、struct irqaction、softirq_vec[ ]、irq_stat[ ]、struct worker_pool等。

      cache和記憶體交換的最小單位是cache line,若結構體沒有和cache line對齊,那麼一個結構體有可能佔用多個cache line。假設cache line的大小是32 Byte,一個本身小於32 Byte的結構體有可能橫跨了兩條cache line,在SMP中會對系統效能有不小的影響。舉個例子,現在有結構體C1和結構體C2,快取到L1 Cache時沒有按照cache line對齊,因此它們有可能同時佔用了一條cache line,即C1的後半部和C2的前半部在一條cache line中。根據cache 一致性協議,CPU0修改結構體C1的時會導致CPU1的cache line失效,同理,CPU1對結構體C2修改也會導致CPU0的cache line失效。如果CPU0和CPU1反覆修改,那麼會導致系統效能下降。這種現象叫做“cache line偽共享”,兩個CPU原本沒有共享訪問,因為要共同訪問同一個cache line,產生了事實上的共享。解決上述問題的一個方法是讓結構體按照cache line對齊,典型地以空間換時間。include/linux/cache.h檔案定義了有關cache相關的操作,其中cacheline_aligned_in_smp的定義也在這個檔案中,它和L1_CACHE_BYTES對齊。


      [include/linux/cache.h]

      #define SMP_CACHE_BYTES L1_CACHE_BYTES

      #define 複製程式碼
      cacheline_aligned attribute ((aligned (SMP_CACHE_BYTES)))
      #define cacheline_aligned_in_smp cacheline_aligned

      #ifndef cacheline_aligned
      #define
      cacheline_aligned
      attribute ((aligned (SMP_CACHE_BYTES),
      section (".data..cacheline_aligned")))
      #endif /複製程式碼
      cacheline_aligned */

      #define cacheline_aligned_in_smp
      cacheline_aligned

      #define __
      cacheline_internodealigned_in_smp
      attribute ((aligned (1 << (INTERNODE_CACHE_SHIFT))))複製程式碼

      (3)資料結構中頻繁訪問的成員可以單獨佔用一個cache line,或者相關的成員在cache line中彼此錯開,以提高訪問效率。例如,struct zone資料結構中zone->lock和zone-> lru_lock這兩個頻繁被訪問的鎖,可以讓它們各自使用不同的cache line,以提高獲取鎖的效率。

      再比如struct worker_pool資料結構中的nr_running成員就獨佔了一個cache line,避免多CPU同時讀寫該成員時引發其他臨近的成員“顛簸”現象,見第5.3節。

      (4)slab的著色區,見第2.5節。

      (5)自旋鎖的實現。在多CPU系統中,自旋鎖的激烈爭用過程導致嚴重的CPU cacheline bouncing現象,見第4章關於自旋鎖的部分內容。

      13.請簡述ARM big.LITTLE架構,包括匯流排連線和cache管理等。

      ARM提出大小核概念,即big.LITTLE架構,針對效能優化過的處理器核心稱為大核,針對低功耗待機優化過的處理器核心稱為小核。

      如圖1.15所示,在典型big.LITTLE架構中包含了一個由大核組成的叢集(Cortex-A57)和小核(Cortex-A53)組成的叢集,每個叢集都屬於傳統的同步頻率架構,工作在相同的頻率和電壓下。大核為高效能核心,工作在較高的電壓和頻率下,消耗更多的能耗,適用於計算繁重的任務。常見的大核處理器有Cortex-A15、Cortex-A57、Cortex-A72和Cortex-A73。小核效能雖然較低,但功耗比較低,在一些計算負載不大的任務中,不用開啟大核,直接用小核即可,常見的小核處理器有Cortex-A7和Cortex-A53。

      圖1.15 典型的big.LITTLE架構

      如圖1.16所示是4核Cortex-A15和4核Cortex-A7的系統匯流排框圖。

      • Quad Cortex-A15:大核CPU簇。
      • Quad Cortex-A7:小核CPU簇。

      圖1.16 4核A15和4核A7的系統匯流排框圖

      • CCI-400模組[4]:用於管理大小核架構中快取一致性的互連模組。CCI-400只能支援兩個CPU簇(cluster),而最新款的CCI-550可以支援6個CPU簇。
      • DMC-400[5]:記憶體控制器。
      • NIC-400[6]:用於AMBA匯流排協議的連線,可以支援AXI、AHB和APB匯流排的連線。
      • MMU-400[7]:系統記憶體管理單元。
      • Mali-T604:圖形加速控制器。
      • GIC-400:中斷控制器。

      ARM CoreLink CCI-400模組用於維護大小核叢集的資料互聯和cache一致性。大小核叢集作為主裝置(Master),通過支援ACE協議的從裝置介面(Slave)連線到CCI-400上,它可以管理大小核叢集中的cache一致性和實現處理器間的資料共享。此外,它還支援3個ACE-Lite從裝置介面(ACE-Lite Slave Interface),可以支援一些IO主裝置,例如GPU Mali-T604。通過ACE-Lite協議,GPU可以監聽處理器的cache。CCI-400還支援3個ACE-Lite主裝置介面,例如通過DMC-400來連線LP-DDR2/3或DDR記憶體裝置,以及通過NIC-400匯流排來連線一些外設,例如DMA裝置和LCD等。

      ACE協議,全稱為AMBA AXI Coherency Extension協議,是AXI4協議的擴充套件協議,增加了很多特性來支援系統級硬體一致性。模組之間共享記憶體不需要軟體干預,硬體直接管理和維護各個cache之間的一致性,這可以大大減少軟體的負載,最大效率地使用cache,減少對記憶體的訪問,進而降低系統功耗。

      14.cache coherency和memory consistency有什麼區別?

      cache coherency快取記憶體一致性關注的是同一個資料在多個cache和記憶體中的一致性問題,解決快取記憶體一致性的方法主要是匯流排監聽協議,例如MESI協議等。而memory consistency關注的是處理器系統對多個地址進行儲存器訪問序列的正確性,學術上對記憶體訪問模型提出了很多,例如嚴格一致性記憶體模型、處理器一致性記憶體模型,以及弱一致性記憶體模型等。弱記憶體訪問模型在現在處理器中得到廣泛應用,因此記憶體屏障指令也得到廣泛應用。

      15.請簡述cache的write back有哪些策略。

      在處理器核心中,一條儲存器讀寫指令經過取指、譯碼、發射和執行等一系列操作之後,率先到達LSU部件。LSU部件包括Load Queue和Store Queue,是指令流水線的一個執行部件,是處理器儲存子系統的最頂層,連線指令流水線和cache的一個支點。儲存器讀寫指令通過LSU之後,會到達L1 cache控制器。L1 cache控制器首先發起探測(Probe)操作,對於讀操作發起cache讀探測操作並將帶回資料,寫操作發起cache寫探測操作。寫探測操作之前需要準備好待寫的cache line,探測工作返回時將會帶回資料。當儲存器寫指令獲得最終資料並進行提交操作之後才會將資料寫入,這個寫入可以Write Through或者Write Back。

      對於寫操作,在上述的探測過程中,如果沒有找到相應的cache block,那麼就是Write Miss,否則就是Write Hit。對於Write Miss的處理策略是Write-Allocate,即L1 cache控制器將分配一個新的cache line,之後和獲取的資料進行合併,然後寫入L1 cache中。

      如果探測的過程是Write Hit,那麼真正寫入有兩種模式。

      • Write Through(直寫模式):進行寫操作時,資料同時寫入當前的cache、下一級cache或主儲存器中。Write Through策略可以降低cache一致性的實現難度,其最大的缺點是消耗比較多的匯流排頻寬。
      • Write Back(回寫模式):在進行寫操作時,資料直接寫入當前cache,而不會繼續傳遞,當該Cache Line被替換出去時,被改寫的資料才會更新到下一級cache或主儲存器中。該策略增加了cache一致性的實現難度,但是有效降低了匯流排頻寬需求。

      16.請簡述cache line的替換策略。

      由於cache的容量遠小於主儲存器,當Cache Miss發生時,不僅僅意味著處理器需要從主儲存器中獲取資料,而且需要將cache的某個cache line替換出去。在cache的Tag陣列中,除了具有地址資訊之外還有cache block的狀態資訊。不同的cache一致性策略使用的cache狀態資訊並不相同。在MESI協議中,一個cache block通常含有M、E、S和I這4個狀態位。

      cache的替換策略有隨機法(Random policy)、先進先出法(FIFO)和最近最少使用演算法(LRU)。

      • 隨機法:隨機地確定替換的cache block,由一個隨機數產生器來生成隨機數確定替換塊,這種方法簡單,易於實現,但命中率比較低。
      • 先進先出法:選擇最先調入的那個cache block進行替換,最先調入的塊有可能被多次命中,但是被優先替換,因而不符合區域性性規律。
      • 最近最少使用演算法:LRU演算法根據各塊使用的情況,總是選擇最近最少使用的塊來替換,這種演算法較好地反映了程式區域性性規律。

      在Cortex-A57處理器中,L1 cache採用LRU演算法,而L2 cache採用隨機演算法。在最新的Cortex-A72處理器中,L2 cache採有偽隨機演算法(pseudo-random policy)或偽LRU演算法(pseudo-least-recently-used policy)。

      17.多程式間頻繁切換對TLB有什麼影響?現代的處理器是如何面對這個問題的?

      在現代處理器中,軟體使用虛擬地址訪問記憶體,而處理器的MMU單元負責把虛擬地址轉換成實體地址,為了完成這個對映過程,軟體和硬體共同來維護一個多級對映的頁表。當處理器發現頁表中無法對映到對應的實體地址時,會觸發一個缺頁異常,掛起出錯的程式,作業系統軟體需要處理這個缺頁異常。我們之前有提到過二級頁表的查詢過程,為了完成虛擬地址到實體地址的轉換,查詢頁表需要兩次訪問記憶體,即一級頁表和二級頁表都是存放在記憶體中的。

      TLB(Translation Look-aside Buffer)專門用於快取記憶體中的頁表項,一般在MMU單元內部。TLB是一個很小的cache,TLB表項(TLB entry)數量比較少,每個TLB表項包含一個頁面的相關資訊,例如有效位、虛擬頁號、修改位、物理頁幀號等。當處理器要訪問一個虛擬地址時,首先會在TLB中查詢。如果TLB表項中沒有相應的表項,稱為TLB Miss,那麼就需要訪問頁表來計算出相應的實體地址。如果TLB表項中有相應的表項,那麼直接從TLB表項中獲取實體地址,稱為TLB命中。

      TLB內部存放的基本單位是TLB表項,TLB容量越大,所能存放的TLB表項就越多,TLB命中率就越高,但是TLB的容量是有限的。目前Linux核心預設採用4KB大小的小頁面,如果一個程式使用512個小頁面,即2MB大小,那麼至少需要512個TLB表項才能保證不會出現TLB Miss的情況。但是如果使用2MB大小的大頁,那麼只需要一個TLB表項就可以保證不會出現TLB Miss的情況。對於消耗記憶體以GB為單位的大型應用程式,還可以使用以1GB為單位的大頁,從而減少TLB Miss情況。

      18.請簡述NUMA架構的特點。

      現在絕大數ARM系統都採用UMA的記憶體架構(Uniform Memory Architechture),即記憶體是統一結構和統一定址。對稱多處理器(Symmetric Multiple Processing,SMP)系統大部分都採用UMA記憶體架構。因此在UMA架構的系統中有如下特點。

      • 所有硬體資源都是共享的,每個處理器都能訪問到系統中的記憶體和外設資源。
      • 所有處理器都是平等關係。
      • 統一定址訪問記憶體。
      • 處理器和記憶體通過內部的一條匯流排連線在一起。

      如圖1.17所示,SMP系統相對比較簡潔,但是缺點也很明顯。因為所有對等的處理器都通過一條匯流排連線在一起,隨著處理器數量的增多,系統匯流排成為系統的最大瓶頸。

      NUMA系統[8]是從SMP系統演化過來的。如圖1.18所示,NUMA系統由多個記憶體節點組成,整個記憶體體系可以作為一個整體,任何處理器都可以訪問,只是處理器訪問本地記憶體節點擁有更小的延遲和更大的頻寬,處理器訪問遠端記憶體節點速度要慢一些。每個處理器除了擁有本地的記憶體之外,還可以擁有本地匯流排,例如PCIE、STAT等。

      圖1.17 SMP架構示意圖

      圖1.18 NUMA架構示意圖

      現在的x86陣營的伺服器晶片早已支援NUMA架構了,例如Intel的至強伺服器。對於ARM陣營,2016年Cavium公司釋出的基於ARMv8-A架構設計的伺服器晶片“ThunderX2”[9]也開始支援NUMA架構。

      19.ARM從Cortex系列開始效能有了質的飛越,比如Cortex-A8/A15/A53/A72,請說說Cortex系列在晶片設計方面做了哪些重大改進?

      計算機體系結構是一個權衡的藝術,尺有所短,寸有所長。在處理器領域經歷多年的優勝劣汰,市面上流行的處理器核心在技術上日漸趨同。

      ARM處理器在Cortex系列之後,加入了很多現代處理器的一些新技術和特性,已經具備了和Intel一較高下的能力,例如2016年釋出的Cortex-A73處理器。

      2005年釋出的Cortex-A8核心是第一個引入超標量技術的ARM處理器,它在每個時鐘週期內可以並行發射兩條指令,但依然使用靜態排程的流水線和順序執行方式。Cortex-A8核心採用13級整型指令流水線和10級NEON指令流水線。分支目標緩衝器(Branch Target Buffer,BTB)使用的條目數增加到512,同時設定了全域性歷史緩衝器(Global History Buffer,GHB)和返回堆疊(Return Stack,RS)部件,這些措施極大地提高了指令分支預測的成功率。另外,還加入了way-prediction部件。

      2007年Cortex-A9釋出,引入了亂序執行和猜測執行機制以及擴大L2 cache的容量。

      2010年Cortex-A15釋出,最高主頻可以到2.5GHz,最多支援8個處理器核心,單個cluster最多支援4個處理器核心,採有超標量流水線技術,具有1TB實體地址空間,支援虛擬化技術等新技術。指令預取匯流排寬度為128bit,一次可以預取4~8條指令,和Cortex-A9相比,提高了一倍。Decode部件一次可以譯碼3條指令。Cortex-A15引入了Micro-Ops概念。Micro-ops指令和X86的uops指令想法較為類似。在x86處理器中,指令譯碼單元把複雜的CISC指令轉換成等長的upos指令,再進入到指令流水線中;而Cortex-A15,指令譯碼單元把RISC指令進一步細化為Micro-ops指令,以充分利用指令流水線中的多個併發執行單元。指令譯碼單元為3路指令譯碼,在一個時鐘週期可以同時譯碼3條指令。

      2012年釋出64位的Cortex-A53和Cortex-A57,ARM開始進軍伺服器領域。Cortex-A57是首款支援64位的ARM處理器核心,採用3發亂序執行流水線(Out-of-Order pipeline),並且增加資料預取功能。

      2015年釋出Cortex-A57的升級版本Cortex-A72,如圖1.19所示。A72在A57架構的基礎上做了大量優化工作,包括新的分支預測單元,改善解碼流水線設計等。在指令分發

      圖1.19 Cortex-A72處理器架構圖[10]

      單元(Dispatch)也做了很大優化,由原來A57架構的3發射變成了5發射,同時發射5條指令,並且還支援並行執行8條微操作指令,從而提高解碼器的吞吐量。

      最新近展

      最近幾年,x86和ARM陣營都在各自領域中不斷創新。異構計算是一個很熱門的技術方向,比如Intel公司最近釋出了整合FPGA的至強伺服器晶片。FPGA可以在客戶的關鍵演算法中提供可程式設計、高效能的加速能力,另外提供了靈活性,關鍵演算法的更新優化,不需要購買大量新硬體。在資料中心領域,從事海量資料處理的應用中有不少關鍵演算法需要優化,如金鑰加速、影像識別、語音轉換、文字搜尋等。在安防監控領域,FPGA可以實現對大量車牌的並行分析。強大的至強處理器加上靈活高效的FPGA會給客戶在雲端計算、人工智慧等新興領域帶來新的技術創新。對於ARM陣營,ARM公司釋出了最新的Cortex-A75處理器以及最新處理器架構DynamIQ等新技術。DynmaIQ技術新增了針對機器學習和人工智慧的全新處理器指令集,並增加了多核配置的靈活性。另外ARM公司也釋出了一個用於資料中心應用的指令集——Scalable Vector Extensions,最高支援2048 bit可伸縮的向量計算。

      除了x86和ARM兩大陣營的創新外,最近幾年開源指令集(指令集架構,Instruction Set Architecture,ISA)也是很火熱的新發展方向。開源指令集的代表作是OpenRISC,並且Open Risk已經被Linux核心接受,成為官方Linux核心支援的一種體系結構。但是由於OpenRISC是由愛好者維護的,因此更新緩慢。最近幾年,伯克利大學正在嘗試重新設計一個全新的開源指令集,並且不受專利的約束和限制,這就是RISC-V,其中“V”表示變化(variation)和向量(vectors)。RISC-V包含一個非常小的基礎指令集和一系列可選的擴充套件指令集,最基礎的指令集只包含40條指令,通過擴充套件可以支援64位和128位運算以及變長指令。

      伯克利大學對RISC-V指令集不斷改進,迅速得到工業界和學術屆的關注。2016年,RISC-V基金會成立,成員包括谷歌、惠普、甲骨文、西部資料、華為等巨頭,未來這些大公司非常有可能會將RISC-V運用到雲端計算或者IoT等產品中。RISC-V指令集類似Linux核心,是一個開源的、現代的、沒有專利問題和歷史包袱的全新指令集,並且以BSD許可證釋出。

      目前RISC-V已經進入了GCC/Binutils的主線,相信很快也會被官方Linux核心接受。另外目前已經有多款開源和閉源的RISC-V CPU的實現,很多第三方工具和軟體廠商也開始支援RISC-V。RISC-V是否會變成開源硬體或是開源晶片領域的Linux呢?讓我們拭目以待吧!

      推薦書籍

      計算機體系結構是一門電腦科學的基礎課程,除了閱讀ARM的晶片手冊以外,還可以閱讀一些經典的書籍和文章。

      • 《計算機體系結構:量化研究方法》,英文版是《Computer Architecture : A Quantitative》,作者John L. Hennessy, David A. Patterson。
      • 《計算機組成與體系結構:效能設計》,作者William Stallings。
      • 《大話處理器:處理器基礎知識讀本》,作者萬木楊。
      • 《淺談cache memory》,作者王齊。
      • 《ARM與x86》,作者王齊。
      • 《現代體系結構上的UNIX系統:核心程式設計師的對稱多處理和快取技術》,作者Curt Schimmel。


      [1] infocenter.arm.com

      [2] Trustzone技術在ARMv6架構中已實現,在ARMv7-A架構的Cortex-A系列處理器中開始大規模使用。

      [3] 該圖參考pc.watch.impress.co.jp/docs/column…。雖然該圖出自非ARM官方資料,但是對理解Cortex-A系列處理器內部架構很有幫助。

      [4] 詳見<ARM CoreLink CCI-400 Cache Coherent Interconnect Technical Reference Manual>。

      [5] 詳見<ARM CoreLink DMC-400 Dynamic Memory Controller Technical Reference>。

      [6] 詳見<ARM CoreLink NIC-400 Network Interconnect Technical Reference>。

      [7] 詳見<ARM CoreLink MMU-400 System Memory Management Technical Reference>。

      [8] frankdenneman.nl/2016/07/06/…

      [9] www.cavium.com/ThunderX2_A…

      [10] pc.watch.impress.co.jp/img/pcw/doc…

      相關文章