Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@suwa3

簡易的なOS自作

More than 1 year has passed since last update.

はじめに

かねがねOSの自作をしてみたいなと思っていたので
書籍などを参考に、OS自作をガッツリ始める前に
まずは簡易的なOSの実装にチャレンジしてみました。

ちょうど手頃なチュートリアルを見つけたので
試してみたものを、簡単な解説とともに作業ログ的にまとめました。

実際に作成したコードはこちらになります
https://github.com/Ishizuka427/MyOS

環境

macOS

参照

主に参考にした記事&書籍です

実行環境を整える

まず、実行環境ですが
ホストマシンに

  • nasm
  • make
  • qemu

をインストールします。

※DockerやVMなどを用いてローカル環境を汚さずに行うことも可能です。
ただ今回の内容程度であれば、特に問題ないと判断しました。
今後、自作OSを続けたい場合はgit管理などで仮想マシンへの移動をスムーズにすると良いかもしれません。

$ brew install nasm
$ brew install make
$ brew install qemu

🐾解説

nasm
アセンブラです。
機械が理解できる命令プログラムアセンブリコード
機械が理解できる命令バイナリコードに置き換えて、コンピューターが実行できるようにします。

make
コンパイルを自動化するツールです。

qemu
キューエミューと読みます。
自作したOSは、このエミュレーターを介して実行されます。
いわゆる仮想マシンです。

コンピューターの起動について

コンピューターが起動してからの処理について
わかりやすい概要を見つけたので記載します。

電源ボタンをONするとまずBIOS(BasicInputOutputSystem)が動き出します。BIOSはその名の通り基本的な(Basic)入力(Input)と出力(Output)を制御するハードウェアとソフトウェアのシステム(System)です。(このあたりは別に読み飛ばしても問題ありません)。BIOSが動き出すとPOST(PowerOnSelfTest)と呼ばれている処理を行います。POST処理では接続されたデバイスのチェック・初期設定、メモリーのチェックを行って正常にシステムが起動できるかどうかをチェックします。起動できると判断すると次にブートローダーまたはOSをメモリーにロードします。ブートローダーまたはプログラムがロードされた後にそのプログラムが様々な処理を簡単に行えるように、BIOSは入出力デバイスの操作をプログラムから利用できるインターフェースを用意しています。
--「0から作るOS開発」から引用--

MBR(マスターブートレコード)の容量には上限があるため、それ以上のロードにはブートローダーが用いられます。
今回作成するOSはとてもシンプルなため、ブートローダを使用してさらにコードをロードすることはしません。

アセンブリコードを書いてみる

実際にアセンブリコードを書いてみます。

$ vi boot.asm
boot.asm
; boot.asm
hang:
jmp hang

times 510-($-$$) db 0

; This is a comment

db 0x55
db 0xAA

🐾解説

hang
コード内の名前つきマーカーです。

jmp hang
マーカーhangにジャンプします。これで無限ループになります。

times 510-($-$$) db 0
残りのバイトを0で埋めるためのnasm構文です。

$
現在の行の先頭

$$
ファイルやセクションの先頭

($-$$)
ファイルの先頭から現在の位置を引く
2つのdbコマンドが、最後に2バイト分で格納するので
MBRの残りの512バイトを埋めるのに512という数を使いません。

;
コロン以降はコメントアウトになります。

0x55と0xAA
BIOSに「はい、これは実行可能なMBRです」と知らせます。
作成したファイルをバイナリファイルに変換して、コンピューターが実行できるようにします。

$ nasm -f bin boot.asm -o boot.bin

🐾解説

-f bin
nasmがバイナリフォーマットにアセンブルします。

boot.asm
nasmがアセンブルしようとしているアセンブリファイルです。

boot.bin
出力ファイル名です。

エミュレーターで実行してみます。

$ qemu-system-x86_64 boot.bin

image.png

アセンブリファイルをコンパイルして実行することができました👏

Makefileの作成

次に、Makefileを作成してコンパイルを自動化します。

$ vi Makefile
Makefile
boot.bin: boot.asm
    nasm -f bin boot.asm -o boot.bin

qemu: boot.bin
    qemu-system-x86_64 boot.bin

clean:
    rm *.bin

🐾Makefileの文法解説

Makefile
ターゲット名α: 依存ファイル名α
    コマンド行α

ターゲット名β: 依存ファイル名β
    コマンド行β

ターゲット名γ: 依存ファイル名γ
    コマンド行γ

ターゲット名を指定してmakeを実行する場合は
$ make ターゲット名
とします。また、コロンの後の値は依存ファイルです。

例①

$ make qemu

👆こちらを実行すると、makeboot.binのコマンド(nasm -f bin boot.asm -o boot.bin)を実行したあと、qemu-system-x86_64 boot.binを実行します。

例②

$ make clean

👆こちらを実行すると、rm *.binが実行され、すべてのアセンブルされたファイルが削除されます。

※ターゲットを省略するとMakefileの中で先頭のターゲットが実行されます。
[注意⚠]Makefile内でコマンド行を記述する場合は、必ず先頭にタブ文字を入れてください。

文字を表示してみる

boot.asmアセンブリファイルを書き換えて、スクリーンに文字を表示させてみます。

$ vi boot.asm
boot.asm
; boot.asm
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov si, msg
mov ah, 0x0E

print_character_loop:
lodsb

or al, al
jz hang

int 0x10

jmp print_character_loop

msg:
db 'Hello, World!', 13, 10, 0

hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

ファイルを編集したら実行してみます。

$ make clean
$ make qemu

🐾解説

mov ax, 0x07c0
mov ds, ax

movはデータを動かす命令です。
ここでは0x07c0を汎用レジスタax (アキュムレータ)へ移動したあとにaxds(データセグメント)にロードします。
BIOSは毎回0x07c0:0x0000の位置にMBRをロードするため、このように設定します。
dsはレジスタから移動してきたデータのみを受け取る仕様のため、このように段階を踏んでデータの受け渡しを行います。

image.png

レジスタ
CPU内の記憶装置です。
それぞれに役割があり、例えば今回のケースではセグメントレジスタに値を入れることでセグメントの開始位置が変わります。
0x07C0をdsレジスターに格納することで、セグメントの開始位置が0x7C00になります。

セグメント
メモリーをセグメントと呼ばれるある程度の大きさに区切ったものをいいます。
全容量の上限は決まっていますが、一つ一つの大きさやメモリー上の場所を自分で変更することが可能です。
また、セグメント同士の領域はお互い重複するようにすることもできます。

image.png

(参照: 「0から作るOS開発」)

ファイルの中身についてざっくりと解説をします。

(解説用)
; boot.asm
mov ax, 0x07c0    ; 0x07c0 を ax に動かす
mov ds, ax        ; ax を dx に動かす

mov ah, 0x0       ; 0x0 を ah に動かす
mov al, 0x3       ; 0x3 を al に動かす
int 0x10          ; 上記を 0x10 (ビデオサービスを管理するBIOS)に int命令で割り込み 

mov si, msg       ; msg を si へ動かす。ソースインデックスに msg を格納
mov ah, 0x0E      ; 0x0E を ah へ動かす。画面の文字列を 0x10 へ割り込み可能に

; ループ開始
print_character_loop:    
lodsb             ; lodsb命令 si から al に1byteロードする  

or al, al         ; al(0x3)と(0x3)をor演算。文字列が0になり終わると、プロセッサはオペランドの位置にジャンプ 
jz hang

int 0x10          ; 文字列を画面にプリントする

jmp print_character_loop    ; ループの先頭に戻り、文字列が終わりでなければ次の文字をプリントする。
; ループここまで

msg:
db 'Hello, World!', 13, 10, 0    ; db => これらの値をmsg:アドレスに格納するよう命じる

hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

Hello, Worldが表示された画面がこちらです👏

image.png

Nextステップにオススメの書籍

30日でできる! OS自作入門 Kindle版

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
suwa3
Docker好きなお花屋さん から、SREになりました

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?