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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

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

發(fā)布時(shí)間:2024/1/8 linux 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 arm linux内核启动过程,ARM64的启动过程之(一):内核第一个脚印 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

ARM64的啟動(dòng)過(guò)程之(一):內(nèi)核第一個(gè)腳印

作者:linuxer 發(fā)布于:2015-10-10 15:06

分類:ARMv8A Arch

一、前言

kernel的整個(gè)啟動(dòng)過(guò)程涉及的內(nèi)容很多,不可能每一個(gè)細(xì)節(jié)都描述清楚,因此我打算針對(duì)部分和ARM64相關(guān)的啟動(dòng)步驟進(jìn)行學(xué)習(xí)、整理,并方便后續(xù)查閱。本文實(shí)際上描述在系統(tǒng)啟動(dòng)最開(kāi)始的時(shí)候,bootloader和kernel的交互以及kernel如何保存bootloader傳遞的參數(shù)并進(jìn)行校驗(yàn),此外,還有一些最基礎(chǔ)的硬件初始化的內(nèi)容。

本文中的source來(lái)自4.1.10內(nèi)核,這是一個(gè)long term的版本,后續(xù)一段時(shí)間的文章都會(huì)基于這個(gè)long term版本進(jìn)行。

二、進(jìn)入kernel之前

系統(tǒng)啟動(dòng)過(guò)程中,linux kernel不是一個(gè)人在戰(zhàn)斗,在kernel之前bootloader會(huì)執(zhí)行若干的動(dòng)作,然后把控制權(quán)轉(zhuǎn)移給linux kernel。需要特別說(shuō)明的是:這里bootloader是一個(gè)寬泛的概念,其實(shí)就是為kernel準(zhǔn)備好執(zhí)行環(huán)境的那些軟件,可能是傳統(tǒng)意義的bootloader(例如Uboot),也可能是Hypervisor或者是secure monitor。具體bootloader需要執(zhí)行的動(dòng)作包括:

1、初始化系統(tǒng)中的RAM并將RAM的信息告知kernel

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

3、解壓內(nèi)核(可選)

4、將控制權(quán)轉(zhuǎn)交給內(nèi)核。當(dāng)然,bootloader和kernel的交互的時(shí)候需求如下:

MMU = off, D-cache = off, I-cache = on or off

x0 = physical address to the FDT blob

這里需要對(duì)data cache和instruction cache多說(shuō)幾句。我們知道,具體實(shí)現(xiàn)中的ARMv8處理器的cache是形成若干個(gè)level,一般而言,可能L1是分成了data cache和instruction cache,而其他level的cache都是unified cache。上面定義的D-cache off并不是說(shuō)僅僅disable L1的data cache,實(shí)際上是disable了各個(gè)level的data cache和unified cache。同理,對(duì)于instruction cache亦然。

此外,在on/off控制上,MMU和data cache是有一定關(guān)聯(lián)的。在ARM64中,SCTLR, System Control Register用來(lái)控制MMU icache和dcache,雖然這幾個(gè)控制bit是分開(kāi)的,但是并不意味著MMU、data cache、instruction cache的on/off控制是彼此獨(dú)立的。一般而言,這里MMU和data cache是綁定的,即如果MMU 是off的,那么data cache也必須要off。因?yàn)槿绻蜷_(kāi)data cache,那么要設(shè)定memory type、sharebility attribute、cachebility attribute等,而這些信息是保存在頁(yè)表(Translation table)的描述符中,因此,如果不打開(kāi)MMU,如果沒(méi)有頁(yè)表翻譯過(guò)程,那么根本不知道怎么來(lái)應(yīng)用data cache。當(dāng)然,是不是說(shuō)HW根本不允許這樣設(shè)定呢?也不是了,在MMU OFF而data cache是ON的時(shí)候,這時(shí)候,所有的memory type和attribute是固定的,即memory type都是normal Non-shareable的,對(duì)于inner cache和outer cache,其策略都是Write-Back,Read-Write Allocate的。

更詳細(xì)的ARM64 boot protocol請(qǐng)參考Documentation/arm64/booting.txt文檔。

三、參數(shù)的保存和校驗(yàn)

最開(kāi)始的ARM64啟動(dòng)代碼位于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------將dtb的地址暫存在x21寄存器中,釋放出x0以便后續(xù)做臨時(shí)變量使用

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的參數(shù)

b??? __inval_cache_range

ENDPROC(preserve_boot_args)

由于MMU = off, D-cache = off,因此寫(xiě)入boot_args變量的操作都是略過(guò)data cache的,直接寫(xiě)入了RAM中(前面說(shuō)過(guò)了,這里的D-cache并不是特指L1的data cache,而是各個(gè)level的data cache和unified cache),為了安全起見(jiàn)(也許bootloader中打開(kāi)了D-cache并操作了boot_args這段memory,從而在各個(gè)level的data cache和unified cache有了一些舊的,沒(méi)有意義的數(shù)據(jù)),需要將boot_args變量對(duì)應(yīng)的cache line進(jìn)行清除并設(shè)置無(wú)效。在調(diào)用__inval_cache_range之前,x0是boot_args這段memory的首地址,x1是末尾的地址(boot_args變量長(zhǎng)度是4x8byte=32byte,也就是0x20了)。

為何要保存x0~x3這四個(gè)寄存器呢?因?yàn)锳RM64 boot protocol對(duì)啟動(dòng)時(shí)候的x0~x3這四個(gè)寄存器有嚴(yán)格的限制:x0是dtb的物理地址,x1~x3必須是0(非零值是保留將來(lái)使用)。在后續(xù)setup_arch函數(shù)執(zhí)行的時(shí)候會(huì)訪問(wèn)boot_args并進(jìn)行校驗(yàn)。

對(duì)于invalidate cache的操作而言,我們可以追問(wèn)幾個(gè)問(wèn)題:如果boot_args所在區(qū)域的首地址和尾部地址沒(méi)有對(duì)齊到cache line怎么辦?具體invalidate cache需要操作到那些level的的cache?這些問(wèn)題可以通過(guò)閱讀__inval_cache_range的代碼獲得答案,這里就不描述了。

還有一個(gè)小細(xì)節(jié)是如何訪問(wèn)boot_args這個(gè)符號(hào)的,這個(gè)符號(hào)是一個(gè)虛擬地址,但是,現(xiàn)在沒(méi)有建立好頁(yè)表,也沒(méi)有打開(kāi)MMU,如何訪問(wèn)它呢?這是通過(guò)adr_l這個(gè)宏來(lái)完成的。這個(gè)宏實(shí)際上是通過(guò)adrp這個(gè)匯編指令完成,通過(guò)該指令可以將符號(hào)地址變成運(yùn)行時(shí)地址(通過(guò)PC relative offset形式),因此,當(dāng)運(yùn)行的MMU OFF mode下,通過(guò)adrp指令可以獲取符號(hào)的物理地址。不過(guò)adrp是page對(duì)齊的(adrp中的p就是page的意思),boot_args這個(gè)符號(hào)當(dāng)然不會(huì)是page size對(duì)齊的,因此不能直接使用adrp,而是使用adr_l這個(gè)宏進(jìn)行處理,如果讀者有興趣可以自己看source code。

最后,我們來(lái)解釋一下dmb?? ?sy這一條指令。在ARM ARM文檔中,有關(guān)于數(shù)據(jù)訪問(wèn)指令和 data cache指令之間操作順序的約定,原文如下:

All data cache instructions, other than DC ZVA, that specify an address can execute in any order relative to loads or stores that access any address with the Device memory attribute,or with Normal memory with Inner Non-cacheable attribute unless a DMB or DSB is executed between the instructions.

因此,在Non-cacheable的情況下,必須要使用DMB來(lái)保證stp指令在dc ivac指令之前執(zhí)行完成。

2、el2_setup

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

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----寫(xiě)回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啟動(dòng)時(shí)候的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開(kāi)始的4個(gè)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)當(dāng)前的exception level保存在PSTATE中,程序可以通過(guò)MRS或者M(jìn)SR來(lái)訪問(wèn)PSTATE,當(dāng)然需要傳遞一個(gè)Special-purpose register做為參數(shù),CurrentEL就是獲取PSTATE中current exception level域的特殊寄存器。

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

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

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

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

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

下面我們進(jìn)入第二段代碼:

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上運(yùn)行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 ---------------設(shè)定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都屬于標(biāo)識(shí)該P(yáng)E信息的read only寄存器。MIDR_EL1,Main ID Register主要給出了該P(yáng)E的architecture信息,Implementer是誰(shuí)等等信息。MPIDR_EL1,Multiprocessor Affinity Register,該寄存器保存了processor ID。vpidr_el2和vmpidr_el2是上面的兩個(gè)寄存器是對(duì)應(yīng)的,只不過(guò)是for virtual processor的。

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

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

(4)當(dāng)系統(tǒng)發(fā)生了異常并進(jìn)入EL2,SPSR_EL2,Saved Program Status Register (EL2)會(huì)保存處理器狀態(tài),ELR_EL2,Exception Link Register (EL2)會(huì)保存返回發(fā)生exception的現(xiàn)場(chǎng)的返回地址。這里是設(shè)定SPSR_EL2和ELR_EL2的初始值。w20寄存器保存了cpu啟動(dòng)時(shí)候的Eexception level ,因此w20被設(shè)定為BOOT_CPU_MODE_EL2。

(5)eret指令是用來(lái)返回發(fā)生exception的現(xiàn)場(chǎng)。實(shí)際上,這個(gè)指令僅僅是模擬了一次異常返回而已,SPSR_EL2和ELR_EL2都已經(jīng)設(shè)定OK,執(zhí)行該指令會(huì)使得CPU返回EL1狀態(tài),并且將SPSR_EL2的值賦給PSTATE,ELR_ELR就是返回地址(實(shí)際上也恰好是函數(shù)的返回地址)。

完成了el2_setup這個(gè)函數(shù)分析之后,我們?cè)倩仡^思考這樣的問(wèn)題:為何是el2_setup?為了沒(méi)有el3_setup?當(dāng)一個(gè)SOC的實(shí)現(xiàn)在包括了EL3的支持,那么CPU CORE缺省應(yīng)該進(jìn)入EL3狀態(tài),為何這里只是判斷EL2還是EL1,從而執(zhí)行不同的流程,如果是EL3狀態(tài),代碼不就有問(wèn)題了嗎?實(shí)際上,即便是由于SOC支持TrustZone而導(dǎo)致cpu core上電后進(jìn)入EL3,這時(shí)候,接管cpu控制的一定不是linux kernel(至少目前來(lái)看linux kernel不會(huì)做Secure monitor),而是Secure Platform Firmware(也就是傳說(shuō)中的secure monitor),它會(huì)進(jìn)行硬件平臺(tái)的初始化,loading trusted OS等等,等到完成了secure world的構(gòu)建之后,把控制權(quán)轉(zhuǎn)交給non-secure world,這時(shí)候,CPU core多半處于EL2(如果支持虛擬化)或者EL1(不支持虛擬化)。因此,對(duì)于linux kernel而言,它感知不到secure world(linux kernel一般也不會(huì)做Trusted OS),僅僅是在non-secure world中呼風(fēng)喚雨,可以是Hypervisor或者rich OS。

3、set_cpu_boot_mode_flag

在進(jìn)入這個(gè)函數(shù)的時(shí)候,有一個(gè)前提條件:w20寄存器保存了cpu啟動(dòng)時(shí)候的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)

由于系統(tǒng)啟動(dòng)之后仍然需要了解cpu啟動(dòng)時(shí)候的Eexception level(例如判斷是否啟用hyp mode),因此,有一個(gè)全局變量__boot_cpu_mode用來(lái)保存啟動(dòng)時(shí)候的CPU mode。代碼很簡(jiǎn)單,大家自行體會(huì)就OK了,我這里補(bǔ)充幾點(diǎn)描述:

(1)本質(zhì)上我們希望系統(tǒng)中所有的cpu在初始化的時(shí)候處于同樣的mode,要么都是EL2,要么都是EL1,有些EL2,有些EL1是不被允許的(也許只有那些精神分裂的bootloader才會(huì)這么搞)。

(2)所有的cpu core在啟動(dòng)的時(shí)候都處于EL2 mode表示系統(tǒng)支持虛擬化,只有在這種情況下,kvm模塊可以順利啟動(dòng)。

(3)set_cpu_boot_mode_flag和el2_setup這兩個(gè)函數(shù)會(huì)在各個(gè)cpu上執(zhí)行。

(4)變量__boot_cpu_mode定義如下:

ENTRY(__boot_cpu_mode)

.long?? ?BOOT_CPU_MODE_EL2--------A

.long?? ?BOOT_CPU_MODE_EL1--------B

如果cpu啟動(dòng)的時(shí)候是EL1 mode,會(huì)修改變量__boot_cpu_mode A域,將其修改為BOOT_CPU_MODE_EL1。如果cpu啟動(dòng)的時(shí)候是EL2 mode,會(huì)修改變量__boot_cpu_mode B域,將其修改為BOOT_CPU_MODE_EL2。

4、__vet_fdt

在進(jìn)入具體函數(shù)之前,x21和x24都被設(shè)定成了指定的值。x21被設(shè)定為fdt在RAM中的物理地址(參考preserve_boot_args函數(shù)),x24被設(shè)定為_(kāi)_PHYS_OFFSET,定義為:

#define __PHYS_OFFSET??? (KERNEL_START - TEXT_OFFSET)

#define KERNEL_START??? _text

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

. = PAGE_OFFSET + TEXT_OFFSET;

.head.text : {

_text = .;

HEAD_TEXT

}

因此,KERNEL_START的值和PAGE_OFFSET以及TEXT_OFFSET這兩個(gè)offset的設(shè)定有關(guān)。TEXT_OFFSET標(biāo)識(shí)了內(nèi)核正文段的offset,其實(shí)如果該宏被定義為KERNEL_TEXT_OFFSET會(huì)更好理解。我們知道,操作系統(tǒng)運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,假設(shè)內(nèi)核空間的首地址是x(一般也是RAM的首地址),那么是否讓kernel運(yùn)行在x地址呢?對(duì)于arm,在內(nèi)核空間的開(kāi)始有32kB(0x00008000)的空間用于保存內(nèi)核的頁(yè)表(也就是進(jìn)程0的PGD)以及bootload和kernel之間參數(shù)的傳遞,對(duì)于ARM64,在其Makefile中定義了這個(gè)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

kernel image的開(kāi)始部分包括了一個(gè)ARM64 image header的內(nèi)容,這個(gè)header定義了bootloader如何來(lái)copy kernel image。ARM64 image header中有一個(gè)域(text_offset)就是告知bootloader,它應(yīng)該按照多大的偏移來(lái)copy kernel image。當(dāng)然了,也許有些bootloader不鳥(niǎo)這些,對(duì)于ARM64平臺(tái),反正大家一直都是固定為0x80000,因此,bootloader也沒(méi)有什么太大的動(dòng)力來(lái)修改支持這個(gè)特性。怎么破?雖然目前ARM64的kernel的TEXT_OFFSET就是固定為0x80000,但是也許將來(lái)內(nèi)核會(huì)修改這個(gè)offset啊。在這種情況下,內(nèi)核的開(kāi)發(fā)者提供了一個(gè)CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET選項(xiàng),在編譯內(nèi)核的時(shí)候可以randomize內(nèi)核的TEXT_OFFSET值,以此來(lái)測(cè)試bootloader是否能夠正確的copy kernel image到正確的內(nèi)存偏移位置上去。通過(guò)這樣一個(gè)配置項(xiàng),可以盡快的暴露問(wèn)題,確保了整個(gè)系統(tǒng)(bootloader + kernel)穩(wěn)定的運(yùn)行。

搞定了TEXT_OFFSET,我們?cè)賮?lái)看看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數(shù)(該值也就是定義了用戶態(tài)程序或者內(nèi)核能夠訪問(wèn)的虛擬地址空間的size),假設(shè)VA_BITS被設(shè)定為39個(gè)bit,那么PAGE_OFFSET就是0xffffffc0-00000000。PAGE_OFFSET的名字也不好(個(gè)人觀點(diǎn),可能有誤),OFFSET表明的是一個(gè)偏移,內(nèi)核空間被劃分成一個(gè)個(gè)的page,PAGE_OFFSET看起來(lái)應(yīng)該是定義以page為單位的偏移。但是,以什么為基準(zhǔn)的偏移呢?PAGE_OFFSET的名字中沒(méi)有給出,當(dāng)然實(shí)際上,這個(gè)符號(hào)是定義以整個(gè)address space的起始地址(也就是0)為基準(zhǔn)。另外,雖然這個(gè)地址是要求page對(duì)齊,但是實(shí)際上,這個(gè)符號(hào)仍然定義的是虛擬地址的offset(而不是page的offset)。根據(jù)上面的理由,我覺(jué)得定義成KERNEL_IMG_OFFSET會(huì)更好理解一些。一句話總結(jié):PAGE_OFFSET定義了將kernel image安放在虛擬地址空間的哪個(gè)位置上。

OK,經(jīng)過(guò)漫長(zhǎng)的說(shuō)明之后,__PHYS_OFFSET實(shí)際上就是kernel image的首地址(并不是__PHYS_OFFSET的位置開(kāi)始就是真實(shí)的kernel image,實(shí)際上從__PHYS_OFFSET開(kāi)始,首先是TEXT_OFFSET的保留區(qū)域,然后才是真正的kernel image)。實(shí)際上,__PHYS_OFFSET定義的是一個(gè)虛擬地址而不是物理地址,這里的PHYS嚴(yán)重影響了該符號(hào)的含義,實(shí)際上adrp這條指令可以將一個(gè)虛擬地址轉(zhuǎn)換成物理地址(在沒(méi)有打開(kāi)MMU的時(shí)候)。而函數(shù)__vet_fdt主要是對(duì)這個(gè)bootloader傳遞給kernel的fdt參數(shù)進(jìn)行驗(yàn)證,看是否OK,主要驗(yàn)證的內(nèi)容包括:

(1)是否是8字節(jié)對(duì)齊的

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

__vet_fdt:

tst??? x21, #0x7----是否是8字節(jié)對(duì)齊的

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)

四、參考文獻(xiàn)

1、Documentation/arm64/booting.txt

2、ARM Architecture Reference Manual

change log:

1、2015-11-30,增加對(duì)el2_setup的思考。

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

3、2016-6-28,(1)增加對(duì)MMU、data cache的依賴關(guān)系描述。(2)增加對(duì)preserve_boot_args中dmb sy指令的描述。

4、2016-7-7,(1)增加對(duì)set_cpu_boot_mode_flag的描述。

5、2016-7-8,(1)修正對(duì)CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET的描述

原創(chuàng)文章,轉(zhuǎn)發(fā)請(qǐng)注明出處。蝸窩科技

評(píng)論:

linuxxx

2020-05-19 09:16

wowo,你好:

根據(jù)我個(gè)人的理解,kernel代碼段的起始地址是

VA_START + KASAN_SHADOW_SIZE+MODULES_SIZE+TEXT_OFFSET =

(0xffffffffffffffff - 1 << 39 + 1) + 0 + 0x8000000 + 0x80000 = 0xffffff8008080000

根據(jù)我通過(guò)aarch64-linux-readelf命令讀取Image頭的信息里 Entry point address,這兩個(gè)值也是一致的。

是不是PAGE_OFFSET有其他理解方式呢?

2020-05-20 10:43

@linuxxx:代碼在變化更新,所以文章只能參考。

2019-02-19 19:17

感謝作者寫(xiě)了這么好的文檔, 我在們的產(chǎn)品上用的arm64架構(gòu),??我發(fā)現(xiàn)CONFIG_RANDOMIZE_BASE配置了(CONFIG_RANDOMIZE_BASE=y),CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET沒(méi)有配置(is not set), 我編譯個(gè)若干次內(nèi)核進(jìn)行測(cè)試, 發(fā)現(xiàn)kernel image會(huì)被load到固定的位置(0xffff000010080000~0xffff000010c95000), 其中vmalloc區(qū)域的地址是0xffff000010000000~0xffff7bffbfff0000。

這樣看來(lái)即使配置了CONFIG_RANDOMIZE_BASE,也沒(méi)有實(shí)現(xiàn)kernel image會(huì)被隨機(jī)copy到不同的位置。 反倒時(shí)我覺(jué)得配置了CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET才能實(shí)現(xiàn)kernel image被隨機(jī)copy到不同的位置。 因CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET會(huì)決定TEXT_OFFSET的值。

感謝能解答一下

matchchen

2017-07-17 00:45

@linuxer

你好,對(duì)于ARM64下uboot啟動(dòng)kernel傳參我有點(diǎn)疑惑未理清:

從kernel_entry入口來(lái)看,uboot jump 到linux傳遞的唯一信息就只有dtb的地址即X0,不再傳遞cmdline/ramdisk地址等atags了。

那么ramdisk數(shù)據(jù)地址是如何傳遞;如果我要在uboot和linux之間傳遞一些數(shù)據(jù)文件該如何實(shí)現(xiàn)為妥?

2017-07-17 10:05

@matchchen:既然能夠把dtb傳遞給kernel,還有什么不能傳呢?

都加到dtb里面就行了啊。

至于ramdisk,做法也類似,不過(guò)可能會(huì)有一些小技巧,例如緊隨dtb的結(jié)尾啦(可參考fdt_initrd函數(shù)的實(shí)現(xiàn))。

matchchen

2017-07-23 18:58

@wowo:并沒(méi)看到這樣的實(shí)例,是否像image tree中的內(nèi)核、ramdisk、dtb一樣的語(yǔ)法指定數(shù)據(jù)文件的路徑,把文件打包到dtb中?

2017-07-24 09:09

@matchchen:舉個(gè)簡(jiǎn)單的例子:dtb里面有命令行參數(shù),你可以增加一個(gè)參數(shù),xxx=XXXX。這樣的話,什么都可以傳遞啊!

see

2019-09-20 14:55

@wowo:images->ep和readelf vmlinux的的Entry point address是同一地址碼?

raceant

2017-06-22 13:20

@linuxer

#define __PHYS_OFFSET????(KERNEL_START - TEXT_OFFSET)

難道在arm64中所有的物理地址都是從0開(kāi)始的? 我記得arm中好像mach-xxx/中有個(gè)文件配置物理地址的,

但在arm64中卻沒(méi)有找到這個(gè)物理地址怎樣配置

2017-06-23 09:27

@raceant:需要定義內(nèi)存物理地址的起始位置嗎?一般情況下,kernel image被copy到(內(nèi)存首地址+TEXT_OFFSET)這樣的一個(gè)位置上去,因此,在沒(méi)有打開(kāi)MMU的時(shí)候,代碼中實(shí)際上是有技巧可以獲取自己執(zhí)行在哪一個(gè)地址上的。

adrp????x24, __PHYS_OFFSET

x24中即保存了內(nèi)存的物理地址。

編譯器在編譯這一條指令的時(shí)候,把該指令所在的虛擬地址和__PHYS_OFFSET的offset保存在了指令的imm域。當(dāng)實(shí)際運(yùn)行的時(shí)候,x24被賦值為當(dāng)前PC值+offset,也就是內(nèi)存的物理地址了。BTW,上面說(shuō)的實(shí)際上是需要對(duì)齊到page size的。

raceant

2017-06-23 13:43

@linuxer:@linuxer: 謝謝你的解答,我現(xiàn)在明白了,所以kernel的地址(image地址 - TEXT_OFFSET)一定是放在物理地址的首地址處,才能將正確的物理地址位置放到x24中

zhusbj

2017-03-24 16:01

針對(duì) MMU OFF / Cache ON有一些不同的見(jiàn)解。摘自ARMv8-A TRM

---------------------------------------------------------

Behavior when stage 1 address translation is disabled

When a stage 1 address translation is disabled, memory accesses that would otherwise be translated by that stage of

translation are treated as follows:

Non-secure EL1 and EL0 accesses if the HCR_EL2.DC bit is set to 1

For the Non-secure EL1&0 translation regime, when the value of HCR_EL2.DC is 1, the stage 1

translation assigns the Normal Non-shareable, Inner Write-Back Read-Write-Allocate, Outer

Write-Back Read-Write-Allocate memory attributes.

Note

This applies for both instruction and data accesses.

All other accesses

For all other accesses, when stage 1 address translation is disabled, the assigned attributes depend

on whether the access is a data access or an instruction access, as follows:

Data access

The stage 1 translation assigns the Device-nGnRnE memory type.

Instruction access

The stage 1 translation assigns the Normal memory attribute, with the cacheability and

shareability attributes determined by the value of the SCTLR.I bit for the translation

regime, as follows:

When the value of I is 0

The stage 1 translation assigns the Non-cacheable and Outer Shareable

attributes.

When the value of I is 1

The stage 1 translation assigns the Cacheable, Inner Write-Through no

Write-Allocate Read-Allocate, Outer Write-Through no Write-Allocate

Read Allocate Outer Shareable attribute.

---------------------------------------------------------

DC, bit [12]

Default Cacheable. When this bit is set to 1, this causes:

? The SCTLR_EL1.M bit to behave as 0 when in the Non-secure state for all purposes other

than reading the value of the bit.

? The HCR_EL2.VM bit to behave as 1 when in the Non-secure state for all purposes other

than reading the value of the bit.

The memory type produced by the first stage of translation used by EL1 and EL0 is Normal

Non-Shareable, Inner WriteBack Read-WriteAllocate, Outer WriteBack Read-WriteAllocate.

When this bit is 0 and the stage 1 MMU is disabled, the default memory attribute for Data accesses

is Device-nGnRnE.

This bit is permitted to be cached in a TLB.

---------------------------------------------------------

樓主的解釋需要一個(gè)前提條件,就是HCR_EL2.DC 必須已經(jīng)置1。但是這個(gè)bit默認(rèn)值很可能是0,并且代碼中也沒(méi)有置1的操作,所以memory的屬性應(yīng)該是Device-nGnRnE for data access。對(duì)于 instruction access,取決于SCTLR.I bit。

zhusbj

2017-03-24 15:16

從英文表述來(lái)看,因?yàn)閐evice屬性的memory store指令和cache 指令沒(méi)有數(shù)據(jù)依賴關(guān)系,所以可以亂序執(zhí)行。

但是源代碼中明顯是考慮到了 cache on的情況,否則也不必要在最后調(diào)用一下 cache invalidate操作。

所以我覺(jué)得在能保證 non-cacheable 的情況下,可以不必插入dmb指令。

newjourney

2017-03-15 09:52

請(qǐng)問(wèn)??在Non-cacheable的情況下,必須要使用DMB來(lái)保證stp指令在dc ivac指令之前執(zhí)行完成?? 是為何呢?

2017-03-16 16:10

@newjourney:文章中的那段英文說(shuō)的就是數(shù)據(jù)訪問(wèn)指令和data cache操作指令的順序,處理器設(shè)計(jì)就是這樣的,具體為何這樣設(shè)計(jì),這個(gè)問(wèn)題太深?yuàn)W了,超出我的能力范圍了。

2018-11-08 10:08

@linuxer:@linuxer

那段英文應(yīng)該說(shuō)的是要么用devcie memory attribute,如果使用normal nocache,那么必須加上DMB或者DSB吧

deven

2019-01-11 17:53

@linuxer:關(guān)鍵是為什么要保證這種順序?即使先無(wú)效了d-cache后寫(xiě)變量的值感覺(jué)也沒(méi)啥問(wèn)題,實(shí)驗(yàn)了下確實(shí)沒(méi)發(fā)生問(wèn)題

deven

2019-01-11 20:12

@linuxer:關(guān)鍵為什么stp指令要先完成?當(dāng)前cache沒(méi)有enable~

eric

2017-01-24 14:53

個(gè)人覺(jué)得樓主對(duì) adrp????x24, __PHYS_OFFSET 這里的寫(xiě)法可能有點(diǎn)歧義,參考ARM手冊(cè),adrp的格式是adrp ,其中l(wèi)abel的定義是Is the program label whose 4KB page address is to be calculated. Its offset from the page address of this instruction, in the range +/-4GB, is encoded as "immhi:immlo" times 4096.所以這里宏展開(kāi)后是adrp x24, _text-TEXT_OFFSET,_text這個(gè)label表示的是當(dāng)前指令地址所在的“page address”與text段開(kāi)始位置的偏移,稱作虛擬地址有點(diǎn)不太合適,這里其實(shí)并不需要再繼續(xù)宏展開(kāi)到KIMAGE_VADDR去得到一個(gè)0xffffffxxxx的虛擬地址,只是一個(gè)相對(duì)位置而已。

2017-01-25 09:13

@eric:我覺(jué)得,這段英文只是在解釋“l(fā)abel在adrp中的含義以及操作方式”。對(duì)label本身而言,肯定不會(huì)只是一個(gè)偏移(你可以去System.map中看看_text的值)。

haibin

2019-01-24 16:12

@wowo:1. __PHYS_OFFSET是內(nèi)核加載的偏移地址,因?yàn)閎ootloader沒(méi)有傳遞這個(gè)值,實(shí)際也不需要,通過(guò)這個(gè)方式可以計(jì)算出來(lái)

2. adrp在這個(gè)環(huán)境下得到的是物理地址,因?yàn)榇藭r(shí)MMU沒(méi)有開(kāi)啟,不是你說(shuō)的system.map里面的地址

3. arm32中有一條ldr的偽指令,ldr reg,=label,這條指令才是獲取system.map中的地址,arm64中好像沒(méi)有類似的

2016-12-13 10:41

從kernel 代碼看

這一切地址范圍的起始都是由ARM64_4K_PAGES 引起的。

choice

prompt "Page size"

default ARM64_4K_PAGES

config ARM64_VA_BITS

int

default 39 if ARM64_VA_BITS_39

config PGTABLE_LEVELS

int

default 3 if ARM64_4K_PAGES && ARM64_VA_BITS_39

#define VA_BITS????????????(CONFIG_ARM64_VA_BITS)

#define VA_START????????(UL(0xffffffffffffffff) << VA_BITS)??//0xffffff8000000000

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

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

但是為啥用ARM64_4K_PAGES 做引子想不通為啥,也不知道聯(lián)系。

2016-12-14 09:07

@lover713814:4K的page是經(jīng)典的設(shè)置,缺省情況下當(dāng)然選用4K頁(yè)了

dashan

2016-10-28 23:25

謝謝樓主分享這么好的文章。我想問(wèn)一個(gè)問(wèn)題,在armv8 Linux Kernel啟動(dòng)過(guò)程中能不能一直關(guān)閉data cache?如果可以的花能否告知怎么做?謝謝

2016-10-31 09:51

@dashan:你可以參考ARM ARM文檔中關(guān)于SCTLR_EL1寄存器的描述。

dashan

2016-10-31 12:35

@linuxer:謝謝,我直接把bit2清掉了在匯編中:

crval:

.word????0xfcffffff????????????// clear

.word????0x34d5d919????????????// set

我目前準(zhǔn)備把soc 上cpu的頻率調(diào)高,比如由1GHz跑到1.2G(芯片是支持這個(gè)頻率的),uboot能跑起來(lái),但內(nèi)核總是跑一會(huì)就停了,這個(gè)時(shí)候jtag也連不上,從打印上看停的位置不固定, 基本上從start_kernel開(kāi)始跑個(gè)8秒左右就會(huì)停。

請(qǐng)問(wèn)你能否提供點(diǎn)建議?謝謝

1 2

發(fā)表評(píng)論:

昵稱

郵件地址 (選填)

個(gè)人主頁(yè) (選填)

總結(jié)

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

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。