ARM 的 啟動代碼 是非常重要的代碼,直接關系到系統的穩定性和可靠性(這裡主要讨論 arm 7, arm9;cortex系列的會在後續的文章中讨論)。上次我們通過兩則文章讨論了ARM啟動代碼的過程,
ARM的啟動代碼(1):介紹
ARM的啟動代碼(2):AT91SAM9260啟動詳解
這次我們聊聊ARM的代碼的具體編寫。那麼什麼樣的代碼會涉及到這些問題呢?
1.Bootloader或者位于啟動序列上進行加載其他應用程式的程式;
2.單獨的二進制鏡像,直接可以在ARM處理器上直接執行。
這兩種代碼都需要對 ARM 的啟動過程有深入了解。說深入了解,其實隻有一條,鬧鬧記住,ARM7,ARM9的異常向量表從位址0開始。這是鐵打不能改變的事實。這樣一來,所有的程式都要用0位址存儲自己的向量表,這豈不是成了稀缺資源。是以不同家的ARM晶片都提供了一些辦法解決這種問題。
對于arm7,很多晶片使用片内的flash。如at91sam7x256。為了友善,經常需要bootloader + 應用程式的方式。At91sam7x256提供了一個叫boot Memory的位址,1Mbytes, 從0x0~0x000F FFFF。可以映射成為内部的Flash和内部的SRAM。這個映射是:
Boot Memory: 0x0~0x000F FFFF, 1Mbytes
Internal Flash: 0x0010 0000~0x001F FFFF, 1Mbytes
Internal SRAM:0x0020 0000~0x002F FFFF, 1Mbytes
當Boot Memory映射成Internal Flash,flash的位址仍然從0x0010 0000開始,但是從0x0通路,等同于通路0x0010 0000;當映射成為 Internal SRAM,SRAM位址不變,通路0x0位址,等同于通路0x0020 0000。其實硬體做起來很簡單,就是将位址線用邏輯電路稍微處理一下。
由于代碼都要存放在FLASH裡,否則,沒電以後,啥都沒有了,也無法再次啟動。是以,異常向量要從0x0開始,那麼自然也要把向量放在這個位置,7x256上電以後預設boot memory 映射從flash開始。也就是說,把向量放在0x0010 0000即可解決這樣的問題。
當0x0010 0000被占用以後,bootloader 的向量問題解決了,那使用者代碼的中斷向量怎麼辦呢?不可能把bootloader的向量擦了,把使用者自己的向量寫入,那豈不是bootloader也完了?這裡有個小技巧,如果使用者程式從0x0010 1000開始,向量依然從這個位置開始。隻不過,在打開中斷,向量真正起作用前,将0x0010 1000這個地方向量到SRAM的首位址上,然後切換Boot Memory映射SRAM。那麼向量依然從0x0開始。這樣的話,無論多少級boot代碼,都可以完美的解決該問題。
ARM9除了以上的方法,還有個終極的利器,那就是MMU,你高興放哪就放哪,在向量起作用之前,用MMU将其位址變換為0即可。一點技術含量都沒有。
道理總是簡單的,實作起來總是有點點彎彎繞。我們看看實際的實作吧。這是7x256的向量代碼:
__vector:
LDR PC, [PC,#24] ; Absolute jump can reach 4 GByte
LDR PC, [PC,#24] ; Branch to undef_handler
LDR PC, [PC,#24] ; Branch to swi_handler
LDR PC, [PC,#24] ; Branch to prefetch_handler
LDR PC, [PC,#24] ; Branch to data_handler
DC32 0 ; Reserved
LDR PC, [PC,#24] ; Branch to irq_handler
LDR PC, [PC,#24] ; Branch to fiq_handler
DC32 _program_start
DC32 ARM_ExceptUndefInstrHndlr
DC32 ARM_ExceptSwiHndlr
DC32 ARM_ExceptPrefetchAbortHndlr
DC32 ARM_ExceptDataAbortHndlr
DC32 0
DC32 ARM_ExceptIrqHndlr
DC32 ARM_ExceptFiqHndlr
這裡相對比較簡單,對這個指令做一下解釋。LDR PC, [PC,#24]是将目前PC+24的位址的值載入到PC寄存器中。由于ARM流水線的問題,目前執行的指令,位址已經是後面兩條了。是以,是+24并不是+32。也就是把_program_start加載入PC指針裡。這段代碼已經消除了指令目前位置對跳轉位置的影響,可以随意的拷貝到任意的地方去執行。這段代碼放在0x20 0000地方,可以正常執行;放在0x10 0000地方也可以正常執行。
RTEMS的ARM9(CSB337)啟動向量:
vector_block:
ldr pc, Reset_Handler
ldr pc, Undefined_Handler
ldr pc, SWI_Handler
ldr pc, Prefetch_Handler
ldr pc, Abort_Handler
nop
ldr pc, IRQ_Handler
ldr pc, FIQ_Handler
Reset_Handler: b bsp_reset
Undefined_Handler: b Undefined_Handler
SWI_Handler: b SWI_Handler
Prefetch_Handler: b Prefetch_Handler
Abort_Handler: b Abort_Handler
nop
IRQ_Handler: b IRQ_Handler
FIQ_Handler: b FIQ_Handler
Rtems是個複雜的作業系統,在彙編代碼裡安裝的隻是一個簡單的複位向量。其它向量都隻是簡單的死循環。作業系統運作起來以後,還要再次安裝向量的。向量指向作業系統的複雜的處理函數。但不管這些,向量存儲的位址是沒有改變的。Link腳本上可以看到向量被放在内部的SRAM的首位址上。(CSB337)
SECTIONS
{
.base :
{
_sram_base = .;
arm_exception_table = .;
. += 64;
連接配接器雖然把位置空出來了,但連接配接器依然不知道将vector_block放到什麼位置。怎麼辦?這裡的代碼解釋了一切。
ldr r0, =mem_map
bl mmu_init
mov r0, #0
adr r1, vector_block
ldmia r1!, {r2-r9}
stmia r0!, {r2-r9}
ldmia r1!, {r2-r9}
stmia r0!, {r2-r9}
Gnu的工具鍊并不針對某一個具體的平台。是以解決方案從某種意義上說,更具有普遍意義。先調用mmu_init,這是幹什麼,實際上是将MMU初始化,将我們定義的.base位址放到0x0位置去。然後緊接着下面的幾行代碼,是将上面的中斷向量到0x0位置去。一共64個位元組,實作4GB内的位址絕對跳轉。