基於S3C44B0微處理器的uClinux內核引導剖析
姜江
Blog:http://blog.csdn.net/jznsmail
摘 要:本文采用三星公司的S3C44B0微處理器,對uClinux操作系統內核的引導過程進行了剖析。
關鍵字:S3C44B0X;uClinux;嵌入式系統;內核引導
1 前言
伴隨着微電子的發展,用於嵌入式設備的處理器速度越來越快,功能也越來越強大。三星公司生產的S3C44B0微處理器,採用的是ARM7TDMI內核。該內核因爲有着功耗小、成本低等特點,因此非常適合作爲移動手持終端的處理器核心。Linux操作系統因爲它的開放性,使得它不斷的被應用到各個領域。在嵌入式領域同樣也出現了各種各樣的Linux變體,最常用的是uClinux。也正是因爲uClinux操作系統支持不帶MMU單元的ARM處理器,因此該系統可以對S3C44B0微處理器有很好的支持。
在嵌入式系統開發中,第一個部分便是系統的引導。而系統的引導過程是通過BootLoader來完成的。BootLoader程序是與硬件緊密相關的一段代碼,而且編寫的時候比較複雜,它主要的功能是初始化微處理器以及周邊的硬件資源,並且引導操作系統的啓動。下面我將以S3C44B0微處理器來作爲例子,對uClinux操作系統內核的引導過程進行一個剖析。
2 BootLoader程序概念
簡單的說Boot Lodaer就是在操作系統內核運行之前運行的一段小程序,通過這段小程序,可以初始化硬件設備、建立系統的內存空間映射圖,從而將系統的軟硬件環境設置成一個適合的狀態,以便爲最終調用操作系統內核準備好正確的環境。最終,BootLoader把操作系統內核映象加載到RAM中,並將系統控制權傳遞給它。
2.1 典型的BootLoader程序框架
操作系統角度來說,Boot Loader的總目標就是正確的調用內核來執行。
由於Boot Loader的實現依賴於CPU的體系結構,因此大多數Boot Loader都分爲Stage1和Stage2兩大部分。依賴於CPU體系結構的代碼,例如設備初始化代碼等,通常都放在Stage1中,而且通常都用匯編語言來實現,以達到短小精悍的目的。而Stage2通常用C語言來實現,這樣可以實現更加複雜的功能,而且代碼會具有更好的可讀性和可移植性。
Boot Loader的Stage1通常包括如下步驟:
1) 硬件設備初始化
2) 爲加載Boot Loader的Stage2準備RAM空間
3) 複製Boot Loader的Stage2到RAM空間中
4) 設置好堆棧
5) 跳轉到Stage2的C入口點
Boot Loader的Stage2通常包括如下步驟:
1) 始化本階段要使用的硬件設備
2) 檢測系統內存映射(Memory Map)
3) 將Kernel映象和根文件系統映象從FLASH上讀取到RAM空間中
4) 爲內核設置啓動參數
5) 調用內核
2.2 系統內存組織
由於嵌入式設備具有很好的制定性,因此通常硬件環境會變的千差萬別。就算是用戶使用了相同的處理器芯片,但是也很有可能因爲外圍設備電路設計的不同,而存在差異。對於BootLoader程序來說,存儲設備的與處理器的連接方式,與其息息相關。對於我們採用的S3C44B0微處理器來說,在系統加電之後,指令指針是指向0x00000000的,也就是說系統是從0x00000000開始之行。正是因爲這個原因,通常這個地址空間我們會安排給FLASH存儲器。這樣我們可以將BootLoader啓動代碼以及我們之後將會要啓動的uClinux操作系統映像燒寫到Flash裏。對於RAM地址空間,S3C44B0芯片將其設定爲從0x0C000000到0x0FFFFFFF一共64MB的範圍裏。我們可以通過設定存儲器控制寄存器來重新設定RAM的大小。例如我們試驗採用的存儲設備安排如下:
0x00000000 – 0x003FFFFF 4MB Flash
0x0C000000 – 0x0C7FFFFF 8MB RAM
通常來說對於系統的引導和操作系統的啓動,可以完全都在Flash中進行,但是Flash存儲器的速度相對於RAM來說會慢很多,因此出於速度上的考慮,我們通常會將啓動代碼和uClinux操作系統的內核映像文件拷貝到RAM中之行。
下面我將對典型的BootLoader程序框架進行分析。
2.3 Stage1階段
該階段的主要工作是完成對系統中斷向量的設置,初始化微處理器內部寄存器,初始化堆棧,初始化RAM地址空間,並且將Stage2部分的C代碼拷貝到RAM空間的指定地點,然後跳轉到C代碼入口點繼續執行。對於這段代碼來說,做的都是一些準備工作,因此爲了提高效率,這段代碼通常都是使用匯編語言來完成的。下面我將結合具體的代碼來分析一下Stage1的啓動過程。
1)設置中斷向量
設置S3C44B0處理器定義的8種系統中斷的中斷向量地址。這八種系統中斷分別是復位中斷、未定義指令中斷、軟件中斷、指令預取異常中斷、數據異常中斷、地址異常中斷、IRQ中斷和FIQ中斷。這8箇中斷通常是通過無條件跳轉的方式來實現的。具體的代碼如下。
__entry :
b ResetHandler /* Reset vector */
b HandlerUndef /* Undefined instruction */
b HandlerSWI /* SWI */
b HandlerPabort /* Prefetch abort */
b HandlerDabort /* Data abort */
b . /* Address exception */
b HandlerIRQ /* IRQ */
b HandlerFIQ /* FIQ */
2)初始化微處理器內部寄存器
這段代碼主要是要完成硬件部分的初始化,包括關閉中斷響應、初始化微處理器通用端口、設置CPU頻率等操作。不過需要注意的是,在進行硬件初始化之前需要將微處理器的運行狀態轉換到SVC模式下。
MRS a1,CPSR /*; Pickup current CPSR*/
BIC a1,a1,#MODE_MASK /*; Clear the mode bits*/
ORR a1,a1,#SUP_MODE /*; Set the supervisor mode bits*/
ORR a1,a1,#LOCKOUT /*; Insure IRQ and FIQ intr are locked out*/
MSR CPSR_cxsf,a1 /*; Setup the new CPSR*/
3)初始化系統RAM空間
這個部分的工作主要是爲之後啓動代碼和內核映像的拷貝操作做準備,並且也爲之後的C代碼的執行初始化堆棧。這部分的工作主要可以分成兩個部分來處理。首先,根據系統配置的存儲器特性來初始化相關的存儲器控制寄存器。在我們使用的S3C44B0處理器中,存儲空間被分成了BANK0-BANK7一共8個塊,分別由BANKCON0-BANKCON7控制各個塊存儲器的讀寫時鐘和片選時鐘等信號參數。具體代碼如下:
ldr r0,=rBANKCON0
ldr r1,=0x700
str r1,[r0]
ldr r0,=rBANKCON1
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON2
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON3
ldr r1,=0x7568
str r1,[r0]
ldr r0,=rBANKCON4
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON5
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON6
ldr r1,=0x18008
str r1,[r0]
ldr r0,=rBANKCON7
ldr r1,=0x18000
str r1,[r0]
ldr r0,=rREFRESH
ldr r1,=0xac03e1
str r1,[r0]
ldr r0,=rBANKSIZE
ldr r1,=0x16
str r1,[r0]
ldr r0,=rMRSRB6
ldr r1,=0x020
str r1,[r0]
ldr r0,=rMRSRB7
ldr r1,=0x020
str r1,[r0]
初始化RAM空間的第二個部分就是初始化連接腳本文件中指定的需要清0的地址空間,將該斷地址空間的內容清0。該部分地址空間主要是用來存放C語言代碼中的全局變量等內容的。實現代碼如下:
LDR a1,=Image_ZI_Base /* Pickup the start of the BSS area */
MOV a3,#0 /* Clear value in a3 */
LDR a2,=Image_ZI_Limit /* Pickup the end of the BSS area */
CMP a1,a2
BEQ move_data
clear_loop :
STR a3,[a1],#4 /* Clear a word, a1 += 4 */
CMP a1,a2 /* end of ZI ? */
BNE clear_loop
4)爲Stage2的C語言代碼的執行準備必要的堆棧
因爲在Stage2階段一般都是採用C語言代碼來完成的,因此必須在使用C語言代碼之前先建立起必要的堆棧信息。通常爲了避免堆棧數據被執行代碼破壞,通常都是放在RAM的高端地址,並且使得堆棧指針的增長方向是向下增長的。
5)將初始化代碼拷貝到RAM中,並且跳轉到RAM中執行。因爲在我們採用的S3C44B0微處理器裏對於FLASH和RAM地址空間是使用的統一編址的,因此我們可以直接使用一個簡單循環來完成拷貝。
ldr r3, =0x10000 /* 64K Bytes */
ldr r2, =0xc700000
ldr r1, =0
next :
ldr r0,[r1],#4
str r0,[r2],#4
cmp r1,r3
bne next
6)跳轉到C代碼執行(即Stage2階段)
這個過程是直接給指令指針賦值於跳轉的C代碼的入口地址,在我們的試驗中該入口地址是Main。
LDR pc,=Main
2.4 Stage2階段
該階段的代碼主要使用C語言來實現的。該階段的工作主要是建立開發板與宿主機之間的通信,加載uClinux內核映像文件和配置內核啓動參數,並且啓動內核。
嵌入式設備與宿主機的通訊方式有多種,最常用的是使用串口方式進行數據交換。本試驗採用的S3C44B0微處理器提供了兩個UART口,因此我們可以任選其中一個來初始化並且使用它來與宿主機交互。對於串口的初始化主要是波特率、奇偶校驗、停止位、數據位等內容。
對於串口的波特率和波特因子的計算採用如下公式
Iubrd =((int(mclk/16 / baud + 0.5) – 1)
mclk是頻率、baud爲波特率
2.4.1 檢測內存
該部分的功能主要是檢測系統在進行硬件初始化的時候是否發生了內存映射錯誤,即是否物理地址是否被映射到不存在的地址空間。通常是使用讀寫方式來檢測的,即以內存頁爲單位,在每個頁頭進行讀寫操作,比較讀寫結果。因爲S3C44B0處理器並不支持內存映射,因此我們在Stage2過程中並沒有包含該部分功能函數。
2.4.2 加載uClinux內核映像
該過程其實只是一個從Flash的指定位置(該位置是uClinux燒寫的起始地址)拷貝到RAM中指定的地址空間裏。在拷貝之前必須要爲uClinux的全局變量結構,即啓動參數、內核頁表、RAM的頁目錄等信息預留一定的空間。如果我們將FLASH和RAM看成連在一起的線性地址,則系統的空間分配會如下圖:
2.4.3 配置內核啓動參數
我們採用的uClinux是2.4.x內核版本,該版本的內核支持參數啓動過程。在嵌入式系統中,啓動參數的傳入主要是依靠bootloader程序向標記列表(tagged list)的相關域中填寫相應的值來完成的。
2.5 uClinux內核引導
當我們初始化完畢uClinux的啓動參數之後,控制權就可以交給uClinux內核了,uClinux系統調用內核解壓函數(decompress_kernel)來對上一個階段拷貝的uClinux內核在RAM空間裏進行解壓(當然如果系統內核在建立的時候沒有配置成壓縮格式,則解壓過程略去)。在解壓完畢後,跳轉到內核調用函數(call_kernel),該函數實際上執行的是start_kernel(),這個函數包含了有關處理器初始化、中斷初始化、進程初始化等操作。最後,將控制權完全的交與uClinux操作系統來執行。
僞處理過程如下:
IF(啓動參數正確)
CALL decmporess_kernel()
CALL call_kernel()
ELSE
啓動失敗
decompress_kernel()
{
解壓內核映像
}
call_kernel()
{
...
start_kernel()
....
}
3 總結
本文是對S3C44B0的啓動過程進行了一次分析,啓動部分的代碼可以說是嵌入式設備開發比較重要的部分。而且該部分的處理工作往往又比較麻煩,因此在這裏我只是想起到拋磚引玉的作用。因爲成文時間比較倉促,難免有錯誤,請大家批評指正。