「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