freshtree 1.1 __lookup_processor_type() 話說內核映像解壓后,又跳到c0008000這個地址。這個地址指向內核代碼的什么地方,我們肯定很想知道。在arch/arm/kernel/vmlinux.lds.S中,可以發現這樣的代碼: SECTIONS { #ifdef CONFIG_XIP_KERNEL . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR); #else . = PAGE_OFFSET + TEXT_OFFSET; #endif .text.head : { _stext = .; _sinittext = .; *(.text.head) } … } 一般內核都不配置成XIP方式的,所以這段腳本等同于: SECTIONS { . = PAGE_OFFSET + TEXT_OFFSET; .text.head : { _stext = .; _sinittext = .; *(.text.head) } … } 這段腳本告訴我們SECTIONS的起始地址是 .text.head的起始地址_stext,且 _stext= PAGE_OFFSET + TEXT_OFFSET; PAGE_OFSET在.config文件中設置: PAGE_OFFSET=0xC0000000; TEXT_OFFSET在主目錄下的Makefile文件中設置: textofs-y := 0x00008000 TEXT_OFFSET := $(textofs-y) 結合arch/arm/kernel/head.S,會發現如下代碼: .section ".text.head", "ax" ENTRY(stext) 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=cupid … ENDPROC(stext) 第一句代碼的意思是表示下面的內容都屬于.text.head 段的,”ax”表示這段內容是可分配且可執行的(allocable and executable) 。 所以c0008000處放的代碼就是stext的入口地址。接下來的 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE 就是把fiq_mask(快速中斷屏蔽位)和irq_mask(快速中斷屏蔽位)都置位,同時把處理器模式設置為svc模式。這就是要告訴閑雜人等不要來打擾,這里要辦重要的事。cpsr_c 代表當前狀態寄存器;鑒于當前狀態寄存器的重要性,arm特意開發了msr指令,專門用來設置當前狀態寄存器。 mrc p15, 0, r9, c0, c0 是將協處理器cp15 c0的值賦值到r9中。接下來的 bl __lookup_processor_type 是長跳轉到__lookup_processor_type,查看processor ID是否被內核支持。 __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 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 ENDPROC(__lookup_processor_type) adr是條偽指令,作用就是把標號為3位置的地址賦值給r3寄存器。3后面加f是表示這是個長距離(far)的標號。有同學可能就要問了,ldr也能起到這個作用,為什么不用ldr?首先 ldr r3, 3f取的是標號3這個地址的內容,而不是地址本身;其次,可以用ldr r3,=3f來取地址本身,但這是一個絕對地址;而adr取得的是相對地址。如果要保證程序在任何內存都能運行,就必須保證代碼是地址無關的,也就是PIC(position independent code)。顯然adr偽指令很對PIC的胃口,它的取相對地址方式符合PIC的設定。 .long __proc_info_begin .long __proc_info_end 3: .long . .long __arch_info_begin .long __arch_info_end 我們接著往下看。 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 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 ldmada r3,(r5-r7) 是把標簽3所指的地址的內容(也就是標簽3的虛擬地址)賦值給r7,把比標簽3所指的地址小4的地址的內容(也就是__proc_info_end)賦值給r6,把比標簽3所指的地址小8的地址的內容(__proc_info_begin)賦值給r5。這里的虛擬地址是線性邏輯地址,它和物理地址之間有著一一映射關系。因為__proc_info_begin 和__proc_info_end都是虛擬地址,此時我們MMU還沒有打開,就必須要使用物理地址。這就需要我們先把它們轉換為物理地址。接下來的三句代碼就是完成這樣的工作。 __proc_info_begin和__proc_info_end是在vlinux.lds.S中定義的。 __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .; 這說明在__proc_info_begin和__proc_info_end之間的是所有的.proc.info.init段。我們可以在arch/arm/mm/proc_*.S中找到相應的.proc.info.init段。Smdk6410屬于armv6,我們可以在proc_v6.S找到armv6處理器的id和id掩碼。 之后的代碼就是把處理器的id和id掩碼賦值到r3,r4中;把r9與處理器掩碼做與操作,然后與處理器id(r3)比較,看是否相等;如不相等,就取下一個處理器id進行比較;如果到最后都沒有處理器id相符,就將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_processor_type函數。跳出之后會對處理器id是否有效做一個判斷;如果不是有效的處理器,就進行相應的錯誤處理;如果是有效的處理器,就進行機器類型查找: movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p' bl __lookup_machine_type @ r5=machinfo |