zImageのロード&展開調査/ARM (3)

お土産の醤油


zImageのロード&展開調査/ARM (2) - まだ見えない先の の続き.
展開されたカーネルイメージ Image の先頭へ PC を変更した後の話.まずは,Image の先頭はどのファイルの内容かを調べる.

Image の先頭を構成するファイル

Image は linux-2.6.22/vmlinux から生成されるので,その vmlinux のメモリマップを調べることで Image の先頭を構成するファイルを調べる.
vmlinux のメモリマップは,2.6.22/.vmlinux.cmd によると arch/arm/kernel/vmlinux.lds と分かる *1 .

下記の arch/arm/kernel/vmlinux.lds より,vmlinux を構成するオブジェクトファイルが持つ .text.head セクションが Image の先頭を構成することが分かる.

...
SECTIONS
{
 . = (0xc0000000) + 0x00028000;
 .text.head : {
  _stext = .;
  _sinittext = .;
  *(.text.head)
 }
...

2.6.22/.vmlinux.cmd より,vmlinux を構成するファイルは下記の通り.

arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-ns9750/built-in.o arch/arm/nwfpe/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o net/built-in.o --end-group .tmp_kallsyms2.o

--start-group --end-group について *2
この中で, .text.head セクションを持つファイルは,arch/arm/kernel/head.S のみ.よって,Image の先頭を構成するファイルは head.S である.

head.S の内容

zImage 展開後の PC は下記のコードを指し,実行する.ここでは,(展開時に続き再度)プロセッサ,(ボード依存の)アーキテクチャを調べ,MMU を初期化する.再度実行する理由は,vmlinux が必ずしも zImage から展開・実行されるわけではなく,直接メモリにロードされ実行されたりするためである.head.S (がincludeする head-common.S) は init/main.c:start_kernel() を呼び出して終わる.ここで,zImage展開後 call_kernel が呼ばれる前に MMU が再び OFF になっていることに注意.また,r1 にはアーキテクチャID (architecture number/machine id),r2 には atags へのポインタが入っている.

        .section ".text.head", "ax"
        .type   stext, %function
ENTRY(stext)
        msr     cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode (super visor call)
                                                @ and irqs disabled
        mrc     p15, 0, r9, c0, c0              @ get processor id

@ カーネルイメージ中では,r9 に対応する値は arch/arm/mm/proc-*.S 中の .proc.info.init セクションの先頭に記述される.
@ カーネルが複数のプロセッサをサポートする場合,複数の (proc-*.S 中の) .proc.info.init セクションが (vmlinux 中の) .proc.info.init セクションに含まれる.
@ .proc.info.init の配置は前述の vmlinux.lds で記述されている.
@ __lookup_processor_type は,カーネルが r9 プロセッサをサポートしているかどうかを(上記セクションとの比較をもって)確認する.
@ 返り値 r5 は,r9 が一致したプロセッサの .proc.info.init セクション先頭アドレスを指している.
@ r9 に一致するプロセッサが存在しない場合,r5 には0が入る.
        bl      __lookup_processor_type         @ r5=procinfo r9=cpuid
        movs    r10, r5                         @ invalid processor (r5=0)?
@ __error_p では,(CONFIG_DEBUG_LL が有効な場合は)プロセッサをサポートしていない旨の警告を出し,ループに入る.
@ カーネルコンフィグにて CONFIG_DEBUG_LL を有効にした場合,アーキテクチャ毎に include/asm/arch/debug-macro.S にてデバッグ出力方法 (serial 出力等) を定義する必要がある.
        beq     __error_p                       @ yes, error 'p'

@ カーネルイメージ中では,r1 (ブートローダより渡された machine id) に対応する値は arch/arm/tools/mach-types にてマシン名と共に定義され
@ arch/arm/mach-*/ にて,MACHINE_START マクロによって .arch.info.init セクションに記述される.
@ __lookup_machine_type は,カーネルが r1 アーキテクチャ(マシン)をサポートしているかどうかを(上記セクションとの比較をもって)確認する.
@ 返り値 r5 は,r1 が一致したマシンの .arch.info.init セクション先頭アドレスを指している.
@ r1 に一致するアーキテクチャが存在しない場合,r5 には0が入る.
        bl      __lookup_machine_type           @ r5=machinfo
        movs    r8, r5                          @ invalid machine (r5=0)?
@ __error_p と同様
        beq     __error_a                       @ yes, error 'a'

@ __create_page_tables では,
@ 上記 r8r9r10 を使用し,返り値として r4 にページテーブルの物理アドレスが入る.(r0r3r6r7 は壊される)
        bl      __create_page_tables

        /*  
         * The following calls CPU specific code in a position independent
         * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
         * xxx_proc_info structure selected by __lookup_machine_type
         * above.  On return, the CPU will be ready for the MMU to be
         * turned on, and r0 will hold the CPU control register value.
         */
        ldr     r13, __switch_data              @ address to jump to after
                                                @ mmu has been enabled
        adr     lr, __enable_mmu                @ return (PIC) address
        add     pc, r10, #PROCINFO_INITFUNC

/*
 * Setup common bits before finally enabling the MMU.  Essentially
 * this is just loading the page table pointer and domain access
 * registers.
 */
        .type   __enable_mmu, %function
__enable_mmu:
...
        orr     r0, r0, #CR_A
...
        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

/*
 * Enable the MMU.  This completely changes the structure of the visible
 * memory space.  You will not be able to trace execution through this.
 * If you have an enquiry about this, *please* check the linux-arm-kernel
 * mailing list archives BEFORE sending another post to the list.
 *
 *  r0  = cp#15 control register
 *  r13 = *virtual* address to jump to upon completion
 *
 * other registers depend on the function called upon completion
 */
        .align  5
        .type   __turn_mmu_on, %function
__turn_mmu_on:
        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

/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 * 
 * r8  = machinfo
 * r9  = cpuid
 * r10 = procinfo
 * 
 * Returns:
 *  r0, r3, r6, r7 corrupted
 *  r4 = physical page table address
 */
        .type   __create_page_tables, %function
__create_page_tables:
@pgtblについては補足1を参照
        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

        ldr     r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

        /*
         * Create identity mapping for first MB of kernel to
         * cater for the MMU enable.  This identity mapping
         * will be removed by paging_init().  We use our current program
         * counter to determine corresponding section base address.
         */
        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

        /* 
         * Now setup the pagetables for our kernel direct
         * mapped region.
         */
        add     r0, r4,  #(KERNEL_START & 0xff000000) >> 18
        str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
        ldr     r6, =(KERNEL_END - 1)
        add     r0, r0, #4
        add     r6, r4, r6, lsr #18
1:      cmp     r0, r6
        add     r3, r3, #1 << 20
        strls   r3, [r0], #4
        bls     1b

#ifdef CONFIG_XIP_KERNEL
        /*
         * Map some ram to cover our .data and .bss areas.
         */
        orr     r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)
        .if     (KERNEL_RAM_PADDR & 0x00f00000)
        orr     r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)
        .endif
        add     r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18
        str     r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
        ldr     r6, =(_end - 1)
        add     r0, r0, #4
        add     r6, r4, r6, lsr #18
1:      cmp     r0, r6
        add     r3, r3, #1 << 20
        strls   r3, [r0], #4
        bls     1b
#endif

        /*
         * Then map first 1MB of ram in case it contains our boot params.
         */
        add     r0, r4, #PAGE_OFFSET >> 18
        orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
        .if     (PHYS_OFFSET & 0x00f00000)
        orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)
        .endif
        str     r6, [r0]
...
        mov     pc, lr
        .ltorg

#include "head-common.S"

補足1

head.S のマクロ定義部

#define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)                            
#define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)

/*                                                                                     
 * swapper_pg_dir is the virtual address of the initial page table.                    
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must            
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect             
 * the least significant 16 bits to be 0x8000, but we could probably                   
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.                 
 */
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

        .globl  swapper_pg_dir
        .equ    swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000
        .macro  pgtbl, rd
        ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)
        .endm

#ifdef CONFIG_XIP_KERNEL
#define KERNEL_START    XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
#define KERNEL_END      _edata_loc
#else
#define KERNEL_START    KERNEL_RAM_VADDR
#define KERNEL_END      _end                                                           
#endif

補足2

mm_mmuflags の例 (arch/arm/mm/proc-arm926.S)

        .long   PMD_TYPE_SECT | \         @    ページテーブルはセクションエントリ                                                                                 である                                                                                                     
                PMD_SECT_BUFFERABLE | \ @ バッファ可能
                PMD_SECT_CACHEABLE | \ @ キャッシュ可能
                PMD_BIT4 | \ @ L1 で セクションエントリでは 1 としなければいけない?
                PMD_SECT_AP_WRITE | \ @ 書き込み可能
                PMD_SECT_AP_READ @ 読み込み可能

*1:

cmd_vmlinux := arm-unknown-linux-gnu-ld -EL -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o ...

*2:

`-( ARCHIVES -)'
`--start-group ARCHIVES --end-group'
The ARCHIVES should be a list of archive files. They may be
either explicit file names, or `-l' options.

The specified archives are searched repeatedly until no new
undefined references are created. Normally, an archive is
searched only once in the order that it is specified on the
command line. If a symbol in that archive is needed to resolve an
undefined symbol referred to by an object in an archive that
appears later on the command line, the linker would not be able to
resolve that reference. By grouping the archives, they all be
searched repeatedly until all possible references are resolved.

Using this option has a significant performance cost. It is best
to use it only when there are unavoidable circular references
between two or more archives.