U-Boot完美解讀(2)——啓動文件start.s解析

2、誰來喚醒我

在上一節的u-boot.lds文件中有這樣一句是:

cpu/arm_cortexa8/start.o (.text)
這句話就是調用初始化代碼stat.s的元老級功臣,這可和神話中的女媧、盤古之類的有得一拼的,只是那時代沒有計算機,要不還真得一較高低才行。說遠了,迴歸主題,話說從這裏調用並執行start.s文件後,該文件又是如何執行的呢?

2.1、天生我才必有用

start.s是就是所謂啓動的第一階段,其主要功能如下:

(1)定義入口。由於一個可執行的image必須有一個入口點,並且只能有一個全局入口,通常這個入口放在rom(Flash)的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改連接器腳本來完成。
(2)設置異常向量(exception vector)。
(3)設置CPU的速度、時鐘頻率及中斷控制寄存器。
(4)初始化內存控制器 。
(5)將rom中的程序複製到ram中。
(6)初始化堆棧 。
(7)轉到ram中執行,該工作可使用指令ldr pc來完成。


2.2、出問題了?相信表哥

怎麼不是相信春哥得永生呢?其實我也這麼想的,關鍵是程序不認識春哥呀,這個傻不啦嘰的傢伙只認識表哥呀,當然這裏可不是你的表哥喲,所謂的表即“異常向量表”,明白了吧,關鍵時候還得看錶哥的。

系統上電後,pc指針從0x00000000地址開始執行,這個地址是處理器可以直接訪問的,所以這個時候不要指望能運行到你的外存上,實際上現在還在內部flash中,就是所謂的nor flash。所以,我們就要在0x00000000位置放置有意義的東西,不然系統怎麼啓動呢?廢話少說,直接看下面的代碼再作解釋:

.globl _start
_start:    b   start_code
    ldr    pc, _undefined_instruction
    ldr    pc, _software_interrupt
    ldr    pc, _prefetch_abort
    ldr    pc, _data_abort
    ldr    pc, _not_used
    ldr    pc, _irq
    ldr    pc, _fiq

該部分爲處理器的異常處理向量表。地址範圍爲0x0000 0000 ~ 0x0000 0020,剛好8條指令,在ARM的異常向量表(如下表所示):

中斷向量地址 異常中斷類型 異常中斷模式 優先級(6最低)
0x00 復位 特權模式 1
0x04 未定義的指令 UND終止模式 6
0x08 軟件中斷 特權模式 6
0x0C 指令預取終止 終止模式 5
0x10 數據訪問終止 終止模式 2
0x14 保留 未使用 未使用
0x18 外部中斷請求 IRQ模式 4
0x1C 快速中斷請求 FIQ模式 3

FIQ異常向量放在所有向量最後,目的是可以將FIQ異常處理程序直接放在向量的地址上. 這樣在執行FIQ處理時,就可以不用進行跳轉,快速響應中斷.FIQ向量放在最後,允許FIQ異常處理程序直接放在地址0x0000001C或0xFFFF001C開始 的位置,而不需要由向量的分支指令。也就是說直接從0x0000 0001C開始執行,這樣省去了一個跳轉指令,如果FIQ不是在頂端,那麼當然需要一次跳轉.

這裏千萬別理解爲_start就是存放在0x00開始的地方,而是發生中斷跳轉的位置。而翻譯後_start的位置由是編譯鏈接工具決定,可由編譯後u-boot目錄下生成的System.map文件可知,_start的位置竟然是0x33f80000,那這個地址是哪兒來的呢?相信不是天上掉下來的林妹妹,我也想天上掉個林妹妹給我呢,就我這種碼農,掉下來林妹妹也不見得是我的,更何況是根本不會實現的,想也白想了。因此,這個地址得有一個地方指定纔對吧!OK,見證奇蹟的時候到了,我們可以通過根目錄下的Makefile查看是u-boot是如何鏈接的,有如下一段代碼爲證:

GEN_UBOOT = \
		UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
		sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
		cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \
			--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
			-Map u-boot.map -o u-boot
$(obj)u-boot:	depend \
		$(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
		$(GEN_UBOOT)

可以看到LDFLAGS標誌,而它又被定義了,所以得找到罪魁禍首才行,在根目錄下的config.mk文件中,可以看到如下代碼:

LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)
ifneq ($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
endif

LDFLAGS裏面除了指定鏈接腳本,如果TEXT_BASE不等於空,還加上了-Ttext$(TEXT_BASE),TEXT_BASE的值是多少呢?我們可以在config.mk裏面看到它的定義(系統中有多個文件,不同處理器對應的文件不同,如移植s3c2440的可以參見目錄board/samsung/smdk2410/,當然也要防止U-Boot的結構調整,指不定這個目錄又放到哪兒去了),親,看到了嗎?就只有這一行喲,它的值爲0x33f80000。

TEXT_BASE = 0x33F80000

關於異常向量表就囉嗦說這麼多了,不過還是沒覺得輕鬆多少呢?因爲真正的解釋部分還沒開始,接下來正文開始了,首先說明一下第一行,學習過彙編的童鞋都應該知道globl是聲明全局變量的意思,所以這裏把_start聲明是全局的,至於全局變量和局部變量的區別,相信各位童鞋的能力,就此略去三千字吧。

程序段正文的第一行可是一個亮點喲,親們,這裏不能打瞌睡喲!

_start:    b       start_code

程序開始的第一句就是一個跳轉,而且是直接跳轉的喲,可沒有半點含糊,表示的直接啓動或重啓,爲什麼會有重啓功能呢?因爲第一次開機,和熱啓動都要從這裏執行,只是冷啓動前有一次上電操作,而熱啓動直接進入中斷向量表,調用這條指令執行start_code,而且還要記得熱啓動設備一直都處於帶電狀態,現在是不是也明白了我們的電腦有熱啓動和冷啓動的差別了?

只用說明第一條是跳轉執行的,那接下來的幾句也不用多作說明了吧,分別在對應異常的發生時進行相應的跳轉,跳轉的位置可以通過接下來的幾行代碼來看:

_undefined_instruction:	.word undefined_instruction
_software_interrupt:	.word software_interrupt
_prefetch_abort:	.word prefetch_abort
_data_abort:		.word data_abort
_not_used:		.word not_used
_irq:			.word irq
_fiq:			.word fiq
例如,當發生fiq中斷時,將進行跳轉到對應的中斷,而對應的fiq標籤可以在文件後面找到相應的實現部分(其實這裏還有一個分支,僅用一個作示例):

fiq:
	get_bad_stack
	bad_save_user_regs
	bl	do_fiq
再回到上面的中斷髮生處理中,在標籤前面還有一個.word,在彙編裏.word僞操作用於分配一段字內存單元(分配的單元都是字對齊的)
然後接下來有一個很變態的句子,我也一直在想,結果還是在網上找了一些參考,找到一個合理的解釋,差點忘了,先把代碼貼出來:

.balignl 16,0xdeadbeef
首先來解釋哈.balignl的應用形式:
.balign[wl] abs-expr, abs-expr, abs-expr
增加位置計數器(在當前子段)使它指向規定的存儲邊界。第一個表達式參數(結果必須是純粹的數字)是必需參數:邊界基準,單位爲字節。例如,‘.balign 8’向後移動位置計數器直至計數器的值等於8的倍數。如果位置計數器已經是8的倍數,則無需移動。第2個表達式參數(結果必須是純粹的數字)給出填充字節 的值,用這個值填充位置計數器越過的地方。第2個參數(和逗點)可以省略。如果省略它,填充字節的值通常是0。但在某些系統上,如果本段標識爲包含代碼, 而填充值被省略,則使用no-op指令填充空白區。第3個參數的結果也必須是純粹的數字,這個參數是可選的。如果存在第3個參數,它代表本對齊命令允許跳 過字節數的最大值。如果完成這個對齊需要跳過的字節數比規定的最大值還多,則根本無法完成對齊。您可以在邊界基準參數後簡單地使用兩個逗號,以省略填充值 參數(第二參數);如果您在想在適當的時候,對齊操作自動使用no-op指令填充,本方法將非常奏效。.balignw和.balignl是.balign命令的變化形式。.balignw使用2個字節來填充空白區。.balignl使用4字節來填充。例如,.balignw 4,0x368d將地址對齊到4的倍數,如果它跳過2個字節,GAS將使用0x368d填充這2個字節(字節的確切存放位置視處理器的存儲方式而定)。

ARM920T處理器核心,支持32位與16位兩種指令長度,16位的指令叫thumb指令集,由於我使用的是32位指令集,所以一切都是以32位指令集進行說明。既然是32位指令集,所以一條指令就佔32位,即4字節。

而在前面的兩段代碼中,可以通過計算,第一部分佔了4x8=32字節內存;第二部分佔了4x7=28字節內存。一共佔了4x15=60個字節的內存,所以本代碼的作者當時就簡單的在15這個數上,加了個1,即16,把當前指針往後移到地址爲64的位置,然後在前面插上了0xdeadbeef這個特殊的值。果說最小的值的話,那麼也可以寫成.balignl 8,0xdeadbeef,也可以達到同樣的目的。因爲60不是8的倍數,但是64是8的倍數,如果寫8,也正好插到64前面,也即60這個內存起始地址。如果更大一點兒的呢,那麼填32也可以達到同樣的效果,即.balignl 32,0xdeadbeef,道理同上。當然,不能爲4,因爲pc值在任何時候,都是4的倍數,只要不爲0就爲4的倍數,呵呵,這個值不行,如果用了這個值,0xdeadbeef永遠也插不進去。-----------------以上解釋引自http://haoyeren.blog.sohu.com/84511571.html

這裏再補充一點爲什麼用‘.balign 4’就不行,因爲可以計算出當然PC的位置是60個字節,前面也有計算,所以4x15=60正好是4的倍數,無需移動,而指令的使用是用0xdeadbeef填充移動的空間,因爲沒有移動,所以中間沒有空餘的空間不能插呀!

2.3、山高我爲峯

前面對我們的表哥進行了算是詳細的介紹,但正如前面所說的那樣,系統上電後運行到第一句就跳走了,後面的是在異常發生時再進行跳轉,所以真正引導系統啓動的代碼還沒有開始喲!親,辛苦了喲,現在就是開始的代碼:

/*
 * the actual start code
 */

start_code:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr
	bic	r0, r0, #0x1f
	orr	r0, r0, #0xd3
	msr	cpsr, r0
從以上的註釋也可以看到,這裏是將CPU的模式爲設置爲SVC模式,至於SVC是什麼模式,這裏得說明一下處理器硬件的東西了。ARM處理器共有37個寄存器,31個通用寄存器,分別爲R0-R15,其中R8-R12爲分組寄存器,R13可理解爲堆棧指針(彙編裏用的SP就是鼎鼎有名的R13),R14爲鏈接寄存器(彙編裏可也有這位大俠的句號喲,就是所用的LR鏈接寄存器),至於R15就更不用說了,就是傳說中滴PC大神,程序計數器。除了31個通用寄存器還有6個狀態寄存器,分爲CPSR和SPSR,其中SPSR有五個分支,就是爲處理器5種狀態(fiq、irq、svc、abt、und,除此之外還有系統模式和用戶模式。這裏涉及到的模式就由此展開,不同的模式下可訪問的資源權限是不同的,接下來通過一個表格詳細介紹一下ARM中CPU的幾種模式:

處理器模式 說明 主要功能
用戶(usr) 正常程序工作模式 此模式下程序不能夠訪問一些受操作系統保護的系統資源,應用程序也不能直接進行處理器模式的切換。
系統(sys) 用於支持操作系統的特權任務等 與用戶模式類似,但具有可以直接切換到其它模式等特權
快中斷(fiq) 支持高速數據傳輸及通道處理 FIQ異常響應時進入此模式
中斷(irq) 用於通用中斷處理 IRQ異常響應時進入此模式
管理(svc) 操作系統保護代碼 系統復位和軟件中斷響應時進入此模式
中止(abt) 用於支持虛擬內存和/或存儲器保護 在ARM7TDMI沒有大用處
未定義(und) 支持硬件協處理器的軟件仿真 未定義指令異常響應時進入此模式

sys模式和usr模式相比,所用的寄存器組,都是一樣的,但是增加了一些訪問一些在usr模式下不能訪問的資源。而svc模式本身就屬於特權模式,本身就可以訪問那些受控資源,而且,比sys模式還多了些自己模式下的影子寄存器,所以,相對sys模式來說,可以訪問資源的能力相同,但是擁有更多的硬件資源。所以,從理論上來說,雖然可以設置爲sys和svc模式的任一種,但是從uboot方面考慮,其要做的事情是初始化系統相關硬件資源,需要獲取儘量多的權限,以方便操作硬件,初始化硬件。


2.4、關門,放狗!

狗,很形象的東西,如果不喂狗就會亂咬人

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
         /* turn off the watchdog */
# if defined(CONFIG_S3C2400)
#  define pWTCON    0x15300000
#  define INTMSK       0x14400008        /* Interupt-Controller base addresses */
#  define CLKDIVN     0x14800014        /* clock divisor register */
#else
#  define pWTCON    0x53000000
#  define INTMSK       0x4A000008       /* Interupt-Controller base addresses */
#  define INTSUBMSK         0x4A0000
#  define CLKDIVN     0x4C000014       /* clock divisor register */
# endif
 
         ldr     r0, =pWTCON
         mov  r1, #0x0
         str     r1, [r0]
         /*
          * mask all IRQs by setting all bits in the INTMR - default
          */
         mov  r1, #0xffffffff
         ldr     r0, =INTMSK
         str     r1, [r0]
# if defined(CONFIG_S3C2410)
         ldr     r1, =0x3ff
         ldr     r0, =INTSUBMSK
         str     r1, [r0]
# endif


如上代碼中,前面的
#if defined....
.....
#else
....
#endif
是定義到寄存器,就是給寄存器取了一個別名,如同是我們的名字一樣。如果在其它地方有定義,直接引入就可以了,不必重新定義,這裏就不用多說了,此處略去一千字解釋張三、李四名字的由來......
接下來通過ldr指向看門狗寄存器,下面就直接向狗寫入0,這裏就是一個關狗的過程。看門狗的原理就是:當看門狗開啓後,如果一段時間不去喂狗,狗就會餓呀,狗餓了做幹嘛呢,搶答題?你係統就會不斷的一直重啓,重啓,還是重啓。這裏就是爲什麼在剛開始時會把狗關起來的原因,防止喂狗的時間到了,狗餓死了,然後你就掛了。
最後就是關中斷,因爲系統在初始化的時候不需要中斷處理,如果打開的時候,你突然按一下按鍵,跳去處理按鍵,但系統還沒初始化完呢?會出現什麼情況呢,一樣的結局,還沒看到開機界面呢,掛了。

發佈了21 篇原創文章 · 獲贊 13 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章