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