この記事では、マルチブート標準を使用して最小限のオペレーティングシステムカーネルを作成する方法について説明します。 実際、画面にロードしてOK
を印刷するだけOK
。 将来の記事では、 Rust
プログラミング言語を使用して拡張します。
私はすべてを詳細に説明し、コードをできる限りシンプルにしようとしました。 質問、提案、または問題がある場合は、 GitHub
コメントを残すか、タスクを作成しGitHub
。 ソースコードはリポジトリで利用可能です 。

復習
コンピュータの電源を入れると、特別なフラッシュメモリからBIOSがロードされます。 BIOS
はセルフテストとハードウェア初期化テストを実行し、起動可能なデバイスを検索します。 少なくとも1つが見つかった場合、制御はブートローダーに転送されます。ブートローダーは、ストレージデバイスの先頭に格納されている実行可能コードのごく一部です。 ブートローダーは、デバイス上のカーネルイメージの場所を特定し、それをメモリにロードします。 また、x86プロセッサはデフォルトで非常に限られたリアルモードで起動するため(1978年のプログラムと互換性があるため)、プロセッサをいわゆるプロテクトモードに切り替える必要があります。
これはそれ自体が複雑なプロジェクトであるため、ローダーを作成しません(実際にこれを実行したい場合は、 ここで読んでください )。 代わりに、 多くのテスト済みブートローダーの 1つを使用して、CD-ROMからカーネルをブートします。 しかし、どれですか?
マルチブート
幸いなことに、ブートローダーの標準、マルチブート仕様があります。 コアは、仕様をサポートし、互換性のあるブートローダーがそれをロードできることを示すだけです。 Multiboot 2
仕様を使用します( PDF )
有名なブートローダーGRUB 2とともに。
ブートローダーに
Multiboot 2
サポートを知らせるには、カーネルを次の形式の
で開始する必要があります。
野原 | 種類 | 価値 |
---|
マジックナンバー | u32 | 0xE85250D6 |
建築 | u32 | i386の場合は0 、MIPSの場合は4 |
ヘッダー長 | u32 | タグを含む合計ヘッダーサイズ |
チェックサム | u32 | -( + + ) |
タグ | 可変 |
終了タグ | (u16、u16、u32) | (0, 0, 8) |
x86アセンブラーに変換すると、次のようになります( Intel
構文):
section .multiboot_header header_start: dd 0xe85250d6 ; (multiboot 2) dd 0 ; 0 ( i386) dd header_end - header_start ; ; dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)) ; `multiboot` ; dw 0 ; dw 0 ; dd 8 ; header_end:
x86アセンブラーがわからない場合は、簡単な紹介を次に示します。
- ヘッダーは
.multiboot_header
というセクションに書き込まれ.multiboot_header
(これは後で必要になります) header_start
とheader_end
はメモリ内の場所を示すラベルです。これらを使用してヘッダーの長さを計算し、dd
はdefine double
(32ビット)をdefine word
ことを意味し、 dw
はdefine word
(16ビット)をdefine word
ことを意味しdefine word
。 それらは単に指定された32ビット/ 16ビット定数を出力しますが、- チェックサム計算の
0x100000000
定数は、コンパイラの警告を避けるための小さなハックです。
nasm
を使用して、このファイル( multiboot_header.asm
と呼びます)を既にコンパイルできます。
`archlinux`にnasmをインストールする [loomaclin@loomaclin ~]$ yaourt nasm 1 extra/nasm 2.13.02-1 An 80x86 assembler designed for portability and modularity 2 extra/yasm 1.3.0-2 A rewrite of NASM to allow for multiple syntax supported (NASM, TASM, GAS, etc.) 3 aur/intel2gas 1.3.3-7 (3) (0.20) Converts assembly language files between NASM and GNU assembler syntax 4 aur/nasm-git 20150726-1 (1) (0.00) 80x86 assembler designed for portability and modularity 5 aur/sasm 3.9.0-1 (18) (0.61) Simple crossplatform IDE for NASM, MASM, GAS, FASM assembly languages 6 aur/yasm-git 1.3.0.r30.g6caf1518-1 (0) (0.00) A complete rewrite of the NASM assembler under the BSD License ==> Enter n° of packages to be installed (eg, 1 2 3 or 1-3) ==> --------------------------------------------------------- ==> 1 [sudo] password for loomaclin: resolving dependencies... looking for conflicting packages... Packages (1) nasm-2.13.02-1 Total Download Size: 0.34 MiB Total Installed Size: 2.65 MiB :: Proceed with installation? [Y/n] :: Retrieving packages... nasm-2.13.02-1-x86_64 346.0 KiB 1123K/s 00:00 [
次のコマンドはフラットバイナリファイルを生成し、結果のファイルには24バイトが含まれます(x86マシンで作業している場合はlittle endian
)。
[loomaclin@loomaclin ~]$ cd IdeaProjects/ [loomaclin@loomaclin IdeaProjects]$ mkdir a_minimal_multiboot_kernel [loomaclin@loomaclin IdeaProjects]$ cd a_minimal_multiboot_kernel/ [loomaclin@loomaclin a_minimal_multiboot_kernel]$ nano multiboot_header.asm [loomaclin@loomaclin a_minimal_multiboot_kernel]$ nasm multiboot_header.asm [loomaclin@loomaclin a_minimal_multiboot_kernel]$ hexdump -x multiboot_header 0000000 50d6 e852 0000 0000 0018 0000 af12 17ad 0000010 0000 0000 0008 0000 0000018 [loomaclin@loomaclin a_minimal_multiboot_kernel]$
ブートコード
カーネルをロードするには、ローダーが呼び出すことができるコードを追加する必要があります。 boot.asm
ファイルを作成しましょう。
global start section .text bits 32 start: ; `OK` mov dword [0xb8000], 0x2f4b2f4f hlt
ここにはいくつかの新しいコマンドがあります:
global
エクスポートラベル(それらをパブリックにします)。 start
ラベルは、コアへのエントリポイントになります。公開ラベルである必要があります。.text
セクションは、実行可能コードのデフォルトセクションです。bits 32
は、次の行が32ビット命令であることを意味します。 GRUB
がカーネルを起動するとき、プロセッサは保護モードのままなので、これが必要です。 次の記事でロングモードに切り替えると、 bits 64
(64ビット命令)を実行できます。mov dword
命令は、32ビット定数0x2f4b2f4f
のメモリアドレスにb8000
(これにより、画面にOK
と表示されます。これについては、以下の記事で説明します)hlt
は、命令の実行を停止するようにプロセッサに指示する命令です。
組み立て、表示、および分解した後、プロセッサのオペコードが動作しているのがわかります。
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ nano boot.asm [loomaclin@loomaclin a_minimal_multiboot_kernel]$ nasm boot.asm [loomaclin@loomaclin a_minimal_multiboot_kernel]$ hexdump -x boot 0000000 05c7 8000 000b 2f4f 2f4b 00f4 000000b [loomaclin@loomaclin a_minimal_multiboot_kernel]$ ndisasm -b 32 boot 00000000 C70500800B004F2F mov dword [dword 0xb8000],0x2f4b2f4f -4B2F 0000000A F4 hlt [loomaclin@loomaclin a_minimal_multiboot_kernel]$
実行可能ファイルを作成する
後でGRUB
を介して実行可能ファイルをダウンロードするには、 ELF
実行可能ファイルでなければなりません。 したがって、単純なバイナリの代わりにnasm
を使用してELF
オブジェクトファイルを作成する必要があります。 これを行うには、単に引数に-f elf64
を追加します。
ELF
実行可能コード自体を作成するには、オブジェクトファイルをリンクする必要があります。 linker.ld
と呼ばれるリンク用のカスタムスクリプトを使用します。
ENTRY(start) SECTIONS { . = 1M; .boot : { *(.multiboot_header) } .text : { *(.text) } }
人間の言語で書かれたものを翻訳しましょう:
start
はエントリポイントであり、ローダーはカーネルのロード後にこのラベルに移動します。. = 1M;
1メガバイトから最初のセクションのブートアドレスを設定します。これは、カーネルをロードするための場所の標準です。- 実行可能部分には2つのセクションがあり
.text
boot
の開始boot
と.text
後、 - 最後のセクション
.text
は、すべての着信セクション.text
が含まれ.text
。 .multiboot_header
という名前のセクションが最初の出力セクション( .boot
)に追加され、実行可能コードの先頭に配置されます。 GRUB
はファイルの先頭でマルチブートヘッダーを検出するため、これが必要です。
ELF
オブジェクトファイルを作成し、上記のリンカースクリプトを使用してリンクします。
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ nasm -f elf64 multiboot_header.asm [loomaclin@loomaclin a_minimal_multiboot_kernel]$ nasm -f elf64 boot.asm [loomaclin@loomaclin a_minimal_multiboot_kernel]$ ld -n -o kernel.bin -T linker.ld multiboot_header.o boot.o [loomaclin@loomaclin a_minimal_multiboot_kernel]$
-n
(または--nmagic
)フラグをリンカーに渡すことが非常に重要です。これにより、実行可能ファイル内の自動セクションアライメントが無効になります。 そうしないと、リンカは実行可能ファイルの.boot
セクションのページを整列させる場合があります。 これが発生すると、 GRUB
はマルチブートヘッダーを見つけることができなくなります。これは、先頭にないためです。
objdump
コマンドを使用して、生成された実行可能ファイルのセクションを表示し、 .boot
セクションにファイル内の最小オフセットがあることを確認します。
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ objdump -h kernel.bin kernel.bin: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .boot 00000018 0000000000100000 0000000000100000 00000080 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 0000000b 0000000000100020 0000000000100020 000000a0 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE [loomaclin@loomaclin a_minimal_multiboot_kernel]$
注: ld
およびobjdump
コマンドはプラットフォームに依存しています。 x86_64アーキテクチャで作業していない場合、 binutilsをクロスコンパイルする必要があります 。 その後、 x86_64‑elf‑ld
およびx86_64‑elf‑objdump
代わりに、それぞれx86_64‑elf‑ld
およびx86_64‑elf‑objdump
使用します。
ISOイメージの作成
すべてのBIOS
ベースのパーソナルコンピューターはCD-ROMから起動する方法を知っているため、カーネルとGRUB
ブートローダーファイルを含むブート可能なCD-ROMイメージをISOと呼ばれる単一のファイルに作成する必要があります。 次のディレクトリ構造を作成し、 kernel.bin
をboot
ディレクトリにコピーしboot
。
isofiles └── boot ├── grub │ └── grub.cfg └── kernel.bin
grub.cfg
は、カーネルファイル名とmultiboot 2
との互換性を示します。 次のようになります。
set timeout=0 set default=0 menuentry "my os" { multiboot2 /boot/kernel.bin boot }
コマンドを実行します。
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ mkdir isofiles [loomaclin@loomaclin a_minimal_multiboot_kernel]$ mkdir isofiles/boot [loomaclin@loomaclin a_minimal_multiboot_kernel]$ mkdir isofiles/boot/grub [loomaclin@loomaclin a_minimal_multiboot_kernel]$ cp kernel.bin isofiles/boot/ [loomaclin@loomaclin a_minimal_multiboot_kernel]$ nano grub.cfg [loomaclin@loomaclin a_minimal_multiboot_kernel]$ cp grub.cfg isofiles/boot/grub/
次のコマンドを使用して、起動可能なイメージを作成できます。
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ grub-mkrescue -o os.iso isofiles xorriso 1.4.8 : 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, 7675m free Added to ISO image: directory '/'='/tmp/grub.jN4u6m' xorriso : UPDATE : 898 files added in 1 seconds Added to ISO image: directory '/'='/home/loomaclin/IdeaProjects/a_minimal_multiboot_kernel/isofiles' xorriso : UPDATE : 902 files added in 1 seconds xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img' ISO image produced: 9920 sectors Written to medium : 9920 sectors at LBA 0 Writing to 'stdio:os.iso' completed successfully.
注:一部のプラットフォームでは、 grub-mkrescue
呼び出すと問題が発生する場合があります。 うまくいかなかった場合は、次の手順を試してください。
--verbose
てコマンドを実行し、xorriso
ライブラリxorriso
インストールされていることを確認してxorriso
( xorriso
またはlibisoburn
パッケージ)。
`Archlinuxに` libisoburn`を置かなければならなかった[loomaclin @ loomaclin a_minimal_multiboot_kernel] $ yaourt xorriso
1個の追加/ libisoburn 1.4.8-2
ライブラリlibburnおよびlibisofsのフロントエンド
==>インストールするパッケージのn°を入力します(例:1 2 3または1-3)
==>-==> 1
[sudo] loomaclinのパスワード:
依存関係の解決...
競合するパッケージを探しています...
パッケージ(3)libburn-1.4.8-1 libisofs-1.4.8-1 libisoburn-1.4.8-2
合計ダウンロードサイズ:1.15 MiB
合計インストールサイズ:3.09 MiB
::インストールを続行しますか? [Y / n]
::パッケージの取得...
libburn-1.4.8-1-x86_64 259.7 KiB 911K / s 00:00 [################################## ##################################################] 100%
libisofs-1.4.8-1-x86_64 237.8 KiB 2.04M / s 00:00 [################################ ###################################################]] 100%
libisoburn-1.4.8-2-x86_64 683.8 KiB 2.34M / s 00:00 [################################ ###################################################]] 100%
(3/3)キーリングのキーをチェックする[############################################# ###########################################] 100%
(3/3)パッケージの整合性のチェック[############################################ #########################################] 100%
(3/3)パッケージファイルの読み込み[############################################# #########################################] 100%
(3/3)ファイルの競合のチェック[############################################# ###########################################] 100%
(3/3)利用可能なディスク容量[############################################ ###########################################] 100%
::パッケージ変更の処理...
(1/3)libburn [#############################################のインストール######################################] 100%
(2/3)libisofs [###############################################のインストール######################################] 100%
(3/3)libisoburnのインストール
- EFIシステムを使用する場合、
grub-mkrescue
はデフォルトのEFI
イメージを作成しようとします。 引数-d /usr/lib/grub/i386-pc
を指定してこの動作を取り除くか、 mtools
パッケージをインストールしてmtools
EFI
イメージを取得できます。 - 一部のシステムでは、コマンドの名前は
grub2-mkrescue
です。
読み込み中
OSをダウンロードします。 これを行うには、 QEMUを使用します。
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ qemu-system-x86_64 -cdrom os.iso (qemu-system-x86_64:10878): Gtk-WARNING **: Allocating size to GtkScrollbar 0x7f2337e5a280 without calling gtk_widget_get_preferred_width/height(). How does the code know the size to allocate? (qemu-system-x86_64:10878): Gtk-WARNING **: Allocating size to GtkScrollbar 0x7f2337e5a480 without calling gtk_widget_get_preferred_width/height(). How does the code know the size to allocate? (qemu-system-x86_64:10878): Gtk-WARNING **: Allocating size to GtkScrollbar 0x7f2337e5a680 without calling gtk_widget_get_preferred_width/height(). How does the code know the size to allocate?
エミュレータウィンドウが表示されます:

左上隅にある緑色のテキストOK
に注意してください。 これがうまくいかない場合は、コメントのセクションをご覧ください。
何が起こったのかをまとめます:
- BIOSは、仮想CD-ROM(ISO)からブートローダー(GRUB)をロードします。
- ブートローダーはカーネル実行可能コードを読み取り、マルチブートヘッダーを見つけました。
.boot
および.text
.boot
をメモリにコピーしました( 0x100000
および0x100020
)。- エントリポイントに移動しました(
0x100020
、これはobjdump -f
呼び出すことで見つけることができます)。 - カーネルはテキスト
OK
緑色で表示し、プロセッサを停止しました。
これを実際のハードウェアでテストすることもできます。 結果のイメージをディスクまたはUSBドライブに書き込み、そこから起動する必要があります。
ビルドの自動化
ここで、ファイルを変更するたびに4つのコマンドを正しい順序で呼び出す必要があります。 これは悪いです。 Makefileを使用してこのプロセスを自動化しましょう。 ただし、最初に、適切なディレクトリ構造を作成して、アーキテクチャ依存のファイルを分離する必要があります。
… ├── Makefile └── src └── arch └── x86_64 ├── multiboot_header.asm ├── boot.asm ├── linker.ld └── grub.cfg
私たちは作成します:
[loomaclin@loomaclin a_minimal_multiboot_kernel]$ mkdir -p src/arch/x86_64 [loomaclin@loomaclin a_minimal_multiboot_kernel]$ cp multiboot_header.asm src/arch/x86_64/ [loomaclin@loomaclin a_minimal_multiboot_kernel]$ cp boot.asm src/arch/x86_64/ [loomaclin@loomaclin a_minimal_multiboot_kernel]$ cp linker.ld src/arch/x86_64/ [loomaclin@loomaclin a_minimal_multiboot_kernel]$ cp grub.cfg src/arch/x86_64/ [loomaclin@loomaclin a_minimal_multiboot_kernel]$ nano Makefile
メイクファイルは次のようになります。
arch ?= x86_64 kernel := build/kernel-$(arch).bin iso := build/os-$(arch).iso linker_script := src/arch/$(arch)/linker.ld grub_cfg := src/arch/$(arch)/grub.cfg assembly_source_files := $(wildcard src/arch/$(arch)/*.asm) assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \ build/arch/$(arch)/%.o, $(assembly_source_files)) .PHONY: all clean run iso all: $(kernel) clean: @rm -r build run: $(iso) @qemu-system-x86_64 -cdrom $(iso) iso: $(iso) $(iso): $(kernel) $(grub_cfg) @mkdir -p build/isofiles/boot/grub @cp $(kernel) build/isofiles/boot/kernel.bin @cp $(grub_cfg) build/isofiles/boot/grub @grub-mkrescue -o $(iso) build/isofiles 2> /dev/null @rm -r build/isofiles $(kernel): $(assembly_object_files) $(linker_script) @ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files)
コメント(以前にmakeを使用したことがない場合は、 makefileチュートリアルをご覧ください ):
- $(ワイルドカードsrc / arch / $(arch)/ *。asm)は、
src/arch/$(arch)
ディレクトリ内のすべてのアセンブラファイルを選択するため、ファイルを追加するときにMakefileを更新する必要はありません。 patsubst
操作は、 src/arch/$(arch)/XYZ.asm
をbuild/arch/$(arch)/XYZ.o
です- アセンブリターゲット
$<
および$@
は自動的に出力変数です 。 - クロスコンパイルされたbinutilsを使用する場合は、
ld
をx86_64-elf-ld
置き換えるだけです。
これでmake
を呼び出すmake
ができ、更新されたすべてのアセンブラファイルがコンパイルおよびリンクされます。 make iso
コマンドもISOイメージを作成し、 make run
がQEMUを起動します。
次は?
次の記事では、ページテーブルを作成し、プロセッサ構成を実行して64ビットロングモードモードに切り替えます。
注釈
- 表の式
-(magic + architecture + header_length)
は、32ビットに収まらない負の値を作成します。 0x100000000
から減算することにより、減算した値を変更せずに値を正のままにします。 その結果、追加の符号ビットなしで、結果は32ビットに配置され、コンパイラは満足です:) - メモリの多くの特定の領域は1メガバイトマークまで配置できるため、オフセット0x0でカーネルをロードする必要はありません(たとえば、画面に
OK
を表示するために使用する0xb8000
いわゆるVGAバッファー)。