Operating Systems Development Series | |
System Architecture
はじめにようこそ!:)前回のチュートリアルでは、ついにブートローダを完成させました!やったー!今のところ、とにかく :)FAT12 ファイルシステムを詳しく説明し、ステージ 2 の読み込み、解析、実行について見てきました。 このチュートリアルは、前回の続きとなります。まず、x86 アーキテクチャについて詳しく見ていきます。これは特にプロテクトモードにおいて重要であり、プロテクトモードがどのように機能するかを理解することができます。 起動時のBIOSとの関係を理解するために、他のプロセッサを「起動」させることができることを思い出してください。BIOSはメインプロセッサでこれを行い、私たちはマルチプロセッサシステムをサポートするために同じことを行うことができます。 これから取り上げます。
基本的な概念を理解することで、プロテクトモードをより詳細に理解することができます。 次のチュートリアルでは、ここで学んだことをすべて使って、プロテクトモードに切り替えてみましょう。 それでは、お楽しみに......。 プロテクトモードの世界この言葉は聞いたことがありますね?80286以降のプロセッサで利用可能な動作モードです。PModeは、主にシステムの安定性を高めるために設計されました。これまでのチュートリアルでご存知のように、リアルモードにはいくつかの大きな問題があります。一つは、好きなところにバイトを書き込むことができることです。これは、ソフトウェアポートやプロセッサ、あるいは私たち自身によって使用されるかもしれないコードやデータを上書きすることができます。しかも、直接的にも間接的にも、4,000以上の異なる方法でこれを行うことができるのです。 リアルモードにはメモリープロテクトがありません。すべてのデータとコードは、単一の多目的使用メモリブロックにダンプされます。 リアルモードでは、16ビットのレジスターに制限されます。このため、1MBのメモリに制限されます。 ハードウェアレベルのMemory Protectionや Multitaskingはサポートされていない。 最大の問題点は、「リング」というものが存在しないことだ。すべてのプログラムはリング0レベルで実行され、すべてのプログラムがシステムを完全に制御できる。つまり、シングルタスク環境では、気をつけないとたった1つの命令(cli/hltなど)でOS全体がクラッシュする可能性があるのです。 このあたりは、リアルモードについて詳しく説明したときと同じように思えるかもしれません。Protected Modeは、これらの問題をすべて解決してくれます。 プロテクトモード
システム・アーキテクチャとプロセッサの仕組みを理解することで、このOSの理解が深まります。 システム・アーキテクチャx86ファミリーのコンピュータは、ヴァン・ノイマン・アーキテクチャーに準拠しています。ヴァンニューマンアーキテクチャとは、典型的なコンピュータシステムが3つの主要な構成要素を持っているという設計仕様である。
![]() 重要なことが2つあります。ご存知のように、CPU はメモリからデータや命令を取り込みます。メモリコントローラは、それが存在する正確なRAMチップとメモリセルを計算する役割を担っています。このため、The CPUはMemory Controllerと通信を行います。 また、「I/Oデバイス」にもご注目ください。これらはシステムバスに接続されている。すべてのI/Oポートは、所定のメモリ位置にマッピングされています。これにより、IN命令とOUT命令を使うことができるのです。 ハードウェアデバイスは、システムバスを介してメモリにアクセスすることができます。また、何かが起こったときにデバイスに通知することもできる。例えば、ハードウェアデバイスコントローラが読み出すために、あるメモリ位置にバイトを書き込むと、プロセッサはそのアドレスにデータがあることをデバイスに通知することができます。これは、システムバス全体のうち、コントロールバスの部分を通じて行われます。これが、ソフトウェアがハードウェアデバイスとやりとりする基本的な方法です。後ほど詳しく説明しますが、プロテクトモードのデバイスと通信する唯一の方法であり、重要なポイントです。 まず、すべてを詳しく説明します。次に、ハードウェアレベルで命令が実行されるのを見ながら、それらを組み合わせて、どのように動作するかを学びます。ここからは、I/Oポートについて、また、ソフトウェアがハードウェアとどのように相互作用するかについて説明します。 x86アセンブリの経験をお持ちの方なら、一部、あるいは大部分を知っているはずです。しかし、ほとんどのアセンブリの本では詳しく説明されていないことをたくさん取り上げるつもりです。具体的には、Ring 0のプログラムに特化した内容です。 システムバスシステムバスは フロントサイドバスとも呼ばれ、CPUをマザーボード上のノースブリッジに接続します。システムバスは、データバス、アドレスバス、コントロールバスを組み合わせたもので、このバスの各電子線が1つのビットを表します。0」と「1」を表す電圧レベルは、TTL(Standard Transistor-Transistor Logic)レベルをベースにしている。しかし、このことを知る必要はありません。TTLは、コンピュータを構成するデジタル・ロジック・エレクトロニクスの一部である。 ご存知のように、システムバスは3つのバスで構成されています。それでは詳しく見ていきましょう。 データバスデータバスは、データを伝送することができる一連の電子線である。データバスのサイズは、16本/ビット、32本/ビット、64本/ビット。電子線と1ビットが直接関係していることに注意してください。32ビットプロセッサは、32ビットのデータバスを持ち、使用します。つまり、4バイトのデータを同時に扱うことができるのです。これを知っていれば、プログラムのデータサイズに気を配ることができ、高速化につながります。 どのように?プロセッサは、1、2、4、8、16ビットのデータをデータバスのサイズに合わせて0を詰める必要があります。大きなデータピースは、プロセッサがデータバス上で正しくバイトを送信できるように分割(およびパディング)する必要があります。データバスの大きさのデータ片を送信すると、余分な処理がないため、より高速になります。 たとえば、64ビットのデータ型があり、32ビットのデータバスがあるとします。最初のクロックサイクルでは、最初の32ビットだけがデータバスを介してメモリコントローラに送信されます。2番目のクロックサイクルでは、プロセッサは最後の32ビットを参照します。注:データ型が大きくなればなるほど、より多くのクロックサイクルを要することに注意してください 一般に、「32ビットプロセッサ」、「16ビットプロセッサ」などの用語は、データバスの大きさを指しています。つまり、「32ビットプロセッサー」は32ビットのデータバスを使用します。 アドレスバスプロセッサやI/Oデバイスがメモリを参照する必要がある場合、アドレスバスにそのアドレスを置きます。 さて、メモリアドレスがメモリ内の位置を表すことは、誰もが知っていることです。しかし、これは抽象的な表現です。メモリーアドレスとは、メモリーコントローラーが使用する番号のことです。それだけです。メモリコントローラは、このバスから数字を取り出し、それをメモリの場所として解釈します。各RAMチップの大きさを知っていれば、メモリコントローラは簡単に正確なRAMチップとその中のバイトオフセットを参照することができます。メモリコントローラは、メモリセル0を起点に、このオフセットを、目的のアドレスとして積分する。 アドレスバスは、コントロールユニット(CU)、I/Oコントローラを介してプロセッサに接続されている。コントロールユニットはプロセッサ内部にあるので、後で見ていくことにする。I/Oコントローラは、ハードウェアデバイスとのインターフェイスを制御する。これについても後ほど見ていきます。 データバスと同様に、各電子ラインは1つのビットを表します。1ビットに含まれる値は2つだけなので、CPUがアクセスできるアドレスは厳密に2^n個となります。したがって、アドレスバスのビット数/ライン数は、CPUがアクセスできる最大メモリ数を表しています。 8080から80186までのプロセッサでは、それぞれ20ライン/ビットのアドレスバスを備えていた。80286と80386は24本/ビット、80386+は32本/ビットである。 x86ファミリ全体が、古いプロセッサとも移植できるように設計されていることを忘れてはならない。そのため、Real Modeでスタートする。そのため、x86ファミリーのプロセッサは、0番から19番までの20本のアドレスラインしか利用できず、1MBに制限されていました。 この制限は、今でも私たちに適用されるからです。そこで、20本目のアドレスからアクセスできるようにするのです。これでOSは4GB以上のメモリにアクセスできるようになります。詳しくは後述します。 コントロールバスでは、Data Busにデータを載せて、Address Busでメモリアドレスを参照する。 しかし、このデータをどう扱うのか?メモリから読み出すのか?それともデータを書き込むのか?コントロールバスは、デバイスが何をしようとしているかを表す一連のライン/ビットです。例えば、プロセッサはREADビットやWRITEビットを設定して、アドレスバスに格納されたメモリ位置からデータバスのデータを読み取りまたは書き込みたいことをメモリコントローラーに知らせます。 また、Control Busは、プロセッサがデバイスに信号を送るためのものです。これは、デバイスに注意を促すためのものです。例えば、アドレスバスのメモリ位置から読み出すようにデバイスに要求することもできます。このように、デバイスに必要なことを知らせることができます。これは、I/Oソフトウェアポートで重要なことです。 もちろん、システムバスはハードウェアデバイスに直接接続されているわけではないことを忘れてはならない。代わりに、それは中央のコントローラに接続されている -I / Oコントローラ、順番に、デバイスに信号を送信します。 それはすべてシステムバスは以上である。システムバスは、プロセッサ(CU)とI/Oデバイス(I/Oコントローラ)から、メモリコントローラ(RAMチップを正確に計算し、アクセスしたいメモリセルを見つける役割)へ、メモリにアクセスしたり読み出すための経路となっています。「コントローラ」...あなたは私がこの用語alotを言うのを聞くことができます。その理由は後ほど説明します。 メモリコントローラメモリコントローラは、マザーボード上のシステムバス(フロントサイドバス、FSB)と物理的なRAMチップの間の主要なインターフェイスです。コントローラという言葉はよく耳にしますよね。コントローラとはいったい何なのでしょうか? コントローラコントローラは、基本的なハードウェア制御機能を提供します。また、ハードウェアとソフトウェアの間の基本的なインターフェイスを提供します。これは私たちにとって重要なことです。保護モードでは、割り込みが使えないことを忘れないでください。ブートローダでは、ハードウェアと通信するためにいくつかの割り込みを使用しました。これらの割り込みをプロテクトモードで使用すると、トリプルフォールトが発生します。 どうすればいいのでしょう?私たちはハードウェアと直接通信する必要があります。これはコントローラを通して行います(コントローラの仕組みについては、後ほどI/Oサブシステムを取り上げる際に詳しく説明します)。 メモリコントローラメモリコントローラは、ソフトウェアでメモリの位置を読み書きする方法を提供します。 メモリコントローラは、RAMチップが情報を保持できるように、常にリフレッシュする役割も担っています。メモリコントローラには、マルチプレクサと デマルチプレクサ回路があり、アドレスバスのアドレスを参照する正確なRAMチップと位置を選択します。 ダブルデータレート(DDR)コントローラDDRコントローラはDDR SDRAMのリフレッシュに使用され、システムクロックパルスを使用してメモリの読み取りと書き込みを可能にします。デュアルチャネルコントローラデュアルチャネルコントローラは、DRAMデバイスを2つの小さなバスに分離し、一度に2つのメモリロケーションの読み取りと書き込みを可能にするために使用されます。これにより、RAMへのアクセスが高速化されます。メモリコントローラ まとめメモリコントローラは、私たちがアドレスバスに入力したアドレスを受け取ります。でも、どうやってメモリコントローラにメモリの読み書きを指示するのでしょうか?メモリを読み出す場合、プロセッサはControl BusにReadビットをセットします。同様に、プロセッサはControl Bus にメモリを書き込むときにWriteビットをセットします。コントロールバスは、プロセッサが他のデバイスのバスの使い方を制御するためのものであることを忘れないでください。 メモリコントローラが使用するデータはData Busの中にある。使用するAddressはAddress Busの中にある。 メモリの読み出しメモリを読み出す場合、プロセッサは読み出す絶対アドレスをアドレス・バスに置きます。 その後、プロセッサはREADコントロール・ラインをセットします。メモリコントローラが制御します。コントローラは、マルチプレクサ回路を使用して絶対アドレスを物理的なRAM位置に変換し、データをデータバスに格納します。その後、READビットを0にリセットし、READYビットを設定します。 プロセッサは、データがデータ・バスにあることを認識します。このデータをコピーして、残りの命令を実行し、BXに格納するのでしょうか。 メモリの書き込みメモリを書き込むプロセスも同様です。まず、プロセッサはメモリのアドレスをアドレスバスに格納します。次に、書き込むデータをデータバスに格納する。そして、コントロールバスにWRITEビットをセットする。 これにより、メモリコントローラは、データバスのデータをアドレスバスの絶対アドレスに書き込むことを知ることができる。書き込みが完了すると、メモリコントローラはWRITEビットをリセットし、Control BusにREADYビットを設定します。 まとめソフトウェアで直接メモリコントローラと通信するのではなく、間接的に通信する。メモリの読み書きをするときは、必ずメモリコントローラを使用します。これが、ソフトウェアとメモリコントローラ/RAMチップのハードウェアのインターフェースです。さて、I/Oサブシステムを見てみましょう。あ、そうだ。1337マルチプレクサの回路はどうなっているのでしょうか?あれはメモリコントローラの中にある物理的な電子回路です。その仕組みを理解するには、デジタルロジックの知識が必要だ。しかし、これは私たちには理解しがたいことなので、ここでは説明しません。もっと知りたい方は、Google! I/OサブシステムI/Oサブシステムは、簡単に言うとポートI/Oを表しています。ソフトウェアとハードウェア・コントローラの間のインターフェイスを提供する基本的なシステムです。もっと詳しく見てみましょう。 ポートポートとは、簡単に言えば、2つのデバイス間のインターフェイスを提供するものです。ポートには、ハードウェアポートとソフトウェアポートの2種類がある。ハードウェア・ポートハードウェアポートは、2つの物理デバイス間のインターフェイスを提供します。このポートは、通常、一種の接続デバイスです。これには、以下のものが含まれますが、これらに限定されるものではありません。シリアルポート、パラレルポート、PS/2ポート、1394、FireWire、USBポートなど。これらのポートは、通常、一般的なコンピュータシステムの側面/背面/前面にあります。 さて...あなたがポートを見たい場合は、ちょうどあなたのコンピュータに接続されている任意の行をたどってください。どうか、ジーヴスのために、これらが何をするものなのか私に聞かないでください。マジで!? 一般的な電子機器では、これらのポートのピンは、ハードウェアデバイスによって異なることを表す信号を伝達します。これらのピンは、システムバスと同じように......ちょっと待てよ。ビットです。各ピンは1つのビットを表します。そう、それです。 ハードウェアのポートには、「オス」と「メス」の2つの分類があります。オス型は、ピンがコネクタから出ている接続です。ハードウェアポートには、コントローラからアクセスします。詳しくは後ほど... ソフトウェアのポートこれは私たちにとって非常に重要なものです。これは、ハードウェアとのインターフェイスです。ソフトウェア・ポートとは番号です。それだけです。この番号はハードウェアのコントローラーを表している...という感じです。複数のポート番号が同じコントローラを表すことがあるのはご存知でしょう。その理由は?メモリマップドI/Oです。基本的な考え方は、特定のメモリアドレスを指定してハードウェアと通信する。ポート番号はこのアドレスを表す...もう一回、ちょっとだけ。アドレスの意味は、デバイスの特定のレジスタを表したり、制御レジスタを表したりします。 後でもっと詳しく見ていきます。 メモリマッピングx86アーキテクチャでは、プロセッサは特定のメモリ・ロケーションを使用して特定のものを表します。例えば、0xA000:0というアドレスは、ビデオカード内のVRAMの先頭を表しています。この場所にバイトを書き込むことで、現在ビデオメモリにあるものを実質的に変更し、画面に表示されるものを実質的に変更することができます。 他のメモリアドレスは、例えばフロッピーディスクドライブコントローラ(FDC)用のレジスタなど、他のものを表すことができます。 どのアドレスが何であるかを理解することは、私たちにとって非常に重要です。 x86 Real Mode Memory Map一般的なx86のリアルモードメモリマップ。
注:上記のデバイスをすべて再マップして、メモリの異なる領域を使用することが可能です。これは、BIOS POSTがデバイスを上の表にマップするために行うものです。 さて、これはクールですべてです。これらのアドレスは異なるものを表しているので、特定のアドレスを読み(書き)出すことで、コンピュータのさまざまな部分から簡単に情報を入手(変更)することができます。 例えば、INT 0x19の話をしましたね。0x0040:0x0072に0x1234という値を書き込み、0xFFFF:0にジャンプすると、実質的にコンピュータをウォームリブートさせることができると説明しました。(Windowsのctrl+alt+delと同じです。) セグ:オフセットアドレッシングモードと絶対アドレスの変換を思い出して、0x0040:0x0072を絶対アドレス0x000000472、BIOSデータエリア内の1バイトに変換できます。 もう一つの例は、テキスト出力です。0x000B8000に2バイト書き込めば、テキストモードメモリの内容を実質的に変更することができます。これは表示時に常にリフレッシュされるため、実質的に画面に文字を表示することになります。どうです? ポートマッピングに戻りましょう。この表は後でもっとたくさん見返すことになります。 ポートマッピング - メモリマップドI/Oポートアドレス」は、各コントローラがリッスンする特別な番号です。起動時に、ROM BIOSはこれらのコントローラデバイスに異なる番号を割り当てます。プライマリプロセッサーを起動し、BIOSプログラムを0xFFFF:0にロードします(これを覚えていますか? 前のセクションの表と比較してみてください)。ROM BIOSは、これらの番号を異なるコントローラに割り当て、コントローラは自分自身を識別する方法を持っています。これは、BIOSは、この特別な番号を使用してハードウェアに通信する割り込みベクターテーブルをセットアップすることができます。 プロセッサは、I/Oコントローラと連携する際に、同じシステムバスを使用します。プロセッサは、あたかもメモリを読み込むかのように、特別なポート番号をアドレスバスに入力します。また、同様にコントロールバスのREADまたはWRITEラインを設定します。しかし、プロセッサはメモリの書き込みとコントローラへのアクセスをどのように区別しているのでしょうか? プロセッサは、コントロールバス上にもう1本、I/O ACCESSラインを設定します。このラインが設定されると、I/Oサブシステム内のI/Oコントローラは、アドレスバスを監視します。アドレス・バスがデバイスに割り当てられた番号に反応した場合、そのデバイスはデータ・バスからその値を受け取って動作する。 メモリコントローラは、このラインが設定されている場合、いかなるリクエストも無視します。したがって、ポート番号が割り当てられていない場合は、まったく何も起こりません。どのコントローラも動作せず、メモリコントローラはそれを無視します。 では、このポート番号について見てみましょう。これは非常に重要です。これは、プロテクトモードのハードウェアと通信するための*唯一の*方法です!: 警告。この表は大きいです。
この表は完全ではありませんが、間違いがないことを祈ります。時間が経過し、より多くのデバイスが開発されたら、この表を追加する予定です。 上の表にあるように、これらのメモリ範囲はすべて、特定のコントローラで使用されます。ポートアドレスの正確な意味は、コントローラによって異なります。コントロールレジスタ、ステートレジスタ、その他何でもありです。これは不愉快なことです。 上の表をプリントアウトしておくことを強くお勧めします。ハードウェアと通信するたびに、この表を参照する必要があります。 この表を更新した場合は、チュートリアルの最初のほうで更新します。そうすれば、この表を再度印刷することができ、誰もが最新のコピーを手にすることができます。 これらのことを念頭に置きながら、すべてをまとめてみましょう... IN命令とOUT命令x86プロセッサは、ポートI/Oに使用する2つの命令を持っています。INと OUTです。デバイスと通信することをプロセッサに伝える命令です。これは、プロセッサがコントロールバスのI/O DEVICEラインを設定することを保証するものです。 キーボードコントローラのインプットバッファから読み出すことができるかどうか、試してみましょう。 上のポート表を見ると、キーボードコントローラはポートアドレス0x60から0x6Fにあることがわかります。この表から、最初のQWORDと2番目のQWORD(ポートアドレス0x60から始まる)は、キーボードとPS/2マウス用であることがわかります。最後の2つのQWORDはシステム用なので、無視することにします。 さて、キーボードコントローラはポート0x60から、厳密にはポート0x68にマッピングされています。これは素晴らしいことですが、私たちにとってどういう意味があるのでしょうか?これはデバイス固有のものです。 キーボードの場合、ポート0x60はコントロールレジスタ、ポート0x64はステータスレジスタです。前にも言いましたが、この用語はもっといろいろな文脈で使われます。ステータス・レジスタのビット1がセットされていれば、データは入力バッファの中にある。つまり、...CONTROLレジスタをREADに設定すれば、入力バッファの内容をどこかにコピーすることができます。 これが、ハードウェアプログラミングとデバイスドライバの基本です。 IN命令では、プロセッサはポートアドレス(0x64など)をアドレスバスに入れ、コントロールバスにI/O DEVICEライン、それに続いてREADラインを設定します。ROM BIOSで0x60に割り当てられたデバイス(この場合、キーボードコントローラのステータスレジスタ)は、READラインが設定されているので、読み取り操作であることを認識します。そこで、キーボード・レジスタ内のある場所からデータをデータ・バスにコピーし、コントロール・バスのREADラインとI/O DEVICEラインをリセットして、READYラインをセットします。これで、プロセッサは読み込んだデータバスのデータを手に入れることができます。 OUT命令も同様です。プロセッサは、書き込むバイトをデータバスにコピーします(データバス幅にゼロ拡張します)。次に、コントロールバスのWRITEラインとI/O DEVICEラインをセットする。そして、ポートアドレス(仮に0x60とする)をAddress Busにコピーする。I/O DEVICEラインが設定されているのは、すべてのコントローラにアドレスバスを監視するように伝える信号である。もし、アドレス・バス上の数字が、割り当てられた数字と一致すれば、そのデバイスはそのデータに対して動作する。この場合、キーボード・コントローラは、コントロール・バスにWRITEラインが設定されているので、WRITE操作であることがわかる。 そこで、データ・バス上の値を、ポート・アドレス0x60が割り当てられた制御レジスタにコピーする。 キーボード・コントローラは、WRITEとI/O DEVICEラインをリセットしてコントロール・バスにREADYラインを設定して、プロセッサを再び制御状態にする。 ポートマッピングとポートI/Oは、非常に重要です。これは、プロテクトモードのハードウェアと通信するための唯一の方法です。割り込みは、書き込まないと使えません。入力や出力のようなハードウェアルーチンとともに、割り込みを書き込むには、ドライバを書く必要があります。そのためには、ハードウェアに直接アクセスする必要があります。もし、あなたが慣れないうちは、まず少し練習してから、このセクションを読み直してください。何か質問があれば、私に知らせてください。 プロセッサー特別な指示80x86の命令のほとんどは、どんなプログラムでも実行することができる。しかし、Kernelレベルのソフトウェアだけがアクセスできる命令もある。このため、読者の皆さんには馴染みのない命令もあるかもしれません。これらの命令のほとんどを使用する必要がありますので、理解することが重要です。
上記の命令をカーネルモードアクセス(リング0)でない他のプログラムが実行すると、General Protection Fault、またはTriple Faultが発生します。 これらの命令が分からなくても心配しないでください。このシリーズを通して、必要に応じてそれぞれを解説していきます。 80x86のレジスタx86プロセッサは、現在の状態を保存するために多くの異なるレジスタを持っています。ほとんどのアプリケーションは、一般フラグ、セグメントフラグ、およびEフラグにのみアクセスできます。その他のレジスタは、KernelのようなRing 0プログラム専用です。x86ファミリには、次のようなレジスタがあります。RAX(EAX(AX/AH/AL))、RBX(EBX(BX/BH/BL))、RCX(EXX(CX/CH/CL))、RDX(EXX(DH/DL))、CS、SS、ES、DS、FS、GS、RSI(ESI(Si))、RDI(EDI(DI))、RBP(EBP(BP))、RSP(ESP(SP))。RSP(ESP(SP))、RIP(EIP(IP))、RFLAG(EFLAG(フラグ))、DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7、TR1、TR2、TR3、TR4、TR5、TR6、TR7、CR0、CR1,CR2、CR3、CR4、CR8、ST、mm0、mm1、mm2、mm3、mm4、mm5、mm6、mm7、xmm0、xmm1、xmm2、xmm3、xmm4、xmm5、xmm6、xmm7、GDTR、LDTR、IDTR、MSRおよびTRです。これらのレジスタはすべて、プロセッサ内部のレジスタファイルと呼ばれる特別なメモリ領域に格納されています。詳しくは、プロセッサ・アーキテクチャのセクションを参照してください。その他のレジスタには、レジスターファイルにないものもありますが、次のようなものがあります。PC、IR、ベクターレジスター、ハードウェアレジスターなどです。 これらのレジスタの多くは、リアルモードリング0プログラムでのみ使用可能です。これには非常に大きな理由があります。これらのレジスタのほとんどは、プロセッサ内の多くの状態に影響を与えます。これらのレジスタの設定を誤ると、CPUは簡単にトリプルフォールトを起こすことができます。また、CPUの誤動作を引き起こす可能性もあります。(特にTR4,TR5,TR6,TR7を使用した場合)。 その他のレジスタの中には、CPUの内部にあり、通常の方法ではアクセスできないものがあります。 これらにアクセスするためには、プロセッサ自体を再プログラムする必要があります。最も顕著なのは、IRと呼ばれるベクターレジスタです。 これらの特殊なレジスタを知る必要があるので、詳しく見ていきましょう。 注:CPUを通常のデバイスと同じように考え、通信を行う必要があります。コントロールレジスタの概念(およびレジスタ自体)は、後ほど他のデバイスと通信する際に重要になります。 また、いくつかのレジスタは文書化されていないことに注意してください。このため、リストアップされたものよりも多くのレジスタが存在する可能性があります。もし知っているのであれば、追加できるように教えてください。 汎用レジスタこれらのレジスタは32ビットで、ほとんどすべての目的に使用できます。しかし、これらのレジスタはそれぞれ特別な目的も持っています。
これらの32ビットレジスタは、それぞれ2つの部分から構成されています。高次ワードと 低次ワードです。 高次ワードは上位16ビットです。低位ワードとは下位16ビットのことです。 64ビットプロセッサの場合、これらのレジスタは64ビット幅で、RAX, RBX, RCX, RDXという名前になります。下位32ビットは32ビットEAXレジスタです。 上位16ビットには特別な名称はありません。これらの名称は、上位8ビットの場合はH、下位8ビットの場合はLが付加されています。 例えばRAXでは これはどういうことかというとAHとALはAXの一部であり、AXはEAXの一部です。したがって、これらの名前のいずれかを変更すると、実質的に同じレジスタ - EAXを変更します。 これは、順番に、64ビットマシン上でRAXを変更する。 上記はBX、CX、DXにも当てはまります。 汎用レジスタは、Ring0からRing4まで、どのプログラム内でも使用することができます。基本的なアセンブリ言語なので、どのように動作するかはすでにご存じであると仮定します。 セグメントレジスタセグメントレジスタは、リアルモードでの現在のセグメントアドレスを変更します。これらはすべて16ビットです。
通常、次のように参照されます。DS:SI、DSはセグメントアドレス、SIはオフセットアドレスです。 セグメントレジスタは、Ring 0からRing 4まで、どのようなプログラムでも使用することができます。セグメントレジスタは基本的なアセンブリ言語であるため、その仕組みはすでにご存知だと思います。 インデックスレジスタx86は、メモリにアクセスする際に役立つレジスタをいくつか使用しています。
32ビットプロセッサの場合、これらのレジスタは32ビットで、ESI、EDI、EBP、ESPという名前が付いています。 64ビットプロセッサの場合、各レジスタは64ビットで、RSI、RDI、RBP、RSPという名前になります。 16ビットレジスタは32ビットレジスタのサブセットで、それは64ビットレジスタのサブセットです。 スタックポインターは、特定の命令に遭遇するたびに、自動的に一定量のバイトをインクリメント、デクリメントします。このような命令には、push*、pop*命令、ret/iret、call、syscallなどがある。 C 言語は、ほとんどの言語で、スタックを定期的に使用します。C 言語が正しく動作するように、スタックを適切なアドレスに設定する必要があります。また、覚えておいてください。スタックは下に向かって成長します。 命令ポインタ / プログラムカウンタ命令ポインタ(IP)レジスタは、現在実行中の命令の現在のオフセット・アドレスを格納します。 覚えておいてください。これはオフセット・アドレスであり、絶対的なアドレスではありません。命令ポインタ(IP)は、プログラムカウンタ(PC)とも呼ばれることがあります。 32ビットマシンでは、IPは32ビットサイズで、EIPと呼ばれます。 64ビット機では、IPは64ビットで、RIPという名称が使われます。 命令レジスタこれは、通常の手段ではアクセスできないプロセッサ内部のレジスタです。これは、命令キャッシュ内のプロセッサの制御ユニット(CU)内に格納されています。プロセッサが内部で使用するためにマイクロ命令に変換されている現在の命令が格納されます。詳しくは、プロセッサ・アーキテクチャを参照してください。EFlagsレジスタEFLAGSレジスタは、x86プロセッサのステータスレジスタです。このレジスタは、現在の状態を判断するために使用されます。 これまで、実際にたくさん使ってきました。簡単な例:jc, jnc, jb, jnb命令ほとんどの命令では、EFLAGSレジスタを操作して、その値が他の値より低いか高いかといった条件を調べることができます。 EFLAGSは FLAGSレジスタで構成されています。同様に、RFLAGSは EFLAGSと FLAGSで構成されています。
IO特権レベル(IOPL)は、特定の命令を使用するために必要な現在のリングレベルを制御します。 例えば、CLI、STI、INおよびOUT命令は、現在の特権レベルがIOPLと同じかそれ以上の場合にのみ実行されます。そうでない場合は、プロセッサによってGPF(General Protection Fault)が生成されます。 ほとんどのオペレーティングシステムでは、IOPFは0または1に設定されています。これは、カーネルレベルのソフトウェアだけがこれらの命令を使用できることを意味します。 これは非常に良いことです。結局のところ、アプリケーションがCLIを発行すれば、実質的にKernelの実行を停止させることができるのです。 ほとんどの操作では、FLAGSレジスタを使うだけでよいのです。RFLAGSレジスタの最後の32ビットは、nill、null、non existant、見る楽しみのために存在することに注意してください。つまり...そうです。もちろんスピードアップのためですが、多くのバイトが無駄になっています... ...そうです。 この表はサイズが大きいので、後で参照するためにプリントアウトすることをお勧めします。 テストレジスタx86ファミリーはテスト用にいくつかのレジスタを使用しています。これらのレジスタの多くは、文書化されていません。x86シリーズでは、TR4,TR5,TR6,TR7がこれにあたります。TR6はコマンドテストに、TR7はテストデータ用レジスタとして最もよく使われます。MOV命令でアクセスできます。このレジスタは、pmモードとリアルモードの両方において、リング0でのみ使用可能です。他の方法でアクセスすると、GPF(General Protection Fault)が発生し、トリプルフォルトになります。 デバッグレジスタこれらのレジスタはプログラムのデバッグに使用されます。DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7です。テスト・レジスタと同様に、MOV命令でアクセスすることができます。ブレークポイント レジスタDR0、DR1、DR2、DR3レジスタには、ブレークポイント条件の絶対アドレスが格納されます。ページングが有効な場合、アドレスは絶対アドレスに変換されます。 これらのブレークポイント条件は、DR7でさらに定義されています。Debug Control RegisterDR7は32ビット・レジスタで、ビット・パターンを使用して現在のデバッグ・タスクを識別します。 ここにそれがあります。
上記リストのビット0...7では,以下のようになります。
デバッグステータスレジスタこれは、エラーが発生したときに何が起こったかを判断するためにデバッガで使用されます。プロセッサは、有効な例外エラーに遭遇すると、このレジスタの下位4ビットを設定し、例外ハンドラを実行します。警告デバッグ・ステータス・レジスタ(DR6)は決してクリアされません。プログラムを続行させる場合は、このレジスタを必ずクリアしてください。 モデル固有レジスタこれは、他のプロセッサにはないような、プロセッサ固有の機能を提供する特別な制御レジスタです。これらはシステムレベルであるため、リング0プログラムのみがこのレジスタにアクセスすることができます。これらのレジスタは各プロセッサに固有であるため、実際のレジスタは変更される可能性があります。 x86には、このレジスタにアクセスするための特別な命令が2つあります。
このレジスタは非常にプロセッサに依存するものです。このため、それらを使用する前にCPUID命令を使用するのが賢明です。 あるレジスタにアクセスするには、アクセスしたいレジスタを表すAddressを命令に渡さなければなりません。 インテルは長年にわたり、マシン固有ではないMSRをいくつか使用してきました。これらのMSRは、x86アーキテクチャでは一般的なものです。
ここに挙げた以外にも、MSRはたくさんあります。完全なリストは、 インテル開発マニュアルの付録Bを参照してください。 このシリーズはまだ開発中なので、どのMSRを参照するかはわかりません。必要に応じて、このリストに追加していく予定です。 RDMSR命令この命令では、CXで指定されたMSRをEDX:EAXにロードします。この命令は特権的な命令で、Ring 0 (Kernel Level)でしか実行することができません。特権のないアプリケーションがこの命令を実行しようとした場合、またはCSの値が有効なMSRアドレスを表していない場合、一般保護フォールトまたはトリプル・フォールトが発生します。 この命令は、いかなるフラグにも影響を与えません。 この命令の使用例です(チュートリアルの後半で再び登場します)。
WRMSR命令EDX:EAX に格納されている 64 ビット値を CX で指定された MSR に書き込みます。この命令は特権命令であり、Ring0(Kernel Level)でのみ実行可能です。特権のないアプリケーションがこの命令を実行しようとした場合、またはCSの値が有効なMSRアドレスを表していない場合、一般保護フォールトまたはトリプル・フォールトが発生します。 この命令は、いかなるフラグにも影響を与えません。 以下は、その使用例です。
制御レジスタここが重要なポイントになりそうです。コントロール・レジスタは、プロセッサの動作を変更するためのものです。それらはCR0、CR1、CR2、CR3、CR4です。 CR0 コントロール・レジスタCR0は主制御レジスタです。32ビットで、次のように定義されています。
例えば、以下のようになります。 すごい、簡単ですね。そうでもないですよ。 このコードをブートローダにダンプすると、ほぼ確実にトリプルフォールトになります。プロテクトモードは、リアルモードとは異なるメモリアドレシングシステムを使用します。また、pmodeには割り込みがないので、1回のタイマ割り込みでトリプルフォルトになります。また、異なるアドレッシングモデルを使用しているため、CSは無効です。32ビットコードに移行するためには、CSを更新する必要があります。さらに、メモリマップの特権レベルは設定されていません。 詳しくは後ほど説明します。 CR1コントロール・レジスタインテルが予約したもので、使用しないでください。CR2コントロール・レジスタPage Fault Linear Address (ページフォルトリニアアドレス)。Page Fault Exceptionが発生した場合,CR2にはアクセスしようとしたアドレスが格納されます.CR3コントロールレジスタCR0のPGビットがセットされている場合に使用されます.下位20ビットにページディレクトリ・ベース・レジスタ(PDBR)が格納されています.CR4コントロールレジスタプロテクトモードにおいて、v8086モード、I/Oブレークポイントの有効化、ページサイズ拡張、マシンチェック例外などの動作を制御するために使用されます。これらのフラグを使用するかどうかはわかりません。これらのフラグが何であるか理解できなくても、あまり気にしないでください。
CR8制御レジスタタスクプライオリティレジスタ(TPR)へのリード/ライトアクセスを提供します。PModeセグメンテーションレジスタx86ファミリーは、各セグメントディスクリプタの現在のリニアアドレスを格納するために、いくつかのレジスタを使用します。 これについては後で詳しく説明します。これらのレジスタは
プロセッサ・アーキテクチャこのシリーズを読んでいると、プロセッサとマイクロコントローラの間に多くの類似点があることに気づきます。 つまり、マイクロコントローラにはレジスタがあり、プロセッサと同じように命令を実行することができるのです。CPU自体は特殊なコントローラチップに過ぎません。ブートプロセスについては、もう少し後で、非常に低レベルの観点から、再度見ていきます。これは、BIOS POSTが実際にどのように開始され、POSTを実行し、プライマリプロセッサを開始し、BIOSをロードするかに関する多くの質問に答えるものです。何をするかは説明しましたが、どのようにするかはまだ説明していません。 注意:このセクションはかなり技術的です。もし、すべてを理解できなくても、心配しないでください。このセクションは、どんなコンピューターシステムにも必要な主要コンポーネントであり、私たちのコードを実行する役割を担っているものに飛び込むためです。機械語はどのように私たちのコードを実行するのでしょうか?機械語は何がそんなに特別なのか?これらについては、ここですべてお答えします。 この後、カーネルと デバイスドライバの開発に入りますが、基本的なハードウェアコントローラのコンポーネントを理解することは、素晴らしい学習体験になるだけでなく、そのコントローラのプログラミング方法を理解するために必要な場合もあることを学ぶことができます。 プロセッサを分解するここでは、説明のためにPentium IIIプロセッサを取り上げます。まず、このプロセッサを開いて、個々の部品に分解してみましょう。![]() Alot of things in the processor, huh? Notice how complex this is. We are not going to learn much from this picture alone, so lets look at each component.
命令の実行方法IPレジスタには現在実行中の命令のオフセット・アドレスが格納され、CSにはセグメント・アドレスが格納されていることを思い出してください。では、プロセッサが命令を実行する場合、具体的にはどのようなことが起こるのでしょうか。 まず、読み出すべき絶対アドレスが計算されます。セグメント:オフセットモデルでは、絶対アドレス=セグメント×16+オフセットであることを思い出してください。あるいは、本質的には、絶対アドレス = CS * 16 + IPです。 プロセッサは、このアドレスをアドレスバスにコピーします。アドレスバイトは、それぞれが1つのビットを表す一連の電子線であることを忘れないでください。このビットパターンは、次の命令の絶対アドレスのバイナリ形式を表しています。 この後、プロセッサは「Read Memory」ラインを有効にします(そのビットを1に設定することで)。これは、メモリコントローラに対して、メモリからの読み出しが必要であることを伝えるものです。 メモリコントローラは制御を行う。メモリコントローラは、アドレスバスからアドレスをコピーし、パリキュラーRAMチップの正確な位置を計算します。メモリコントローラは、この位置を参照し、データバスにコピーする。これは、Control Busに "Read Memory "ラインが設定されているためです。 メモリコントローラはコントロールバスをリセットし、プロセッサが実行を終了したことを認識できるようにします。プロセッサはデータバスの値を受け取り、デジタル・ロジック・ゲートで「実行」します。この「値」は機械命令のバイナリ表現に過ぎず、一連の電子パルスとしてエンコードされています。 例えば、mov ax, 0x4c00という命令があった場合、プロセッサのデータバスには0xB8004Cという値が格納されます。0xB8004Cは、オペレーションコード(OPCode)と呼ばれるものです。すべての命令には、それに関連するオペコードがあります。i86アーキテクチャの場合、この命令はオペコード0xB8004Cと評価されます。 この数字を2進数に変換すると、電子線としてパターンを見ることができ、1がハイ(アクティブ)、0がローを意味します。 プロセッサは、CPUのデジタル論理回路に組み込まれた一連の個別命令に従います。 これらの命令は、プロセッサに一連のビットをどのように符号化するかを指示します。すべてのx86プロセッサは、このビットパターンをmov ax, 0x4c00命令としてエンコードします。 命令が複雑化したため、新しいプロセッサのほとんどは、実際に独自の内部命令セットに従っています。マイクロコントローラの多くは、電子回路の複雑さを軽減するために、複数の内部命令セットを使用しています。通常、これらはマクロコードと マイクロコードである。 マクロコードは、プロセッサが命令をマイクロコードにデコードするために使用する抽象的な命令セットである。マクロコードは通常、電子技術者が開発した特殊なマクロ言語で記述され、コントローラ内部のROMチップに格納され、マクロアセンブラでコンパイルされる。マクロアセンブラは、マクロコードをさらに低レベルの言語(コントローラの言語)にアセンブルする。これがコントローラの言語である「マイクロコード」である。 マイクロコードは、電子技術者が開発した非常に低レベルな言語である。マイクロコードは、コントローラやプロセッサが命令(例えば、0xB8004C(mov ax, 0x4c00)命令)をデコードするために使用される。 CPUはALU(Arithmitic Logic Unit)を使って、0x4C00という数字を得ることができます。そして、それをAXにコピーします(単純なビットコピー)。 この例は、すべてがどのように組み合わされるかを示しています。CPUはシステムバスを使用し、メモリコントローラがメモリロケーションをデコードし、コントロールバスをフォローする仕組みになっています。 これは重要なコンセプトです。Software Portsも同じようにメモリコントローラに依存しています。 プロテクトモード - 理論さて、ではなぜアーキテクチャの話をしたのでしょうか。実は、プロテクトモードでは割り込みが発生しないのです。つまり...割り込みがない。システムコールもない。標準ライブラリもないすべて自分でやらなければならないのです。そのため、私たちを導いてくれる助けの手はありません。 一つの間違いでシステムがクラッシュしたり、気をつけないとハードウェアが壊れたりします。 フロッピーディスクだけでなく、ハードディスク、外部(および内部)デバイスなどです。システムアーキテクチャを理解することは、私たちが多くの間違いを犯さないように、すべてをより良く理解するのに役立ちます。また、直接ハードウェアプログラミングの入門にもなりますし、私たちができるのはこれだけですから。 あなたはこう思うかもしれません。待ってください、あなたが約束した超高性能の1337 Cカーネルはどうなっているんですか!そうですね...C言語はある意味、低レベルの言語であることを思い出してください。インライン・アセンブルによって、ハードウェアとのインターフェイスを作ることができたのです。そしてCは、C++と同じように、プロセッサが直接実行できるx86マシン命令しか生成しない。ただし、標準ライブラリがないことだけは覚えておいてほしい。そして、たとえ高級言語を使っていても、非常に低レベルな環境でプログラミングをしていることになる。 この点については、カーネルを開始するときに説明します。 まとめ私はこの種のチュートリアルを書くのが好きではありません。チュートリアルは、膨大な量の情報を詰め込み、コードは少なく、理解しやすいようにコンセプトを具体的に表示します。単純に書くのが大変なんです、わかりますか? 私は、すべてを十分に説明できたと思います。メモリマッピング、ポートマッピング、x86のポートアドレス、x86の全レジスタ、x86メモリマップ、システムアーキテクチャ、IN/OUTキーワードとその実行方法、そして命令の実行方法について、順を追って見ていきました。また、基本的なハードウェアのプログラミングについても取り上げました。 次回のチュートリアルでは、32ビットの世界へようこそ!ということで、32ビットへの移行を行います。また、GDTについても詳しく見ていきますが、これは切り替えの際に必要となるものです。 また、各ステップごとに、よくあるエラーに対する警告を出すつもりです。前にも言いましたが、プロテクトモードに入るときにちょっとしたミスをすると、プログラムがクラッシュしてしまいます。 楽しくなりそうです... :) |