天天看點

系統啟動流程-armV7

作者:哆哆jarvis
  • 核心複位後在裸機上運作的代碼,即在不使用作業系統的情況下運作的代碼。這是首次啟動晶片或系統時經常遇到的情況。
  • bootloader如何加載和運作Linux核心。

從裸機啟動

晶片複位後,将在異常向量表中複位向量的位置開始執行。複位操作的代碼必須做以下事情:

  • 在多核系統中,使非主核進入睡眠狀态
  • 初始化異常向量。
  • 初始化記憶體系統,包括MMU。
  • 初始化核心模式堆棧和寄存器。
  • 初始化任何關鍵的 I/O 裝置。
  • 執行NEON 或VFP 的任何必要初始化。
  • 啟用中斷。
  • 更改核心模式或狀态。
  • 處理安全世界所需的任何設定(參見第 21 章)。
  • 調用main() 應用程式。

GNU 彙編器中的 _start 指令告訴連結器将代碼定位在特定位址,并可用于将代碼放置在向量表中。初始向量表将位于非易失性存儲器中,并且可以包含跳轉到自我指令(除了複位向量),因為此時預計不會出現異常。通常,複位向量包含指向 ROM 中引導代碼的分支。

ROM 可以别名為異常向量的位址。然後,ROM 寫入一些将 RAM 映射到位址 0 的記憶體重映射外設,并将真正的異常向量表複制到 RAM 中。這意味着處理重新映射的引導代碼部分必須與位置無關,因為隻能使用 PC 相對尋址。

下一步是設定緩存、MMU 和分支預測器。

MMU TLB 必須無效。分支目标預測器硬體可能不必顯式失效,但必須由引導代碼啟用。此時可以安全地啟用分支預測;這将提高性能。

在此之後,您可以建立一些翻譯表,如示例 13-4 的示例代碼所示。變量 ttb_address 用于表示要用于初始轉換表的位址。這必須是一個 16KB 的記憶體區域(其起始位址與 16KB 邊界對齊),此代碼可以向其中寫入 L1 轉換表。

接下來的步驟将取決于系統的确切性質。例如,可能需要對将儲存未初始化 C 變量的記憶體進行零初始化,将其他變量的初始值從 ROM 映像複制到 RAM,并設定應用程式堆棧和堆空間。可能還需要初始化 C 庫函數、調用頂級構造函數(用于 C++ 代碼)和其他标準嵌入式 C 初始化。

Booting Linux

如果選擇了 HIVECS(稱為高向量),那麼了解核心從複位中出來并在異常基位址 0x00000000 或 0xFFFF0000 處執行其第一條指令會發生什麼是很有用的,直到出現 Linux 指令提示符為止。

當核心存在于記憶體中時,基于 ARM 處理器的系統上的序列類似于台式計算機上可能發生的序列。但是,引導加載過程可能非常不同,因為基于 ARM 處理器的手機或更深入的嵌入式裝置可能缺少硬碟驅動器或類似 PC 的 BIOS。

通常,當您打開系統電源時會發生硬體特定的引導代碼從閃存或 ROM 運作。此代碼初始化系統,包括任何必要的硬體外圍代碼,然後啟動引導加載程式(例如 U-Boot)。這會初始化主記憶體并将壓縮的 Linux 核心映像複制到主記憶體中(從閃存裝置、闆上的記憶體、MMC、主機 PC 或其他地方)。引導加載程式将某些初始化參數傳遞給核心。然後,Linux 核心會自行解壓并初始化其資料結構和運作的使用者程序,然後再啟動指令 shell 環境。讓我們更詳細地看看這些過程中的每一個。

Reset handler

通常有少量特定于系統的引導監控代碼,用于配置記憶體控制器并執行其他系統外圍裝置初始化。它在記憶體中設定堆棧,通常将自身從 ROM 複制到 RAM,然後更改硬體記憶體映射,以便 RAM 映射到異常向量位址,而不是 ROM。本質上,此代碼獨立于要在闆上運作的作業系統并執行類似于 PC BIOS 的功能。當它完成執行後,它将調用一個 Linux 引導加載程式,例如 U-Boot。

Bootloader

Linux 需要執行一定數量的代碼才能完成重置,以初始化系統。這将執行核心啟動所需的基本任務:

  • 初始化記憶體系統和外圍裝置。
  • 将核心映像加載到記憶體中的适當位置(也可能是初始 RAM 磁盤)。
  • 生成要傳遞給核心的引導參數(包括機器類型)。
  • 為核心設定控制台(視訊或串行)。
  • 進入核心。

不同引導加載程式所采取的具體步驟有所不同,是以有關詳細資訊,請參閱您要使用的引導加載程式的文檔。U-Boot 是一個廣泛使用的示例,但其他可能的引導加載程式包括 Apex、Blob、Bootldr 和 Redboot。

當引導加載程式啟動時,它通常不存在于主存儲器中。它必須首先配置設定堆棧并初始化核心(例如使其緩存無效)并将其自身安裝到主記憶體。它還必須為全局資料和 malloc() 使用配置設定空間,并将異常向量條目複制到适當的位置。

Initialize memory system

這在很大程度上是一塊闆或系統特定的代碼。Linux 核心不負責系統中 RAM 的配置。它顯示了實體記憶體布局,但沒有其他關于記憶體系統的知識。在許多系統中,可用 RAM 及其位置是固定的,并且引導加載程式任務很簡單。在其他系統中,必須編寫代碼來發現系統中可用的 RAM 量。

Kernel images

建構過程中的核心映像通常以 zImage 格式壓縮(可引導核心映像的正常名稱)。它的頭代碼包含一個魔術字,用于驗證解壓的完整性,加上開始和結束位址。核心代碼與位置無關,可以位于記憶體中的任何位置。按照慣例,它被放置在距離實體 RAM 基數 0x8000 的偏移處。這為放置在 0x100 偏移處的參數塊提供了空間(用于轉換表等)。

許多系統需要一個初始 RAM 磁盤 (initrd),因為這可以讓您擁有一個可用的根檔案系統,而無需設定其他驅動程式。引導加載程式可以将初始 ramdisk 映像放入記憶體,并使用 ATAG_INITRD2(描述壓縮 RAM 磁盤映像的實體位置的标簽)和 ATAG_RAMDISK 将其位置傳遞給核心。

引導加載程式通常會在目标中設定一個串行端口,使核心串行驅動程式能夠檢測該端口并将其用于控制台。在某些系統中,可以将另一個輸出裝置(例如視訊驅動程式)用作控制台。核心指令行參數console=可以用來傳遞資訊。

Kernel parameters using ATAGs

從曆史上看,傳遞給核心的參數是以标記清單的形式,放置在實體 RAM 中,寄存器 R2 儲存清單的位址。标簽頭包含兩個 32 位無符号整數,第一個給出标簽的字大小,第二個提供标簽值(訓示标簽的類型)。有關可以傳遞的參數的完整清單,請參閱相應的文檔。示例包括描述實體記憶體映射的 ATAG_MEM 和描述壓縮 ramdisk 映像所在位置的 ATAG_INITRD2。引導加載程式還必須提供 ARM Linux 機器類型号 (MACH_TYPE)。這可以是寫死的值,或者引導代碼可以檢查可用的硬體并相應地配置設定一個值。

有一種更靈活或更通用的方法可以使用扁平裝置樹 (FDT) 傳遞此資訊。

Kernel parameters using Flattened Device Trees

為 PowerPC 核心引入了 Linux 裝置樹或 FDT 支援,作為 32 位和 64 位核心合并的一部分,通過使用适用于所有 PowerPC 平台、伺服器、桌上型電腦和嵌入式的開放固件接口來标準化固件接口 . 它已成為 PowerPC、Micro Blaze 和 SPARC 架構的 Linux 核心中使用的配置方法。

裝置樹是描述硬體配置的資料結構。它包括有關處理器、記憶體大小群組、中斷配置和外圍裝置的資訊。資料結構被組織成一棵樹,有一個名為 / 的根節點。除根節點外,每個節點都有一個父節點。每個節點都有一個名稱,并且可以有任意數量的子節點。節點還可以包含具有任意資料的命名屬性值,它們以鍵值對表示。

Kernel entry

核心執行必須從處于固定狀态的核心開始。bootloader通過直接跳轉到它的第一條指令(arch/arm/boot/compressed/head.S中的開始标簽)來調用核心映像。必須禁用 MMU 和資料緩存。核心必須處于超級使用者模式,并設定 CPSR寄存器的 I 和 F 位(禁用 IRQ 和 FIQ)。R0 必須包含 0,R1 是 MACH_TYPE 值,R2 是标記參數清單的位址。

核心工作的第一步是解壓縮它。這是獨立于架構的。儲存從bootloader傳遞的參數并啟用緩存和MMU。在調用arch/arm/boot/compressed/misc.c 中的decompress_kernel() 之前,會檢查解壓後的圖像是否會覆寫壓縮後的圖像,清除緩存然後再次禁用。然後支到 arch/arm/kernel/head.S 中的核心啟動入口點。

Platform-specific actions

首先使用__lookup_processor_type()檢查核心類型,該函數傳回一個碼,指定它在哪個核心上運作。然後使用函數__lookup_machine_type()來查找機器類型。然後定義一組基本的轉換表,映射核心代碼。然後初始化緩存和MMU并設定其他控制寄存器。資料段被複制到 RAM 并調用start_kernel()。

Kernel start-up code

原則上,啟動順序的其餘部分在任何架構上都是相同的,但實際上某些功能仍然依賴于硬體。

  • 使用 local_irq_disable() 禁用 IRQ 中斷,而 lock_kernel() 用于阻止 FIQ 中斷中斷核心。它初始化tick control、記憶體系統和特定于體系結構的子系統,并處理bootloader傳遞的指令行選項。
  • 設定堆棧并初始化 Linux 排程程式。
  • 設定各種記憶體區域并配置設定頁面。
  • 設定中斷和異常表和處理程式,以及 GIC
  • 系統計時器已設定,此時 IRQ 已啟用。進行額外的記憶體系統初始化,然後使用一個名為 BogoMips 的值來校準核心時脈速度。
  • 設定核心的内部元件,包括檔案系統和初始化程序,然後是建立核心線程的線程守護程序。
  • 核心解鎖(啟用 FIQ)并啟動排程程式
  • 調用函數 do_basic_setup() 來初始化驅動程式、sysctl、工作隊列和網絡套接字。此時,執行到使用者模式的切換。

核心虛拟記憶體映射圖

系統啟動流程-armV7

繼續閱讀