一小時教你學會 ARM 架構
本文來自作者劉盼 在 GitChat 上分享,「閱讀原文」檢視交流實錄
「文末高能」
編輯 | 奕迅
架構的演變歷史
我們首先介紹 ARM Ltd,這裡先說的是公司而不是架構。ARM 的發展歷史非常久遠,超乎許多人的想象。
首先,我們提供一些背景資訊,ARM 成立於20世紀90年代末,從另一家位於劍橋的公司分拆而來,那家公司叫做 Acorn Computers,曾經是英國教育市場的著名個人臺式計算機供應商,現已不復存在。
80年代中期時,Acorn 一個小團隊接受了一個挑戰,為他們的下一代計算機挑選合適的處理器,他們起草了一個技術需求說明書,經過相當長的摸索後得出一個結論:無法找到與之相符的產品,於是 Acorn 決定自己設計處理器,一個小團隊只用了18個月就完成了設計並實現了這款處理器。
1985年4月26日,第一臺原型機在 Acorn 的劍橋辦公室中開始執行程式碼,那時它被稱為”Acorn RISC Machine”,隨著 Acorn 公司轉向衰落,處理器設計部門被分了出來,組成了一家新公司,最初叫做 Advanced RSIC Machines Ltd。現在公司和處理器都簡稱為ARM。
ARM 以其各種 RISC 處理器核心而著稱,但也出品大量的支援技術滿足晶片設計師和軟體開發者的需要,這包括物理IP,軟體模型和開發工具,圖形處理器,以及外圍裝置,注意,但是 ARM 並不生產晶片,ARM是半導體智慧財產權業務中的開拓先鋒,目前市面上大量 ARM 裝置都是由ARM分佈於世界各地的授權商製造的。
下面說下 ARM 產品在哪些領域通過通用數字產品發揮用武之地,ARM 提供:在系統晶片(SoC)上的系統級IP,以及物理IP,確保其可製造性開發工具,幫助設計和製造系統架構和軟體,當然生產出成品還需要許多其他投入,如工業設計,封裝,環境調查,作業系統,外圍IP等,這些都不是 ARM 的產品,但 ARM 有很多合作伙伴,製造出了成千上萬的裝置,從下圖中你會發現許許多多部署了基於 ARM 解決方案的應用。
從硬碟驅動器到印表機,從遊戲裝置到智慧量表,從洗衣機到電視機,可謂包羅永珍,即便如此,還沒有終點,時刻都在增長,到目前為止,以ARM架構的晶片出貨量總共達到500億片,每年還以大約80億的數字在增長,到2020年,總量有望達到1000億。
ARM 核心現在廣受歡迎的一個原因是支援一系列的效能和功能點。我相信許多人聽說 ARM 是從 ARM7TDMI 處理器核心的成功開始的,從上世紀90年代起這一核心在手機行業得到了廣泛採用,也是ARM早期成功的奠基石。
雖然現在依然受到廣泛使用,也可以購買到包含這一核心的大量部件,但不再提供 ARM7TDMI 的授權許可,現在已經從這一核心發展出以實時嵌入式空間為目標的整個產品路線圖,現在有兩大產品系列,Cortex-M系列主要用於注重成本節約的微控制器;
Cortex-R系列,提供非常高的效能和吞吐量,同時保持精準的時序屬性和可預測的中斷延時,通常用於時序關鍵的應用中,如引擎管理系統和硬碟驅動器控制器。
後來發展了整個系列的應用處理器,從產品線最初的ARM926EJ-S開始,發展到了 ARM11MP,現在包含了 Cortex-A 系列,這些處理器設計為可在要求 linux 等平臺作業系統的應用中提供可縮放的高效能。
它們融合了精密的記憶體管理功能,以及多媒體處理擴充套件指令集,從 ARM11MP 開始加入了針對多核系統的支援,Cortex-A 系列的最新核心現在以多核配置提供,這使得它們能夠真正涵蓋廣闊的功率和效能點範圍。
我們現在已經對公司淵源和架構有了些瞭解,現在讓我們進入 ARM 晶片的內部吧。
核心的工作原理
經典的ARM系統級晶片或所謂的SocC包含許多元件,其中只有一些直接源自ARM。首先,核心本身通常深度嵌入在裝置內部,在裝置範疇內通常不直接可見,而除錯埠通常是唯一和核心本身相連的外露部分,有一些粘合邏輯,如時鐘和復位積體電路。
由於 ARM 核心只有兩個中斷輸入,最常見的外設就是某種中斷控制器,在外設內部,各元件通過晶片上互聯匯流排架構相互連線,對於極大多數基於ARM的裝置而言,這就是標準的 AMBA 互聯。
AMBA 指定了兩個匯流排,稱為AXI的高效能系統匯流排,和稱為APB的低功耗外設匯流排,APB通常用於連線所有外設,AXI則用於儲存器和其他發高速裝置,大多數裝置都有一定數量的晶片上儲存以及連線外設儲存器裝置的介面,但是注意,與裝置的外部連線並不是AMBA匯流排,這僅在裝置內部使用,並不外露。
下面看看這個Soc的工作原理——程式設計器模型(programmer’s model)。謹記,A系列和R系列配置在程式設計器模型上非常相似,但M系列配置在許多非常重要的方面都有很大不同,這在接下來的講解中會指出這些差別。
從根本上說,ARM是RISC架構,你可能會否認現在的ARM核心其實不屬於RISC平臺,但它們與RISC有很大的淵源,也保留了傳統上與RISC架構相關的許多特性,例如大多數指令在一個週期內執行,暫存器集基本上是正交的,而且指令集實施載入儲存式架構,也就意味著能夠直接處理記憶體中內容的指令只有載入和儲存指令,如果需要對記憶體中的值執行任何處理,程式必須將這些值載入到暫存器中,執行所需的處理,然後將結果存回到記憶體中,其他常見架構則能夠直接操控或修改記憶體中的內容。
所有的內部暫存器除了一些受到NEON架構的向量處理功能支援外都是32位寬的,它們的內部由32位ALU處理,記憶體則通常在32位元中予以處理,這就是ARM的字長。
談到指令集時,你會發現 ARM 核心不只有一個指令集,所有 ARMv7-A 和 ARMv7-R 核心都支援32位原生 ARM 指令集和 Thumb 指令集,後者中的指令可以是32位或者16位的。
ARM 指令集釋放了核心的完整效能潛力,而Thumb指令集則提供了更出色的程式碼密度,我們把ARM和Thumb指令間切換這一過程稱為“互動工作”,不要擔心,編譯器和連結器會處理它們。一些較舊的核心支援Thumb指令集的早期版本,其中所有的指令都是16位指令,比如 ARMv7-M 核心僅就支援Thumb指令集。
如果你之前接觸過處理器架構,相信你會熟悉執行模式的概念以及特權的概念。許多架構通常支援兩種模式,分別為“Supervisor”和“User”,其中一個模式擁有特權,另一個則沒有。
在無特權模式下程式碼可能無法直接執行某些特定的操作,比如,禁用中斷,重新配置記憶體保護,或訪問特定的記憶體區域,這是大多數作業系統的基本要求,允許系統從使用者任務中保護自己。ARM核心通常支援七種基本執行模式,每種模式有權訪問自己的堆疊空間,以及一組不同的暫存器子集,除一個外其餘都是由特權的模式,如下:
其中6種是特權模式,User 模式是沒有特權的模式,作為唯一的無特權模式,User 模式供作業系統用於使用者任務和處理器。
此外,有5種模式稱為“異常模式”,每一種都與處理特定種類的異常或中斷相關,例如,當核心開始處理外部中斷時會自動進入 IRQ 模式,而 Supervisor 模式則用於處理 SVC 指令和硬體復位,這些模式分別擁有專屬的堆疊空間,以及一小組專用暫存器,我們把這一功能叫做暫存器編組,只是這些異常屬於不同的型別。
注意,上圖僅適用於 Cortex-A 和 Cortex-R 處理器,Cortex-M 微控制器的模式結構則全然不同。ARMv7-M 架構配置僅定義了兩種模式,如下圖,分別是 Thread 模式和 Handler 模式,Thread 模式沒有特權,用於應用程式程式碼, Handler 模式有特權,用於異常處理程式,當系統復位時在 Thread 模式中開始執行,遇到異常時自動變為 Handler 模式,處理程式完成後再回到 Thread 模式。
下面我們重點講下這些模式是如何與暫存器組互動工作的:
比如我們來看看核心切換到IRQ模式以處理外部異常時會發生什麼,從圖中你可以看到User模式的r13和r14切換為IRQ模式中與它們對應的暫存器,由於r13用作堆疊指標,所以這表示IRQ中斷在獨立的堆疊中進行處理。
此外也可以看到另一個暫存器也加入到集合中來,它是 Saved Program Status Register 即 SPSR,用於保留模式更改發生時處理器狀態的快照,才能使得在處理中斷事件後返回到 User 模式並恢復程式變得非常容易,當中斷處理結束後,就回到User模式,重新獲取原先的暫存器。
在以上描述的暫存器集合和組織適用於 Cotex-M 之外的所有ARM核心,Cotex-M 核心具有不同的暫存器集合和組織,見下圖。
之前一直強調Cotex-M暫存器是不同的,差別就在這裡,只有18個暫存器沒有我們在其他核心上看到的編組方案。
首先,有13個通用暫存器,其中r0到r7是低位暫存器,r8 到 r12 是高位暫存器,還有3個特殊暫存器:Stack Pointer,Link Register 和 Program Counter,最後一個暫存器是程式狀態暫存器 xPSR。
注意,Contex-M 核心有兩種處理器模式:Thread 模式和 Handler 模式,只有一個暫存器在這兩種模式之間編組,它就是 Stack Pointer。
這裡擴充套件下狀態暫存器 Program status register:
左邊28到31位是ALU條件程式碼,由資料處理指令進行可選設定,並由條件指令進行測試,還有4個額外的狀態位GE位,用於記錄來自SIMD指令的多個結果。只有這些ALU狀態位可以在處於User模式時進行修改。
最右邊的5位顯示當前的處理器模式,它們在響應異常中出現模式更改時自動設定,也可以手動修改以便在程式控制下更改模式。
J和T這兩個位記錄處理器的當前狀態,告訴核心當前正在執行哪一行指令集,可能是ARM狀態,即正在執行ARM指令;Thumb狀態,即正在執行Thumb指令;或者Jazelle狀態,即正在執行Java位元組程式碼。
I位和F位可啟用或禁用IRQ和FIQ中斷。A位允許禁用或暫時停用非同步資料中止。E位允許在程式控制下動態更改資料介面的位元組序(Little或Big位元組序),簡化了混合位元組序資料的處理。剩餘的位被“保留”或者用於和特定指令的內部系統狀態,不可由程式修改。
下面來講一下 Cortex-M 核心中可用的狀態暫存器:
你會發現它比前面講的狀態暫存器簡單的多,這也說明了Cortex-M核心的簡潔性。有一個T位,因為 Cortex-M 核心僅支援 Thumb 指令集,所以此位始終是1。最後又一個欄位,它在核心執行異常處理程式時包含當前活動的異常編號。
初學者可能會問異常時會發生什麼,在ARM架構中,異常是某種型別的事件,導致任何內容正常的程式流中出現中斷,異常可以是內部的,如記憶體轉譯錯誤;也可以是外部的,如來自外設的中斷;也可以是同步的,如SVC指令;或者是非同步的,如計時器中斷。無論原因如何,核心對所有異常的處理方式基本上相同。
當一個應用程式在逐一執行各個指令時,異常來時核心要做的第一件事就是確保它能夠在異常之後回到這一點上,為此我們必須對當前狀態抓取一個快照,所以核心複製 CPSR 並儲存在 SPSR 中,再複製PC並儲存在LR中,然後核心切換到相應的異常模式禁用進一步的中斷,確保它處於正確的狀態,接著使用矢量表確定可以找到異常處理程式的位置,每一個異常型別分別有一個條目,每一條目是一個指令,分出相關的處理程式程式碼,所以核心就是從正確的矢量表條目載入 Program Counter 執行異常處理程式。
當處理程式完成時,要返回到中斷的程式就簡單了,只要從SPSR中保留的副本還原CPSR,再從連結暫存器還原 Program Counter。當然Cortex-M在處理異常時完全是另一回事,這裡就不詳講了。
現在相信你已經瞭解了暫存器,模式和狀態的所有資訊,現在我們來談談ARM核心提供的指令集。目前市場上的大多數ARM核心至少支援兩種指令集:原生的32位 ARM 指令集,以及混合了16位和32位的Thumb指令集,我們先看看ARM指令集。
雖然這次chat不是ARM組合語言的課程,但也能讓你有足夠的瞭解。ARM指令集中的所有指令都是32位長,乍一看ARM指令的語法似乎非常複雜,不過一旦你瞭解運算子和可能的運算物件的基本結構,其實還是非常簡單的,畢竟它是RISC架構。下面舉例說明,第一個真的很簡單:
-
SUB r0, r1, #5
它顯然是個減法指令,有3個引數。第一個引數是暫存器,指定減法結果的目的地;另外兩個引數指定輸入引數,可以理解為從左到右為”r0 = r1 - 5”。 -
ADD r2, r3, r3, LSL #2
這是一個加法指令,提供一個作為第二輸入運算物件的暫存器,再指定內聯移動或迴圈運算應用到運算物件上,作為指令的一部分,這個指令可以理解為”r2等於r3加上r3向左移動兩個位置”。 -
ANDS r4, r4, #0x20
這是一個邏輯AND指令,注意這個AND有個字尾’S’,這指定將CPSR中的ALU條件程式碼設為反映該結果,ARM資料處理運算預設情況下不影響條件程式碼,所以使用這個’S’字尾來指定需要這麼做的運算。 -
ADDEQ r5, r5, r6
這又是一個ADD,它是有條件指令,該助記符帶有“EQ”字尾,表明只有在達到EQ條件為真時才會執行這一指令,如果該條件不為真,指令將表現為NOP。 -
LDR r0, [r1]
這是一個載入指令,將r1中指定地址的值載入到r0中。在指定記憶體訪問指令的地址時,我們使用方括號來表達。 -
STRNEB r2, [r3, r4]
這是儲存指令,只有在NE條件有效時才會執行操作,其次它是一個位元組層面的儲存,它將r2中最不重要的位元組儲存到r3加r4得到的記憶體位置上。
目前為止,我們只是談了 ARM 指令集,眾所周知所有 ARM 指令都是32位的,為了提供更好的程式碼密度,ARM在很久之前推出了第二指令編碼,叫做 Thumb, Thumb 所有指令都是16位的。
Thumb 程式碼通常在程式碼密度上可以改善大約35%,大多數C和C++程式碼都針對具備 Thumb 功能的核心上的 Thumb 進行編譯。
既然 Thumb 這麼好,我們為何要把真麼多精力放在ARM指令集上呢?這是因為 Thumb 是編譯程式碼的最佳目標,如果你直接在彙編程式中編寫程式碼,ARM相對是更好的選擇。下面讓我們進一步地剖析ARM的實現原理。
ARM 的技術實現
要想深入理解ARM的實現原理是個很大的學習工程,這裡一樣希望讀者讀後能對ARM起到一個總體的認識,後續可以進一步的深入學習。我們先以 ARM 彙編基礎來展開這一章的 chat。
組合語言是機器程式碼上的一個薄的語法層,它由以二進位制編碼的指令組成,這是我們的計算機所理解。那麼為什麼我們不寫程式碼呢?可想而知以二進位制來進行 coding 的話是多麼的痛苦,因此我們將編寫ARM程式集。
但是計算機本身只識別機器碼是不能執行彙編程式碼的,這就需要將彙編程式碼裝到機器程式碼中的工具 GNU Binutils 專案中的 GNU Assembler。一旦用副檔名*.s編寫程式就需要把它與其進行組合並與ld連結起來:
$ as program.s -o program.o $ ld program.o -o program
我們從最底層來看下,在最底層,電路上有電訊號,訊號是將電壓切換為兩個電平來形成的,例如0伏(關)或5伏(開)。
因為只是我們不能輕易的告訴電路電壓,只能選擇使用1/0來寫入開/關的模式,然後我們對0和1的順序進行分組,以形成機器碼指令,該指令是計算機處理器的最小工作單元,以下是機器語言的示例:
1110 0001 1010 0000 0010 0000 0000 0001
我們知道ARM處理器只能對暫存器執行資料處理,所以與儲存器的互動有兩種:從儲存器載入到暫存器,並將值從暫存器儲存到儲存器,即ARM使用載入/儲存(LDR和STR)模型進行記憶體訪問。
通常 LDR 用於將記憶體中的內容載入到暫存器中,STR用於儲存暫存器中的內容到儲存器地址。我們來舉一個基本例子:
第一看的小夥伴或許會一頭霧水,下面以一張動態圖來解釋下 ARM 是如何和儲存器互動的:
參考
正如剛開始所說的,本次 chat 不是所有 ARM 架構和技術的詳盡概覽,而是通向 ARM 世界的一扇大門,ARM 網站上有豐富的文件等你去查閱,探索。
比如(http://infocenter.arm.com)可以找到架構參考手冊,知識庫文章,常見問題解答,處理器文件,以及開發者指南等。
ARM 還有一個不斷壯大的全球大學計劃,為你提供大量的教學和培訓資源,軟體工具,以及硬體開發板。
近期熱文