30日OS自作入門1-4日目

積んでいた30日OS本に手をつけてみようと思う。 本当は3月入ったらすぐ手をつける予定だったが、VRにドハマりして何も手をつけないまま半月過ぎていた...やばーい...

環境

最近ヤフオクthinkpadを入手しmanjaro20.2.1(Cinnamon)を入れた。Arch系に慣れる良い機会なのでこのマシンをつかってみる。
書籍はwindowsでの実装で、naskや筆者のツールがlinuxではつかえないため、 以下の記事を参考に進めた。

qiita.com

1日目

やるぞやるぞOSやるぞ

仮想マシンエミュレータqemuと、バイナリエディタ、ghexをインストール。

sudo pacman -Syu qemu-arch-extra ghex

手打ちで、1474560バイト打ち込むのは骨が折れるので、まずは、0埋めしたファイルを作る。

python3 -c 'print("\x00"*1474559)' > helloos.img
du -b helloos.img
1474560 helloos.img

1474560バイト書き込まれたことが確認できれば良し。 ghexでhelloos.imgを編集する。

ghex helloos.img

f:id:tosonshirley:20210315214956p:plain

保存して、qemu上で実行。

qemu-system-i386 helloos.img

f:id:tosonshirley:20210315215345p:plain

わーい

次はアセンブリで記述する。 筆者は独自のアセンブラnaskを使用しているが、windowsでしか使えないため、今回はnasmを使用する。 nasmとnaskの違いは以下を参照。

tools/nask - hrb-wiki

バイナリに変換して、実行。

nasm helloos.asm -o helloos.img
qemu-system-i386 helloos.img

さっきと同じくhello,worldと表示された。ok

少し詰まったところ

19             DB  "HELLO-OS       "
20             DB  "FAT12      "

ディスクの名前とフォーマットの名前のメモリ容量?は決まっているため、11バイト、8バイト指定してあげないといけない。つまりディスクの名前が11文字以下の場合、空白で11文字まで満たす必要がある。 DB "HELLO=OS" としたらエラーが出た。

2日目

アセンブリまとめ

特に命令(ニーモニック)について良く知らないため、勉強もかねて備忘録としてまとめておく。

ORG命令

Origin 開始点を意味する。機械語がメモリの何番地から読み込まれるか指定する。 ORG命令は疑似命令といい、実際にマイクロプロセッサが実行する命令ではない。あくまでアセンブラに指示を与えるためのもの。

ORG    0x7c00

JMP命令

c言語のgo to と同じ。指定したラベルへジャンプする。

JMP    entry

JE命令

jump if equel 条件ジャンプ命令の一種。 比較命令の結果が等しければジャンプ。

CMP    AL,0
JE    fin

MOV命令

今回はintel記法で書いている。第一オペランド(a)に第二オペランド(b)を代入する。AT&T記法だと逆になるらしい。

MOV    a,b
MOV    AL,[SI]

[]はメモリを意味する。SIレジスタに934が入っていたら934番地の値をALに代入。

DB命令

ORGと同じく疑似命令。オペランドで指定した数値を1バイトずつデータとしてメモリに格納。文字列もオペランドとして指定可能。

DB    0x43
DB    "HELLO-OS    "

DB 0x43, 0x01 → 01000011 00000001

DW命令

これも疑似命令。オペランドで指定した数値を2バイトずつデータとしてメモリに格納。
DW 0x43, 0x01 → 00000000 01000011 00000000 00000001

DD命令

疑似命令。オペランドで指定した数値を4バイトずつデータとしてメモリに格納。
DD 0x43, 0x01 → 00000000 00000000 00000000 01000011 00000000 00000000 00000000 00000001

INT命令

ソフトウェア割り込み。

HLT命令

CPUを待機状態にさせる命令。

レジスタまとめ

レジスタについてもまとめておく。
レジスタとは・・・CPU内部の記憶装置。主にフリップフロップで構成されており、アクセス速度が速い。~1ns

AX(アキュムレータ)・・・算術演算操作の結果を格納
CX(カウンタ)・・・ループ命令等のカウントを記録
DX(データ)・・・算術演算操作とI/O操作に使用
BX(ベース)・・・算術演算とセグメントモードでのDSのデータ指定
SP(スタックポインタ)・・・スタックのトップを指すポインタ
BP(スタックベースポインタ)・・・スタックのベースを指す
SI(ソースインデックス)・・・ストリーム操作(MOV命令等)のソース(入力元)のポインタ
DI(デスティネーションインデックス)・・・ストリーム操作(MOV命令等)のディスティネーション(出力先)のポインタ
上記は16bitの場合。32bitの場合は頭にEが付く。

メモリマップ

BIOSが確保するメモリ領域や、自由に使ってはいけない領域は決まっている。
例えばブートセクタが読まれるアドレスは0x00007c00 - 0x00007dff である。
以下を参照。
(AT)memorymap - os-wiki

3日目

詰まるところはなかったと思う。
IPL(初期プログラムローダ)の実装。

アセンブリ

JC命令

jump if carry キャリーフラグ(EEFLAGSレジスタの一種。1bitのみのレジスタ。)が1ならジャンプ。

JC    error

JNC命令

jump if not carry キャリーフラグが0ならジャンプ。

JBE命令

jump if below or equal 小さいかもしくは等しければジャンプ。

CMP    CL,18
JBE    readloop  ; CL <= 18ならreadloopへ

4日目

ここからアセンブリを抜け出して、C言語で書いていく。 冒頭の記事を参考にリンカを作成したが、コンパイルgccが.note.gnu.propertyという注釈セクションを吐き出してしまい、これが.dataセクション(初期値をもつ変数を格納するためのセクション)とメモリ領域がかぶってしまい、エラーが出る。
f:id:tosonshirley:20210421091903p:plain

注釈セクションは他のプログラムから準拠性や互換性などを確認するためのものであり、今回は必要ないので、リンカスクリプトで指定して、生成されないようにした。

SECTIONS
{
    .head 0x0 : {
        LONG(64 * 1024)  /*  0 : stack+.data+heap の大きさ(4KBの倍数) */
        LONG(0x69726148)      /*  4 : シグネチャ "Hari" */
        LONG(0)               /*  8 : mmarea の大きさ(4KBの倍数) */
        LONG(0x310000)        /* 12 : スタック初期値&.data転送先 */
        LONG(SIZEOF(.data))   /* 16 : .dataサイズ */
        LONG(LOADADDR(.data)) /* 20 : .dataの初期値列のファイル位置 */
        LONG(0xE9000000)      /* 24 : 0xE9000000 */
        LONG(HariMain - 0x20) /* 28 : エントリアドレス - 0x20 */
        LONG(0)               /* 32 : heap領域(malloc領域)開始アドレス */
    }

    .text : { *(.text) }

    .data 0x310000 : AT ( ADDR(.text) + SIZEOF(.text) ) {
        *(.data)
        *(.rodata*)
        *(.bss)
    }

    /DISCARD/ : { *(.eh_frame) *(.note.gnu.property) }

}

他詰んだところはなかったと思う。 一日で一日のやることが終わらないことが多く、意外と時間がかかる...完成には2ヶ月くらいかかるかな...