低水準プログラミング
低水準プログラミングは、コンピュータのハードウェアに近い形で行われるプログラミング手法です。アセンブリ言語や機械語により直接的に制御し、メモリやレジスタ、命令セットなどにアクセスします。高度な抽象化を提供せず、ハードウェアの細部に対して直接的な操作が可能なため、制御の精度が高まりますが、同時に複雑さも増します。通常、高水準言語よりもハードウェアと密接な関わりがあり、性能最適化やリソース制御が求められます。
- 予備知識
- 本書を読むには予備知識としてアセンブラの知識と、X86系CPUのレジスタなどのアーキテクチャの知識が必要である。
一般的にプログラミングにおいて、ハードウェアの制御は、コンパイラが対応していない命令(特権命令やIO命令など)はアセンブリ言語で記述する事になる。
もし知らなければwikibooks記事『X86アセンブラ』などで解説してある。特に『X86アセンブラ/GASでの文法』『X86アセンブラ/x86アーキテクチャ』『X86アセンブラ/x86アセンブラ』では初心者むけに説明してある。
本書では触れてないが「カーネル」とか「ユーザランド」とかの用語についてはwikibooks『高等学校工業/ソフトウェア技術』などで触れてある。
総論
[編集]学習の方向性
[編集]概要
[編集]低水準プログラミングの学習では、コンピュータの内部構造や動作に深く迫り、高度な技術的理解を養います。アーキテクチャの理解から始め、アセンブリ言語や機械語の習得を通じて、コンピュータハードウェアに直接アクセスするスキルを身につけます。また、レジスタやメモリの操作、デバッグ技術の磨きも重要です。これにより、プログラムの効率やパフォーマンスを向上させる能力が向上します。アーキテクチャの理論を応用することで、組み込みシステムやベアメタルプログラミングなど、実践的なプロジェクトにも挑戦できます。総じて、低水準プログラミングの学習は、コンピュータサイエンスの基盤を深化させ、プログラミングの本質を理解する重要な一環です。
ブートの概要
[編集]BIOS(基本入出力システム)は長らくコンピュータの起動プロセスを担ってきたブートストラップ方式でした。起動時にハードウェアの初期化とオペレーティングシステムの起動を行っていました。しかし、BIOSはセキュリティや機能性の向上に制限があり、これを克服する必要が生じました。
UEFI(統一拡張ファームウェアインターフェース)は、BIOSの制約を乗り越えるために開発されました。UEFIは高度なセキュリティ機能、拡張性、性能向上など多数の利点を提供しています。その結果、BIOSからUEFIへの移行が進み、現代の多くのPCでUEFIが採用されています。
歴史的な経緯からBIOSも言及すると、初期のPCはBIOSが標準的でしたが、技術の進化とともに限界が明らかとなりました。UEFIの導入により、セキュリティの向上や新たな機能の実現が可能となり、コンピュータのブートプロセスが進化しました。UEFIはBIOSよりも柔軟で効率的なブートストラップ方式として広く認識され、現代のコンピュータアーキテクチャにおいて不可欠な存在となっています。
BIOS/MBR によるブートストラップ手順:レガシィ
[編集]BIOS(基本入出力システム)とMBR(マスターブートレコード)によるブートストラップ手順は、コンピュータの起動プロセスの基本を形成します。
- 電源投入とPOST(電源自己診断)
- ユーザが電源を投入すると、BIOSが動作し、POSTが実行されます。POSTはハードウェアの自己診断を行い、異常がなければブートプロセスが続行されます。
- ブートデバイスの選択
- BIOSは事前に指定されたブートデバイス(通常はハードディスク)からブートローダーを読み込むように構成されています。
- MBRの読み込み
- BIOSは選択されたブートデバイスの先頭セクタにあるMBRを読み込みます。MBRは512バイトの特別な領域で、パーティション情報とブートローダーへのジャンプ命令が格納されています。
- ブートローダーの実行
- MBRからジャンプされたブートローダーは、オペレーティングシステムのインストールされたパーティションを特定し、その先頭セクタからオペレーティングシステムのカーネルを読み込みます。
- オペレーティングシステムの起動
- ブートローダーがカーネルをメモリに読み込んだら、制御をカーネルに移し、オペレーティングシステムが本格的に起動します。
この手順により、BIOSとMBRを使用したブートストラップが実現され、オペレーティングシステムが起動します。ただし、UEFIが普及する中で、新しいシステムではUEFI/GPT方式が採用されることが一般的になっています。
UEFI/GPT によるブートストラップ手順:モダン
[編集]UEFI(統一拡張ファームウェアインターフェース)とGPT(ガイドパーティションテーブル)は、BIOS/MBRに代わる先進的なブートストラップ方式で、新しいコンピュータアーキテクチャで広く利用されています。
- UEFI Firmwareの起動
- 電源投入時、UEFI Firmwareが起動し、ハードウェアの初期化を行います。UEFI Firmwareはファームウェアとしての基本機能だけでなく、ブートマネージャとしても機能します。
- UEFI Boot Managerの起動
- UEFI Firmwareは内蔵のUEFI Boot Managerを呼び出し、ブートデバイスのリストを取得します。この際、GPTが使用されていることが前提です。
- EFI System Partition(ESP)の選択
- UEFI Boot ManagerはEFI System Partition(ESP)を特定し、その中からブートローダー(例: GRUBやWindows Boot Manager)を探します。ESPはGPT内で特定され、複数のオペレーティングシステムのブートローダーが格納されていることがあります。
- ブートローダーの読み込み
- 選択されたブートローダーがESPから読み込まれ、メモリに配置されます。
- オペレーティングシステムの起動
- ブートローダーがオペレーティングシステムのカーネルを特定し、それをメモリに読み込んだ後、制御をカーネルに渡してオペレーティングシステムを起動します。
UEFI/GPT方式は、従来のBIOS/MBR方式よりも柔軟性があり、大容量ディスクやセキュリティ機能の向上など多くの利点があります。また、複数のオペレーティングシステムをサポートしやすいため、現代の多様な環境に適しています。
メモリマップ
[編集]BIOSとUEFIは、コンピュータの初期化やブートプロセスにおいてハードウェア情報を管理するために、メモリマップを利用します。以下に、BIOSとUEFIのメモリマップの違いについて説明します。
- BIOSのメモリマップ
-
- 物理メモリ基準
- BIOSのメモリマップは物理メモリを基準にしています。
- 32ビットアドレッシング
- 32ビットアドレッシングを使用しており、システムのメモリサイズが4GBを超える場合に問題が発生する可能性があります。
- RAM、ROM、PCIデバイス、システムファームウェアなどを含む
- システムのメモリアドレス空間にはRAM、ROM、PCIデバイス、システムファームウェアなどが含まれます。
- UEFIのメモリマップ
-
- 仮想メモリ基準
- UEFIのメモリマップは仮想メモリを基準にしています。
- 64ビットアドレッシング
- 64ビットアドレッシングを使用しており、システムのメモリサイズに制限がなく、大容量の物理メモリを効率的に扱えます。
- 物理メモリ、PCIデバイス、システムファームウェア、UEFIサービス、ACPIテーブルなどを含む
- UEFIのメモリマップには物理メモリだけでなく、PCIデバイス、システムファームウェア、UEFIサービス、ACPIテーブルなどが含まれます。
UEFIのメモリマップは、物理メモリのアドレッシングやデバイスドライバの管理を通じて高度な機能を提供します。これにより、BIOSよりも多様なオペレーティングシステムに対応でき、柔軟性が向上します。また、UEFIサービスを介してシステムの状態の取得や変更が可能であり、BIOSよりも進化した機能を提供しています。
割り込み
[編集]BIOSとUEFIは、システムの初期化やオペレーティングシステムの起動など、ブートプロセスにおいて割り込みを活用します。以下に、BIOSとUEFIの割り込みの違いについて説明します。
- BIOSの割り込み
-
- 16ビットリアルモード
- BIOSは16ビットリアルモードを使用し、割り込みの処理にはアセンブリ言語で書かれたINT命令が使用されます。
- 割り込みベクターテーブル
- BIOSの割り込みは、BIOS ROM内に格納された割り込みベクターテーブルを参照して処理を実行します。
- ハードウェア制御、デバイスドライバの初期化、オペレーティングシステムの起動
- BIOSの割り込みは、ハードウェアの制御、デバイスドライバの初期化、オペレーティングシステムの起動などのタスクを実行します。
- UEFIの割り込み
-
- 64ビットモード
- UEFIは64ビットモードを使用し、割り込みの処理にはC言語で書かれたUEFIサービスを呼び出すことによって実行されます。
- System Service Table(SST)
- UEFIの割り込みは、UEFIファームウェア内に格納されたSystem Service Table(SST)を参照して処理を実行します。
- 高度な機能の提供
- UEFIの割り込みは、BIOSと比較してより高度な機能を提供します。C言語で書かれたサービスを呼び出すことにより、柔軟性が向上し、多様なオペレーティングシステムやアプリケーションに対応します。
UEFIは、C言語で書かれたサービスを使用することで、より高度な機能を提供し、柔軟性が向上しています。これにより、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ネットワークスタックには、以下のようなプロトコルやサービスが含まれています。
- Preboot Execution Environment(PXE): PXEは、ネットワーク上からブート可能なイメージを取得するためのプロトコルです。UEFIのネットワークスタックには、PXEブートをサポートするコードが含まれています。
- Transmission Control Protocol/Internet Protocol(TCP/IP): TCP/IPは、インターネットを構成するためのプロトコルです。UEFIネットワークスタックには、TCP/IPスタックが含まれており、ネットワーク上のデバイスと通信するためのプロトコルとして使用できます。
- User Datagram Protocol(UDP): UDPは、TCP/IPの一種のプロトコルで、信頼性の低い通信に使用されます。UEFIネットワークスタックには、UDPスタックが含まれており、PXEブートなどの通信に使用できます。
- 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の開発には、当初からIntelのほか、IBM、AMD、Microsoft、およびHPなどの大手企業が参加しました。2005年には、UEFIフォーラムが設立され、UEFIの標準化と普及を促進するための活動が開始されました。その後、UEFIの仕様は、フォーラムに参加する会員企業によって共同で策定されました。
UEFIの初期バージョンは、主にサーバー向けに開発され、デスクトップやモバイルデバイス向けのバージョンは、より後のバージョンで提供されました。UEFI 2.0が2006年にリリースされ、UEFI 2.7Aが2018年にリリースされました。
UEFIは、BIOSよりも高速でセキュアな起動プロセスを提供することができるため、現在では多くのコンピュータに採用されています。UEFIの普及は、現代のコンピュータアーキテクチャにおいて重要な役割を果たしています。UEFIアプリ
[編集]UEFI(Unified Extensible Firmware Interface)アプリケーションの開発は、通常、C言語やアセンブリ言語を使用して行います。以下に、簡単なUEFIアプリケーションの例を示します。なお、UEFIアプリのビルドは専用のツールチェーンが必要です。一般的に、EDK II(EFI Development Kit II)を使用してビルドします。
まず、以下は簡単な "Hello, UEFI!" を表示するサンプルプログラムのUEFIアプリケーションの例です。このプログラムはC言語で書かれています。
#include <efi.h> #include <efilib.h> EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); Print(L"Hello, UEFI!\n"); return EFI_SUCCESS; }
このプログラムでは、efi_main
関数がエントリーポイントとなります。InitializeLib
関数は ImageHandle
と SystemTable
を初期化します。Print
関数を使用して画面にメッセージを表示しています。このプログラムは EFI_SUCCESS
を返すことで正常終了を示しています。
これをビルドするには、EDK IIを使用します。以下に簡単な手順を示しますが、ビルド環境やプロジェクト構成により異なる場合があります。
- EDK IIをダウンロードします。EDK IIのGitHubリポジトリ から最新のソースコードを入手します。
edksetup.sh
(Linux/macOS)またはedksetup.bat
(Windows)を実行してビルド環境を初期化します。BaseTools
ディレクトリに移動し、ビルドツールをビルドします。make -C BaseTools
- ソースコードがあるディレクトリに移動し、プロジェクトファイル(
.inf
拡張子のファイル)を作成します。例えば、HelloWorld.inf
というファイルに以下の内容を書きます。- HelloWorld.inf
[Defines] INF_VERSION = 1.25 BASE_NAME = HelloWorld FILE_GUID = C8F81A05-79B8-4A25-BA22-03A995E8D5A7 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 1.0 ENTRY_POINT = efi_main [Sources] HelloWorld.c [Packages] MdePkg/MdePkg.dec
- ビルドを実行します。
build -p HelloWorld.inf
- ビルドが成功すると、ビルドディレクトリに生成された
HelloWorld.efi
がUEFIアプリケーションとして使用できます。
なお、これは簡単な例であり、実際のUEFIアプリケーションはより複雑な機能を持つことがあります。UEFI仕様に基づいたプログラミングについては、UEFI Specifications を参照してください。
UEFI Specificationsの機能の概要
[編集]UEFI(Unified Extensible Firmware Interface)は、従来のBIOS(Basic Input/Output System)の代替として設計された仕様で、システムファームウェアの標準化と拡張性を提供します。UEFIは、主に次のような機能を提供します。
- EFIブートサービス
- UEFIファームウェアは、EFIブートサービスを提供し、UEFIアプリケーションの起動を可能にします。これにより、様々なデバイスからのブートが可能になり、従来のBIOSの制約を克服します。
- UEFIランタイムサービス
- UEFIファームウェアは、起動後もシステムが実行されている間に利用可能なランタイムサービスを提供します。これにより、UEFIアプリケーションはシステム機能にアクセスでき、オペレーティングシステムが起動してからもシステム制御が可能になります。
- ファームウェアボリューム
- UEFIには、EFI System Partition(ESP)と呼ばれる特別なパーティションがあります。ESPには、UEFIブートローダーや設定ファイルなどが格納され、システムのブートプロセスをサポートします。
- UEFIアプリケーションのロードと実行
- UEFIアプリケーションは、UEFIファームウェアによってロードおよび実行されます。これにより、UEFIアプリケーションは様々なデバイスやファイルシステムからロードできます。
- グラフィカルユーザーインターフェース(GUI)
- UEFIは、グラフィカルユーザーインターフェースをサポートしており、ユーザーフレンドリーなメニューインターフェースを提供します。これは、UEFIセットアップユーティリティやUEFIブートローダーなどによって利用されます。
- セキュアブート
- UEFIにはセキュアブート機能が組み込まれており、署名されたコードのみが実行されるように保護します。これにより、マルウェアや未署名のコードからのシステムへの侵入が防がれます。
- ネットワークスタック
- UEFIは、ネットワークスタックをサポートしており、ネットワークからOSのインストールやアップデートを行うことができます。
- UEFIドライバ
- UEFIアプリケーションやUEFIファームウェアは、ハードウェアやプロトコルに関するドライバをサポートし、これにより様々なデバイスとの通信が可能になります。
UEFIの仕様は継続的に進化しており、新しい機能が追加されています。各マザーボードメーカーはこれらのUEFI仕様に基づいて独自のUEFIファームウェアを実装します。
EDK II
[編集]EDK II(Extensible Firmware Interface Development Kit II)は、UEFI(Unified Extensible Firmware Interface)ファームウェア開発のためのオープンソースの開発キットです。EDK IIは、UEFIファームウェアの設計、開発、テスト、デバッグをサポートするためのフレームワークとツールセットを提供します。
以下に、EDK IIの主な特徴と機能をいくつか挙げてみましょう。
- UEFI 仕様への準拠
- EDK IIは、UEFIフォーラムの仕様に準拠しています。これにより、EDK IIで開発されたファームウェアはUEFI標準に基づいており、様々なハードウェアやオペレーティングシステムでの動作が期待できます。
- モジュールベースの設計
- EDK IIはモジュールベースで設計されており、異なるモジュール(例: ドライバ、アプリケーション、ブートローダーなど)が簡単に組み合わせられます。これにより、再利用性が向上し、柔軟な構成が可能です。
C言語およびUEFI Shellのサポート: EDK IIはC言語で開発され、C言語を使用してファームウェアを記述することができます。また、UEFI Shellと呼ばれる独自のコマンドライン環境もサポートしており、システムのテストやデバッグが容易に行えます。
- ビルド環境とツールチェーン
- EDK IIには、ファームウェアのビルドやパッケージングに必要なツールチェーンが含まれています。また、ビルド構成の管理やモジュール間の依存性解決をサポートするビルド環境も提供されています。
- 豊富なサンプルコード
- EDK IIには、様々なサンプルコードが含まれており、これを基にしてファームウェアの開発を始めることができます。これにより、開発者はUEFI仕様に準拠したファームウェアの作成に役立つリソースを活用できます。
アクティブなコミュニティとサポート: EDK IIはオープンソースプロジェクトであり、アクティブな開発者コミュニティが存在しています。開発者はフォーラムやメーリングリストを通じて質問や情報共有ができ、コミュニティによるサポートが期待できます。 E DK IIは、UEFIファームウェアの開発者が効率的かつ標準に基づいたファームウェアを構築するための有力なツールキットの一つです。
類似のキット
[編集]UEFIファームウェアの開発において、EDK II以外にもいくつかの類似したフレームワークやキットが存在します。以下に、いくつかの代表的なものを紹介します。
- TianoCore (EDK I)
- EDK I(旧称: TianoCore)はEDK IIの前身であり、UEFI仕様の初期のバージョンに対応しています。EDK Iは、UEFIファームウェアの開発に使用されるフレームワークとして広く利用されました。しかし、後にEDK IIが導入され、現在では主にEDK IIが推奨されています。
- Coreboot
- Corebootは、オープンソースのファームウェアとして知られていますが、UEFIにも対応しています。Corebootは軽量かつ高速で、特に組み込みシステムやカスタムハードウェア向けに利用されています。UEFIとしてはSlim Bootloader(SlimBoot)がCorebootと組み合わせて使用されることがあります。
- OVMF (Open Virtual Machine Firmware)
- OVMFは、仮想環境(主にQEMUなど)で使用されるUEFIファームウェアです。UEFI仕様に準拠した仮想マシンを構築するために利用されます。OVMFはTianoCoreプロジェクトの一部であり、EDK IIの一部も含まれています。
- U-Boot
- U-Bootは、組み込みシステムや組み込みLinuxデバイスで一般的に使用されるブートローダーですが、一部のバージョンではUEFIにも対応しています。U-Bootは柔軟で拡張可能なブートローダーとして知られており、LinuxやAndroidデバイスなどで広く利用されています。
これらのフレームワークやキットは、UEFIの特定の用途や要件に応じて選択されます。開発者はプロジェクトのニーズに合わせて最適なツールキットを選択し、UEFIファームウェアの開発を進めることが重要です。
実験の手段
[編集]一般的に、低水準プログラミングの学習やOS開発の実験では、エミュレーターが利用されます。無料で入手可能なエミュレータソフトウェアとして、QEMUなどが挙げられます。また、アセンブリ言語でのプログラム開発には、無料で利用できるNetwide Assembler(NASM)などが使われます。
- QEMUを使用する
QEMUは、仮想マシンのエミュレーションや仮想化を行うためのオープンソースのエミュレーターです。以下は、QEMUを用いた実験手順の大まかな流れです。
- QEMUのインストール
- QEMUを公式サイトからダウンロードし、適切なプラットフォームにインストールします。
- アセンブリコードの記述
- NASMを用いてアセンブリ言語でプログラムを記述します。例えば、x86アーキテクチャのアセンブリ言語を使用します。
- アセンブルとリンク
- NASMを使用してアセンブリコードを機械語にアセンブルし、必要に応じてリンカを使用してオブジェクトファイルを生成します。
- イメージの作成
- ブート可能なイメージを生成します。これには、アセンブルされた機械語ファイルをディスクイメージに組み込む作業が含まれます。
- QEMUでの実行
- 作成したブート可能なイメージをQEMUでエミュレートし、実行します。QEMUは仮想環境内でプログラムが実行される様子を模倣します。
- バイナリエディタの利用
原理的には、バイナリエディタ(16進数で表示されるヘックスエディタ)を使用して機械語を直接記述することも可能です。ただし、これはコードの記述が困難であり、内容の理解が難しいため、アセンブラを用いる方が現実的です。
- NASMの活用
NASMはアセンブリ言語をサポートする無料のアセンブラであり、学習者にとって手軽に利用できます。アセンブリ言語を通じて機械語を入力し、エミュレーターで実行することで、コンピュータの動作原理を理解しやすくなります。 エミュレーターの使用は、実機での実験を簡略化し、手間を省くための効果的な手段です。これにより、OSやハードウェアの開発者は効率的に実験を進め、デバッグを行うことができます。
エミュレータの種類
[編集]エミュレータは、様々な方式や種類が存在します。これらの方式には、ホストOSのCPUを利用する方式や、ソフトウェア内で仮想のCPUを作成する方式があります。
- ソフトウェア・イミュレーション
-
- QEMU
- コミュニティベースのQEMUは、ホストOSのCPUを直接利用するのではなく、ソフトウェア内で仮想のCPUを作成する方式を採用しています。QEMUはオープンソースであり、柔軟性がありながらもリソースを効率的に利用できる特徴があります。学習者にとっては、複数のCPUが混ざり合うことなく、分かりやすい環境を提供します。
- Bochs
- BochsもQEMUと同様に、ソフトウェア内で仮想のCPUを制作する方式のエミュレータです。Bochsは高い柔軟性を持ち、様々な環境で利用できる特長がありますが、処理速度が相対的に低いという特徴もあります。
- ホストCPUを間借りする方式
-
- VirtualBox
- Oracleが提供するVirtualBoxは、ホストPCのCPUを直接利用する方式のエミュレータです。複数のOSを同時に実行できる仮想環境を提供し、処理速度が速いことが特徴です。ただし、長所としては学習的に複数のCPUが混ざり合うと分かりづらいことが挙げられます。
- VMWare
- ヴイエムウェア社のVMWareも、ホストCPUを直接利用する方式のエミュレータの一例です。VMWareは企業向けに高度な仮想化環境を提供しており、処理速度やセキュリティの面で優れた性能を持っています。
- 学習の目的におけるエミュレータの選択
学習の目的において、QEMUのようなソフト的にCPUの機能を再現するエミュレータが分かりやすいとされます。QEMUはオープンソースであり、学習者が柔軟に実験を進める上で利点があります。 一方で、VirtualBoxやVMWareのようなホストCPUを間借りする方式も性能が高く、仮想化の中で処理速度を速くしやすいという利点があります。ただし、学習者が理解する上では、複数のCPUが関与することで分かりにくくなる可能性があるため、初学者向けにはQEMUの利用が優れていると考えられます。
本書では、QEMUを前提として説明を進めます。
qemuの設定方法と使用方法
[編集]設定方法(Windowsの場合)
[編集]QEMUをWindows上で使用するためには、まずQEMUの実行ファイルが格納されているフォルダを環境変数PATHに追加する必要があります。以下に手順を示します。
- パスの追加:
- デスクトップで「システムのプロパティ」を検索し、システムのプロパティを開きます。
- [詳細設定]タブの右下にある[環境変数]ボタンをクリックします。
- [環境変数]画面を開きます。
- '''システム環境変数'''の設定の先頭に、
C:\Program Files\qemu;
といった形でQEMUのフォルダのパスを追加します。
- パスの確認:
- パスが追加されたら、コマンドプロンプトを起動して、以下のコマンドでQEMUが正しくインストールされているか確認します。
qemu-img
- 上記コマンドを実行すると、
qemu-img: Not enough arguments Try 'qemu-img --help' for more information
のような警告が表示されれば、QEMUのインストールは成功しています。
これにより、QEMUのコマンドをコマンドプロンプトから直接実行できるようになります。これ以降はQEMUを利用して仮想マシンを構築し、学習や実験を進めることができます。
使い方
[編集]QEMUを使用して自作のOSイメージを実行する手順は以下の通りです。まず、機械語で書かれたOSイメージ(例: testos.img
)を用意します。その後、QEMUをコマンドプロンプトから起動します。
OSイメージの作成
[編集]- アセンブラの利用:
- アセンブラコード形式でOSにしたいファイルを作成します。一般的にはNetwide Assembler(NASM)を使用します。
- 例えば、
testos.asm
という名前で保存します。
- アセンブラで機械語に変換:
- コマンドプロンプトで以下のコマンドを実行して、アセンブリコードを機械語に変換します。
nasm testos.asm -o testos.img
- これにより、
testos.img
というブート可能なイメージが作成されます。
QEMUでの起動
[編集]- QEMUのコマンド:
- コマンドプロンプトを開き、以下のコマンドを実行してQEMUを起動します。
qemu-system-i386 testos.img
- これにより、QEMUが仮想環境内で
testos.img
をブートし、自作OSが実行されます。
注意事項
[編集]- OSイメージを作成する際は、ネット上で公開されている他の自作OSのブートローダーコードを利用することも可能です。ただし、自身でアセンブリコードを理解することが学習に寄与します。
- GCCなどのコンパイラを使用する場合、余分な情報が含まれる可能性があるため、OS制作ではなるべくアセンブラを使用することが推奨されます。
- 本書では、初等的なブートローダーのコードも提供しているため、学習者はそれを利用して実験することができます。
Bochs の使い方
[編集]Bochsは、あらかじめ作成されたイメージファイルを使用してエミュレーションを行うツールです。以下はBochsの基本的な使い方の手順です。
Bochsのインストールと環境変数の設定
[編集]- Bochsのインストール:
- Bochsを公式ウェブサイトからダウンロードしてインストールします。
- 環境変数の設定:
- Bochsをインストールしたディレクトリのパスを環境変数に追加します。これにより、コマンドプロンプトからBochsを直接呼び出せるようになります。
Bochsの起動
[編集]- コマンドプロンプトでBochsを起動:
- コマンドプロンプトを開き、以下のコマンドを実行してBochsを起動します。
bochs
- 上記コマンドを実行すると、Bochsのメイン画面が表示されます。
- Disk Optionsの設定:
- メイン画面から「Edit Options」を選択し、「Disk & boot」をダブルクリックします。
- 表示される「Bochs Disk Options」画面の「ATA Channel 0」>「First HD/CD on Channel 0」で、Path or physical device name欄に起動したいイメージファイルのパスを入力します。例:
testos.img
- 起動:
- 「Bochs Disk Options」画面を閉じて、メイン画面に戻ります。
- メイン画面の右ペインにある「Start」ボタンをクリックしてエミュレーションを開始します。
- Panicウィンドウの処理:
- 起動時にPanicウィンドウが表示され、「specified geometry doesn't fit on disk」と表示される場合があります。この際はContinueをクリックし、OKを押して続行します。
- エミュレーションの終了:
- エミュレーションが終了したら、メニューバー右上の「(|)」ボタンをクリックしてBochsを終了します。×ボタンは使用できません。
これでBochsを使用して自作OSのイメージをエミュレートする準備が整いました。 Bochsは直感的なGUIを提供し、設定も簡単に行えるため、学習者にとって使いやすいエミュレータです。
BochsのDtart>OKの後のエミュレート起動後の終了方法は、ウインドウの上部にあるメニューバーの右側のほうに、○印の中にタテ線「|」のある終了ボタン「(|)」があるので、これを押せばいい。(右上の×ボタンは使えない。×ボタンをクリックしても反応しない。)
擬似命令を使ってブートローダを作ろう
[編集]擬似命令とブートセクタの作成
[編集]擬似命令(pseudo-instruction)は、アセンブリ言語において、アセンブラ自体に対する指示や処理を行う命令のことです。代表的なものにDB
(Data Byte)があり、これは指定されたメモリアドレスにバイトデータを書き込むためのものです。擬似命令は、機械語への変換の対象ではなく、アセンブラに対する指示としてのみ解釈されます。
ブートセクタを作成するためには、以下の手順があります。
アセンブラでブートセクタを作成
[編集]まず、DB
命令を使用してブートセクタをアセンブリ言語で作成します。例えば、以下はシンプルなブートセクタの例です。
ORG 0x7C00 ; ブートセクタのロードアドレスを0x7C00に指定 start: JMP start ; ブートセクタの無限ループ DB 0x55 ; シグネチャの0xAA55 DB 0xAA
この例では、ORG
命令でブートセクタのロードアドレスを0x7C00
に指定し、JMP start
によって無限ループを実現しています。そして、最後にDB
命令でブートセクタの終了を示すシグネチャ0xAA55
を書き込んでいます。
アセンブリ言語を機械語にアセンブル
[編集]作成したアセンブリコードをアセンブラで機械語に変換します。一般的にはnasm
コマンドを使用します。
nasm boot_sector.asm -o boot_sector.bin
このコマンドにより、boot_sector.asm
がboot_sector.bin
として機械語に変換されます。
ブートセクタの書き込み
[編集]機械語に変換されたブートセクタをブート可能なメディア(ハードディスク、USBメモリ、フロッピーディスクなど)に書き込みます。これは専用のツールやコマンドを使用します。例えば、dd
コマンドを使用する場合:
dd if=boot_sector.bin of=/dev/sdX bs=512 count=1
ここで、/dev/sdX
は書き込む対象のデバイスを指定します。注意が必要で、適切なデバイスを指定しないと誤って他のデータを上書きする可能性があります。
以上の手順により、ブートセクタを作成し、それをブート可能なメディアに書き込むことができます。このブートセクタは非常に基本的なものであり、実際のOSのブートローダーやカーネルの開発にはさらに多くのコードや機能が必要です。
ブートローダ
[編集]ブートローダを作成するためには、Master Boot Record(MBR)の仕様に準拠する必要があります。MBRは512バイトで構成され、最後の2バイトには0xAA55
というシグネチャが必要です。
以下は、シンプルなブートローダの例です。この例では、文字の表示を通じてブートローダの基本的な仕組みを説明します。
ORG 0x7C00 ; ブートローダのロードアドレスを0x7C00に指定 start: mov cx, 5 ; 文字列の長さ mov si, msg ; 文字列のアドレス print_loop: mov ah, 0x0e ; 1文字出力 lodsb ; SIが指すアドレスからALに文字をロード int 0x10 loop print_loop ; CXが0でない場合はループ times 510 - ($ - $$) db 0 ; 510バイトまで0で埋める db 0x55, 0xaa ; MBRの終了を示すシグネチャ msg db 'Hello', 0 ; 文字列 'Hello' を定義
このブートローダは、ディスプレイに文字列 "Hello" を表示するだけの非常にシンプルなものです。int 0x10
はBIOSに対して文字の表示を依頼する割り込みです。
ブートローダのアセンブリコードを機械語に変換するには、以下のコマンドを使用します。
nasm bootloader.asm -o bootloader.bin
これにより、bootloader.asm
がbootloader.bin
として機械語に変換されます。
作成されたブートローダをブート可能なメディアに書き込むためには、dd
コマンドなどを使用します。以下は例です(/dev/sdX
は対象のデバイスに適切に置き換えてください)。
dd if=bootloader.bin of=/dev/sdX bs=512 count=1
これにより、MBRにブートローダが書き込まれます。
このブートローダは非常に基本的なものであり、実際のOSのブートローダはさらに高度で複雑な機能を持っています。ただし、この例を通じてブートローダの基本的な構造やアセンブリ言語の使用方法を理解することができます。
メモリ直書き
[編集]グラフィックのVRAM直書き
[編集]グラフィックモードでのVRAMへの直書きは、ビデオモードを設定し、セグメント方式を使用してアドレスを計算することが必要です。以下は、例としてVRAMに色を書き込むアセンブリコードです。
ORG 0x7C00 start: mov al, 0x13 ; グラフィックモード0x13(320 x 200ドット x 16色モード) mov ah, 0x00 int 0x10 mov bx, 1111 ; VRAMに書き込むアドレスの計算用 mov ax, 0xA000 ; VRAMアクセスのためのセグメント処理で使用 mov ds, ax mov al, 0x01 ; 色の設定 mov [ds:bx], al ; VRAMに書き込み ; 以下同様に続けて書き込む times 510 - ($ - $$) db 0 ; 510バイトまで0で埋める db 0x55, 0xAA ; MBRの終了を示すシグネチャ
このコードでは、ビデオモード0x13を設定して320x200ドットの16色モードに切り替えます。その後、mov ds, ax
でセグメントレジスタを使用してVRAMにアクセスします。mov [ds:bx], al
で指定アドレスに色を書き込んでいます。
テキスト直書き
[編集]一般的に、テキストモードでのVRAMへの直書きも同様にして行います。以下は、テキストモード(80x25文字)でのVRAM直書きの例です。
ORG 0x7C00 start: mov al, 3 ; テキストモード(80x25) mov ah, 0 int 0x10 mov bx, 0 ; VRAMに書き込むアドレスの計算用 mov ax, 0xB800 ; VRAMアクセスのためのセグメント処理で使用 mov ds, ax mov al, 'H' ; 文字 'H' を書き込む mov [ds:bx], al ; 以下同様に続けて書き込む times 510 - ($ - $$) db 0 ; 510バイトまで0で埋める db 0x55, 0xAA ; MBRの終了を示すシグネチャ
この例では、ビデオモードを設定し、テキストモードに切り替えてから、mov [ds:bx], al
で指定アドレスに文字を書き込んでいます。実際のテキスト表示はASCIIコードに基づくものであり、それに対応する文字を指定して書き込みます。
VESA BIOSコール
[編集]上記のコードはVESA BIOSコールではなく、古典的なBIOS割り込みを使用しています。VESA(Video Electronics Standards Association)は、グラフィックモードの設定や操作を行うための標準を提供しており、そのためのBIOSコールも存在します。
上記のコードは、BIOSのINT 0x10を使用してビデオモードを切り替え、VRAMに直接書き込むためにセグメントアドレッシングを利用しています。VESA BIOSコールを使用する場合、より高度なグラフィックモードの設定や操作が可能ですが、そのためにはVESA BIOSの機能を呼び出す必要があります。
以下は、VESA BIOSコールを使用してVESAグラフィックモードに切り替え、VRAMに直接書き込むための簡単な例です。ただし、VESA BIOSコールはBIOSの機能に依存するため、全てのシステムで動作するわけではありません。
ORG 0x7C00 start: ; VESA BIOSコールでグラフィックモード0x13(320 x 200ドット x 256色) mov ax, 0x4F02 mov bx, 0x0013 int 0x10 ; VRAMに直接書き込む処理(例: カラー0xFF) mov edi, 0xA0000 ; VRAMの物理アドレス mov ecx, 320 * 200 ; 画面サイズ(320x200) mov eax, 0xFF ; 書き込む色 rep stosd ; ecx回だけVRAMにeaxの値を書き込む times 510 - ($ - $$) db 0 ; 510バイトまで0で埋める db 0x55, 0xAA ; MBRの終了を示すシグネチャ
このコードはVESA BIOSコールを使用してグラフィックモードを切り替え、rep stosd
命令を使用してVRAMに指定の色を書き込んでいます。ただし、VESA BIOSコールのサポートが必要なため、全ての環境で動作するわけではありません。
VESA BIOSの 存在テスト
[編集]VESA BIOSの存在をテストするためには、VESA BIOSコールのサポート状況を確認する必要があります。以下は、簡単な例です。
ORG 0x7C00 start: ; VESA BIOSコールサポートを確認する mov ax, 0x4F00 mov di, 0x0 ; VESA情報の構造体を格納するためのポインタ int 0x10 ; AX=0x4Fをサポートしている場合、CF=0 jc unsupported_vesa ; CF=0の場合、VESA BIOSコールがサポートされている ; ここに処理を追加する unsupported_vesa: ; サポートされていない場合の処理 ; 例: エラーメッセージを表示 mov ah, 0x0E mov al, 'V' int 0x10 mov al, 'E' int 0x10 jmp $ times 510 - ($ - $$) db 0 ; 510バイトまで0で埋める db 0x55, 0xAA ; MBRの終了を示すシグネチャ
このコードでは、VESA BIOSコールのサポートを確認するために0x4F00
をint 0x10
で呼び出し、Carry Flag (CF) を確認しています。CFがセットされている場合、VESA BIOSコールがサポートされていないことを示します。CFがクリアされている場合、VESA情報が構造体に格納され、VESA BIOSコールがサポートされていることを示します。
もしサポートされていない場合、エラーメッセージを表示して終了します。サポートされている場合、unsupported_vesa
のラベルの下に処理を追加してください。
BIOSに予約されたメモリ領域
[編集]メモリ領域のうち、0xa0000 から0xfffff までの領域は通常のパソコンでは、BIOSがハードウェアを管理するために使用することになっています[1]。
このため、それらハード管理以外のソフトウェア的な処理をするためにメモリ使用したい場合は、この領域を避けてメモリを使用する必要があります。
こういった用途には Linuxなどの現代のオープンソースOSでは、 メモリを使用する際には 0x100000 以降の領域を使うのが一般的です。
原理的には 0xa0000 未満の領域も使用可能ですが、使いすぎ等のミスによって0xa0000以降にハミ出る恐れがあるので、なるべく 0x100000 以降の領域だけを使うほうが安全でしょう。
- 註:
- 上記の記述はBIOSのメモリ管理に関する基本的な概念を説明したものですが、最新のシステムやプラットフォームではUEFI(Unified Extensible Firmware Interface)が一般的になっており、BIOSに代わる形で使用されています。UEFIはBIOSよりも柔軟で機能豊富なファームウェアインターフェースを提供し、従来のBIOSの制約を克服しています。
- また、メモリ管理においても、UEFIがより高度で広範な機能を提供しています。例えば、UEFIブートローダーを使用してメモリの初期化やマッピングを行うことがあります。そのため、BIOSに予約されたメモリ領域の詳細な取り決めや制約もUEFI環境において異なる場合があります。
ハードディスクなどへのアクセス
[編集]まず、フロッピーディスクやハードディスクなどに読み書きのできる割り込み命令で、 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方式といいます。
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ビットモードには、アクセス権の無い状態からのアクセスを禁止するという、特権レベルによる保護機能などがあるので、プロテクトモードという。
プロテクトモードにいこうするためのコードの一部を抜粋すると、おおむね書きのような感じになる[2]。
- コード例
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の先読みした命令を破棄するためである[3][4] 。ジャンプ命令には、先読みを破棄する機能がある。
なお、パイプラインという仕組みにより、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のマスクを解除しないといけない。
下記の順序で作業しないといけない。そういう仕様である。
- GDT(Global Descriptor Table)の作成
- GDTレジスタの設定
- IDT(Interrupt Descriptor Table)の作成
- IDTレジスタの設定
- A20のマスク解除
- CPUへの割り込み禁止
- cr0レジスタの最下位ビット(PEビット)を1に設定
- CPUの先読み(パイプライン)を除去する(jmp命令で除去できる)
- セグメントレジスタの設定
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自作本の中には、絶版になってしまったものもある。
脚注
[編集]- ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、70ページ
- ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、98ページ
- ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、98ページ
- ^ 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日 初版 第1刷 発行、471ページ