Operating Systems Development Series | |
PIC, PIT, and exceptions
Please note: This tutorial covers hardware interrupt handling, not software interrupt
handling. If you are looking for software interrupts, please see
Tutorial 15. This tutorial requires knowledge of software interrupt
handling.
はじめにようこそ...あれ?もうチュートリアル16?前回のチュートリアルでは、割込み処理の世界に深く潜り込みました。ソフトウェア割込み処理に必要なことはほぼすべて網羅しました。しかし、ハードウェア割り込みについてはどうでしょうか? 多くの重要なシステムデバイスは割り込みを使うので、ハードウェアデバイスによって引き起こされる割り込みを処理し、キャッチすることができるようにする必要があります。良いニュース?これはもうすでに私たちのために行われています!8259プログラマブル割り込みコントローラ(PIC)である。次のセクションで詳しく見ていきましょう。 たとえハードウェア割り込みがそれ自体で動作するようになったとしても、システムタイマーの問題に遭遇することになります。システムタイマーは、私たちが設定した有効な割り込みハンドラを使用しない限り、ハードウェア割り込みを有効にしてから数ミリ秒後にトリプルフォルトを起こします。結局のところ、無効な割り込みハンドラを呼び出すことになるわけですからね。したがって、この小さな問題は、プログラマブルインターバルタイマ(PIT)として知られるシステムタイマを再プログラムすることによっても解決されるでしょう。 このチュートリアルでは、多くのことを学びます。このチュートリアルでは、次のようなことを学びます。
さて、それでは早速見ていきましょう。 ハードウェア割り込み割り込みには、ソフトウェアで発生させるもの(INT, INT 3, BOUND, INTOなどの命令で発生させるもの)と、ハードウェアで発生させるものとがあります。ハードウェア割り込みは、PCにとって非常に重要です。他のハードウェアデバイスから、何かが起ころうとしていることをCPUに知らせることができます。例えば、キーボードのキーストロークや、内部タイマーの1クロックの刻みなどです。 これらの割り込みが発生したときに、どのような割り込み要求(IRQ)を発生させるかをマッピングしておく必要があります。こうすることで、ハードウェアの変化を追跡することができます。 これらのハードウェア割り込みについて見てみましょう。
各デバイスについては、まだそれほど心配する必要はありません。 8259Aのピンは、8259PICチュートリアルで詳しく説明されています。 この表に記載されている割り込み番号は、これらのイベントが発生したときに実行するデフォルトのDOS割り込み要求(IRQ)です。 ほとんどの場合、新しい割り込みテーブルを再作成する必要があります。このように、ほとんどのOSでは、PICが使用する割り込みを再マッピングして、IVT内で適切なIRQを呼び出すことを保証する必要があります。これは、リアルモードIVTのBIOSによって行われます。 このチュートリアルの後半で、これを行う方法についても説明します。 待てよ、このPICというのは何なんだ?ハードウェアデバイスに信号を送ることができるこれらのハードウェアデバイスはすべて、8259Aプログラマブル割り込みコントローラ(PIC)に間接的に接続されています。これは特別な、そして非常に重要なマイクロコントローラーで、マイクロプロセッサーがハードウェア割り込みを発射する必要があるときに信号を送るために使用されます。 このマイクロコントローラのプログラミングは、このチュートリアルの後半で少し行います。このマイクロコントローラはかなり複雑なので、別のチュートリアルを用意しています。こちらをお読みください。 割り込みの連鎖IDT(Interrupt Descriptor Table)内に独自の割り込みハンドラを非常に簡単に設置することができるようになります。ソフトウェア割り込みだけでなく、ハードウェアデバイスから発生する割り込みも処理するために割り込みハンドラを作成します。覚えておいてください。ハードウェアデバイスは、プログラマブル割り込みコントローラに信号を送り、プロセッサにハードウェア割り込みの発生を要求します。PICはプロセッサに、割り込み記述子テーブル(IDT)内でどの割り込み要求(IRQ)を呼び出すかを知らせます。しかし、待ってください...IDTの中でどのIRQを呼び出すか、PICはどのように知るのでしょうか?私たちはそれを教えています。 そのため、どの割り込みを使用するかを知らせるために、PICを再プログラムする必要があります。 さて、ソフトウェアとハードウェアの割り込みを処理するための割り込みハンドラができたとします。さて、どうでしょう?私たちの視点から見るとどうでしょうか?確かにデバイスごとにハンドラをインストールするのは簡単ですが、複数のデバイスが同じ割り込みを要求したらどうでしょう?ソフトウエア割り込みで複数の機能が必要な場合はどうでしょうか?そこで登場するのがインタラプトチェーンです。 インタラプトチェーニングは、同じ割り込み番号を共有するすべての割り込みハンドラを復元して呼び出すために使用される技術です。これは、以前の割込みルーチン(IR)を関数ポインタに保存し、新しいハンドラをインストールし、新しいIRが呼ばれるたびに以前の割込みハンドラを呼び出すことによって行われます。 以下はその例です。 ご覧のように、割り込みの連鎖は非常に簡単です。setvect()は新しい割り込みベクタをインストールし、getvect()は割り込みベクタを返します。これらの割り込みベクタは、割り込みベクタテーブル(IVT)または割り込みディスクリプタテーブル(IDT)に格納することができます。待って、何?そうです、私たちのものです。) 割込み処理の実装の準備私たちは割り込みと割り込み処理について多くの領域をカバーしました。テキストだけでは限界があります。ハードウェアの割り込み処理の仕組みも少し見てきましたが、まだ十分ではありません。プログラマブルインタラプトコントローラのプログラミングを学ぶまでは、ハードウェア割り込み処理を実装できません。 また、タイミングの問題を解決するまでは、ハードウェア割り込みを有効にできません(BIOSのおかげでプログラマブルインターバルタイマがまだIRQ8に接続されているのを覚えていますか?つまり、ハードウェア割り込みを再度有効にすると、次のタイマーティックでダブルフォールトが発生するのです)。このため、Programmable Interval Timerを再プログラムする方法を学ぶ必要があります。 ここからが、読者の皆さんにとって、ややこしいところです。ハードウェアプログラミングの世界へようこそ!) しかし、良いニュースもあります...これらのマイクロコントローラは、どれもそれほど複雑ではありません。しかし、本シリーズが複雑になり過ぎないように、これらのマイコンに特化したチュートリアルを2つ書くことにしました。このチュートリアルは、この先のデモやコードを理解するための必読書です。 このため、読者の皆さんには、次のチュートリアルを読んでから続きを読むことをお勧めします。 もし、このチュートリアルの内容をすべて理解できなくても心配しないでください。この後、このチュートリアルの内容をすべて実装していきます。:)また、私はここですべてを説明しますので、あなたを暗闇に導くことはありません。また、上記のチュートリアルを参考文献として使用することも有用でしょう。この後のセクションで、上記のチュートリアルを多く参照します。 さてと...?何を待っているのですか?これらのチュートリアルに飛び込んでください。そして、終わったらここに戻って来てください。心配しないでください、私はただのテキストです...あなたが戻ってきても、私はまだここにいます。) ハードウェアの抽象化最初に見ていくのは、ハードウェア抽象化レイヤーが提供するインターフェイスです。これはinclude/hal.hと hal/hal.cppを見ればわかることです。ルーチンのほとんどは非常に単純で、私たちが開発した(そしてこれから開発する)他のインターフェース(GDT、IDT、CPU、PIC、PITなど)を単に使用するだけなので、深く説明することはありません。その代わりに、インタフェースそのものを見てみたいと思います。これはカーネルとデバイスドライバが使うインタフェースになるわけですが、なぜそうしないのでしょうか?新しい hal.hここで、ハードウェアの抽象化がいかに有用であるかがわかるのです。私は、16bitのDOSをプログラミングするのと同じように簡単に使える「DOS」的なインタフェースを提供したいと思いました。そのために、さまざまな目的に使えるルーチンの簡単なリストを作りました。これらのルーチンを見てみると、そのルーチンが使用するハードウェアデバイスやテーブルを全く参照していないことがわかります。 これこそハードウェアの抽象化です。アーキテクチャを抽象化するのではなく、そのアーキテクチャが使用するハードウェアを抽象化するのです。この後使うコードの多くは、HALの中のルーチンを使ってタスクを実行します。このため、ハードウェアの抽象化レイヤと、それが提供するルーチンを見ていただきたいと思います。
もし、あなたが16bit DOSをプログラムしたことがあるなら、今すぐ家にいるような気分になれるはずです!:) プログラマブルインタラプトコントローラ8259:マイクロコントローラ8259マイクロコントローラファミリは、PIC(Programmable Interrupt Controller)集積回路(IC)のセットであり、ハードウェア割り込みが要求されると、ハードウェアコントローラはPICに間接的に接続される。このため、ハードウェア割り込みを処理するためには、このマイクロコントローラのプログラム方法を理解しておく必要があります。8259は複雑なマイクロコントローラなので、ここですべてを説明します。このため、このコントローラだけをカバーするために、完全なチュートリアルを用意しました。このため、このセクションを最大限に活用するために、PICについて学ぶために次のチュートリアルを参照してください(参照)。 8259Aプログラマブルインタラプトコントローラチュートリアル 注意:ここでは、PICやハードウェア割り込み処理に関するすべてをカバーするわけではありません。上記のチュートリアルを参照してください。 8259:概要PIC(Programmable Interrupt Controller)は、割り込み線を通じてデバイスとプロセッサの接続を行うためのマイクロコントローラです。これにより、デバイスがシステムソフトウェアやエグゼクティブからの注意を必要とするときはいつでも、プロセッサに信号を送ることができます。これがIRQ(Interrupt Request)です。PICはハードウェア割り込み要求のすべてを制御します。これにより、異なるハードウェアデバイスが注意を必要とするときに、そのデバイスからの信号を受信することができます。フロッピーディスクコントローラ(FDC)のようなデバイスが注意を必要とするとき、それは割り当てられているIRQを発射するようにPICに指示します。ここから、PICはプロセッサに信号を送り、呼び出すべき割り込み番号を伝えます。プロセッサは次にIDTにオフセットし、リング0で割り込みハンドラを実行します。 すべての割り込みハンドラを定義したので、これで制御が可能になりました。 これの最も良いところは、PICのおかげですべて自動で行われることです。デバイスがPICに信号を送るたびに、私たちの割り込みハンドラが自動的に実行されます。プロセッサはリング0へのタスクスイッチも実行するので、我々は常にカーネルランドで要求を処理することになる。クールでしょう? PIC自体は複雑なマイクロコントローラです。このチュートリアルを最大限に活用するために、読者の皆さんには上記のPICチュートリアルを読んでいただくことをお勧めします。 それでは、インターフェイスを見ていきましょう。このコードはすべて、このチュートリアルの最後にあるデモの中で見ることができます。 操作コマンドオペレーションコマンドは、ビットパターンで構成される特別なコマンドです。このビットパターンを設定して、マイコンにコマンドを記述する必要がある。オペレーションコマンドには、基本的に、ICW(Initialization Command Words)とOCW(Operation Command Words)の2種類が存在します。ICWは操作コマンドで、デバイスの初期化中にのみ使用する必要があります。OCWは、デバイスが初期化された後に、デバイスを制御するために使用されます。 pic.h:インターフェースこのファイルは、それ以外のミニドライバ全体のインターフェイスを提供します。これは、PICを制御・管理するためのインターフェイスです。私は「ミニドライバ」を、独立したソフトウェアではなく、ソフトウェアの一部に組み込まれたドライバと定義しています。pic.h:デバイスの接続PICのチュートリアルでは、ハードウェアの割り込みを深く掘り下げて見てきました。ハードウェアデバイスがシステムソフトウェアやエグゼクティブの注意を必要とするときに、どのようにPICに信号を送るかを見てきました。これが機能するために、各デバイスはPIC上の割り込み要求(IR)ラインに間接的に接続されています。このラインは、デバイスが使用する割り込み要求(IRQ)だけでなく、そのプリリティレベル(IRQ番号が低いほど、プリリティが高い)も表しています。個々のデバイスとそのIRQを扱うときに役立つように、それらが使用するIRQを抽象化したいと思います。 これは移植性を高めるだけでなく、それらが美しい定数の後ろにあるため可読性を高めるのにも役に立ちます。覚えておいてください。マジックナンバーは悪です! 上記の定数は、使用するすべてのデバイス(とIRQライン/番号)をリストアップしています。PICには8本のIR線があり、それゆえPICごとに8つのIRQしかありません。PICはセカンダリPICとカスケード接続できることを忘れないでください(最大8個のPICを互いにカスケード接続できます)。一般的なx86アーキテクチャでは、2個(1個のプライマリと1個のセカンダリ)だけです。 今、私たちにとって最も重要な2つのデバイスは、タイマー(I86_PIC_IRQ_TIMER)とキーボード(I86_PIC_IRQ_KEYBOARD)です。このチュートリアルでは、I86_PIC_IRQ_TIMERを使用するので、すべてがどのように連動しているかがわかると思います。 pic:8259コマンドPICのセットアップは非常に複雑です。これは、初期化と操作に使用されるさまざまな状態を含むビットパターンである、一連のコマンドワードを通して行われます。まず、PICを制御するために使用されるオペレーションコマンドワード(OCW)を見ていきます。初期化コマンドについては、もう少し後で見ていきます。pic:オペレーション・コマンド・ワード1これは、IMR(Interrupt Mask Register)の値を表しています。特別なフォーマットはないので、インプリメンテーションファイルで直接扱って、ハードウェア割り込みを有効・無効にします。 サイズは1バイトです。正しいビットを設定することで、割り込み要求ラインを有効/無効にします(「マスク/アンマスク」)。1つのPICに8つのIRQしかないことを思い出してください。つまり、IMR のビット 0 は IRQ 0、ビット 1 は IRQ 1、ビット 2 は IRQ 2、...といった具合です。この後、インタラプトマスクレジスタについて説明します。 pic:オペレーション・コマンド・ワード(OCW)2これがPICを制御するための主要な制御語です。では、見てみましょう。
それでは! OCW 2のフォーマットは非常に簡単です。最初の3ビットは現在の割り込みレベルです。ビット3-4は予約です(0でなければなりません)。ビット5はEOI(End of Interrupt)を表します。Bit 6はSelectionビットです。ビット 7 はローテーションコマンドを提供します。 各コマンドは個別のビットで選択されるため、これらのコマンドをビットごとに ORして OCW 2 を生成することができます。 これです。これは、私たちにとって重要なコマンドワードです。すべての割り込みハンドラからこのコマンドワードを送信することが要求されます。 PICは実行されると割り込みをマスクオフするのを覚えていますか?これは、プロセッサがPICを確認するまで、そのIRライン上の割り込み要求がそれ以上実行できないことを意味します。これは、End of Interruptコマンドワードを正しいPICに送信することによって行われます。これは、コマンドワードのEOIビットをマスクオフすることで行えます。これがI86_PIC_OCW2_MASK_EOIが使用される理由です。 少し後に、インターフェイスにはi86_pic_send_commandルーチンがあり、これはPICにコマンドを送信するために使用されることがわかります。このルーチンを使ってEOIコマンドを送信する例を見て、どのように動作するのかを確認しましょう。 上記のコードは、EOIコマンドをpicNumberにあるPICに送ります、クール? OCW 2はこれで終わりです。次のものに移ります。 pic:オペレーションコマンドワード 3*このセクションに追加する予定です。
pic.cpp:インプリメンテーションさて...ここまでは簡単だったでしょう?おそらく、"チャレンジはどこだ!?"と思っていることでしょう。じゃあ、いいや。pic.cppは、PICインターフェイスの実装を提供します。最初に見ておかなければならないのは、レジスタです。 pic.cppを参照してください。レジスタ定数ここでは、PICのポート位置を抽象化するための定数を定義しています。同じポートアドレスを共有していても、すべてのレジスタ名に対して定数を定義していることに注意してください。 理由は完全性のためです。同じポート位置を共有していても、異なるレジスタであることに変わりはないのです。難しいことではありません。データレジスタから書き込む場合、割り込みマスクレジスタ(IMR)にアクセスし、割り込み要求を手動でマスクオフまたはマスク解除するために使用します。このようにして、割り込み要求を有効にしたり、無効にしたりすることができます。 アクセスするレジスタは、書き込み操作か読み出し操作かによって異なります。ポート 0x20 に書き込む場合は、コマンド・レジスタにアクセスすることになります。ポート 0x20 に書き込む場合は、コマンド・レジスタにアクセスし、ポート 0x20 から読み出す場合は、ステータ ス・レジスタにアクセスします。 最後に、これは実装の詳細なので、インタフェースではなく、実装(pic.cpp)の一部となります。 次に、初期化時に使用される定数について見てみましょう。 pic.cpp。初期化制御語1これはPICを初期化するときに使用されるプライマリコントロールワードです。 これはプライマリPICコマンドレジスタに入れる必要がある7ビットの値です。このような形式になっています。
このように、いろいろなことが起こっています。これらのいくつかは、以前にも見たことがあります。これらのビットのほとんどはx86プラットフォームでは使用されないので、これはそれほど難しいことではありません。 各コマンドワードには、2種類の定数があります。1つ目のタイプはビットマスクで、データが表すビットをマスクするために使用されます。もう1つはコマンド・コントロール・ビットで、マスクと組み合わせて正しい値に設定するために使用されます。 もう少し詳しく見てみましょう。以下はICWの1ビットマスクです。最後の3ビットはx86アーキテクチャでは常に0なので、何も定義していません。
さて、上記のビットマスクを使えば、ICW1のビットを設定するのは簡単ですが、その意味をどうやって知るのでしょうか? つまり、設定したいビットをマスクしたとき、設定する値の意味をどうやって知るのでしょうか?そこで登場するのがコマンド・コントロール・ビットです。 コマンド制御ビットには、上記のマスクオフされたビットに設定するための定数値が格納されています。これにより、可読性と拡張性が大幅に向上します。 ICW1のコマンド・コントロール・ビットを以下に示します。見てみましょう。 難しくはありません。この命名規則が使われているので、何をどこで使えばいいのかが簡単にわかります。例えば、I86_PIC_ICW1_SNGL_YESは I86_PIC_ICW1_MASK_SNGLで、I86_PIC_ICW1_LTIM_EDGETRIGGEREDは I86_PIC_ICW1_MASK_LTIMで使用されるのだそうです。 以下は、それらの連携例です。PICを初期化する際、初期化を有効にし、ICW4を送信する必要があります。 これを行うには、単純に以下のようにICW1を設定します。 それだけですか!そうです。すべてがどのように機能し、組み合わされているかに注目してください。これは、特定のビット(または一連のビット)を既知の値に設定するために、実装全体で使用されています。ここで一番良いのは、上記のコードを見るだけで、それが何をやっているのかがわかることです。(初期化を開始し、ICW 4を期待する)。この方法は、このシリーズを通して、ビットの設定やマスクオフの際に必要なときに使用する予定です。 初期化制御ワード2この制御ワードは、PICが使用するIVTのベースアドレスをマップするために使用されます。
初期化時に、ICW2をPICに送って、使用するIRQのベースアドレスを伝える必要があります。もしICW1がPICに送られたなら(初期化ビットが設定された状態で)、次にICW2を送らなければなりません。そうしないと、未定義の結果になることがあります。ほとんどの場合、不正な割り込みハンドラが実行されます。 このコマンドは複雑な形式ではないので、pic.cpp内部で直接処理され、定数はありません。 初期化制御ワード 3このコマンドワードは、PICコントローラがどのようにカスケード接続されるかを知らせるために使用されます。複数のPICをカスケード接続するには、PICのIRラインを互いに接続する必要があります。それがどのラインなのかを知らせるために、このコマンドワードを使用します。
このコマンドは複雑な形式ではないので、pic.cpp内部で直接処理され、定数はありません。 初期化制御ワード4イエーイ!これは最終的な初期化制御ワードです。これは、すべてがどのように動作するかを制御します。
これはかなり複雑なコマンドワードですが、それほど悪くはありません。それでは、定義されたビット・マスクを見てみましょう。 上図のようなフォーマットになっていることに注目してください。 ICW 1と同様に、プロパティを設定するためにビットマスクと組み合わせて使用される制御ビットのセットがあります。これが... これはシンプルなスナフキンですね。^_^ 上記のコントロールビットをビットマスクと組み合わせて使うことで、コントロールワードを構築することができます。命名規則が使われているので、どのようなビットマスクと一緒に使われているのかが簡単にわかります。 インプリメンテーションで使用する定数はこれで終わりとします。では、関数に取りかかりましょう。 i86_pic_send_command ():PICにコマンドを送信します。このルーチンは、PICのコマンドレジスタにコマンドバイトを送信します。picNumは、アクセスするPICを表すゼロベースのインデックスです。x86では、これは0か1のどちらかであるべきです。正しいコマンド・レジスタを得るために、どのPICで作業しているかをテストしていることに注意してください。これはインターフェイスの一部ですが、インターフェイスの外ではそれほど使用されるべきものではありません。これは、必要であれば、手動でPICを送信し、制御できるようにメソッドを提供します。これは、EOIコマンドを送信するための割り込みハンドラによって必要とされるでしょう。
i86_pic_send_data()とi86_pic_read_data()。PICにデータバイトを送ったり、PICからデータバイトを返したりします。これらのルーチンは上記のルーチンと非常に似ていますが、picNumのPICに応じてPICのデータレジスタに書き込みまたは読み出しを行います。 これらのルーチンの両方がインラインであることに注意してください。これらのルーチンは小さいので、関数コールを取り除きたいと思います。
i86_pic_initialize()。PICを初期化するこれはPICインターフェイスのための最終ルーチンです。これは、上記のすべてのルーチンと、初期化制御語用に定義された定数を使って、動作のために両方のPICを初期化します。このルーチンはあまり複雑ではありません。というか、見た目ほど複雑ではありません ;)このルーチンが行うのは、PICに初期化コマンドを送るだけです。これは、コマンドワードのI86_PIC_ICW1_INIT_YESビットを設定することで行います。また、I86_PIC_ICW1_IC4_EXPECTビットを設定し、コントローラがICW 4を送信することを保証しています。 定数が可読性を向上させていることにお気づきでしょうか。 ICWは...そう...icwに格納されています。i86_pic_send_command()ルーチンを使って、両方のPICにコマンドを送ります。 ICW 1が送信された後、ICW 2を送信して初期化を開始します。ICW 2にはベース割り込み番号が含まれており、base0と base1パラメータに渡されます。 ICW 3はPICコントローラのマスターとセカンダリ間の接続に使用されます。 最後にICW 4ですが、I86_PIC_ICW4_UPM_86MODEビットをセットして、x86モードをセットアップしています。このルーチンをPICチュートリアルにある例と比較してみてください、驚くことでしょう...その類似性にとても驚かされることでしょう! *ふぅー*、これでPICの大仕事はすべて終わったようです。心配しないでください、PICほど複雑ではありません。 見てみましょう... プログラマブルインターバルタイマよし...PICの準備ができたので、ハードウェア割り込みを有効にすることができますね?ええ、ちょっとだけ。今のところすべて順調ですが、PIT用の割り込みハンドラはまだインストールされていません。では、次のタイマティックで何が起こるのでしょうか?プログラマブルインターバルタイマ(PIT)は、プログラムされたカウントに達すると割り込みを発生させるカウンタです。8253および8254マイコンは、i86アーキテクチャで使用可能なPITで、i86互換システムのタイマとして使用されます。 x86アーキテクチャでは、PITはシステムタイマーとして動作し、PICのIR0ラインに接続されています。 これにより、PITはタイマーを刻むごとにIRQ 0を発生させることができます。このため、このマイクロコントローラを使用する前に、再プログラムする必要があります。 PITはプログラミングが複雑なマイコンです。このため、PITについては、別のチュートリアルを作成しました。 それでも、すべてを詳細に説明するつもりですが、ここでは、PITのすべてをカバーすることはできません。 PITについて学ぶには、以下のチュートリアルをご覧ください(参考)。 pit.h:インターフェースPITの良いところは、プログラミングがそれほど複雑でないことです。それほど多くのコマンドを含んでいるわけでもなく、かといってそれほど多くのコマンドを必要とするわけでもない。PITは小さいけれども、ハードウェアのタイミングやリクエストに使われるパワフルなチップです。操作コマンドワードPITは、カウンタの初期化に使うオペレーションコマンドワード(OCW)を1つだけ含んでいます。 これは、カウンタのカウントモード、オペレーションモードを設定し、初期カウント値を設定するためのものです。コマンドワードは、少し複雑です。以下は、コマンドワードの完全版です。
PICのインターフェースと同様に、コマンドのフォーマットを記述するために、いくつかのビットマスクを設定します。以下は、その内容です... なるほど、PICで設定したICWやOCWより小さいが、実はこちらの方が複雑である。PICで使うコマンドは1ビットと単純ですが、この演算コマンドワードで使うコマンドはそうではありません。 これはコマンド制御ビットが輝くところです。これらは、上記の異なるビットマスクのための異なる設定とビットの組み合わせを定義するのに役立ちます。以下はその例です。 例を見てみましょう。例えば、カウンタ0を矩形波発生器として、バイナリカウント方式で初期化したいとします。このようにします。 簡単すぎると思いますが、いかがでしょうか?ocwには、PICに送信できる演算コマンドワードが入ります。これらの定数を使用することで、読みやすさを向上させるだけでなく、エラーの可能性を減少させることができることに注意してください。 私はそれがpit.hにあるすべてであると思います。次は、pit.cppに飛び込んでみましょうか?ウィー......! pit.cpp。インプリメンテーションこれは、PITミニドライバの大部分を含んでいます。これは、インターフェースと実装の両方で使用される各ルーチンの実装を含んでいます。pit.cpp。レジスタここでは、PITのポートの位置を抽象化するための定数を定義しています。悪くないですね。I86_PIT_REG_COUNTER0、I86_PIT_REG_COUNTER1、 I86_PIT_REG_COUNTER2は、各カウンタのデータ・レジスタです。PIT には 3 つの内部カウンタがあることを思い出してください。I86_PIT_REG_COMMANDはコマンド・レジスタで、PIT を制御・操作するためにコマンド・レジスタにコマンドを書き込む必要があります。 また、_pit_ticksに注目してください。これは非常に特別で重要なグローバルです。 PITカウンタ0がPICのIR0ラインに接続されているのを覚えていますか?つまり、カウンタ0が発火すると、割り込み要求(IRQ)0が発生します。この要求を処理するために、割り込みハンドラを作成し、インストールする必要があります。 割り込みハンドラが行うべきことは、システムのグローバルティックカウントを更新することです。そのために_pit_ticksがあります。 i86_pit_irq()。PITカウンタ0割り込みハンドラIRQ 0の要求を処理する割り込みハンドラです。カウンタ0が発火するたびに、この割り込みハンドラを呼び出します。このハンドラが行うことは、割り込みが発生するたびにグローバルティックカウントをインクリメントすることだけです。割り込みハンドラの一般的な書式に注意してください。 intstart()は、ハードウェア割り込みを無効にしてスタックフレームを保存し、タスクのスタックを空けずに復帰するために使用するマクロです。この目的は、単純に現在のスタックを変更されないように保護し、そのスタックを維持したままタスクに戻るためです。これらのマクロは、カーネルやデバイスドライバの割り込みハンドラで使用できるようにasm/system.hで定義されています。 割り込みは、特定のコンパイラでのみ使用される特別な定数です。MSVC++の場合は、__declspec(裸)として定義されています。これは、コンパイラが追加したコードを気にする必要がないようにするためです。一部のコンパイラはこのキーワードを直接サポートしています(最も顕著なのは16ビットコンパイラです)。他は(MSVC++のように)サポートしていないので、定義する必要があります。 interruptdone()は、ハードウェア抽象化レイヤで定義された特別なルーチンです。これは、PICに割り込み終了コマンドを送信する責任があります。 これは、私たちのすべての割り込みハンドラが使用する一般的な形式です。
i86_pit_send_command ():PIT にコマンドを送信このルーチンは、PITにコマンドを送信するための非常に重要なルーチンです。これは、送信先のコマンドポートを隠すことができ、ポート名を変更する必要がある場合に便利です。コマンドは、オペレーションコマンドワード(OCW)の形式です。例えば、上記のビットマスクとコマンド制御ビットを使ってOCWを構築することができます。そして、i86_pit_send_command()を使って、OCWをPITに送ります。 i86_pit_send_data()とi86_pit_read_data()。カウンタにデータを送る、カウンタからデータを読み取るこれらのルーチンは、カウンタの読み書き時に使用されるポート名の抽象化を支援します。 これらは、現在のカウント値を設定したり取得したりするために使用されます。これらのルーチンが行うのは、カウンタに渡されたカウンタをテストして、正しいポートを取得することを確認することだけです。そして、そのポートを介して、単純な読み取りまたは書き込み操作を行うだけです。
i86_pit_initialize ():PITを初期化するさて、PITの初期化について説明しましょう。はい!初期化する必要がないので、特に話すことはありません。irqは使用する割り込み番号、irCodeSegは グローバルディスクリプターテーブル(GDT)のコードスレターオフセットです。i86_install_ir()ルーチンを使って、割り込みハンドラ(i86_pit_irq)を割り込み記述子表にインストールします。irqは、プライマリ PIC が IRQ 0 にマッピングされたことを確認するために使用したのと同じベース IRQ 番号であるべきです。
i86_pit_start_counter()。内部カウンタをスタートさせるこれは、PITインタフェースの最後のルーチンです。これはカウンタを立ち上げます。modeは、カウンタに使わせたい動作モード(I86_PIT_OCW_MODE_SQUAREWAVEGENなど)、freqは、カウンタに使わせたい周波数レートを含んでいます。本ルーチンは、ルーチンに渡されたパラメータに基づいて、操作コマンド・ワードを構築する。
まとめこれで基本的なことはすべて完了です。プロセッサのモードやアーキテクチャから、プロセッサテーブル、割り込み、割り込み管理など、このシリーズでは多くのことをカバーしました。これはカーネルの始まりであり、カーネルがそこから構築される場所です。 このチュートリアルでは、PIC、PIT、例外、およびハードウェア割り込み管理のサポートを追加しました。これは重要なステップで、多くの重要なデバイスがハードウェア割り込みを使用するからです。また、これはハードウェア割り込みを再有効化する手段を提供します(保護モードに切り替える前に、ハードウェア割り込みを無効にする必要があったことを思い出してください)。 次のチュートリアルでは、カーネル自体に戻ります。その時は、コンピュータシステムの最も基本的な側面の1つについて話をします。ページングと 低レベルのメモリ管理です。これはまた、私たち自身のシステムAPIの基礎となるものです。 |