<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > linux內(nèi)核啟動過程——基于S3C2410

          linux內(nèi)核啟動過程——基于S3C2410

          作者: 時間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
          本文以流行的Samsung公司的S3C2410,openmoko平臺和linux-2.6.24為例,介紹如何在ZIX嵌入式開發(fā)環(huán)境下探索linux內(nèi)核啟動過程。

          Linux內(nèi)核啟動一般由外部的bootloader引導(dǎo),也可以在內(nèi)核頭部嵌入一個loader,實際的應(yīng)用中這兩種方式都會經(jīng)常遇到。所以要了 解內(nèi)核啟動最開始的過程,必須對bootloader如何引導(dǎo)內(nèi)核有所熟悉。下面我們從u-boot加載linux內(nèi)核的代碼開始分析(關(guān)于u-boot 自身的啟動流程,請參考u-boot 啟動過程 —— 基于S3C2410)。

          本文引用地址:http://www.ex-cimer.com/article/201611/317999.htm

          在u-boot的do_bootm_linux函數(shù)里,實現(xiàn)了處理器架構(gòu)相關(guān)的linux內(nèi)核加載代碼,特別是tags傳遞。

          該函數(shù)中,在lib_arm/bootm.c的76行調(diào)用了getenv將bootargs環(huán)境變量保存在commandline

          char*commandline =getenv("bootargs");

          然后解析uImage文件頭,并且按照頭中的定義分解和加載uImage。所以這部分代碼的運行取決于uImage文件是如何生成的,本文不做過多敘述,可參考另文了解u-boot使用。接下來進行tags設(shè)置工作,分別調(diào)用了

          • setup_start_tag()
          • setup_memory_tag()
          • setup_commandline_tag()
          • setup_initrd_tag()
          • setup_end_tag()

          然后對TLB、cache等進行ivalid操作,這是通過在lib_arm/bootm.c的156行調(diào)用cleanup_before_linux()實現(xiàn),然后即可跳入從uImage中分解出來的內(nèi)核Image或zImage入口

          cleanup_before_linux (); theKernel (0, machid, bd->bi_boot_params); /* does not return */return;

          在s3c2410平臺上,該入口theKernel一般是物理地址0x30008000。如果我們使用zImage自解壓內(nèi)核映像,對應(yīng)的代碼正是 自解壓頭,位置在內(nèi)核源碼linux-2.6.24-moko-linuxbj的arch/arm/boot/compressed/start.S第 114行的start符號

          start: .type start,#function .rept 8 mov r0, r0 .endr b 1f .word 0x016f2818 @ Magic numbers to help the loader .word start @ absolute load/run zImage address .word _edata @ zImage end address 1: mov r7, r1 @ save architecture ID mov r8, r2 @ save atags pointer

          這也標志著u-boot將系統(tǒng)完全的交給了OS,bootloader生命終止。之后代碼在133行會讀取cpsr并判斷是否處理器處于supervisor模式——從u-boot進入kernel,系統(tǒng)已經(jīng)處于SVC32模式;而利用angel進入則處于user模式,還需要額外兩條指令。之后是再次確認中斷關(guān)閉,并完成cpsr寫入

          mrs r2, cpsr @ get current mode tst r2, #3 @ not user? bne not_angel mov r0, #0x17 @ angel_SWIreason_EnterSVC swi 0x123456 @ angel_SWI_ARM not_angel: mrs r2, cpsr @ turn off interrupts to orr r2, r2, #0xc0 @ prevent angel from running msr cpsr_c, r2

          然后在LC0地址處將分段信息導(dǎo)入r0-r6、ip、sp等寄存器,并檢查代碼是否運行在與鏈接時相同的目標地址,以決定是否進行處理。由于現(xiàn)在很少有人不使用loader和tags,將zImage燒寫到rom直接從0x0位置執(zhí)行,所以這個處理是必須的(但是zImage的頭現(xiàn)在也保留了不用loader也可啟動的能力)。arm架構(gòu)下自解壓頭一般是鏈接在0x0地址而被加載到0x30008000運行,所以要修正這個變化。涉及到

          • r5寄存器存放的zImage基地址
          • r6和r12(即ip寄存器)存放的got(global offset table)
          • r2和r3存放的bss段起止地址
          • sp棧指針地址

          很簡單,這些寄存器統(tǒng)統(tǒng)被加上一個你也能猜到的偏移地址 0x30008000。該地址是s3c2410相關(guān)的,其他的ARM處理器可以參考下表

          • PXA2xx是0xa0008000
          • IXP2x00和IXP4xx是0x00008000
          • Freescale i.MX31/37是0x80008000
          • TI davinci DM64xx是0x80008000
          • TI omap系列是0x80008000
          • AT91RM/SAM92xx系列是0x20008000
          • Cirrus EP93xx是0x00008000

          這些操作發(fā)生在代碼172行開始的地方,下面只粘貼一部分

          add r5, r5, r0 add r6, r6, r0 add ip, ip, r0

          后面在211行進行bss段的清零工作

          not_relocated: mov r0, #0 1: str r0, [r2], #4 @ clear bss str r0, [r2], #4 str r0, [r2], #4 str r0, [r2], #4 cmp r2, r3 blo 1b

          然后224行,打開cache,并為后面解壓縮設(shè)置64KB的臨時malloc空間

          bl cache_on mov r1, sp @ malloc space above stack add r2, sp, #0x10000 @ 64k max

          接下來238行進行檢查,確定內(nèi)核解壓縮后的Image目標地址是否會覆蓋到zImage頭,如果是則準備將zImage頭轉(zhuǎn)移到解壓出來的內(nèi)核后面

          cmp r4, r2 bhs wont_overwrite sub r3, sp, r5 @ > compressed kernel size add r0, r4, r3, lsl #2 @ allow for 4x expansion cmp r0, r5 bls wont_overwrite mov r5, r2 @ decompress after malloc space mov r0, r5 mov r3, r7 bl decompress_kernel

          真實情況——在大多數(shù)的應(yīng)用中,內(nèi)核編譯都會把壓縮的zImage和非壓縮的Image鏈接到同樣的地址,s3c2410平臺下即是0x30008000。這樣做的好處是,人們不用關(guān)心內(nèi)核是Image還是zImage,放到這個位置執(zhí)行就OK,所以在解壓縮后zImage頭必須為真正的內(nèi)核讓路。

          在250行解壓完畢,內(nèi)核長度返回值存放在r0寄存器里。在內(nèi)核末尾空出128字節(jié)的??臻g用,并且使其長度128字節(jié)對齊。

          add r0, r0, #127 + 128 @ alignment + stack bic r0, r0, #127 @ align the kernel length

          算出搬移代碼的參數(shù):計算內(nèi)核末尾地址并存放于r1寄存器,需要搬移代碼原來地址放在r2,需要搬移的長度放在r3。然后執(zhí)行搬移,并設(shè)置好sp指針指向新的棧(原來的棧也會被內(nèi)核覆蓋掉)

          add r1, r5, r0 @ end of decompressed kernel adr r2, reloc_start ldr r3, LC1 add r3, r2, r3 1: ldmia r2!, {r9 - r14} @ copy relocation code stmia r1!, {r9 - r14} ldmia r2!, {r9 - r14} stmia r1!, {r9 - r14} cmp r2, r3 blo 1b add sp, r1, #128 @ relocate the stack

          搬移完成后刷新cache,因為代碼地址變化了不能讓cache再命中被內(nèi)核覆蓋的老地址。然后跳轉(zhuǎn)到新的地址繼續(xù)執(zhí)行

          bl cache_clean_flush add pc, r5, r0 @ call relocation code

          注意——zImage在解壓后的搬移和跳轉(zhuǎn)會給gdb調(diào)試內(nèi)核帶來麻煩。因為用來調(diào)試的符號表是在編譯是生成的,并不知道以后會被搬移到何處去,只有在內(nèi)核解壓縮完成之后,根據(jù)計算出來的參數(shù)“告訴”調(diào)試器這個變化。以撰寫本文時使用的zImage為例,內(nèi)核自解壓頭重定向后,reloc_start地址由0x30008360變?yōu)?x30533e60。故我們要把vmlinux的符號表也相應(yīng)的從0x30008000后移到0x30533b00開始,這樣gdb就可以正確的對應(yīng)源代碼和機器指令。

          隨著頭部代碼移動到新的位置,不會再和內(nèi)核的目標地址沖突,可以開始內(nèi)核自身的搬移了。此時r0寄存器存放的是內(nèi)核長度(嚴格的說是長度外加128Byte的棧),r4存放的是內(nèi)核的目的地址0x30008000,r5是目前內(nèi)核存放地址,r6是CPU ID,r7是machine ID,r8是atags地址。代碼從501行開始

          reloc_start: add r9, r5, r0 sub r9, r9, #128 @ do not copy the stack debug_reloc_start mov r1, r4 1: .rept 4 ldmia r5!, {r0, r2, r3, r10 - r14} @ relocate kernel stmia r1!, {r0, r2, r3, r10 - r14} .endr cmp r5, r9 blo 1b add sp, r1, #128 @ relocate the stack

          接下來在516行清除并關(guān)閉cache,清零r0,將machine ID存入r1,atags指針存入r2,再跳入0x30008000執(zhí)行真正的內(nèi)核Image

          call_kernel: bl cache_clean_flush bl cache_off mov r0, #0 @ must be zero mov r1, r7 @ restore architecture number mov r2, r8 @ restore atags pointer mov pc, r4 @ call kernel

          zImage自解壓過程結(jié)束。

          從zImage頭跳轉(zhuǎn)進來,此時的狀態(tài)

          • MMU為off
          • D-cache為off
          • I-cache為dont care,on或off沒有關(guān)系
          • r0為0
          • r1為machine ID
          • r2為atags指針

          內(nèi)核代碼入口在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head.S文件的83行。首先進入SVC32模式,并查詢CPU ID,檢查合法性

          msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error p

          接著在87行進一步查詢machine ID并檢查合法性

          bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error a

          其中__lookup_processor_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的149行,該函數(shù)首將標號3的實際地址加載到r3,然后將編譯時生成的__proc_info_begin虛擬地址載入到r5,__proc_info_end虛擬地址載入到r6,標號3的虛擬地址載入到r7。由于adr偽指令和標號3的使用,以及__proc_info_begin等符號在linux-2.6.24-moko-linuxbj/arch/arm/kernel/vmlinux.lds而不是代碼中被定義,此處代碼不是非常直觀,想弄清楚代碼緣由的讀者請耐心閱讀這兩個文件和adr偽指令的說明。

          r3和r7分別存儲的是同一位置標號3的物理地址(由于沒有啟用mmu,所以當前肯定是物理地址)和虛擬地址,所以兒者相減即得到虛擬地址和物理地址之間的offset。利用此offset,將r5和r6中保存的虛擬地址轉(zhuǎn)變?yōu)槲锢淼刂?/p>__lookup_processor_type: adr r3, 3f ldmda r3, {r5 - r7} sub r3, r3, r7 @ get offset between virt&phys add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space

          然后從proc_info中讀出內(nèi)核編譯時寫入的processor ID和之前從cpsr中讀到的processor ID對比,查看代碼和CPU硬件是否匹配(想在arm920t上運行為cortex-a8編譯的內(nèi)核?不讓?。H绻幾g了多種處理器支持,如versatile板,則會循環(huán)每種type依次檢驗,如果硬件讀出的ID在內(nèi)核中找不到匹配,則r5置0返回

          1: ldmia r5, {r3, r4} @ value, mask and r4, r4, r9 @ mask wanted bits teq r3, r4 beq 2f add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) cmp r5, r6 blo 1b mov r5, #0 @ unknown processor 2: mov pc, lr

          __lookup_machine_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的197行,編碼方法與檢查processor ID完全一樣,請參考前段

          __lookup_machine_type: adr r3, 3b ldmia r3, {r4, r5, r6} sub r3, r3, r4 @ get offset between virt&phys add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type teq r3, r1 @ matches loader number? beq 2f @ found add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc cmp r5, r6 blo 1b mov r5, #0 @ unknown machine 2: mov pc, lr

          代碼回到head.S第92行,檢查atags合法性,然后創(chuàng)建初始頁表

          bl __vet_atags bl __create_page_tables

          創(chuàng)建頁表的代碼在218行,首先將內(nèi)核起始地址-0x4000到內(nèi)核起始地址之間的16K存儲器清0

          __create_page_tables: pgtbl r4 @ page table address /* * Clear the 16K level 1 swapper page table */ mov r0, r4 mov r3, #0 add r6, r0, #0x4000 1: str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 teq r0, r6 bne 1b

          然后在234行將proc_info中的mmu_flags加載到r7

          ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

          在242行將PC指針右移20位,得到內(nèi)核第一個1MB空間的段地址存入r6,在s3c2410平臺該值是0x300。接著根據(jù)此值存入映射標識

          mov r6, pc, lsr #20 @ start of kernel section orr r3, r7, r6, lsl #20 @ flags + kernel base str r3, [r4, r6, lsl #2] @ identity mapping

          完成頁表設(shè)置后回到102行,為打開虛擬地址映射作準備。設(shè)置sp指針,函數(shù)返回地址lr指向__enable_mmu,并跳轉(zhuǎn)到linux-2.6.24-moko-linuxbj/arch/arm/mm/proc-arm920.S的386行,清除I-cache、D-cache、write buffer和TLB

          __arm920_setup: mov r0, #0 mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4 mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4 #ifdef CONFIG_MMU mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4 #endif

          然后返回head.S的158行,加載domain和頁表,跳轉(zhuǎn)到__turn_mmu_on

          __enable_mmu: #ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #CR_A #else bic r0, r0, #CR_A #endif #ifdef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CR_C #endif #ifdef CONFIG_CPU_BPREDICT_DISABLE bic r0, r0, #CR_Z #endif #ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I #endif mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | / domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | / domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | / domain_val(DOMAIN_IO, DOMAIN_CLIENT)) mcr p15, 0, r5, c3, c0, 0 @ load domain access register mcr p15, 0, r4, c2, c0, 0 @ load page table pointer b __turn_mmu_on

          在194行把mmu使能位寫入mmu,激活虛擬地址。然后將原來保存在sp中的地址載入pc,跳轉(zhuǎn)到head-common.S的__mmap_switched,至此代碼進入虛擬地址的世界

          mov r0, r0 mcr p15, 0, r0, c1, c0, 0 @ write control reg mrc p15, 0, r3, c0, c0, 0 @ read id reg mov r3, r3 mov r3, r3 mov pc, r13

          在head-common.S的37行開始清除內(nèi)核bss段,processor ID保存在r9,machine ID報存在r1,atags地址保存在r2,并將控制寄存器保存到r7定義的內(nèi)存地址。接下來跳入linux-2.6.24-moko-linuxbj/init/main.c的507行,start_kernel函數(shù)。這里只粘貼部分代碼

          __mmap_switched: adr r3, __switch_data + 4 ldmia r3!, {r4, r5, r6, r7} cmp r4, r5 @ Copy data segment if needed 1: cmpne r5, r6 ldrne fp, [r4], #4 strne fp, [r5], #4 bne 1b

          在main.c第507行,是硬件無關(guān)的C初始化代碼

          asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __stop___param[]; smp_setup_processor_id();

          s3c2410平臺linux-2.6.24內(nèi)核早期的匯編初始化到這里就結(jié)束了

          調(diào)試技巧:

          利用gdb調(diào)試內(nèi)核Image啟動流程是一種很好的分析手段,要使用好這種手段有一個問題需要解決——內(nèi)核地址映射問題

          1. 代碼執(zhí)行的早期是處于mmu關(guān)閉的狀態(tài)下,軟件直接使用硬件相關(guān)的物理地址(s3c2410的ram從0x30000000開始);
          2. 后來啟用mmu并建立映射之后,軟件使用虛擬地址。在3G用戶空間配置下(大多數(shù)32位嵌入式系統(tǒng)都是此配置),內(nèi)核使用的PAGE_OFFSET為0xc0000000,與硬件無關(guān),核心虛擬地址變?yōu)閺?xc0000000開始;

          調(diào)試器無法自動接受這樣的地址轉(zhuǎn)變,需要使用上文介紹的訣竅,手工“告訴”調(diào)試器該怎么做。

          對內(nèi)核編譯產(chǎn)生的vmlinux文件使用objdump工具

          $ /usr/local/linuxbj/eabi-glibc/arm/bin/arm-linuxbj-linux-gnueabi-objdump -t vmlinux|more vmlinux: file format elf32-littlearm SYMBOL TABLE: c0008000 l d .text.head 00000000 .text.head c0008240 l d .init 00000000 .init c0027000 l d .text 00000000 .text c03377ec l d .notes 00000000 .notes c0338000 l d __ksymtab 00000000 __ksymtab c033ca40 l d __ksymtab_gpl 00000000 __ksymtab_gpl c033e2d0 l d __ksymtab_gpl_future 00000000 __ksymtab_gpl_future c033e2e8 l d __ksymtab_strings 00000000 __ksymtab_strings c034c9ec l d __param 00000000 __param c034e000 l d .data 00000000 .data c0373e20 l d .bss 00000000 .bss

          可以看到內(nèi)核符號表的.text鏈接虛擬地址是0xc0027000,所以在mmu處于關(guān)閉的階段中,應(yīng)該將內(nèi)核符號表在調(diào)試器里加載到0x30027000地址。使得head.S入口.text.head正好是0x30008000,與實際的內(nèi)存一致。



          評論


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

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();