本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。
「いちばんやさしい!OS自作超入門」 第4章でつまずいたこと | UNIX Cafe

第4章で気になったことを整理する
前回は、『いちばんやさしい!OS自作超入門』の第3章までを進める中で、emu.pyの役割やHALT / RETの違いでつまずいた点を整理しました。
今回は第4章です。第4章では、単体プログラムをCPUエミュレータで動かす段階から、os.binとしてSimple OSを起動し、その上でreg、ls、exec、dateなどのコマンドを動かす段階へ進みます。
この記事では、第4章を進める中で気づいたこと、つまずいた点と、手元で確認した対応を、書籍の進行順に整理します。
使用環境
- macOS
- Python 3
- GitHubリポジトリ: https://github.com/sueyasu/os_book_code
- 補助リポジトリ: https://github.com/k1117n-cmyk/os-book-code-practice/tree/main/chapter4
- 作業ディレクトリ:
os_book_codeローカル側のディレクトリ名です。
4-2 CPUエミュレータを変更
emu.pyがos.bin起動用になる
テキスト P.76
# プログラムをロード
try:
with open("os.bin", "rb") as file:
prog = file.read()
memory[0:len(prog)] = prog
except:
print("OSを読み込めませんでした。")
sys.exit()第4章では、CPUエミュレータ側でos.binを読み込む形に変更し、SPやPCの初期値もOS起動用に変わります。
この変更後は、第1章から第3章で使っていたような、次の手順ではhello.binを直接実行できません。
python emu.py hello.binこの時点のemu.pyは、引数のhello.binではなく、カレントディレクトリのos.binを読み込む前提になります。
- Simple OSを起動する場合は、
python emu.pyを使います hello.binのような単体プログラムを直接確認したい場合は、序盤用のearly.pyを使います- Simple OSから外部プログラムを実行する場合は、
execコマンドが使えるようになってからdir/配下に.binを置いて実行します
第4章の途中では一時的に「前回のhello.bin直接実行手順」と「これから作るOS上でのexec実行手順」の間にいる、と理解すると混乱しにくいです。
作業前にgit statusを確認する
第4章の脚注には、次のようなコマンドが出てきます。
git fetch && git checkout v0.2
git fetch && git checkout v0.3これらは指定されたタグやブランチの状態へ作業ツリーを切り替える操作です。手元で編集しているos.asmやasm.pyがある場合、内容が上書きされたり、切り替え前にコミットや退避を求められたりします。
実行前に、次のコマンドで作業ツリーの状態を確認しておきます。
git status はファイルの変更状況を確認するコマンドです。画面に working tree clean と表示されれば、安全に次の切り替え操作に進めます。
git statusエラーメッセージを追いやすくする
アセンブリのタイポが「Pythonのエラー」になる原因
python asm.py os.asmos.asm をアセンブルするとき、ラベル名をタイプミスすると、次のようなPythonのtracebackが表示されました。
NameError: name '_get__nth_token_end' is not defined一見するとasm.pyのバグに見えますが、原因は os.asm 側の記述ミスです。
JPZI _get__nth_token_end定義されているラベルは_get_nth_token_endなので、正しくは次のように書きます。
JPZI _get_nth_token_endasm.py は、ラベルの評価にPythonの eval() を利用しているため、未定義のラベルを読み込むとPythonの NameError がそのまま表示されてしまいます。
補助ツールasmx.pyを用意する
元のasm.pyでは、即値やラベルを評価するときにPythonのeval()を使っています。
return eval(exp)ラベル名がsymbol_tableに存在しない場合、その名前がPythonの未定義変数としてeval()に渡されます。その結果、アセンブラとしてのエラーではなく、PythonのNameErrorがそのまま表示されていました。
そこで、asm.pyのエラーメッセージを少し改造した補助ツールとして、asmx.pyを用意しました。
asmx.pyのxは、asm.pyのextended版という意味です。短く入力でき、元のasm.pyをもとにした補助版だと分かりやすい名前にしています。
python asmx.py os.asm元のasm.pyを直接置き換えず、エラー原因を詳しく見たいときだけasmx.pyを使う形にしておくと、書籍の元コードとの差分も追いやすくなります。
asmx.pyでは、gccやclangのように「どのファイルの何行目で、どの部分が原因か」を見やすくしました。
os.asm:185: error: undefined symbol '_get__nth_token_end'
JPZI _get__nth_token_end
^^^^^^^^^^^^^^^^^^^
hint: did you mean '_get_nth_token_end'?未定義ラベル、オペランド不足、オペランド過多、不正レジスタ、.BYTEなどの引数不足、20ビット即値の範囲外も、Pythonのtracebackではなくアセンブラ風のエラーとして表示するようにしました。
asmx.pyはこのブログ記事のGitHubで公開していますので、気になる方は試してみてください。
4-4 プログラムを実行するコマンドを追加
execコマンドで外部プログラムを実行する
テキスト P.93
4-4では、Simple OSにexecコマンドを追加します。ここからは、hello.binやpi.binのようなユーザープログラムを、エミュレータに直接渡すのではなく、Simple OSのプロンプトから起動する流れになります。
実行する.binファイルは、dir/配下に置きます。
python asm.py pi.asm
mkdir -p dir
cp pi.bin dir/pi.bin
python emu.pySimple OSが起動したら、プロンプトで次のように入力します。
> exec pi.bindir/pi.binが無い場合は、次のようなエラーになります。
File open error.4-5 現在時刻を表示できるようにする
tick数カウント追加時のADDRエラー
テキスト P.101
os.asmにtick数をカウントするための割り込み処理を追加したところ、次のエラーが出ました。
os.asm:131: error: .ADDR が現在位置より小さい値を指定しています: 0xB0000 < 0xFF865
.ADDR 0xB0000
^^^^^^^このエラーは、.ADDR 0xB0000そのものが間違っているという意味ではありません。
直前に追加した割り込みベクタテーブルを、0xFF800へ配置していたことが原因でした。
; Vector Table
.ADDR 0xFF800
vector_table:
.DWORD int_timer
.DWORD int_other
.DWORD int_other
.DWORD int_otherasm.pyは、ソースコードを上から順番に読みながら、出力ファイル上の現在位置を進めていきます。そのため、一度.ADDR 0xFF800で現在位置を高い番地へ進めると、そのあとに.ADDR 0xB0000で低い番地へ戻ることはできません。
対応として、割り込みベクタテーブルをソースの末尾側へ移動しました。
0x80000 OS本体
0xB0000 システム関数
0xB1000 get_nth_token
0xC0000 keybuffer
0xC1000 tokenbuffer
0xFF800 割り込みベクタテーブルテキストを見直すと、図22の下には「os.asmの末尾にこのコードを追加する」という注意書きがあります。ここを読み落として、図21の直後に続けて書くと、今回のように.ADDRの後戻りエラーになります。
修正後は次のように成功しました。
python3 asm.py os.asmWrote os.bin (1046544 bytes)os.binのサイズが大きくなっているのは、.ADDR 0xFF800まで0でパディングされるためです。
tick数が増えていることを確認する
タイマー割り込み処理では、TPレジスタをインクリメントしています。
int_timer:
INC TPregコマンドで確認すると、TPが増えていることが分かりました。
TP: 000000EA
TP: 000001F40xEAは10進数で234、0x1F4は10進数で500です。この値が増えているので、タイマー割り込みごとにint_timerが呼ばれ、INC TPが実行されていると見てよさそうです。
dateコマンド追加時のつまずき
テキスト P.104
do_dateラベルが未定義になる
続いて、書籍の図24から図26に従ってdateコマンドを追加したところ、do_dateが未定義というエラーになりました。
os.asm:84: error: undefined symbol 'do_date'
JPZI do_date
^^^^^^^
hint: did you mean 'cmd_date'?書籍の図25には、dateコマンドを判定するコードが載っています。
; date
MOVI R9, cmd_date
CALLI cmp_str
JPZI do_dateまた、図26にはcmd_dateとbasetimeのデータ定義が載っています。
cmd_date:
.STRING "date"
basetime:
.DWORD 0ただ、図24、図25、図26で説明が終わり、その次が第5章に進んでいますので、ジャンプ先であるdo_date:本体の追加コードは見当たりません。
そのため、図25のコードだけを追加すると、JPZI do_dateのジャンプ先が存在せず、undefined symbol 'do_date'になります。
SYSCALL 11の説明から考えると、最低限必要なdo_date:本体は次の形になります。
do_date:
LDDI R8, [basetime]
SYSCALL 11
JPI cmdloopLDDI R8, [basetime]で、OS起動時に保存したUNIXタイムをbasetimeから読み出します。そのあとSYSCALL 11を呼ぶと、エミュレータ側でR8の基準時刻とTPのtick数から現在時刻を計算して表示します。最後にJPI cmdloopでSimple OSのコマンド入力待ちへ戻ります。
date判定を入れる場所
dateコマンドの判定コードは、未知コマンドのエラー表示より前に置く必要があります。例えば、exec判定の後、次のエラー表示へ進む前に入れます。
; exec
MOVI R9, cmd_exec
CALLI cmp_str
JPZI do_exec
; date
MOVI R9, cmd_date
CALLI cmp_str
JPZI do_date
MOVI R8, cmd_error1
SYSCALL 1未知コマンドのエラー表示より後ろにdate判定を置くと、dateと入力しても先にエラー処理へ流れてしまい、do_dateまで到達しません。
date追加時の完成形
冒頭部分は次のようにします。
.ADDR 0x80000
SYSCALL 10
STDI R8, [basetime]
MOVI TP, 0
MOVI VT, vector_table
EI
MOVI R8, start_message
SYSCALL 1コマンド実行部分には、書籍のページ内では見当たらなかったdo_date:本体を追加します。
do_date:
LDDI R8, [basetime]
SYSCALL 11
JPI cmdloopデータ部分には、書籍の図26どおりcmd_dateとbasetimeを追加します。
cmd_date:
.STRING "date"
basetime:
.DWORD 0修正後は、アセンブルが成功しました。
python3 asm.py os.asmWrote os.bin (1046544 bytes)OSを起動して、dateコマンドを実行すると、現在時刻が表示されます。
$ python emu.py
Welcome to Simple OS!
> date
2026-08-27 19:46:50
> 今回のまとめ
第4章では、emu.pyの役割が単体プログラム実行用からos.bin起動用へ変わります。そのうえで、os.asmにコマンド処理、割り込み処理、ベクタテーブル、データ領域を少しずつ追加していくため、コードを入れる場所が重要になります。
emu.py変更後はpython emu.py hello.binではhello.binを直接実行できません- ラベル名のタイプミスは、元の
asm.pyではPython tracebackとして出ます - 補助ツール
asmx.pyを使うと、エラーの位置と候補を確認しやすくなります .ADDRは現在位置を低い番地へ戻す用途には使えません- 図22の割り込みベクタテーブルは、
os.asmの末尾に追加します - 図24から図26の範囲では、
do_date:本体の追加コードは見当たりません
本格的にアセンブラを作り込むなら、式パーサをeval()ではなく専用の安全な実装に置き換える方法もあります。ただ、学習中の補助としては、今回の最低限の診断だけでも、ラベル名や命令の書き間違いをかなり見つけやすくなります。
