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 では, @ 上記 r8,r9,r10 を使用し,返り値として r4 にページテーブルの物理アドレスが入る.(r0,r3,r6,r7 は壊される) 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.