Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.5. ソフトウェア割り込み

Supervisor Software Interrupt を利用して、自作プロトコルスタックが必要とするソフトウェア割り込み機能を作ります。ハードウェア割り込みの処理(ISR)はパケットを受信キューへ積むだけの最小限に留め、時間のかかるプロトコル処理はソフトウェア割り込みへ遅延させる、という役割分担です。

Note

RISC-Vの割り込みは「ソフトウェア割り込み」「タイマ割り込み」「外部割り込み」の3種類に分かれており、それぞれが特権レベルごとに用意されています。このうち Supervisor Software Interrupt は、スーパバイザ(S)モードのソフトウェア割り込みです。外部割り込みがデバイス起因なのに対し、ソフトウェア割り込みは「ソフトウェアが自分自身に対して発生させる割り込み」で、sipレジスタのSSIPビットを立てることで発火し、sieレジスタのSSIEビットで有効/無効を制御します。本来はマルチコア間の通知(IPI)などに使われる仕組みですが、ここでは「重い処理を後回しにするために、あとで割り込みハンドラを起動させる」ための手段として利用します。

Supervisor Software Interrupt を扱えるようにする

スーパバイザレベルで処理待ちの割り込みを示すsipレジスタと、割り込みの有効/無効を制御するsieレジスタについて、Supervisor Software Interrupt に対応するビットの定義をkernel/riscv.hに追加します。

📝 kernel/riscv.h

...

 // Supervisor Interrupt Pending
+#define SIP_SSIP (1L << 1) // software
 static inline uint64
 r_sip()
 {

...

 // Supervisor Interrupt Enable
 #define SIE_SEIE (1L << 9) // external
 #define SIE_STIE (1L << 5) // timer
+#define SIE_SSIE (1L << 1) // software
 static inline uint64
 r_sie()
 {

...

Note

どちらもビット1が Software Interrupt に対応することはRISC-Vの仕様で定められています。

CPUの起動時にソフトウェア割り込みが有効になるように、kernel/start.csieレジスタのSIE_SSIEビットをセットします。

📝 kernel/start.c

...
 void
 start()
 {
...

   // delegate all interrupts and exceptions to supervisor mode.
   w_medeleg(0xffff);
   w_mideleg(0xffff);
-  w_sie(r_sie() | SIE_SEIE | SIE_STIE);
+  w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);

...
 }
...

ソフトウェア割り込みの発生と種別の管理

Supervisor Software Interrupt そのものは「発生したかどうか」しか分かりません。自作プロトコルスタックでは、ソフトウェア割り込みの発生理由(どのIRQに対するものか)を自前のビットマップ(pending変数)で管理します。

3.1でスタブにしていたintr_raise()を実装します。あわせて、保留中のソフトウェア割り込みを処理するintr_soft_dispatch()を追加します。

📝 kernel/net/platform/xv6-riscv/intr.c

...

 /*
  * NOTE: if you want to add/delete the entries after intr_run(),
  *       you need to protect these lists with a mutex.
  */
 static struct irq_entry *irqs;
 
+static lock_t pendinglock = LOCK_INITIALIZER;
+static uint64_t pending;
+
 int
 intr_register(unsigned int irq, intr_isr_t isr, int flags, void *arg)
 {

...

+/*
+ * NOTE: only accepts soft IRQs (single bit value out of PLIC source range)
+ */
 int
 intr_raise(unsigned int irq)
 {
+    lock_acquire(&pendinglock);
+    pending |= irq;
+    lock_release(&pendinglock);
+    w_sip(r_sip() | SIP_SSIP);
     return 0;
 }

...

+/*
+ * NOTE: called from devintr() in kernel/trap.c
+ */
+void
+intr_soft_dispatch(void)
+{
+    uint64_t irqs, irq;
+
+    // clear SSIP before taking the pending snapshot, so that an irq
+    // raised while the ISRs run re-triggers the software interrupt.
+    w_sip(r_sip() & ~SIP_SSIP);
+
+    lock_acquire(&pendinglock);
+    irqs = pending;
+    pending = 0;
+    lock_release(&pendinglock);
+
+    for (irq = 1; irqs; irq <<= 1) {
+        if (irqs & irq) {
+            intr_dispatch(irq);
+            irqs &= ~irq;
+        }
+    }
+}
+
 int
 intr_init(void)
 {

...

Important

intr_raise()が受け付けるのはソフトウェア割り込みのIRQ番号だけです。ソフトウェア割り込みのIRQ番号はビットマップで管理する都合上、PLICの割り込みソース番号(1〜53)の範囲外で、かつ1ビットだけ立った値にする必要があります(3.1でINTR_IRQ_SOFT64と定義しておいたのはこのためです)。

Note

intr_raise()pendingにビットを立てたあと、w_sip(r_sip() | SIP_SSIP)で現在のCPUコアに対して Supervisor Software Interrupt を保留状態に設定します。これにより、割り込みが有効になったタイミングでソフトウェア割り込みが発生します。

Note

intr_soft_dispatch()は冒頭でw_sip(r_sip() & ~SIP_SSIP)を実行し、保留中のSSIPをクリアします。これは「今回のソフトウェア割り込みを受け付けた」というアックで、クリアしないと同じ割り込みが繰り返し発火してしまいます。重要なのは、このクリアをpendingのスナップショットを取る前に行っている点です。こうしておくと、スナップショット後(ハンドラ実行中)に新たなintr_raise()が呼ばれた場合、SSIPが改めてセットされ、今回のディスパッチが終わってトラップから戻ったあとに再びソフトウェア割り込みが発生します。もし順序が逆(スナップショットの後にクリア)だと、その間に立ったSSIPを消してしまい、新しいIRQを取りこぼす恐れがあります。

intr_soft_dispatch()はxv6側の割り込み処理から呼び出すため、kernel/defs.hにプロトタイプ宣言を追加します。

📝 kernel/defs.h

...

 // net/platform/xv6-riscv/intr.c
 void            intr_dispatch(unsigned int);
+void            intr_soft_dispatch(void);
 
 // net/platform/xv6-riscv/driver/virtio_net.c
 struct net_device* virtio_net_init(void);

...

Supervisor Software Interruptの捕捉

kernel/trap.cdevintr()で Supervisor Software Interrupt を捕捉してintr_soft_dispatch()を呼び出すようにします。

📝 kernel/trap.c

...
 int
 devintr()
 {
...
     if (irq)
       plic_complete(irq);

     return 1;
+  } else if (scause == 0x8000000000000001L) {
+    // software interrupt.
+    intr_soft_dispatch();
+    return 1;
   } else if (scause == 0x8000000000000005L) {
     // timer interrupt.
...
 }

Note

scause0x8000000000000001Lなら Supervisor Software Interrupt が発生しています。

  • 最上位ビットが1 … 割り込みが発生していることを示しています。0だった場合は例外が発生していることを示しています。
  • 下位の1L … 割り込みの種別が Software Interrupt であることを示しています。

ビルドの確認

ここで実装したソフトウェア割り込みが実際に使われるのは、受信キューを持つプロトコルモジュール(IPやARP)を移植してからです。ここではビルドが通ることを確認して次へ進みます。

$ make