新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 嵌入式 arm平臺kernel啟動第一階段匯編head.s分析

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

        作者: 時間:2016-11-09 來源:網絡 收藏
        arm_linux內核生成過程:

        1.依據arch/arm/kernel/vmlinux.lds生成linux內核源碼根目錄下的vmlinux,這個vmlinux屬于未壓縮,帶調試信息、符號表的最初的內核,大小約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://www.104case.com/article/201611/317681.htm


        2.將上面的vmlinux去除調試信息、注釋、符號表等內容,生成arch/arm/boot/Image,這是不帶多余信息的linux內核,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.依據arch/arm/boot/compressed/vmlinux.lds將arch/arm/boot/compressed/目錄下的文件head.o、piggy.o、misc.o鏈接生成arch/arm/boot/compressed/vmlinux,這個vmlinux是經過壓縮且含有自解壓代碼的內核,大小約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去除調試信息、注釋、符號表等內容,生成arch/arm/boot/zImage大小約1.5MB;這已經是一個可以使用的linux內核映像文件了;
        命令:arm-linux-gnu-objcopy-Obinary-Sarch/arm/boot/compressed/vmlinuxarch/arm/boot/zImage


        7.將arch/arm/boot/zImage添加64Bytes的相關信息打包為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

        內核啟動分析:

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

        本文中涉及到的術語約定如下:

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

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

        zImage下載地址:即bootloader將zImage下載到目標板內存的某個地址或者nandread將zImage讀到內存的某個地址;

        zImage加載地址:由Linux的bootloader完成的將zImage搬移到目標板內存的某個位置所對應的地址值,默認值0x30008000。

        1、Linux內核啟動第一階段:內核解壓縮和重定位

        該階段是從u-boot引導進入內核執行的第一階段,我們知道u-boot引導內核啟動的最后一步是:通過一個函數指針thekernel()帶三個參數跳轉到內核(zImage)入口點開始執行,此時,u-boot的任務已經完成,控制權完全交給內核(zImage)。

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

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

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

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

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

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

        由上述zImage的生成過程我們可以知道,第一階段運行的內核映像實際就是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時,通常的內存分布圖:

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

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

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

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

        start:

        .typestart,#function/*uboot跳轉到內核后執行的第一條代碼*/

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

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

        .endr

        b1f@跳轉到后面的標號1處

        .word0x016f2818@輔助引導程序的幻數,用來判斷鏡像是否是zImage

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

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

        1:movr7,r1@savearchitectureID保存體系結構ID用r1保存

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

        mrsr2,cpsr@getcurrentmode得到當前模式

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

        bnenot_angel@如果不是處于用戶模式,就跳轉到not_angel標號處

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

        movr0,#0x17@angel_SWIreason_EnterSVC,向SWI中傳遞參數

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

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

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

        orrr2,r2,#0xc0@preventangelfromrunning關閉中斷

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

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

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

        .typeLC0,#object

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

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

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

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

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

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

        .word_got_end@ipGOT結束地址

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

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

        .section".stack","w"

        user_stack:.space4096

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

        .text

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

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

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

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

        beqnot_relocated@ifdeltaiszero,wearerunningattheaddresswewerelinkedat.

        @如果為0,則不用重定位了,直接跳轉到標號not_relocated處執行

        /*

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

        *r5–zImage基地址

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

        *ip–GOT結束地址

        */

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

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

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

        /*

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

        *r2–BSS起始地址

        *r3–BSS結束地址

        *sp–堆棧指針

        */

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

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

        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/*開啟指令和數據Cache,為了加快解壓速度*/

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

        movr1,sp@mallocspaceabovestack

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

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

        /*

        *檢查是否會覆蓋內核映像本身

        *r4=最終解壓后的內核首地址

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

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

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

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

        cmpr4,r2

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

        (2)zImage的起始地址大于vmlinux的目標起始地址加上vmlinux大小(4M)的地址,所以將zImage直接解壓到vmlinux的目標地址

        addr0,r4,#4096*1024@4MBlargestkernelsize

        cmpr0,r5

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

        @前兩種方案通常都不成立,不會跳轉到wont_overwrite標號處,會繼續走如下分支,其解壓后的內存分配示意圖如下:

        movr5,r2@decompressaftermallocspace

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

        movr3,r7

        bldecompress_kernel

        /進入decompress_kernel*/

        @decompress_kernel共有4個參數,解壓的內核地址、緩存區首地址、緩存區尾地址、和芯片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;/*保存緩存區首地址*/

        free_mem_ptr_end=free_mem_ptr_end_p;/*保存緩沖區結束地址*/

        __machine_arch_type=arch_id;

        arch_decomp_setup();

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

        putstr("UncompressingLinux...");

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

        putstr("done,bootingthekernel.n");

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

        }

        /從decompress_kernel函數返回*/

        addr0,r0,#127+128

        bicr0,r0,#127@alignthekernellength對齊內核長度

        /*

        *r0=解壓后內核長度

        *r1-r3=未使用

        *r4=真正內核執行地址0x30008000

        *r5=臨時解壓內核Image的起始地址

        *r6=處理器ID

        *r7=體系結構ID

        *r8=參數列表0x30000100

        *r9-r14=未使用

        */

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

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

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

        addr1,r5,r0@endofdecompressedkernel解壓內核的結束地址

        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跳轉到重定位代碼開始執行

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

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

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

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

        1:

        .rept4

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

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

        .endr

        cmpr5,r9

        blo1b

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

        addsp,sp,#128@relocatethestack

        call_kernel:blcache_clean_flush@清除cache

        blcache_off@關閉cache

        movr0,#0@mustbezero

        movr1,r7@restorearchitecturenumber

        movr2,r8@restoreatagspointer

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

        movpc,r4@callkernel

        到此kernel的第一階段zImage解壓縮階段已經執行完。

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



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 台山市| 长岭县| 永丰县| 同德县| 加查县| 信丰县| 朝阳市| 尼玛县| 平湖市| 公安县| 曲水县| 磴口县| 新丰县| 永平县| 延长县| 清河县| 沧源| 公主岭市| 桐庐县| 石台县| 滨海县| 抚远县| 普兰县| 盐亭县| 大渡口区| 郓城县| 鄂托克前旗| 乌拉特后旗| 和田县| 崇仁县| 金坛市| 工布江达县| 永新县| 富源县| 全椒县| 辉县市| 茂名市| 山阳县| 屏边| 且末县| 交城县|