IRON MAIDEN

0-1 導入

割り込み処理を少しやってみたいと思いますが、私は、この部分は、提供されているオペレーティングシステムに任せ、突付かない方がよいと思っています。
理由として、最近のオペレーティングシステムで用意されている他の機能と違い、所謂割り込み処理というのは、まだ成熟しきっていない為、
やってる間にまったく別のものになる可能性があるからです。
他のところよりもこれが変わっちゃ困るでしょ。(変わると書いたのは、バージョンアップではなく、まったく別のものになる可能性があるということです)

ある BBS でこの部分の文書をあげておくと書いたんですが、それが遅れているのも、上記の理由と私の愛すべき上司達が原因です。
(もうこの仕事終わっていいですか・・・だめ、あっそう。)

問題になっている機能ですが、触りだけ触れておきます。(これを確かめるのに時間がかかったというのもあります。)
あまりハッキリとは触れませんが、別のやり方については、かなり触れておりますので、ご容赦ください。

0-2 ソフト割り込み

(software interrupt = softirq)ソフト割り込み
名称はおそらくこれであっていると思います。これはおそらく Linux2.4 系で導入されています。
この辺りが曖昧なのは、私は現在2.4系のことは、あまり知らない・・・ということからです。これ以前の割り込み処理しかやったことが無い。
唯それでも充分だったりします。というより、区分を明確にすると言う意味で、そっちのほうがいいかな?と思ったんで。(失敗かも)
私が引っ掛かったのが、これはよくいうソフトウェア割り込みとは、まったく別のものだというところです。
単純に同じもので、言い回しの違いだと思っていました。ソフトウェア割り込みというのは、プログラマが意識的に発生させるもの。
ソフト割り込みは、カーネル関数を遅延して実行することが出来る。
呼んで解かるとおり、両者はまったく違う・・・。多分、ボトムハーフに似ている。
今までのものとの違いは、今までは例え異なる CPU 上でも、同時に 2 つのボトムハーフは、実行できない。かつそれは、順序を保って実行される。
(ボトムハーフについては別項)に対し、ソフト割り込みの場合は、 2 つの CPU が同時に同じソフト割り込みを利用することは問題なく、順序もない。

もう一つの機能として、(tasklet)タスクレットがあり、ソフト割り込みの上で活動します。
しかしこれには順序があり、 2 つの CPU が同時に 2 つのタスクレットを実行できるが、同時に同じタスクレットは実行できない。

勿論、ボトムハーフもあるにはあるんですが、 Linux2.4 では、タスクレットの上に実現されていて、この場合も、 2 つの CPU から同時にボトムハーフを実行することは出来ない。

これからもこの成熟しきっていない機能が変わっていく度に、デバイスドライバを創る人が泣く羽目になると思うんですが、
ま、知ったこっちゃね〜や、でも頑張って下さい。(というスタンスでは危険か?)

1-1 割り込み処理

ここからやっと当初意図していた事をやっていきます。
まず、割り込み処理とはなんだ?というところですが、定義として、現在実行しているプロセスを変更するような外部(内部)からのイベント、でいいと思います。
よくいわれているのは、プロセッサの生成する命令列を変更するイベントだと思います。

CPU 内部(外部)のハードウェア回路が生成する電気的信号により発生し、同期、非同期の 2 種類に分類されている。

----------------------------------------------------------------------
同期型割り込み・・・・・ CPU が命令を実行中、 CPU により発生、名前の通り、 1 命令の実行終了時のみ制御回路が発行。
非同期型割り込み・・・ CPU のクロック信号に対し、任意のタイミングで CPU 以外のハードウェアデバイスが生成。
----------------------------------------------------------------------

普通、前者を例外後者を割り込みと呼びますが、両者あわせ、割り込み信号と呼ぶこともあります。
例外はプログラミングエラーやカーネルが処理しなければならない異常のときに発生、割り込みは I/O デバイス、インタバルタイマが発生させます。
(身近なところであなたがキーを叩くとき)

1-2 Intel 文書での割り込みと例外の分類

----------割り込み---------------------------------------------

マスカブル割り込み
プロセッサの INTR ピンに送る割り込み。禁止する場合、 eflags レジスタの IF フラグをクリアする。
I/O デバイスが発行するすべての IRQ は、マスカブル割り込みを発生させる。(多分)

ノンマスカブル割り込み
プロセッサの NMI ピンに送る割り込み。 IF フラグをクリアしても禁止できない。
どういったものが発生させるか?というと、ハードウェアの故障等。

---------------------------------------------------------------

----------プリプロセッサが発生させる例外-----------------------

フォルト
例外ハンドラが終了すればこの命令から再実行出来る。例外の要因となったものを取り除いた場合、同じ命令から再実行出来ることが必要。
退避した eip の値はフォルトを発生させた命令のアドレス。

トラップ
完了した命令を再実行する必要のないときにだけ発生。用途として、プログラムのデバッグ等。
プログラム内のブレイクポイントに到達したのをデバッガに知らせる。デバッガで値を突付いてやったら、非デバッガプログラムを次の命令から再実行させることも。
退避した eip の値はトラップを発生した命令の後に実行する命令のアドレス。

アボート
ハードウェアエラー、システム内に無効な値が含まれる場合等に発生。制御回路から送られる割り込み信号はアボート例外ハンドラに制御を移す緊急信号。
該当プロセスを終了させること以外何も行えない。
eip レジスタの更新すらできない場合がある。

------------------------------------------------------------

----------ソフトウェアが発生させる例外-------------------------

プログラマの要求により発生。オーバーフローを調べる into とアドレス境界を調べる bound も結果が真でない場合、この命令を発生。
用途として、システムコールの実装、デバッガに特定のイベントを通知がある。

------------------------------------------------------------

1-3 割り込みが何をしているか?

eip と cs レジスタの内容をカーネルモードスタックに退避しています。その後割り込みの種類に対応したアドレスをプログラムカウンタに代入しています。
はっ!!!これはもしや、プロセス切り替えと同じでは?と思ったのもつかの間、割り込みハンドラ、例外ハンドラが実行するコードはプロセスじゃないじゃん。と自己完結しました。
実行中のプロセスに変わり処理を行うカーネル実行パス・・・のひとつ。ということは、当然プロセスよりも処理が軽い。

もうちょっと詳しく

これは私が勝手に思うことですが、
出来るだけ速く割り込みへの対応をし、この処理の多くの部分を出来るだけ遅延させるという目標を、カーネルは持っていなければなりません。
割り込みはいつ発生するか解かりません。
ネットワークからデータが届いた場合、ハードウェアがカーネルに割り込み、カーネルはデータが存在する印をつけ、それだけでプロセッサを開放します。
それまでに処理していたことを実行させ、後でデータをバッファに渡しネットワークからのデータを持っていたプロセスに渡し、そのプロセスの実行を続けます。
即座に処理する部分と後で処理する部分に分かれているわけです。これをトップハーフ、ボトムハーフといいます。(最初に出てきたやつ)
カーネルは後で実行するボトムハーフ関数へのポインタをキューに保持、適当なタイミングで取り出し、実行
割り込みはどんなタイミングでも発生する為、この処理の実行中に別の割り込みが発生する場合も考えられます。
割り込みは I/O デバイスをビジー状態のまま留めておきます。可能な限り割り込みを受け付けるようにするべきだと思います。

----------------------------------------------------------------------

割り込みハンドラはカーネル実行パスがネストしても支障なく実行できなければならない。
終了すると割り込まれたプロセスの実行を再開しなければならない。
別の割り込み信号が再スケジューリングを起こす場合、他のプロセスに切り替える必要もある。

----------------------------------------------------------------------

先ほど少し触れた、割り込み処理中に別の割り込みを受ける場合、
こういった状態を禁止しなければならないクリティカルリージョンもカーネルコード内に複数存在しますが、このようなものはどうかと思うわけです。
理由として、割り込みハンドラ実行中は出来るだけ長く割り込みを許可しておくべきだと思うからです。
確かに必要だとは思いますが、このために重要なことを見逃す危険もあるわけです。

1-4 割り込みコントローラ

割り込み要求を発行できるハードウェアデバイスコントローラは、 IRQ 用に設計された出力ラインを持っています。
すべての IRQ ラインは、割り込みコントローラと呼ばれるハードウェア回路の入力ピンに接続されています。

----------簡単な動作説明---------------------------------------

IRQ ラインの監視、信号の発生を調べる。
信号発生
発生した信号を対応するベクタへ変換。
ベクタを割り込みコントローラの I/O ポートへ送る。
データパスを通じ CPU が読み取れるようにする。
発生した信号をプロセッサの INTR ピンに送る。(これが割り込みの発生)
PIC プログラマブル割り込みコントローラは、 CPU からの ACK を待つ。
CPU が PIC の I/O ポートに書き込む。
応答が返れば INTR ラインをクリア
最初に戻る

------------------------------------------------------------

IRQ ラインは 0 から番号が振られ Intel は IRQn にデフォルトで n+32 を割り当てています。
割り込みコントローラのポートへ適当な I/O 命令を発行すれば、 IRQ とベクタのマッピングを変更できます。

1-5 ベクタ

割り込みと例外は、 0 〜 255 までの 8 ビット符号無し数字で表される。これをベクタと呼んでいるのは Intel です。
このうち、ノンマスカブル割り込みと例外ベクタは固定で、マスカブル割り込みのベクタは割り込みコントローラを操作することにより変更可能です。

---------- Linux で使用しているベクタ--------------------------

0 〜 31 例外、ノンマスカブル割り込みに対応。
32 〜 47 マスカブル割り込みに対応。つまり、 IRQ による割り込み。
48 〜 255 ソフトウェア割り込みの識別用。 Linux では 128 番( 0x80 )のみ使用。
ユーザモードプロセスが int 0x80 のアセンブリ命令を実行すると CPU はカーネルモードになり、 system_call() カーネル関数を実行。

-------------------------------------------------------------

1-6 ( IDT )割り込みディスクリプタテーブルについて

このシステムテーブルには割り込み、例外のベクタとハンドラのアドレスの対応を登録します。カーネルは割り込みを許可する前にこれを適当に初期化処理しなければなりません。
これに対応するエントリは 8 バイトのディスクリプタで、 IDT には最大 256*8=2048 バイト必要です。
割り込みディスクリプタテーブルはメモリ上の任意の位置に置くことが出来、 idtr レジスタに IDT ベース物理アドレスと上限を指定します。
割り込みを使用する前に idtr アセンブリ命令を使用し初期化します
。 IDT には 3 種類のディスクリプタがありますが、おいおいやっていきます。
実はこの辺りがまだ映像にならない・・・理解はしているんですがそれだけということでしょう。・・・泣)

1-7 例外
あっ!これを書いてない。 80x86 の場合、だいたい例外の発生種類は 20 種類ぐらいらしいです。
正確な数値はプロセッサモデルに依存しているので、『らしい』と書きました。
カーネルは例外の種類に応じ例外ハンドラを用意する・・・当たり前か。
例外ハンドラ実行前にハードウェアエラーコードを生成し、カーネルモードスタックに積む例外もあります。

----------例外の種類/例外ハンドラ/シグナル----------------------------

0 除算エラー(フォルト) プログラムが 0 で除算しようとすると発生。  divide_error()   SIGFPE

1 デバッグ例外(トラップ/フォルト)  eflags レジスタの T フラグが設定されると発生。  debug()   SIGTRAP

2 未使用( NMI ピン使用) ノンマスカブル割り込みように予約。厳密にはカスケード接続用に予約。  nmi()  無し

3 ブレイクポイント(トラップ)  int3 命令により発生。通常デバッガがこの命令を埋め込む。  int3()   SIGTRAP

4 オーバーフロー(トラップ)  into 命令を実行したとき eflags レジスタの OF ( overflow )フラグが設定されていると発生。  overflow()   SIGSEGV

5 Bound 範囲超過(フォルト)  bound 命令をアドレスの有効範囲外にあるオペランドとともに実行すると発生。  bounds()   SIGSEGV

6 無効オペコード(フォルト)  CPU が無効なオペコードを検出。  invalid_op()   SIGILL

7 デバイス使用不可能(フォルト)  ESCAPE 命令か MMX 命令実行時 cr0 レジスタの TS フラグが実行されていると発生。  device_not_available()   SIGGSEGV

8 ダブルフォルト(アボート)  CPU が例外ハンドラを呼び出すとき別の命令を検出すると、この例外は順次処理されるが、プロセッサは連続して処理できないこともある。そのときにこの命令が発生。
  double_fault()   SIGSEGV

9 プロセッサセグメントオーバーラン(アボート) 外部算術演算コプロセッサで問題が起こると発生。( 80386 プロセッサのみ)  coprocessor_segment_overrum()   SIGFPE

10 無効 TSS (フォルト) 無効なタスク状態セグメントを持つプロセスに切り替えようとすると発生。  invalid_tss()   SIGSEGV

11 セグメント不在(フォルト) メモリ中に存在しない(セグメントディスクリプタのセグメント存在)フラグがクリアされていると発生。  segment_not_present()   SIGBUS

12 スタックフォルト(フォルト) 命令がセグメントの範囲を超えようとした場合、 ss レジスタが指すセグメントがメモリ中に存在しない場合発生。  stack_segment()   SIGBUS

13 一般保護(フォルト)  Intel80x86 のプロテクトモードの保護規則に違反すると発生。  general_protection()   SIGSEGV

14 ページフォルト(フォルト) 参照されたページがメモリ上に存在しない、対応するページテーブルエントリが NULL 、ページング保護機構に違反した場合発生。  page_fault()   SIGSEGV

15 Intel が予約済み 無し 無し

16 浮動小数点エラー(フォルト)  CPU に統合されている浮動小数点回路がエラーを検出すると発生。算術オーバーフロー、ゼロ除算等。  coprocessor_error()   SIGFPE

17 アラインメントチェック(フォルト) オペランドのアドレスが正しくアラインされていないと発生。  alignment_check()   SIGSEGV

18 〜 31 将来の為 Intel が予約。

-----------------------------------------------------------------------

あっ!例外ハンドラとシグナル先に書いて説明書きを加えりゃよかった。これじゃ右見た後に左見るから不便だ・・・ま、いいや。

とりあえず、説明的なことはこれぐらいにして、次からはもうちょっと面白い部分に入っていこうと思いますが、
間違いが沢山あるかもしれませんので、他にこの処理の部分を扱ったページとかがあれば、こちらは相互参照的な意味合いで利用されるほうがよいかもしれません。