ハードウェアの割り込み
つづき。わくわく。
割り込みというのは、備え付けのハードウェアから CPU への通知の方法。
カーネルが定期的に入力をチェックしに行くのではなく(=ポーリングするわけではなく)、キーボードの側からカーネルに対して通知するといったことを可能にする。
そっちの方が速いし、カーネルががんばらなくてよくなるからそうしている。
全部のデバイスを CPU につなぐというのは現実的ではないから、CPU とは分離された interrupt controller というものがすべてのデバイスの割り込みを一度集約し、それを CPU に伝えるという構成をとっている。
ハードウェアの割り込みは、CPU 例外とは違って非同期に行われる。そういうわけで、いきなり並行性をこの OS にもちこむことになるので、ちょっとバグるかもしれない。もちろん、Rust のちからを借りられる(デッドロックを除いて)。
リアルワールドでのサンプルや実際のコードがあったら知りたいかも……
Intel 8259
その後後継の APIC が出てきたけれど、後方互換性のために Intel 8259 向けのインタフェースはサポートされ続けている。
APIC
Programmable Interrupt Controller (PIC)。
Intel 8259 には
- Primary Interrupt Controller
- Secondary Interrupt Controller
という2種類のインスタンスが備え付けられている。Secondary は Primary に紐付いているが、Primary は Primary でキーボードやタイマー、フロッピーディスクやシリアルポートを別個で紐付けている。
記事の図。
____________ ____________
Real Time Clock --> | | Timer -------------> | |
ACPI -------------> | | Keyboard-----------> | | _____
Available --------> | Secondary |----------------------> | Primary | | |
Available --------> | Interrupt | Serial Port 2 -----> | Interrupt |---> | CPU |
Mouse ------------> | Controller | Serial Port 1 -----> | Controller | |_____|
Co-Processor -----> | | Parallel Port 2/3 -> | |
Primary ATA ------> | | Floppy disk -------> | |
Secondary ATA ----> |____________| Parallel Port 1----> |____________|
見た感じ、マウスとキーボードは切り離されているんだ。なぜそれを Primary (または Secondary) に紐づけているのか、みたいな話はちょっと知りたいかも。
それぞれのコントローラは command と data の2つの IO ポートをもっている。
なんかいろいろマッピングのし直しが必要らしいので、下記クレートを用いて PIC を用意していく。
ChainedPics
という構造体が、Primary/Secondary PIC レイアウトを表現している。unsafe なのは、PIC の設定ミスがあったときに UB するから。
pub static PICS: spin::Mutex<ChainedPics> =
spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
ところで、VSCode の rust-analyzer が使い始めて以来めっちゃクラッシュするのだけど、みんなそうなのかな。
C-like enum を利用することで、下記のように enum を楽に u8 などの数値型と行き来させられるようになる。
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
Timer = PIC_1_OFFSET,
}
impl InterruptIndex {
fn as_u8(self) -> u8 {
self as u8
}
fn as_usize(self) -> usize {
usize::from(self.as_u8())
}
}
普段は num-derive を使って実質的に変換してたなあ。でも、補助的な実装があまり複雑にならないのであれば、こちらを使わずに C-like enum を使ったほうがよさそう。
core::ops::IndexMut
を使うと、mutable でのインデックスに対するアクセスを実装できる。
a[index]
というアクセスは *a.index_mut[index]
のシンタックスシュガーになっている。イミュータブルな値の取り出しにしたい場合は Index
トレイトを使用すること。
struct の実装に対して enum を使ってインデックスで取り出すみたいなやり方、ドキュメント見るとできそうな感じがする。やったことない…
hlt
命令。opcode は 0xF4
次の外部からの割り込みが来るまで CPU を止めておく命令。
scancode というのは、キーボードが押されたり離されたりした際にキーボードから CPU に送られる符号のこと。
キーと scancode のマッピングには scancode sets と呼ばれる3つの標準が存在する。
- IBM XT
- IBM 3270 PC
- IBM AT
(それぞれ調べるとすごい古そうなコンピュータの写真付き Wikipedia が登場するw)
このあたりの実装は、下記クレートを行ってちょっと楽をする。
最後にちょくちょく出てきたけどわかっていなかった PS/2 と呼ばれるものを調べる。
画像見たらすぐにわかった。かなり昔の PC 向けのものというイメージがある。コネクタの規格のひとつ。USB とか。