08 February 2012

ARM Linux のカーネルのエントリーから、 start_kernel 関数にジャンプするまでのコードを読んでみました。

ARM にそこそこ詳しくないと理解しづらいかもしれませんが、 ARM ARM を見ながら根気強く読んでいくと Linux Kernel だけでなく ARM の勉強にもなります。

カーネルのエントリーは arch/arm/kernel/head.S にあります。 バージョンによってちょっと違うかもしれないけど、エントリー部分のコードは以下のような感じになっています。 (以下は v2.6.32 のコードです)

ENTRY(stext)
        setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 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'
        bl      __lookup_machine_type           @ r5=machinfo
        movs    r8, r5                          @ invalid machine (r5=0)?
        beq     __error_a                       @ yes, error 'a'
        bl      __vet_atags
        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, BSYM(__enable_mmu)           @ return (PIC) address
 ARM(   add    pc, r10, #PROCINFO_INITFUNC      )
 THUMB( add    r12, r10, #PROCINFO_INITFUNC     )
 THUMB( mov    pc, r12                          )
ENDPROC(stext)

2行目の setmode はマクロで、定義は arch/arm/include/asm/assembler.h にあります。

mrc     p15, 0, r9, c0, c0

のコードは processor id を r9 に入れている。

__lookup_processor_type は r9 に入っている processor id を元に、該当する proc_info_list 構造体へのポインタを得て r5 に入れます。 struct proc_info_list の型定義は arch/arm/include/asm/procinfo.h にあります。 各 processor ごとの proc_info_list 構造体は arch/arm/mm/ 以下で定義されています。

__lookup_processor_type の関数の中身は arch/arm/kernel/head-common.S で定義されています。

ここはまだ物理アドレスで動いている部分。つまり、リンク時のシンボル情報と異なるアドレスで動いています。 なので、相対アドレス参照はそのままで動きますが、絶対アドレス参照の部分は、論理アドレスから物理アドレスに変換しながら動かしています。

__lookup_machine_type__lookup_processor_type と同様の動きです。 ただし、マシーン番号は自分ではわからないので、ブートローダーから渡された値(r1に入っている)を使って、machine_desc 構造体へのポインタを得て r5 に入れています。

__arch_info_begin__arch_info_end.arch.info.init セクションの最初と最後を表しています。 .arch.info.init セクションには machine_desc 構造体がずらずらっと集められているので、r1 の番号に一致するのをサーチしています。

machine_desc 構造体のインスタンスを記述するには、 MACHINE_START (arch/arm/include/asm/mach/arch.h で定義) というマクロで使うらしい。 各ボードごとの machine_desc 構造体は arch/arm/mach-XX の下で定義されている。

途中に出てくる #MACHINFO_TYPE などのマクロは asm/asm-offsets.h で定義されているが、これは arch/arm/kernel/asm-offsets.s から変換して作られているっぽい。ややこしいな。

続いて __create_page_tables ルーチンへジャンプし、ページテーブルを作成する。 ページテーブルはカーネルのエントリーアドレスから `0x4000’ を引いたアドレスに置かれます。 ここはまだページテーブルを作るだけで、まだ MMU ON にはしません。

ldr    r13, __switch_data

という行は __switch_data に書かれているデータを r13(sp) にセットしています。

__switch_data: .long __mmap_switched

となっているので、r13 に __mmap_switched の「仮想アドレス」が入ることになります。

adr    lr, BSYM(__enable_mmu)

の部分は __enable_mmu の「物理アドレス」をr14(lr)にセットしています。

 ARM(   add    pc, r10, #PROCINFO_INITFUNC      )
 THUMB( add    r12, r10, #PROCINFO_INITFUNC     )
 THUMB( mov    pc, r12                          )

は thum 命令が ON/OFF かによって、どちらかが残るようになっています。 proc_info_list 構造体の __cpu_flush メンバーのアドレスが pc に入ります。

arch/arm/mm/proc-XXX.S を見ますと、 __cpu_flush メンバーのところには

b       __v7_setup

みたいなジャンプ命令が書いてあります。

つまり、ここから ARM のアーキテクチャバージョン依存のセットアップ関数にジャンプします。 D Cache Clean とか、 ARM のアーキテクチャバージョンによってコード実装の異なる処理がここで行われます。

__v7_setup を抜けるときに、lr に入っているアドレスへリターンする。 つまり __enable_mmu に飛ぶ。

__enable_mmu からさらに __turn_mmu_on へと飛んで、MMU ON にします。 そして、

mov     r3, r13
mov     pc, r3

の部分で __mmap_switched にジャンプする。

ここでようやく、仮想アドレスでプログラムが実行される。 つまり、リンク時のシンボル情報と実際に動いているアドレスが一致する。

__mmap_switched では、 .data セクションをロードアドレスから仮想アドレスへコピーするが、 普通はロードアドレスと仮想アドレスは同じになっているので、すっ飛ばされる。

続いて、 .bss セクションをゼロクリア。

processor ID と machine type と ATAGS pointer を後で参照できるように外部変数に保存する。

cr_alignment というのはよくわかりませんでした。

最後に init_thread_union + THREAD_START_SP の値を sp レジスタの初期値としてセットしています。

この部分は、 thread_info 構造体とスタックの関係を理解してないといけないとピンと来ないと思います。 Linux での実装では thread_info 構造体とカーネルスタックは常に同じ領域 (サイズは普通 8192 byte)を共有しています。 このようにしておくと、スタックの値の下位 13 bit を 0マスクするだけで、現在動作中の thread_info 構造体へのポインタ求められるため、メリットが大きい。

init_thread_union (arch/arm/kernel/init_task.c で定義)というのは一番最初の thread_info で、これにカーネルスタックサイズのTHREAD_START_SP (= 8192 - 8) を足したものが、スタックの初期値になるわけですね。

最後に、start_kernel 関数 (init/main.c で定義)にジャンプする。 ここからは基本的には arch 非依存な部分で、C 言語で記述されています。



blog comments powered by Disqus