嵌入式 arm平臺kernel啟動第一階段匯編head.s分析
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
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í)行完。
第二階段的在另外一篇中分析。
評論