【Rocket-chip 6】BootRom和操作系統啓動學習

前言

這段時間有點不知道幹什麼,不知道從哪方面繼續學習。
學長說可以暫時不去管vcs的部分,所以我就先把那部分留着了。
反正還是挺焦慮的,學期過半了感覺自己什麼都沒學到的樣子。
這一篇簡單介紹一下BootRom和操作系統啓動的知識。

Rocket-chip的BootRom

以下內容可能有誤,待以後更正。
BootRom是芯片內部集成的一塊很小的存儲區域,裏面一般會固化一段啓動代碼。當系統上電後,首先會將PC寄存器設置成BootRom裏面的代碼對應的一個地址。不同芯片的BootRom所佔用的地址空間地址、大小可能不同。

首先我們先打開~/rocket-chip/bootrom/文件夾下,可以看到五個文件:
在這裏插入圖片描述
我們這裏簡單看一下前三個文件的內容。

bootrom.img

img是image的縮寫,bootrom.img應該是燒到機器的ROM. 當CPU通電後,會去指定的內存地址讀取第一條指令。這個地址指向的就是ROM, 也即是boot.img裏的機器代碼。

A disk image, in computing, is a computer file containing the contents and structure of a disk volume or of an entire data storage device, such as a hard disk drive, tape drive, floppy disk, optical disc, or USB flash drive.

bootrom.S

以下代碼是bootrom.S中的內容:
沒找到Markdown的RISC-V彙編代碼塊就隨便選了個C代碼的

#define DRAM_BASE 0x80000000

.section .text.start, "ax", @progbits
.globl _start
_start:
  csrwi 0x7c1, 0 // disable chicken bits
  li s0, DRAM_BASE
  csrr a0, mhartid
  la a1, _dtb
  jr s0

.section .text.hang, "ax", @progbits
.globl _hang
_hang:
  csrwi 0x7c1, 0 // disable chicken bits
  csrr a0, mhartid
  la a1, _dtb
  csrwi mie, 0
1:
  wfi
  j 1b

.section .rodata.dtb, "a", @progbits
.globl _dtb
.align 5, 0
_dtb:
.ascii "DTB goes here"

我們可以簡單分析一下這個文件的內容:
首先是定義了DRAM的起始位置,也即是前幾篇我們所運行的代碼的存儲地址。

#define DRAM_BASE 0x80000000

緊接着這一部分的代碼可以查看RISC-V彙編指示符進行理解:

.section .text.start, "ax", @progbits //定義當前段名爲.text.start, 後面兩個不知道什麼意思
.globl _start //使用.global僞操作指定彙編程序入口:將_start標籤定義爲全局可見
_start:  //定義此處的標籤爲_start
  csrwi 0x7c1, 0 // disable chicken bits, 立即數寫入CSR 0x7c1
  //0x7c-0x7FF: Non-standart read/write CSR
  li s0, DRAM_BASE //li: 立即數加載
  csrr a0, mhartid //讀CSR的值寫入a0
  //mhartid: Hart編號寄存器。多hart系統中,至少有一個編號爲0
  la a1, _dtb //地址加載
  jr s0 //寄存器跳轉

一些小注釋:
chicken bit

A bit on a chip that can be used to disable one of the features of the chip if it proves or negatively impacts performance

Hart
取自"Hardware Thread"之意,表示一個硬件線程,單個處理器核中可能實現多份硬件線程。RISC-V架構規定,如果在單Hart或者多Hart的系統中,起碼要有一個Hart的編號必須是0。

然後這一段代碼:

.section .text.hang, "ax", @progbits  //定義當前段名爲.text.hang
.globl _hang //使用.global僞操作指定彙編程序入口:將_hang標籤定義爲全局可見
_hang: //定義此處的標籤爲_hang
  csrwi 0x7c1, 0 // disable chicken bits
  csrr a0, mhartid //讀CSR的值寫入a0
  //mhartid: Hart編號寄存器。多hart系統中,至少有一個編號爲0
  la a1, _dtb //地址加載
  csrwi mie, 0  //立即數寫CSR
  //mie: 0x304 機器模式中斷使能寄存器
1: 
  wfi //等待中斷
  j 1b

一些小注釋:
mie
mie寄存器用於控制不同中斷類型的局部屏蔽。

wfi
如果沒有待處理的中斷,則使處理器處於空閒狀態。

最後一段代碼:

.section .rodata.dtb, "a", @progbits //定義當前段名爲.rodata.dtb
//rodata: 只讀數據段
.globl _dtb //使用.global僞操作指定彙編程序入口:將_dtb標籤定義爲全局可見
.align 5, 0
_dtb: //定義此處的標籤爲_dtb
.ascii "DTB goes here"

一些小問題
align
在彙編指示符中:

指示符 描述
.align n Align the next datum on a 2n-byte boundary

比如:.align 2是按字對齊的(aligns the next value on a word boundary)

沒有找到.align x, y這種語句

linker.ld

文件內容及解釋如下:

SECTIONS
{
    ROM_BASE = 0x10000; /* ... but actually position independent */
    
     //把定位器符號置爲0x10000(若不指定,則該符號的初始值爲0)
    . = ROM_BASE;  
    //將所有(*符號代表任意輸入文件)輸入文件的.text.start合併爲一個.text.start
    //該section的地址由定位器符號的值指定
    .text.start : { *(.text.start) }
    
    //把定位器符號置爲0x10040
    . = ROM_BASE + 0x40;
    .text.hang : { *(.text.hang) }
    
    //把定位器符號置爲0x10080    
    . = ROM_BASE + 0x80;
    .rodata.dtb : { *(.rodata.dtb) }
}

操作系統啓動

引導加載程序

引導加載程序是系統加電運行的第一段軟件代碼。

PC 機中的引導加載程序由 BIOS(其本質就是一段固件程序)和位於硬盤 MBR 中的 OS Boot Loader(比如,LILO 和 GRUB 等)一起組成。BIOS 在完成硬件檢測和資源分配後,將硬盤 MBR 中的 Boot Loader 讀到系統的 RAM 中,然後將控制權交給 OS Boot Loader。Boot Loader 的主要運行任務就是將內核映象從硬盤上讀到 RAM 中,然後跳轉到內核的入口點去運行,也即開始啓動操作系統。
而在嵌入式系統中,通常並沒有像 BIOS 那樣的固件程序(注,有的嵌入式 CPU 也會內嵌一段短小的啓動程序),因此整個系統的加載啓動任務就完全由 Boot Loader 來完成。比如在一個基於 ARM7TDMI core 的嵌入式系統中,系統在上電或復位時通常都從地址 0x00000000 處開始執行,而在這個地址處安排的通常就是系統的 Boot Loader 程序。

BootLoader是操作系統內核運行前運行的一段程序,可以初始化硬件設備,建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便爲最終調用操作系統內核準備好正確的環境。

通常BootLoader是多階段的,可以分爲stage1和stage2

Stage1

  1. 基本的硬件初始化
    屏幕所有的中斷。爲中斷提供服務通常是 OS 設備驅動程序的責任,因此在 Boot Loader 的執行全過程中可以不必響應任何中斷。中斷屏蔽可以通過寫 CPU 的中斷屏蔽寄存器或狀態寄存器(比如 ARM 的 CPSR 寄存器)來完成。
    設置CPU的速度和時鐘頻率
    RAM初始化。包括正確地設置系統的內存控制器的功能寄存器以及各內存庫控制寄存器等。
    初始化LED。通過 GPIO 來驅動 LED,其目的是表明系統的狀態是 OK 還是 Error。如果板子上沒有LED,那麼也可以通過初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息來完成這一點。
    關閉CPU內部指令/數據cache

  2. 爲加載stage2準備RAM空間
    爲了獲得更快的執行速度,通常把 stage2 加載到 RAM 空間中來執行,因此必須爲加載 Boot Loader 的 stage2 準備好一段可用的 RAM 空間範圍。

  3. 拷貝stage2到RAM中
    拷貝時要確定兩點:(1) stage2 的可執行映象在固態存儲設備的存放起始地址和終止地址;(2) RAM 空間的起始地址。

  4. 設置堆棧指針 sp
    堆棧指針的設置是爲了執行 C 語言代碼作好準備。此外,在設置堆棧指針 sp 之前,也可以關閉 led 燈,以提示用戶我們準備跳轉到 stage2。

  5. 跳轉到 stage2 的 C 入口點
    在上述一切都就緒後,就可以跳轉到 Boot Loader 的 stage2 去執行了。比如,在 ARM 系統中,這可以通過修改 PC 寄存器爲合適的地址來實現。

Stage2

stage2的代碼通常由C語言實現,以便於實現更復雜的功能和取得更好的可讀性和可移植性。

  1. 初始化本階段要使用到的硬件設備
    這通常包括:(1)初始化至少一個串口,以便和終端用戶進行 I/O 輸出信息;(2)初始化計時器等。在初始化這些設備之前,也可以重新把 LED 燈點亮,以表明我們已經進入 main() 函數執行。設備初始化完成後,可以輸出一些打印信息,程序名字字符串、版本號等。

  2. 檢測系統的內存映射
    Boot Loader 的 stage2 必須知道 CPU 預留的全部 RAM 地址空間中的哪些被真正映射到 RAM 地址單元,哪些是處於 “unused” 狀態的。

  3. 加載內核映像和根文件系統映像
    對於內核映像,一般將其拷貝到從(MEM_START+0x8000) 這個基地址開始的大約1MB大小的內存範圍內(嵌入式 Linux 的內核一般都不操過 1MB)。爲什麼要把從 MEM_START 到 MEM_START+0x8000 這段 32KB 大小的內存空出來呢?這是因爲 Linux 內核要在這段內存中放置一些全局數據結構,如:啓動參數和內核頁表等信息。
    而對於根文件系統映像,則一般將其拷貝到 MEM_START+0x0010,0000 開始的地方。

  4. 設置內核的啓動參數
    應該說,在將內核映像和根文件系統映像拷貝到 RAM 空間中後,就可以準備啓動 Linux 內核了。但是在調用內核之前,應該作一步準備工作,即:設置 Linux 內核的啓動參數。

  5. 調用內核
    Boot Loader 調用 Linux 內核的方法是直接跳轉到內核的第一條指令處,也即直接跳轉到 MEM_START+0x8000 地址處。

後記

對彙編代碼不是太懂,翻了很多次手冊才知道代碼在幹什麼。爲什麼資料這麼少啊我佛了。
爲什麼沒有賣CSDN殭屍粉的啊,我想買幾十個給我刷666,實在寫不動了.
現在腦子裏都是小主播愛說的話:點個關注唄。

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 829
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章