ARMv8 與 Linux的新手筆記
Posted Jarry_le
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARMv8 與 Linux的新手筆記相关的知识,希望对你有一定的参考价值。
ARMv8 與 Linux的新手筆記
by loda
hlchou@gmail.com
從iPhone 5S採用ARMv8處理器架構後,對於ARMv8 64bits的相關討論很多,也受到大家關注,Google也如預期在2014年底前推出了android Lollipop (也就是Android 5.0) 操作環境.(官方網站http://www.android.com/versions/lollipop-5-0/ ) ,這也是目前第一個同時支援32bits與64bits執行環境的Android操作環境,而面對新的ARMv8 64bits技術,目前主要還是以ARM本身所公布的技術資料為主,筆者也會基於ARM與Google Android正是公開的技術資料為主,來跟大家介紹ARMv8相關的軟體開發技術,並會佐以Linux Kernel Source Code做對照,讓大家除了知道技術名詞外,也可以實際的在代碼上有所掌握,,期待這能對產業軟體開發者有一點點幫助.
首先,讓我們簡單的回顧一下ARM處理器的歷史,簡單的區分, 像是ARMv5核心架構主要用於ARM9處理器系列,而ARMv6的核心架構,則用於ARM11系列產品,雖然前述處理器架構都是32bits的指令,但為了更節省記憶體的需求,ARM也推出了16bits Thumb指令,用以在記憶體受限的環境下,可以在些微影響效能的前提,達到兩者的平衡,隨後又延伸這個需求,提供 16/32bits 效率更高的Thumb2指令集架構. 再來就是,近期智慧型手機最主流的Cortex-A系列處理器,則採用的是32bits ARMv7的處理器架構技術,而在導入64bits ARMv8架構前,也開始有一些更大記憶體需求的產品應運而生,ARM提供給32bits架構不超過4GB記憶體限制的解決方案就是LPAE(Large Physical Address Extension)技術,用以支援最大40bit的實體記憶體定址範圍,然在這機制下,每個Process所見的記憶體空間仍受限於32bits 4GB定址.
本文所主要討論的ARMv8架構,同時支援了 32bits 與 64bits兩個模式,並在32bits模式下,也向後相容之前ARMv7架構32bits的軟體產品,藉此可提供目前現有ARM 32bits架構的產品一個平順過渡到ARMv8 32/64bits架構的無縫接軌. ARM並支援在ARMv8 64bits的產品上,同時執行32bits的既有軟體模塊與針對最新64bits軟體架構所設計的軟件,兩個不同指令集與記憶體框架的行程可以同時執行,並透過系統提供的IPC(Inter-Process Communication) 進行協同工作.
參考ARM網頁(http://www.arm.com/zh/products/processors/cortex-a/cortex-a53-processor.php ),目前已知對外公開的ARMv8架構包含了Cortex A53與A57,這兩者可用於配搭 big.LITTLE的架構,或可單獨採用.
如下表,為大家常見的ARMv7與ARMv8處理器的簡要比較,供參考.
CPU | Core Version | Pipeline | DMIPS/MHz | Physical Memory Addresses |
Cortex A7 | ARMv7 | In-Order | 1.9 | LPAE 40bits |
Cortex A15 | ARMv7 | Out-of-Order | 3.5 | LPAE 40bits |
Cortex A53 | ARMv8 | In-Order | 2.3 | 40bits |
Cortex A57 | ARMv8 | Out-of-Order | 4.1 | 44bits |
由於本文會有不少ARMv8 與 32/64bits相關技術名詞引用,為了便於訊息的一致性,筆者在此先定義如下
Items | Explain |
AArch64 | 指基於64bits運作的ARMv8 Architecture.( General Purpose Registers X0-X30) |
AArch32 | 指基於32bits運作的ARMv8 Architecture.並且相容於原本的ARMv7 Architecture. (General Purpose RegistersR0-R15) |
A64 | 指在AArch64模式下支援的 ARM 64bits 指令集. |
A32 | 指ARMv7架構下支援的 ARM 32bits 指令集,在ARMv8中也有新加入的A32指令集. |
T32 | 指ARMv7架構下支援的 Thumb2 16/32bits指定集,在ARMv8中也有新加入的T32指令集. |
本文所引用的軟體代碼,會以筆者撰寫本文時參考的Linux Kernel 3.16.3 為基礎,線上瀏覽的Source Code網址可以參考 http://hala01.com/src/linux/linux-3.16.3/HTML/ .最後,撰寫時雖盡力確保資料無誤,若仍有所疏失還請多加包含.
ARMv8的基礎認識
目前的理解,談到ARMv8最多人引用的圖會是ARM網站(http://www.arm.com/zh/products/processors/instruction-set-architectures/index.php)所提供如下的架構示意圖(http://www.arm.com/zh/images/roadmap/V5_to_V8_Architecture.jpg). 簡要來說, ARMv8的架構沿襲以往ARMv7 與之前處理器技術的基礎,除了有既有16/32bits Thumb2指令的支援外,也向前相容現有的ARM 32bits指令集. 基於64bits的AArch64架構,除了新增A64 (ARM 64bits)指令外,也擴充現有的A32 (ARM 32bits) 與T32 (Thumb2 32bits)指令編碼可在ARMv8 AArch32架構中執行,因此若有特別針對ARMv8 32bits指令集新增指令所撰寫的程式碼(例如: 32bits Crypto),這類程式將只能在ARMv8或之後新的處理器架構上執行,而無法向前相容於ARMv8之前的32bits實體處理器執行上.
AArch64 表示為支援64bits Execution State,而AArch32則是支援 ARMv8之前的32bits Execution State,並有因應AArch64新增額外的能力,確保相容於ARMv7-A的架構. AArch32/64也都支援SIMD (Single-Instruction Multiple-Data)與Floating-Point指令,其中的差異是,AArch32的SIND/Floating-Point使用64bits的暫存器,而AArch64使用的是128bits暫存器.
參考文件 Procedure Call Standard for the ARMR Architecture(http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf ) 與 Procedure Call Standard for the ARM 64-bit Architecture(AArch64) (http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf),其中有關General purpose registers and AAPCS64 usage段落有針對這31個General Purpose 64bits暫存器的使用方式,可知64bits提供的額外暫存器,包括在Function Parameter/Return值處理,以及可供函式內優化使用的暫存器數量都增加,善用這些額外暫存器可減少對外部記憶體存取的頻率,並讓編譯器優化時,更有空間去改善執行效能.
AArch64 Register | Special | Role in the procedure call standard |
x0…x7 | | Parameter/result registers |
x8 | | Indirect result location register |
x9…x15 | | Temporary registers |
x16 | IP0 | The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register. |
x17 | IP1 | The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register. |
x18 | | The Platform Register, if needed; otherwise a temporary register. |
x19…x28 | | Callee-saved registers |
x29 | FP | The Frame Pointer |
x30 | LR | The Link Register |
SP | | The Stack Pointer. |
AArch32 Register | Special | Role in the procedure call standard |
r0…r3 | | Parameter/result registers |
r4…r8 r9 (also as platform register) r10,r11 | | Temporary registers |
r12 | IP | The Intra-Procedure-call scratch register. |
r13 | SP | The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register. |
r14 | LR | The Platform Register, if needed; otherwise a temporary register. |
r15 | PC | Callee-saved registers |
如下表,筆者嘗試從支援的暫存器/指令來分類AArch32 與 AArch64,以供簡要參考.
Execution State | Note |
AArch64 | 1,提供31個64bits寬的General-Purpose Registers (from x0~x30,其中 x30亦可作為Procedure Link Registers) 2,提供64bits Program Counter(PC), Stack-Poiner(SP)與Exception-Link-Register (ELR) 3,提供32個128bits寬的SIMD Vector 與 Scalar Floating-Point暫存器 4,定義ARMv8 EL0~EL3共4個Execution Privilege 5, 支援64bits Virtual-Addressing 6, 定義一組PSTATE用以保存PE(Processing Element)狀態. |
AArch32 | 1,提供16個32bits寬的General-Purpose Registers (from r0~r12, 其中r13=SP, r14=LR and r15=PC, 並且r14需同時供ELR與Procedure Link Registers之用) 2, 提供一個ELR,用以作為從Hyp-Mode的Exception返回之用. 3, 提供32個64bits寬的Advanced SIMD Vector 與 Scalar Floating-Point暫存器 4, 提供A32與T32兩種指令集的組態 5, 使用32bits Virtual-Addressing 6, 只使用CPSR (Current Program State Register)保存PE(Processing Element)狀態. |
ARMv8共支援以下幾種Data types,
Data Type | Length |
Byte (B) | 8 bits. |
Halfword (H) | 16 bits. |
Word (S) | 32 bits. |
Doubleword (D) | 64 bits. |
Quadword (V) | 128 bits. |
而ARMv8通用暫存器.可區分32bits (Wn)與 64bits (Xn)兩類,可供程式執行依需求使用.
至於SIMD/浮點數的部分,參考下圖可區分為8bits~128bits(16bits Half-Precision, 32bits Single-Precision and 64bits Double-Precision)不同的Size,並相容於IEEE 754.
ARMv8跟之前ARM處理器相比,最大的亮點之一就是Crypto加密指令集的支援,目前這部份的支援主要包括基於SIMD指令的 AES,SHA1與SHA2-256硬體加速指令,可參考如下簡表.
Crypto New ARMv32 Instruction | Explain |
AESD.8 | AES single round decryption. |
AESE.8 | AES single round encryption. |
AESIMC.8 | AES inverse mix columns. |
AESMC.8 | AES mix columns. |
SHA1C.32 | SHA1 hash update accelerator (choose). |
SHA1M.32 | SHA1 hash update accelerator (majority). |
SHA1P.32 | SHA1 hash update accelerator (parity). |
SHA1H.32 | SHA1 hash update accelerator (rotate left by 30). |
SHA1SU0.32 | SHA1 schedule update accelerator, first part |
SHA1SU1.32 | SHA1 schedule update accelerator, second part |
SHA256H.32 | SHA256 hash update accelerator. |
SHA256H2.32 | SHA256 hash update accelerator upper part. |
SHA256SU0.32 | SHA256 schedule update accelerator, first part |
SHA256SU1.32 | SHA256 schedule update accelerator, second part |
VMULL.P64 | Polynomial multiply long, AES-GCM acceleration 64×64 to 128-bit. |
綜觀上述的介紹後,筆者嘗試把AArch32與AArch64兩個執行環境用如下圖加以說明,希望會比較好理解兩者的差異. 紅色部分為這次ARMv8新增的模塊,而黃色部分則為ARMv7既有支援的指令集範圍,可以看到在AArch32的架構下,A32與T32指令集間可以透過BX搭配Address bit 0 為1的方式由ARM Mode切換到Thumb Mode,並可透過BX指令從Thumb Mode切換回ARM Mode,兩者轉換的成本很低. 且ARMv8所支援的Crypto指令也同時在AArch32與AArch64模式下都有支援,可用於加速這兩類指令集模式下加解密指令效率.
而跟過去習知ARM Mode與Thumb Mode指令集切換最大的差異在於,在AArch32與AArch64兩者32bits/64bits執行模式切換時,目前ARMv8架構下只能透過觸發Exception的方式進行切換.也因此這表示一個應用程式撰寫時就必須決定自己是要處於32bits 或64bits的場景.如果希望可以享受到兩個模式下的好處,就必須要同時具備32bits Process與64bits Process,兩者之間再透過IPC(Inter-Process Communication)進行溝通.
ARMv8把Execution Privilege區分為EL0到EL3共4個Level,根據目前的架構,會由下層系統的Execution State決定上層系統所在的模式.若下層系統為32bits, 則上層就只能為32bits
反之,若下層系統為64bits,上層就可選擇為32bits或64bits兩者之一,也因此若想要同時讓你的處理器軟體執行環境支援32bits Process與64bits Process,就必須要使用 64bits的Kernel執行環境.
把ARMv8處理器的基礎差異說明後,接下來讓我們進一步嘗試說明ARMv8 AArch32與AArch64在執行時期的Memory體系為何,藉此可對應用程式運作有更進一步的了解.
ARMv8 Memory Model
參考Linux Kernel 3.16.3(in /arch/arm/mm/proc-v7-2level.S, http://hala01.com/src/linux/linux-3.16.3/HTML/S/19381.html), ARM 32bits下會用TTBR0儲存當下User-Space行程所在的Page Table (也就是0xC0000000以下的記憶體空間),並用TTBR1儲存Kernel Space所在的Page Table (也就是0xC0000000以上的記憶體空間).
在ARMv8 64bits架構下,會透過EL1的TTBR0 (ttbr0_el1, in /arch/arm64/mm/proc.S, http://hala01.com/src/linux/linux-3.16.3/HTML/S/22828.html )儲存當下User-Space行程所在的Page Table,與EL1的TTBR1儲存Kernel Space所在的Page Table,並會依據Page Size與32/64bits行程而有不同的記憶體空間配置. 參考如下圖所示
如下為原本大家習知32bits Linux Kernel記憶體定址方式,依據需求開發者可以配置為 User與Kernel Space各2GB的Layout,或是修改為 User與Kernel Space分別為3GB與1GB的定址方式.
但由於ARM 64bits Kernel的分頁(Page)有分4KB 與 64KB兩種大小,參考 TASK_SIZE_64 (/arch/arm64/include/asm/memory.h,http://hala01.com/src/linux/linux-3.16.3/html/S/22730.html#L62 )所指定的數值,其中當分頁大小為4KB時,決定TASK_SIZE_64大小的VA_BITS會等於 39,也就是2^39大小的Task空間(=512GB),若分頁大小為64KB時,則TASK_SIZE_64對應的VA_BITS會等於 42,也就是2^42大小的Task空間(=4TB).
同時, Kernel Space的範圍會透過 PAGE_OFFSET=(UL(0xffffffffffffffff) << (VA_BITS – 1))取得(in /arch/arm64/include/asm/memory.h , http://hala01.com/src/linux/linux-3.16.3/HTML/S/22730.html#L49), 且ARM64 Linux Kernel下的high_memory會等於裝置所配置的實體記憶體大小加上PAGE_OFFSET,這塊記憶體可用於線性Memory Mapping實體記憶體與核心虛擬記憶體空間. 若有配置Sparse Memory Virtual memmap用以優化 pfn_to_page/page_to_pfn 流程,則vmemmap 起點為Linear Mapping記憶體的起點virt_to_page(PAGE_OFFSET)終點會等於virt_to_page(high_memory).
當分頁大小為4KB時,決定PAGE_OFFSET大小的VA_BITS會等於 39,因此 PAGE_OFFSET=(UL(0xffffffffffffffff) << (39 – 1))= 0xFFFFFFC000000000,而Kernel Driver所在的區間MODULES_END =PAGE_OFFSET=0xFFFFFFC000000000 與 MODULES_VADDR=MODULES_END – SZ_64M=0xFFFFFFBFFC000000, 並且VMALLOC_START= (UL(0xffffffffffffffff) << VA_BITS)= (UL(0xffffffffffffffff) << 39)= 0xFFFFFF8000000000,與VMALLOC_END=(PAGE_OFFSET – UL(0x400000000) – SZ_64K)= 0xFFFFFFBBFFFF0000.
若分頁大小為64KB時,則PAGE_OFFSET對應的VA_BITS會等於 42,也就是PAGE_OFFSET=(UL(0xffffffffffffffff) << (42 – 1))= 0xFFFFFE0000000000, 而Kernel Driver所在的區間MODULES_END =PAGE_OFFSET=0xFFFFFE0000000000與 MODULES_VADDR=MODULES_END – SZ_64M=0xFFFFFDFFFC000000. 並且VMALLOC_START= (UL(0xffffffffffffffff) << VA_BITS)= (UL(0xffffffffffffffff) << 42)= 0xFFFFFC0000000000,與VMALLOC_END=(PAGE_OFFSET – UL(0x400000000) – SZ_64K)= 0xFFFFFDFBFFFF0000.
有關Kernel Memory Mapping的概念,可參考如下示意圖
秉持一貫有Source Code有真相的原則,讓我們從Linux Kernel Source Code的角度來進一步的說明原委,首先從ARM64架構下一個應用程式從執行檔開始貼到記憶體布局的流程來加以說明,
1, 當使用者或應用載入一個新的程式時(例如呼叫load_elf_binary),就會透過函式setup_new_exec(in /fs/exec.c, http://hala01.com/src/linux/linux-3.16.3/HTML/S/8148.html#L1101 )來為新的Process的記憶體布局進行配置.
2, 在函式setup_new_exec中,則會呼叫arch_pick_mmap_layout(current->mm);進入函式arch_pick_mmap_layout(in /arch/arm64/mm/mmap.c, http://hala01.com/src/linux/linux-3.16.3/HTML/S/22812.html#L84 ),依據不同的處理器差異(ARM 32bits/64bits,MIPS,Parisc,PowerPC,S390,Sparc,Tile,x86,..etc),為目前的Process 記憶體布局進行配置
3, 由於本文著重於ARM 64bits,因此會以ARM64中的函式 arch_pick_mmap_layout(in /arch/arm64/mm/mmap.c, http://hala01.com/src/linux/linux-3.16.3/HTML/S/22812.html#L84 )為主加以說明,首先這函式可以分為兩個部分
void arch_pick_mmap_layout(struct mm_struct *mm)
if (mmap_is_legacy())
mm->mmap_base = TASK_UNMAPPED_BASE;
mm->get_unmapped_area = arch_get_unmapped_area;
else
mm->mmap_base = mmap_base();
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
3.1, 若if (mmap_is_legacy())成立è
此時Task的mm->get_unmapped_area為arch_get_unmapped_area(in /mm/mmap.c, http://hala01.com/src/linux/linux-3.16.3/HTML/S/9939.html#L1873 ),表示該Task Process每一個新的Memory Mapping (包括應用程式本身,動態函式庫,MMAP配置…等),都會由低位址往高位址依序配置而上,這比較像是最早對於原本對於MMAP的概念,也就是Legacy的作法.
對應到 32bits Kernel, Task 的mm->mmap_base會等於TASK_UNMAPPED_BASE,若此時Kernel Space起點為0xC0000000,則會從0x40000000為起點.
若是 64bits Kernel, 則 32bits Task的TASK_UNMAPPED_BASE 會等於(PAGE_ALIGN(TASK_SIZE / 4))(in /arch/arm64/include/asm/memory.h, http://hala01.com/src/linux/linux-3.16.3/HTML/S/22730.html#L65 ),其中因為TASK_SIZE_32為0x100000000,因此會跟32bits Kernel下的32bits Task一樣都以0x40000000為起點.
若為64bits Kernel下的64bits Task, 由於 TASK_SIZE_64 等於 (UL(1) << VA_BITS),若該64bits Kernel使用4KB Page為單位,則VA_BITS 等於39 (若使用64KB Page為單位,則VA_BITS 等於42),對應到TASK_SIZE_64 等於(UL(1) << VA_BITS),因此在4KB Page設定下, TASK_UNMAPPED_BASE 透過(PAGE_ALIGN(TASK_SIZE / 4))會等於0x2000000000 (若使用64KB Page,則為0x10000000000), 由於這些組合比較多,筆者把上述的組合繪製如下圖所示
3.2,若if (mmap_is_legacy()) 不成立è< 以上是关于ARMv8 與 Linux的新手筆記的主要内容,如果未能解决你的问题,请参考以下文章 [FPGA][Nios][DP83848] 網路開發筆記-軟體篇 NancyFx-打造小型 WebAPI 與 Microservice 的輕巧利器