GRUBで簡単なOSカーネルを動かしてみる
「x86 bootloaderから簡単なOSカーネルを動かしてみる」では、bootloaderを作った上で簡単なOSカーネルを動かした。 ここでは、GRUBをbootloaderとして利用してOSカーネルを動かしてみる。
環境
Ubuntu 14.04.3 LTS 64bit版、QEMU 2.0.0
$ uname -a Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.3 LTS Release: 14.04 Codename: trusty $ as --version GNU assembler (GNU Binutils for Ubuntu) 2.24 $ ld --version GNU ld (GNU Binutils for Ubuntu) 2.24 $ grub-mkrescue --version grub-mkrescue (GRUB) 2.02~beta2-9ubuntu1.6 $ qemu-system-x86_64 --version QEMU emulator version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.21), Copyright (c) 2003-2008 Fabrice Bellard $ make --version GNU Make 3.81
必要なパッケージをインストールする
以降の手順で必要となるパッケージを次のようにしてインストールする。
$ sudo apt-get install build-essential xorriso qemu
マルチブートヘッダーを書く
まず、GRUBマルチブート用のヘッダーを用意する。 具体的には、次のようなファイルを書く。
# multiboot_header.s .section .multiboot_header header_start: .long 0xe85250d6 # magic number (multiboot 2) .long 0 # architecture 0 (protected mode i386) .long header_end - header_start # header length # checksum .long 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)) # insert optional multiboot tags here # required end tag .word 0 # type .word 0 # flags .long 8 # size header_end:
カーネルコードを書く
次に、実際に動かすカーネルのコードを書く。 ここでは、スクリーンにOKと表示する次のような簡単なコードを使うことにする。
# kernel.s .intel_syntax noprefix .global kernel_start .section .text .code32 kernel_start: # print `OK` to screen mov dword ptr [0xb8000], 0x2f4b2f4f hlt
0xb8000はVGAのtext-modeバッファが位置する物理メモリアドレスである。
上のコードでは、このアドレスに緑背景白文字のOK
を書き込んでいる。
カーネルイメージを作る
作成したマルチブートヘッダとカーネルコードをリンクし、カーネルイメージを作る。 まず、次のようなリンカスクリプトを書く。
/* linker.ld */ ENTRY(kernel_start) SECTIONS { . = 1M; .text : { /* ensure that the multiboot header is at the beginning */ *(.multiboot_header) *(.text) } }
このリンカスクリプトを利用して、次のようにしてカーネルイメージを作る。
$ as -o multiboot_header.o multiboot_header.s $ as -o kernel.o kernel.s $ ld -n -o kernel.bin -T linker.ld multiboot_header.o kernel.o
実際に作成されたイメージを調べると、マルチブートヘッダの後にカーネルコードが置かれていることが確認できる。
$ objdump -d kernel.bin kernel.bin: file format elf64-x86-64 Disassembly of section .text: 0000000000100000 <header_start>: 100000: d6 (bad) 100001: 50 push rax 100002: 52 push rdx 100003: e8 00 00 00 00 call 100008 <header_start+0x8> 100008: 18 00 sbb BYTE PTR [rax],al 10000a: 00 00 add BYTE PTR [rax],al 10000c: 12 af ad 17 00 00 adc ch,BYTE PTR [rdi+0x17ad] 100012: 00 00 add BYTE PTR [rax],al 100014: 08 00 or BYTE PTR [rax],al ... 0000000000100018 <kernel_start>: 100018: c7 05 00 80 0b 00 4f mov DWORD PTR [rip+0xb8000],0x2f4b2f4f # 1b8022 <kernel_start+0xb800a> 10001f: 2f 4b 2f 100022: f4 hlt
ISOファイルを作る
最後に、上で作成したカーネルイメージをもとに起動可能なISOファイルを作成する。 まず、次のようなGRUBのコンフィグファイルを用意する。
# grub.cfg set timeout=0 set default=0 menuentry "test os" { multiboot2 /boot/kernel.bin boot }
次に、以下のようなディレクトリ構成でファイルを配置し、grub-mkrescueコマンドでISOファイルを作成する。
$ mkdir -p isofiles/boot/grub $ cp grub.cfg isofiles/boot/grub/ $ cp kernel.bin isofiles/boot/ $ grub-mkrescue -o os.iso isofiles/
QEMUで動かしてみる
QEMUを使い、作成したISOファイルから仮想マシンを起動するには次のようにする。
$ qemu-system-x86_64 -drive format=raw,file=os.iso
起動後のスクリーンショットを次に示す。
左上にOKと表示されていることから、正常にカーネルコードが実行できていることがわかる。
Makefileを書いてみる
ここまでの手順をMakefileで書くと次のようになる。
# Makefile kernel.bin: multiboot_header.o kernel.o $(LD) -n -o $@ -T linker.ld $^ os.iso: grub.cfg kernel.bin mkdir -p isofiles/boot/grub cp grub.cfg isofiles/boot/grub/ cp kernel.bin isofiles/boot/ grub-mkrescue -o $@ isofiles/ $(RM) -r isofiles/ .PHONY: clean run clean: $(RM) multiboot_header.o kernel.o kernel.bin os.iso run: os.iso qemu-system-x86_64 -drive format=raw,file=$<
$@
はターゲットのファイル名、$<
は依存関係にある最初のファイル、$^
は依存関係にあるファイルすべてを表す。
また、.PHONY
は右に書かれた名前のターゲットがファイルと無関係にコマンドとして実行されることを表すものである。
なお、.c
から.o
を生成するルールは省略されており、標準で定義されているものが利用されるようになっている。
このようなMakefileを用意することで、一連の手順を次のようにまとめて実行することができる。
$ make run as -o multiboot_header.o multiboot_header.s as -o kernel.o kernel.s ld -n -o kernel.bin -T linker.ld multiboot_header.o kernel.o mkdir -p isofiles/boot/grub cp grub.cfg isofiles/boot/grub/ cp kernel.bin isofiles/boot/ grub-mkrescue -o os.iso isofiles/ xorriso 1.3.2 : RockRidge filesystem manipulator, libburnia project. Drive current: -outdev 'stdio:os.iso' Media current: stdio file, overwriteable Media status : is blank Media summary: 0 sessions, 0 data blocks, 0 data, 23.7g free Added to ISO image: directory '/'='/tmp/grub.731jtJ' xorriso : UPDATE : 281 files added in 1 seconds Added to ISO image: directory '/'='/home/user/tmp/grub/isofiles' xorriso : UPDATE : 285 files added in 1 seconds xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img' xorriso : UPDATE : 100.00% done ISO image produced: 2481 sectors Written to medium : 2481 sectors at LBA 0 Writing to 'stdio:os.iso' completed successfully. rm -f -r isofiles/ qemu-system-x86_64 -drive format=raw,file=os.iso
上の結果から、依存するファイルが順に生成されながらコマンドが実行されていることがわかる。
なお、次のようにすれば生成されたファイル一式を削除することができる。
$ make clean rm -f multiboot_header.o kernel.o kernel.bin os.iso