オペレーティングシステム

本書を読むには予備知識としてアセンブラの知識と、X86系CPUのレジスタなどのアーキテクチャの知識が必要である。
一般的にプログラミングにおいて、ハードウェアの制御は、コンパイラが対応していない命令(特権命令やIO命令など)はアセンブリ言語で記述する事になる。
もし知らなければwikibooks記事『X86アセンブラ』などで解説してある。特に『X86アセンブラ/GASでの文法』『X86アセンブラ/x86アーキテクチャ』『X86アセンブラ/x86アセンブラ』では初心者むけに説明してある。
本書では触れてないが「カーネル」とか「ユーザランド」とかの用語についてはwikibooks『高等学校工業/ソフトウェア技術』などで触れてある。
本書はタイトルが今のところは「オペレーティングシステム」だが、実際はコンピュータアーキテクチャ理論やデジタル回路理論など低レイヤーの理論や実務的知識が混在したものになっている(あとで整理する)。大学の科目の分類が縦割りのタコツボ・時代おくれで、全体像が分かりづらいので、予定では、今後の構成でも、意識的にコンピュータアーキテクチャなど関連分野の説明を、整理後にも、ある程度は残しておく予定。
なのでOSと言うタイトルなのにマイコンCPU(Z80など)にも触れているという状況である。
オペレーティングシステムは、ユーザーがハードウェアレベルのプログラミングをする必要をなくし、パソコンをより使いやすくするために設計されています。UEFIを使用する場合、起動時にUEFIファームウェアがハードウェアを初期化し、UEFIブートローダーを使用してオペレーティングシステムを起動します。デバイスドライバは、オペレーティングシステム開発者が自分で作成する必要があります。
BIOSは古くから使われてきたブートストラップ方式で、起動時にハードウェアを初期化し、オペレーティングシステムを起動していました。しかし、BIOSにはいくつかの制限があり、セキュリティや機能性の向上が必要とされるようになりました。そのため、UEFIが開発され、BIOSからUEFIに移行することが決定されました。UEFIは、より高度なセキュリティ機能、拡張性、性能の向上など、多数の利点を提供しています。現在、多くのPCはUEFIを使用しており、BIOSよりも優れたブートストラップ方式として認められています。
ここでは、歴史的な経緯を紹介するためBIOSにも言及します。
PCに電源を入れた瞬間から順に、以下のようなプロセスが起こります。
UEFI/GPTの場合、PCに電源を入れた瞬間から順に、以下のようなプロセスが起こります。
UEFI/GPTの場合は、UEFIがBIOSよりも高度な機能を提供するため、より高速で信頼性の高いブートプロセスを提供します。 また、GPTはMBRよりも多数のパーティションをサポートし、パーティションのサイズ制限も緩和されます。
BIOSとUEFIは、システムの初期化やブートプロセスに必要なハードウェア情報の取得と管理を行うために、メモリマップを使用します。以下に、BIOSとUEFIのメモリマップの違いを説明します。
UEFIのメモリマップは、より柔軟性があり、より高度な機能を提供します。UEFIは、物理メモリのアドレッシングやデバイスドライバの管理により高度な機能を提供するため、BIOSよりも多数のOSに対応することができます。また、UEFIのメモリマップは、UEFIサービスを介して、システムの状態の取得や変更を行うことができるため、BIOSよりも柔軟性があります。
BIOSとUEFIは、ハードウェアの初期化やオペレーティングシステムの起動など、システムのブートプロセスに必要なタスクを実行するために、割り込みを使用します。以下に、BIOSとUEFIの割り込みの違いを説明します。
U EFIの割り込みは、BIOSと比較して、より高度な機能を提供することができます。UEFIは、C言語で書かれたサービスを呼び出すことによって、より高度な機能を提供できます。これにより、UEFIは、BIOSよりも柔軟性があり、より多数のオペレーティングシステムやアプリケーションに対応することができます。
UEFI(Unified Extensible Firmware Interface)は、従来のBIOS(Basic Input/Output System)に代わる新しいファームウェア規格です。 UEFIは、従来のBIOSに比べていくつかの優れた機能を提供しています。本稿では、UEFIがなぜ必要になり、何が優れているのかについて説明します。
[UEFIとは何か、従来のBIOSとの違いは何かについて簡単に説明します。]
UEFI(Unified Extensible Firmware Interface)は、コンピュータの起動時に実行されるファームウェア規格です。従来のBIOS(Basic Input/Output System)に代わるものとして開発されました。BIOSは、1970年代に開発され、コンピュータの起動時に必要な基本的なハードウェア設定や起動ドライブの選択を行うために使用されていました。しかし、BIOSは時代遅れになり、UEFIが開発されるようになりました。
UEFIは、BIOSに比べて多数の優れた機能を提供しています。セキュリティ機能が強化されており、悪意のあるソフトウェアをブロックすることができます。拡張性が高く、ドライバーやアプリケーションをファームウェアに統合できるため、機能追加が容易になっています。起動時間の短縮や大容量ストレージのサポート、グラフィカルインターフェイスの向上なども特徴的です。
従来のBIOSは、16ビットのアセンブリ言語で開発されており、拡張性が低かったため、UEFIは64ビットのC言語で開発されました。また、UEFIはBIOSよりも起動時間が短く、セキュリティ面でも優れているため、現代のコンピュータに必要な要件を満たすようになっています。
UEFIは、現代のコンピュータにとって欠かせない規格となっています。UEFIの普及により、コンピュータの起動時間が短くなり、セキュリティや機能性も向上するとともに、グラフィカルインターフェイスによってユーザーエクスペリエンスも向上しています。
[UEFIは、セキュリティ機能が強化されています。Secure BootやUEFI Secure Flashなど、UEFIにはセキュリティ機能が多数実装されています。これらの機能は、コンピュータの起動時に悪意のあるソフトウェアをブロックすることができます。]
UEFIのセキュリティ機能の一つであるSecure Bootは、UEFIファームウェアが起動する前に、署名されたオペレーティングシステムやドライバーを認証することができます。これにより、起動時に悪意のあるソフトウェアが起動するのを防止し、システムのセキュリティを高めることができます。Secure Bootは、UEFIの機能の中でも特に重要なセキュリティ機能であり、WindowsやLinuxなどのオペレーティングシステムでもサポートされています。
また、UEFI Secure Flashは、UEFIファームウェアを保護する機能です。これにより、UEFIファームウェアが偽造されたり、マルウェアに感染したりすることを防止することができます。UEFI Secure Flashは、UEFIファームウェアの更新時にも役立ちます。署名されたファームウェアを使用することで、正当な更新プログラムであることを確認し、システムを保護することができます。
さらに、UEFIにはTrusted Platform Module(TPM)と呼ばれるセキュリティチップをサポートすることができます。TPMは、コンピュータに組み込まれた暗号キーを使用し、システムのセキュリティを強化するための機能です。TPMは、セキュリティに特に敏感な企業や政府機関などで広く使用されています。
TPMは、Windows 11 から必須要件となっています。
以上のように、UEFIはセキュリティ面でも優れた機能を提供しています。これらの機能により、悪意のあるソフトウェアからコンピュータを保護し、ユーザーの個人情報や重要なデータを守ることができます。
[UEFIは、BIOSよりも拡張性が高いです。UEFIは、ドライバーやアプリケーションをファームウェアに統合できるため、従来のBIOSよりも機能追加が容易になっています。]
UEFIの拡張性は、従来のBIOSと比べて大幅に向上しています。UEFIは、ドライバーやアプリケーションをファームウェアに統合できるため、従来のBIOSよりも機能追加が容易になっています。これにより、UEFIのファームウェアに新しい機能を追加することができ、コンピュータの機能を拡張することができます。
また、UEFIはファームウェアに対して、エクステンシブル・ファームウェア・インターフェース(EFI)と呼ばれる標準インターフェースを提供します。このインターフェースは、ハードウェアとソフトウェアの間の橋渡しを行う役割を果たし、システムの拡張性を高めることができます。EFIは、ドライバーやアプリケーションが互換性のある方法でインストール、実行されることを保証することができます。
さらに、UEFIは、ファームウェアを実行するプラットフォーム上で動作するアプリケーションのためのフレームワークを提供することができます。これにより、ファームウェア上で直接アプリケーションを実行することができ、オペレーティングシステムが起動する前に必要な処理を行うことができます。UEFIアプリケーションは、UEFIの拡張性を高め、システム管理やユーティリティなどの機能を提供することができます。
以上のように、UEFIは、従来のBIOSよりも拡張性が高く、ドライバーやアプリケーションをファームウェアに統合できるため、機能追加が容易になっています。さらに、UEFIはEFIやUEFIアプリケーションの提供により、システムの拡張性を高めることができます。
アーキテクチャ非依存のドライバとは、特定のCPUアーキテクチャに依存せず、複数のアーキテクチャに対応できるドライバのことです。これは、コンピュータのハードウェアが異なる場合でも、同じドライバを使用できるため、ドライバの開発や管理が簡素化され、互換性の向上につながります。
UEFIにおいても、アーキテクチャ非依存のドライバが使用されています。これらのドライバは、EDK II(EFI Development Kit II)に含まれており、プラットフォームのアーキテクチャに関係なく、UEFI上で動作することができます。
また、UEFIのアーキテクチャ依存のドライバと同様に、アーキテクチャ非依存のドライバもUEFIのドライバモデルに基づいて開発されています。UEFIのドライバモデルは、PEI(Pre-EFI Initialization)フェーズ、DXE(Driver Execution Environment)フェーズ、BDS(Boot Device Selection)フェーズ、そしてランタイムフェーズの4つのフェーズに分かれており、それぞれのフェーズで必要なドライバがロードされます。アーキテクチャ非依存のドライバは、これらのフェーズのうち、DXEフェーズでロードされます。
[UEFIは、BIOSよりも高速な起動時間を実現します。UEFIは、従来のBIOSに比べて、メモリの管理が効率的であるため、起動時間を短縮できます。]
UEFIは、BIOSよりも高速な起動時間を実現することができます。これは、UEFIが従来のBIOSに比べて、メモリの管理が効率的であるためです。UEFIは、BIOSと比べて、より多くのメモリをサポートすることができます。また、UEFIは、メモリ管理において、従来のBIOSよりも優れたアルゴリズムを採用しています。これにより、起動時のメモリ管理がより効率的に行われ、起動時間が短縮されます。
さらに、UEFIは、ブートローダーの読み込みにおいても優れた性能を発揮します。UEFIは、従来のBIOSよりも大きなディスク容量をサポートしており、UEFIファームウェアによるブートローダーの読み込み速度が速いため、起動時間を短縮することができます。
また、UEFIは、ファームウェアにおいて、並列処理を採用することができます。これにより、UEFIは、複数のタスクを同時に処理することができます。従来のBIOSでは、タスクを順次処理する必要がありましたが、UEFIでは、並列処理により、タスクを同時に処理することができるため、起動時間が短縮されます。
以上のように、UEFIは、BIOSよりも高速な起動時間を実現することができます。これは、UEFIがメモリの管理が効率的であり、ブートローダーの読み込みにおいても優れた性能を発揮するためです。さらに、UEFIは、並列処理を採用することができるため、起動時間を短縮することができます。
[UEFIは、BIOSよりも大容量ストレージのサポートが容易です。UEFIは、GUID Partition Table(GPT)をサポートしているため、2TB以上のHDDやSSDなどの大容量ストレージをサポートすることができます。]
UEFIは、BIOSよりも大容量ストレージのサポートが容易です。UEFIは、GUID Partition Table(GPT)をサポートしており、2TB以上のHDDやSSDなどの大容量ストレージをサポートすることができます。
従来のBIOSでは、Master Boot Record(MBR)を使用していたため、2TB以上のストレージをサポートすることができませんでした。MBRは、512バイトのサイズ制限があるため、2TB以上のストレージを使用する場合には、パーティションを複数に分割する必要がありました。しかし、UEFIは、GPTをサポートしているため、2TB以上のストレージを単一のパーティションとして認識することができます。これにより、大容量ストレージの管理が容易になり、ストレージの最適化がより簡単になります。
また、UEFIは、多くのストレージデバイスに対して、より高度な機能をサポートすることができます。UEFIは、Advanced Host Controller Interface(AHCI)やNVMeなどの高度なストレージインターフェースをサポートしており、SSDなどの高速なストレージデバイスの性能を最大限に引き出すことができます。
以上のように、UEFIは、BIOSよりも大容量ストレージのサポートが容易であるため、2TB以上のHDDやSSDなどの大容量ストレージをサポートすることができます。また、UEFIは、高度なストレージインターフェースをサポートすることができるため、ストレージデバイスの性能を最大限に引き出すことができます。
[UEFIには、BIOSよりも優れたグラフィカルインターフェイスがあります。これにより、BIOSよりも視覚的な操作が可能になり、ユーザーエクスペリエンスが向上します。]
UEFIには、BIOSよりも優れたグラフィカルインターフェイスがあります。従来のBIOSは、文字ベースのインターフェイスを使用しており、操作が限定されていました。しかし、UEFIは、グラフィカルインターフェイスを使用することができます。これにより、視覚的な操作が可能になり、ユーザーエクスペリエンスが向上します。
UEFIのグラフィカルインターフェイスは、通常、マウスやタッチパッドを使用して操作することができます。また、キーボードでも操作することができます。グラフィカルインターフェイスを使用することで、UEFIの設定や構成をより簡単に行うことができます。また、グラフィカルインターフェイスを使用することで、BIOSよりも視覚的な情報を提供することができます。
さらに、UEFIのグラフィカルインターフェイスは、スクリーンリーダーや拡張性の高いアシスト技術をサポートすることができます。これにより、視覚障害者などのユーザーでも、UEFIを使用することができます。
以上のように、UEFIには、BIOSよりも優れたグラフィカルインターフェイスがあります。グラフィカルインターフェイスを使用することで、UEFIの設定や構成をより簡単に行うことができ、視覚的な情報を提供することができます。また、スクリーンリーダーや拡張性の高いアシスト技術をサポートすることができるため、視覚障害者などのユーザーでもUEFIを使用することができます。
UEFI Shellとは、UEFIベースのシステムにおいて、UEFIファームウェアが提供するコマンドラインシェルのことです。UEFI Shellは、UEFIファームウェアによって提供される機能の一部であり、ユーザーがUEFIファームウェアを直接制御することができます。
UEFI Shellは、UEFIファームウェアによって提供されるため、UEFIベースのシステムにはすべて搭載されています。UEFI Shellは、UEFIの拡張性の高さを示す代表的な例であり、ドライバーやアプリケーションをシェルに統合することができます。また、UEFI Shellは、UEFIのセキュリティ機能を使用してシステムのセキュリティを確保することもできます。
UEFI Shellは、UEFIベースのシステムの設定やデバッグ、トラブルシューティングなどに使用されます。ユーザーは、UEFI Shellを使用して、ストレージデバイスの操作、ファイルシステムの操作、ネットワークの設定などを行うことができます。UEFI Shellは、コマンドラインインターフェースを提供するため、一般的なコマンドやスクリプトを使用してタスクを自動化することもできます。
UEFI Shellは、UEFIの機能を理解するために必要な知識を持つエンジニアや開発者にとって、非常に便利なツールです。UEFI Shellを使用することで、システムの動作やパフォーマンスを最適化することができます。
UEFIは、ネットワークブートに必要なプロトコルとサービスを提供することにより、ネットワークサポートを強化しています。UEFIネットワークスタックには、以下のようなプロトコルやサービスが含まれています。
UEFIのネットワークサポートは、OSに依存しないため、UEFI自体がネットワーク接続を確立でき、ネットワーク上のリソースにアクセスできることを意味しています。これにより、ネットワーク上のリモートサーバーからOSをブートすることができます。
UEFIは、複数のファイルシステムをサポートしており、主要なものとしてFAT32、NTFS、ISO 9660、UDFなどがあります。UEFIファームウェアには、これらのファイルシステムにアクセスするためのドライバーが組み込まれています。
特に、UEFIはFAT32ファイルシステムを必須としており、UEFIシステムパーティションにはFAT32が使用されます。このパーティションには、UEFIファームウェアや起動ローダー、設定ファイルなどが含まれています。UEFIファームウェアは、このパーティションを自動的に認識し、必要なファイルを読み込んでシステムを起動することができます。
また、UEFIはネットワークファイルシステムにも対応しており、PXEブートなどを利用して、ネットワーク上のサーバーからOSを起動することができます。これにより、大規模なサーバー環境でのOSのデプロイやメンテナンスが容易になります。
Macintoshは、UEFIの前身であるEFI(Extensible Firmware Interface)を採用しています。EFIは、Appleが1998年に開発したプラットフォームファームウェアで、Intelと共同で開発されました。当初はMacintoshのプラットフォームファームウェアとして採用され、後にUEFIの基礎となりました。
MacintoshのEFIは、標準的なPCのUEFIとは異なり、Macintosh固有の仕様があります。たとえば、MacintoshのEFIは、GPTスキームに基づくブートデバイスの選択方法が異なります。また、MacintoshのEFIには、OS XやmacOSの起動時に使用されるBoot Campなどの固有の機能が含まれています。
Macintoshは、UEFIを正式にサポートするようになったのは、2015年以降のMacモデルからとなりました。これにより、Macintoshも他のPCと同様に、UEFIの機能やセキュリティを活用することができるようになりました。
IBMは、UEFIの策定において重要な役割を果たしました。IBMは、PC BIOSの標準化を提唱しており、1990年代初頭にPC/AT互換機向けのPC BIOS規格を開発しました。しかし、PC BIOSは、コンピュータの機能拡張に対応できなくなってきたことから、IBMは新しいファームウェア規格の策定を提唱しました。
その結果、UEFIが開発され、2005年にはUEFIフォーラムが設立されました。IBMはUEFIフォーラムの設立メンバーの1つであり、UEFIの開発において重要な役割を果たしました。また、IBMは、x86_64アーキテクチャにおけるUEFIの開発にも取り組んでいます。
PowerPCは、UEFIのサポートを提供するアーキテクチャの1つです。UEFIは、x86アーキテクチャだけでなく、ARMやPowerPCなどの様々なアーキテクチャをサポートしています。PowerPCにおいても、UEFIを使用することで、従来のBIOSよりも高速な起動時間や、セキュリティ機能の強化などの恩恵を受けることができます。また、UEFIを使用することで、PowerPCに対するソフトウェア開発の容易化も期待されています。

Power Architectureは、サーバーや組み込みシステムで使用されるプロセッサアーキテクチャのファミリーで、PowerPCプロセッサを含んでいます。UEFIは、x86アーキテクチャに最初に導入されたが、その後、他のアーキテクチャにも広がっていきました。Power Architectureに対するUEFIのサポートは、2012年にUEFI Forumによって標準化されました。
Power Architectureにおいて、UEFIを実装する際には、EFIファームウェアをPowerPCアーキテクチャ向けに移植する必要があります。Power Architecture向けのUEFIの実装には、オープンソースのOpenPOWER FoundationとIBMのOpen Firmwareが利用されています。また、UEFIの実装には、Linuxカーネルのようなオープンソースソフトウェアも使用されています。
[UEFIは、BIOSに比べてセキュリティや拡張性、起動時間の短縮など多数の優れた機能を提供しています。今後も、UEFIはコンピュータ業界で重要な役割を果たすことが予想されます。]
以上のように、UEFIはBIOSよりも優れた機能を持ち、より高度なセキュリティや拡張性、起動時間の短縮などの面で優れています。さらに、UEFIは大容量ストレージのサポートや、より視覚的なグラフィカルインターフェイスも提供しています。これらの機能は、現代の高度なコンピュータシステムにとって必要不可欠であり、UEFIはその要件を満たすために開発されました。 UEFIがBIOSを置き換えることにより、コンピュータ業界はますます効率的になり、コンピュータの起動時間が短縮され、より高度なセキュリティ機能を持つことができます。UEFIは、現在のコンピュータシステムの基盤となっており、今後もますます重要な役割を果たすことが予想されます。
一般に、エミューレーターを使って実験します。
無料のフリーソフトでも、w:QEMUなどのエミュレータを使えるので、アセンブラのw:NASM(無料ソフト)などで(アセンブリコード入力を経由して)機械語を入力していきます。
※ 次の節で、qemuの使い方を大まかに説明する。
原理的には、アセンブラを経由せずともバイナリエディタ(Hex editor)といわれるもので機械語を直接に書いてプログラムするのも、原理的には可能です。そもそもアセンブラ自体どうやって開発されたかを想像すれば、おそらくバイナリエディタを、流通しているCPU用のアセンブラ命令にあわせて特化してプログラミング用に作られたソフトウェア(がアセンブラ)なのでしょう。
原理の理解としてはバイナリエディタの存在に気づくことも重要ですが、しかしバイナリエディタによるプログラミングではコード記述が覚えづらく非現実的ですし、一目では内容が分かりづらいです。なので、一般にアセンブラで入力していくのが、OS製作では現実的でしょう。
さて、NASMは、あくまでエミュレーター上での仮想化なので、完全には仕組みを再現していませんが、しかし個人の学習では、止む(やむ)を得ません。 もしもエミュレータを使わずに、BIOSのブート設定を 毎回 書き換えて実機のパソコンでブートの実験などを毎回したとしたら、とてもメンドウです。なので、エミュレータを使って、手間を省きます。
なお、一般的なLinuxなどでは、リリース直前の開発の後半などで、確認のために最終的に開発者たちはDVDなどに書き込んでブートしてみたりするなどして、実験します(いわゆる「ベータテスト」などで、DVDのISOを無料配布している)。
なお余談ですが、OS自作でなくCPU自作をしたい場合、w:GNU Binutilsなどの無料のクロスアセンブラがありますので、そういうのを活用します。半導体製造工場などを個人は持ってないので、ソフト的にエミュレートするしかありません。
エミュレータにも種類や方式が色々とあります。
方式のひとつとして、ホストOSのインストールされているパソコンのCPUを間借りする方式があります。
別の方式として、ホスト側のCPUは間借りせずソフトウェア内に仮想のCPUを制作する方式のものなどもあります。
コミュニティ ベース の qemuやBochs というエミュレータは、(ホストCPUをあまり)間借りしないで、ソフト的に仮想のCPUを作る方式のものである
いっぽう、オラクル社のVirtual Box やヴイエムウェア社のVMWare というエミュレータは、ホストPCのCPUを間借りする方式のものである。
私たちの学習の目的には、VirtualBox 的に複数のCPUが混ざり合うと学習的に分かりづらくなるので、qemuのようなソフト的にCPUの機能を再現するほうが分かりやすいと考え、qemuを優先して紹介することにする。
また、qemu はオープンソースである。
なお、Virtual Box は昔は非オープンソースだったが、現在はオープンソース版のVirtual Box がある。
なお、オラクル社のようなホストCPUを間借りする方式にも長所はあり、仮想化の中では処理速度を速くしやすいという長所もある。

本書では、とりあえず qemu を前提として説明する。

前提として、ダウンロードとインストールは既に終わっていると仮定する。
qemuは、コマンドプロンプトから使うソフトウェアである。
なので、まずパス(Path)を通さないといけない。
具体的には、環境変数PATHにqemuのあるフォルダーを追加する。
さて、パスが追加し終わったら、はたして本当にパスが通ってるの確認のため、Windwosのコマンド プロンプトを起動して
というふうに、コマンド「qemu-img」でも入力してみよう。
なお、「qemu」というコマンドは無いので、「qemu」とコマンド入力しても。
さて qemu-imgコマンドは引数を指定して使用するコマンドなので、上記コマンド「qemu-img」を実行すれば、端末上で引数が足りないことをqemuから警告されるハズである。
のように、qemuから警告されれば、ひとまずqemuのインストールは成功である。
あらかじめ、起動したいファイルを機械語で作っておき、たとえば、testos.img とかの名前をつけて、ホームディレクトリーなどコマンド端末の認識できる場所に保存しておく。
そしてコマンド端末で
のようにコマンド qemu-system-i386 を使えばいい。

では、そのOSイメージをどうやって作るか。
原理は、
まず、アセンブラの nasm で書く。(nasm は Netwide Assembler ともいう。)
あらかじめアセンブラコード形式で、OSにしたいファイルをアセンブリコードで作っておいて、testos.asm などの名前で保存しておく必要がある。
nasmなら、testos.asm を(PEフォーマットでない、直訳の)機械語にするコマンドは
だけで終わる。
そして、こうして作成したブートイメージをqemuで起動する方法は、ホームファイルに先ほど作成した testos.img を置いた上で、
のコマンドだけで終わる。
では、元になるアセンブリコードをどうやって調達するか? とりあえず、ネットで読者の勉強用に(彼らの)自作OSのブートローダーのアセンブリコードを公開してくれている人がチラホラといるので、彼らのコードで実験するのが良いだろう。
ネット上に Bochs の使い方の入門書がぜんぜん無いので、wikibook で教えることにする。
Bochsでは、アセンブルはできない。
Bochsの用途は、すでに別ツール(たとえば nasm んど)で作成ずみの img ファイルをBochsで起動するだけのものである。
asmファイルからimgファイルへのアセンブルは、あらかじめ nasm などで行っておく。
Bochsの利便性は、コマンドを覚えなくて言いことである。起動コマンドのBochsだけ押せばウィンドウが起動するので、あとはそのウィンドウ側でGUI的に操作してエミュレータの起動をできるという便利ツールなエミュレータが Bochs である。
さて、Boshs をインストールしてから、環境変数(パス)の設定を終えれば、 コマンドプロンプトで、コマンド bochs で起動する。つまり
のように「bochs」の部分を入力する。
で、起動するとダイアログ画面が現れる。
さて、これだけでは、そもそも何のファイルを起動するかすらも設定されてない。なので、これから、このダイアログ画面で、それを設定する。
まず、真ん中の項目(中央ペイン)にある「Edit Option」 の「Disk & boot」をダブルクリックすると、画面が遷移して、
オプション画面である「Bochs Disk Options」画面になる。この項目で、何のファイルを起動するかを設定できる。 そのためには、
その「Bochs Disk Options」画面の中で、タブ「ATA Channel 0」> 子タブ「First HD/CD on Channel 0」 をクリックすると出てくる画面に、
上から2段目あたりに
という項目があるので、その のなかに、起動したいimgファイル名を入れる。
たとえば、「testos.img」ファイルを起動したいなら、
のようになる。
こうして、(おそらく)あとはこのオプション画面を終了して(ダイアログ・ウィンドウの右上のクローズ用の×ボタンを押せばいい)メインメニュー画面に戻り、右ペインにあるStartボタンを押せばいい。
Panic ウィンドウが表示されて Message 欄に「specified geometry doesn't fit on disk」 と書いてあるが、このまま左下の欄にある Continue をクリックして、OKを押せばいい。
なお、Path or physical device name の設定をしてないで空欄のままにしておくと、Startの際に Message 欄に 「no bootable device」 と出る。
BochsのDtart>OKの後のエミュレート起動後の終了方法は、ウインドウの上部にあるメニューバーの右側のほうに、○印の中にタテ線「|」のある終了ボタン「(|)」があるので、これを押せばいい。(右上の×ボタンは使えない。×ボタンをクリックしても反応しない。)
一般的なアセンブラには DB 命令というのがあり、これはORG命令などで指定したメモリに値を書き込む命令であるが、これで機械語も書き込みできる。
もし、機械語を書き込む先を、メモリではなく、ハードディスクやUSBメモリやフロッピーディスクなどのブート可能なメディアにすれば、原理的には、これでブートローダを作れる。
なお、DB とは data byte の略だと言われている。

さて、では、OSを作るためには、まず、ブートセクタを書き込めばいいのですが、では、どういう内容のことを書き込めばいいのでしょうか。

まず、ブートローダを書き込むわけですが、
規格により、
また、
と決まっている。

なので、このために
をどこかに書き込む必要があります。
times とは、nasm の擬似命令のひとつで、繰り返し命令のことです。
「aa55」の署名を書き込まずにQemu上で機械語を起動しても、いくつかのメッセージのあとに「No Bootable device」などと表示されるだけです。
さて、上記コードの場合は
という命令です。
$ は、そのtimes命令が出されたときの現在のアドレスです。
$$ は、現在のセクションの最初のアドレスです。
間違えて、「55aa」(マチガイ!)を書き込まないようにしてください。詳しくは『w:リトルエンディアン』で調べてください。
MBRのサイズは512バイトと決まっており、その末尾2バイトに「aa55」と書き込むので、times 繰り返し命令 では「510」と2バイトぶん、余らせています。

なので、とりあえず下記のように書き込みましょう。

成功すれば、 qemu上で、「H」と表示されます。
int 0x10 とは、ディスプレイへの割り込み命令のひとつです。int でBIOSにより割り込み命令を指示しています。intの引数で、どんな割り込みをするかを指示しており、int 0x10 はビデオサービスの割り込みです。
ですが、int 0x10 だけでは、ディスプレイに文字も画像も表示できません。
int 0x10に加えて、さらに、何を割り込ませるかの指定を行う必要があり、 mov ah, 0x0e で文字割り込みを指定しています。(※ 詳しくは『en:w:INT 10H』(英語版ウィキペディア)などを参照してください。)
int 0x10 の命令は、上記の手本コードのように、 アキュムレータ レジスタで指示しなければなりません。
なお、学校などで、もしかしたら、情報科学・計算機科学の教育では「アキュムレータ」とは「加減乗除などのためのレジスタ」とか習うかもしれませんが、しかし実際のCPUでは、(上記コード例のように)設定などの一時保存にもアキュムレータが流用されています。

。CR0の最下位ビットのが0ならリアルモードです。実際には、プロテクトモードの移行のためには、さらに グローバル デスクリプタ テーブル(GDT)というものを作成する必要があるが、初学者には当面は知らなくていいので、もうプロテウトモードの説明は後回しにする(※ 現時点では未記述)。

さて、この場合での int 0x10 は、alレジスタにある文字を表示できます。
「Hello 」と表示させたければ、「H」だけでなく、同様の操作を繰り返し、「e」「l」「l」「o」を追加で表示させればいいだけなので、

でブート後に「Hello」と表示できます。
ラベルやジャンプ命令を使うと、繰り返し命令を実装できる。
上の「Hello」のプログラムを、ラベルなどによる繰り返し処理でプログラムするとするなら、下記のようになるl

org 0x7c00 とは、このようなテキスト処理をするのにBIOSに予約されている領域が7c00 なので、そこから書き始める必要がある。 org は擬似命令であり、これからの書き込みのメモリ位置を指定する命令である。
また、 mov si, greet は形式的には、あたかもラベルgreetの内容を代入するかのような表現ですが、
実際は、単にgreet ラベルの先頭のメモリを代入するだけです。
int 0x10 は、繰返し命令の中で、毎回、使用する必要がある。
次のようlodsb 命令とsiレジスタを使っても良い。lodsbは[ds:si]からalに1バイト読込む(ロード load)命令であり、さらに使用後に自動的にsiレジスタの指し示す位置を1バイトぶんだけインクリメント(DF=0の場合、DF=1の場合はデクリメント)してくれるので、手間が省ける。
※ 2022年現在のx86プロセッサは、x86命令セットをマイクロ命令に分解し、マイクロ命令の組合せによっては複合的なマイクロ命令に合成し実行しています。ところが、lodsbの様な複雑な命令はマイクロ命令に翻訳されず固定的なマイクロコードにより実行されます。このため、かつて命令読込みのオーバーヘッドがなくせるという理由で「最適化」の代表だった複雑な命令を使うことは、現在は実行速度を低下させることになります。
解説
lodsb の内容は
と等価である(DF=0の場合、また OF, SF, ZF, AF, PF は inc si の結果により修飾されるので厳密には異なる)。

の代わりに
でも良い。
また test の代わりに
でも良い。
※ かつては、定数とレジスタの比較はレジスタファイルの読出しハザードがありストールの原因になりましたが、Sandy Bridgeマイクロアーキテクチャ以降のプロセッサであればマイクロフュージョンの対象となり、test命令やor命令を使うメリットはなく、專ら可読性を損なうだけです。
まず、設定として、
というコードが必要です。
int 0x10 はグラフィック割り込みです。
グラフィック割り込みの場合、ahは0に固定するように規格で決まっています。
alによって、ビデオモードを指定しています。
alが0x13なら、320 x 200ドット x 16色モード(8bitカラー)
という意味です。
一般的なパソコンではグラフィック描画用のVRAMアドレスは 0xa0000 から 0xaffffの領域が割り当てられています。
なので、mov命令で、このアドレスに書き込むと、VRAMに直書きできます。

で、問題は、通常の16ビットCPUモードのmov命令では、 メモリ番号は4ケタまでしかアクセスできない。「a0000」や「affff」は5ケタであることに注目。
で、しかも、ブートローダ起動中のリアルモードでは、この16ビットCPUのモードである(らしい)
なので、ともかく、通常の方法ではアクセスできない。
5ケタのメモリ番号にアクセスするためには、
「セグメント方式」という手法を使う。
簡単に言うと、
なお、上式の数は十進数である。
16進数になおすと、単にセグメントベース×16は、末尾に0をつけたすだけである。
これらのセグメントベースを格納するために「セグメントレジスタ」と言う専用のレジスタを使う。
16ビットCPUのセグメントレジスタには
の4つがある。
なおFSとGSは32ビットCPU以降のセグメントレジスタである。
CSは、CPUが実行するプログラムを格納するためのセグメントとして、CPUに使用させる。
DSは、メモリの読み書きといったデータ関係の手段のセグメントとして、CPUに使用させる。
SSはスタック関係のセグメントで使用。
「エクストラ」とは、「その他」とか「追加の」のような意味。
よく分からなければ「セグメントベース」でググると、詳しく紹介してくれている親切なITブロガー日本人さんが何人かネットにいるので、それらのページを参照してください。
で、書式は、[セグメントベース:オフセット]の書式である。

下記のようなコードで、ブート直後の画面の任意の場所にピクセル単位で色をぬれる。

さて、mov 命令でメモリに書き込むには mov BYTE [123], 0x4567 の書式で書き込みます。
この場合、メモリの123番地に、数値(16進数で)4567を書き込むわけです。
[ ] をつけると、メモリへの指示だとアセンブラなどが認識します。
ここでの「BYTE」 は1バイト長で書き込め、という命令です。
2バイト長なら(BYTE でなく) WORD になります。
4バイト長さなら DWORD になります。
なお
のように書いた場合は、メモリ123番地にある内容をレジスタalに書き込め、という命令になります。
このように、(書き込みだけでなく)メモリからの読み込みにも [ ] は使えます。
つまり、 [ ] をつけると、メモリへの指示だとアセンブラなどが認識します。

0xa0000は画面の左上だが、そこに色を塗っても見づらいので、
上記コードでは 0xa1111 から色を塗り始めることにした。
なお 0xaffff は画面の右下である。
画面の真ん中の上のほうに、なんか緑色っぽい線が5ミリくらいの長さで水平に引かれている結果が表示されると思う。
上記のコードでは、分かりやすさを重視して、あえて繰り返し命令(ラベルやジャンプ命令で実装できる)を使わなかったが、実際に図形を書きたい場合は、ラベルを活用して繰り返し命令で処理するのが効率的だろう。
なお、上記コード例1で
とあるが、
これを、(下記はエラーになる)
とまとめても、なぜかエラーになる。
なので、手本のコードのように、レジスタを経由する必要がある。
一般的名パソコンでは、英数字などのテキスト出力も、ブート直後の段階では、専用のメモリ領域が用意されるので、このメモリを書き換えることで、テキスト文字を出力できます。(ただし、漢字や平仮名・カタカナなどは無理。)
ブートプログラムの確認作業などに、便利な機能でしょう。

また、 int 命令によって、1文字ずつ書いていく方法は、原理は単純ですが、実装では、処理速度があまり速くないという問題があります。
ラベル命令やジャンプ命令などを使ってコードの行数を減らしても、int命令で書き込みをするよりも、テキスト用メモリを直に書き換えするほうが処理速度が速くなります。
一般的なパソコンでは 0xb800 からの領域がテキスト用メモリに割り当てられています。
なので、まず
と指定します。


メモリ領域のうち、0xa0000 から0xfffff までの領域は通常のパソコンでは、BIOSがハードウェアを管理するために使用することになっています。
このため、それらハード管理以外のソフトウェア的な処理をするためにメモリ使用したい場合は、この領域を避けてメモリを使用する必要があります。
こういった用途には Linuxなどの現代のオープンソースOSでは、 メモリを使用する際には 0x100000 以降の領域を使うのが一般的です。
原理的には 0xa0000 未満の領域も使用可能ですが、使いすぎ等のミスによって0xa0000以降にハミ出る恐れがあるので、なるべく 0x100000 以降の領域だけを使うほうが安全でしょう。

まず、フロッピーディスクやハードディスクなどに読み書きのできる割り込み命令で、 int 0x13 というのがあります。
2000年代の現代では、これを大容量デバイス用に拡張した拡張 int 0x13 というのがあります。
どちらの int 0x13 とも、ドライブ番号は、DLレジスタ(データレジスタの下位(Low)の部分)で指定します。
このように、int 0x13 では、割り込み時におけるレジスタの役割が決められています。
さて、拡張 int 0x13 を使って、フロッピーディスクやハードディスクなどの記憶媒体に割り込みをできます。(USB対応については今後の見通しは不明。書き込みできるものもあるようだが、)
int 13h は、レジスタなどの数値で 書き込みの方式や対象を指定します。拡張 int 13h と、非格調 int 13h では、レジスタの解釈が違っております。
格調 int 0x13 のほうの方式を LBA方式といいます。
格調 int 0x13 では、レジスタに収まりきらない様々な情報を、任にの Disk Addres Packet (DAP)という場所に配置しており、その形式も決まっています。
※ 調査中

なお、拡張されてない int 0x13 は、ハードディスク容量などの小さい時代の古い規格のものであり、現代では、読み書きに時間が掛かったり、あるいは不可能です。
非拡張の int 0x13 では、ドライブ番号は、DLレジスタ(データレジスタの下位(Low)の部分)で指定します。
AHレジスタが 0x02 なら 読込み、AHレジスタが 0x03 なら 書込み です。

int 13hともいう。
まず、int命令で、キーボードサービスの割りこみがあり、 int 0x16 がキーボードサービスである。
さてint 0x16 を呼び出す際、
なら、ah = 0x00 は、キーボードのキー入力待ちである。ah = 0x10 だと拡張キーボード対応らしい 。

実は一般的なパソコンでは、アセンブリ言語の命令で、いくつかのハードウェアに読み書きのアクセスするための、専用の命令がある。
IN 命令と、 OUT命令である。
そして、キーボードなど、昔のどこのパソコンにも存在したハードウェアは、実はハードウェア番号が決められている(「I/Oポートアドレス」などという)。
たとえば、キーボードはハードウェア番号(I/Oポートアドレス)が十六進数で0x60 番である。(メモリマップとは別。メモリアドレスの0x0060などにdb命令で書き込んでも、目的のハードにはアクセスできない。 )
IN命令で、引数で指定したレジスタ(普通はALレジスタやAXレジスタを指定する)に、もうひとつの引数で指定した目的デバイスから送られた値が保存されます。(引数の順序はアセンブラの種類などによって異なるので、説明を除外。)
また、OUT命令で、引数で指定したレジスタ(普通はALレジスタやAXレジスタを指定する)に格納されている値が、もうひとつの引数で目的デバイスに送られます。

たとえば、下記のようなコードで、キーボードの文字「E」または前後のWかRを押すと、1行ぶん下の位置に文字「G」が表示される。

in al, 0x60 を使えば、キーボードコントローラーから送られてきたキーも al に入力されます。
in al, 0x60 とは、けっして「レジスタ al に 60 を代入しろ!」(×、マチガイ)という意味ではなく(そもそも定数の代入だけなら mov 命令だけで可能である)、「ポートアドレス 0x60番 のポートから送信されてきたデータを、レジスタalに代入しろ」という意味です。勘違いしないでください。
そして、キーボードコントローラーのポートアドレスが 0x60 なので、めでたく、上記コードでキーボードから押されたボタンの情報を受け取れます。
さて、キーボードを押したとき、押したボタンに対応するスキャンコードが、パソコン内部にあるキーボードコントローラー(KBC)という装置に送信される仕組みになっています。
このスキャンコードは、アスキーコードとは異なります。
上記コード例にある cmp al, 18 の数値「18」とは、スキャンコードでの番号です。だいたいスキャンコードで18番のあたりが文字 W,E,R のどれかのあたりです。
また、アスキーでは「2」と「"」とは別の文字ですが、しかし日本語キーボードの場合、「2」と「"」はボタンが同じなので、スキャンコードでは同一になります。
このように、物理的に同じ位置にあるかどうかで、スキャンコードは決まります(なお、JIS配列キーボードやUS配列キーボードのように言語が違うキーボードでも、位置が同じなら、ほとんどのボタンのスキャンコードの内容も同じ場合が多い)。

また、スキャンコードは、押されている時に送信されるコード(「メイク コード」という)と、離した瞬間に送信されるコード(「ブレイク コード」という)とが、それぞれ違うコードです。
日本語キーボードは OADG という規格にほぼ統一されています。
ですが、世界的にスキャンコードの規格は、古いものでも3種類くらいあり、さらにUSBキーボードの規格は別です。このため、日本語 OADG のスキャンコードも、現在でも3種類くらいあります。
下記のリンクが詳しいです。
なお、一般的に「メイクコード/ブレイクコード」の書式です。
たとえば、ボタン「1」(「!」と同じ)のスキャンコードが「16 / F0 16」とかかれていれば、メイクコードが「16」であり、ブレイクコードが「FD 16」という意味です。(ある規格では、ブレイクコードは、メイクコードの先頭にF0がついたものになっている。)
なお、キーボードコントローラーもインテルなどが製造していました。かつて Intel i8042 というキーボードコントローラーが有名でした。
より正確な仕組みとしては、シリアル通信(PS 2 信号)などで送られた信号をKBCあたりでスキャンコードに変換しているわけですが、CPUから見ればスキャンコードしか見えないので、あまり気にしなくていいでしょう。

0から作るOS開発 カーネルローダその3 プロテクティッドモードへの移行とA20
※ 本wikibooksの当ページが完成するまでの間、上記の参考サイトが分かりやすくて役立つと思いますので勉強してください。


次のようなコードを使えばledが点滅するらしいのだが、しかしエミュレータでの実験では分からなかった(Windows起動により、すでにLEDが点灯しているので、区別しづらい)。
0xED というのは、LED点灯に関する命令の番号。

一般的なブートローダのいくつかには、ブートセクタに BIOS Parameter Block (BPB)というものが書かれており、これは BIOS への指示や設定を出すブロックです。
パソコンの電源を投入して、まず最初に起動するのは BIOS なのですから、このブロックが必要です。少なくとも Windows系OSのブートローダーでは、そうなっていると言われています。
このように、ブートセクタは、書式がほぼ決まっています。
という構成になっています。
ジャンプしてしまうので、BPB はプログラムカウンタでは読み取れず、BPBはBIOSしか読み取れないことに注目しましょう。
市販のOS自作本にある、DB命令で書き込む冒頭の 「DB 0xeb」 と言うのも、このJMP命令のことです。
JMP命令はX86系CPUの機械語では eb です。
『IA-32 インテル®アーキテクチャソフトウェア・デベロッパーズ・マニュアル』『中巻A:命令セット・リファレンスA-M』3-411


プロテクトモードとは、CPUの32ビットモード(および32ビット以上のモード)のこと。
32ビットモードには、アクセス権の無い状態からのアクセスを禁止するという、特権レベルによる保護機能などがあるので、プロテクトモードという。
プロテクトモードにいこうするためのコードの一部を抜粋すると、おおむね書きのような感じになる。
cr0レジスタの最下位ビット(PEビットという)が1だとプロテクトモードである、という仕様である。
の3行の処理で、cr0レジスタの最下位ビットを1に設定している。なお、このcr0の最下位ビットのことをプロテクト・エネーブルド pr0tect enebled という意味でPEビットという。
設定後にわざわざジャンプ命令 jmp を通す理由は、CPUの先読みした命令を破棄するためである 。ジャンプ命令には、先読みを破棄する機能がある。
なお、パイプラインという仕組みにより、CPUは先読みしている。このパイプラインの先読みを破棄することをフラッシュという。
実はCPUは、いくつか先の命令をすでに先読みしている(これがパイプライン)。プロテクトモード以降では、それが不具合の原因になるので、いったんフラッシュする(カラにする)必要がある。
なので、CPUのパイプラインをフラッシュするためにジャンプ命令を使っている。


さて、一般にWindowsやLinuxなどのOSには、パーティションという、インストール時にハードディスクの使用領域を決める機能がある。
実はCPU側に、メモリに関する機能だが、似たような動作を機能がある。
GDT(グローバル ディスクリプタ テーブル Global Descriptor Table)といって、メモリのアドレスのどこからどこまでがそのCPUで使える領域を定義する機能がある。
で、プロテクトモードでは、あらかじめ、このGDTを設定しないと動作しない。そういう仕様で、むかしのインテルあたりの人が決めてしまったので、従うしかない。
で、実はCPUにGDTレジスタ(GDTR)という、GDTの場所を保管する専用レジスタがあるので、このGDTレジスタにGDTのアドレスなどの情報を入れる必要がある。
さらに、このGDTレジスタに書き込むための専用の命令 lgdt (ロードgdt)があるので、これを使う必要がある。(「書き込みだから save では?」という疑問も、わくかもしれないが、こういう名前に決まってしまってるので、従うしかない。)

同様に IDT(Interrupt Descriptor Table)というのがある。
さらに、16ビット時代の昔はCPUのアドレスバスが20本までだったので、リアルモードでは利用するアドレスバスが20本までという制限が掛かっており、A00からA19までを使用している。A20以降はマスクされている。
この制限のことを「A20のマスク」という。プロテクトモードに以降するためには、このA20のマスクを解除しないといけない。
下記の順序で作業しないといけない。そういう仕様である。
A20マスクの解除には複数の方法がある。
キーボードコントローラから解除できる理由は、単に昔のインテルかどこかの人が設計したとき、たまたまキーボードコントローラ用のアドレスバスが余ってたからだけと言う理由らしく、あまり技術的な深い意味は無い。
なお作業の順番について、A20のマスク解除の順場は多少前倒しになっても平気なようである。
ある程度、理解が進むと、 Linux など実際に活用されているオープンソースOSのブートローダを調べたいと感じるかもしれません。
まずOSの起動で最初に動くのはブートローダだからです。
しかし Linux を開発している kernel.org のサイトには、ブートローダは無いのです。
Linux で仕様されているブートローダは、Gnu(グニュー)というオープンソース・ソフトウェア団体の作っている GRUB (グラブ)というソフトウェアです。
なのでブートローダをソースコードを探す場合も、Gru Grub のウェブサイトを探す必要があります。
書籍だとアスキー出版『Linuxのブートプロセスを見る』などの題名の書籍で Grub を紹介してるので、ついつい何となく、ソースコードを読むためにリーナスの管理している kernel.org を探しがちですが、しかし、よくよく考えたら、Grub は Linux ではありません(実際、Windows版Grubもある)。
もし『Grubのブートプロセスを見る』だと売れないので、出版社が「Linuxのブートプロセスを見る」という題名にしたのでしょう。
私たちOS開発をしたい読者は、けっして出版社にマインド・コントロールされたままでは、イケません。真実「Grub は Linux ではない」に気がつきましょう。
Grub のソースコードのダウンロードにgitコマンド(git clone などのコマンド)が必要なので、あらかじめインストールしておくか、Gitコマンドが最初から使える Ubuntu か Fedora などのLinuxをパソコンにインスト-ルしておきましょう。
Git コマンドのインストールと、Git Hub などのウェブサイトとは別物ですので、混同しないようにしましょう。
外部リンク: Gitの公式サイト
なお Windows版のGitはVimエディターにしか対応してないとの情報がインストール時に出ますが、しかしわざわざvimを別途インストールしなくても、Win版GitをインストールすればWindwowsコマンドプロンプトでGitコマンドを使えるようになります。