30日OS自作入門1-4日目
積んでいた30日OS本に手をつけてみようと思う。 本当は3月入ったらすぐ手をつける予定だったが、VRにドハマりして何も手をつけないまま半月過ぎていた...やばーい...
環境
最近ヤフオクでthinkpadを入手しmanjaro20.2.1(Cinnamon)を入れた。Arch系に慣れる良い機会なのでこのマシンをつかってみる。
書籍はwindowsでの実装で、naskや筆者のツールがlinuxではつかえないため、
以下の記事を参考に進めた。
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
保存して、qemu上で実行。
qemu-system-i386 helloos.img
わーい
次はアセンブリで記述する。 筆者は独自のアセンブラnaskを使用しているが、windowsでしか使えないため、今回はnasmを使用する。 nasmとnaskの違いは以下を参照。
バイナリに変換して、実行。
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セクション(初期値をもつ変数を格納するためのセクション)とメモリ領域がかぶってしまい、エラーが出る。
注釈セクションは他のプログラムから準拠性や互換性などを確認するためのものであり、今回は必要ないので、リンカスクリプトで指定して、生成されないようにした。
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ヶ月くらいかかるかな...