整體簡介
本筆記為記錄嵌入式Linux的uboot部分基礎知識,結合源碼對uboot的實作原理和應用展開學習
授課老師:朱有鵬
記錄少女:當機醬=v=
2017.03
本part講uboot的使用和相關概念。
後面會講uboot的設計群組成原理,再往後就是移植過程。
我們的目的就是把ARM端的作業系統搭建起來,以後呢,就不玩裸機了喂。
筆記正文
一、學習前傳
1.1為什麼要有uboot
uboot是用來 啟動作業系統核心 的!還有别的輔助功能。
1啟動作業系統核心
2部署計算機系統
3操作Flash等上硬碟驅動
4提供指令行界面
uboot類似于PC機系統中的BIOS設計。典型的嵌入式系統的部署是uboot在一塊支援啟動的Flash上,OS部署在Flash(通常是另一塊Flash,NandFlash or iNand,慢慢會發展成一塊兒)。
其中,環境變量和指令是uboot的重要部分。此外uboot對于Flash和DDR有管理功能。
啟動過程
step1: 先執行uboot
step2: uboot初始化DDR,初始化Flash
step3: 将OS從Flash讀取到DDR中,啟動OS。啟動完成後uboot就無用了。
總結:嵌入式系統啟動其實和PC沒兩樣
隻是BIOS代替成uboot
硬碟替換成了Flash
1.2 uboot之發展曆史
uboot經過多年發展,已經成為事實上業内的bootloader标準。
uboot的版本号問題
早期的uboot版本号類似于:
uboot1.3.4 //這個1.3.4是最後的老版本
從2008左右開始就不是這樣子了,後來變成了類似于:
uboot-2010-06
(字尾帶有 -rc 的是非正式版本)
核心部分就幾乎沒變化,越新版本支援的開發闆越多。當然也不是越新越好,新的東西有很多備援。
uboot可移植了解
universal bootloader(通用的啟動代碼),具有可移植性。
注意:并不是說在哪個開發闆都可以随便用。而是具有在源代碼級别的移植能力。如果沒有移植的話就可以直接用的。代碼必然是要修改的!不是下載下傳下來就能用。
uboot的成功:時勢造英雄,他的出現是一種必然。如果沒有uboot也會有另一個bootloader代替。
在那個年代嵌入式瘋狂的發展,很需要一種bootloader能夠移植上去,啟動作業系統。是以uboot站出來了
1.4 uboot工作方式
uboot是一個單線程裸機程式。一旦運作uboot就不能運作别的 程式。
入口是開機自動啟動
出口是唯一出口,啟動核心
核心啟動之後,uboot就會結束,而且不再重生。除非下一次開機。
uboot沒有運作時表現為uboot.bin,放在存儲媒體裡,運作時被加載入記憶體,逐漸執行。
1.5 常用指令
uboot的指令和環境變量機制幾乎和OS完全一樣,連題目的名稱都不帶變的。
指令使用行緩沖。 //linux緩沖方式還有無緩沖和全緩沖
簡寫指令:
printenv = print //列印環境變量
setenv = set //設定環境變量
注意:自己嘗試過,pri就能自己列印出printenv的内容
指令族
有些指令有衍生的 多個指令
如 movi
movi init,movi read等。指令一樣,參數不一樣。
環境變量和全局變量的不同
環境變量生命周期長,他存儲在FLash的一塊專門區域。而全局變量在程式結束後消亡。
理論上講,環境變量在更改後,下一次開機依然保留才對,但是我試過的和老朱說的不一樣。
是因為我們沒有儲存!指令:save/saveenv
我們操作的環境變量的是DDR中的環境變量,
DDR斷電不儲存,save指令是就把運作時記憶體中的環境變量寫回Flash
注意這裡的 save,會讓原來的Flash中環境變量完全重寫。
Flash裡面有好多分區
完整Linux系統分區簡表(按順序,uboot為最開始)
序号 | 分區名 | 簡寫名 |
---|---|---|
1 | uboot | uboot |
2 | 環境變量 | env |
3 | 作業系統核心 | kernel |
4 | 根檔案系統 | rootfs |
1.9 uboot指令
指令規範
movi read {aaa | bbb} [xxxx]
//movi read 是必填,{ }裡面的内容多選一,[ ]的内容是選填
uboot預設數字問題
指令行中,uboot中所有的數字都會被當做16進制處理!!
uboot中沒有預設十進制的。uboot中出現數字90%的情況都是位址,是有意義的。
【movi read】
movi read u-boot 0x30000000
//将iNand中的u-boot分區讀到DDR的0x30000000處,注意讀的是分區
//也許uboot本身不大,但分區有1M那麼這裡還是讀1M大小過去
//在uboot代碼中,iNand被分成了很多個分區,每個分區都有獨自的位址和名稱。
movi read {sector#} {byte(hex)} {addr}
//sector是扇區,讀某扇區多少位元組,讀到哪個位址。
【nand】
//完全類似movi,操作NandFlash
【記憶體操作指令:mm mw md】
記憶體是沒有分區的,我們要注意防止越界,越界會踩到别人。作業系統中不容易越界,但uboot是裸機程式,是以我們使用uboot時不注意,就可能發生自己把自己資料給覆寫了的問題。
我們之前usb下載下傳放在0x23E00000位址處就是放在了一個低位址。(也和虛拟位址映射有關)
md,memory display
mw,寫記憶體,一般用的很少
mm,批量寫記憶體
【啟動核心指令:bootm, go】
bootm是能夠傳參的,他其實是正宗的啟動指令
go不能傳參,他本身不是為了啟動核心用的,内部就是吧pc跳轉到一個記憶體位址去執行。
go可以在uboot中執行任何的裸機程式
有一種調試裸機程式的方法就是事先啟動uboot,然後再uboot中下載下傳裸機程式 ,用go指令執行裸機程式。
1.10 uboot常用環境變量
ipaddr:開發闆ip位址
serverip:tftp伺服器的ip位址
gatewayip:開發闆的本地網關位址
ethaddr:開發闆本地網卡的MAC位址。
Tips:重裝系統為什麼MAC位址會變?是因為驅動重新安裝了
啟動核心的最關鍵環境變量
bootcmd(重要)自動運作指令設定
uboot啟動後會倒數秒數,如果沒有人按下Enter鍵打斷則會啟動核心
這個 啟動核心的 功能其實内部就是執行了bootcmd
查一下:
bootcmd=movi read kernel
//讀iNand中核心分區到30008000
movi read rootfs 30B00000 300000; //讀根檔案系統
bootm 30008000 30B00000 //啟動核心和根檔案系統
//iNand,可以簡單的看成SD卡或MMC卡晶片化
bootargs(重要)
bootargs是uboot給kernel傳參用的。linux核心與uboot有約定好的參數。這樣的設計是為了靈活,為了核心在不重新編譯的條件下以不同方式啟動。
bootargs=console=ttySAC2, //控制台使用SAC2,即序列槽2,波特率115200
root=/dev/mmcblk0p2 rw //rootfs使用mmc,block端口0,第二分區
//rw表示可讀可寫
init=/linuxrc //linux的程序1(init程序)的路徑
rootfstype=ext3 //rootfs類型,ext3是一種檔案系統類型。
核心傳參是非常重要的,新手經常忘記和核心傳參,或者傳參不對,造成核心啟動失敗。
【建立、删除環境變量】
set var value //建立、更改
set var //删除
1.12.uboot中對Flash和DDR的管理
uboot對Flash和記憶體進行分區。
分區方法不是一定的,不是固定的,是可以變動的。但是在一個移植中必須事先設計好定死,一般在設計系統移植時就會定好,定的标準是:
uboot:uboot必須從Flash起始位址開始存放(也許是扇區0,也許是扇區1,也許是其他,取決于SoC的啟動設計),uboot分區的大小必須保證uboot肯定能放下,一般設計為512KB或者1MB(因為一般uboot肯定不足512KB,給再大其實也可以工作,但是浪費);
環境變量:環境變量分區一般緊貼着uboot來存放,大小為32KB或者更多一點。
kernel:kernel可以緊貼環境變量存放,大小一般為3MB或5MB或其他。
rootfs:rootfsl可以緊貼kernel存放
再往後面:則是自由分區。
總結:一般規律如下:
(1)各分區彼此相連,前面一個分區的結尾就是後一個分區的開頭。
(2)整個flash充分利用,從開頭到結尾。
(3)uboot必須在Flash開頭,其他分區相對位置是可變的。
(4)各分區的大小由系統移植工程師自己來定,一般定為合适大小(不能太小,太小了容易溢出;不能太大,太大了浪費空間)
(5)分區在系統移植前确定好,在uboot中和kernel中使用同一個分區表。将來在系統部署時和系統代碼中的分區方法也必須一樣。
12.2、uboot階段DDR的分區
(1)DDR的分區和Flash的分區不同,因為Flash是掉電存在的,而DDR是掉電消失,是以可以說DDR是每次系統運作時才開始部署使用的。
(2)記憶體的分區主要是在linux核心啟動起來之前,linux核心啟動後核心的記憶體管理子產品會接管整個記憶體空間,那時候就不用我們來管了。
(3)注意記憶體分區關鍵就在于記憶體中哪一塊用來幹什麼必須配置設定好,以避免各個不同功能使用了同一塊記憶體造成的互相踩踏。譬如說我們tftp 0x23E00000 zImage去下載下傳zImage到記憶體的0x23E00000處就會出錯,因為這個記憶體處實際是uboot的鏡像所在。這樣下載下傳會導緻下載下傳的zImage把記憶體中的uboot給沖掉。
2.1.shell介紹
shell是作業系統的終端指令行
系統提供給使用者操作的指令行界面,是人機互動的一種方式
shell用來幹什麼?
在linux下建立100個檔案,分别為a1.c a2.c…..a100.c。最好的做法就是把建立過程寫成一 個shell腳本程式。
shell是一類程式設計語言,而不是一個語言。
shell語言,又叫腳本語言。常用shell語言:sh、bash、csh、ksh、perl、python等
perl、python等進階shell語言,常用在網絡管理配置等領域,系統運維人員一般要學習這些。
腳本語言一般在嵌入式中應用,主要是用來做配置。
linux下最常用的腳本就是bash,我們學習也是以bash為主。
shell腳本的運作機制:解釋運作
C語言(C++)這種編寫過程是:編寫出源代碼(源代碼是不能直接運作的)然後編譯連結形成可執行二進制程式,然後才能運作;腳本程式編寫好後源代碼即可直接運作(沒有編譯連結過程)
所謂解釋運作就是說當我們執行一個shell程式時,shell解析器會逐行的解釋shell程式代碼,然後一行一行的去運作。
CPU實際隻認識二進制代碼,根本不認識源代碼。腳本程式源代碼其實也不是二進制代碼,CPU也不認識,也不能直接執行。【shell程式的編譯連結過程不是以腳本程式源代碼為機關進行的,而是在腳本運作過程中逐行的解釋執行時完成二進制轉化,進而無需編譯連結,而是調用他。】
2.2.動手寫第一個shell
編輯器 vim
編譯器 無
運作方法如下
第一種:./xx.sh,這樣運作shell要求shell程式必須具有可執行權限。
第二種:source xx.sh,source是linux的執行腳本程式指令。不需要腳本具有可執行權限。
第三種:bash xx.sh,bash是一個腳本程式解釋器,本質上是一個可執行程式。這樣執行相當于我們執行了bash程式,然後把xx.sh作為argv[1]傳給他運作。
shell程式的第一行一般都是: #!/bin/sh
指定shell程式執行時被哪個解釋器解釋執行
可以将第一行寫為:#!/bin/bash來指定使用bash執行該腳本。
注意:在ubuntu上面預設使用的預設腳本解釋器sh其實不是bash,而是dash!
腳本中的注釋使用#,例如“#same as ‘//’ ”,和C語言的//是一樣的
2.3.shell程式設計學習1
shell中的變量定義和引用
shell是弱類型語言(語言中的變量如果有明确的類型則屬于強類型語言)
和C語言不同。在shell程式設計中定義變量不需要制定類型,也沒有類型這個概念。
變量定義時可以初始化,使用=進行初始化指派。在shell中指派的=兩邊是不能有空格的
注意:shell對文法非常在意,非常嚴格。很多地方空格都是必須沒有或者必須有,而且不能随意有沒有空格。
變量引用
shell中引用一個變量必須使用 符号, 符号就是變量解引用符号。
注意:$符号後面跟一個字元串,這個字元串就會被當作變量去解析。如果這個字元串本身沒有定義,執行時并不會報錯,而是把這個變量解析為空。
注意:變量引用的時候可以 var,也可以 {var}。這兩種的差別是在某些情況下隻能用 var而不能簡單的 var。 var可用的情況下, {var}是肯定能用的。
什麼情況下$var是不能用的呢?
eg:
var="hello"
echo "$varyou"
則列印不出東西,因為找不到varyou這個變量
正确寫法:
列印出:helloyou
變量定義、初始化
string="hello world"
echo $string
如果echo string(不加$),那麼解釋器就會将string當成一個新的變量,列印一個string出來。
單引号:完全字面替換 ‘23\"33’ 就會列印出23\"33
雙引号:
$加變量名可以取變量的值
\$表示$的字面值 輸出$符号
\`表示`的字面值
\"表示"的字面值
\\表示\的字面值
示例 列印結果
echo new string new string
echo 'new \"string' new \"string
echo "new \"string" new "string
echo "$string" hello world
PWD=`pwd`
echo $PWD
//列印指令pwd的傳回值。傳回目前路徑
反引号括起來執行。有時候我們在shell中調用linux指令是為了得到這個指令的傳回值(結果值),這時候就适合用一對反引号(鍵盤上ESC按鍵下面的那個按鍵,和~在一個按鍵上)來調用執行指令。
shell中的分支結構
if文法很多,隻介紹重要部分。
典型if語言格式。
if [表達式]; then
xxx
else
xxx
fi
判斷檔案是否存在。(-f),注意[]裡面前後都有空格,不能省略。 file
判斷目錄是否存在 (-d) diractory
判斷字元串是否相等(”str1” = “str2”),注意用一個等号而不是兩個
判斷數字是否相等(-eq)、大于(-gt)、小于(-lt)、大于等于(-ge)、小于等于(-le)
while [ $# -gt 0 ] ; do
判斷式中使用“-o”表示邏輯或
if [ -f a.c ]; then
ehco yes
【注意】:[ -f a.c ]
-f前面有空格!!!
a.c後面有空格!!! 不寫空格會報錯
if [ - eq -o "abcd" = "abcd" ]; then
ehco "yes"
else ...
fi
邏輯&&和邏輯||在shell中的獨特用法
話說&& 和 || 在shell中也有,出現在shell中簡寫的if表達式:沒有if,隻有中括号
//str如果為空,就不顯示233;如果不為空則顯示233
2.5.shell中的循環結構
for循環
能看懂、能改即可。不要求能夠完全不參考寫出來
for i in
do
echo $i
done
//循環列印12345。1 2 3 4 5,這裡的數字是我們周遊的集合,沒有邊界,不需要用()、[]和{}
for i in `ls` #目前目錄檔案名的集合
do
echo $i
done
while循環
(1)和C語言的循環在邏輯上無差别。while後面的[]兩邊都有空格。i++的寫法中有兩層括号。
i=
j=
while [ $i -lt $j ]; do
echo $i
i=$(($i + )) #這是C語言裡面的 i++,看起來有點怪異。 +兩邊可以有空格
done
列印資訊傳入一個檔案
echo "start" > a.txt //建立一個a.txt,把start寫進去
i=
j=
while [ $i -lt $j ]; do
echo $i >> a.txt #追加$i的内容,寫入已經存在的a.txt裡面末尾去。
i=$(($i + ))
done
列印1到10開始,大于8和等于4 的數
#!/bin/sh
str="deep"
str="列印出的是:"
i=
touch test.txti
while [ $i -lt -o $i -eq ]; do
if [ $i -eq -o $i -gt ]; then
echo $str$i >> test.txt
fi
i=$(($i + ))
done
case的用法
var=
case var in
) echo "1" ;;
) echo "2" ;;
esac
bash中的傳參
$# 傳參個數,注意隻考慮真正的參數個數。
$1 $2 $3 參數
eg
sh a.sh aa bb cc 執行sh :# = 3。0是執行shell的應用程式名字,如bash
break跳出在shell中不是用于case的,而是跳出循環的。(因為case是不用break的)
C語言的argv是 隻讀的,是不可改的。而在shell中,$1是可以用shift改的。
echo $# $1
shift;
echo $# $1
上例中輸入source a.sh aa bb cc
列印
3 aa
2 bb
可見shift有點像左移運算符。把shell的傳參左移了一個移出去。原來的 2變成了原來的 1。
2.7 Makefile
kernel 的 本質是C語言的項目,由很多個檔案組成,是以都需要Makefile的管理。
分析uboot必須對Makefile有所了解
目标、依賴、指令是Makefile中的三個最主要的成分
目标是我們要去make xxx的那個,是最後生成的東西
依賴是用來生成目标的原材料。
指令就是對原材料的加工方法。
%通配符 和 自動推導
示例:ARM裸機中的makefile
led.bin: start.o
//要生成bin,但目前沒有led.o,看規則
arm-linux-ld -Ttext -o led.elf $^
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 led.bin bin
%.o : %.S
//規則:做自動推導。
//%是通配符,代表一個或幾個字母
//%.o代表所有以 .o 結尾的檔案
//要%.o,就需要依賴%.S。然後就回去找%.S
arm-linux-gcc -o [email protected] $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o [email protected] $< -c -nostdlib
clean:
//僞目标,無依賴,無條件執行
rm *.o *.elf *.bin *.dis mkx210 -f
Makefile知道要得到什麼,需要先得到什麼,他會自己一步一步去推導。還是挺聰明的。
自動推導就是把目标檔案往規則上套,如果找到了就生成。
@:規則的目标檔案名 <: 規則的依賴檔案名
$^: 依賴的檔案集合
makefile 定義和使用變量
類似shell,沒有變量類型,引用時候用 $var
僞目标(.PHONY)
僞目标本身不代表一個檔案,而是單純的執行指令。不生成檔案或得到某個東西。
僞目标沒有依賴。
為了明确聲明這是僞目标,一般會在前面加一個
eg:
.PHONY
clean //聲明僞目标
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
【makefile的引用】
有時候makefile總體比較複雜,是以makefile有時候 會引用其他makefile
include,和C語言一樣,也是原地展開。
例如uboot中有一個:
include $(obj)include/config.mk
【makefile條件語句】
ifeq ($(xxx),var)
xxx =
endif
2.8 Makefile 補充學習
【注釋用#】
【@指令:靜默執行。】
示例:不使用靜默執行
all:
echo "helloworld"
列印資訊:
echo “helloworld”
helloworld
可見:makefile是預設列印指令的。如果不想看到指令本身,隻想看到執行,那麼靜默執行即可。
all:
@echo "helloworld"
變量指派運算符
(1)= 最簡單的指派 //他的值取決于最後一次指派時的值
(2):= 一般也是指派 //就地直接解析
以上這兩個大部分情況下效果是一樣的,但是有時候不一樣。
用=指派的變量,取決于最後一次指派時的值,不能隻往前面看,還要往後面看。
用:=來指派的,則是就地直接解析,隻用往前看即可。
(3)?= 如果變量未定義則執行這條指派,若定義了則本行被忽略。【注意空值也算指派過】
(4)+= 用來給一個已經指派的變量接續指派,意思就是把這次的值加到原來的值的後面,有點類似于strcat。(在shell makefile等檔案中,可以認為所有變量都是字元串,+=就相當于給字元串stcat接續内容)(注意一個細節,+=續接的内容和原來的内容之間會自動加一個空格隔開)
注意:Makefile中并不要求指派運算符兩邊一定要有空格或者無空格,這一點比shell的格式要求要松一些。
重點:關于=和:=
eg:
A=ABC
B=$(A)DEF
A=GH
all:
echo $(B)
結果:GHDEF
說明猜測是正确的。A=ABC要看最後一次A是什麼。在被解析時取決最後一次!!
【關于uboot中的makefile】
一共3000多行,真正有意義的差不多就是470行。用=指派的變量在uboot中真不好分析。
我們能看懂就行了,但是這個=沒:=安全。
對于安全的代碼做到什麼程度呢?其實想保留B指派A的原始值,隻需要B用:=即可
A=ABC
B:=$(A)DEF
A=GH
all:
echo $(B)
這種情況列印出的值就是 ABCDEF
【makefile 的環境變量】
用export導出的就是環境變量。一般情況下要求環境變量名用大寫,普通變量名用小寫。
環境變量 類似于整個工程中所有Makefile之間可以共享的全局變量
普通變量 隻是目前本Makefile中使用的局部變量
注意定義環境變量可能會影響一個工程中的其他makefile,是以要小心。
有一些環境變量可能是makefile本身自己定義的内部的環境變量
這就好像C語言中編譯器預定義的宏__LINE__ __FUNCTION__等一樣。
有一些環境變量可能是目前的執行環境提供的環境變量
譬如我們在make執行時給makefile傳參。
這裡給目前Makefile傳了一個環境變量CC,值是arm-linux-gcc。用make傳參優先級最高。
CC =arm-linux-gcc //普通變量
export CC //導出。變成環境變量
關于make給環境變量傳參
CC =gcc
all:
echo $(CC)
1 make列印結果:gcc
2 make CC=arm-linux-gcc 列印結果:arm-linux-gcc (覆寫前環境變量值)
makefile常見通配符
* 若幹個任意字元
? 1個任意字元
[] 将[]中的字元依次去和外面的結合比對
% 也是通配符,表示任意多個字元,和*很相似,但是%一般隻用于規則描述中,又叫做規則通配符。
all :1.c 2.c 12.c test.h
echo *.c
echo ?.c
echo [12].c #中括号内的字元依次去外面去比對
列印
1.c 2.c 12.c
1.c 2.c
1.c 2.c
【自動變量】
自動變量的含義:預定義的特殊意義的符号。就類似于C語言編譯器中預制的那些宏FILE一樣。
檔案集合中檔案非常多,描述的時候很麻煩。
@、 <、$^ 就是典型自動變量
led.bin: start.o
arm-linux-ld -Ttext -o led.elf $^
#$^代表start.o
%.o : %.c
arm-linux-gcc -o [email protected] $< -c -nostdlib
#[email protected]代表.o檔案
#$<代表.c檔案
常見自動變量:
@規則的目标檔案名 < 規則的依賴檔案名
$^ 依賴的檔案集合
all :.c .c .c test.h
echo [email protected]
echo $<
echo $^
列印結果:
all
1.c
1.c 2.c 12.c test.h
【關于 <需要注意】在目标和依賴中 <script type="math/tex" id="MathJax-Element-10">< 需要注意】 在目标和依賴中</script><代表第一個依賴
在規則中$<代表的是所有的依賴!
3.1 uboot實踐
【關于tar】
tar -jxvf //解壓
tar -cjx //打包
bsp是闆級支援包
選用的uboot是 B盤 linux/qt4.8/bsp
uboot在任意檔案夾下解壓,tar -jxvf qt_x210v3.tar.bz2
注意uboot和linux kernel等複雜項目都不能直接編譯,需要事先配置。
進入uboot的根目錄
執行:make x210_sd_config。 //執行了就配置完了
編譯得到uboot.bin
編譯前要注意:
1 一定要檢查arm-linux-gcc,我們用的是arm-2009q3。
2 還有注意makefile中關于交叉編譯工具鍊的設定:
(第147行) CROSS_COMPILE = 。。。
要保證這一行的交叉編譯工具路徑和我們的工具鍊路徑一緻,否則不能工作
我們放置在/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi
我自己防止/usr/local/arm/arm-2009q3/bin,路徑相同
以上做完之後即可編譯
make
//多線程編譯(4核心編譯)
du -h u-boot.bin
//檢視編譯生成的uboot.bin大小,視訊中是384KB
uboot分uboot官方、SoC級uboot和開發闆uboot
smdkv210是三星官方制定的開發闆,昂貴且龐大。
三星的uboot有68個對象,九鼎有31個,可以說九鼎包括的功能是三星的子集
gitignore 版本管理的
config.mk arm_config.mk 某個makefile中調用的
COPYING 版權聲明,GPL許可,即開源項目
credit 鳴謝
image_split 分割uboot到 BL1的
Makefile ! 很重要,主makefile,我們就是用這個mkfile編譯的
mkconfig ! 很重要,uboot的可移植性就是通過此配置腳本維護的
rules.mk 很重要,但不學習他
./mk 快速編譯:先清理再設定後編譯
mkmovi SD卡啟動相關
檔案夾
api. 硬體無關的api,是uboot本身使用的,移植不用管
common 【重要】硬體無關。普遍适用代碼。環境變量和指令系統。
board 【重要】子檔案夾非常多,每一個檔案夾代表一個支援的開發闆
這麼多檔案夾還能找到,要歸功于配置過程。配置就是定要用于哪個檔案夾
【配置】說白了配置就是确定路徑,這個路徑的确定過程保證了可移植。
【曆史】以前是board下存放晶片型号,後來太多了就改成了廠家目錄。但為了向前相容, 還是把以前放在外面的晶片沒有動。因為挪了位置很可能就不能用了。
smdkc110:是遺留問題。uboot中的borad下沒有s5pv210是因為c110先出的,服務于手 機。c110和s5pv210有%的相似度。
cpu 【重要】與SoC有關,初始化和控制代碼
drivers 【重要】linux中摳出來 的驅動。如網卡、iNand等
(uboot的驅動其實是linux驅動的一部分。)
sd_fusing【重要】刷寫sd卡的。實作了燒錄uboot鏡像到SD卡。其實以前在linux下刷機時就用了。
include 【重要】頭檔案目錄
lib_ 【重要】架構相關的庫檔案。如lib_arm就算是arm相關的庫檔案。移植不用管。
libfdt 裝置樹相關。
linux在以前的版本使用傳參的方式啟動。
之後的版本采用裝置樹的機制進行硬體資訊描述
目前用到的晶片使用的核心版本都低于,故暫時不讨論裝置樹,以後講通過專題。
net 網絡相關的,裡面的tftp等功能實作就用這個。程式很精小,學網絡相關可以看這個。
fs 也是從linux移植過來的,檔案系統,管理Flash的
tools 有用的工具
nand_spl onenand post 略
3.6 SourceInsight,簡稱SI
真正的項目往往有龐大數量的檔案。而且代碼之間的關聯非常複雜。是以讀代碼是問題。
sourceinsight有友善我們跳轉的功能。
【建立工程】
首先要建立工程。New project
工程名字和路徑
注意:工程項目檔案和源代碼的目錄可以不一樣,但是建議放在一起
eg
;老朱自己有一個uboot的項目,習慣在uboot下建立一個“SI_Proj”檔案夾
新工程設定,一般不管
向項目中添加檔案
左邊是被選的檔案,右邊是添加了的檔案。選中整個項目的檔案夾,Add Tree
然後就添加了好多檔案。總視窗右邊就出現了我們添加了的檔案。
遺留問題:SI找不到未知檔案類型的檔案
比如start.s就不在其中,因為SI不認識。
解決方法:選項 - 文檔選項,選擇C源碼,字尾添加*.S
然後就能搜到了。告訴SI,*.S是一個c檔案,随之當做C語言處理
這還是欺騙SI的小技巧,但是很好使。
解決方法:選項 - 裝入設定,裝入。裝入朱有鵬給的一個,自動幫我們加載了.S .cc
再次添加檔案
剛剛漏過的加回來!
項目,加入和删除項目檔案。
【解析工程檔案】
SI把我們的全部源代碼所有符号存入資料庫,查找時不是查檔案而是查資料庫,是以索引速度非常快。
在此之前應該預先進行解析。
菜單欄:項目,同步檔案,選中至少上面個,确定。這兩個選項分别是自動解析、強制解析。
eg:解析uboot工程項目過程大約s
【使用SI】
試着選中一個變量,很快就出來了
試着右鍵函數,可以"跳轉到定義處"。想要回去回來點→箭頭。後來慢慢就會用了。
4.1 uboot主Makefile分析
很多人自己學uboot自己看不懂是因為uboot的檔案太多了,我們不得不去研究地圖。
不這樣的話容易在大量無關代碼中迷失自己。
Makefile中的400行我們也不完全去研究清楚,我們研究makefile完了之後能對uboot的整體規劃有一個大緻了解。
從頭開始
【uboot版本确定:24-29行】
VERSION =
//主版本号
PATCHLEVEL =
//次版本号,更新檔級别
SUBLEVEL =
//再次版本号
EXTRAVERSION = //附加版本資訊,我們可以自己标記自己的名字。
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
最終版本号:
VERSION_FILE = $(obj)include/version_autogenerated.h
//該obj變量在後面定義。“=”的緣故
//另,version_autogenerated.h源目錄中沒有。但是編譯過後的uboot中有,是宏定義
//這個宏是我們配置和編譯之後的版本号
【hostarch(主機架構) 和 hostos(主機系統)】
這兩個是環境變量。有點麻煩買之前沒講過。
HOSTARCH := $(shell uname -m | \
sed -e s/i.86/i386/ \
-e s/sun4u/sparc64/ \...
關于 (shellxxx)其實和pwd=‘pwd′,echo (pwd)。是一樣的
echo $(shell pwd)與之等同。
“ |”在shell中是管道運算符。把 前一個的輸出當做後一個的輸入。
我們 uname -m //得到結果是:i686,代表硬體的體系。即CPU型号傳給sed -e
sed -e是字元串替換工具
sed -e s/i./i386/ \
用後面的替換前面的。eg在這是将“i.86”替換為”i386”
實驗:我們此時把上面一段複制,echo $(HOSTARCH),即得到i386。
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
uname -s 出的是“Linux”
然後轉為小寫:即“linux”
【靜默編譯silent builds,50-54】
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
注意ifeq裡面的逗号。若後面的為空,那麼echo。如果後面不為空則靜默編譯
即,若MakeFLAG裡面有s,那麼靜默編譯。usage:make -s
其中 -s會作為MAKEFLAGS參數。在此代碼作用下XECHO變量會變成空(預設是echo)
【原地編譯 和 單獨輸出檔案夾編譯:56-76,注釋】
原地編譯
預設.o .c檔案都會放在一起,預設都是這樣的,這就是原地編譯。原地編譯處理起來簡單,但壞處是污染了源檔案。(編譯過的uboot7M多,打包給别人會産生誤會);其二,一套源代碼隻能用一次配置。
比如産品有 三種不同的高、中、低性能,我的軟體要去變,我希望能做不同的配置。
如果是原地編譯我必須要make disclean。如果弄三份,後面維護起來又很麻煩、重複勞動。
解決方法就是使用 單獨輸出檔案夾方式編譯。這種方式linux kernel也是支援的。
單獨輸出檔案夾編譯
基本思路是另外生成一個其他的生成目錄。這樣更改了源代碼都能應用到,屆時配置産生差別
具體用法: 預設原地編譯
第一種 make O=/輸出目錄 //推薦方法(但注意)
注意:清除、配置、編譯都要加 O=/.......
即eg:
make O=/tmp/build disclean
make O=/tmp/build x210_sd_config
make O=/tmp/build all
否則會報錯。
但實際上我們用的uboot是有問題的,我們要手工建立目錄。
第二種 export BUILD_DIR=輸出目錄 導出環境變量,make
若兩個都指定了,這種情況第一種優先級更高
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
若指令行中有O,把我們make O=/輸出目錄中 O的值傳給BUILD_DIR.
【單獨輸出檔案夾編譯中的環境變量:95】
OBJTREE //編譯後的.o檔案存放目錄。原地編譯的情況下,這個變量等于原地
SRCTREE //源代碼目錄,即目前目錄,主makefile所在的目錄
TOPDIR //
【MKCONFIG檔案】
這裡定義了一個MKCONFIG變量,暫時用不到,但非常重要
MKCONFIG的值即為根目錄下面的mkconfig配置腳本。
include $(obj)include/config.mk
//這裡的obj變量就是OBJTREE,檔案存放目錄
export ARCH CPU BOARD VENDOR SOC(No行)
//config.mk内容是配置生成的。源碼不含。
//之是以不直接給出這5個值,是因為便于集中配置
//這5個值 就在x210_sd_config指令中,調用的就是MKCONFIG這個腳本,這個 過程中這5個值是作為參數傳進MKCONFIG腳本去的(No.2589行)
//移植uboot在一定程度上就是移植這個!
我們x210在iNand情況下配置生成的config.mk為:
ARCH = arm
CPU = s5pc11x
BOARD = x210
VENDOR = samsung
SOC = s5pc110
【CROSS_COMPILE 136-182】
由ARCH決定。
CROSS_COMPILE是定義交叉編譯字首的
交叉編譯工具不是單獨一個工具,共同的特點是共同的字首。架構不同則字首不同
是以定義時區分工具字首,才能實作可移植性
如果未定義CROSS_COMPILE,則進行多種條件編譯:
注意:除了在makefile中改變該值,也可make CROSS_COMPILE=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi- 手動改。當然,這個值也會修改makefile中的值。
【導入config.mk 185】
前面的工作是:編譯工具導入、完整工具鍊補全(補全後段)。我們無需知道細節。
【config.mk112-142行】
主要内容是編譯屬性類操作,不重點分析
# Load generated board configuration
sinclude $(OBJTREE)/include/autoconf.mk
開發闆配置項目,生成autoconf.mk檔案,用來指導uboot的編譯過程
這個檔案裡面都是以CONFIG_開頭的宏,它們是條件編譯的關鍵,用來指導程式的走向。
原材料:uboot_9ding/include/configs/x210_sd.h (裡面也都是宏定義)
【注意!】 這裡面出現的宏就是我們對開發闆uboot移植的關鍵!宏是對開發闆的配置
【config.mk 142行,指定連結腳本】
ifndef LDSCRIPT
ifeq ($(CONFIG_NAND_U_BOOT),y)
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
如果CONFIG_NAND_U_BOOT = y,那麼連結腳本用u-boot-nand.lds
否則用u-boot.lds。
因為我們的x210開發闆是用的iNand啟動而非nand,是以u-boot.lds是我們的連結腳本。
【config.mk 156行,TEXT_BASE】
ifneq ($(TEXT_BASE),)
CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
endif
【行】
ifneq ($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
endif
若TEXT_BASE不為空,那麼CPPFLAGS中添加TEXT_BASE的值
這個TEXT_BASE的值源碼中沒有,由配置中生成于broad/config.mk中
生成之後他的值是0xc3e0 0000,是整個uboot連結時的位址。
注意:這個位址可能由虛拟位址映射成23E0 0000(主要是忘了是哪兒了)
(我們DNW刷機的時候先下載下傳210_usb.bin,然後下載下傳uboot.bin到0x23e0 0000,這裡的下載下傳位址就是為了和現在連結的位址比對。)
注意:在u-boot.lds腳本中的連結位址依然是零位址。但最後還是會連結到0xc3e0 0000這個位置。這就是我們裸機裡學的
“arm-linux-ld -Text 0x0 -o led.elf”中的0x0一樣。
【235-256行】
自動推導規則。
4.7 uboot的配置過程詳解
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
$(@:_config=) 這個是一個替換符,就等于[email protected]
[email protected]在前面講了是代表原材料,
:..=代表冒号 後面的東西用 = 後面的東西替換,
是以這裡就代表x210_sd
{整句注解:x210_sd_config,把“_config”替換為“”(空)}
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
//mkconfig 參數 參數 參數 參數 參數 參數
//$1 = x210_sd $2 = arm $3 = s5pc11x
//$4 = x210 $5 = samsung $6 = s5pc110
//$1 代表第一個參數 , $#=
【mkconfig腳本:14-21】
while [ $# -gt ] ; //當 參數個數$# 大于
do
case "$1" in
--) shift ; break ;;
//第一個參數(目前我們的值是x210_sd)
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
done
【mkconfig腳本:23】
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
簡寫的if表達式:
${BOARD_NAME}是Ture,後面就不用執行了。
我們之前沒定義${BOARD_NAME},值為False。那麼定義BOARD_NAME=“x210_sd”
【mkconfig腳本:】
[ $# -lt ] && exit
//若參數個數 < 腳本退出,傳回
[ $# -gt ] && exit //若參數個數 > 腳本退出,傳回
//注意傳回是正常的。傳回其實是出現錯誤了
echo "Configuring for ${BOARD_NAME} board..."
//這句來了!使我們配置的時候列印的那句話
"Configuring for x210_sd board..."
【mkconfig腳本:33-118】建立符号連結
這些符号連結的存在就是整個配置過程的核心。主要作用是給頭檔案等提供指向性連結。
根本目的是為了移植性。
因為裡面有很多重複、平行的代碼,他們來自于不同的架構或開發闆。
我們提供一個具體名字的檔案夾,選代碼其中的一部分,供編譯使用。
這個選不同的配置、使用不同的檔案的過程是符号連結的功勞
【46-48】
if [ "$SRCTREE" != "$OBJTREE" ] ; then
//若 make -O
mkdir -p ${OBJTREE}/include
....
else
cd ./include
rm -f asm
ln -s asm-$2 asm //step1 建立一個符号連結asm,指向asm_arm。
//這是一個檔案夾
fi
【53:】
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
//step2 生成了一個arch,後來在行被删了
後來重新又生成了一個。
samsung相當于打了個粗略的更新檔。
${LNPREFIX}值是include/asm-arm/
【83】
# create link for s5pc11x SoC
if [ "$3" = "s5pc11x" ] ; then
rm -f regs.h
//删目前目錄下regs.h(includes/)
ln -s $6.h regs.h
//step3 建立 include/regs.h,指向s5pc110.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
//step4 建立 include/asm-arm/arch,指向arch-s5pc11x
fi
【107】
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc // 建立 include/asm-arm/proc
,指向 include/asm-arm/proc-armv
${LNPREFIX}值是include/asm-arm/
fi
總結:mkconfig腳本一共建立了4個符号連結:asm arch regs.h proc
将來在寫代碼時,頭檔案非常有用。
eg:
如果我們想找這個xx.h,我們不能去asm/下找,應該去include/asm-arm/下找這個檔案。
4.8
【mkconfig 123-129】建立include/config.mk檔案
mkconfig進行配置,為編譯過程提供了 ARCH=arm CPU=s5pv110 這樣的變量,起指導編譯作用。
mkconfig這個檔案不寫在一個Makefile檔案裡是為了便于維護。
注意:在腳本程式中,時刻要注意目前目錄是什麼。進去了沒出來,那就是沒出來。
【mkconfig 134】
make -a 追加 include/config.h檔案,裡面就一句引用了x210_sd.h ,這個x210_sd.h用于生成一個autoconfig.mk,指導編譯過程
4.9.uboot的連結腳本
uboot的連結腳本和我們之前裸機中的連結腳本并沒有本質差別,隻是複雜度高一些,檔案多一些,使用到的技巧多一些。
ENTRY(_start)用來指定整個程式的入口位址。所謂入口位址就是整個程式的開頭位址,可以認為就是整個程式的第一句指令。有點像C語言中的main
指定程式的連結位址有2種方法:一種是在Makefile中ld的flags用-Ttext 0x20000000來指定;第二種是在連結腳本的SECTIONS開頭用.=0x20000000來指定。兩種都可以實作相同效果。其實,這兩種技巧是可以共同配合使用的,也就是說既在連結腳本中指定也在ld flags中用-Ttext來指定。兩個都指定以後以-Ttext指定的為準。
【關于代碼段16KB的檔案優先排列】
代碼段中注意檔案排列的順序。指定必須放在前面部分的那些檔案就是那些必須安排在前16KB内的檔案,這些檔案中的函數在前16KB會被調用。
連結腳本中有很多自定義段
例如 u_boot_cmd段就是自定義段。自定義段很重要。
移植的過程中要非常仔細。對于新手來說心裡沒有底很正常,後面做移植的時候關鍵的地方要一遍一遍的背才能不出錯。老手不會慌,因為幾年前就出過這樣的錯了。