BOOT閱讀筆記

做了近兩年ARM下的驅動開發,常用的各個設備驅動基本都碰過,不過Boot由於任務安排的緣故(公司一直有專人在做),一直沒有機會接觸,從剛開始接觸嵌入式的時候,就一直想弄清楚板子上電後,程序是怎麼執行的,不過看了下公司boot源碼,就很快放棄了,當時對彙編充滿了畏懼,做了1年多的驅動後,再看彙編感覺就沒那邊痛苦了,最近把boot的資料整理下,把我覺得boot比較核心的部分,完整的看了一遍,現在做個記號.我把我覺得我之前比較困惑的難點整理出來,也許大家一起討論下,也許和我一樣的新手就可以少走些彎路.
BOOT的核心就是relocate,目前見到的典型嵌入式系統,除了處理器,至少都有ROM(norflash,nandflash)RAM (SDRAM),一般把Bootloader代碼放在norflash裏面,而nandflash因爲本身硬件原因不能隨機訪問,一般只是用來放應用程序.在系統加電或復位後,CPU通常由CPU製造商預先安排上地址取指令,arm體系下一般都是0x0地址取它的第一條指令,即PC = 0開始.
和boot緊密相關的個人覺得就是一下幾點.
1.remap.
remap比較簡單,和MMU的功能可以看做是等價的,只是一般remap地址估定爲0x0 ,網上有個帖子叫<&lt;ARM remap與重定位摘抄>&gt;專門講了它對remap的理解,對remap的作用是這樣講的: 當ARM處理器上電或者Reset之後,處理器從0x0取指。因此,必須保證系統上電時,0x0處有指令可以執行。所以,上電的時候,0x0地址處必定是 ROM或者Flash(NOR)。但是,爲了加快啓動的速度,也方便可以更改異常向量表,加快中斷響應速度,往往把異常向量表映射到更快、更寬(32bit/16bit)的RAM中。但是異常向量表的開始地址是由ARM架構決定的,必須位於0x0處,因此,必須把RAM映射到0x0。
文中提到了ARM處理器remap的三種情況,如下
1)如果處理器有專門的寄存器可以完成Remap。那麼Remap是通過Remap寄存器的相應bit置1完成的。
如Atmel AT91xx
2)如果處理器沒有專門的寄存器,但是memory的bank控制寄存器可以用來配置bank的起始地址,那麼只要把RAM的起始地址編程爲0x0,也可以完成remap。如samsung s3c4510 .
3)如果上面兩種機制都沒有,那麼Remap就不要做了。因爲處理器實現決定了SDRAM對應的bank地址是不能改變的。如Samsung S3c2410.
不過我的看法有點稍微不一樣,如果上面兩種機制都沒有,那麼Remap就不要做了,它給的典型例子是Samsung S3c2410 ,2410雖然sdram對應的bank地址不能改變,但它有MMU功能, MMU可以起到remap的作用,常用的最典型的應該是例子Samsung S3c44b0,它既沒有mmu,又SDRAM對應地址有沒辦法改變.順便補充下除了4510可以改變每個bank的地址,還有華邦的w90P740 (arm7),呵呵,我現在用的U就是這款U,可以把bank的地址隨意的設置.
2.relocate .
relocate (地址重定位),個人覺得這個是boot裏面最麻煩也是最核心的部分,剛開始看boot代碼的時候,它簡直是我的惡夢,不知道大家分析boot的源碼流程是否這樣,也可能我大學不是計算機的,沒學過編譯原理(現在也沒看過)對鏈接和加載一無所知,有兩個星期非常痛苦,就是不懂人家boot裏面的鏈接腳本爲什麼要那樣寫.網上關於uboot的帖子很多,但對鏈接加載這塊,始終寫的不詳細,不知道是不是太過於基礎了,高手都不願意講,最後自己找資料,發現其實一切痛苦的根源都是對鏈接和加載不太清楚造成的,但個人感覺boot除了初始化以外就是搬運程序,如何搬運?爲什麼要那樣搬運都需要對硬件板的地址分佈很清楚?而這些都是鏈接決定的,所以非弄清楚不可!
1.我們爲什麼需要relocate ? 經濟方面,(nandflash和norflash 每兆價格相差懸殊),把boot代碼放在norflash裏面(爲什麼不放在nandflash裏面,因爲nandflash讀需要驅動支持, norflash可以直接訪問),boot通常很小,只需要佔用幾十k的空間,所以只需要很小的norflash芯片,這樣很便宜,而把應用程序通常很大,所以用價格低廉nandflash來儲存,實際應用,通過執行boot程序,把nandflash裏面代碼和數據搬運到內存中來執行,這樣比程序直接放在norflash裏執行,可以.另外還有運行速度方面的差別,程序在norflash裏執行的速度遠遠小於在sdram中執行的速度,爲了追求更高的速度,也需要relocate,讓程序在sdram裏面執行 .
2.關於加載域(VMA)和運行域(LMA),杜春雷在它那本經典的<&lt;arm體系結構與編程>&gt;一書專門有一章來講加載域和運行域不一致的情況,但我當初接觸了它的這些加載域和運行域後,看uboot的lds ,uboot的lds沒有設置LMA,只是設置了VMA,爲此我疑惑很久.直到耐心的看了那本鏈接器和加載器的書才豁然明白(http://bbs.chinaunix.net/viewthread.php?tid=817770 ),任何一個鏈接器和加載器的基本工作都非常簡單: 將更抽象的名字與更底層的名字綁定起來,好讓程序員使用更抽象的名字編寫代碼,鏈接器的就是把源文件進行符號解析,把解析出來的符號和地址的進行綁定,把全局變量,函數,標號等等這些符合和地址綁定起來.
3.boot上電後開始能夠正確執行還有個很重要的原因,是要保證boot在系統加電或復位後最初執行的代碼是跟地址無關的,(即在代碼搬運前所執行的代碼是與地址無關),地址無關即地址無關代碼生成的這個映象文件可以被放在內存中的任何一個地址上運行。對於地址無關的代碼, 尋址是基於pc值的, 在pc值上+/-一個偏移值, 得到運行地址,如跳轉指令B.當我們執行完代碼搬運,就需要跳到和地址相關的地方去執行,即我們的RAM中,一般是跳轉到一個標號, 這時地址相關代碼就開始運行了ldr pc,_start_armboot.因爲在bin映象生成的時候,就已經把_start_armboot這個符號,和實際地址綁定在一起,當我們執行 ldr pc,_start_armboot 程序就從在ROM中執行跳入到RAM中了,但前提是我們進行了代碼搬移,如果沒有代碼搬運ldr pc,_start_armboot,RAM中沒有代碼程序就馬上飛掉了,所有我們在在搬運之前不能尋址絕對地址有關代碼,必須執行代碼地址無關.

拿u-boot-1.1.4下的smdk2410來做例子,和smdk2410 board密切相關的就兩個文件夾\board\smdk2410和\cpu\arm920t,裏面核心文件就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的結束地址
}
u-boot.lds 是鏈接腳本文件, 我剛開始看這個鏈接腳本文件時,我疑惑很久,不明白lds中VMA= LMA(資料上很多鏈接腳本包括我們公司項目裏面自己寫的lds腳本是通過AT命令設置過LMA,這樣看起來地址空間分配更清晰),而且整個image 的VMA按照lds爲基址爲0x0,而2410芯片不能remap,0x0地址是ROM的區域,不是運行時RAM的地址,我的理解是代碼段地址應該是指向該硬件板內存區域,設置 .text=TEXT_BASE 而不是lds中的.text=0x0 ,這個疑點弄的我當時很鬱悶,想了很久也沒想沒有搞清楚u-boot這樣鏈接腳本都能讓boot跑起來,當我把編譯出來的bin燒到norflash中, uboot居然跑起來了,同時發現了一個問題, u-boot.map 中發現 .text 是從config.mk 定義TEXT_BASE =0x33f80000 ,而不是lds設置的0x0,這又讓我吃驚,沒清楚是怎麼會事,手上有介紹移植uboot的資料,但都對uboot鏈接這部分,寫的不夠詳細,知道事 config.mk文件搞的鬼,但把makefile文件看了幾遍都沒找不到是怎麼回事(還是對makefile不熟啊!),最後把編譯uboot的過程看了隱藏了個機關是
arm-linux-ld –Tu-boot-1.1.4\board\smdk2410\u-boot.lds –Ttext 0x33f80000
arm-linux-objcopy --gap-fill =0xff –O binary uboot ubtoot.bin
不知道uboot設計者爲什麼要在這裏加一個–Ttext 而不是在lds就設置?而很多移植uboot的資料對lds文件都有所描述,但這個重要的細節似乎都漏掉了,不知道是不是因爲太基礎了,所以沒有講.
不過最後生成的bin 從上看arm-linux-objcopy --gap-fill =0xff –O binary ubootubtoot.bin沒有對鏈接生成的elf文件進行重定位,因此它的運行地址是config.mk 定義TEXT_BASE爲基地址,順序按照lds的順序依次增加的,所以整個uboot最初運行的流程
_start &#61664; reset &#61664; cpu_init_crit &#61664; relocate
這個部分就是完成初始化,設SVC32,關看門狗,關中斷,設置時鐘,初始化SDRAM(爲代碼搬運到SDRAM做準備),這些都很簡單
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 &lt;- size of armboot */
add r2, r0, r2 /* r2 &lt;- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
看了下網上的帖子,adr指令,網上很多人被這這個指令弄鬱悶,我看杜春雷的&lt;&lt;arm體系結構與編程>&gt;P143講, 這個指令是基於PC或者寄存器的,讀到是地址無關的,一般被編譯器替換爲SUB r0, pc,#offset ,不要理解爲讀取符合表中_start符號的地址(0x33f80000).在我們上電開始執行時,pc從0開始,所以現在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搬運到內存的高端地址.然後跳到內存中執行, 提高速度.
之後就relocate &#61664; stack_setup &#61664; clear_bss &#61664; ldr pc, _start_armboot ( ROM&#61664;RAM)
_start_armboot: .word start_armboot ( u-boot-1.1.4\lib_arm\board.c)
stack_setup , clear_bss設置堆棧清bss段,都是爲進入C語言做初始化準備,通過對start_armboot鏈接後以及把這個函數地址已經綁定在RAM中, 當執行完ldr pc, label 指令,程序將從標號綁定地址開始執行,從而實現了從地址無關程序到地址相關的轉變,我們做代碼搬移也是爲了跳轉做準備,如果沒有搬移,直接訪問地址相關, 由於RAM中都是隨機值,一跳轉就馬上飛了.當進入start_armboot C函數,剩下的都沒什麼難度了.可以慢分析源碼搞定.2410沒有remap寄存器, relocate時候要容易些,有remap寄存器的芯片在relocate時候進行remap會讓情況更復雜些.不過原理都差不多.
在進入board.c後,uboot還做了一次代碼搬運如下,大概如下圖,不過分兩種,一種是把pc機傳的image通過串口或者網絡傳到內存開始執行,或者從nandflash裏把應用搬到內存開始執行,不過原理都差不多.
正好公司內部給我們做了板級初始化培訓,把硬件板初始流程注意要點整理出來,.和boot這部分初始化對比,可以發現硬件板初始化流程都差不多.比較頭痛還是鏈接這部分,這方面的資料感覺太少了,沒人可以指點,自己看這部分資料看的很痛苦.
【CPU核相關初始化】&#61664; 【Watchdog初始化】&#61664; 【GPIO初始化】&#61664; 【系統時鐘初始化】&#61664; 【內存初始化】&#61664; 【模式初始化】&#61664; 【中斷向量初始化】&#61664; 【MMU初始化】&#61664; 【Cache初始化】&#61664; 【總線初始化】&#61664; 【語言相關初始化】&#61664; 【設備相關初始化】
4.elf 格式和bin格式
executable and linking format (ELF)重定位,可以參與程序的鏈接(創建一個程序)和程序的執行(運行一個程序) ,主要鏈接,和執行,但介紹elf文件的資料很多,沒時間仔細看和實際密切的就是調試程序時候都用elf格式調試,因爲它包含了調試所需的各種符號, 固化的時候都是用的bin格式,是可執行映象,用objcopy 把elf 轉換成bin ,不過網上介紹bin格式的資料很少,只是知道bin程序,只要把pc設置爲bin映象的入口地址,就可以正確執行, objcopy 可以對elf 轉換成bin再進行地址重定位,不過目前還沒看見過這麼幹過,對於elf,和bin這些理解的都不繫統,資料也很少,工作中,集成開發工具IDE又把這些設置都給屏蔽起來,有沒有那個強人能寫一個文檔,把這些都系統的講清楚就好了!
順便問下,論壇上上海的多不多,大家找工作都是在網上找的?有個MM拉我去上海,雖然對現在工作很滿意,不過MM比工作更重要,要我做選擇,只有去上海了,不過在51job上投了點簡歷,都石沉大海,按理說2年也不短了,至少也會冒一個泡的,有沒有上海的能夠指點下,你們在上海石怎麼找相關工作的?
補充一個當時找資料看見對網上一個帖子,感覺寫的很精闢的,關於地址無關的解釋,網頁地址被改成相當路徑了,就沒辦法地址粘貼出來,現在把原文粘貼出來.
關鍵詞: 地址無關
術語
地址無關: 編譯地址不等於運行地址.
地址相關: 編譯地址等於運行地址.
常見的一些Boot(如, U-Boot, VIVI)和Linux Kernel代碼開始的一段是位置無關的, 意思就是說運行地址與編譯地址無關. 如, Kernel編譯地址是0xc0008000, 而運行地址是0x30008000.
爲什麼?
爲什麼代碼的編譯地址和運行地址會不相等呢? 原因主要有以下幾種: 1) 對於Boot, 用於存放Boot代碼的存儲器容量小於代碼量. 如, Boot片有4K, 而代碼通常有50-60K. 這樣, 通常會在前4K代碼裏, 讓Boot把自己複製到RAM, 再接着運行.這裏我們需要作出一個選擇, 是讓前面的代碼與地址相關, 還是讓後面的代碼與地址相關呢? 顯然我們會選擇前面一段代碼量小的與地址無關. 2) 對於Linux Kernel, 它是運行在虛擬地址空間的, 如0xc0008000, 但在MMU打開之前, 通常這個地址是
不存在的, 也就是說在MMU打開之前, Kernel的代碼必須是地址無關的.
怎麼辦?
對於位置無關的代碼, 尋址是基於pc值的, 在pc值上+/-一個偏移值, 得到運行地址.以ARM爲例, 用adr來尋址, adr的實際上是一個宏指令, 在代碼編譯時, 會被編譯器替換成對pc的+/-運算
這裏要注意, 對pc的+/-運行顯然是有一個地址範圍的, 所以我們在上面選擇代碼量小的地址無關, 是很明智的.
而訪問地址相關的代碼, 只需要使用其它的尋址指令就行了. 但在這之前, 必須保證代碼被放在正確的地址上, 所以通常都會有一個複製代碼的過程, 然後就是跳轉到一個標號, 地址相關代碼就開始運行了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章