1. 程式人生 > >ARM系列之“什麼是體系結構”

ARM系列之“什麼是體系結構”

學習《嵌入式系統――採用公開原始碼和StrongARM/XScale處理器》一書,對ARM核的體系結構有了一個比較全面而且深入的認識,糾正了以前不少錯誤的認識。現在以ARM核的體系結構為主線,按照理解的先後順序,結合自己的實際應用經驗,總結相關知識點,以獲得更大的提高。

什麼是體系結構?

所謂“體系結構”,也可以稱為“系統結構”,是指程式設計師在為特定處理器編制程式時所“看到”從而可以在程式中使用的資源及其相互間的關係。

體系結構最為重要的就是處理器所提供的指令系統和暫存器組。指令系統分為CISCComplex Instruction Set Computer,複雜指令集計算機)和RISC

Reduced Instruction Set Computer,精簡指令集計算機)。其中,嵌入式系統中的CPU往往是RISC結構的,至於原因,在後面總結完CISCRISC之後會給出。ARM核就是RISC結構,由於其在嵌入式系統佔的比重比較大,所以ARM幾乎成為RISC的代名詞了。暫存器組與採用的指令系統是密切相關的,從這一點上考慮,體系結構中最為重要的應該就是指令系統了。

在體系結構中,還有儲存器結構。現在有兩種:馮·諾依曼結構和哈佛結構。傳統的計算機採用馮·諾依曼結構,也稱為普林斯頓結構,是一種將程式指令儲存器和資料儲存器合併在一起的儲存器結構。主要特點是:程式和資料共用一個儲存空間;程式指令儲存地址和資料儲存地址指向同一個儲存器的不同物理位置;採用單一的地址及資料匯流排;程式指令和資料的寬度相同。這樣,處理器在執行指令時,必須從儲存器中取出指令解碼,再取運算元執行運算,即使單條指令也要耗費幾個甚至幾十個週期,那麼,在高速運算的時候,在傳輸通道上會出現瓶頸效應。目前使用馮·諾依曼結構的MPU

MCU有很多,如Intel 8086ARM公司的ARM7MIPS公司的MIPS處理器等。Harvard結構是一種將程式指令儲存和資料儲存分開的儲存器結構,Harvard結構是一種並行體系結構,主要特點是:程式和資料儲存在不同的儲存空間中,即程式儲存器和資料儲存器是兩個相互獨立的儲存器,每個儲存器獨立編址、獨立訪問。與兩個儲存器相對應的是系統中的4套匯流排:程式的資料匯流排和地址匯流排,資料的資料匯流排和地址匯流排。這種分離的程式匯流排和資料匯流排可允許在一個機器週期內同時獲取指令字和運算元,從而提高了執行速度,使資料的吞吐量提高了1倍。又由於程式和資料儲存器在兩個分開的物理空間中,因而取指和執行能完全重疊。目前使用Harvard
結構的如所有的DSP處理器、Motolora公司的MC68系列、Zilog公司的Z8系列、ATMEL公司的AVR系列、ARM公司的ARM9等。

體系結構與實現的關係是什麼?

一個處理器的體系結構就是它的邏輯抽象,至於這個抽象的處理器具體如何實現,則稱為(硬體)組成,或者就成為“實現”。同一個體系結構可以有不同的實現。計算機專業的基礎課程《計算機體系結構與組成原理》就是從理論研究邏輯抽象及其實現方法。這兩個方面是不同層次上的概念,原則上是相互獨立的。但是實際上是相互影響的。這裡有一個比較重要的概念就是微程式。它對指令系統的設計有比較密切的關係和影響。

CISC與微程式

微程式是指令系統實現的一種方法,另外,相同的指令系統也可以通過“硬連結”來實現。不過,CISC體系結構的處理器一般採用微程式來實現複雜指令集,當然也可以採用其他實現方法,而RISC則不需要。由於微程式在CISC中比較重要,下面CISC均指微程式實現的CISC

為了支援複雜指令集,CISC通常採用一個複雜的資料通路和一個微程式控制器。微程式控制器由一個微程式儲存器、一個微程式計數器和地址選擇邏輯構成。基本工作原理如下:在微程式儲存器中的每個字表示一個控制字,並且包含了一個時鐘週期內所有資料通路控制訊號的值。這就意味著控制字中的每一位表示一個數據通路控制線的值。而每個處理器指令是由一系列的控制字組成。當從記憶體中取出這樣的一條指令時,首先把它放在指令暫存器中,然後地址選擇邏輯再根據它來確定微程式儲存器中相應的控制字順序起始位置。當把該起始位置放到微程式計數器中後,就從微程式儲存器中找到相應的控制字,並利用它在資料通路中把資料從一個暫存器傳送到另一個暫存器。由於微程式計數器中的地址併發遞增來指向下一個控制字,因此對於序列中的每個控制器都會重複上述步驟。最後,執行完最後一個控制字,該條指令執行結束,就從記憶體中取出一條新的指令,繼續如上執行。

從巨集觀上理解,採用微程式技術的儲存器中,實際上有兩種不同層次的指令。一種是面向程式設計師(軟體)的、高層的“指令”,另一種是面向硬體實現的、低層的“微指令”。這樣,由電子元件和線路完成一套基本的功能,就可以通過微程式對這些功能進行不同的組合和編排,以實現高層指令所要求的複雜功能。而高層的“指令系統”,則相當於由函式庫向用戶提供的API。所以,微程式的採用實際上是對軟體設計中“子程式呼叫”這個概念的推廣,將其適用範圍向下推進了一個層次,從而對所要求的操作進一步化整為零,分而治之(Divide and Conquer),使指令系統的實現得到簡化。

從另一個角度來看,微程式的採用鼓勵助長了在指令系統中採用複雜指令的傾向。人們曾經普遍認為:既然微程式的採用使複雜指令的實現成為可能和並行,處理器就應該為一些典型的複雜操作和典型的高階語言成分提供相應的指令,這樣執行效率應該比較高。結果自然使人們得到一個印象,即指令系統的設計應該向高階語言看齊和靠攏,其“最高境界”就是與某種高階語言相同。在這種傾向下,各種處理器的指令系統越來越大,一些指令也越來越複雜。但是,隨著技術的發展,人們對此產生懷疑,這是有著堅實的實踐基礎的。主要有以下幾個方面:

1、為了提高運算速度,在微處理器中採用了“預取指令”等“流水線操作技術”。流水線技術是提高總體運算速度的有效手段,但是對CISC來說,控制字的數量和時鐘週期的數目對於每一條指令都可以是不同的,所以很難實現指令流水操作。

2、即使不考慮流水線,在實現中也往往因為要顧及一些特殊的複雜指令的實現,而只好讓簡單指令作出一些犧牲。問題在於這樣做從總體和全域性看是否划算,這就取決於指令的使用頻度。然而調查研究表明,20%的比較簡單的指令被反覆使用,使用量約佔整個程式的80%;而80%左右的指令則很少使用,使用量僅佔20%左右。這就是指令的2/8規律。

3、微處理器的整合規模是受半導體技術以及生產成本限制的,而微程式儲存器又是微處理器內部“佔地”面積最大的部分,複雜指令又是其中的“大戶”。如果能消減微程式儲存器的大小,或者在同樣大小的晶片中整合其他的功能部件,或者可以減少晶片的尺寸和耗電,或者二者兼得,對嵌入式系統有著特殊的意義。因為用於嵌入式系統的處理器/控制器,常常要求將這些外圍模組整合在同一晶片中。而要消減微程式儲存器的大小,從指令系統中砍掉複雜指令當然是首選,最好是不用。

4VLSI製造工藝要求CPU控制邏輯的規整性

進入20世紀80年代後,VLSI技術的發展非常迅速,往往每34年整合度就提高一個數量級。VLSI工藝要求規整性,而CISC處理器中,為了實現大量複雜的指令,控制邏輯極不規整,給VLSI工藝造成很大困難。

5、現代的微處理器都帶有較大的快取記憶體,執行中訪問記憶體時命中快取記憶體的概率可以達到較高的程度。命中時,快取記憶體的訪問速度與微程式儲存器相似。這樣使得許多簡單指令沒有必要用微程式來實現,而複雜的指令,用微程式來實現和用簡單指令組成的子程式實現已經沒有多大區別。

基於以上原因,RISC的概念和技術應運而生了。

RISC的特點

RISC是在繼承CISC的成功技術並且克服其缺點的基礎上產生並發展起來的,大部分RISC具有以下特點。

1RISC體系結構中的指令系統都比較小,即不同指令的數量較少,並且只提供簡單指令。所謂“精簡指令集”,一方面是說指令集的大小,另一方面是說每條指令的複雜程度,兩個條件缺一不可。

2、每條運算指令的運算元都必須先預存於暫存器中。也就是說,一般運算指令在執行過程中是不訪問記憶體的,所有的運算元不是立即數就是來自某個暫存器,而執行結果也只能存入暫存器。為此,需要配備專門的訪內指令。主要是兩條:LOADSTORE,都是以微處理器為中心進行的命名。Load就是從記憶體load資料到某暫存器,Store就是store暫存器中的資料到記憶體中。這樣所有指令的執行長度就變得整齊劃一,而定址方式的實現也就變得既簡單又整齊。

3、指令的格式也整齊劃一,典型的RISC體系結構中,每條指令長度為32位,包括一個操作碼位段和三個運算元位段。這樣,由於每條指令都是32位,其執行長度固定,採用流水線以後就可以基本上達到每個時鐘脈衝執行一條指令的目標,因此,“每個時鐘脈衝一條指令”成了RISC擁護者的旗號和目標。

4、由於一般指令的運算元都必須事先存放在暫存器中,計算過程中的中間結果自然也就不應該存放到記憶體中,也應該存放到暫存器中。這樣就需要較多的通用暫存器,而傳統的CISC暫存器的數量一般不超過16個,不適合RISC體系結構的要求。所以RISC體系結構一般都有比較多的暫存器組,通常是32個。

5RISC的子程式呼叫過程,或“子程式連結”,與CISC的不同。在CISC體系結構中,call指令將返回地址存入記憶體中的堆疊,因而需要訪問記憶體。而RISC的原則之一就是儘量少訪問記憶體,所以返回地址是放在暫存器中而不是堆疊中的。這樣,一般專門拿一個暫存器作為“(子程式)連結暫存器”(R14,對映為LR)。這樣如果呼叫的是底層的“葉”子程式,便不需要為子程式連結訪問記憶體,而如果是中層的子程式,則可按照一定的呼叫約定將此暫存器的內容濺出到堆疊中。此外,呼叫引數的傳遞也不通過堆疊,而是通過暫存器。對於有大量子程式呼叫,特別是有大量很小的“葉”子程式呼叫的軟體,這樣的連結方法可以節省很多訪問記憶體的開銷,有利於提高效率。

6、中斷的過程也可以看成是特殊的子程式連結。對於RISC體系結構,對中斷是的暫存器作出約定,那些暫存器的內容是必須儲存,因而在中斷處理程式中可以自由使用。如果中斷處理程式需要更多的暫存器,那麼就先行一步濺出這些暫存器的內容。

CISC架構相比較,儘管RISC架構有諸多的優點,但是決不能認為可以取代CISC架構。事實上,RISCCISC架構各有優勢,而且界限並不那麼明顯。現代的CPU往往採用CISC的外圍,內部加入了RISC的特性,如超長指令集CPU就是融合了RISCCISC的優勢,成為未來CPU的發展方向之一。

嵌入式系統的處理器為什麼大都是RISC結構的?

1、在同樣的整合規模下,RISCCPU核在晶片上佔用面積要小得多。這樣就可以將一些如外設介面等等的外圍模組整合在同一塊晶片上。

2、有利於減少晶片的尺寸和功耗(有利於散熱)。

3、結構簡單,開發成本低。

4、對於實時應用,RISC指令具有均勻劃一並且較小的執行長度,因此有利於中斷延遲的可預測性,並且有利於縮短中斷延遲。

ARM的四層含義

1ARM是一種RISC MPU/MCU的體系結構,如同x86架構是一種CISC體系結構一樣。另外,還有MIPS架構、PowerPC架構等等。

2ARMAdvanced RISC Machine Limited公司的簡稱。

3ARMAdvanced RISC Machine Limited公司的產品,該產品以IP CoreIntellectual Property Core,智慧財產權核)的形式提供的。

4ARM還用以泛指許多半導體廠商買了這種設計後生產出來的“ARM處理器”系列的晶片及其衍生產品。

半導體廠商固然可以光購買ARM公司的設計而直接生產ARM處理器晶片,但是更好的方法是以ARM處理器為核心,在同一塊晶片上配上自己開發的外圍模組,形成面向特定應用和市場的專用晶片,甚至“片上系統(System on a Chip SoC)”。這樣,作為專用處理器/控制器晶片的生產商既可以減少開發中的風險,又可以大大縮短開發週期,降低成本。所以,“ARM處理器”一般是作為“核心”存在於一些專用處理器/控制器的內部,因而又常常叫做“ARM核”。特別地,如果一個處理器核不帶浮點運算功能,有時候就對此特別加以強調,稱之為“整形核”。

ARM體系結構版本及其變種的含義

ARM體系結構的版本的字串由下面幾個部分組成:

1、字串ARMv

2ARM指令集版本號

3、表示變種的字元。由於在ARM體系版本4以後,M變種成為系統的標準功能,字元M通常不需要列出。

4、使用字元x表示排除某種功能。

例如:ARMv4T表示ARM版本4,支援Thumb

變種字母含義:

1T――支援Thumb

2M――Multiplier,支援長乘法指令

3E――Extended,支援增強型DSP指令

4J――Java加速器Jazelle

5SIMD――支援ARM媒體功能擴充套件

還有幾個字母含義如下:

1D――Debug,提供除錯支援

2I――晶片上帶有內建的ICE,從而支援程式內的斷點和資料空間的“觀察點”設定。

ARM的儲存器結構

ARM體系結構的第4版為界限,之前採用的是馮·諾依曼結構,此版本及其之後的版本都是採用Harvard結構。不過這種結構是一種Modified Harvard,就是將CPU訪問記憶體的通道分成指令和資料兩個相互獨立的通道。CPU雖然可以同時從“記憶體”取指令和讀寫資料,但實際上只是分別訪問兩個快取記憶體。而這兩個快取記憶體最後還是通過共同的匯流排訪問記憶體。

ARM指令系統

系統的指令系統介紹這裡就不羅列了,沒有多大意義。這裡只是總結一些小的知識點和經驗,便於對ARM指令系統有更為深入的理解。

1ARM處於使用者態模式時,可見的通用暫存器是16個,即R0-R15。外加一個CPSRCurrent Program Status Register,當前程式狀態暫存器),總共是17個。其中3個有特殊用途:R15――程式計數器PCR14――程式連結暫存器LRR13――堆疊指標SP

2ARM7中執行狀態:usrfiqirqsveabtundsys。除使用者狀態外的6中均為系統狀態(特權模式)。當CPU從使用者狀態進入系統狀態時,或者發生系統狀態間的切換時,都需要將CPSR的內容儲存起來,以備將來恢復原來的執行狀態,所以每個系統狀態都有個“儲存程式狀態暫存器(Saved Program Status Register)”SPSRCPSRSPSR都是32位的,實際上只用了其中的一部分。

3ARM體系結構中,每一條指令都可以條件執行。例如:

cmp r0, #0

addeq r0, r2, r5

addne r0, r0, r0, lsl #1

等價於:

if (r0 = = 0) {

r0 = r2 + r5;

}

else {

r0 = r0 * 3;

}

可見,利用這種獨特的條件執行可以得到很簡潔的彙編程式碼,要不然就得在彙編程式碼中插入條件轉移指令。不過,當ifelse下面的條件執行部分較大時,插入條件轉移指令更合適。有人做過分析,當一個條件執行部分的大小超過三條指令時,就還是以插入條件轉移指令為好;反之則以條件執行指令為好。

在條件轉移的設計中,同時要注意程式的可讀性。雖然程式都能得到正確的結果,但是安排合理的程式更具有可讀性,也更加易於維護。寧肯犧牲一部分時間來完善,也不要在事後程式碼維護的時候才意識到。

4、關於堆疊定址

堆疊是一塊連續的記憶體,也可以說是儲存區,不過因為作為特定的資料結構,它對資料儲存順序是有要求的,即先進後出(或者說是後進先出)。堆疊定址時,使用SP指向一塊儲存區域,指標所指向的單元就是堆疊的棧頂。儲存器堆疊可以分為兩種:

一種是向上生長,就是向著高地址方向生長,稱為遞增堆疊。

一種是向下生長,就是向著低地址方向生長,稱為遞減堆疊。

另外,堆疊指標指向最後壓入的堆疊的有效資料項,稱為滿堆疊;堆疊指標指向下一個要放入的空位置,稱為空堆疊。這樣,就有四種組合:滿遞增、空遞增、滿遞減、空遞減。

D代表DescendingA代表AscendingF代表FullE代表Empty

寫程式通過分析結果來理解堆疊定址是一種最好的方法,形象直觀。現在根據彙編實驗分析結果對上述堆疊定址作出總結。

入棧規律:

1)滿堆疊操作先調整SP,然後存入資料。

2)空堆疊操作先存入資料,然後調整SP

3)遞增堆疊調整SP時,執行SP=SP+4

4)遞減堆疊調整SP時,執行SP=SP-4

出棧規律正好與入棧相反,也就是入棧的逆操作。

1)空堆疊操作先調整SP,然後存入資料。

2)滿堆疊操作先存入資料,然後調整SP

3)遞減堆疊調整SP時,執行SP=SP+4

4)遞增堆疊調整SP時,執行SP=SP-4

明確了這四個規律,就很容易分析各種堆疊定址方式對應的堆疊分佈情況了。

stmfd sp!, {r4-r11}

假設初始SP0x0400,那麼執行完畢後記憶體0x03E0-0x03FF儲存暫存器R4-R11的內容。

stmed sp!, {r4-r11}

假設初始SP0x0400,那麼執行完畢後記憶體0x03E4-0x0403儲存暫存器R4-R11的內容。

實際應用中,只選用一種方式使用就可以了。最常用最典型的就是字尾為“FD”時的結構,這是人們熟悉的堆疊結構。

stmfd sp!, {r4-r11, lr} /*入棧*/

ldmfd sp!, {r4-r11, lr} /*出棧*/

5、關於轉移指令

ARM的轉移指令是獨特的,最簡單最基本的轉移指令是b,表示“branch”,例如:

b reset

這裡“標籤”是一段程式的入口,一般是一個函式,或者是彙編程式的一個標籤。在基本的操作碼b後面加上條件字尾EQNEGTLT等等,就成了條件轉移指令。由於指令的長度只有32位,編碼在指令中的就只能是一個相對於PC當前值的位移,而不可能是個32位的絕對地址。所以,這是一條“相對轉移”指令。如果要做絕對轉移,那麼就得采用別的手段,例如,可以把轉移的目標地址放到暫存器R4中,那麼將它傳遞到PC中就可以完成轉移。

mov pc, r4

這就變成絕對轉移了,但是,b指令不允許以暫存器的內容作為目標地址。

事實上,子程式呼叫的返回指令mov pclr也是絕對轉移。

ARM處理器中有一條執行指令的流水線,不管是相對轉移還是絕對轉移,當CPU執行到引起轉移時,即引起pc突變的指令時,其後面的幾條指令已經被取入了流水線,甚至已經對指令解碼了。程式計數器pc的突變迫使流水線捨棄這些已經在流水線中的指令,使流水線短暫斷流,然後從新的地址取指令,並又逐步“灌滿”流水線。在這個過程中,CPU可能會有一個短暫(例如幾個時鐘週期)的“無所事事”的空隙。在典型的RISC結構中,一般都把轉移前的最後一條指令改放到轉移指令後面,或者把轉移目標處的第一條指令搬過來放到轉移指令後面,稱為指令排程。這樣,把本來會浪費掉的幾個時鐘週期利用起來,效率當然提高了,但是對於程式碼的閱讀、理解以及除錯,都有不利的影響。所以常常受到來自CISC陣營的批評和攻擊。ARM體系結構的設計者並沒有緊跟RISC的潮流,仍然採用傳統的方法,寧可浪費一點效率也要保證程式的簡潔,所以不採用指令排程,而只是丟棄已經進入流水線的指令。畢竟,大多數情況下,因此而降低的效率只佔很小的比例。

相對轉移指令b有個變形bl,意為“轉移並連線(Branch and Link)”,專門用於子程式呼叫。執行這條指令時CPUpc的當前值(指向下一條指令)儲存在暫存器lr中,即R14中,同時轉向目標地址。這是,要從子程式返回時,只要把lr的內容寫入pc就行了。例如:

bl uHALir_ReadMode

uHALir_ReadMode:

mov pc, lr

這裡考慮為什麼不像傳統的做法那樣自動把返回地址儲存在堆疊中?原因前面提到過了,RISC的設計原則之一就是儘量少訪問記憶體,而改用暫存器代替,這樣可以有效的提高效率。堆疊是在記憶體中,把返回的地址放到堆疊意味著訪問記憶體,而暫存器間的訪問比訪問記憶體操作要快得多。這樣,先通過較快的方法進入到子程式,如果還需要進一步呼叫更深層的子程式,則可以到那時再把lr的內容儲存(“濺出”)堆疊中,如果不需要訪問深層子程式,則可以省去為返回地址而讀/寫記憶體的操作。程式在執行時會形成一顆“子程式呼叫樹”。統計表明,對葉節點,即底層子程式的呼叫常常佔很高的比例,這是因為對底層子程式的呼叫往往是在迴圈中進行的,而且,底層子程式本身往往是很小的,為呼叫本身很小的底層子程式訪問記憶體兩次,所佔的比例就不小了。