日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ARM64的启动过程之(一):内核第一个脚印

發布時間:2025/3/15 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ARM64的启动过程之(一):内核第一个脚印 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、前言

kernel的整個啟動過程涉及的內容很多,不可能每一個細節都描述清楚,因此我打算針對部分和ARM64相關的啟動步驟進行學習、整理,并方便后續查閱。本文實際上描述在系統啟動最開始的時候,bootloader和kernel的交互以及kernel如何保存bootloader傳遞的參數并進行校驗,此外,還有一些最基礎的硬件初始化的內容。

本文中的source來自4.1.10內核,這是一個long term的版本,后續一段時間的文章都會基于這個long term版本進行。

二、bootloader和kernel的交互

系統啟動過程中,linux kernel不是一個人在戰斗,在kernel之前bootloader會進行下面的動作

1、初始化系統中的RAM并將RAM的信息告知kernel

2、準備好device tree blob的信息并將dtb的首地址告知kernel

3、解壓內核(可選)

4、將控制權轉交給內核。當然,bootloader和kernel的交互的時候需求如下:

MMU = off, D-cache = off, I-cache = on or off?
?? x0 = physical address to the FDT blob

更詳細的ARM64 boot protocol請參考Documentation/arm64/booting.txt文檔。

三、參數的保存和校驗

最開始的ARM64啟動代碼位于arch/arm64/kernel/head.S文件中,代碼如下:

ENTRY(stext)?
??? bl??? preserve_boot_args?
??? bl??? el2_setup??????????? // Drop to EL1, w20=cpu_boot_mode?
??? adrp??? x24, __PHYS_OFFSET?
??? bl??? set_cpu_boot_mode_flag

??? bl??? __vet_fdt??
??? ……?
ENDPROC(stext)

1、preserve_boot_args

preserve_boot_args:?
??? mov??? x21, x0------將x0的值暫存在x21寄存器中,后面要使用x0

??? adr_l??? x0, boot_args---x0保存了boot_args變量的地址?
??? stp??? x21, x1, [x0]----保存x0和x1的值到boot_args[0]和boot_args[1]?
??? stp??? x2, x3, [x0, #16] ---保存x2和x3的值到boot_args[2]和boot_args[3]

??? dmb??? sy---------full system data memory barrier

??? add??? x1, x0, #0x20----x0和x1是傳遞給__inval_cache_range的參數?
??? b??? __inval_cache_range?
ENDPROC(preserve_boot_args)

由于MMU = off, D-cache = off,因此寫入boot_args變量的操作都是略過data cache的,直接寫入了RAM中,為了安全起見(也許bootloader中打開了D-cache并操作了boot_args這段memory),將boot_args變量對應的cache line進行清除并設置無效。在調用__inval_cache_range之前,x0是boot_args這段memory的首地址,x1是末尾的地址(boot_args變量長度是4x8byte=32byte,也就是0x20了)。

為何要保存x0~x3這四個寄存器呢?因為ARM64 boot protocol對啟動時候的x0~x3這四個寄存器有嚴格的限制:x0是dtb的物理地址,x1~x3必須是0。在后續setup_arch函數執行的時候會訪問boot_args并進行校驗。

最后一個小細節是如何訪問boot_args這個符號的,這個符號是一個虛擬地址,但是,現在沒有建立好頁表,也沒有打開MMU,如何訪問它呢?這是通過adr_l這個宏來完成的。這個宏實際上是通過adrp這個匯編指令完成,通過該指令可以將符號地址變成運行時地址(通過PC relative offset形式),因此,當運行的MMU OFF mode下,通過adrp指令可以獲取符號的物理地址。不過adrp是page對齊的(adrp中的p就是page的意思),boot_args這個符號當然不會是page size對齊的,因此不能直接使用adrp,adr_l這個宏進行處理,如果讀者有興趣可以自己看source code。

2、el2_setup

程序執行至此,CPU處于哪一個exception level呢?根據ARM64 boot protocol,CPU要么處于EL2(推薦)或者non-secure EL1。如果在EL1,情形有些類似過去arm處理器的感覺,處于EL2稍微復雜一些,需要對virtualisation extensions進行基本的設定,然后將cpu退回到EL1。代碼太長了,我們分成兩段來閱讀,第一段如下:

ENTRY(el2_setup)?
??? mrs??? x0, CurrentEL------------------------(1)?
??? cmp??? x0, #CurrentEL_EL2------判斷是否處于EL2?
??? b.ne??? 1f--------------不是的話,跳到1f?
??? mrs??? x0, sctlr_el2-------------------------(2)?
CPU_BE(??? orr??? x0, x0, #(1 << 25)??? )??? // Set the EE bit for EL2?
CPU_LE(??? bic??? x0, x0, #(1 << 25)??? )??? // Clear the EE bit for EL2?
??? msr??? sctlr_el2, x0----寫回sctlr_el2寄存器?
??? b??? 2f?
1:??? mrs??? x0, sctlr_el1-------------------------(3)?
CPU_BE(??? orr??? x0, x0, #(3 << 24)??? )??? // Set the EE and E0E bits for EL1?
CPU_LE(??? bic??? x0, x0, #(3 << 24)??? )??? // Clear the EE and E0E bits for EL1?
??? msr??? sctlr_el1, x0?
??? mov??? w20, #BOOT_CPU_MODE_EL1----w20寄存器保存了cpu啟動時候的Eexception level?
??? isb---------instruction memory barrier?
??? ret

2:??? mov??? x0, #(1 << 31) ------------------------(4)?
??? msr??? hcr_el2, x0

??? mrs??? x0, cnthctl_el2 -------------------------(5)?
??? orr??? x0, x0, #3???????????????? // Enable EL1 physical timers?
??? msr??? cnthctl_el2, x0?
??? msr??? cntvoff_el2, xzr??????? // Clear virtual offset

??? mrs??? x0, id_aa64pfr0_el1 -----------------------(6)?
??? ubfx??? x0, x0, #24, #4 ----取出24 bit開始的4個bit的值并將該值賦給x0?
??? cmp??? x0, #1?
??? b.ne??? 3f -----不支持system register接口

??? mrs_s??? x0, ICC_SRE_EL2?
??? orr??? x0, x0, #ICC_SRE_EL2_SRE??? // Set ICC_SRE_EL2.SRE==1?
??? orr??? x0, x0, #ICC_SRE_EL2_ENABLE??? // Set ICC_SRE_EL2.Enable==1?
??? msr_s??? ICC_SRE_EL2, x0?
??? isb??????????????????? // Make sure SRE is now set?
??? msr_s??? ICH_HCR_EL2, xzr??????? // Reset ICC_HCR_EL2 to defaults

3:?????????????? ……

(1)當前的exception level保存在PSTATE中,程序可以通過MRS或者MSR來訪問PSTATE,當然需要傳遞一個Special-purpose register做為參數,CurrentEL就是獲取PSTATE中current exception level域的特殊寄存器。

(2)sctlr_el2也是一個可以通過MRS/MSR指令訪問的寄存器,當CPU處于EL2狀態的時候,該寄存器可以控制整個系統的行為。當然,這里僅僅是設定EL2下的數據訪問和地址翻譯過程中的endianess配置,也就是EE bit[25]。根據配置,CPU_BE和CPU_LE包圍的指令只會保留一行。對于little endian而言,實際上就是將sctlr_el2寄存器的EE(bit 25)設定為0。順便說一下,這個bit不僅僅控制EL2數據訪問的endianess以及EL2 stage 1的地址翻譯過程中的endianess(當然,EL2只有stage 1),還可以控制EL1和EL0 stage 2地址翻譯的過程的endianess(這時候有兩個stage的地址翻譯過程)。

(3)執行到這里說明CPU處于EL1,這種狀態下沒有權限訪問sctlr_el2,只能是訪問sctlr_el1。sctlr_el1可以通過EE和E0E來控制EL1和EL0狀態下是little endian還是big endian。EE bit控制了EL1下的數據訪問以及EL1和EL0 stage 1地址翻譯的過程的endianess。E0E bit用來控制EL0狀態下的數據訪問的endianess。此外,需要注意的是:由于修改了system control register(設定endianess狀態),因此需要一個isb來同步(具體包括兩部分的內容,一是確認硬件已經執行完畢了isb之前的所有指令,包括修改system control寄存器的那一條指令,另外一點是確保isb之后的指令從新來過,例如取指,校驗權限等)。

(4)執行到這里說明CPU處于EL2,首先設定的是hcr_el2寄存器,Hypervisor Configuration Register。該寄存器的大部分bit 值在reset狀態的時候就是0值,只不過bit 31(Register Width Control)是implementation defined,因此這里set 31為1,確保Low level的EL1也是Aarch64的

(5)這一段代碼是對Generic timers進行配置。要想理解這段代碼,我們需要簡單的了解一些ARMv8上Generic timer的運作邏輯。一個全局范圍的system counter、各個PE上自己專屬的local timer以及連接這些組件之間的bus或者信息傳遞機制組成了Generic Timer。對于PE而言,通過寄存器訪問,它能看到的是physical counter(實際的system counter計數)、virtual counter(physical counter基礎上的offset)、physical timer、virtual timer等。NTHCTL_EL2,Counter-timer Hypervisor Control register,用來控制系統中的physical counter和virutal counter如何產生event stream以及在EL1和EL0狀態訪問physical counter和timer的硬件行為的。在EL1(EL0)狀態的時候訪問physical counter和timer有兩種配置,一種是允許其訪問,另外一種就是trap to EL2。這里的設定是:不陷入EL2(對應的bit設置為1)。更詳細的信息可以參考ARMv8 ARM文檔。cntvoff_el2是virtual counter offset,所謂virtual counter,其值就是physical counter的值減去一個offset的值(也就是cntvoff_el2的值了),這里把offset值清零,因此virtual counter的計數和physical counter的計數是一樣的。

(6)這一段代碼是對GIC V3進行配置。ID_AA64PFR0_EL1,AArch64 Processor Feature Register 0,該寄存器描述了PE實現的feature。GIC bits [27:24]描述了該PE是否實現了system register來訪問GIC,如果沒有(GIC bits 等于0)那么就略過GIC V3的設定。ICC_SRE_EL2,Interrupt Controller System Register Enable register (EL2),該寄存器用來(在EL2狀態時候)控制如何訪問GIC CPU interface模塊的,可以通過memory mapped方式,也可以通過system register的方式。將SRE bit設定為1確保通過system register方式進行GIC interface cpu寄存器的訪問。將enable bit設定為1確保在EL1狀態的時候可以通過ICC_SRE_EL1寄存器對GIC進行配置而不是陷入EL2。

下面我們進入第二段代碼:

??? mrs??? x0, midr_el1 -----------------------------(1)?
??? mrs??? x1, mpidr_el1?
??? msr??? vpidr_el2, x0?
??? msr??? vmpidr_el2, x1

??? mov??? x0, #0x0800??????????? // Set/clear RES{1,0} bits ---------------(2)?
CPU_BE(??? movk??? x0, #0x33d0, lsl #16??? )??? // Set EE and E0E on BE systems?
CPU_LE(??? movk??? x0, #0x30d0, lsl #16??? )??? // Clear EE and E0E on LE systems?
??? msr??? sctlr_el1, x0

??? mov??? x0, #0x33ff-------Disable Coprocessor traps to EL2?
??? msr??? cptr_el2, x0??????????? // Disable copro. traps to EL2

#ifdef CONFIG_COMPAT-----是否支持64 bit kernel上運行32bit 的application?
??? msr??? hstr_el2, xzr??????????? // Disable CP15 traps to EL2?
#endif

??? mrs??? x0, pmcr_el0------------------------------(3)?
??? ubfx??? x0, x0, #11, #5??????????? // to EL2 and allow access to?
??? msr??? mdcr_el2, x0??????????? // all PMU counters from EL1??
??? msr??? vttbr_el2, xzr ----清除Stage-2 translation table base address register

??? adrp??? x0, __hyp_stub_vectors?
??? add??? x0, x0, #:lo12:__hyp_stub_vectors?
??? msr??? vbar_el2, x0 ---------------設定EL2的異常向量表的基地址

??? mov??? x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT |\?
????????????? PSR_MODE_EL1h)?
??? msr??? spsr_el2, x0 ------------------------------(4)?
??? msr??? elr_el2, lr?
??? mov??? w20, #BOOT_CPU_MODE_EL2??????? // This CPU booted in EL2?
??? eret-------------------------------------(5)
ENDPROC(el2_setup)

(1)midr_el1和mpidr_el1都屬于標識該PE信息的read only寄存器。MIDR_EL1,Main ID Register主要給出了該PE的architecture信息,Implementer是誰等等信息。MPIDR_EL1,Multiprocessor Affinity Register,該寄存器保存了processor ID。vpidr_el2和vmpidr_el2是上面的兩個寄存器是對應的,只不過是for virtual processor的。

(2)這段代碼實際上是將0x33d00800(BE)或者0x30d00800(LE)寫入sctlr_el1寄存器。BE和LE的設定和上面第一段代碼中的描述是類似的,其他bit的設定請參考ARMv8 ARM文檔

(3)PMCR_EL0,Performance Monitors Control Register,該寄存器的[15:11]標識了支持的Performance Monitors counter的數目,并將其設定到MDCR_EL2(Monitor Debug Configuration Register (EL2))中。MDCR_EL2中其他的bit都設定為0,其結果就是允許EL0和EL1進行debug的操作(而不是trap to EL2),允許EL1訪問Performance Monitors counter(而不是trap to EL2)。

(4)當系統發生了異常并進入EL2,SPSR_EL2,Saved Program Status Register (EL2)會保存處理器狀態,ELR_EL2,Exception Link Register (EL2)會保存返回發生exception的現場的返回地址。這里是設定SPSR_EL2和ELR_EL2的初始值。w20寄存器保存了cpu啟動時候的Eexception level ,因此w20被設定為BOOT_CPU_MODE_EL2。

(5)eret指令是用來返回發生exception的現場。實際上,這個指令僅僅是模擬了一次異常返回而已,SPSR_EL2和ELR_EL2都已經設定OK,執行該指令會使得CPU返回EL1狀態,并且將SPSR_EL2的值賦給PSTATE,ELR_ELR就是返回地址(實際上也恰好是函數的返回地址)。

完成了el2_setup這個函數分析之后,我們再回頭思考這樣的問題:為何是el2_setup?為了沒有el3_setup?當一個SOC的實現在包括了EL3的支持,那么CPU CORE缺省應該進入EL3狀態,為何這里只是判斷EL2還是EL1,從而執行不同的流程,如果是EL3狀態,代碼不就有問題了嗎?實際上,即便是由于SOC支持TrustZone而導致cpu core上電后進入EL3,這時候,接管cpu控制的一定不是linux kernel(至少目前來看linux kernel不會做Secure monitor),而是Secure Platform Firmware(也就是傳說中的secure monitor),它會進行硬件平臺的初始化,loading trusted OS等等,等到完成了secure world的構建之后,把控制權轉交給non-secure world,這時候,CPU core多半處于EL2(如果支持虛擬化)或者EL1(不支持虛擬化)。因此,對于linux kernel而言,它感知不到secure world(linux kernel一般也不會做Trusted OS),僅僅是在non-secure world中呼風喚雨,可以是Hypervisor或者rich OS。

3、set_cpu_boot_mode_flag

在進入這個函數的時候,有一個前提條件:w20寄存器保存了cpu啟動時候的Eexception level ,具體代碼如下:

ENTRY(set_cpu_boot_mode_flag)?
??? adr_l??? x1, __boot_cpu_mode?
??? cmp??? w20, #BOOT_CPU_MODE_EL2?
??? b.ne??? 1f?
??? add??? x1, x1, #4?
1:??? str??? w20, [x1]??????????? // This CPU has booted in EL1?
??? dmb??? sy?
??? dc??? ivac, x1??????????? // Invalidate potentially stale cache line?
??? ret?
ENDPROC(set_cpu_boot_mode_flag)

由于系統啟動之后仍然需要了解cpu啟動時候的Eexception level,因此,有一個全局變量__boot_cpu_mode用來保存啟動時候的CPU mode。代碼很簡單,大家自行體會就OK了。

4、__vet_fdt

在進入具體函數之前,x21和x24都被設定成了指定的值。x21被設定為fdt在RAM中的物理地址(參考preserve_boot_args函數),x24被設定為__PHYS_OFFSET,定義為:

#define __PHYS_OFFSET??? (KERNEL_START - TEXT_OFFSET)

#define KERNEL_START??? _text

KERNEL_START是kernel開始運行的虛擬地址,更確切的說是內核正文段開始的虛擬地址。 在鏈接腳本文件中(參考arch/arm64/kernel下的vmlinux.lds.S),KERNEL_START被設定為:

. = PAGE_OFFSET + TEXT_OFFSET;

.head.text : {?
??? _text = .;?
??? HEAD_TEXT?
}

因此,KERNEL_START的值和PAGE_OFFSET以及TEXT_OFFSET這兩個offset的設定有關。TEXT_OFFSET標識了內核正文段的offset,其實如果該宏被定義為KERNEL_TEXT_OFFSET會更好理解。我們知道,操作系統運行在內核空間,應用程序運行在用戶空間,假設內核空間的首地址是x(一般也是RAM的首地址),那么是否讓kernel運行在x地址呢?對于arm,在內核空間的開始有32kB(0x00008000)的空間用于保存內核的頁表(也就是進程0的PGD)以及bootload和kernel之間參數的傳遞,對于ARM64,在其Makefile中定義了這個offset是512KB(0x00080000)。

ifeq ($(CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET), y)?
TEXT_OFFSET := $(shell awk 'BEGIN {srand(); printf "0x%03x000\n", int(512 * rand())}')?
else?
TEXT_OFFSET := 0x00080000?
endif

CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET是randomize內核的啟動地址,估計是防止黑客入侵之類的,我們這里簡化處理,就是固定為0x00080000好了。

搞定了TEXT_OFFSET,我們再來看看PAGE_OFFSET,在arch/arm64/include/asm/memory.h中,PAGE_OFFSET被定義為:

#define VA_BITS??????????? (CONFIG_ARM64_VA_BITS)?
#define PAGE_OFFSET??????? (UL(0xffffffffffffffff) << (VA_BITS - 1))

VA_BITS定義了用戶空間虛擬地址的bit數(該值也就是定義了用戶態程序能夠訪問的地址空間的size),假設VA_BITS被設定為39個bit,那么PAGE_OFFSET就是0xffffffc0-00000000。PAGE_OFFSET的名字也不好(個人觀點,可能有誤),OFFSET表明的是一個偏移,內核空間被劃分成一個個的page,PAGE_OFFSET看起來應該是定義以page為單位的偏移。但是,以什么為基準的偏移呢?PAGE_OFFSET的名字中沒有給出,當然實際上,這個符號是定義以整個address space的起始地址(也就是0)為基準。另外,雖然這個地址是要求page對齊,但是實際上,這個符號仍然定義的是虛擬地址的offset(而不是page的offset)。根據上面的理由,我覺得定義成KERNEL_IMG_OFFSET會更好理解一些。一句話總結:PAGE_OFFSET定義了將kernel image安放在虛擬地址空間的哪個位置上。

OK,經過漫長的說明之后,__PHYS_OFFSET實際上就是kernel image的首地址(并不是__PHYS_OFFSET的位置開始就是真實的kernel image,實際上從__PHYS_OFFSET開始,首先是TEXT_OFFSET的保留區域,然后才是真正的kernel image)。實際上,__PHYS_OFFSET定義的是一個虛擬地址而不是物理地址,這里的PHYS嚴重影響了該符號的含義,實際上adrp這條指令可以將一個虛擬地址轉換成物理地址(在沒有打開MMU的時候)。而函數__vet_fdt主要是對這個bootloader傳遞給kernel的fdt參數進行驗證,看是否OK,主要驗證的內容包括:

(1)是否是8字節對齊的

(2)是否在kernel space的前512M內

__vet_fdt:?
??? tst??? x21, #0x7----是否是8字節對齊的?
??? b.ne??? 1f?
??? cmp??? x21, x24-----是否在小于kernel space的首地址?
??? b.lt??? 1f?
??? mov??? x0, #(1 << 29)?
??? add??? x0, x0, x24?
??? cmp??? x21, x0?
??? b.ge??? 1f-------是否大于kernel space的首地址+512M?
??? ret?
1:?
??? mov??? x21, #0----------傳遞的fdt地址有誤,清零?
??? ret?
ENDPROC(__vet_fdt)

四、參考文獻

1、Documentation/arm64/booting.txt

2、ARM Architecture Reference Manual


change log:

1、2015-11-30,增加對el2_setup的思考。

2、2015-12-1,(1)修正對PAGE_OFFSET的描述。(2)增加adrp和adr_l的描述(3)增加對__PHYS_OFFSET符號名字的置疑


原文地址: http://www.wowotech.net/linux_kenrel/arm64_initialize_1.html

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的ARM64的启动过程之(一):内核第一个脚印的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。