u-boot的啟動過程比較簡單,大致做下面的工作:
1 cpu初始化
2 時鐘,串列埠,記憶體(ddr ram)初始化
3 記憶體劃分,分配棧,資料,配置引數,以及u-boot**在記憶體中的位置。
4 對u-boot**做relocate
5 初始化 malloc,flash,pci 以及外設(比如,網口)
6 進入命令列或者直接啟動linux kernel
基本上,這就是u-boot的啟動要做的事情,我也曾經大致看過arm的啟動**,也是類似。
不過,這裡以mips作為例子進行介紹。
啟動涉及到幾個檔案: start.s, cache.s, lowlevel_init.s 和 board.c
前三個都是彙編**。
程式從start.s的_start開始執行。首先,初始化中斷向量,暫存器清零,大致包括32
個通用暫存器reg0-reg31和協處理器的一些暫存器:cp0_watchlo, cp0_watchhi
, cp0_cause, cp0_count, cp0_compare等等。
之後,配置暫存器cp0_status,設定所使用的協處理器,中斷以及cpu
執行級別(核心級)。
配置gp暫存器,把got段的位址賦給gp暫存器。(gp暫存器的用處會在後面relocate code
的部分詳細解釋)
這時,開始執行lowlevel_init.s的lowlevel_init,主要目的是工作頻率配置,比如cpu
的主頻,匯流排(ahb),ddr工作頻率等。
然後,呼叫cache.s的mips_cache_reset對cache進行初始化。接著呼叫cache.s的
mips_cache_lock。這個呼叫的目的,起初讓我不解,後來才知道。這時ddr ram
並沒有配置好,而如果直接呼叫c語言的函式必須完成棧的設定,而棧必定要在ram
中。所以,只有先把一部分cache拿來當ram用。做法就是把一部分cache
配置為棧的位址,鎖定。這樣,當讀寫棧的記憶體空間時,只會訪問cache
,而不會訪問真的ram位址了。
這時,配置棧的位址,進行呼叫函式board_init_f(board.c)
進入函式board_init_f後,首先做一系列初始化:
timer_init 時鐘初始化
env_init 環境變數初始化(取得環境變數存放的位址)
init_baudrate 串列埠速率
serial_init 串列埠初始化
console_init_f 配置控制台
display_banner 顯示u-boot啟動資訊,版本號等
checkboard 執行board相關的操作。
init_func_ram 初始化記憶體,配置ddr controller
這一系列工作完成後,串列埠和記憶體都已經可以用了。然後,就要把記憶體進行劃分,
在記憶體的最後一部分,留出u-boot**大小的空間,準備把u-boot**從flash搬移到這裡
然後,是堆的空間,malloc的記憶體就來自於這裡。緊接著放兩個全域性資料結構bd_info
global_data和環境變數boot_params。最後,是棧的空間。
記憶體劃分好,就準備進行relocate code了。關於relocate code
和其他啟動過程下次再介紹。
上次講到,記憶體劃分好,準備進行relocate code。
relocate code的意思是這樣的。通常u-boot的執行**肯定是在flash上(當除錯的時候也可以放在ram上)。當啟動起來以後,要把它從flash上搬移到ram裡執行。這個工作就叫做relocate code。
但是,問題在於,flash上的位址和ram上的位址是不同的。當我們把**從flash上搬移到ram上以後,當執行函式跳轉時,**裡的函式位址還是flash上的位址,所以一跳就跳回去了。
這怎麼辦呢?
在u-boot裡面用的是pic(position-independent code)的方式解決這個問題。
簡單介紹一下其原理。當你用pic方式時,在用gcc編譯時需加上 -fpic的選項。編譯器會為你的可執行**建立乙個got(global offset table)的段。乙個位址在got表中有一項,裡面存放位址的資訊,而在使用這個位址時,只要根據這個位址的編號(也可以叫做偏移量offset)找到表中相應的專案,就可以取得那個位址了。
而如果位置發生變化,只要對got表中的位址進行修改就可以了。
我們可以通過反彙編,看乙個簡單的函式呼叫例子:
lw t9,1088(gp)
jalr t9
這裡,gp存放的就是got表的起始位址,而1088就是要呼叫函式的offset,也就是說got表的那個位置存放著它的位址。lw t9,1088(gp) 把函式位址放入t9, 然後呼叫就可以了。
知道了pic的原理,解釋u-boot relocate code的方法就簡單了。
簡單的說就把u-boot的執行**直接從flash裡copy到ram的相應區域。
然後,把got表中的位址都加上乙個偏移量,這個偏移量就是flash裡的位址與ram裡的位址的差。
還有其他一些工作比如:設定新的棧指標,從flash**裡跳轉到ram**裡 等等。
之後,就進入board.c的board_init_r函式,在這個函式裡初始化 malloc,flash,pci 以及外設(比如,網口),最後進入命令列或者直接啟動linux kernel。
這樣,u-boot的啟動工作就完成了。
基於mips架構的uboot啟動流程(3)
要注意mips具有流水線可見性,所以跟在跳轉指令後的下一條指令,在執行跳轉到的地方前,都會執行,這個叫分支延遲。但是編譯器會隱藏該特性,但可以通過設定 set noreorder 來禁止編譯器重新組織 順序。每個板子都有自己的lds檔案。這個主要是用來說明編譯生成的指令,及執行過程中用到的資料放置的...
基於mips架構的uboot啟動流程(4)
特點和功能 u boot yamon 支援的 cpu 和 board 1.支援種類繁多,包括 arm 的各個系列 ppc,mips 2.mips 的支援較差,目前支援 au1100,au1500 系列,4kec 的需要自己移植 工作量主要在起始 的初始化部分,包括 cahe 和中斷的初始化 1.基本...
基於mips架構的uboot啟動流程(4)
特點和功能 u boot yamon 支援的 cpu 和 board 1.支援種類繁多,包括 arm 的各個系列 ppc,mips 2.mips 的支援較差,目前支援 au1100,au1500 系列,4kec 的需要自己移植 工作量主要在起始 的初始化部分,包括 cahe 和中斷的初始化 1.基本...