1. 程式人生 > >ARM架構核心執行過程

ARM架構核心執行過程

     在瞭解這些之前我們首先需要了解幾個名詞,這些名詞定義在/Documentation/arm/Porting裡面,這裡首先提到其中的幾個,其餘幾個會在後面kernel的執行過程中講述:

     1)ZTEXTADDR  boot.img執行時候zImage的起始地址,即kernel解壓程式碼的地址。這裡沒有虛擬地址的概念,因為沒有開啟MMU,所以這個地址是實體記憶體的地址。解壓程式碼不一定需要載入RAM才能執行,在FLASH或者其他可定址的媒體上都可以執行。

     2)ZBSSADDR  解壓程式碼的BSS段的地址,這裡也是實體地址。

     3)ZRELADDR  這個是kernel解壓以後存放的記憶體實體地址,解壓程式碼執行完成以後會跳到這個地址執行kernel的啟動,這個地址和後面kernel執行時候的虛擬地址滿足:__virt_to_phys(TEXTADDR) = ZRELADDR。

     4)INITRD_PHYS  Initial Ram Disk存放在記憶體中的實體地址,這裡就是我們的ramdisk.img。

     5)INITRD_VIRT  Initial Ram Disk執行時候虛擬地址。

     6)PARAMS_PHYS 核心啟動的初始化引數在記憶體上的實體地址。

     下面我們首先來看看boot.img的構造,瞭解其中的內容對我們瞭解kernel的啟動過程是很有幫助的。首先來看看Makefile是如何產生我們的boot.img的:

      out/host/linux-x86/bin/mkbootimg-msm7627_ffa  --kernel out/target/product/msm7627_ffa/kernel --ramdisk out/target/product/msm7627_ffa/ramdisk.img --cmdline "mem=203M console=ttyMSM2,115200n8 androidboot.hardware=qcom" --output out/target/product/msm7627_ffa/boot.img

      根據上面的命令我們可以首先看看mkbootimg-msm7627ffa這個工具的原始檔:system/core/mkbootimg.c。看完之後我們就能很清晰地看到boot.img的內部構造,它是由boot header /kernel  /ramdisk /second stage構成的,其中前3項是必須的,最後一項是可選的。

[c-sharp] view plaincopyprint?
  1. /* 
  2. ** +-----------------+  
  3. ** | boot header     | 1 page 
  4. ** +-----------------+ 
  5. ** | kernel          | n pages  
     
  6. ** +-----------------+ 
  7. ** | ramdisk         | m pages   
  8. ** +-----------------+ 
  9. ** | second stage    | o pages 
  10. ** +-----------------+ 
  11. ** 
  12. ** n = (kernel_size + page_size - 1) / page_size 
  13. ** m = (ramdisk_size + page_size - 1) / page_size 
  14. ** o = (second_size + page_size - 1) / page_size 
  15. ** 
  16. ** 0. all entities are page_size aligned in flash 
  17. ** 1. kernel and ramdisk are required (size != 0) 
  18. ** 2. second is optional (second_size == 0 -> no second) 
  19. ** 3. load each element (kernel, ramdisk, second) at 
  20. **    the specified physical address (kernel_addr, etc) 
  21. ** 4. prepare tags at tag_addr.  kernel_args[] is 
  22. **    appended to the kernel commandline in the tags. 
  23. ** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr 
  24. ** 6. if second_size != 0: jump to second_addr 
  25. **    else: jump to kernel_addr 
  26. */
 

      關於boot header這個資料結構我們需要重點注意,在這裡我們關注其中幾個比較重要的值,這些值定義在boot/boardconfig.h裡面,不同的晶片對應vendor下不同的boardconfig,在這裡我們的值分別是(分別是kernel/ramdis/tags載入ram的實體地址):

[c-sharp] view plaincopyprint?
  1. #define PHYSICAL_DRAM_BASE   0x00200000  
  2. #define KERNEL_ADDR          (PHYSICAL_DRAM_BASE + 0x00008000)  
  3. #define RAMDISK_ADDR         (PHYSICAL_DRAM_BASE + 0x01000000)  
  4. #define TAGS_ADDR            (PHYSICAL_DRAM_BASE + 0x00000100)  
  5. #define NEWTAGS_ADDR         (PHYSICAL_DRAM_BASE + 0x00004000)
 

      上面這些值分別和我們開篇時候提到的那幾個名詞相對應,比如kernel_addr就是ZTEXTADDR,RAMDISK_ADDR就是INITRD_PHYS,而TAGS_ADDR就是PARAMS_PHYS。bootloader會從boot.img的分割槽中將kernel和ramdisk分別讀入RAM上面定義的地址中,然後就會跳到ZTEXTADDR開始執行。

      基本瞭解boot.img的內容之後我們來分別看看裡面的ramdisk.img和kernel又是如何產生的,以及其包含的內容。從簡單的說起,我們先看看ramdisk.img,這裡首先要強調一下這個ramdisk.img在arm linux中的作用。它在kernel啟動過程中充當著第一階段的檔案系統,是一個CPIO格式打成的包。通俗上來講他就是我們將生成的root目錄,用CPIO方式進行了打包,然後在kernel啟動過程中會被mount作為檔案系統,當kernel啟動完成以後會執行init,然後將system.img再mount進來作為Android的檔案系統。在這裡稍微解釋下這個mount的概念,所謂mount實際上就是告訴linux虛擬檔案系統它的根目錄在哪,就是說我這個虛擬檔案系統需要操作的那塊區域在哪,比如說ramdisk實際上是我們在記憶體中的一塊區域,把它作為檔案系統的意思實際上就是告訴虛擬檔案系統你的根目錄就在我這裡,我的起始地址賦給你,你以後就能對我進行操作了。實際上我們也可以使用rom上的一塊區域作為根檔案系統,但是rom相對ram慢,所以這裡使用ramdisk。然後我們在把system.img mount到ramdisk的system目錄,實際上就是將system.img的地址給了虛擬檔案系統,然後虛擬檔案系統訪問system目錄的時候會重新定位到對system.img的訪問。我們可以看看makefile是如何生成它的:

      out/host/linux-x86/bin/mkbootfs  out/target/product/msm7627_ffa/root | out/host/linux-x86/bin/minigzip > out/target/product/msm7627_ffa/ramdisk.img    

      下面我們來看看kernel產生的過程,老方法,從Makefile開始/arch/arm/boot/Makefile ~

[c-sharp] view plaincopyprint?
  1. $(obj)/Image: vmlinux FORCE  
  2.     $(call if_changed,objcopy)  
  3.     @echo '  Kernel: [email protected] is ready'
  4. $(obj)/compressed/vmlinux: $(obj)/Image FORCE  
  5.     $(Q)$(MAKE) $(build)=$(obj)/compressed [email protected]  
  6. $(obj)/zImage:  $(obj)/compressed/vmlinux FORCE  
  7.     $(call if_changed,objcopy)  
  8.     @echo '  Kernel: [email protected] is ready'
      

      我們分解地來看各個步驟,第一個是將vmlinux經過objcopy後生成一個未經壓縮的raw binary(Image 4M左右),這裡的vmlinux是我們編譯連結以後生成的vmlinx,大概60多M。這裡稍微說一下這個objcopy,在啟動的時候ELF格式是沒法執行的,ELF格式的解析是在kernel啟動以後有了作業系統之後才能進行的。因為雖然我們編出的img雖然被編成ELF格式,但要想啟動起來必須將其轉化成原始的二進位制格式,我們可以多照著man objcopy和OBJCOPYFLAGS    :=-O binary -R .note -R .note.gnu.build-id -R .comment -S(arch/arm/Makefile)來看看這些objcopy具體做了什麼事情 ~

      得到Image以後,再將這個Image跟解壓程式碼合成一個vmlinux,具體的我們可以看看arch/arm/boot/compressed/Makefile:

[c-sharp] view plaincopyprint?
  1. $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o /  
  2.         $(addprefix $(obj)/, $(OBJS)) FORCE  
  3.     $(call if_changed,ld)  
  4.     @:  
  5. $(obj)/piggy.gz: $(obj)/../Image FORCE  
  6.     $(call if_changed,gzip)  
  7. $(obj)/piggy.o:  $(obj)/piggy.gz FORCE  
 

      從這裡我們就可以看出來實際上這個vmlinux就是將Image壓縮以後根據vmlinux.lds與解壓程式碼head.o和misc.o連結以後生成的一個elf,而且用readelf或者objdump可以很明顯地看到解壓程式碼是PIC的,所有的虛擬地址都是相對的,沒有絕對地址。這裡的vmlinx.lds可以對照著後面的head.s稍微看一下~得到壓縮以後的vmlinx以後再將這個vmlinx經過objcopy以後就得到我們的zImage了,然後拷貝到out目錄下就是我們的kernel了~~

      在這裡要強調幾個地址,這些地址定義在arch/arm/mach-msm/makefile.boot裡面,被arch/arm/boot/Makefile呼叫,其中zreladdr-y就是我們的kernel被解壓以後要釋放的地址了,解壓程式碼跑完以後就會跳到這個地址來執行kernel的啟動。不過這裡還有其他兩個PHYS,跟前面定義在boardconfig.h裡面的值重複了,不知道這兩個值在這裡定義跟前面的值是一種什麼關係???

       好啦,講到這裡我們基本就知道boot.img的構成了,下面我們就從解壓的程式碼開始看看arm linux kernel啟動的一個過程,這個解壓的source就是/arch/arm/boot/compressed/head.S。要看懂這個彙編需要了解GNU ASM以及ARM彙編指令,ARM指令就不說了,ARM RVCT裡面的文件有得下,至於GNU ASM,不需要訊息瞭解的話主要是看一下一些偽指令的含義(http://sources.redhat.com/binutils/docs-2.12/as.info/Pseudo-Ops.html#Pseudo%20Ops)

       那麼我們現在就開始分析這個解壓的過程:

       1)bootloader會傳遞2個引數過來,分別是r1=architecture ID, r2=atags pointer。head.S從哪部分開始執行呢,這個我們可以看看vmlinx.lds:

[c-sharp] view plaincopyprint?
  1. ENTRY(_start)  
  2. SECTIONS  
  3. {  
  4.   . = 0;  
  5.   _text = .;  
  6.   .text : {   
  7.     _start = .;  
  8.     *(.start)  
  9.     *(.text)  
  10.     *(.text.*)  
  11.     *(.fixup)  
  12.     *(.gnu.warning)  
  13.     *(.rodata)  
  14.     *(.rodata.*)  
  15.     *(.glue_7)  
  16.     *(.glue_7t)  
  17.     *(.piggydata)  
  18.     . = ALIGN(4);  
  19.   }  
 

      可以看到我們最開始的section就是.start,所以我們是從start段開始執行的。ELF對程式的入口地址是有定義的,這可以參照*.lds的語法規則裡面有描述,分別是GNU LD的-E ---> *.lds裡面的ENTRY定義  ---> start Symbol  ---> .text section --->0。在這裡是沒有這些判斷的,因為還沒有作業系統,bootloader會直接跳到這個start的地址開始執行。

       在這裡稍微帶一句,如果覺得head.S看的不太舒服的話,比如有些跳轉並不知道意思,可以直接objdump vmlinx來看,dump出來的彙編的流程就比較清晰了。

[c-sharp] view plaincopyprint?
  1. 1:      mov r7, r1          @ save architecture ID  
  2.         mov r8, r2          @ save atags pointer  
  3. #ifndef __ARM_ARCH_2__
  4. /* 
  5.          * Booting from Angel - need to enter SVC mode and disable 
  6.          * FIQs/IRQs (numeric definitions from angel arm.h source). 
  7.          * We only do this if we were in user mode on entry. 
  8.          */
  9.         mrs r2, cpsr        @ get current mode  
  10.         tst r2, #3          @ not user?  
  11.         bne not_angel       @ 如果不是  
  12.         mov r0, #0x17       @ angel_SWIreason_EnterSVC  
  13.         swi 0x123456        @ angel_SWI_ARM  
  14. not_angel:  
  15.         mrs r2, cpsr        @ turn off interrupts to  
  16.         orr r2, r2, #0xc0       @ prevent angel from running  
  17.         msr cpsr_c, r2  
 

       上面首先儲存r1和r2的值,然後進入超級使用者模式,並關閉中斷。

[c-sharp] view plaincopyprint?
  1. .text  
  2. adr r0, LC0  
  3. ldmia   r0, {r1, r2, r3, r4, r5, r6, ip, sp}  
  4. subs    r0, r0, r1      @ calculate the delta offset  
  5.                 @ if delta is zero, we are  
  6. beq not_relocated       @ running at the address we  
  7.                 @ were linked at.  
 

      這裡首先判斷LC0當前的執行地址和連結地址是否一樣,如果一樣就不需要重定位,如果不一樣則需要進行重定位。這裡肯定是不相等的,因為我們可以通過objdump看到LC0的地址是0x00000138,是一個相對地址,然後adr r0, LC0 實際上就是將LC0當前的執行地址,而我們直接跳到ZTEXTADDR跑的,實際上PC裡面現在的地址肯定是0x00208000以後的一個值,adr r0, LC0編譯之後實際上為addr0, pc, #208,這個208就是LC0到.text段頭部的偏移。

[c-sharp] view plaincopyprint?
  1. add r5, r5, r0  
  2. add r6, r6, r0  
  3. add ip, ip, r0  
 

      然後就是重定位了,即都加上一個偏移,經過重定位以後就都是絕對地址了。

[c-sharp] view plaincopyprint?
  1. not_relocated:  mov r0, #0  
  2. 1:      str r0, [r2], #4        @ clear bss  
  3.         str r0, [r2], #4  
  4.         str r0, [r2], #4  
  5.         str r0, [r2], #4  
  6.         cmp r2, r3  
  7.         blo 1b  
  8. /* 
  9.          * The C runtime environment should now be setup 
  10.          * sufficiently.  Turn the cache on, set up some 
  11.          * pointers, and start decompressing. 
  12.          */
  13.         bl  cache_on  
 

      重定位完成以後開啟cache,具體這個開啟cache的過程咱沒仔細研究過,大致過程是先從C0裡面讀到processor ID,然後根據ID來進行cache_on。

[c-sharp] view plaincopyprint?
  1. mov r1, sp          @ malloc space above stack  
  2. add r2, sp, #0x10000    @ 64k max  
 

       解壓的過程首先是在堆疊之上申請一個空間

[c-sharp] view plaincopyprint?
  1. /* 
  2.  * Check to see if we will overwrite ourselves. 
  3.  *   r4 = final kernel address 
  4.  *   r5 = start of this image 
  5.  *   r2 = end of malloc space (and therefore this image) 
  6.  * We basically want: 
  7.  *   r4 >= r2 -> OK 
  8.  *   r4 + image length <= r5 -> OK 
  9.  */
  10.         cmp r4, r2  
  11.         bhs wont_overwrite  
  12.         sub r3, sp, r5      @ > compressed kernel size  
  13.         add r0, r4, r3, lsl #2  @ allow for 4x expansion  
  14.         cmp r0, r5  
  15.         bls wont_overwrite  
  16.         mov r5, r2          @ decompress after malloc space  
  17.         mov r0, r5  
  18.         mov r3, r7  
  19.         bl  decompress_kernel  
  20.         add r0, r0, #127 + 128  @ alignment + stack  
  21.         bic r0, r0, #127        @ align the kernel length  
 

        這個過程是判斷我們解壓出的vmlinx會不會覆蓋原來的zImage,這裡的final kernel address就是解壓後的kernel要存放的地址,而start of this image則是zImage在記憶體中的地址。根據我們前面的分析,現在這兩個地址是重複的,即都是0x00208000。同樣r2是我們申請的一段記憶體空間,因為他是在sp上申請的,而根據vmlinx.lds我們知道stack實際上處與vmlinx的最上面,所以r4>=r2是不可能的,這裡首先計算zImage的大小,然後判斷r4+r3是不是比r5小,很明顯r4和r5的值是一樣的,所以這裡先將r2的值賦給r0,經kernel先解壓到s申請的記憶體空間上面,具體的解壓過程就不描述了,定義在misc.c裡面。(這裡我所說的上面是指記憶體地址的高地址,預設載入的時候從低地址往高地址寫,所以從記憶體低地址開始執行,stack處於最後面,所以成說是最上面)

[c-sharp] view plaincopyprint?
  1. * r0     = decompressed kernel length  
  2. * r1-r3  = unused  
  3. * r4     = kernel execution address  
  4. * r5     = decompressed kernel start  
  5. * r6     = processor ID  
  6. * r7     = architecture ID  
  7. * r8     = atags pointer  
  8. * r9-r14 = corrupted  
  9. */  
  10.        add r1, r5, r0      @ end of decompressed kernel  
  11.        adr r2, reloc_start  
  12.        ldr r3, LC1  
  13.        add r3, r2, r3  
  14. :      ldmia   r2!, {r9 - r14}     @ copy relocation code  
  15.        stmia   r1!, {r9 - r14}  
  16.        ldmia   r2!, {r9 - r14}  
  17.        stmia   r1!, {r9 - r14}  
  18.        cmp r2, r3  
  19.        blo 1b  
  20.        add sp, r1, #128        @ relocate the stack  
  21.        bl  cache_clean_flush  
  22.        add pc, r5, r0      @ call relocation code  
 

      因為沒有將kernel解壓在要求的地址,所以必須重定向,說穿了就是要將解壓的kernel拷貝到正確的地址,因為正確的地址與zImage的地址是重合的,而要拷貝我們又要執行zImage的重定位程式碼,所以這裡首先將重定位程式碼reloc_start拷貝到vmlinx上面,然後再將vmlinx拷貝到正確的地址並覆蓋掉zImage。這裡首先計算出解壓後的vmlinux的高地址放在r1裡面,r2存放著重定位程式碼的首地址,r3存放著重定位程式碼的size,這樣通過拷貝就將reloc_start移動到vmlinx後面去了,然後跳轉到重定位程式碼開始執行。

[c-sharp] view plaincopyprint?
  1. /* 
  2.  * All code following this line is relocatable.  It is relocated by 
  3.  * the above code to the end of the decompressed kernel image and 
  4.  * executed there.  During this time, we have no stacks. 
  5.  * 
  6.  * r0     = decompressed kernel length 
  7.  * r1-r3  = unused 
  8.  * r4     = kernel execution address 
  9.  * r5     = decompressed kernel start 
  10.  * r6     = processor ID 
  11.  * r7     = architecture ID 
  12.  * r8     = atags pointer 
  13.  * r9-r14 = corrupted 
  14.  */
  15.         .align  5  
  16. reloc_start:    add r9, r5, r0  
  17.         sub r9, r9, #128        @ do not copy the stack  
  18.         debug_reloc_start  
  19.         mov r1, r4  
  20. 1:  
  21.         .rept   4  
  22.         ldmia   r5!, {r0, r2, r3, r10 - r14}    @ relocate kernel  
  23.         stmia   r1!, {r0, r2, r3, r10 - r14}  
  24.         .endr  
  25.         cmp r5, r9  
  26.         blo 1b  
  27.         add sp, r1, #128        @ relocate the stack  
  28.         debug_reloc_end  
  29. call_kernel:    bl  cache_clean_flush  
  30.         bl  cache_off  
  31.         mov r0, #0          @ must be zero  
  32.         mov r1, r7          @ restore architecture number  
  33.         mov r2, r8          @ restore atags pointer  
  34.         mov pc, r4          @ call kernel  
 

       這裡就是將vmlinx拷貝到正確的地址了,拷貝到正確的位置以後,就將kernel的首地址賦給PC,然後就跳轉到真正kernel啟動的過程~~

      最後我們來總結一下一個基本的過程:

      1)當bootloader要從分割槽中資料讀到記憶體中來的時候,這裡涉及最重要的兩個地址,一個就是ZTEXTADDR還有一個是INITRD_PHYS。不管用什麼方式來生成IMG都要讓bootloader有方法知道這些引數,不然就不知道應該將資料從FLASH讀入以後放在什麼地方,下一步也不知道從哪個地方開始執行了;

      2)bootloader將IMG載入RAM以後,並跳到zImage的地址開始解壓的時候,這裡就涉及到另外一個重要的引數,那就是ZRELADDR,就是解壓後的kernel應該放在哪。這個引數一般都是arch/arm/mach-xxx下面的Makefile.boot來提供的;

      3)另外現在解壓的程式碼head.S和misc.c一般都會以PIC的方式來編譯,這樣載入RAM在任何地方都可以執行,這裡涉及到兩次衝定位的過程,基本上這個重定位的過程在ARM上都是差不多一樣的。

   寫這個總結的時候咱的心情是沉重的,因為還有好多東西沒弄明白。。。感嘆自己的知識還是淺薄得很,前途錢途漫漫阿~~不過基本脈絡是清楚的,具體的細節只能留在以後有時間再啃了。這裡的第二部分啟動流程指的是解壓後kernel開始執行的一部分程式碼,這部分程式碼和ARM體系結構是緊密聯絡在一起的,所以最好是將ARM ARCHITECTURE REFERENCE MANUL仔細讀讀,尤其裡面關於控制暫存器啊,MMU方面的內容~

      前面說過解壓以後,程式碼會跳到解壓完成以後的vmlinux開始執行,具體從什麼地方開始執行我們可以看看生成的vmlinux.lds(arch/arm/kernel/)這個檔案:

[c-sharp] view plaincopyprint?
  1. OUTPUT_ARCH(arm)  
  2. ENTRY(stext)  
  3. jiffies = jiffies_64;  
  4. SECTIONS  
  5. {  
  6.  . = 0x80000000 + 0x00008000;  
  7.  .text.head : {   
  8.   _stext = .;  
  9.   _sinittext = .;  
  10.   *(.text.h  

      很明顯我們的vmlinx最開頭的section是.text.head,這裡我們不能看ENTRY的內容,以為這時候我們沒有作業系統,根本不知道如何來解析這裡的入口地址,我們只能來分析他的section(不過一般來說這裡的ENTRY和我們從seciton分析的結果是一樣的),這裡的.text.head section我們很容易就能在arch/arm/kernel/head.S裡面找到,而且它裡面的第一個符號就是我們的stext:

[c-sharp] view plaincopyprint?
  1. .section ".text.head""ax"
  2. Y(stext)  
  3. msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode  
  4.                     @ and irqs disabled  
  5. mrc p15, 0, r9, c0, c0      @ get processor id  
  6. bl  __lookup_processor_type     @ r5=procinfo r9=cpuid  
 

      這裡的ENTRY這個巨集實際我們可以在include/linux/linkage.h裡面找到,可以看到他實際上就是宣告一個GLOBAL Symbol,後面的ENDPROC和END唯一的區別是前面的聲明瞭一個函式,可以在c裡面被呼叫。

[c-sharp] view plaincopyprint?
  1. #ifndef ENTRY  
  2. #define ENTRY(name) /
  3.   .globl name; /  
  4.   ALIGN; /  
  5.   name:  
  6. #endif  
  7. #ifndef WEAK  
  8. #define WEAK(name)     /
  9.     .weak name;    /  
  10.     name:  
  11. #endif  
  12. #ifndef END  
  13. #define END(name) /
  14.   .size name, .-name  
  15. #endif
  16. /* If symbol 'name' is treated as a subroutine (gets called, and returns) 
  17.  * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of 
  18.  * static analysis tools such as stack depth analyzer. 
  19.  */ 
  20. #ifndef ENDPROC  
  21. #define ENDPROC(name) /
  22.   .type name, @function; /  
  23.   END(name)  
  24. #endif
 

      找到了vmlinux的起始程式碼我們就來進行分析了,先總體概括一下這部分程式碼所完成的功能,head.S會首先檢查proc和arch以及atag的有效性,然後會建立初始化頁表,並進行CPU必要的處理以後開啟MMU,並跳轉到start_kernel這個symbol開始執行後面的C程式碼。這裡有很多變數都是我們進行kernel移植時需要特別注意的,下面會一一講到。

      在這裡我們首先看看這段彙編開始跑的時候的暫存器資訊,這裡的暫存器內容實際上是同bootloader跳轉到解壓程式碼是一樣的,就是r1=arch  r2=atag addr。下面我們就具體來看看這個head.S跑的過程:

[c-sharp] view plaincopyprint?
  1. msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode  
  2.                     @ and irqs disabled  
  3. mrc p15, 0, r9, c0, c0      @ get processor id  
 

      首先進入SVC模式並關閉所有中斷,並從arm協處理器裡面讀到CPU ID,這裡的CPU主要是指arm架構相關的CPU型號,比如ARM9,ARM11等等。

[c-sharp] view plaincopyprint?  

       然後跳轉到__lookup_processor_type,這個函式定義在head-common.S裡面,這裡的bl指令會儲存當前的pc在lr裡面,最後__lookup_processor_type會從這個函式返回,我們具體看看這個函式:      

[c-sharp] view plaincopyprint?
  1. __lookup_processor_type:  
  2.     adr r3, 3f  
  3.     ldmda   r3, {r5 - r7}  
  4.     sub r3, r3, r7          @ get offset between virt&phys  
  5.     add r5, r5, r3          @ convert virt addresses to  
  6.     add r6, r6, r3          @ physical address space  
  7. 1:  ldmia   r5, {r3, r4}            @ value, mask  
  8.     and r4, r4, r9          @ mask wanted bits  
  9.     teq r3, r4  
  10.     beq 2f  
  11.     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)  
  12.     cmp r5, r6  
  13.     blo 1b  
  14.     mov r5, #0              @ unknown processor  
  15. 2:  mov pc, lr  
  16. ENDPROC(__lookup_processor_type)  
 

       他這裡的執行過程其實比較簡單就是在__proc_info_begin和__proc_info_end這個段裡面裡面去讀取我們註冊在裡面的proc_info_list這個結構體,這個結構體的定義在arch/arm/include/asm/procinfo.h,具體實現根據你使用的cpu的架構在arch/arm/mm/裡面找到具體的實現,這裡我們使用的ARM11是proc-v6.S,我們可以看看這個結構體:

[c-sharp] view plaincopyprint?
  1. .section ".proc.info.init", #alloc, #execinstr  
  2. /*  
  3.  * Match any ARMv6 processor core. 
  4.  */
  5. .type   __v6_proc_info, #object
  6. _proc_info:  
  7. .long   0x0007b000  
  8. .long   0x0007f000  
  9. .long   PMD_TYPE_SECT | /  
  10.     PMD_SECT_BUFFERABLE | /          
  11.     PMD_SECT_CACHEABLE | /  
  12.     PMD_SECT_AP_WRITE | /  
  13.     PMD_SECT_AP_READ  
  14. .long   PMD_TYPE_SECT | /  
  15.     PMD_SECT_XN | /  
  16.     PMD_SECT_AP_WRITE | /  
  17.     PMD_SECT_AP_READ  
  18. b   __v6_setup  
  19. .long   cpu_arch_name  
  20. .long   cpu_elf_name  
  21. .long   HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA  
  22. .long   cpu_v6_name  
  23. .long   v6_processor_functions   
  24. .long   v6wbi_tlb_fns  
  25. .long   v6_user_fns  
  26. .long   v6_cache_fns  
  27. .size   __v6_proc_info, . - __v6_proc_info  
 

       對著.h我們就知道各個成員變數的含義了,他這裡lookup的過程實際上是先求出這個proc_info_list的實際實體地址,並將其內容讀出,然後將其中的mask也就是我們這裡的0x007f000與暫存器與之後與0x007b00進行比較,如果一樣的話呢就校驗成功了,如果不一樣呢就會讀下一個proc_info的資訊,因為proc一般都是隻有一個的,所以這裡一般不會迴圈,如果檢測正確暫存器就會將正確的proc_info_list的實體地址賦給暫存器,如果檢測不到就會將暫存器值賦0,然後通過LR返回。

[c-sharp] view plaincopyprint?
  1. bl  __lookup_machine_type       @ r5=machinfo  
  2. movs    r8, r5              @ invalid machine (r5=0)?        
  3. beq __error_a           @ yes, error 'a'
 

       檢測完proc_info_list以後就開始檢測machine_type了,這個函式的實現也在head-common.S裡面,我們看看它具體的實現:

[c-sharp] view plaincopyprint?
  1. __lookup_machine_type:  
  2.     adr r3, 3b  
  3.     ldmia   r3, {r4, r5, r6}  
  4.     sub r3, r3, r4          @ get offset between virt&phys   
  5.     add r5, r5, r3          @ convert virt addresses to      
  6.     add r6, r6, r3          @ physical address space         
  7. 1:  ldr r3, [r5, #MACHINFO_TYPE]    @ get machine type  
  8.     teq r3, r1              @ matches loader number?         
  9.     beq 2f              @ found      
  10.     add r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc  
  11.     cmp r5, r6  
  12.     blo 1b  
  13.     mov r5, #0              @ unknown machine  
  14. 2:  mov pc, lr  
  15. ENDPROC(__lookup_machine_type)  
 

        這裡的過程基本上是同proc的檢查是一樣的,這裡主要檢查晶片的型別,比如我們現在的晶片是MSM7X27FFA,這也是一個結構體,它的標頭檔案在arch/arm/include/asm