Bootloader介紹
Bootloader是進行嵌入式開發必然會接觸的一個概念,它是嵌入式學院<嵌入式工程師職業培訓班>二期課程中嵌入式linux系統開發方面的重要內容。本篇文章主要講解Bootloader的基本概念以及內部原理,這部分內容的掌握將對嵌入式linux系統開發的學習非常有幫助!
Bootloader的定義:
Bootloader是在操作系統運行之前執行的一小段程序,通過這一小段程序,我們可以初始化硬件設備、建立內存空間的映射表,從而建立適當的系統軟硬件環境,為終調用操作系統內核做好準備。
意思就是說如果我們要想讓一個操作系統在我們的板子上運轉起來,我們就必須首先對我們的板子進行一些基本配置和初始化,然后才可以將操作系統引導進來運行。具體在Bootloader中完成了哪些操作我們會在后面分析到,這里我們先來回憶一下PC的體系結構: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程序。(先想一下,通用PC和嵌入式系統為何會在此處存在如此的差異呢?)
Bootloader是基于特定硬件平臺來實現的,因此幾乎不可能為所有的嵌入式系統建立一個通用的Bootloader,不同的處理器架構都有不同的Bootloader,Bootloader不但依賴于cpu的體系結構,還依賴于嵌入式系統板級設備的配置。對于2塊不同的板子而言,即使他們使用的是相同的處理器,要想讓運行在一塊板子上的Bootloader程序也能運行在另一塊板子上,一般也需要修改Bootloader的源程序。
Bootloader的啟動方式
Bootloader的啟動方式主要有網絡啟動方式、磁盤啟動方式和Flash啟動方式。
1、網絡啟動方式

圖1 Bootloader網絡啟動方式示意圖
如圖1所示,里面主機和目標板,他們中間通過網絡來連接,首先目標板的DHCP/BIOS通過BOOTP服務來為Bootloader分配IP地址,配置網絡參數,這樣才能支持網絡傳輸功能。我們使用的u-boot可以直接設置網絡參數,因此這里就不用使用DHCP的方式動態分配IP了。接下來目標板的Bootloader通過TFTP服務將內核映像下載到目標板上,然后通過網絡文件系統來建立主機與目標板之間的文件通信過程,之后的系統更新通常也是使用Boot Loader的這種工作模式。工作于這種模式下的Boot Loader通常都會向它的終端用戶提供一個簡單的命令行接口。
2、磁盤啟動方式
這種方式主要是用在臺式機和服務器上的,這些計算機都使用BIOS引導,并且使用磁盤作為存儲介質,這里面兩個重要的用來啟動linux的有LILO和GRUB,這里就不再具體說明了。
3、Flash啟動方式
這是我們常用的方式。Flash有NOR Flash和NAND Flash兩種。NOR Flash可以支持隨機訪問,所以代碼可以直接在Flash上執行,Bootloader一般是存儲在Flash芯片上的。另外Flash上還存儲著參數、內核映像和文件系統。這種啟動方式與網絡啟動方式之間的不同之處就在于,在網絡啟動方式中,內核映像和文件系統首先是放在主機上的,然后經過網絡傳輸下載進目標板的,而這種啟動方式中內核映像和文件系統則直接是放在Flash中的,這兩點在我們u-boot的使用過程中都用到了。
U-boot的定義
U-boot,全稱Universal Boot Loader,是由DENX小組的開發的遵循GPL條款的開放源碼項目,它的主要功能是完成硬件設備初始化、操作系統代碼搬運,并提供一個控制臺及一個指令集在操作系統運行前操控硬件設備。U-boot之所以這么通用,原因是他具有很多特點:開放源代碼、支持多種嵌入式操作系統內核、支持多種處理器系列、較高的穩定性、高度靈活的功能設置、豐富的設備驅動源碼以及較為豐富的開發調試文檔與強大的網絡技術支持。另外u-boot對操作系統和產品研發提供了靈活豐富的支持,主要表現在:可以引導壓縮或非壓縮系統內核,可以靈活設置/傳遞多個關鍵參數給操作系統,適合系統在不同開發階段的調試要求與產品發布,支持多種文件系統,支持多種目標板環境參數存儲介質,采用CRC32校驗,可校驗內核及鏡像文件是否完好,提供多種控制臺接口,使用戶可以在不需要ICE的情況下通過串口/以太網/USB等接口下載數據并燒錄到存儲設備中去(這個功能在實際的產品中是很實用的,尤其是在軟件現場升級的時候),以及提供豐富的設備驅動等。
u-boot源代碼的目錄結構
1、board中存放于開發板相關的配置文件,每一個開發板都以子文件夾的形式出現。
2、Commom文件夾實現u-boot行下支持的命令,每一個命令對應一個文件。
3、cpu中存放特定cpu架構相關的目錄,每一款cpu架構都對應了一個子目錄。
4、Doc是文檔目錄,有u-boot非常完善的文檔。
5、Drivers中是u-boot支持的各種設備的驅動程序。
6、Fs是支持的文件系統,其中常用的是JFFS2文件系統。
7、Include文件夾是u-boot使用的頭文件,還有各種硬件平臺支持的匯編文件,系統配置文件和文件系統支持的文件。
8、Net是與網絡協議相關的代碼,bootp協議、TFTP協議、NFS文件系統得實現。
9、Tooles是生成U-boot的工具。
對u-boot的目錄有了一些了解后,分析啟動代碼的過程就方便多了,其中比較重要的目錄就是/board、/cpu、/drivers和/include目錄,如果想實現u-boot在一個平臺上的移植,就要對這些目錄進行深入的分析。
u-boot的啟動過程
系統啟動的入口點。既然我們現在要分析u-boot的啟動過程,就必須先找到u-boot實現的是哪些代碼,完成的是哪些任務。另一方面一個可執行的image必須有一個入口點,并且只能有一個全局入口點,所以要通知編譯器這個入口在哪里。由此我們可以找到程序的入口點是在/board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)說明程序從_start開始運行,而他指向的是cpu/arm7tdmi/start.o文件。因為我們用的是ARM7TDMI的cpu架構,在復位后從地址0x00000000取它的條指令,所以我們將Flash映射到這個地址上,這樣在系統加電后,cpu將首先執行u-boot程序。
u-boot的啟動過程是多階段實現的,分了兩個階段。依賴于cpu體系結構的代碼(如設備初始化代碼等)通常都放在stage1中,而且通常都是用匯編語言來實現,以達到短小精悍的目的。而stage2則通常是用C語言來實現的,這樣可以實現復雜的功能,而且代碼具有更好的可讀性和可移植性。
下面我們先詳細分析下stage1中的代碼,如圖2所示:

圖2 Start.s程序流程
代碼真正開始是在_start,設置異常向量表,這樣在cpu發生異常時就跳轉到/cpu/arm7tdmi/interrupts中去執行相應得中斷代碼。在interrupts文件中大部分的異常代碼都沒有實現具體的功能,只是打印一些異常消息,其中關鍵的是reset中斷代碼,跳到reset入口地址。
reset復位入口之前有一些段的聲明。在reset中,首先是將cpu設置為svc32模式下,并屏蔽所有irq和fiq。在u-boot中除了定時器使用了中斷外,其他的基本上都不需要使用中斷,比如串口通信和網絡等通信等,在u-boot中只要完成一些簡單的通信就可以了,所以在這里屏蔽掉了所有的中斷響應。
初始化外部總線。這部分首先設置了I/O口功能,包括串口、網絡接口等的設置,其他I/O口都設置為GPIO。然后設置BCFG0~BCFG3,即外部總線控制器。這里bank0對應Flash,設置為16位寬度,總線速度設為慢,以實現穩定的操作;Bank1對應DRAM,設置和Flash相同;Bank2對應RTL8019。
接下來是cpu關鍵設置,包括系統重映射(告訴處理器在系統發生中斷的時候到外部存儲器中去讀取中斷向量表)和系統頻率。
lowlevel_init,設定RAM的時序,并將中斷控制器清零。這些部分和特定的平臺有關,但大致的流程都是一樣的。
下面就是代碼的搬移階段了。為了獲得更快的執行速度,通常把stage2加載到RAM空間中來執行,因此必須為加載Boot Loader的stage2準備好一段可用的RAM空間范圍。空間大小是memory page大小(通常是4KB)的倍數,一般而言,1M的RAM空間已經足夠了。flash中存儲的u-boot可執行文件中,代碼段、數據段以及BSS段都是首尾相連存儲的,所以在計算搬移大小的時候就是利用了用BSS段的首地址減去代碼的首地址,這樣算出來的就是實際使用的空間。程序用一個循環將代碼搬移到0x81180000,即RAM底端1M空間用來存儲代碼。然后程序繼續將中斷向量表搬到RAM的頂端。由于stage2通常是C語言執行代碼,所以還要建立堆棧去。在堆棧區之前還要將malloc分配的空間以及全局數據所需的空間空下來,他們的大小是由宏定義給出的,可以在相應位置修改。基本內存分布圖:

圖3 搬移后內存分布情況圖
接下來是u-boot啟動的第二個階段,是用c代碼寫的,這部分是一些相對變化不大的部分,我們針對不同的板子改變它調用的一些初始化函數,并且通過設置一些宏定義來改變初始化的流程,所以這些代碼在移植的過程中并不需要修改,也是錯誤相對較少出現的文件。在文件的開始先是定義了一個函數指針數組,通過這個數組,程序通過一個循環來按順序進行常規的初始化,并在其后通過一些宏定義來初始化一些特定的設備。在程序進入一個循環,main_loop。這個循環接收用戶輸入的命令,以設置參數或者進行啟動引導。
本篇文章將分析重點放在了前面的start.s上,是因為這部分無論在移植還是在調試過程中都是容易出問題的地方,要解決問題就需要程序員對代碼進行修改,所以在這里簡單介紹了一下start.s的基本流程,希望能對大家有所幫助。