いちばんやさしい!OS自作超入門 学習メモまとめ|第3章・第4章・第7章のつまずき整理 | UNIX Cafe

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

System Note $ cat /proc/ai-disclosure

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

いちばんやさしい!OS自作超入門 学習メモまとめ|第3章・第4章・第7章のつまずき整理 | UNIX Cafe

書籍『いちばんやさしい!OS自作超入門』を読みながら、実際にコードを写経して動かした際の学習メモをまとめました。

この記事は書籍の内容を置き換えるものではなく、手元でコードを動かすときのつまずきやすいポイントを整理するための補助メモです。

この記事で分かること:

  • emu.pyearly.pyhello.binos.bin の役割の違い
  • HALTRETexecdir/ 配置でつまずきやすい理由
  • asm.py のエラー表示、taskexec、ページテーブル、仮想メモリを追うときの見方

本書では、Pythonで作られた小さなCPUエミュレータ上で、OSを少しずつ育てていきます。最初はシンプルなアセンブリを動かすところから始まり、マルチタスクを処理する「Simple OS」の実装、タスク切り替え、そして仮想メモリの導入へとステップアップしていく構成です。

しかし、実際に手元でコードを動かしてみると、「本に書いてある通りに打ったはずなのに、画面の表示が少し違う」という場面に直面しました。原因を調べてみたところ、書籍の本文で想定されているコードと、この記事執筆時点でGitHubに公開されているコードで、一部挙動が異なっている箇所があることが分かりました。

特に、以下のようなポイントで迷いやすいかと思います。

  • 起動ファイルの取り違え: emu.pyhello.bin を渡したつもりでも、実際には os.bin が起動する
  • 終了命令の選択: プログラムを終わらせる際、HALTRET のどちらを使うべきか迷う
  • 配置場所の迷子: exec で実行するための .bin ファイルをどこに配置すればよいか分かりにくい
  • エラーの判別: asm.py のエラーが、アセンブリの記述ミスではなく、Pythonのエラーのように見える
  • 第7章の難所: PCやページテーブルの意味をしっかり整理しないと、処理を追いかけるのが難しい

この記事は、各章ごとに解説した詳細な学習メモへの案内ページです。書籍を読み進める中で「ここで手が止まった」「この出力結果は何だろう?」と疑問に思ったときは、ぜひ該当する章の解説記事を参考にしてみてください。

一番ハードルが低いOS自作本。「OSってどう動くの?」の疑問がこれ1冊で解ける。
目次

この記事で対象とする開発環境

この記事に記載しているメモは、次の環境で動作を確認しています。

主な用語とファイル構成

解説の中で頻繁に登場するプログラムやファイルの一覧です。

  • asm.py: アセンブリで書いたファイルを .bin に変換するプログラムです
  • emu.py: Simple OS を起動するためのCPUエミュレータです
  • early.py: 書籍序盤の hello.bin などを直接動かすために使う補助エミュレータです
  • os.bin: Simple OS 本体です
  • hello.bin: 書籍序盤で使う、Hello, World! 用の小さなプログラムです

はじめに:全体の流れを整理する

『いちばんやさしい!OS自作超入門』は、最初からいきなり巨大なOSを作り込むわけではありません。

  • 第1ステップ: アセンブリで書いた小さなプログラムを .bin ファイルに変換し、CPUエミュレータ上で動かす。
  • 第2ステップ: os.bin として「Simple OS」を起動し、そのOS上で様々なコマンドを実行する。
  • 第3ステップ: 複数のタスクを切り替えたり、タスクごとに独立したメモリ空間(仮想メモリ)を割り当てる。

このステップを上がっていく中で、最も混乱しやすいのが「今、自分はどの環境で、何を起動しているのか」という点です。

実は、書籍の序盤で想定されている挙動と、この記事執筆時点でGitHubに公開されているコードの挙動には少しギャップがあります。ここを混同してしまうと、「hello.bin を動かしたつもりなのに、なぜかSimple OSが起動してしまう……」といった現象に悩まされることになります。

なぜ表示や挙動が食い違うのか?

書籍の序盤を読んでいる段階では、以下のように hello.bin を直接指定して動かす流れのように見えます。

python3 asm.py asm_code/hello.asm
python3 emu.py asm_code/hello.bin

しかし、この記事執筆時点で確認したGitHub公開版の emu.py は、引数で渡されたファイルを読み込むのではなく、同じディレクトリにある os.bin を自動的に読み込んで「Simple OS」を起動する作りになっています。

そのため、スムーズに学習を進めるためには、以下の3つの役割を明確に分けて考えるのがおすすめです。

  • asm.py.asmから .binを生成するプログラム。この段階ではまだ実行はしません。
  • early.py:書籍の序盤に登場する、単体のプログラムを直接実行するために使用します。
  • emu.py:この記事執筆時点で確認したGitHub公開版では、 os.bin を読み込んで Simple OS を起動します。

第3章まで:emu.py、hello.bin、HALT / RET の整理

第3章までは、OS本体の実装に入る前の「準備段階」です。まずは、アセンブリ言語で書いた小さなプログラムを変換し、CPUエミュレータ上で動かすまでの一連の流れを確認していきます。

1. 最初に出会う「Wrote …」はエラーではない

ここで最初につまずきがちなのが、python3 asm.py asm_code/hello.asm を実行した際に表示される以下のテキストです。

Wrote asm_code/hello.bin (28 bytes)

一見すると何かの警告のようにも見えますが、これはエラーではありません
asm_code/hello.asm の変換に成功し、asm_code/hello.bin というファイルを作成しました」という成功メッセージです。

ここで重要なのは、この時点ではまだ「Hello, World!」のプログラムは実行されていないという点です。asm.py はあくまで「バイナリファイルへ変換する係」であり、プログラムを動かす役割は持っていません。

2. 「hello.bin」を動かしたつもりが「Simple OS」が起動する?

次に迷いやすいのが、生成した hello.bin を動かそうとして、以下のコマンドを実行したときです。

python3 emu.py asm_code/hello.bin

前述の通り、この記事執筆時点で確認したGitHub公開版の emu.py をそのまま使うと、hello.bin が直接動くわけではありません。手元の環境が os.bin を読み込む仕様になっている場合、以下のような画面が立ち上がります。

Welcome to Simple OS!
>

この表示は hello.bin の実行結果ではなく、OS本体(os.bin)が起動した結果です。

もし書籍の序盤の通り、hello.bin という単体プログラムを直接動かして動作を確認したい場合は、補助リポジトリに用意している early.py を使用します。これは「指定した .bin ファイルをそのまま単体で実行する」ための補助プログラムです。

python3 asm.py asm_code/hello.asm
python3 early.py asm_code/hello.bin

3. 「HALT」と「RET」の使い分け

第3章では、HALTRET という2つの終了命令の違いを理解することも大切です。どちらもプログラムの終わりを意味しますが、活躍する場面が異なります。

  • HALT: 単体プログラムとして直接実行し、そこでCPUを止めます
  • RET: 呼び出し元へ戻る命令です。Simple OS の exec から起動した場合は、OS側が戻り先をスタックに積んでからプログラムへ飛ぶため、結果としてOSのプロンプトへ戻ります

特に円周率を計算する pi.asm などは、書籍内の記述とGitHub公開版のコードとで、プログラム末尾の命令が異なっている(HALT になっていたり RET になっていたりする)箇所があります。

「今回は単体で動かすから HALT なんだな」「今回はSimple OSの上から呼び出すから RET なんだな」というように、「そのプログラムをどう実行するのか」という目的とセットで捉えると、一気に理解しやすくなります。

詳しくは、こちらの記事で整理しています。

第4章:os.bin、Simple OS、asm.py のエラー表示

第4章からは、これまでの「小さな単体プログラムだけを動かす」段階を終え、いよいよ「Simple OS を起動し、そのOS上で様々なコマンド(プログラム)を動かす」という本格的な段階へと進みます。

この章では、reg(レジスタ表示)、ps(タスク一覧)、exec(プログラム実行)、date(日付表示)などのコマンドを、Simple OSのコンソール上で実際に動かしていきます。

1. 第4章以降の「emu.py」の役割

ここで大切なのは、「第4章以降の emu.py は、基本的に os.bin(OS本体)を起動するためのもの」と割り切って考えることです。
つまり、以下のコマンドを実行した時点で、まずはOS本体が立ち上がることになります。

python emu.py

第1章〜第3章までの感覚が残っていると、つい以下のようにファイル名を指定して実行したくなりますが、これを行っても hello.bin が直接読み込まれているわけではないので注意してください。

python3 emu.py asm_code/hello.bin

混乱を防ぐためにも、第4章以降は以下の3つのパターンを明確に分けて整理しておきましょう。

  • Simple OS を起動する場合は python emu.py を使います
  • hello.bin のような単体プログラムを直接確認したい場合は early.py を使います
  • Simple OS の exec から外部プログラムを実行する場合は、.bindir/ 配下に置きます

2. execから呼び出すプログラムの配置方法

たとえば、円周率計算プログラム(pi.bin)を Simple OS の exec コマンドから起動したい場合は、以下のようにファイルを配置してエミュレータを立ち上げます。

# 1. アセンブルしてバイナリを作成
python asm.py pi.asm

# 2. dir ディレクトリを作成し、そこにバイナリをコピー
mkdir -p dir
cp pi.bin dir/pi.bin

# 3. エミュレータ(Simple OS)を起動
python emu.py

Simple OS が無事に起動したら、プロンプトに対して以下のように入力することで実行できます。

> exec pi.bin

もし dir/pi.bin が正しい場所に置かれていない状態でこのコマンドを実行すると、File open error. というエラーになります。これは pi.asm の文法エラーではなく、「Simple OS が探しに行った場所に pi.bin が見つからなかった」という意味の、ファイル配置ミスによるエラーです。

3. asm.py の分かりにくいエラー表示への対策

第4章では、アセンブラ(asm.py)を実行した際のエラー表示でも迷いやすいポイントがあります。
たとえば、os.asm の中でラベル名(ジャンプ先などの名前)をタイポ(入力ミス)してしまうと、アセンブリのエラーではなく、以下のようなPython自体の長くて不親切なエラーが画面に出てしまうことがあります。

NameError: name '_get__nth_token_end' is not defined

このような画面が出ると「ツール(asm.py)自体が壊れてしまったのかな?」と焦ってしまいますが、原因は asm.py の故障ではなく、os.asm(アセンブリコード側)のラベル名のタイポです。見るべきなのはPythonのスクリプトではなく、自分が書いたアセンブリの記述箇所になります。

この分かりにくいエラーを解消するため、補助リポジトリではエラーを読みやすくした asmx.py というツールを用意しています。

python asmx.py os.asm

この asmx.py を使ってアセンブルを行うと、未定義のラベルや不正なレジスタ名などがあった場合に、「どのファイルの何行目でエラーが起きているのか」を分かりやすく教えてくれるため、デバッグの効率が格段にアップします。

詳しくは、こちらの記事で整理しています。

第7章:taskexec、ページテーブル、仮想メモリ

第7章では、いよいよ「タスクごとに独立したメモリ空間を持たせる仕組み(仮想メモリ)」を実装します。ここから一気にOSらしさがぐっと強くなります。

1. 第5章・第6章と第7章の違い

  • 第5章・第6章(タスク管理・タイマー割り込み)
    「今、どのタスクをCPUで動かすか」という時間の切り替えの仕組みを学びました。
  • 第7章(ページテーブルの導入)
    時間の切り替えに加え、空間(メモリの見え方)の切り替えが加わります。

「ページテーブル」とは、プログラム側から見えている住所(論理アドレス)と、実際の物理メモリ上の住所(物理アドレス)をマッピングする「対応表」のようなものです。

この仕組みのおかげで、タスク1・タスク2・タスク3が、すべて「自分は 0x00000 から始まっている」と思い込みながら、実際にはメモリ上の異なる物理ページに配置されて動作できるようになります。

2. taskexec コマンドとタスクの限界

第7章で taskexec hello_task.bin
を連続で実行すると、タスク1からタスク3へと順番にプログラムが割り当てられていきます。

> taskexec hello_task.bin
> taskexec hello_task.bin
> taskexec hello_task.bin
> taskexec hello_task.bin
Could not assign tid.

3回目までは正常にタスクID(tid)が割り当てられますが、4回目で Could not assign tid. というエラーが出ます。これは、Simple OSに用意されている「タスクの最大枠」をすべて使い切ったことを意味しています。

3. 「タスクが割り当てられたのに動かない?」の罠

ここで迷いやすいのが、「タスクIDは割り当てられたのに、プログラム(Hello, World!)が画面に表示されない」という現象です。

書籍の解説や途中の図だけをそのまま追いかけて実装すると、新しく作ったタスク1〜3の初期 PC(プログラムカウンタ:次に実行する場所)が、OSのアイドル状態を維持する idle_loop のままになってしまう場合があります。

PCが idle_loop を指したままだと、タスクが切り替わっても hello_task.bin の先頭へと処理が進みません。そのため、メモリ上にはプログラムがあるのに画面には何も出ない、という状態になります。

ちなみに、作者様の公式リポジトリ(v1.1)を確認すると、タスク1〜3の初期 PC はしっかりと 0 に設定されています。

MOVI    R0, 0
PUSH    R0              ; PC

この 0 が、各タスクから見た「先頭アドレス」です。ページテーブルのおかげで、同じ 0x00000 という指定であっても、タスクごとに異なる本物のメモリへと正しく誘導されます。

4. 第7章を乗り越えるための細かなチェックポイント

ソースコードを書き換える際は、以下のポイントでタイポやエラーが起きやすいので注意してください。

  • T0_PT の中央はアルファベットの「O(オー)」ではなく、数字の「0(ゼロ)」です。
  • print_t1_message から print_t3_message の定義を消す場合は、参照側も合わせて整理します
  • PT レジスタとページテーブルの対応を見ると、アドレス変換の流れが追いやすくなります
  • ページテーブルのエントリが 0xFF になっている論理ページへアクセスすると、ページフォルトとして扱われます

第7章は一気に難しく見えますが、実際には「CPUの実行状態(レジスタなど)を切り替える話」と、「ページテーブルによってメモリの見え方を切り替える話」の2つに頭を切り分けて整理すれば、迷わずにコードを追いかけられるようになります。

詳しくは、こちらの記事で整理しています。

どの記事から読むか(トラブルシューティング・ナビ)

現在手元で直面しているエラーやつまずきポイントに合わせて、以下の表から読むべき記事を選んでみてください。原因をすばやく特定するためのヒントが見つかるはずです。

困っていること読む記事
Wrote asm_code/hello.bin ... という表示の意味が分からない第3章の記事
python3 emu.py asm_code/hello.bin を実行すると Simple OS が起動してしまう第3章の記事
HALTRET の使い分けや違いが分からない第3章の記事
exec pi.bin を実行したときに File open error. になる第3章または第4章の記事
os.binhello.bin の関係で迷っている第4章の記事
asm.py を実行したときに長い NameError が発生する第4章の記事
taskexec を実行したのに画面に Hello, World! が表示されない第7章の記事
T0_PT の記述ルールやページテーブルの仕組みが分かりにくい第7章の記事
ページフォルト(例外処理)が具体的に発生する条件を確認したい第7章の記事

特に今つまずいている箇所がない場合や、最初から体系的に振り返りたい場合は、第3章の記事 ➔ 第4章の記事 ➔ 第7章の記事 の順に読み進めていくのが一番おすすめです。

このシリーズで大事にしている見方

このシリーズでは、「書籍の本文とGitHubの最新コード、どちらかが間違っている」と決めつけないことを大切にしています。

その理由は、実際に手元で動かしたときに出るエラーやつまずきの原因は、コードの間違いではなく「実行方法や前提条件の違い」にあることがほとんどだからです。

たとえば、hello.bin を単体で動かす場合と、os.bin(Simple OS)を起動した上で exec コマンドからプログラムを呼び出す場合では、同じCPUエミュレータを使っていても内部の処理フローが全く異なります。

この「今、どのような前提で動いているのか」という違いを常に意識できるようになると、以下のようなセルフチェックが自然とできるようになります。

  • いま実行しているのは early.pyemu.py
  • いまエミュレータに読み込まれているのは hello.bin か、それとも os.bin
  • プログラムの最後は HALT がよいのか、RET がよいのか
  • exec コマンドが探している .bin ファイルは、正しく dir/ 配下に置かれているか
  • 新しく作ったタスクの初期 PC(プログラムカウンタ)は、どこを指しているか
  • PT レジスタは、いまどのページテーブルを指しているか

OS自作の学習において、一番の迷子ポイントはエラーそのものよりも、「いま自分は、どの段階のコードを、どの方法で動かしているのか」という現在地を見失ってしまうことにあります。

このシリーズでは、そんな複雑に絡まったポイントを一つずつ丁寧にほどきながら、皆さんが迷子にならずに楽しくOS自作を完走できるようサポートしていきます。

次のステップへ進むためのロードマップ

OS自作の道のりは長く見えますが、各章の「核心」さえ押さえられれば、確実に一歩ずつ前へ進むことができます。以下をこれからの学習の目安にしてみてください。

  • 第3章をクリアする目安
    asm.py(アセンブラ)、early.py(単体実行)、emu.py(OS起動)のそれぞれの役割を明確に区別して説明できるようになること。これができれば、第4章へのステップアップはスムーズです。
  • 第4章をクリアする目安
    「Simple OSの起動」と「exec コマンドによる外部プログラムの実行」という、OSとアプリケーションの関係性を区別できるようになること。この理解が、後半のマルチタスクの実装にしっかりと繋がっていきます。
  • 第7章をクリアする目安
    すべて同じ 0x00000 というアドレスを指しているのに、タスクごとにまったく別々の物理メモリへと対応している様子を確認できること。これが実感できれば、仮想メモリという巨大な山の入り口は完全に捉えたも同然です。

最初は画面に出てくる細かなエラーに一喜一憂してしまうかもしれません。しかし、仕組みを一つずつ紐解いていけば、OSが「CPUの状態」と「メモリの見え方」をどのようにコントロールしているのか、少しずつつながっていきます。

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

この記事を書いた人

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

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

目次