「いちばんやさしい!OS自作超入門」 第4章でつまずいたこと | UNIX Cafe

* 当サイトでは、コンテンツの一部に広告を掲載しています。

System Note $ cat /proc/ai-disclosure

本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。

「いちばんやさしい!OS自作超入門」第2回 第4章でつまずいたこと | UNIX Cafe
目次

第4章で気になったことを整理する

前回は、『いちばんやさしい!OS自作超入門』の第3章までを進める中で、emu.pyの役割やHALT / RETの違いでつまずいた点を整理しました。

今回は第4章です。第4章では、単体プログラムをCPUエミュレータで動かす段階から、os.binとしてSimple OSを起動し、その上でreglsexecdateなどのコマンドを動かす段階へ進みます。

この記事では、第4章を進める中で気づいたこと、つまずいた点と、手元で確認した対応を、書籍の進行順に整理します。

一番ハードルが低いOS自作本。「OSってどう動くの?」の疑問がこれ1冊で解ける。
\最大5.5%ポイントアップ!/
Amazonで探す
\楽天ポイント4倍セール!/
楽天市場で探す

使用環境

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を読み込む形に変更し、SPPCの初期値も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.asmasm.pyがある場合、内容が上書きされたり、切り替え前にコミットや退避を求められたりします。

実行前に、次のコマンドで作業ツリーの状態を確認しておきます。

git status はファイルの変更状況を確認するコマンドです。画面に working tree clean と表示されれば、安全に次の切り替え操作に進めます。

git status

エラーメッセージを追いやすくする

アセンブリのタイポが「Pythonのエラー」になる原因

python asm.py os.asm

os.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_end

asm.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.pyxは、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.binpi.binのようなユーザープログラムを、エミュレータに直接渡すのではなく、Simple OSのプロンプトから起動する流れになります。

実行する.binファイルは、dir/配下に置きます。

python asm.py pi.asm
mkdir -p dir
cp pi.bin dir/pi.bin
python emu.py

Simple OSが起動したら、プロンプトで次のように入力します。

> exec pi.bin

dir/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_other

asm.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.asm
Wrote os.bin (1046544 bytes)

os.binのサイズが大きくなっているのは、.ADDR 0xFF800まで0でパディングされるためです。

tick数が増えていることを確認する

タイマー割り込み処理では、TPレジスタをインクリメントしています。

int_timer:
        INC     TP

regコマンドで確認すると、TPが増えていることが分かりました。

TP: 000000EA
TP: 000001F4

0xEAは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_datebasetimeのデータ定義が載っています。

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     cmdloop

LDDI 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_datebasetimeを追加します。

cmd_date:
        .STRING "date"
basetime:
        .DWORD  0

修正後は、アセンブルが成功しました。

python3 asm.py os.asm
Wrote 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()ではなく専用の安全な実装に置き換える方法もあります。ただ、学習中の補助としては、今回の最低限の診断だけでも、ラベル名や命令の書き間違いをかなり見つけやすくなります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

目次