LinuxカーネルのuImageのフォーマット/作成手順/ブート開始

ARM Linuxを動かすとき、u-bootを使うのでuImageをよく作っているのだけど、その造りを知らなかったので調べた。

uImageのフォーマット

Ubuntu14.04でuImageをつくるツールをインストールすると、昔はuboot-mkimageだったのが、u-boot-toolsに変わっていた。 u-boot-toolsに含まれるmkimgeツールでと使うとuImageの作ったりヘッダ情報を調べたりできる。ヘッダ情報を見る場合はlオプションをつける。

$ mkimage -l uImage

Image Name:   Linux-3.16.0
Created:      Sun Aug 31 00:55:42 2014
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    2782856 Bytes = 2717.63 kB = 2.65 MB
Load Address: 40008000
Entry Point:  40008000

uImageのヘッダ情報は、ココらへんで定義されている。 サイズは64バイトで、各メンバの数値の意味は同じファイルに定義されている。

typedef struct image_header {
    __be32      ih_magic;   /* Image Header Magic Number   */
    __be32      ih_hcrc;    /* Image Header CRC Checksum   */
    __be32      ih_time;    /* Image Creation Timestamp    */
    __be32      ih_size;    /* Image Data Size     */
    __be32      ih_load;    /* Data     Load  Address      */
    __be32      ih_ep;      /* Entry Point Address     */
    __be32      ih_dcrc;    /* Image Data CRC Checksum */
    uint8_t        ih_os;      /* Operating System        */
    uint8_t        ih_arch;    /* CPU architecture        */
    uint8_t        ih_type;    /* Image Type          */
    uint8_t        ih_comp;    /* Compression Type        */
    uint8_t        ih_name[IH_NMLEN];  /* Image Name  IH_NMLEN=32 */
} image_header_t;

uImageの作り方

uImageのつくり方を調べるために、make時にnオプションをつける。このオプションをつけると、make時のコマンドが表示される。

$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- LOADADDR=0x40008000 uImage -n

結果を見ると、uImageを作るときのコマンドはファイルに保存されていることがわかる。linux/arch/arm/boot/.uImage.cmdに保存されていて、mkuboot.shは、mkimageを呼び出すだけである。

$ ./scripts/mkuboot.sh -A arm -O linux -C none  -T kernel -a 0x40008000 -e 0x40008000 -n 'Linux-3.16.0' -d arch/arm/boot/zImage arch/arm/boot/uImage

mkimagedオプションはuImageに含めるデータの実体でありzImageを指定している。他のオプションはuImageの先頭につけるヘッダ情報を指定しているだけである。

次にzImageについて調べる。uImageと同様にlinux/arch/arm/boot/.zImage.cmdに作成コマンドが保存さている。

$ arm-none-linux-gnueabi-objcopy -O binary -R .comment -S  arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage

zImageは、vmlinuxから.commentセクションの削除および、シンボルと再配置情報を落としたものであることが分かる。

最後にvmlinuxであるが、これの作成コマンドはlinux/arch/arm/boot/compressed/.vmlinux.cmdに保存されている。

arm-none-linux-gnueabi-ld -EL --defsym _kernel_bss_size=237028 -p --no-undefined -X -T arch/arm/boot/compressed/vmlinux.lds
   arch/arm/boot/compressed/head.o
   arch/arm/boot/compressed/piggy.gzip.o
   arch/arm/boot/compressed/misc.o
   arch/arm/boot/compressed/decompress.o
   arch/arm/boot/compressed/string.o
   arch/arm/boot/compressed/hyp-stub.o
   arch/arm/boot/compressed/fdt_rw.o
   arch/arm/boot/compressed/fdt_ro.o
   arch/arm/boot/compressed/fdt_wip.o
   arch/arm/boot/compressed/fdt.o
   arch/arm/boot/compressed/atags_to_fdt.o
   arch/arm/boot/compressed/lib1funcs.o
   arch/arm/boot/compressed/ashldi3.o
   arch/arm/boot/compressed/bswapsdi2.o
-o arch/arm/boot/compressed/vmlinux

ブート開始

pcduinoというARM Cortex A8ベースのボードで確認する。 手順は、http://linux-sunxi.org/Mainline_Kernel_Howtoに従った。 まずカーネルのビルドをする。 LOADADDRは、普通はconfigファイルで定義されているのかなと思うけど、今回ターゲットにしているpcduinoのconfigには定義されていなかったようである。

$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- LOADADDR=0x40008000 uImage dtbs -n

次に、makeで作ったuImageとdtbを以下のようなu-bootコマンドで、pcduinoのメモリ上にロードする。

ext4load mmc 0:2 0x46000000 /uImage
ext4load mmc 0:2 0x49000000 /sun4i-a10-pcduino.dtb
setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait ${extra}
bootm 0x46000000 - 0x49000000

u-bootのbootmコマンドを実行すると、カーネルの起動が開始される。 bootmで指定したアドレスのうち、0x46000000カーネルをロードするアドレス、0x49000000dtbをロードするアドレスになる。 u-bootは、uImageのヘッダを除いた部分を0x46000000からLOADADDRにコピーする(ココらへん)。 あとは、LOADADDRから実行を開始すればカーネルがスタートする(ココらへん)。

カーネルの最初に実行される部分は、zImageの最初の部分となる。その部分は、vmlinuxの作成コマンドからlinux/arch/arm/kernel/vmlinux.ldsを見ればよいことが分かる。 エントリポイントとなる_startは、.startの最初の部分を指している。これは、linux/arch/arm/boot/compressed/head.Sstartにあたる。 このあとは長い長いカーネルの実行が開始される。