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

このページ「オペレーティングシステム」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。
予備知識

本書を読むには予備知識としてアセンブラの知識と、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にも言及します。

BIOS/MBR によるブートストラップ手順(旧式)編集

PCに電源を入れた瞬間から順に、以下のようなプロセスが起こります。

  1. 電源投入時
    1. 電源がONになり、電源ファンやシステムファンが回り始めます。
    2. 電源が正常に動作しているかどうかを確認するために、POST(Power-On Self-Test)が実行されます。
      POST
      POSTは、コンピュータが正常に動作しているかどうかを確認するために、ハードウェアの自己診断を行います。
    3. POSTが開始されると、BIOS(Basic Input/Output System)が起動されます。
      BIOS
      BIOSは、コンピュータが起動するために必要な最低限のハードウェア情報を提供します。
      BIOSは、ハードウェアの設定を変更できる設定ユーティリティを提供し、またブートデバイスを指定することができます。
      MBR
      BIOSがブートデバイスとして指定された場合、MBR(Master Boot Record)が読み込まれます。
      MBRは、ディスクの先頭に位置する特別な領域で、OSを起動するために必要な情報が含まれています。
      PBR
      MBRが読み込まれると、MBRによって指定されたパーティションにあるPBR(Partition Boot Record)が読み込まれます。
      PBRは、パーティション内のOSを起動するために必要な情報が含まれています。
      OS
      PBRによって指定されたOSが起動されます。
      OSは、コンピュータを操作するために必要なプログラムやデータをメモリにロードし、操作を開始します。
UEFI/GPT によるブートストラップ手順(現行)編集

UEFI/GPTの場合、PCに電源を入れた瞬間から順に、以下のようなプロセスが起こります。

  1. 電源投入時
    1. 電源がONになり、電源ファンやシステムファンが回り始めます。
    2. UEFIファームウェアが起動されます。
      UEFI
      UEFI(Unified Extensible Firmware Interface)は、BIOSと同様にハードウェアの初期化を行いますが、より高度な機能を提供します。
      UEFIは、UEFIモードで起動するために必要な情報を提供し、UEFI設定ユーティリティを提供します。
      GPT
      UEFIが起動した後、GPT(GUID Partition Table)が読み込まれます。
      GPTは、ディスクの先頭に位置する特別な領域で、パーティション情報を格納しています。
      Boot Manager
      UEFIが起動した後、UEFIファームウェアはUEFI Boot Managerを実行し、起動可能なデバイスとブートローダーのエントリーを探します。
      Boot Managerは、起動するOSを選択するためのUEFIブートローダーを起動します。
      OS Loader
      UEFIブートローダーは、起動するOSのローダーを読み込み、OSを起動します。
      OSは、コンピュータを操作するために必要なプログラムやデータをメモリにロードし、操作を開始します。

UEFI/GPTの場合は、UEFIがBIOSよりも高度な機能を提供するため、より高速で信頼性の高いブートプロセスを提供します。 また、GPTはMBRよりも多数のパーティションをサポートし、パーティションのサイズ制限も緩和されます。

メモリマップ編集

BIOSとUEFIは、システムの初期化やブートプロセスに必要なハードウェア情報の取得と管理を行うために、メモリマップを使用します。以下に、BIOSとUEFIのメモリマップの違いを説明します。

BIOSのメモリマップ
  • BIOSのメモリマップは、物理メモリを基準にしています。
  • BIOSのメモリマップには、システムのメモリアドレス空間に含まれるRAM、ROM、PCIデバイス、システムファームウェアなどが含まれます。
  • BIOSのメモリマップは、32ビットアドレッシングを使用しているため、システムのメモリサイズが4GBを超える場合には問題が発生することがあります。
UEFIのメモリマップ
  • UEFIのメモリマップは、仮想メモリを基準にしています。
  • UEFIのメモリマップには、物理メモリ、PCIデバイス、システムファームウェア、UEFIサービス、ACPIテーブルなどが含まれます。
  • UEFIのメモリマップは、64ビットアドレッシングを使用しているため、システムのメモリサイズに制限がなく、大量の物理メモリを効率的に扱うことができます。

UEFIのメモリマップは、より柔軟性があり、より高度な機能を提供します。UEFIは、物理メモリのアドレッシングやデバイスドライバの管理により高度な機能を提供するため、BIOSよりも多数のOSに対応することができます。また、UEFIのメモリマップは、UEFIサービスを介して、システムの状態の取得や変更を行うことができるため、BIOSよりも柔軟性があります。

割り込み編集

BIOSとUEFIは、ハードウェアの初期化やオペレーティングシステムの起動など、システムのブートプロセスに必要なタスクを実行するために、割り込みを使用します。以下に、BIOSとUEFIの割り込みの違いを説明します。

BIOSの割り込み
  • BIOSは、16ビットリアルモードを使用しており、割り込みの処理には、アセンブリ言語で書かれたINT命令を使用します。
  • BIOSの割り込みは、BIOS ROM内に格納された割り込みベクターテーブルを参照して、処理を実行します。
  • BIOSの割り込みは、ハードウェアの制御、デバイスドライバの初期化、オペレーティングシステムの起動などのタスクを実行します。
UEFIの割り込み
  • UEFIは、64ビットモードを使用しており、割り込みの処理には、C言語で書かれたUEFIサービスを呼び出すことによって実行されます。
  • UEFIの割り込みは、UEFIファームウェア内に格納されたSystem Service Table(SST)を参照して、処理を実行します。
  • UEFIの割り込みは、ハードウェアの制御、デバイスドライバの初期化、オペレーティングシステムの起動などのタスクを実行しますが、BIOSと比較して、より高度な機能を提供することができます。

U EFIの割り込みは、BIOSと比較して、より高度な機能を提供することができます。UEFIは、C言語で書かれたサービスを呼び出すことによって、より高度な機能を提供できます。これにより、UEFIは、BIOSよりも柔軟性があり、より多数のオペレーティングシステムやアプリケーションに対応することができます。

UEFIはなぜ必要になり何が優れているのか?編集

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 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ネットワークスタックには、以下のようなプロトコルやサービスが含まれています。

  1. Preboot Execution Environment(PXE): PXEは、ネットワーク上からブート可能なイメージを取得するためのプロトコルです。UEFIのネットワークスタックには、PXEブートをサポートするコードが含まれています。
  2. Transmission Control Protocol/Internet Protocol(TCP/IP): TCP/IPは、インターネットを構成するためのプロトコルです。UEFIネットワークスタックには、TCP/IPスタックが含まれており、ネットワーク上のデバイスと通信するためのプロトコルとして使用できます。
  3. User Datagram Protocol(UDP): UDPは、TCP/IPの一種のプロトコルで、信頼性の低い通信に使用されます。UEFIネットワークスタックには、UDPスタックが含まれており、PXEブートなどの通信に使用できます。
  4. Internet Control Message Protocol(ICMP): ICMPは、ネットワーク上で通信エラーを処理するためのプロトコルです。UEFIネットワークスタックには、ICMPスタックが含まれており、ネットワーク上の通信エラーを処理するために使用できます。

UEFIのネットワークサポートは、OSに依存しないため、UEFI自体がネットワーク接続を確立でき、ネットワーク上のリソースにアクセスできることを意味しています。これにより、ネットワーク上のリモートサーバーからOSをブートすることができます。

UEFIのファイルシステム編集

UEFIは、複数のファイルシステムをサポートしており、主要なものとしてFAT32、NTFS、ISO 9660、UDFなどがあります。UEFIファームウェアには、これらのファイルシステムにアクセスするためのドライバーが組み込まれています。

特に、UEFIはFAT32ファイルシステムを必須としており、UEFIシステムパーティションにはFAT32が使用されます。このパーティションには、UEFIファームウェアや起動ローダー、設定ファイルなどが含まれています。UEFIファームウェアは、このパーティションを自動的に認識し、必要なファイルを読み込んでシステムを起動することができます。

また、UEFIはネットワークファイルシステムにも対応しており、PXEブートなどを利用して、ネットワーク上のサーバーからOSを起動することができます。これにより、大規模なサーバー環境でのOSのデプロイやメンテナンスが容易になります。

MacintoshとUEFI編集

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は、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編集

PowerPCは、UEFIのサポートを提供するアーキテクチャの1つです。UEFIは、x86アーキテクチャだけでなく、ARMやPowerPCなどの様々なアーキテクチャをサポートしています。PowerPCにおいても、UEFIを使用することで、従来のBIOSよりも高速な起動時間や、セキュリティ機能の強化などの恩恵を受けることができます。また、UEFIを使用することで、PowerPCに対するソフトウェア開発の容易化も期待されています。


POWERアーキテクチャとUEFI編集

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は、現在のコンピュータシステムの基盤となっており、今後もますます重要な役割を果たすことが予想されます。

UEFIの歴史
UEFIの歴史は、1998年にIntelによって最初に提唱されました。当時、従来のBIOSは、IBM PC互換機において標準的なファームウェアでしたが、その限界が問題となっていました。UEFIの開発は、BIOSの制限を克服するために行われました。

UEFIの開発には、当初からIntelのほか、IBM、AMD、Microsoft、およびHPなどの大手企業が参加しました。2005年には、UEFIフォーラムが設立され、UEFIの標準化と普及を促進するための活動が開始されました。その後、UEFIの仕様は、フォーラムに参加する会員企業によって共同で策定されました。

UEFIの初期バージョンは、主にサーバー向けに開発され、デスクトップやモバイルデバイス向けのバージョンは、より後のバージョンで提供されました。UEFI 2.0が2006年にリリースされ、UEFI 2.7Aが2018年にリリースされました。

UEFIは、BIOSよりも高速でセキュアな起動プロセスを提供することができるため、現在では多くのコンピュータに採用されています。UEFIの普及は、現代のコンピュータアーキテクチャにおいて重要な役割を果たしています。


実験の手段編集

一般に、エミューレーターを使って実験します。

無料のフリーソフトでも、w:QEMUなどのエミュレータを使えるので、アセンブラのw:NASM(無料ソフト)などで(アセンブリコード入力を経由して)機械語を入力していきます。

※ 次の節で、qemuの使い方を大まかに説明する。

原理的には、アセンブラを経由せずともバイナリエディタ(Hex editor)といわれるもので機械語を直接に書いてプログラムするのも、原理的には可能です。そもそもアセンブラ自体どうやって開発されたかを想像すれば、おそらくバイナリエディタを、流通しているCPU用のアセンブラ命令にあわせて特化してプログラミング用に作られたソフトウェア(がアセンブラ)なのでしょう。

※ なお、日本語ではバイナリ(2進数)エディタといいますが、しかし英語では16進数(hex)で「ヘックス エディタ」Hex editor といいます。なので Linux などで対応の機械語エディタを探す場合は、16進エディタような名前のソフトを探すことになります。


原理の理解としてはバイナリエディタの存在に気づくことも重要ですが、しかしバイナリエディタによるプログラミングではコード記述が覚えづらく非現実的ですし、一目では内容が分かりづらいです。なので、一般にアセンブラで入力していくのが、OS製作では現実的でしょう。


さて、NASMは、あくまでエミュレーター上での仮想化なので、完全には仕組みを再現していませんが、しかし個人の学習では、止む(やむ)を得ません。 もしもエミュレータを使わずに、BIOSのブート設定を 毎回 書き換えて実機のパソコンでブートの実験などを毎回したとしたら、とてもメンドウです。なので、エミュレータを使って、手間を省きます。

なお、一般的なLinuxなどでは、リリース直前の開発の後半などで、確認のために最終的に開発者たちはDVDなどに書き込んでブートしてみたりするなどして、実験します(いわゆる「ベータテスト」などで、DVDのISOを無料配布している)。


なお余談ですが、OS自作でなくCPU自作をしたい場合、w:GNU Binutilsなどの無料のクロスアセンブラがありますので、そういうのを活用します。半導体製造工場などを個人は持ってないので、ソフト的にエミュレートするしかありません。

備考: エミュレータの種類編集

エミュレータにも種類や方式が色々とあります。

方式のひとつとして、ホストOSのインストールされているパソコンのCPUを間借りする方式があります。

別の方式として、ホスト側のCPUは間借りせずソフトウェア内に仮想のCPUを制作する方式のものなどもあります。


コミュニティ ベース の qemuやBochs というエミュレータは、(ホストCPUをあまり)間借りしないで、ソフト的に仮想のCPUを作る方式のものである[1]

いっぽう、オラクル社のVirtual Box やヴイエムウェア社のVMWare というエミュレータは、ホストPCのCPUを間借りする方式のものである。


私たちの学習の目的には、VirtualBox 的に複数のCPUが混ざり合うと学習的に分かりづらくなるので、qemuのようなソフト的にCPUの機能を再現するほうが分かりやすいと考え、qemuを優先して紹介することにする。

また、qemu はオープンソースである。

なお、Virtual Box は昔は非オープンソースだったが、現在はオープンソース版のVirtual Box がある。

なお、オラクル社のようなホストCPUを間借りする方式にも長所はあり、仮想化の中では処理速度を速くしやすいという長所もある。


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


qemuの設定方法と使用方法編集

設定方法(Windowsの場合)編集

前提として、ダウンロードとインストールは既に終わっていると仮定する。

qemuは、コマンドプロンプトから使うソフトウェアである。

なので、まずパス(Path)を通さないといけない。

具体的には、環境変数PATHにqemuのあるフォルダーを追加する。

デスクトップで「システムのプロパティ」を検索し、システムのプロパティを開く
[詳細設定]タブの右下にある[環境変数]ボタンをクリック
[環境変数]画面を開く
システム環境変数の設定の先頭に、C:\Program Files\qemu;を追加する。

さて、パスが追加し終わったら、はたして本当にパスが通ってるの確認のため、Windwosのコマンド プロンプトを起動して

C:\Users\ユーザー名>qemu-img

というふうに、コマンド「qemu-img」でも入力してみよう。

なお、「qemu」というコマンドは無いので、「qemu」とコマンド入力しても。

'qemu' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

さて qemu-imgコマンドは引数を指定して使用するコマンドなので、上記コマンド「qemu-img」を実行すれば、端末上で引数が足りないことをqemuから警告されるハズである。

C:\Users\ユーザー名>qemu-img
qemu-img: Not enough arguments
Try 'qemu-img --help' for more information

のように、qemuから警告されれば、ひとまずqemuのインストールは成功である。

使い方編集

あらかじめ、起動したいファイルを機械語で作っておき、たとえば、testos.img とかの名前をつけて、ホームディレクトリーなどコマンド端末の認識できる場所に保存しておく。

そしてコマンド端末で

qemu-system-i386 testos.img

のようにコマンド qemu-system-i386 を使えばいい。


では、そのOSイメージをどうやって作るか。

原理は、

まず、アセンブラの nasm で書く。(nasm は Netwide Assembler ともいう。)

※ (gccだと難しくなるので、OS制作ではgccは使わないほうがイイ。gccによる出力は、標準設定では、Windowsの実行ファイル(PEフォーマットなど)に自動変換して、OSそのものの自作には余計な情報を負荷してしまう。そうしないためには、引数であれこれと設定する必要がある。gccでアセンブリコードをそのまま機械語にしようとすると、引数がけっこう多くなる。しかも残念なことに、日本のネットには、この操作を詳しく解説した資料が無い。)
なので、 nasm を使おう。


あらかじめアセンブラコード形式で、OSにしたいファイルをアセンブリコードで作っておいて、testos.asm などの名前で保存しておく必要がある。


nasmなら、testos.asm を(PEフォーマットでない、直訳の)機械語にするコマンドは

nasm testos.asm -o testos.img 

だけで終わる。

そして、こうして作成したブートイメージをqemuで起動する方法は、ホームファイルに先ほど作成した testos.img を置いた上で、

C:\Users\ユーザー名>qemu-system-i386 testos.img

のコマンドだけで終わる。


では、元になるアセンブリコードをどうやって調達するか? とりあえず、ネットで読者の勉強用に(彼らの)自作OSのブートローダーのアセンブリコードを公開してくれている人がチラホラといるので、彼らのコードで実験するのが良いだろう。

※ いちおう、本書でも、初等的なブートローダのコードを記載している(※ 後述)。

Bochs の使い方編集

ネット上に Bochs の使い方の入門書がぜんぜん無いので、wikibook で教えることにする。

Bochsでは、アセンブルはできない。

Bochsの用途は、すでに別ツール(たとえば nasm んど)で作成ずみの img ファイルをBochsで起動するだけのものである。

asmファイルからimgファイルへのアセンブルは、あらかじめ nasm などで行っておく。


Bochsの利便性は、コマンドを覚えなくて言いことである。起動コマンドのBochsだけ押せばウィンドウが起動するので、あとはそのウィンドウ側でGUI的に操作してエミュレータの起動をできるという便利ツールなエミュレータが Bochs である。


さて、Boshs をインストールしてから、環境変数(パス)の設定を終えれば、 コマンドプロンプトで、コマンド bochs で起動する。つまり

ユーザー名>bochs

のように「bochs」の部分を入力する。

で、起動するとダイアログ画面が現れる。


さて、これだけでは、そもそも何のファイルを起動するかすらも設定されてない。なので、これから、このダイアログ画面で、それを設定する。


まず、真ん中の項目(中央ペイン)にある「Edit Option」 の「Disk & boot」をダブルクリックすると、画面が遷移して、

オプション画面である「Bochs Disk Options」画面になる。この項目で、何のファイルを起動するかを設定できる。 そのためには、

その「Bochs Disk Options」画面の中で、タブ「ATA Channel 0」> 子タブ「First HD/CD on Channel 0」 をクリックすると出てくる画面に、

上から2段目あたりに

Path or physical device name          Browse

という項目があるので、その          のなかに、起動したいimgファイル名を入れる。

たとえば、「testos.img」ファイルを起動したいなら、

Path or physical device name testos.img      Browse

のようになる。


こうして、(おそらく)あとはこのオプション画面を終了して(ダイアログ・ウィンドウの右上のクローズ用の×ボタンを押せばいい)メインメニュー画面に戻り、右ペインにある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命令などで指定したメモリに値を書き込む命令であるが、これで機械語も書き込みできる。

備考: なお、DB命令やORG命令は、(CPUに対する命令でなく)アセンブラに対する命令なので「擬似命令」(ぎじ めいれい、pseudo-instruction)という。コンパイラなどにより機械語に翻訳する際、擬似命令は翻訳されない。というか、そもそも翻訳の指示に関する命令なので、翻訳内容ではないので、擬似命令は 機械語にならない のは当然である。
※ 擬似命令とは、たとえるなら(C言語などの)高級言語でいう、いわゆる「マクロ」のようなものである。


もし、機械語を書き込む先を、メモリではなく、ハードディスクやUSBメモリやフロッピーディスクなどのブート可能なメディアにすれば、原理的には、これでブートローダを作れる。

※ たとえばマイナビ出版の『OS自作入門』(川合秀美 著)では、まさにこの方法でブートローダやそれから起動するGUIつきOSを作っている。

なお、DB とは data byte の略だと言われている。


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


ブートローダ編集

まず、ブートローダを書き込むわけですが、

規格により、

MBRのサイズは512バイトと決まっており、

また、

ブートローダと認識させるために必要な署名は、512バイト目の最後の2バイトに16進数で「aa55」と書き込まなければならない(※ 511バイト目が「aa」、512バイト目に「55」である)、

と決まっている。


なので、このために

times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

をどこかに書き込む必要があります。

times とは、nasm の擬似命令のひとつで、繰り返し命令のことです。


「aa55」の署名を書き込まずにQemu上で機械語を起動しても、いくつかのメッセージのあとに「No Bootable device」などと表示されるだけです。


さて、上記コードの場合は

510 - ($ -$$) 回数だけ、db 0 (つまり「0を書き込め」)を繰り返せ、

という命令です。

$ は、そのtimes命令が出されたときの現在のアドレスです。

$$ は、現在のセクションの最初のアドレスです。

 
リトルエンディアンの仕組み

間違えて、「55aa」(マチガイ!)を書き込まないようにしてください。詳しくは『w:リトルエンディアン』で調べてください。

MBRのサイズは512バイトと決まっており、その末尾2バイトに「aa55」と書き込むので、times 繰り返し命令 では「510」と2バイトぶん、余らせています。


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


コード例
(※ 実際に動く。 windows7 上の qemu で動作を確認。)
mov ah, 0x0e           ; 1文字出力
mov al, 'H'
int 0x10
times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

成功すれば、 qemu上で、「H」と表示されます。


int 0x10 とは、ディスプレイへの割り込み命令のひとつです。int でBIOSにより割り込み命令を指示しています。intの引数で、どんな割り込みをするかを指示しており、int 0x10 はビデオサービスの割り込みです。

ですが、int 0x10 だけでは、ディスプレイに文字も画像も表示できません。

int 0x10に加えて、さらに、何を割り込ませるかの指定を行う必要があり、 mov ah, 0x0e で文字割り込みを指定しています。(※ 詳しくは『en:w:INT 10H』(英語版ウィキペディア)などを参照してください。)

ah は、アキュムレータ レジスタ の上位ビットの部分です。
al は、アキュムレータ レジスタ の下位ビットの部分です。

int 0x10 の命令は、上記の手本コードのように、 アキュムレータ レジスタで指示しなければなりません。

なお、学校などで、もしかしたら、情報科学・計算機科学の教育では「アキュムレータ」とは「加減乗除などのためのレジスタ」とか習うかもしれませんが、しかし実際のCPUでは、(上記コード例のように)設定などの一時保存にもアキュムレータが流用されています。


(※ 要確認)また、電源の投入直後(いわゆる「ブート」の直後)に使用できるレジスタは、まず16ビットCPU時代のレジスタだけです。32ビットCPUや64ビットCPU時代に追加されたレジスタは、初期状態では使用できないです。つまりraxやeaxは、16ビットCPU時代のモードでは使用できないです。axなどは16ビットCPU時代のモードで使用できます。
16ビットCPU時代のモードを「リアルモード」といいます。32ビットCPU時代以降のモードを「プロテクトモード」といいます。
2010年代の現代のパソコンでもリアルモードは、下位互換性(かい ごかんせい)などのために残されています。下位互換性とは、古いバージョンのプログラムが動くようにするという事です。
一般的名パソコンでは、ブート直後(電源の投入直後)はリアルモードになっています。言い換えれば、ブート直後はプロテクトモードではないです。
なおモードの切り替え方法は、CR0レジスタ(というのがある)の最下位ビットの値が1ならプロテクトモードになります[2]

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


さて、この場合での int 0x10 は、alレジスタにある文字を表示できます。


「Hello 」と表示させたければ、「H」だけでなく、同様の操作を繰り返し、「e」「l」「l」「o」を追加で表示させればいいだけなので、


コード例 『基本』
(※ 実際に動きます。 windows7 上の qemu で動作を確認。)
mov ah, 0x0e           ; 1文字出力
mov al, 'H'
int 0x10

mov al, 'e'
int 0x10

mov al, 'l'
int 0x10

mov al, 'l'
int 0x10

mov al, 'o'
int 0x10

times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

でブート後に「Hello」と表示できます。

発展的な話題編集

ラベルやジャンプ命令を使うと、繰り返し命令を実装できる。

上の「Hello」のプログラムを、ラベルなどによる繰り返し処理でプログラムするとするなら、下記のようになるl


コード例 『発展』
    org 0x7c00

    mov ah, 0x0e
    mov si, greet

loop_top:
    mov al, [si]
    cmp al, 0       ; 0と比較
    je done         ; 等しければ doneにジャンプ
    int 0x10
    inc si          ; 1文字進める
    jmp loop_top

greet:
    db "Hello greet", 0x00     ;文字の終わりとして 0x00 を使用した

    times 510 - ($ - $$ ) db 0
    db 0x55 , 0xaa
done:
org 0x7c00

org 0x7c00 とは、このようなテキスト処理をするのにBIOSに予約されている領域が7c00 なので、そこから書き始める必要がある。 org は擬似命令であり、これからの書き込みのメモリ位置を指定する命令である。

mov si, greet

また、 mov si, greet は形式的には、あたかもラベルgreetの内容を代入するかのような表現ですが、

実際は、単にgreet ラベルの先頭のメモリを代入するだけです。

int 0x10

int 0x10 は、繰返し命令の中で、毎回、使用する必要がある。

その他の例

次のようlodsb 命令とsiレジスタを使っても良い。lodsbは[ds:si]からalに1バイト読込む(ロード load)命令であり、さらに使用後に自動的にsiレジスタの指し示す位置を1バイトぶんだけインクリメント(DF=0の場合、DF=1の場合はデクリメント)してくれるので、手間が省ける。

※ 2022年現在のx86プロセッサは、x86命令セットをマイクロ命令に分解し、マイクロ命令の組合せによっては複合的なマイクロ命令に合成し実行しています。ところが、lodsbの様な複雑な命令はマイクロ命令に翻訳されず固定的なマイクロコードにより実行されます。このため、かつて命令読込みのオーバーヘッドがなくせるという理由で「最適化」の代表だった複雑な命令を使うことは、現在は実行速度を低下させることになります。

コード例1
org 0x7c00
mov ah, 0x0e           ; 1文字出力を設定 

mov si, msg

LOOP:
	lodsb

        cmp al, 0x00
        je Loop_break

	int 0x10

        jmp LOOP

Loop_break:

msg:
    db 'Hello, World!', 13, 10, 0
    
    
times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

解説

lodsb の内容は

mov al, [si] 
inc si

と等価である(DF=0の場合、また OF, SF, ZF, AF, PF は inc si の結果により修飾されるので厳密には異なる)。


その他の例2
cmp al, 0x00
je Loop_break

の代わりに

test al, al
jz Loop_break

でも良い。

また test の代わりに

or al, al
jz Loop_break

でも良い。

※ かつては、定数とレジスタの比較はレジスタファイルの読出しハザードがありストールの原因になりましたが、Sandy Bridgeマイクロアーキテクチャ以降のプロセッサであればマイクロフュージョンの対象となり、test命令やor命令を使うメリットはなく、專ら可読性を損なうだけです。

コード例2
org 0x7c00
mov ah, 0x0e           ; 1文字出力を設定

mov si, msg

LOOP:
	lodsb

        test al, al
        jz Loop_break

	int 0x10

        jmp LOOP

Loop_break:

msg:
    db 'Hello, World!', 13, 10, 0
    
    
times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

メモリ直書き編集

グラフィックのVRAM直書き編集

まず、設定として、

mov al, 0x13
mov ah,0x00
int 0x10

というコードが必要です。

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進数になおすと、単にセグメントベース×16は、末尾に0をつけたすだけである。

※ セグメントベース値の1の違いはオフセット値に換算すれば たった16 なので、よって、もし2つの物理アドレス番号があって、それらのセグメントベースの値が違っていても、オフセット値でそのぶんの差を補う加減があれば、2つの物理アドレス番号が重なることがあるし、用途によっては重なっていても構わない[4]
また、2つのアドレス番号があって、セグメントベース値とオフセット値がそれぞれ異なっていても、「セグメントベース値×16 + オフセット値」の合計値が同じなら、それは同じアドレスを指し示す処理になる[5]。たとえば 0000:1000 と 0100:0000 は同じアドレスを指し示す(※ 文献『Linuxのブートプロセスをみる』での例)。

これらのセグメントベースを格納するために「セグメントレジスタ」と言う専用のレジスタを使う。

16ビットCPUのセグメントレジスタには

CS (コード セグメント)、
DS (データ セグメント)、
ES (エクストラ セグメント)、
SS (スタック セグメント)、

の4つがある。

なおFSとGSは32ビットCPU以降のセグメントレジスタである。

CSは、CPUが実行するプログラムを格納するためのセグメントとして、CPUに使用させる。

DSは、メモリの読み書きといったデータ関係の手段のセグメントとして、CPUに使用させる。

SSはスタック関係のセグメントで使用。

「エクストラ」とは、「その他」とか「追加の」のような意味。


よく分からなければ「セグメントベース」でググると、詳しく紹介してくれている親切なITブロガー日本人さんが何人かネットにいるので、それらのページを参照してください。

で、書式は、[セグメントベース:オフセット]の書式である。



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

コード例 1
mov AL, 0x13            ; ビデオモード0x13
mov AH, 0x00
int 0x10

mov BX, 1111            ; ピクセルで書き込みたいアドレスの計算用
mov AX, 0xa000            ; VRAMアクセスのためのセグメント処理で使用
mov DS, AX

mov AL, 0x01        ; 色の設定
mov byte [DS:BX], AL        ; VRAMに書き込み

mov BX, 1112        ; 次に書き込みたいアドレスの計算用
mov byte [DS:BX], AL        ; VRAMに書き込み(以下、同様)

mov BX, 1113
mov byte [DS:BX], AL

mov BX, 1114
mov byte [DS:BX], AL


mov AL, 0x031        ; 色の変更
mov BX, 1115
mov byte [DS:BX], AL

mov BX, 1116
mov byte [DS:BX], AL

mov BX, 1117
mov byte [DS:BX], AL

mov BX, 1118
mov byte [DS:BX], AL

mov BX, 1119
mov byte [DS:BX], AL

mov BX, 1120
mov byte [DS:BX], AL

mov BX, 1121
mov byte [DS:BX], AL

times 510 - ($-$$) db 0
dw 0xAA55


予備知識
mov命令によるメモリへの書き込み 

さて、mov 命令でメモリに書き込むには mov BYTE [123], 0x4567 の書式で書き込みます。

この場合、メモリの123番地に、数値(16進数で)4567を書き込むわけです。

[ ] をつけると、メモリへの指示だとアセンブラなどが認識します。

ここでの「BYTE」 は1バイト長で書き込め、という命令です。

2バイト長なら(BYTE でなく) WORD になります。

4バイト長さなら DWORD になります。

※ なお、WORDやDWORDを使う場合、リトルエンディアンの順序で書き込まれます。


なお

mov al, BYTE [123]

のように書いた場合は、メモリ123番地にある内容をレジスタalに書き込め、という命令になります。

このように、(書き込みだけでなく)メモリからの読み込みにも [ ] は使えます。

つまり、 [ ] をつけると、メモリへの指示だとアセンブラなどが認識します。


コードの解説

0xa0000は画面の左上だが、そこに色を塗っても見づらいので、

上記コードでは 0xa1111 から色を塗り始めることにした。


なお 0xaffff は画面の右下である。


画面の真ん中の上のほうに、なんか緑色っぽい線が5ミリくらいの長さで水平に引かれている結果が表示されると思う。

上記のコードでは、分かりやすさを重視して、あえて繰り返し命令(ラベルやジャンプ命令で実装できる)を使わなかったが、実際に図形を書きたい場合は、ラベルを活用して繰り返し命令で処理するのが効率的だろう。

なお、上記コード例1で

mov AX, 0xa000
mov DS, AX

とあるが、


これを、(下記はエラーになる)

mov DS, 0xa000

とまとめても、なぜかエラーになる。

なので、手本のコードのように、レジスタを経由する必要がある。

テキスト直書き編集

※ 調査中

一般的名パソコンでは、英数字などのテキスト出力も、ブート直後の段階では、専用のメモリ領域が用意されるので、このメモリを書き換えることで、テキスト文字を出力できます。(ただし、漢字や平仮名・カタカナなどは無理。)

ブートプログラムの確認作業などに、便利な機能でしょう。


また、 int 命令によって、1文字ずつ書いていく方法は、原理は単純ですが、実装では、処理速度があまり速くないという問題があります。

ラベル命令やジャンプ命令などを使ってコードの行数を減らしても、int命令で書き込みをするよりも、テキスト用メモリを直に書き換えするほうが処理速度が速くなります。

一般的なパソコンでは 0xb800 からの領域がテキスト用メモリに割り当てられています。


なので、まず

mov ax, 0xb800

と指定します。



BIOSに予約されたメモリ領域編集

メモリ領域のうち、0xa0000 から0xfffff までの領域は通常のパソコンでは、BIOSがハードウェアを管理するために使用することになっています[6]


このため、それらハード管理以外のソフトウェア的な処理をするためにメモリ使用したい場合は、この領域を避けてメモリを使用する必要があります。

こういった用途には Linuxなどの現代のオープンソースOSでは、 メモリを使用する際には 0x100000 以降の領域を使うのが一般的です。

原理的には 0xa0000 未満の領域も使用可能ですが、使いすぎ等のミスによって0xa0000以降にハミ出る恐れがあるので、なるべく 0x100000 以降の領域だけを使うほうが安全でしょう。


ハードディスクなどへのアクセス編集

まず、フロッピーディスクやハードディスクなどに読み書きのできる割り込み命令で、 int 0x13 というのがあります。

2000年代の現代では、これを大容量デバイス用に拡張した拡張 int 0x13 というのがあります。

どちらの int 0x13 とも、ドライブ番号は、DLレジスタ(データレジスタの下位(Low)の部分)で指定します。

このように、int 0x13 では、割り込み時におけるレジスタの役割が決められています。

拡張 int 0x13

さて、拡張 int 0x13 を使って、フロッピーディスクやハードディスクなどの記憶媒体に割り込みをできます。(USB対応については今後の見通しは不明。書き込みできるものもあるようだが、)


int 13h は、レジスタなどの数値で 書き込みの方式や対象を指定します。拡張 int 13h と、非格調 int 13h では、レジスタの解釈が違っております。

格調 int 0x13 のほうの方式を LBA方式といいます。

格調 int 0x13 (LBA方式)の仕様
AH 読み込みは 42h で固定
AL 読み込むセクタ数
DL ドライブ番号
DS:SI Disk Address Packet のアドレス

格調 int 0x13 では、レジスタに収まりきらない様々な情報を、任にの Disk Addres Packet (DAP)という場所に配置しており、その形式も決まっています。

※ 調査中


非拡張の int 0x13

なお、拡張されてない int 0x13 は、ハードディスク容量などの小さい時代の古い規格のものであり、現代では、読み書きに時間が掛かったり、あるいは不可能です。


非拡張の int 0x13 では、ドライブ番号は、DLレジスタ(データレジスタの下位(Low)の部分)で指定します。

AHレジスタが 0x02 なら 読込み、AHレジスタが 0x03 なら 書込み です。

仕様
AH 読み込みは 0x02 で固定
AL 読み込むセクタ数
CH トラック番号(下位8ビット)
CL
DH ヘッド番号
DH ヘッド番号
DL ドライブ番号
ES:BX または ES:EBX 読み込みたい先のアドレス


参考サイト
ディスクBIOS
INT 13h - jou4のブログ

int 13hともいう。

参考文献
林高勲『作って理解するOS』 328ページで、非拡張 int 13h のCHS方式について解説あり。

キーボードサービス編集

int による割り込み編集

まず、int命令で、キーボードサービスの割りこみがあり、 int 0x16 がキーボードサービスである。

コード例
mov ah, 0x0e           ; 1文字出力
mov al, 'p'            ; 「pushしろ」・・・のつもり
int 0x10

mov al, ' '
int 0x10

mov al, 's'            ; 「space」・・・のつもり
int 0x10

mov al, ' '
int 0x10

mov al, ' '
int 0x10


.LOOP:
	mov ah,0x00       ; キーボード入力待ち. 0x10 でもいい
	int 0x16

	cmp al, ' '         ; 空白なのでスペース
	jne .LOOP           ; 直前の比較cmpの結果が偽(否定)だったらループする


mov ah, 0x0e           ; 1文字出力
mov al, 'f'            ; 「finish 終わったよ」・・・のつもり
int 0x10

times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa


さてint 0x16 を呼び出す際、

mov ah,0x00       ; キーボード入力待ち. 0x10 でもいい

なら、ah = 0x00 は、キーボードのキー入力待ちである。ah = 0x10 だと拡張キーボード対応らしい[1]


in または out命令による処理編集

実は一般的なパソコンでは、アセンブリ言語の命令で、いくつかのハードウェアに読み書きのアクセスするための、専用の命令がある。

IN 命令と、 OUT命令である。

そして、キーボードなど、昔のどこのパソコンにも存在したハードウェアは、実はハードウェア番号が決められている(「I/Oポートアドレス」などという)。

たとえば、キーボードはハードウェア番号(I/Oポートアドレス)が十六進数で0x60 番である。(メモリマップとは別。メモリアドレスの0x0060などにdb命令で書き込んでも、目的のハードにはアクセスできない。 )


IN命令で、引数で指定したレジスタ(普通はALレジスタやAXレジスタを指定する)に、もうひとつの引数で指定した目的デバイスから送られた値が保存されます。(引数の順序はアセンブラの種類などによって異なるので、説明を除外。)

また、OUT命令で、引数で指定したレジスタ(普通はALレジスタやAXレジスタを指定する)に格納されている値が、もうひとつの引数で目的デバイスに送られます。


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

コード例
mov al, 'p'       ; 「push e」と表示の予定
int 0x10

mov al, 'u'
int 0x10

mov al, 's'
int 0x10

mov al, 'h'
int 0x10

mov al, ' '
int 0x10

mov al, 'E'
int 0x10

mov al, 0x0a	;改行の指示
int 0x10


.LOOP:
	in al, 0x60 		; in al, 0x0060 でもよい

	cmp al, 18 		; 値が文字 'E' かどうか判定のつもり
	jne .LOOP 		; 偽なら LOOP 冒頭にジャンプ

mov ah, 0x0e           ; 1文字出力
mov al, 'G'
int 0x10


times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa


解説

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種類くらいあります。

下記のリンクが詳しいです。 [2]

なお、一般的に「メイクコード/ブレイクコード」の書式です。


たとえば、ボタン「1」(「!」と同じ)のスキャンコードが「16 / F0 16」とかかれていれば、メイクコードが「16」であり、ブレイクコードが「FD 16」という意味です。(ある規格では、ブレイクコードは、メイクコードの先頭にF0がついたものになっている。)

なお、キーボードコントローラーもインテルなどが製造していました。かつて Intel i8042 というキーボードコントローラーが有名でした。


より正確な仕組みとしては、シリアル通信(PS 2 信号)などで送られた信号をKBCあたりでスキャンコードに変換しているわけですが、CPUから見ればスキャンコードしか見えないので、あまり気にしなくていいでしょう。


参考サイト

0から作るOS開発 カーネルローダその3 プロテクティッドモードへの移行とA20

※ 本wikibooksの当ページが完成するまでの間、上記の参考サイトが分かりやすくて役立つと思いますので勉強してください。



出力例?編集

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

	mov al, 0xED
	out 0x60, al

0xED というのは、LED点灯に関する命令の番号。


BPB編集

一般的なブートローダのいくつかには、ブートセクタに BIOS Parameter Block (BPB)というものが書かれており、これは BIOS への指示や設定を出すブロックです。

パソコンの電源を投入して、まず最初に起動するのは BIOS なのですから、このブロックが必要です。少なくとも Windows系OSのブートローダーでは、そうなっていると言われています。

このように、ブートセクタは、書式がほぼ決まっています。

冒頭でJMP命令で ブートローダ(IPL: Initial Program Loader)へジャンプ
BPB
IPL
位置0x01FE に 0x55AA と書いて「ここまでがブートローダ」だとBIOSに認識させる

という構成になっています。

ジャンプしてしまうので、BPB はプログラムカウンタでは読み取れず、BPBはBIOSしか読み取れないことに注目しましょう。


市販のOS自作本にある、DB命令で書き込む冒頭の 「DB 0xeb」 と言うのも、このJMP命令のことです。

JMP命令はX86系CPUの機械語では eb です。

『IA-32 インテル®アーキテクチャソフトウェア・デベロッパーズ・マニュアル』『中巻A:命令セット・リファレンスA-M』3-411



プロテクトモード編集

プロテクトモードとは、CPUの32ビットモード(および32ビット以上のモード)のこと。

32ビットモードには、アクセス権の無い状態からのアクセスを禁止するという、特権レベルによる保護機能などがあるので、プロテクトモードという。


プロテクトモードにいこうするためのコードの一部を抜粋すると、おおむね書きのような感じになる[7]

コード例
    mov eax, cr0    ; 
    or ax, 1    ;  
    mov cr0, eax    ;  最下位ビットに1を設定
    jmp ジャンプ先のラベル

ジャンプ先のラベル:
    mov ax, ここに何か    ; mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
解説

cr0レジスタの最下位ビット(PEビットという)が1だとプロテクトモードである、という仕様である。

    mov eax, cr0   
    or ax, 1    
    mov cr0, eax

の3行の処理で、cr0レジスタの最下位ビットを1に設定している。なお、このcr0の最下位ビットのことをプロテクト・エネーブルド pr0tect enebled という意味でPEビットという。


設定後にわざわざジャンプ命令 jmp を通す理由は、CPUの先読みした命令を破棄するためである[8][9] 。ジャンプ命令には、先読みを破棄する機能がある。

なお、パイプラインという仕組みにより、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のマスクを解除しないといけない。


下記の順序で作業しないといけない。そういう仕様である。

  1.  GDT(Global Descriptor Table)の作成
  2.  GDTレジスタの設定
  3.  IDT(Interrupt Descriptor Table)の作成
  4.  IDTレジスタの設定
  5.  A20のマスク解除
  6.  CPUへの割り込み禁止
  7.  cr0レジスタの最下位ビット(PEビット)を1に設定
  8.  CPUの先読み(パイプライン)を除去する(jmp命令で除去できる)
  9.  セグメントレジスタの設定


A20マスクの解除には複数の方法がある。

  • キーボードコントローラーから解除
  • System Control Portから制御
  • BIOSの割り込み命令 int 15 で解除


キーボードコントローラから解除できる理由は、単に昔のインテルかどこかの人が設計したとき、たまたまキーボードコントローラ用のアドレスバスが余ってたからだけと言う理由らしく、あまり技術的な深い意味は無い。


なお作業の順番について、A20のマスク解除の順場は多少前倒しになっても平気なようである。

Linuxのブートローダはkernelには無い編集

ある程度、理解が進むと、 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コマンドを使えるようになります。

参考文献編集

書籍出版物編集

  • 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版
  • 内田公太・上川大介 著『自作エミュレータで学ぶX86アーキテクチャ』、株式会社マイナビ (※出版社名)、2015年8月30日 初版 第1刷 発行、
  • 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日 初版 第1刷 発行、
  • Igor Zhirkov 著、古川邦夫 監訳『低レベルプログラミング』、翔泳社、2018年01月19日 初版 第1刷 発行、

このほか、マイナビ出版の『OS自作入門』(川合秀美 著)を参考にしたが(同じ出版社の『自作エミュレータで学ぶX86アーキテクチャ』でも川合氏の文献をところどころ引用的に出して技術解説している)、しかし本書 wikibooks では残念ながら技術内容の確認作業には使えなかった(著者の川合氏が技術内容の正確さよりも初心者の取っ付きやすさを優先しているため。また、ところどころ説明が不十分(アセンブリ言語による各論の理解よりも、読者がC言語でコードを作って動かせることや、全体像の把握を重視している、川合氏の方針のため)。)技術内容の確認作業には、上記一覧の別文献を参考にした。あと、川合氏の文献は2005年の出版物という事もあり、古いので、現代の動向の確認には別文献に当たることになった。

なお、上記文献一覧にある林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』(技術評論社)を、川合氏が監修している。

ただし、直接にこそ川合氏の文献を本wikiは参考にしてないものの、しかし川合氏の文献は日本でのOS自作解説書の草分け的な存在なので、間接的には本wikiも影響を大きく受けているだろう。だからこそ、2019年代の現代でも増刷・再版され続けている(絶版になってない)。

他の著者(名誉のため名前は伏せる)のOS自作本の中には、絶版になってしまったものもある。

脚注編集

  1. ^ 内田公太・上川大介 著『自作エミュレータで学ぶX86アーキテクチャ』、株式会社マイナビ (※出版社名)、2015年8月30日 初版 第1刷 発行、6ページ
  2. ^ 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日、235ページ
  3. ^ Igor Zhirkov 著、古川邦夫 監訳『低レベルプログラミング』、翔泳社、2018年01月19日 初版 第1刷 発行、53ページ
  4. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、20ページ
  5. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、25ページ
  6. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、70ページ
  7. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、98ページ
  8. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、98ページ
  9. ^ 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日 初版 第1刷 発行、471ページ

関連項目編集

外部リンク編集

  1. 『Tips BIOSサービス割り込み ビデオサービス』
  2. 『Expanded Main Page - OSDev Wiki』(英語)
  3. FATファイルシステムのしくみと操作法
  4. パソコンのレガシィI/O活用大全