新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 嵌入式 arm平臺kernel啟動第一階段匯編head.s分析

嵌入式 arm平臺kernel啟動第一階段匯編head.s分析

作者: 時間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
arm_linux內(nèi)核生成過程:

1.依據(jù)arch/arm/kernel/vmlinux.lds生成linux內(nèi)核源碼根目錄下的vmlinux,這個vmlinux屬于未壓縮,帶調(diào)試信息、符號表的最初的內(nèi)核,大小約23MB;
命令:arm-linux-gnu-ld-ovmlinux-Tarch/arm/kernel/vmlinux.lds
arch/arm/kernel/head.o
init/built-in.o
--start-group
arch/arm/mach-s3c2410/built-in.o
kernel/built-in.o
mm/built-in.o
fs/built-in.o
ipc/built-in.o
drivers/built-in.o
net/built-in.o
--end-group.tmp_kallsyms2.o

本文引用地址:http://butianyuan.cn/article/201611/317681.htm


2.將上面的vmlinux去除調(diào)試信息、注釋、符號表等內(nèi)容,生成arch/arm/boot/Image,這是不帶多余信息的linux內(nèi)核,Image的大小約3.2MB;
命令:arm-linux-gnu-objcopy-Obinary-Svmlinuxarch/arm/boot/Image

3.將arch/arm/boot/Image用gzip-9壓縮生成arch/arm/boot/compressed/piggy.gz大小約1.5MB;命令:gzip-f-9arch/arm/boot/compressed/piggy.gz

4.編譯arch/arm/boot/compressed/piggy.S生成arch/arm/boot/compressed/piggy.o大小約1.5MB,這里實際上是將piggy.gz通過piggy.S編譯進piggy.o文件中。而piggy.S文件僅有6行,只是包含了文件piggy.gz;
命令:arm-linux-gnu-gcc-oarch/arm/boot/compressed/piggy.oarch/arm/boot/compressed/piggy.S


5.依據(jù)arch/arm/boot/compressed/vmlinux.lds將arch/arm/boot/compressed/目錄下的文件head.o、piggy.o、misc.o鏈接生成arch/arm/boot/compressed/vmlinux,這個vmlinux是經(jīng)過壓縮且含有自解壓代碼的內(nèi)核,大小約1.5MB;
命令:arm-linux-gnu-ldzreladdr=0x30008000params_phys=0x30000100-Tarch/arm/boot/compressed/vmlinux.ldsarch/arm/boot/compressed/head.oarch/arm/boot/compressed/piggy.oarch/arm/boot/compressed/misc.o-oarch/arm/boot/compressed/vmlinux


6.將arch/arm/boot/compressed/vmlinux去除調(diào)試信息、注釋、符號表等內(nèi)容,生成arch/arm/boot/zImage大小約1.5MB;這已經(jīng)是一個可以使用的linux內(nèi)核映像文件了;
命令:arm-linux-gnu-objcopy-Obinary-Sarch/arm/boot/compressed/vmlinuxarch/arm/boot/zImage


7.將arch/arm/boot/zImage添加64Bytes的相關(guān)信息打包為arch/arm/boot/uImage大小約1.5MB;
命令:./mkimage-Aarm-Olinux-Tkernel-Cnone-a0x30008000-e0x30008000-nLinux-2.6.35.7-darch/arm/boot/zImagearch/arm/boot/uImage

內(nèi)核啟動分析:

本文著重分析S3C2410linux-2.6.35.7內(nèi)核啟動的詳細過程,主要包括:zImage解壓縮階段、vmlinux啟動匯編階段、startkernel到創(chuàng)建第一個進程階段三個部分,一般將其稱為linux內(nèi)核啟動一、二、三階段,本文也將采用這種表達方式。對于zImage之前的啟動過程,本文不做表述,可參考前面正亮講得“u-boot的啟動過程分析”。

本文中涉及到的術(shù)語約定如下:

基本內(nèi)核映像:即內(nèi)核編譯過程中最終在內(nèi)核源代碼根目錄下生成的vmlinux映像文件,并不包含任何內(nèi)核解壓縮和重定位代碼;

zImage內(nèi)核映像:包含了內(nèi)核piggy.o及解壓縮和重定位代碼,通常是目標(biāo)板bootloader加載的對象;

zImage下載地址:即bootloader將zImage下載到目標(biāo)板內(nèi)存的某個地址或者nandread將zImage讀到內(nèi)存的某個地址;

zImage加載地址:由Linux的bootloader完成的將zImage搬移到目標(biāo)板內(nèi)存的某個位置所對應(yīng)的地址值,默認值0x30008000。

1、Linux內(nèi)核啟動第一階段:內(nèi)核解壓縮和重定位

該階段是從u-boot引導(dǎo)進入內(nèi)核執(zhí)行的第一階段,我們知道u-boot引導(dǎo)內(nèi)核啟動的最后一步是:通過一個函數(shù)指針thekernel()帶三個參數(shù)跳轉(zhuǎn)到內(nèi)核(zImage)入口點開始執(zhí)行,此時,u-boot的任務(wù)已經(jīng)完成,控制權(quán)完全交給內(nèi)核(zImage)。

稍作解釋,在u-boot的文件archarmlibbootm.c(uboot-2010.9)中定義了thekernel,并在do_bootm_linux的最后執(zhí)行thekernel.

定義如下:void(*theKernel)(intzero,intarch,uintparams);

theKernel=(void(*)(int,int,uint))ntohl(hdr->ih_ep);

//hdr->ih_ep----EntryPointAddressuImage中指定的內(nèi)核入口點,這里是0x30008000。

theKernel(0,bd->bi_arch_number,bd->bi_boot_params);

其中第二個參數(shù)為機器ID,第三參數(shù)為u-boot傳遞給內(nèi)核參數(shù)存放在內(nèi)存中的首地址,此處是0x30000100。

由上述zImage的生成過程我們可以知道,第一階段運行的內(nèi)核映像實際就是arch/arm/boot/compressed/vmlinux,而這一階段所涉及的文件也只有三個:

(1)arch/arm/boot/compressed/vmlinux.lds

(2)arch/arm/boot/compressed/head.S

(3)arch/arm/boot/compressed/misc.c

下面的圖是使用64MRAM時,通常的內(nèi)存分布圖:

下面我們的分析集中在arch/arm/boot/compressed/head.S,適當(dāng)參考vmlinux.lds。

從linux/arch/arm/boot/compressed/vmlinux.lds文件可以看出head.S的入口地址為ENTRY(_start),也就是head.S匯編文件的_start標(biāo)號開始的第一條指令。

下面從head.S中得_start標(biāo)號開始分析。(有些指令不影響初始化,暫時略去不分析)

代碼位置在/arch/arm/boot/compressed/head.S中:

start:

.typestart,#function/*uboot跳轉(zhuǎn)到內(nèi)核后執(zhí)行的第一條代碼*/

.rept8/*重復(fù)定義8次下面的指令,也就是空出中斷向量表的位置*/

movr0,r0/*就是nop指令*/

.endr

b1f@跳轉(zhuǎn)到后面的標(biāo)號1處

.word0x016f2818@輔助引導(dǎo)程序的幻數(shù),用來判斷鏡像是否是zImage

.wordstart@加載運行zImage的絕對地址,start表示賦的初值

.word_edata@zImage結(jié)尾地址,_edata是在vmlinux.lds.S中定義的,表示init,text,data三個段的結(jié)束位置

1:movr7,r1@savearchitectureID保存體系結(jié)構(gòu)ID用r1保存

movr8,r2@saveatagspointer保存r2寄存器參數(shù)列表,r0始終為0

mrsr2,cpsr@getcurrentmode得到當(dāng)前模式

tstr2,#3@notuser?,tst實際上是相與,判斷是否處于用戶模式

bnenot_angel@如果不是處于用戶模式,就跳轉(zhuǎn)到not_angel標(biāo)號處

/*如果是普通用戶模式,則通過軟中斷進入超級用戶權(quán)限模式*/

movr0,#0x17@angel_SWIreason_EnterSVC,向SWI中傳遞參數(shù)

swi0x123456@angel_SWI_ARM這個是讓用戶空間進入SVC空間

not_angel:/*表示非用戶模式,可以直接關(guān)閉中斷*/

mrsr2,cpsr@turnoffinterruptsto讀出cpsr寄存器的值放到r2中

orrr2,r2,#0xc0@preventangelfromrunning關(guān)閉中斷

msrcpsr_c,r2@把r2的值從新寫回到cpsr中

/*讀入地址表。因為我們的代碼可以在任何地址執(zhí)行,也就是位置無關(guān)代碼(PIC),所以我們需要加上一個偏移量。下面有每一個列表項的具體意義。

LC0是表的首項,它本身就是在此head.s中定義的

.typeLC0,#object

LC0:.wordLC0@r1LC0表的起始位置

.word__bss_start@r2bss段的起始地址在vmlinux.lds.S中定義

.word_end@r3zImage(bss)連接的結(jié)束地址在vmlinux.lds.S中定義

.wordzreladdr@r4zImage的連接地址,我們在arch/arm/mach-s3c2410/makefile.boot中定義的

.word_start@r5zImage的基地址,bootp/init.S中的_start函數(shù),主要起傳遞參數(shù)作用

.word_got_start@r6GOT(全局偏移表)起始地址,_got_start是在compressed/vmlinux.lds.in中定義的

.word_got_end@ipGOT結(jié)束地址

.worduser_stack+4096@sp用戶棧底user_stack是緊跟在bss段的后面的,在compressed/vmlinux.lds.in中定義的

@在本head.S的末尾定義了zImag的臨時??臻g,在這里分配了4K的空間用來做堆棧。

.section".stack","w"

user_stack:.space4096

GOT表的初值是連接器指定的,當(dāng)時程序并不知道代碼在哪個地址執(zhí)行。如果當(dāng)前運行的地址已經(jīng)和表上的地址不一樣,還要修正GOT表。*/

.text

adrr0,LC0/*把地址表的起始地址放入r0中*/

ldmiar0,{r1,r2,r3,r4,r5,r6,ip,sp}/*加載地址表中的所有地址到相應(yīng)的寄存器*/

@r0是運行時地址,而r1則是鏈接時地址,而它們兩都是表示LC0表的起始位置,這樣他們兩的差則是運行和鏈接的偏移量,糾正了這個偏移量才可以運行與”地址相關(guān)的代碼“

subsr0,r0,r1@calculatethedeltaoffset計算偏移量,并放入r0中

beqnot_relocated@ifdeltaiszero,wearerunningattheaddresswewerelinkedat.

@如果為0,則不用重定位了,直接跳轉(zhuǎn)到標(biāo)號not_relocated處執(zhí)行

/*

*偏移量不為零,說明運行在不同的地址,那么需要修正幾個指針

*r5–zImage基地址

*r6–GOT(全局偏移表)起始地址

*ip–GOT結(jié)束地址

*/

addr5,r5,r0/*加上偏移量修正zImage基地址*/

addr6,r6,r0/*加上偏移量修正GOT(全局偏移表)起始地址*/

addip,ip,r0/*加上偏移量修正GOT(全局偏移表)結(jié)束地址*/

/*

*這時需要修正BSS區(qū)域的指針,我們平臺適用。

*r2–BSS起始地址

*r3–BSS結(jié)束地址

*sp–堆棧指針

*/

addr2,r2,r0/*加上偏移量修正BSS起始地址*/

addr3,r3,r0/*加上偏移量修正BSS結(jié)束地址*/

addsp,sp,r0/*加上偏移量修正堆棧指針*/

/*

*重新定位GOT表中所有的項.

*/

1:ldrr1,[r6,#0]@relocateentriesintheGOT

addr1,r1,r0@table.Thisfixesupthe

strr1,[r6],#4@Creferences.

cmpr6,ip

blo1b

not_relocated:movr0,#0

1:strr0,[r2],#4@clearbss清除bss段

strr0,[r2],#4

strr0,[r2],#4

strr0,[r2],#4

cmpr2,r3

blo1b

blcache_on/*開啟指令和數(shù)據(jù)Cache,為了加快解壓速度*/

@這里的r1,r2之間的空間為解壓縮內(nèi)核程序所使用,也是傳遞給decompress_kernel的第二和第三的參數(shù)

movr1,sp@mallocspaceabovestack

addr2,sp,#0x10000@64kmax解壓縮的緩沖區(qū)

@下面程序的意義就是保證解壓地址和當(dāng)前程序的地址不重疊。上面分配了64KB的空間來做解壓時的數(shù)據(jù)緩存。

/*

*檢查是否會覆蓋內(nèi)核映像本身

*r4=最終解壓后的內(nèi)核首地址

*r5=zImage的運行時首地址,一般為0x30008000

*r2=endofmallocspace分配空間的結(jié)束地址(并且處于本映像的前面)

*基本要求:r4>=r2或者r4+映像長度<=r5

(1)vmlinux的起始地址大于zImage運行時所需的最大地址(r2),那么直接將zImage解壓到vmlinux的目標(biāo)地址

cmpr4,r2

bhswont_overwrite/*如果r4大于或等于r2的話*/

(2)zImage的起始地址大于vmlinux的目標(biāo)起始地址加上vmlinux大?。?M)的地址,所以將zImage直接解壓到vmlinux的目標(biāo)地址

addr0,r4,#4096*1024@4MBlargestkernelsize

cmpr0,r5

blswont_overwrite/*如果r4+映像長度<=r5的話*/

@前兩種方案通常都不成立,不會跳轉(zhuǎn)到wont_overwrite標(biāo)號處,會繼續(xù)走如下分支,其解壓后的內(nèi)存分配示意圖如下:

movr5,r2@decompressaftermallocspace

movr0,r5/*解壓程序從分配空間后面存放*/

movr3,r7

bldecompress_kernel

/進入decompress_kernel*/

@decompress_kernel共有4個參數(shù),解壓的內(nèi)核地址、緩存區(qū)首地址、緩存區(qū)尾地址、和芯片ID,返回解壓縮代碼的長度。

decompress_kernel(ulgoutput_start,ulgfree_mem_ptr_p,ulgfree_mem_ptr_end_p,

intarch_id)

{

output_data=(uch*)output_start;/*Pointstokernelstart*/

free_mem_ptr=free_mem_ptr_p;/*保存緩存區(qū)首地址*/

free_mem_ptr_end=free_mem_ptr_end_p;/*保存緩沖區(qū)結(jié)束地址*/

__machine_arch_type=arch_id;

arch_decomp_setup();

makecrc();/*鏡像校驗*/

putstr("UncompressingLinux...");

gunzip();/*通過free_mem_ptr來解壓縮*/

putstr("done,bootingthekernel.n");

returnoutput_ptr;/*返回鏡像的大小*/

}

/從decompress_kernel函數(shù)返回*/

addr0,r0,#127+128

bicr0,r0,#127@alignthekernellength對齊內(nèi)核長度

/*

*r0=解壓后內(nèi)核長度

*r1-r3=未使用

*r4=真正內(nèi)核執(zhí)行地址0x30008000

*r5=臨時解壓內(nèi)核Image的起始地址

*r6=處理器ID

*r7=體系結(jié)構(gòu)ID

*r8=參數(shù)列表0x30000100

*r9-r14=未使用

*/

@完成了解壓縮之后,由于內(nèi)核沒有解壓到正確的地址,最后必須通過代碼搬移來搬到指定的地址0x30008000。搬運過程中有

@可能會覆蓋掉現(xiàn)在運行的重定位代碼,所以必須將這段代碼搬運到安全的地方,

@這里搬運到的地址是解壓縮了的代碼的后面r5+r0的位置。

addr1,r5,r0@endofdecompressedkernel解壓內(nèi)核的結(jié)束地址

adrr2,reloc_start

ldrr3,LC1@LC1:.wordreloc_end-reloc_start表示reloc_start段代碼的大小

addr3,r2,r3

1:ldmiar2!,{r9-r14}@copyrelocationcode

stmiar1!,{r9-r14}

ldmiar2!,{r9-r14}

stmiar1!,{r9-r14}

cmpr2,r3

blo1b

blcache_clean_flush@清cache

ARM(addpc,r5,r0)@callrelocationcode跳轉(zhuǎn)到重定位代碼開始執(zhí)行

@在此處會調(diào)用重定位代碼reloc_start來將Image的代碼從緩沖區(qū)r5幫運到最終的目的地r4:0x30008000處

reloc_start:addr9,r5,r0@r9中存放的是臨時解壓內(nèi)核的末尾地址

subr9,r9,#128@不拷貝堆棧

movr1,r4@r1中存放的是目的地址0x30008000

1:

.rept4

ldmiar5!,{r0,r2,r3,r10-r14}@relocatekernel

stmiar1!,{r0,r2,r3,r10-r14}/*搬運內(nèi)核Image的過程*/

.endr

cmpr5,r9

blo1b

movsp,r1/*留出堆棧的位置*/

addsp,sp,#128@relocatethestack

call_kernel:blcache_clean_flush@清除cache

blcache_off@關(guān)閉cache

movr0,#0@mustbezero

movr1,r7@restorearchitecturenumber

movr2,r8@restoreatagspointer

@這里就是最終我們從zImage跳轉(zhuǎn)到Image的偉大一跳了,跳之前準(zhǔn)備好r0,r1,r2

movpc,r4@callkernel

到此kernel的第一階段zImage解壓縮階段已經(jīng)執(zhí)行完。

第二階段的在另外一篇中分析。



關(guān)鍵詞: 嵌入式arm平臺kernel啟

評論


技術(shù)專區(qū)

關(guān)閉