1.典型嵌入式linux系統啟動過程:
嵌入式系統上電后先執行uboot、然后uboot負責初始化DDR,初始化Flash,然后將OS從Flash中讀取到DDR中,然后啟動OS(OS啟動后uboot就無用了)
2.uboot必須解決哪些問題
1、自身可開機直接啟動
(1)一般的SoC都支持多種啟動方式,譬如SD卡啟動、NorFlash啟動、NandFlash啟動等·····uboot要能夠開機啟動,必須根據具體的SoC的啟動設計來設計uboot
(2)uboot必須進行和硬件相對應的代碼級別的更改和移植,才能夠保證可以從相應的啟動介質啟動。uboot中第一階段的start.S文件中具體處理了這一塊。
2、能夠引導操作系統內核啟動並給內核傳參
(1)uboot的終極目標就是啟動內核。
(2)linux內核在設計的時候,設計為可以被傳參。也就是說我們可以在uboot中事先給linux內核准備一些啟動參數放在內存中特定位置然后傳給內核,內核啟動后會到這個特定位置去取uboot傳給他的參數,然后在內核中解析這些參數,這些參數將被用來指導linux內核的啟動過程。
3、能提供系統部署功能
(1)uboot必須能夠被人借助而完成整個系統(包括uboot、kernel、rootfs等的鏡像)在Flash上的燒錄下載工作。
(2)裸機教程中刷機(ARM裸機第三部分)就是利用uboot中的fastboot功能將各種鏡像燒錄到iNand中,然后從iNand啟動。
4、能進行soc級和板級硬件管理
(1)uboot中實現了一部分硬件的控制能力(uboot中初始化了一部分硬件),因為uboot為了完成一些任務必須讓這些硬件工作。譬如uboot要實現刷機必須能驅動iNand,譬如uboot要在刷機時LCD上顯示進度條就必須能驅動LCD,譬如uboot能夠通過串口提供操作界面就必須驅動串口。譬如uboot要實現網絡功能就必須驅動網卡芯片。
(2)SoC級(譬如串口)就是SoC內部外設,板級就是SoC外面開發板上面的硬件(譬如網卡、iNand)
5、uboot的“生命周期”
(1)uboot的生命周期就是指:uboot什么時候開始運行,什么時候結束運行。
(2)uboot本質上是一個裸機程序(不是操作系統),一旦uboot開始SoC就會單純運行uboot(意思就是uboot運行的時候別的程序是不可能同時運行的),一旦uboot結束運行則無法再回到uboot(所以uboot啟動了內核后uboot自己本身就死了,要想再次看到uboot界面只能重啟系統。重啟並不是復活了剛才的uboot,重啟只是uboot的另一生)
(3)uboot的入口和出口。uboot的入口就是開機自動啟動,uboot的唯一出口就是啟動內核。uboot還可以執行很多別的任務(譬如燒錄系統),但是其他任務執行完后都可以回到uboot的命令行繼續執行uboot命令,而啟動內核命令一旦執行就回不來了。
6、總結:一切都是為了啟動內核
3.uboot的命令式shell界面
uboot的常用命令
1、類似linux終端的行緩沖命令行
(1)行緩沖的意思就是:當我們向終端命令行輸入命令的時候,這些命令沒有立即被系統識別,而是被緩沖到一個緩存區(也就是系統認為我們還沒有輸入完),當我們按下回車鍵(換行)后系統就認為我們輸入完了,然后將緩沖區中所有剛才輸入的作為命令拿去分析處理。
(2)linux終端設計有3種緩沖機制:無緩沖、行緩沖、全緩沖
2、有些命令有簡化的別名
(1)譬如printenv命令可以簡化為print,譬如setenv可以簡化為set
3、有些命令會帶參數(注意格式是固定的)
(1)uboot的每個命令都有事先規定好的各種格式。有些命令就是不帶參數的,譬如printenv/print命令;有些命令帶可選的參數(可以帶也可以不帶,當然帶不帶參數的執行結果是不同的);有些命令帶必須的參數(譬如setenv/set命令)
4、命令中的特殊符號(譬如單引號)
(1)uboot的有些命令帶的參數非常長,為了告訴uboot這個非常長而且中間有好多個空格的東西是給他的一整個參數,所以用單引號將這個很長且中間有空格隔開的參數引起來。
(2)別的符號也許也有,而且有特定的意義。當碰到uboot的命令行有特殊符號時要注意不是弄錯了,而是可能有特別的含義。
5、有些命令是一個命令族(譬如movi)
(1)命令族意思就是好多個命令開頭都是用同一個命令關鍵字的,但是后面的參數不一樣,這些命令的功能和作用也不同。這就叫一個命令族。
(2)同一個命令族中所有的命令都有極大的關聯,譬如movi開頭的命令族都和moviNand(EMMC、iNand)操作有關。
6、第一個命令:printenv/print
(1)print命令不用帶參數,作用是打印出系統中所有的環境變量。
(2)環境變量就好像程序的全局變量一樣。程序中任何地方都可以根據需要去調用或者更改環境變量(一般都是調用),環境變量和全局變量不同之處在於:全局變量的生命周期是在程序的一次運行當中,開始運行時誕生程序結束時死亡,下次運行程序時從頭開始;但是環境變量被存儲在Flash的另一塊專門區域(Flash上有一個環境變量分區),一旦我們在程序中保存了該環境變量,那么下次開機時該環境變量的值將維持上一次更改保存后的值。
7、設置(添加/更改)環境變量:setenv/set
(1)用法:set name value
8、保存環境變量的更改:saveenv/save
(1)saveenv/save命令不帶參數,直接執行,作用是將內存中的環境變量的值同步保存到Flash中環境變量的分區。注意:環境變量的保存是整體的覆蓋保存,也就是說內存中所有的環境變量都會整體的將Flash中環境變量分區中原來的內容整體覆蓋。
總結:徹底更改一個環境變量的值,需要2步:第一步set命令來更改內存中的環境變量,第二步用save命令將其同步到Flash中環境變量的分區。
有時候我們只是想測試下這個環境變量,不希望影響到下一次開機,那就只set不save,這樣set后當前本次運行的uboot已經起效果了,只不過沒save下一次開機還是會恢復到原來的狀況。
9、網絡測試指令:ping
(1)命令用法: ping ip地址
注意:ping是測試開發板和主機之間的網絡鏈接,注意以下步驟:
1)首先要插上網線。
2)先試圖ping通主機windows。注意Windows中有線網卡的地址設置(設置本地連接)。設置主機windows的本地連接IPv4地址為192.168.1.10
3)第三步確認開發板中uboot里幾個網絡相關的環境變量的值對不對。最重要的是ipaddr(這個環境變量表示當前開發板的IP地址),這個地址必須和主機windows的IP地址在同一個網段。
10、tftp下載指令:tftp
(1)uboot本身主要目標是啟動內核,為了完成啟動內核必須要能夠部署內核,uboot為了部署內核就需要將內核鏡像從主機中下載過來然后燒錄到本地flash中。uboot如何從主機(windows或者虛擬機ubuntu)下載鏡像到開發板上?有很多種方式,主流方式是:fastboot和tftp。fastboot的方式是通過USB線進行數據傳輸。tftp的方式是通過有線網絡的。典型的方式就是通過網絡,fastboot是近些年才新發展的。
(2)tftp方式下載時實際上uboot扮演的是tftp客戶端程序角色,主機windows或虛擬機ubuntu中必須有一個tftp服務器,然后將要下載的鏡像文件放在服務器的下載目錄中,然后開發板中使用uboot的tftp命令去下載即可。
11、nfs啟動內核命令:nfs
(1)uboot中也支持nfs命令,但是我基本沒用過。
12、SD卡/iNand操作指令movi
(1)開發板如果用SD卡/EMMC/iNand等作為Flash,則在uboot中操作flash的指令為movi(或mmc)
(2)movi指令是一個命令集,有很多子命令,具體用法可以help movi查看。
(3)movi的指令都是movi read和movi write一組的,movi read用來讀取iNand到DDR上,movi write用來將DDR中的內容寫入iNand中。理解這些指令時一定要注意涉及到的2個硬件:iNand和DDR內存。
(4)movi read {u-boot | kernel} {addr} 這個命令使用了一種通用型的描述方法來描述:movi 和 read外面沒有任何標記說明每一次使用這個指令都是必選的;一對大括號{}括起來的部分必選1個,大括號中的豎線表是多選一。中括號[]表示可選參數(可以有也可以沒有)
(5)指令有多種用法,譬如 movi read u-boot 0x30000000,意思就是把iNand中的u-boot分區讀出到DDR的0x30000000起始的位置處。(uboot代碼中將iNand分成了很多個分區,每個分區有地址范圍和分區名,uboot程序操作中可以使用直接地址來操作iNand分區,也可以使用分區名來操作分區。);注意這里的0x30000000也可以直接寫作30000000,意思是一樣的(uboot的命令行中所有數字都被默認當作十六進制處理,不管你加不加0x都一樣)。
13、NandFlash操作指令nand
(1)理解方法和操作方法完全類似於movi指令
14、內存操作指令:mm、mw、md
(1)DDR中是沒有分區的(只聽說過對硬盤、Flash進行分區,沒聽說過對內存進行分區····),但是內存使用時要注意,千萬不能越界踩到別人了。因為uboot是一個裸機程序,不像操作系統會由系統整體管理所有內存,系統負責分配和管理,系統會保證內存不會隨便越界。然后裸機程序中uboot並不管理所有內存,內存是散的隨便用的,所以如果程序員(使用uboot的人)自己不注意就可能出現自己把自己的數據給覆蓋了。(所以你思考下我們為什么把uboot放在23E00000地址處)
(2)md就是memory display,用來顯示內存中的內容。
(3)mw就是memory write,將內容寫到內存中
(4)mm就是memory modify,修改內存中的某一塊,說白了還是寫內存(如果需要批量的逐個單元的修改內存,用mm最合適)
15、啟動內核指令:bootm、go
(1)uboot的終極目標就是啟動內核,啟動內核在uboot中表現為一個指令,uboot命令行中調用這個指令就會啟動內核(不管成功與否,所以這個指令是一條死路)。
(2)差別:bootm啟動內核同時給內核傳參,而go命令啟動內核不傳參。bootm其實才是正宗的啟動內核的命令,一般情況下都用這個;go命令本來不是專為啟動內核設計的,go命令內部其實就是一個函數指針指向一個內存地址然后直接調用那個函數,go命令的實質就是PC直接跳轉到一個內存地址去運行而已。go命令可以用來在uboot中執行任何的裸機程序(有一種調試裸機程序的方法就是事先啟動uboot,然后在uboot中去下載裸機程序,用go命令去執行裸機程序)
uboot的常用環境變量
1.環境變量如何參與程序運行
(1)環境變量有2份,一份在Flash中,另一份在DDR中。uboot開機時一次性從Flash中讀取全部環境變量到DDR中作為環境變量的初始化值,然后使用過程中都是用DDR中這一份,用戶可以用saveenv指令將DDR中的環境變量重新寫入Flash中去更新Flash中環境變量。下次開機時又會從Flash中再讀一次。
(2)環境變量在uboot中是用字符串表示的,也就是說uboot是按照字符匹配的方式來區分各個環境變量的。因此用的時候一定要注意不要打錯字了。
2.常用環境變量
1、自動運行倒數時間:bootdelay
2、網絡設置:ipaddr serverip
(1)ipaddr是開發板的本地IP地址
(2)serverip是開發板通過tftp指令去tftp服務器下載東西時,tftp服務器的IP地址。
(3)gatewayip是開發板的本地網關地址
(4)netmask是子網掩碼
(5)ethaddr是開發板的本地網卡的MAC地址。
3、自動運行命令設置:bootcmd
(1)uboot啟動后會開機自動倒數bootdelay秒,如果沒有人按下回車打斷啟動,則uboot會自動執行啟動命令來啟動內核。
(2)uboot開機自動啟動時實際就是在內部執行了bootcmd這個環境變量的值所對應的命令集。
(3)bootcmd=movi read kernel 30008000; bootm 30008000 意思是:將iNand的kernel分區讀取到DDR內存的0x30008000地址處,然后使用bootm啟動命令從內存0x30008000處去啟動內核。
(4)set bootcmd printenv,然后saveenv;然后重啟則會看到啟動倒數后自動執行printenv命令打印出環境變量。這個小實驗說明開機自動執行了bootcmd。
(5)set bootcmd 'movi read kernel 30008000; bootm 30008000'
4、uboot給kernel傳參:bootargs
(1)linux內核啟動時可以接收uboot給他傳遞的啟動參數,這些啟動參數是uboot和內核約定好的形式、內容,linux內核在這些啟動參數的指導下完成啟動過程。這樣的設計是為了靈活,為了內核在不重新編譯的情況下可以用不同的方式啟動。
(2)我們要做的事情就是:在uboot的環境變量中設置bootargs,然后bootm命令啟動內核時會自動將bootargs傳給內核。
(3)bootargs=console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3 意義解釋:
console=ttySAC2,115200 控制台使用串口2,波特率115200.
root=/dev/mmcblk0p2 rw 根文件系統在SD卡端口0設備(iNand)第2分區,根文件系統是可讀可寫的
init=/linuxrc linux的進程1(init進程)的路徑
rootfstype=ext3 根文件系統的類型是ext3
(4)內核傳參非常重要。在內核移植的時候,新手經常因為忘記給內核傳參,或者給內核傳遞的參數不對,造成內核啟動不起來。
5、新建、更改、刪除一個環境變量的方法
(1)新建一個環境變量,使用set var value
(2)更改一個環境變量,使用set var value
(3)刪除一個環境變量,使用set var
6、注意:環境變量更改后的保存 save
(1)修改完成環境變量后一定要保存,否則下次開機更改就又沒了。
uboot中對Flash和DDR的管理
1、uboot階段Flash的分區
(1)所謂分區,就是說對Flash進行分塊管理。
(2)PC機等產品中,因為大家都是在操作系統下使用硬盤的,整個硬盤由操作系統統一管理,操作系統會使用文件系統幫我們管理硬盤空間。(管理保證了文件之間不會互相堆疊),於是乎使用者不用自己太過在意分區問題。
(3)在uboot中是沒有操作系統的,因此我們對Flash(相當於硬盤)的管理必須事先使用分區界定(實際上在uboot中和kernel中都有個分區表,分區表就是我們在做系統移植時對Flash的整體管理分配方法)。有了這個界定后,我們在部署系統時按照分區界定方法來部署,uboot和kernel的軟件中也是按照這個分區界定來工作,就不會錯。
(4)分區方法不是一定的,不是固定的,是可以變動的。但是在一個移植中必須事先設計好定死,一般在設計系統移植時就會定好,定的標准是:
uboot:uboot必須從Flash起始地址開始存放(也許是扇區0,也許是扇區1,也許是其他,取決於SoC的啟動設計),uboot分區的大小必須保證uboot肯定能放下,一般設計為512KB或者1MB(因為一般uboot肯定不足512KB,給再大其實也可以工作,但是浪費);
環境變量:環境變量分區一般緊貼着uboot來存放,大小為32KB或者更多一點。
kernel:kernel可以緊貼環境變量存放,大小一般為3MB或5MB或其他。
rootfs:······
剩下的就是自由分區,一般kernel啟動后將自由分區掛載到rootfs下使用
總結:一般規律如下:
(1)各分區彼此相連,前面一個分區的結尾就是后一個分區的開頭。
(2)整個flash充分利用,從開頭到結尾。
(3)uboot必須在Flash開頭,其他分區相對位置是可變的。
(4)各分區的大小由系統移植工程師自己來定,一般定為合適大小(不能太小,太小了容易溢出;不能太大,太大了浪費空間)
(5)分區在系統移植前確定好,在uboot中和kernel中使用同一個分區表。將來在系統部署時和系統代碼中的分區方法也必須一樣。
2、uboot階段DDR的分區
(1)DDR的分區和Flash的分區不同,主要是因為Flash是掉電存在的,而DDR是掉電消失,因此可以說DDR是每次系統運行時才開始部署使用的。
(2)內存的分區主要是在linux內核啟動起來之前,linux內核啟動后內核的內存管理模塊會接管整個內存空間,那時候就不用我們來管了。
(3)注意內存分區關鍵就在於內存中哪一塊用來干什么必須分配好,以避免各個不同功能使用了同一塊內存造成的互相踩踏。譬如說我們tftp 0x23E00000 zImage去下載zImage到內存的0x23E00000處就會出錯,因為這個內存處實際是uboot的鏡像所在。這樣下載會導致下載的zImage把內存中的uboot給沖掉。