arm BOOT閱讀筆記
BOOT的核心就是relocate,目前見(jiàn)到的典型嵌入式系統(tǒng),除了處理器,至少都有ROM(norflash,nandflash)RAM(SDRAM),一般把Bootloader代碼放在norflash里面,而nandflash因?yàn)楸旧碛布虿荒茈S機(jī)訪問(wèn),一般只是用來(lái)放應(yīng)用程序.在系統(tǒng)加電或復(fù)位后,CPU通常由CPU制造商預(yù)先安排上地址取指令,arm體系下一般都是0x0地址取它的第一條指令,即PC = 0開(kāi)始.
和boot緊密相關(guān)的個(gè)人覺(jué)得就是一下幾點(diǎn).
1.remap.
remap比較簡(jiǎn)單,和MMU的功能可以看做是等價(jià)的,只是一般remap地址估定為0x0 ,網(wǎng)上有個(gè)帖子叫<>專(zhuān)門(mén)講了它對(duì)remap的理解,對(duì)remap的作用是這樣講的: 當(dāng)ARM處理器上電或者Reset之后,處理器從0x0取指。因此,必須保證系統(tǒng)上電時(shí),0x0處有指令可以執(zhí)行。所以,上電的時(shí)候,0x0地址處必定是ROM或者Flash(NOR)。 但是,為了加快啟動(dòng)的速度,也方便可以更改異常向量表,加快中斷響應(yīng)速度,往往把異常向量表映射到更快、更寬(32bit/16bit)的RAM中。但是異常向量表的開(kāi)始地址是由ARM架構(gòu)決定的,必須位于0x0處,因此,必須把RAM映射到0x0。
文中提到了ARM處理器remap的三種情況,如下
1)如果處理器有專(zhuān)門(mén)的寄存器可以完成Remap。那么Remap是通過(guò)Remap寄存器的相應(yīng)bit置1完成的。
如Atmel AT91xx
2)如果處理器沒(méi)有專(zhuān)門(mén)的寄存器,但是memory的bank控制寄存器可以用來(lái)配置bank的起始地址,那么只要把RAM的起始地址編程為0x0,也可以完成remap。如samsung s3c4510 .
3)如果上面兩種機(jī)制都沒(méi)有,那么Remap就不要做了。因?yàn)樘幚砥鲗?shí)現(xiàn)決定了SDRAM對(duì)應(yīng)的bank地址是不能改變的。如Samsung S3c2410.
不過(guò)我的看法有點(diǎn)稍微不一樣,如果上面兩種機(jī)制都沒(méi)有,那么Remap就不要做了,它給的典型例子是Samsung S3c2410 ,2410雖然sdram對(duì)應(yīng)的bank地址不能改變,但它有MMU功能, MMU可以起到remap的作用,常用的最典型的應(yīng)該是例子Samsung S3c44b0,它既沒(méi)有mmu,又SDRAM對(duì)應(yīng)地址有沒(méi)辦法改變.順便補(bǔ)充下除了4510可以改變每個(gè)bank的地址,還有華邦的w90P740(arm7),呵呵,我現(xiàn)在用的U就是這款U,可以把bank的地址隨意的設(shè)置.
2.relocate .
relocate (地址重定位),個(gè)人覺(jué)得這個(gè)是boot里面最麻煩也是最核心的部分,剛開(kāi)始看boot代碼的時(shí)候,它簡(jiǎn)直是我的惡夢(mèng),不知道大家分析boot的源碼流程是否這樣,也可能我大學(xué)不是計(jì)算機(jī)的,沒(méi)學(xué)過(guò)編譯原理(現(xiàn)在也沒(méi)看過(guò))對(duì)鏈接和加載一無(wú)所知,有兩個(gè)星期非常痛苦,就是不懂人家boot里面的鏈接腳本為什么要那樣寫(xiě).網(wǎng)上關(guān)于uboot的帖子很多,但對(duì)鏈接加載這塊,始終寫(xiě)的不詳細(xì),不知道是不是太過(guò)于基礎(chǔ)了,高手都不愿意講,最后自己找資料,發(fā)現(xiàn)其實(shí)一切痛苦的根源都是對(duì)鏈接和加載不太清楚造成的,但個(gè)人感覺(jué)boot除了初始化以外就是搬運(yùn)程序,如何搬運(yùn)?為什么要那樣搬運(yùn)都需要對(duì)硬件板的地址分布很清楚?而這些都是鏈接決定的,所以非弄清楚不可!
1.我們?yōu)槭裁葱枰猺elocate ? 經(jīng)濟(jì)方面,(nandflash和norflash 每兆價(jià)格相差懸殊),把boot代碼放在norflash里面(為什么不放在nandflash里面,因?yàn)閚andflash讀需要驅(qū)動(dòng)支持,norflash可以直接訪問(wèn)),boot通常很小,只需要占用幾十k的空間,所以只需要很小的norflash芯片,這樣很便宜,而把應(yīng)用程序通常很大,所以用價(jià)格低廉nandflash來(lái)儲(chǔ)存,實(shí)際應(yīng)用,通過(guò)執(zhí)行boot程序,把nandflash里面代碼和數(shù)據(jù)搬運(yùn)到內(nèi)存中來(lái)執(zhí)行,這樣比程序直接放在norflash里執(zhí)行,可以.另外還有運(yùn)行速度方面的差別,程序在norflash里執(zhí)行的速度遠(yuǎn)遠(yuǎn)小于在sdram中執(zhí)行的速度,為了追求更高的速度,也需要relocate,讓程序在sdram里面執(zhí)行 .
2.關(guān)于加載域(VMA)和運(yùn)行域(LMA),杜春雷在它那本經(jīng)典的<>一書(shū)專(zhuān)門(mén)有一章來(lái)講加載域和運(yùn)行域不一致的情況,但我當(dāng)初接觸了它的這些加載域和運(yùn)行域后,看uboot的lds ,uboot的lds沒(méi)有設(shè)置LMA,只是設(shè)置了VMA,為此我疑惑很久.直到耐心的看了那本鏈接器和加載器的書(shū)才豁然明白( http://bbs.chinaunix.net/viewthread.php?tid=817770 ),任何一個(gè)鏈接器和加載器的基本工作都非常簡(jiǎn)單: 將更抽象的名字與更底層的名字綁定起來(lái),好讓程序員使用更抽象的名字編寫(xiě)代碼,鏈接器的就是把源文件進(jìn)行符號(hào)解析,把解析出來(lái)的符號(hào)和地址的進(jìn)行綁定,把全局變量,函數(shù),標(biāo)號(hào)等等這些符合和地址綁定起來(lái).
3.boot上電后開(kāi)始能夠正確執(zhí)行還有個(gè)很重要的原因,是要保證boot在系統(tǒng)加電或復(fù)位后最初執(zhí)行的代碼是跟地址無(wú)關(guān)的,(即在代碼搬運(yùn)前所執(zhí)行的代碼是與地址無(wú)關(guān)),地址無(wú)關(guān)即地址無(wú)關(guān)代碼生成的這個(gè)映象文件可以被放在內(nèi)存中的任何一個(gè)地址上運(yùn)行。對(duì)于地址無(wú)關(guān)的代碼, 尋址是基于pc值的, 在pc值上+/-一個(gè)偏移值, 得到運(yùn)行地址,如跳轉(zhuǎn)指令B.當(dāng)我們執(zhí)行完代碼搬運(yùn),就需要跳到和地址相關(guān)的地方去執(zhí)行,即我們的RAM中,一般是跳轉(zhuǎn)到一個(gè)標(biāo)號(hào), 這時(shí)地址相關(guān)代碼就開(kāi)始運(yùn)行了ldr pc,_start_armboot.因?yàn)樵赽in映象生成的時(shí)候,就已經(jīng)把_start_armboot這個(gè)符號(hào),和實(shí)際地址綁定在一起,當(dāng)我們執(zhí)行l(wèi)dr pc,_start_armboot 程序就從在ROM中執(zhí)行跳入到RAM中了,但前提是我們進(jìn)行了代碼搬移,如果沒(méi)有代碼搬運(yùn)ldr pc,_start_armboot,RAM中沒(méi)有代碼程序就馬上飛掉了,所有我們?cè)谠诎徇\(yùn)之前不能尋址絕對(duì)地址有關(guān)代碼,必須執(zhí)行代碼地址無(wú)關(guān).
拿u-boot-1.1.4下的smdk2410來(lái)做例子,和smdk2410 board密切相關(guān)的就兩個(gè)文件夾boardsmdk2410和cpuarm920t,里面核心文件就u-boot.lds , config.mk ,start.S .
ENTRY(_start)
SECTIONS
{
. = 0x00000000;//從0地址起始
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;//為搬運(yùn)代碼提供的符號(hào),來(lái)標(biāo)明bss段地址,方便relocate
.bss : { *(.bss) }
_end = .; //定義整個(gè)image的結(jié)束地址
}
u-boot.lds 是鏈接腳本文件, 我剛開(kāi)始看這個(gè)鏈接腳本文件時(shí),我疑惑很久,不明白lds中VMA= LMA(資料上很多鏈接腳本包括我們公司項(xiàng)目里面自己寫(xiě)的lds腳本是通過(guò)AT命令設(shè)置過(guò)LMA,這樣看起來(lái)地址空間分配更清晰),而且整個(gè)image 的VMA按照l(shuí)ds為基址為0x0,而2410芯片不能remap,0x0地址是ROM的區(qū)域,不是運(yùn)行時(shí)RAM的地址,我的理解是代碼段地址應(yīng)該是指向該硬件板內(nèi)存區(qū)域,設(shè)置 .text=TEXT_BASE 而不是lds中的.text=0x0 ,這個(gè)疑點(diǎn)弄的我當(dāng)時(shí)很郁悶,想了很久也沒(méi)想沒(méi)有搞清楚u-boot這樣鏈接腳本都能讓boot跑起來(lái),當(dāng)我把編譯出來(lái)的bin燒到norflash中,uboot居然跑起來(lái)了,同時(shí)發(fā)現(xiàn)了一個(gè)問(wèn)題, u-boot.map 中發(fā)現(xiàn) .text 是從config.mk 定義TEXT_BASE =0x33f80000 ,而不是lds設(shè)置的0x0,這又讓我吃驚,沒(méi)清楚是怎么會(huì)事,手上有介紹移植uboot的資料,但都對(duì)uboot鏈接這部分,寫(xiě)的不夠詳細(xì),知道事config.mk文件搞的鬼,但把makefile文件看了幾遍都沒(méi)找不到是怎么回事(還是對(duì)makefile不熟啊!),最后把編譯uboot的過(guò)程看了隱藏了個(gè)機(jī)關(guān)是
arm-linux-ld –Tu-boot-1.1.4boardsmdk2410u-boot.lds –Ttext 0x33f80000
arm-linux-objcopy --gap-fill =0xff –O binary uboot ubtoot.bin
不知道uboot設(shè)計(jì)者為什么要在這里加一個(gè)–Ttext 而不是在lds就設(shè)置?而很多移植uboot的資料對(duì)lds文件都有所描述,但這個(gè)重要的細(xì)節(jié)似乎都漏掉了,不知道是不是因?yàn)樘A(chǔ)了,所以沒(méi)有講.
不過(guò)最后生成的bin 從上看arm-linux-objcopy --gap-fill =0xff –O binary uboot ubtoot.bin沒(méi)有對(duì)鏈接生成的elf文件進(jìn)行重定位,因此它的運(yùn)行地址是config.mk 定義TEXT_BASE為基地址,順序按照l(shuí)ds的順序依次增加的,所以整個(gè)uboot最初運(yùn)行的流程是
_start reset cpu_init_crit relocate
這個(gè)部分就是完成初始化,設(shè)SVC32,關(guān)看門(mén)狗,關(guān)中斷,設(shè)置時(shí)鐘,初始化SDRAM(為代碼搬運(yùn)到SDRAM做準(zhǔn)備),這些都很簡(jiǎn)單
relocate:
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2
add r2, r0, r2
copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
看了下網(wǎng)上的帖子,adr指令,網(wǎng)上很多人被這這個(gè)指令弄郁悶,我看杜春雷的<>P143講,這個(gè)指令是基于PC或者寄存器的,讀到是地址無(wú)關(guān)的,一般被編譯器替換為SUB r0, pc,#offset ,不要理解為讀取符合表中_start符號(hào)的地址(0x33f80000).在我們上電開(kāi)始執(zhí)行時(shí),pc從0開(kāi)始,所以現(xiàn)在r0值為0 +offset,不等于_TEXT_BASE(0x33f80000).接下來(lái)要用到鏈接時(shí)確定的符號(hào)地址了,_armboot_start(0x33f80000)., _bss_start(0x33f97954)這些可以在u-boot.map里面的看到, size of armboot =0x33f97954-0x33f80000 ,把_start:0x0 (norflsh)把.text ,.data的代碼往SDRAM里_TEXT_BASE確定的地址: 0x33f80000搬運(yùn).s3c2410的SDRAM基地址是0x3000_0000,由于uboot支持的這個(gè)board SDRAM是64M,(0x3000_0000---0x3400_0000),所以把u-boot.bin搬運(yùn)到內(nèi)存的高端地址.然后跳到內(nèi)存中執(zhí)行,提高速度.
之后就relocate stack_setup clear_bss ldr pc, _start_armboot ( ROMRAM)
_start_armboot: .word start_armboot ( u-boot-1.1.4lib_armboard.c)
stack_setup , clear_bss設(shè)置堆棧清bss段,都是為進(jìn)入C語(yǔ)言做初始化準(zhǔn)備,通過(guò)對(duì)start_armboot鏈接后以及把這個(gè)函數(shù)地址已經(jīng)綁定在RAM中,當(dāng)執(zhí)行完ldr pc, label 指令,程序?qū)臉?biāo)號(hào)綁定地址開(kāi)始執(zhí)行,從而實(shí)現(xiàn)了從地址無(wú)關(guān)程序到地址相關(guān)的轉(zhuǎn)變,我們做代碼搬移也是為了跳轉(zhuǎn)做準(zhǔn)備,如果沒(méi)有搬移,直接訪問(wèn)地址相關(guān),由于RAM中都是隨機(jī)值,一跳轉(zhuǎn)就馬上飛了.當(dāng)進(jìn)入start_armboot C函數(shù),剩下的都沒(méi)什么難度了.可以慢分析源碼搞定.2410沒(méi)有remap寄存器, relocate時(shí)候要容易些,有remap寄存器的芯片在relocate時(shí)候進(jìn)行remap會(huì)讓情況更復(fù)雜些.不過(guò)原理都差不多.
在進(jìn)入board.c后,uboot還做了一次代碼搬運(yùn)如下,大概如下圖,不過(guò)分兩種,一種是把pc機(jī)傳的image通過(guò)串口或者網(wǎng)絡(luò)傳到內(nèi)存開(kāi)始執(zhí)行,或者從nandflash里把應(yīng)用搬到內(nèi)存開(kāi)始執(zhí)行,不過(guò)原理都差不多.
正好公司內(nèi)部給我們做了板級(jí)初始化培訓(xùn),把硬件板初始流程注意要點(diǎn)整理出來(lái),.和boot這部分初始化對(duì)比,可以發(fā)現(xiàn)硬件板初始化流程都差不多.比較頭痛還是鏈接這部分,這方面的資料感覺(jué)太少了,沒(méi)人可以指點(diǎn),自己看這部分資料看的很痛苦.
【CPU核相關(guān)初始化】 【W(wǎng)atchdog初始化】 【GPIO初始化】 【系統(tǒng)時(shí)鐘初始化】 【內(nèi)存初始化】 【模式初始化】 【中斷向量初始化】 【MMU初始化】 【Cache初始化】 【總線初始化】 【語(yǔ)言相關(guān)初始化】 【設(shè)備相關(guān)初始化】
4.elf 格式和bin格式
executable and linking format (ELF)重定位,可以參與程序的鏈接(創(chuàng)建一個(gè)程序)和程序的執(zhí)行(運(yùn)行一個(gè)程序) ,主要鏈接,和執(zhí)行,但介紹elf文件的資料很多,沒(méi)時(shí)間仔細(xì)看和實(shí)際密切的就是調(diào)試程序時(shí)候都用elf格式調(diào)試,因?yàn)樗苏{(diào)試所需的各種符號(hào), 固化的時(shí)候都是用的bin格式,是可執(zhí)行映象,用objcopy 把elf 轉(zhuǎn)換成bin ,不過(guò)網(wǎng)上介紹bin格式的資料很少,只是知道bin程序,只要把pc設(shè)置為bin映象的入口地址,就可以正確執(zhí)行, objcopy 可以對(duì)elf 轉(zhuǎn)換成bin再進(jìn)行地址重定位,不過(guò)目前還沒(méi)看見(jiàn)過(guò)這么干過(guò),對(duì)于elf,和bin這些理解的都不系統(tǒng),資料也很少,工作中,集成開(kāi)發(fā)工具IDE又把這些設(shè)置都給屏蔽起來(lái),有沒(méi)有那個(gè)強(qiáng)人能寫(xiě)一個(gè)文檔,把這些都系統(tǒng)的講清楚就好了!
順便問(wèn)下,論壇上上海的多不多,大家找工作都是在網(wǎng)上找的?有個(gè)MM拉我去上海,雖然對(duì)現(xiàn)在工作很滿意,不過(guò)MM比工作更重要,要我做選擇,只有去上海了,不過(guò)在51job上投了點(diǎn)簡(jiǎn)歷,都石沉大海,按理說(shuō)2年也不短了,至少也會(huì)冒一個(gè)泡的,有沒(méi)有上海的能夠指點(diǎn)下,你們?cè)谏虾J趺凑蚁嚓P(guān)工作的?
補(bǔ)充一個(gè)當(dāng)時(shí)找資料看見(jiàn)對(duì)網(wǎng)上一個(gè)帖子,感覺(jué)寫(xiě)的很精辟的,關(guān)于地址無(wú)關(guān)的解釋,網(wǎng)頁(yè)地址被改成相當(dāng)路徑了,就沒(méi)辦法地址粘貼出來(lái),現(xiàn)在把原文粘貼出來(lái).
關(guān)鍵詞: 地址無(wú)關(guān)
術(shù)語(yǔ)
地址無(wú)關(guān): 編譯地址不等于運(yùn)行地址.
地址相關(guān): 編譯地址等于運(yùn)行地址.
常見(jiàn)的一些Boot(如, U-Boot, VIVI)和Linux Kernel代碼開(kāi)始的一段是位置無(wú)關(guān)的, 意思就是說(shuō)運(yùn)行地址與編譯地址無(wú)關(guān). 如, Kernel編譯地址是0xc0008000, 而運(yùn)行地址是0x30008000.
為什么?
為什么代碼的編譯地址和運(yùn)行地址會(huì)不相等呢? 原因主要有以下幾種: 1) 對(duì)于Boot, 用于存放Boot代碼的存儲(chǔ)器容量小于代碼量. 如, Boot片有4K, 而代碼通常有50-60K. 這樣, 通常會(huì)在前4K代碼里, 讓Boot把自己復(fù)制到RAM, 再接著運(yùn)行.這里我們需要作出一個(gè)選擇, 是讓前面的代碼與地址相關(guān), 還是讓后面的代碼與地址相關(guān)呢? 顯然我們會(huì)選擇前面一段代碼量小的與地址無(wú)關(guān). 2) 對(duì)于Linux Kernel, 它是運(yùn)行在虛擬地址空間的, 如0xc0008000, 但在MMU打開(kāi)之前, 通常這個(gè)地址是
不存在的, 也就是說(shuō)在MMU打開(kāi)之前, Kernel的代碼必須是地址無(wú)關(guān)的.
怎么辦?
對(duì)于位置無(wú)關(guān)的代碼, 尋址是基于pc值的, 在pc值上+/-一個(gè)偏移值, 得到運(yùn)行地址.以ARM為例, 用adr來(lái)尋址, adr的實(shí)際上是一個(gè)宏指令, 在代碼編譯時(shí), 會(huì)被編譯器替換成對(duì)pc的+/-運(yùn)算
這里要注意, 對(duì)pc的+/-運(yùn)行顯然是有一個(gè)地址范圍的, 所以我們?cè)谏厦孢x擇代碼量小的地址無(wú)關(guān), 是很明智的.
而訪問(wèn)地址相關(guān)的代碼, 只需要使用其它的尋址指令就行了. 但在這之前, 必須保證代碼被放在正確的地址上, 所以通常都會(huì)有一個(gè)復(fù)制代碼的過(guò)程, 然后就是跳轉(zhuǎn)到一個(gè)標(biāo)號(hào), 地址相關(guān)代碼就開(kāi)始運(yùn)行了.
評(píng)論