閱讀153 返回首頁    go 技術社區[雲棲]


3.4 預讀機製

隨著處理器製造工藝的提高,處理器主頻越來越高,存儲器和外部設備的訪問速度雖然也得到極大的提升,但是依然不與處理器主頻的提升速度成正比,從而處理器的運行速度和外部設備的訪問速度之間的差距越來越大,存儲器瓶頸問題愈發嚴重。雖然Cache的使用有效緩解了存儲器瓶頸問題,但是僅使用Cache遠遠不夠。

因為不管Cache的命中率有多高,總有發生CacheMiss的情況。一旦Cache行出現Miss,處理器必須啟動存儲器周期,將需要的數據從存儲器重新填入Cache中,這在某種程度上增加了存儲器訪問的開銷。

使用預讀機製可以在一定程度上降低Cache行失效所帶來的影響。處理器係統可以使用的預讀機製,包括指令預讀、數據預讀、外部設備的預讀隊列和操作係統提供的預讀策略。本章將簡要介紹指令預讀,並重點介紹CPU如何對主存儲器和外部設備進行數據預讀。並以此為基礎,詳細說明PCI總線使用的預讀機製。

3.4.1 指令預讀

指令預讀是指在CPU執行某段程序時,根據程序的執行情況,提前將指令從主存儲器預讀到指令Cache中,從而當CPU需要執行這段程序時,不需要從主存儲器而是從指令Cache中獲取指令,從而極大縮短了CPU獲取指令的延時。

在一段程序中,存在大量的分支預測指令,因而在某種程度上增加了指令預讀的難度,因為有時預讀到Cache中的指令,並不會被迅速執行。因此如何判斷程序的執行路徑是指令預讀首先需要解決的問題。

CPU中通常設置了分支預測單元(Branch Predictor),在分支指令執行之前,分支預測單元需要預判分支指令的執行路徑,從而進行指令預取。但是分支預測單元並不會每次都能正確判斷分支指令的執行路徑,這為指令預讀製造了不小的麻煩,在這個背景下許多分支預測策略應運而生。

這些分支預測策略主要分為靜態預測和動態預測兩種方法。靜態預測方法的主要實現原理是利用Profiling工具,靜態分析程序的架構,並為編譯器提供一些反饋信息,從而對程序的分支指令進行優化。如在PowerPC處理器的轉移指令中存在一個“at”字段,該字段可以向CPU提供該轉移指令是否Taken的靜態信息。在PowerPC處理器中,條件轉移指令“bc”表示Taken;而“bc-”表示Not Taken

CPU的分支預測單元在分析轉移指令時可以預先得知該指令的轉移結果。目前在多數CPU中提供了動態預測機製,而且動態預測的結果較為準確。因此在實現中,許多PowerPC內核並不支持靜態預測機製,如E500內核並不支持這種機製。

CPU使用的動態預測機製是本節研究的重點。而在不同的處理器中,分支預測單元使用的動態預測算法並不相同。在一些功能較弱的處理器,如8b/16b微控製器中,分支指令的動態預測機製較為簡單。在這些處理器中,分支預測單元常使用以下幾種方法動態預測分支指令的執行。

(1)      分支預測單元每一次都將轉移指令預測為Taken,采用這種方法無疑非常簡單,而且命中率在50%以上,因為無條件轉移指令都是Taken,當然使用這種方法的缺點也是顯而易見的。

(2)      分支預測單元將向上跳轉的指令預測為Taken,而將向下跳轉的指令預測為Not Taken。分支預測單元使用的這種預測方式與多數程序的執行風格類似,但是這種實現方式並不理想。

(3)      一條轉移指令被預測為Taken,而之後這條轉移指令的預測值與上一次轉移指令的執行結果相同。

當采用以上幾種方法時,分支預測單元的硬件實現代價較低,但是使用這些算法時,預測成功的概率較低。因此在高性能處理器中,如PowerPCx86處理器並不會采用以上這3種方法實現分支預測單元。

目前在高性能處理器中,常使用BTB(Branch Target Buffer) 管理分支預測指令。在BTB中含有多個Entry,這些Entry由轉移指令的地址低位進行索引,而這個EntryTag字段存放轉移指令的地址高位。BTB的功能相當於存放轉移指令的Cache,其狀態機轉換也與Cache類似。當分支預測單元第一次分析一條分支指令時,將在BTB中為該指令分配一個Entry,同時也可能會淘汰BTB中的Entry。目前多數處理器使用LRU (Least recently used)算法淘汰BTB中的Entry

BTB的每個Entry中存在一個Saturating Counter。該計數器也被稱為Bimodal Predictor,由兩位組成,可以表示4種狀態,為0b11時為“Strongly Taken” 0b10時為“Weakly Taken”;為0b01時為“Weakly not Taken”;為0b00時為“Strongly not Taken”。

CPU第一次預測一條轉移指令的執行時,其結果為Strongly Taken,此時CPU將在BTB中為該指令申請一個Entry,並置該EntrySaturating Counter0b11。此後該指令將按照Saturating Counter的值,預測執行,如果預測結果與實際執行結果不同時,將Saturating Counter的值減1,直到其值為0b00;如果相同時,將Saturating Counter的值加1,直到其值為0b11。目前Power E500內核和Pentium處理器使用這種算法進行分支預測。

使用Saturating Counter方法在處理轉移指令的執行結果為1111011111或者0000001000時的效果較好,即執行結果大多數為0或者1時的預測結果較好。然而如果一條轉移指令的執行結果具有某種規律,如為010101010101或者001001001001時,使用Saturating Counter並不會取得理想的預測效果。

在程序的執行過程中,一條轉移指令在執行過程中出現這樣有規律的結果較為常見,因為程序就是按照某種規則書寫的,按照某種規則完成指定的任務。為此Two-Level分支預測方法應運而生。

Two-Level分支預測方法使用了兩種數據結構,一種是BHR(Branch History Register);而另一種是PHT(Pattern History Table)。其中BHRk位組成,用來記錄每一條轉移指令的曆史狀態,而PHT表含有2kEntry組成,而每一個Entry由兩位Saturating Counter組成。BHRPHT的關係如3?10所示。

3.4 預讀機製 - maoxingbing - 毛毛蟲的爹

假設分支預測單元在使用Two-Level分支預測方法時,設置了一個PBHT(Per-address Branch History Table)存放不同指令所對應的BHR。在PBHT表中所有BHR的初始值為全1,而在PHT表中所有Entry的初始值值為0b11BHRPBHT表中的使用方法與替換機製與Cache類似。

當分支預測單元分析預測轉移指令B的執行時,將首先從PBHT中獲得與轉移指令B對應的BHR,此時BHR為全1,因此CPU將從PHT的第11…11Entry中獲得預測結果0b11,即Strongly Taken。轉移指令B執行完畢後,將實際執行結果Rc更新到BHR寄存器中,並同時更新PHT中對應的Entry

CPU再次預測轉移指令B的執行時,仍將根據BHR索引PHT表,並從對應Entry中獲得預測結果。而當指令B再次執行完畢後,將繼續更新BHRPHT表中對應的Entry。當轉移指令的執行結果具有某種規律(Pattern)時,使用這種方法可以有效提高預測精度。如果轉移指令B的實際執行結果為001001001….001,而且k等於4時,CPU將以0010-0100-1001這樣的循環訪問BHR,因此CPU將分別從PHT表中的第001001001001Entry中獲得準確的預測結果。

由以上描述可以發現,Two-Level分支預測法具有學習功能,並可以根據轉移指令的曆史記錄產生的模式,在PHT表中查找預測結果。該算法由T.Y. Yeh and Y.N. Patt1991年提出,並在高性能處理器中得到了大規模應用。

Two-Level分支預測法具有許多變種。目前x86處理器主要使用“Local Branch Prediction”和“Global Branch Prediction”兩種算法。

在“Local Branch Prediction”算法中,每一個BHR使用不同的PHT表,Pentium IIPentium III處理器使用這種算法。該算法的主要問題是當PBHT表的Entry數目增加時,PHT表將以指數速度增長,而且不能利用其它轉移指令的曆史信息進行分支預測。而在“Global Branch Prediction”算法中,所有BHR共享PHT表,Pentium MPentium CoreCore 2處理器使用這種算法。

在高性能處理器中,分支預測單元對一些特殊的分支指令如“Loop”和“Indirect跳轉指令”設置了“Loop Prediction”和“Indirect Prediction”部件優化這兩種分支指令的預測。此外分支預測單元,還設置了RSB(Return Stack Buffer),當CPU調用一個函數時,RSB將記錄該函數的返回地址,當函數返回時,將從RSB中獲得返回地址,而不必從堆棧中獲得返回地址,從而提高了函數返回的效率。

目前在高性能處理器中,動態分支預測的主要實現機製是CPU通過學習以往曆史信息,並進行預測,因而Neural branch predictors機製被引入,並取得了較為理想的效果,本節對這種分支預測技術不做進一步說明。目前指令的動態分支預測技術較為成熟,在高性能計算機中,分支預測的成功概率在95%~98%之間,而且很難進一步提高。

3.4.2 數據預讀

數據預讀是指在處理器進行運算時,提前通知存儲器係統將運算過程中需要的數據準備好,而當處理器需要這些數據時,可以直接從這些預讀緩衝(通常指Cache)中獲得這些數據。[Steven P. Vanderwiel and David J. Lilja] 總結了最近出現的各類數據預讀機製,下文將以3?11為例進一步探討這些數據預讀機製。

3.4 預讀機製 - maoxingbing - 毛毛蟲的爹

3?11列舉了三個實例說明數據預讀的作用。其中實例a沒有使用預讀機製;實例b是一個采用預讀機製的理想情況;而實例c是一個采用預讀機製的次理想情況。我們假設處理器執行某個任務需要經曆四個階段,每個階段都由處理器執行運算指令和存儲指令組成。而處理器一次存儲器訪問需要5個時鍾周期。其中每一個階段的定義如下所示。

(1)      處理器執行4個時鍾周期後需要訪問存儲器。

(2)      處理器執行6個時鍾周期後需要訪問存儲器。

(3)      處理器執行8個時鍾周期後需要訪問存儲器。

(4)      處理器執行4個時鍾周期後完成。

實例a由於沒有使用預讀機製,因此在運算過程中需要使用存儲器中的數據時,不可避免的出現Cache Miss。實例a執行上述任務的過程如下。

(1)      執行第一階段任務的4個時鍾周期,之後訪問存儲器,此時將發生Cache Miss

(2)      Cache Miss需要使用一個時鍾周期,然後在第5個時鍾周期啟動存儲器讀操作。

(3)      在第10個周期,處理器從存儲器獲得數據,繼續執行第二階段任務的6個時鍾周期,之後訪問存儲器,此時也將發生Cache Miss

(4)      處理器在第17~22時鍾周期從存儲器讀取數據,並在第22個時鍾周期,繼續執行第三階段任務的8個時鍾周期,之後訪問存儲器,此時也將發生Cache Miss

(5)      處理器在第31~36時鍾周期從存儲器讀取數據,並在第36個時鍾周期,繼續執行第四階段任務的4個時鍾周期,完成整個任務的執行。

采用這種機製執行上述任務共需40個時鍾周期。而使用預讀機製,可以有效縮短整個執行過程,如3?11中的實例b所示。在實例b中在執行過程中,都會提前進行預讀操作,雖然這些預讀操作也會占用一個時鍾周期,但是這些預讀操作是值得的。合理使用這些數據預讀,完成同樣的任務CPU僅需要28個時鍾周期,從而極大提高了程序的執行效率,其執行過程如下。

(1)      首先使用預讀指令對即將使用的存儲器進行預讀,然後執行第一階段任務的4個時鍾周期。當處理器進行存儲器讀時,數據已經準備好,處理器將在Cache中獲得這個數據然後繼續執行

(2)      處理器在執行第二階段的任務時,先執行2個時鍾周期之後進行預讀操作,最後執行剩餘的4個時鍾周期。當處理器進行存儲器讀時,數據已經準備好,處理器將在Cache中獲得這個數據然後繼續執行。

(3)      處理器執行第三階段的任務時,先執行4個時鍾周期之後進行預讀操作,最後執行剩餘的4個時鍾周期。當處理器進行存儲器讀時,數據已經準備好,處理器將在Cache中獲得這個數據然後繼續執行。

(4)      處理器執行第四階段的任務,執行完4個時鍾周期後,完成整個任務的執行。

當然這種情況是非常理想的,處理器在執行整個任務時,從始至終是連貫的,處理器執行和存儲器訪存完全並行,然而這種理想情況並不多見。

首先在一個任務的執行過程中,並不易確定最佳的預讀時機;其次采用預讀所獲得數據並不一定能夠被及時利用,因為在程序執行過程中可能會出現各種各樣的分支選擇,有時預讀的數據並沒有被及時使用。

3?11所示的實例c中,預讀機製沒有完全發揮作用,所以處理器在執行任務時,Cache Miss時有發生,從而減低了整個任務的執行效率。即便這樣,實例c也比完全沒有使用預讀的實例a的任務執行效率還是要高一些。在實例c中,執行完畢3?11中所示的任務共需要34個時鍾周期。

但是在某些特殊情況下,采用預讀機製有可能會降低效率。首先在一個較為複雜的應用中,很有可能預讀的數據沒有被充分利用,一個程序可能會按照不同的分支執行,而執行每一個分支所使用的數據並不相同。其次預讀的數據即使是有效的,這些預讀的數據也會汙染整個Cache資源,在大規模並行任務的執行過程中,有可能引發Cache顛簸,從而極大地降低係統效率。

什麼時候采用預讀機製,關係到處理器係統結構的每一個環節,需要結合軟硬件資源統籌考慮,並不能一概而論。處理器提供了必備的軟件和硬件資源用以實現預讀,而如何“合理”使用預讀機製是係統程序員考慮的一個細節問題。數據預讀可以使用軟件預讀或者硬件預讀兩種方式實現,下文將詳細介紹這兩種實現方式。

3.4.3 軟件預讀

軟件預讀機製由來已久,首先實現預讀指令的處理器是Motorola88110處理器,這顆處理器首先實現了“touch load”指令,這條指令是PowerPC處理器dcbt指令的雛形。88110處理器是Motorola第一顆RISC處理器,具有裏程碑意義,這顆處理器從內核到外部總線的設計都具有許多亮點。這顆處理器是MotorolaPowerPC架構做出的巨大貢獻,PowerPC架構中著名的60X總線也源於88110處理器。

後來絕大多數處理器都采用這類指令進行軟件預讀,Inteli486處理器中提出了Dummy Read指令,這條指令也是後來x86處理器中PREFETCHh指令的雛形。

這些軟件預讀指令都有一個共同的特點,就是在處理器真正需要數據之前,向存儲器發出預讀請求,這個預讀請求不需要等待數據真正到達存儲器之後,就可以執行完畢。從而處理器可以繼續執行其他指令,以實現存儲器訪問與處理器運算同步進行,從而提高了程序的整體執行效率。由此可見,處理器采用軟件預讀可以有效提高程序的執行效率。我們考慮源代碼3?1所示的實例。

 

3?1沒有采用軟件預讀機製的程序

int ip, a[N], b[N];

 

for (i = 0; i < N; i++)

    ip = ip + a[i]*b[i];

 

這個例子在對數組進行操作時被經常使用,這段源代碼的作用是將int類型的數組a和數組b的每一項進行相乘,然後賦值給ip,其中數組ab都是Cache行對界的。源代碼3?1中的程序並沒有使用預讀機製進行優化,因此這段程序在執行時會因為a[i]b[i]中的數據不在處理器的Cache中,而必須啟動存儲器讀操作。因此在這段程序在執行過程中,必須要等待存儲器中的數據後才能繼續,從而降低了程序的執行效率。為此我們將程序進行改動,如源代碼3?2所示。

3?2 采用軟件預讀機製的程序

int ip, a[N], b[N];

for (i = 0; i < N; i++) {

    fetch(&a[i+1]);

    fetch(&b[i+1]);

    ip = ip + a[i]*b[i];

}

 

以上程序對變量ip賦值之前,首先預讀數組ab,當對變量ip賦值時,數組ab中的數據已經在Cache中,因而不需要進行再次進行存儲器操作,從而在一定程度上提高了代碼的執行效率。以上代碼仍然並不完美,首先ipa[0]b[0]並沒有被預讀,其次在一個處理器,預讀是以Cache行為單位進行的,因此對a[0]a[1]進行預讀時都是對同一個Cache行進行預讀,從而這段代碼對同一個Cache行進行了多次預讀,從而影響了執行效率。為此我們將程序再次進行改動,如源代碼3?3所示。

 

3?3軟件預讀機製的改進程序

int ip, a[N], b[N];

fetch(&ip);

fetch(&a[0]);

fetch(&b[0]);

for (i = 0; i < N-4; i+=4) {

    fetch(&a[i+4]);

    fetch(&b[i+4]);

    ip = ip + a[i]*b[i];

    ip = ip + a[i+1]*b[i+1];

    ip = ip + a[i+2]*b[i+2];

    ip = ip + a[i+3]*b[i+3];

}

for (; i < N; i++)

    ip = ip + a[i]*b[i];

 

對於以上這個例子,采用這種預讀方法可以有效提高執行效率,對此有興趣的讀者可以對以上幾個程序進行簡單的對比測試。但是提醒讀者注意,有些較為先進的編譯器,可以自動的加入這些預讀語句,程序員可以不手工加入這些預讀指令。實際上源代碼3?3中的程序還可以進一步優化。這段程序的最終優化如源代碼3?4所示。

 

3?4軟件預讀機製的改進程序

int ip, a[N], b[N];

fetch( &ip);

for (i = 0; i < 12; i += 4){

    fetch( &a[i]);

    fetch( &b[i]);

}

for (i = 0; i < N-12; i += 4){

    fetch( &a[i+12]);

    fetch( &b[i+12]);

    ip = ip + a[i] *b[i];

    ip = ip + a[i+1]*b[i+1];

    ip = ip + a[i+2]*b[i+2];

    ip = ip + a[i+3]*b[i+3];

}

for ( ; i < N; i++)

    ip = ip + a[i]*b[i];

 

因為我們還可以對ip、數據ab進行充分預讀之後;再一邊預讀數據,一邊計算ip的值;最後計算ip的最終結果。使用這種方法可以使數據預讀和計算充分並行,從而優化了整個任務的執行時間。

由以上程序可以發現,采用軟件預讀機製可以有效地對矩陣運算進行優化,因為矩陣運算進行數據訪問時非常有規律,便於程序員或編譯器進行優化,但是並不是所有程序都能如此方便地使用軟件預讀機製。此外預讀指令本身也需要占用一個機器周期,在某些情況下,采用硬件預讀機製更為合理。

3.4.4 硬件預讀

采用硬件預讀的優點是不需要軟件進行幹預,也不需要浪費一條預讀指令來進行預讀。但硬件預讀的缺點是預讀結果有時並不準確,有時預讀的數據並不是程序執行所需要的。在許多處理器中這種硬件預讀通常與指令預讀協調工作。硬件預讀機製的曆史比軟件預讀更為久遠,在IBM 370/168處理器係統中就已經支持硬件預讀機製。

大多數硬件預讀僅支持存儲器到Cache的預讀,並在程序執行過程中,利用數據的局部性原理進行硬件預讀。其中最為簡單的硬件預讀機製是OBL(One Block Lookahead)機製,采用這種機製,當程序對數據塊b進行讀取出現Cache Miss時,將數據塊b從存儲器更新到Cache中,同時對數據塊b+1也進行預讀並將其放入Cache中;如果數據塊b+1已經在Cache中,將不進行預讀。

這種OBL機製有很多問題,一個程序可能隻使用數據塊b中的數據,而不使用數據塊b+1中的數據,在這種情況下,采用OBL預讀機製沒有任何意義。而且使用這種預讀機製時,每次預讀都可能伴隨著Cache Miss,這將極大地影響效率。有時預讀的數據塊b+1會將Cache中可能有用的數據替換出去,從而造成Cache汙染。有時僅預讀數據塊b+1可能並不足夠,有可能程序下一個使用的數據塊來自數據塊b+2

為了解決OBL機製存在的問題,有許多新的預讀方法湧現出來,如“tagged預讀機製”。采用這種機製,將設置一個“tag位”,處理器訪問數據塊b時,如果數據塊b沒有在Cache中命中,則將數據塊b從存儲器更新到Cache中,同時對數據塊b+1進行預讀並將其放入Cache中;如果數據塊b已經在Cache中,但是這個數據塊b首次被處理器使用,此時也將數據塊b+1預讀到Cache中;如果數據塊b已經在Cache中,但是這個數據塊b已經被處理器使用過,此時不將數據塊b+1預讀到Cache中。

這種“tagged預讀機製”還有許多衍生機製,比如可以將數據塊b+1b+2都預讀到Cache中,還可以根據程序的執行信息,將數據塊b-1b-2預讀到Cache中。

但是這些方法都無法避免因為預讀而造成的Cache汙染問題,於是Stream buffer機製被引入。采用該機製,處理器可以將預讀的數據塊放入Stream Buffer中,如果處理器使用的數據沒有在Cache中,則首先在Stream Buffer中查找,采用這種方法可以消除預讀對Cache的汙染,但是增加了係統設計的複雜性。

與軟件預讀機製相比,硬件預讀機製可以根據程序執行的實際情況進行預讀操作,是一種動態預讀方法;而軟件預讀機製需要對程序進行靜態分析,並由編譯器自動或者由程序員手工加入軟件預讀指令來實現。

3.4.5 PCI總線的預讀機製

在一個處理器係統中,預讀的目標設備並不僅限於存儲器,程序員還可以根據實際需要對外部設備進行預讀。但並不是所有的外部設備都支持預讀,隻有“well-behavior”存儲器支持預讀。處理器使用的內部存儲器,如基於SDRAMDDR-SDRAM或者SRAM的主存儲器是“well-behavior”存儲器,有些外部設備也是“well-behavior”存儲器。這些well-behavior存儲器具有以下特點。

(1)      對這些存儲器設備進行讀操作時不會改變存儲器的內容。顯然主存儲器具有這種性質。如果一個主存儲器的一個數據為0,那麼讀取這個數據100次也不會將這個結果變為1。但是在外部設備中,一些使用存儲器映像尋址的寄存器具有讀清除的功能。比如某些中斷狀態寄存器。當設備含有未處理的中斷請求時,該寄存器的中斷狀態位為1,對此寄存器進行讀操作時,硬件將自動地把該中斷位清零,這類采用存儲映像尋址的寄存器就不是well-behavior存儲器。

(2)      對“well-behavior”存儲器的多次讀操作,可以合並為一次讀操作。如向這個設備的地址nn+4n+8n+12地址處進行四個雙字的讀操作,可以合並為對n地址的一次突發讀操作(大小為4個雙字)

(3)      對“well-behavior”存儲器的多次寫操作,可以合並為一次寫操作。如向這個設備的地址nn+4n+8n+12地址處進行四個雙字的寫操作,可以合並為對n地址的一次突發寫操作。對於主存儲器,進行這種操作不會產生副作用,但是對於有些外部設備,不能進行這種操作。

(4)      對“well-behavior”的存儲器寫操作,可以合並為一次寫操作。向這個設備的地址nn+1n+2n+3地址處進行四個單字的寫操作,可以合並為對n地址的一次DW寫操作。對主存儲器進行這種操作不會出現錯誤,但是對於有些外部設備,不能進行這種操作。

如果外部設備滿足以上四個條件,該外部設備被稱為“well-behavior”。PCI配置空間的BAR寄存器中有一個“Prefectchable”位,該位為1時表示這個BAR寄存器所對應的存儲器空間支持預讀。PCI總線的預讀機製需要HOST主橋、PCI橋和PCI設備的共同參與。在PCI總線中,預讀機製需要分兩種情況進行討論,一個是HOST處理器通過HOST主橋和PCI橋訪問最終的PCI設備;另一個是PCI設備使用DMA機製訪問存儲器。

PCI總線預讀機製的拓撲結構如3?12所示。

3.4 預讀機製 - maoxingbing - 毛毛蟲的爹

由上圖所示,HOST處理器預讀PCI設備時,需要經過HOST主橋,並可能通過多級PCI橋,最終到達PCI設備,在這個數據傳送路徑上,有的PCI橋支持預讀,有的不支持預讀。而PCI設備對主存儲器進行預讀時也將經過多級PCI橋。PCI設備除了可以對主存儲器進行預讀之外,還可以預讀其他PCI設備,但是這種情況在實際應用中極少出現,本節僅介紹PCI設備預讀主存儲器這種情況。

1 HOST處理器預讀PCI設備

PCI設備的BAR寄存器可以設置預讀位,首先支持預讀的BAR寄存器空間必須是一個Well-behavior的存儲器空間,其次PCI設備必須能夠接收來自PCI橋和HOST主橋的MRM(Memory Read Multiple)MRL(Memory Read Line)總線事務。

如果PCI設備支持預讀,那麼當處理器對這個PCI設備進行讀操作時,可以通過PCI橋啟動預讀機製(PCI橋也需要支持預讀),使用MRMMRL總線事務,對PCI設備進行預讀,並將預讀的數據暫時存放在PCI橋的預讀緩衝中。

之後當PCI主設備繼續讀取PCI設備的BAR空間時,如果訪問的數據在PCI橋的預讀緩衝中,PCI橋可以不對PCI設備發起存儲器讀總線事務,而是直接從預讀緩衝中獲取數據,並將其傳遞給PCI主設備。當PCI主設備完成讀總線事務後,PCI橋必須丟棄預讀的數據以保證數據的完整性。此外當PCI橋預讀的地址空間超越了PCI設備可預讀BAR空間邊界時,PCI設備需要“disconnect”該總線事務。

如果PCI橋支持“可預讀”的存儲器空間,而且其下掛接的PCI設備BAR空間也支持預讀時,係統軟件需要從PCI橋“可預讀”的存儲器空間中為該PCI設備分配空間。此時PCI橋可以將從PCI設備預讀的數據暫存在PCI橋的預讀緩衝中。

PCI總線規定,如果下遊PCI橋地址空間支持預讀,則其上遊PCI橋地址空間可以支持也可以不支持預讀機製。如3?12所示,如果PCIB管理的PCI子樹使用了可預讀空間時,PCIA可以不支持可預讀空間,此時PCIA隻能使用存儲器讀總線事務讀取PCI設備,而PCIB可以將這個存儲器讀總線事務轉換為MRL或者MRM總線事務,預讀PCI設備的BAR空間(如果PCI設備的BAR空間支持預讀),並將預讀的數據保存在PCIB的數據緩衝中。

但是PCI總線不允許PCIA從其“可預讀”的地址空間中,為PCIB的“不可預讀”區域預留空間,因為這種情況將影響數據的完整性。

大多數HOST主橋並不支持對PCI設備的預讀,這些HOST主橋並不能向PCI設備發出MRL或者MRM總線事務。由於在許多處理器係統中,PCI設備是直接掛接到HOST主橋上的,如果連HOST主橋也不支持這種預讀,即便PCI設備支持了預讀機製也沒有實際作用。而且如果PCI設備支持預讀機製,硬件上需要增加額外的開銷,這也是多數PCI設備不支持預讀機製的原因。

盡管如此本節仍需要對HOST處理器預讀PCI設備進行探討。假設在3?12所示的處理器係統中,HOST主橋和PCIA不支持預讀,而PCIB支持預讀,而且處理器的Cache行長度為32B(0x20)

如果HOST處理器對PCI設備的0x8000-0000~0x8000-0003這段地址空間進行讀操作時。HOST主橋將使用存儲器讀總線事務讀取PCI設備的“0x8000-0000~0x8000-0003這段地址空間”,這個存儲器讀請求首先到達PCIA,並由PCIA轉發給PCIB

PCIB發現“0x8000-0000~0x8000-0003這段地址空間”屬於自己的可預讀存儲器區域,即該地址區域在該橋的Prefetchable Memory Base定義的範圍內,則將該存儲器讀請求轉換為MRL總線事務,並使用該總線事務從PCI設備中讀取0x8000-0000~0x8000-001F這段數據,並將該數據存放到PCIB的預讀緩衝中。MRL總線事務將從需要訪問的PCI設備的起始地址開始,一直讀到當前Cache行邊界。

之後當HOST處理器讀取0x8000-0004~0x8000-001F這段PCI總線地址空間的數據時,將從PCIB的預讀緩衝中直接獲取數據,而不必對PCI設備進行讀取。

2 PCI設備讀取存儲器

PCI設備預讀存儲器地址空間時,需要使用MRL或者MRM總線事務。與MRL總線周期不同,MRM總線事務將從需要訪問的存儲器起始地址開始,一直讀到下一個Cache行邊界為止。

對於一個Cache行長度為32B(0x20)的處理器係統,如果一個PCI設備對主存儲器的0x1000-0000~0x1000-0007這段存儲器地址空間進行讀操作時,由於這段空間沒有跨越Cache行邊界,此時PCI設備將使用MRL總線事務對0x1000-0000~0x1000-001F這段地址區域發起存儲器讀請求。

如果一個PCI設備對主存儲器的0x1000-001C~0x1000-0024這段存儲器地址空間進行讀操作時,由於這段空間跨越了Cache行邊界,此時PCI設備將使用MRM總線事務對0x1000-001C~0x1000-002F這段地址空間發起存儲器讀請求。

3?12所示的例子中,PCI設備讀取0x1000-001C~0x1000-0024這段存儲器地址空間時,首先將使用MRM總線事務發起讀請求,該請求將通過PCIBA最終到達HOST主橋。HOST主橋將從主存儲器中讀取0x1000-001C~0x1000-002F這段地址空間的數據。如果PCIA也支持下遊總線到上遊總線的預讀,這段數據將傳遞給PCIA;如果PCIAB都支持這種預讀,這段數據將到達PCIB的預讀緩衝。

如果PCIAB都不支持預讀,0x1000-0024~0x1000-002F這段數據將緩存在HOST主橋中,HOST主橋僅將0x1000-001C~0x1000-0024這段數據通過PCIAB傳遞給PCI設備。之後當PCI設備需要讀取0x1000-0024~0x1000-002F這段數據時,該設備將根據不同情況,從HOST主橋、PCIA或者B中獲取數據而不必讀取主存儲器。值得注意的是,PCI設備在完成一次數據傳送後,暫存在HOST主橋中的預讀數據將被清除。PCI設備采用這種預讀方式,可以極大提高訪問主存儲器的效率。

PCI總線規範有一個缺陷,就是目標設備並不知道源設備究竟需要讀取或者寫入多少個數據。例如PCI設備使用DMA讀方式從存儲器中讀取4KB大小的數據時,隻能通過PCI突發讀方式,一次讀取一個或者多個Cache行。

假定PCI總線一次突發讀寫隻能讀取32B大小的數據,此時PCI設備讀取4KB大小的數據,需要使用128次突發周期才能完成全部數據傳送。而HOST主橋隻能一個一個的處理這些突發傳送,從而存儲器控製器並不能準確預知何時PCI設備將停止讀取數據。在這種情況下,合理地使用預讀機製可以有效地提高PCI總線的數據傳送效率。

我們首先假定PCI設備一次隻能讀取一個Cache行大小的數據,然後釋放總線,之後再讀取一個Cache行大小的數據。如果使用預讀機製,雖然PCI設備在一個總線周期內隻能獲得一個Cache行大小的數據,但是HOST主橋仍然可以從存儲器獲得2Cache行以上的數據,並將這個數據暫存在HOST主橋的緩衝中,之後PCI設備再發起突發周期時,HOST主橋可以不從存儲器,而是從緩衝中直接將數據傳遞給PCI設備,從而降低了PCI設備對存儲器訪問的次數,提高了整個處理器係統的效率。

以上描述僅是實現PCI總線預讀的一個例子,而且僅僅是理論上的探討。實際上絕大多數半導體廠商都沒有公開HOST主橋預讀存儲器係統的細節,在多數處理器中,HOST主橋以Cache行為單位讀取主存儲器的內容,而且為了支持PCI設備的預讀功能HOST主橋需要設置必要的緩衝部件,這些緩衝的管理策略較為複雜。

目前PCI總線已經逐漸退出曆史舞台,進一步深入研究PCI橋和HOST主橋,意義並不重大,不過讀者依然可以通過學習PCI體係結構,獲得處理器係統中有關外部設備的必要知識,並以此為基礎,學習PCIe體係結構。

3.5 小結

本章重點介紹了PCI總線的數據交換。其中最重要的內容是與Cache相關的PCI總線事務和預讀機製。雖然與Cache相關的PCI總線事務並不多見,但是理解這些內容對於理解PCI和處理器體係結構,非常重要。



為簡便起見,下文將轉移指令成功進行轉移稱為“Taken”;而將不進行轉移稱為“Not Taken”。

假定從訪問Cache到發現Cache Miss需要一個時鍾周期。

PowerPC處理器使用dcbt指令,而x86處理器使用PREFETCHh指令,實現這種軟件預讀。

假定從Cache中獲得數據需要一個時鍾周期。

dcbt指令是PowerPC處理器的一條存儲器預讀指令,該指令可以將內存中的數據預讀到L1或者L2 Cache中。

PREFETCHh指令是x86處理器的一條存儲器預讀指令。

預讀指令在一個時鍾周期內就可以執行完畢。

假定這個處理器係統的Cache行長度為4個雙字,即128位。

假設中斷狀態寄存器支持讀清除功能。

此時PCI設備的這段區域一定是可預讀的存儲器區域。

假設HOST主橋讀取存儲器時支持預讀,多數HOST主橋都支持這種預讀。

最後更新:2017-04-03 16:48:37

  上一篇:go Linux分區和掛載(mount命令的學習)
  下一篇:go 1.2 PCI總線的信號定義