Linux Kernel Interrupts and Exceptions

Linux Kernel 割り込み & 例外処理 完全ガイド

対象読者: Linux カーネル開発者、SRE、システムエンジニア カーネルバージョン: 主に Linux 6.x 系を対象(5.x 系にも適用可能) 最終更新: 2026-04-10 生成: AI Generated Technical Article


目次

  1. はじめに
  2. 割り込みと例外の基礎概念
  3. 割り込みディスクリプタテーブル (IDT)
  4. ハードウェア割り込み (IRQ) と割り込みコントローラ
  5. 割り込み処理フロー (トップハーフ / ボトムハーフ)
  6. 割り込みハンドラの登録
  7. ソフト割り込み (Softirqs)
  8. タスクレット (Tasklets)
  9. ワークキュー (Workqueues)
  10. スレッド化割り込み (Threaded Interrupts)
  11. CPU 例外処理
  12. ページフォルトハンドラの詳細
  13. タイマー割り込みと高精度タイマー (hrtimer)
  14. IRQ アフィニティと irqbalance
  15. 割り込みコアレッシング
  16. /proc/interrupts と /proc/softirqs
  17. 割り込みストームとその緩和策
  18. NAPI によるネットワーク割り込み処理
  19. 監視・デバッグツール
  20. 実践的なトラブルシューティング
  21. まとめとベストプラクティス
  22. 参考文献

1. はじめに

Linux カーネルにおける割り込み (Interrupt) と例外 (Exception) の処理は、オペレーティングシステムの最も根幹をなすメカニズムである。ハードウェアデバイスからの通知、タイマーイベント、メモリアクセス違反、システムコールなど、CPU が通常のプログラム実行を中断して特別な処理を行う必要がある場面は、システム動作中に秒間数万回〜数十万回発生する。

本記事では、x86_64 アーキテクチャを中心に、Linux カーネルの割り込みと例外処理のメカニズムを体系的かつ包括的に解説する。単なる理論的な説明にとどまらず、実際のカーネルソースコード、設定コマンド、デバッグ手法を交えながら、実運用で必要となる知識を網羅する。

1.1 割り込みと例外の重要性

割り込みと例外は以下の理由からカーネルの中核をなす:

  • デバイスとの通信: ネットワークカード、ディスクコントローラ、キーボードなど、すべてのハードウェアデバイスは割り込みを通じて CPU に通知を送る
  • タイムキーピング: システムクロック、スケジューラのタイムスライス管理はタイマー割り込みに依存する
  • メモリ管理: ページフォルト例外がデマンドページング、Copy-on-Write、スワッピングの基盤となる
  • エラー処理: ゼロ除算、不正メモリアクセス、特権命令の実行などの異常状態を検出・処理する
  • パフォーマンス: 割り込み処理の効率がシステム全体のスループットとレイテンシに直結する

1.2 本記事の構成

割り込み/例外の全体像
├── ハードウェア層
│   ├── 割り込みコントローラ (PIC, APIC, IO-APIC, MSI/MSI-X)
│   ├── IDT (Interrupt Descriptor Table)
│   └── CPU例外メカニズム
├── カーネル処理層
│   ├── トップハーフ (ハードウェア割り込みハンドラ)
│   ├── ボトムハーフ
│   │   ├── Softirqs
│   │   ├── Tasklets
│   │   └── Workqueues
│   └── スレッド化割り込み
├── 特定用途
│   ├── ネットワーク (NAPI)
│   ├── タイマー (hrtimer)
│   └── ページフォルト
└── 運用・デバッグ
    ├── /proc インターフェース
    ├── IRQ アフィニティ
    ├── 割り込みコアレッシング
    └── 監視ツール

1.3 前提知識

本記事を理解するために、以下の前提知識が必要である:

  • C言語の基礎知識
  • x86_64 アーキテクチャの基本概念(レジスタ、特権レベル、メモリモデル)
  • Linux カーネルの基本的な構造(カーネルモジュール、デバイスドライバの概念)
  • コマンドラインでの基本的な Linux 操作

2. 割り込みと例外の基礎概念

2.1 割り込みとは何か

割り込み (Interrupt) とは、CPU の通常の命令実行フローを中断させ、特定のハンドラルーチンに制御を移すメカニズムである。x86_64 アーキテクチャでは、割り込みは以下の 3 種類に大別される:

種類発生源同期/非同期
ハードウェア割り込み外部デバイス非同期NIC、ディスク、タイマー
ソフトウェア割り込みINT 命令同期システムコール (INT 0x80)
例外CPU内部同期ページフォルト、ゼロ除算

2.2 例外の分類

CPU 例外はさらに以下の 3 つに分類される:

2.2.1 フォルト (Fault)

フォルトは修正可能な例外であり、例外が発生した命令を再実行できる。ページフォルトが最も代表的な例である。

実行フロー:
命令実行 → フォルト発生 → ハンドラで修正 → 同じ命令を再実行

2.2.2 トラップ (Trap)

トラップは例外が発生した命令の次の命令から実行を再開する。デバッグ例外やブレークポイントが該当する。

実行フロー:
命令実行 → トラップ発生 → ハンドラで処理 → 次の命令から再開

2.2.3 アボート (Abort)

アボートは回復不可能な深刻なエラーを示し、通常はプロセスまたはシステムの終了につながる。ダブルフォルトやマシンチェック例外が該当する。

実行フロー:
命令実行 → アボート発生 → ハンドラで処理 → プロセス/システム終了

2.3 x86_64 における割り込みベクタ

x86_64 では、0〜255 の 256 個の割り込みベクタが定義されている:

ベクタ番号    用途
─────────────────────────────────────────────
0  (0x00)    除算エラー (#DE)
1  (0x01)    デバッグ例外 (#DB)
2  (0x02)    NMI (Non-Maskable Interrupt)
3  (0x03)    ブレークポイント (#BP)
4  (0x04)    オーバーフロー (#OF)
5  (0x05)    BOUND 範囲超過 (#BR)
6  (0x06)    無効オペコード (#UD)
7  (0x07)    デバイス使用不可 (#NM)
8  (0x08)    ダブルフォルト (#DF)
9  (0x09)    コプロセッサセグメントオーバーラン
10 (0x0A)    無効 TSS (#TS)
11 (0x0B)    セグメント不在 (#NP)
12 (0x0C)    スタックセグメントフォルト (#SS)
13 (0x0D)    一般保護例外 (#GP)
14 (0x0E)    ページフォルト (#PF)
15 (0x0F)    予約済み
16 (0x10)    x87 浮動小数点例外 (#MF)
17 (0x11)    アライメントチェック (#AC)
18 (0x12)    マシンチェック (#MC)
19 (0x13)    SIMD 浮動小数点例外 (#XM)
20 (0x14)    仮想化例外 (#VE)
21 (0x15)    制御保護例外 (#CP)
22-31        予約済み
32-255       ユーザー定義(ハードウェア割り込み、ソフトウェア割り込み)

2.4 割り込み処理の全体的な流れ

割り込みが発生してから処理が完了するまでの全体的な流れを示す:

[ハードウェア/CPU]                    [カーネル]
      │                                   │
      │ 1. 割り込み/例外発生               │
      │ 2. CPU が現在の状態を保存          │
      │    (RIP, CS, RFLAGS, RSP, SS)     │
      │ 3. IDT からハンドラアドレス取得     │
      │ 4. 特権レベル切り替え (必要な場合)  │
      │ 5. ハンドラにジャンプ              │
      │─────────────────────────────────→│
      │                                   │ 6. レジスタ保存 (pt_regs)
      │                                   │ 7. トップハーフ実行
      │                                   │ 8. ボトムハーフスケジュール
      │                                   │ 9. レジスタ復元
      │                                   │ 10. IRET で復帰
      │←─────────────────────────────────│
      │ 11. 中断された処理を再開           │

2.5 割り込みコンテキスト

Linux カーネルでは、コードの実行コンテキストが重要な概念である:

/* コンテキストの判定 */
in_interrupt()    /* 割り込みコンテキスト (ハード or ソフト) */
in_irq()          /* ハード割り込みコンテキスト */
in_softirq()      /* ソフト割り込みコンテキスト */
in_task()         /* プロセスコンテキスト */
in_serving_softirq()  /* ソフト割り込みサービス中 */

各コンテキストで許可される操作:

操作プロセスコンテキストソフト割り込みハード割り込み
スリープ不可不可
メモリ確保 (GFP_KERNEL)不可不可
メモリ確保 (GFP_ATOMIC)
ミューテックス取得不可不可
スピンロック取得
ユーザー空間アクセス不可不可
スケジューラ呼び出し不可不可

3. 割り込みディスクリプタテーブル (IDT)

3.1 IDT の概要

IDT (Interrupt Descriptor Table) は、x86_64 アーキテクチャにおいて割り込みと例外のハンドラアドレスを管理するテーブルである。CPU が割り込みや例外を受け取ると、このテーブルを参照して対応するハンドラにジャンプする。

IDT は最大 256 エントリを持ち、各エントリは 16 バイトの「ゲートディスクリプタ」で構成される。

3.2 ゲートディスクリプタの構造

x86_64 の IDT ゲートディスクリプタ (16 バイト) の構造:

ビット位置    フィールド
──────────────────────────────────
127:96       予約済み (0)
95:64        オフセット [63:32]
63:48        オフセット [31:16]
47           P (Present) ビット
46:45        DPL (Descriptor Privilege Level)
44           0 (固定)
43:40        タイプ (0xE=割り込みゲート, 0xF=トラップゲート)
39:35        予約済み (0)
34:32        IST (Interrupt Stack Table) インデックス
31:16        セグメントセレクタ
15:0         オフセット [15:0]

3.3 カーネルソースにおける IDT の実装

Linux カーネルにおける IDT の定義は arch/x86/kernel/idt.c にある:

/* arch/x86/kernel/idt.c */

/* IDT テーブルの定義 */
static gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

/* IDT ディスクリプタ */
static struct desc_ptr idt_descr __ro_after_init = {
    .size       = IDT_TABLE_SIZE - 1,
    .address    = (unsigned long) idt_table,
};

/* 例外ベクタのエントリ定義 */
static const __initconst struct idt_data def_idts[] = {
    INTG(X86_TRAP_DE,           asm_exc_divide_error),
    ISTG(X86_TRAP_NMI,          asm_exc_nmi, IST_INDEX_NMI),
    INTG(X86_TRAP_BR,           asm_exc_bounds),
    INTG(X86_TRAP_UD,           asm_exc_invalid_op),
    INTG(X86_TRAP_NM,           asm_exc_device_not_available),
    ISTG(X86_TRAP_DF,           asm_exc_double_fault, IST_INDEX_DF),
    INTG(X86_TRAP_OLD_MF,       asm_exc_coproc_segment_overrun),
    INTG(X86_TRAP_TS,           asm_exc_invalid_tss),
    INTG(X86_TRAP_NP,           asm_exc_segment_not_present),
    INTG(X86_TRAP_SS,           asm_exc_stack_segment),
    INTG(X86_TRAP_GP,           asm_exc_general_protection),
    INTG(X86_TRAP_SPURIOUS,     asm_exc_spurious_interrupt_bug),
    INTG(X86_TRAP_MF,           asm_exc_coprocessor_error),
    INTG(X86_TRAP_AC,           asm_exc_alignment_check),
    INTG(X86_TRAP_XF,           asm_exc_simd_coprocessor_error),
    ISTG(X86_TRAP_MC,           asm_exc_machine_check, IST_INDEX_MCE),
#ifdef CONFIG_X86_KERNEL_IBT
    INTG(X86_TRAP_CP,           asm_exc_control_protection),
#endif
#ifdef CONFIG_IA32_EMULATION
    SYSG(IA32_SYSCALL_VECTOR,   entry_INT80_compat),
#endif
};

3.4 IDT の初期化プロセス

IDT の初期化はブートプロセス中に段階的に行われる:

boot 開始
    │
    ├── early_idt_setup()        # 最初期の IDT 設定(最小限)
    │   └── 全エントリに early_idt_handler_array[] を設定
    │
    ├── idt_setup_early_traps()  # 重要な例外ハンドラの設定
    │   └── #DE, #DB, #NMI, #BP, #OF, #DF, #MC
    │
    ├── idt_setup_early_pf()     # ページフォルトハンドラ
    │   └── #PF
    │
    ├── idt_setup_traps()        # 残りの例外ハンドラ
    │   └── #BR, #UD, #NM, #TS, #NP, #SS, #GP, etc.
    │
    ├── idt_setup_ist_traps()    # IST を使用する例外
    │   └── #DF, #NMI, #MC (IST スタック使用)
    │
    └── idt_setup_apic_and_irq_gates()  # APIC と外部割り込み
        └── ベクタ 32-255 の設定

3.5 IST (Interrupt Stack Table)

x86_64 では、Interrupt Stack Table (IST) メカニズムにより、特定の例外に対して専用のスタックを使用できる。これは、スタック破壊時でも例外を安全に処理するために重要である。

/* arch/x86/include/asm/stackprotector.h */
/*
 * IST エントリの定義:
 * IST1: ダブルフォルト (#DF)
 * IST2: NMI
 * IST3: デバッグ (#DB)
 * IST4: マシンチェック (#MC)
 */

#define IST_INDEX_DF    0   /* ダブルフォルト用スタック */
#define IST_INDEX_NMI   1   /* NMI 用スタック */
#define IST_INDEX_DB    2   /* デバッグ用スタック */
#define IST_INDEX_MCE   3   /* マシンチェック用スタック */

IST スタックは各 CPU に対して個別に確保される:

/* per-CPU IST スタック */
DEFINE_PER_CPU_PAGE_ALIGNED(struct ist_stacks, ist_stacks);

struct ist_stacks {
    char    exception_stacks[(N_EXCEPTION_STACKS - 1) * EXCEPTION_STKSZ
                             + DEBUG_STKSZ];
};

3.6 IDT の確認方法

実行中のシステムの IDT 情報を確認するには:

# IDT レジスタの内容を確認 (要 root)
# /proc/cpuinfo からは直接見えないが、以下で確認可能

# 方法 1: dmesg から IDT 関連メッセージを確認
dmesg | grep -i "idt\|interrupt"

# 方法 2: /sys/kernel/debug/x86/idt (カーネルデバッグ有効時)
cat /sys/kernel/debug/x86/idt_table 2>/dev/null

# 方法 3: crash ツールを使用
crash> idt_table
# または
crash> p idt_table

# 方法 4: SystemTap スクリプト
stap -e 'probe begin {
    printf("IDT base: %p\n", @var("idt_descr@arch/x86/kernel/idt.c")->address)
    exit()
}'

3.7 IDTR レジスタ

IDTR (IDT Register) は IDT のベースアドレスとサイズを保持する特殊レジスタである:

┌──────────────────────────────────────────┐
│  IDT ベースアドレス (64ビット) │ リミット (16ビット) │
└──────────────────────────────────────────┘

LIDT 命令で IDT をロードし、SIDT 命令で現在の IDTR の値を取得できる:

/* カーネル内での IDTR 操作 */
static inline void load_idt(const struct desc_ptr *dtr)
{
    asm volatile("lidt %0" :: "m" (*dtr));
}

static inline void store_idt(struct desc_ptr *dtr)
{
    asm volatile("sidt %0" : "=m" (*dtr));
}

4. ハードウェア割り込み (IRQ) と割り込みコントローラ

4.1 割り込みコントローラの進化

x86 プラットフォームにおける割り込みコントローラは、以下のように進化してきた:

PIC (8259A)  →  APIC  →  xAPIC  →  x2APIC
     │              │
     │              ├── Local APIC (各CPUに内蔵)
     │              └── I/O APIC (チップセット上)
     │
     └── MSI/MSI-X (PCIe デバイス向け)

4.2 PIC (Programmable Interrupt Controller) - 8259A

PIC は最も古い割り込みコントローラであり、IBM PC 互換機で使用されていた。2つの 8259A をカスケード接続して 15 本の IRQ ラインを提供する:

        マスター PIC (8259A)          スレーブ PIC (8259A)
        ┌─────────────────┐          ┌─────────────────┐
IRQ 0 ──│ IR0  タイマー     │          │ IR0  RTC        │── IRQ 8
IRQ 1 ──│ IR1  キーボード   │          │ IR1  (ACPI/SCI) │── IRQ 9
IRQ 2 ──│ IR2  ← カスケード │←─────────│ IR2  未使用      │── IRQ 10
IRQ 3 ──│ IR3  COM2       │          │ IR3  未使用      │── IRQ 11
IRQ 4 ──│ IR4  COM1       │          │ IR4  マウス      │── IRQ 12
IRQ 5 ──│ IR5  LPT2/サウンド│         │ IR5  FPU        │── IRQ 13
IRQ 6 ──│ IR6  FDD        │          │ IR6  SATA       │── IRQ 14
IRQ 7 ──│ IR7  LPT1       │          │ IR7  SATA       │── IRQ 15
        └─────────────────┘          └─────────────────┘
              │                            │
              └────────── CPU ─────────────┘

PIC の制限:

  • 最大 15 個の IRQ しか扱えない
  • マルチプロセッサ環境に対応していない
  • 割り込みの優先度制御が限定的
  • すべての割り込みが BSP (Bootstrap Processor) に配信される
/* PIC の初期化 (レガシー) */
/* arch/x86/kernel/i8259.c */
static void init_8259A(int auto_eoi)
{
    unsigned long flags;

    raw_spin_lock_irqsave(&i8259A_lock, flags);

    outb(0xff, PIC_MASTER_IMR);    /* マスク全割り込み */
    outb(0xff, PIC_SLAVE_IMR);

    /* ICW1: エッジトリガ、カスケード、ICW4必要 */
    outb_pic(0x11, PIC_MASTER_CMD);
    /* ICW2: ベクタオフセット (IRQ0 = ベクタ32) */
    outb_pic(0x20, PIC_MASTER_IMR);
    /* ICW3: IR2 にスレーブ接続 */
    outb_pic(0x04, PIC_MASTER_IMR);
    /* ICW4: 8086モード、自動EOI */
    outb_pic(auto_eoi ? 0x03 : 0x01, PIC_MASTER_IMR);

    /* スレーブ PIC も同様に初期化 */
    outb_pic(0x11, PIC_SLAVE_CMD);
    outb_pic(0x28, PIC_SLAVE_IMR);     /* ベクタオフセット 40 */
    outb_pic(0x02, PIC_SLAVE_IMR);     /* カスケード接続先 */
    outb_pic(0x01, PIC_SLAVE_IMR);

    raw_spin_unlock_irqrestore(&i8259A_lock, flags);
}

4.3 APIC (Advanced Programmable Interrupt Controller)

APIC は PIC の後継であり、マルチプロセッサ環境をサポートする。APIC は 2 つのコンポーネントで構成される:

4.3.1 Local APIC

各 CPU に内蔵される割り込みコントローラ:

┌─────────────── CPU コア ───────────────┐
│                                        │
│  ┌──── Local APIC ────────────────┐    │
│  │                                │    │
│  │  LVT (Local Vector Table)     │    │
│  │  ├── Timer                    │    │
│  │  ├── Thermal Monitor          │    │
│  │  ├── Performance Counter      │    │
│  │  ├── LINT0                    │    │
│  │  ├── LINT1                    │    │
│  │  ├── Error                    │    │
│  │  └── CMCI                    │    │
│  │                                │    │
│  │  IRR (Interrupt Request Reg)   │    │
│  │  ISR (In-Service Register)     │    │
│  │  TMR (Trigger Mode Register)   │    │
│  │  TPR (Task Priority Register)  │    │
│  │  PPR (Processor Priority Reg)  │    │
│  │  EOI (End Of Interrupt Reg)    │    │
│  │  ICR (Interrupt Command Reg)   │    │
│  │  LDR (Logical Dest Register)   │    │
│  │                                │    │
│  └────────────────────────────────┘    │
│                                        │
└────────────────────────────────────────┘

Local APIC の主要レジスタ:

レジスタオフセット説明
ID0x020APIC ID
Version0x030バージョン情報
TPR0x080タスク優先度
APR0x090調停優先度
PPR0x0A0プロセッサ優先度
EOI0x0B0End of Interrupt
LDR0x0D0論理先アドレス
DFR0x0E0論理先フォーマット
SVR0x0F0スプリアスベクタ
ISR0x100-0x170In-Service
TMR0x180-0x1F0トリガモード
IRR0x200-0x270割り込み要求
ESR0x280エラーステータス
ICR0x300-0x310割り込みコマンド
LVT Timer0x320タイマーエントリ
LVT Thermal0x330温度監視エントリ
LVT Perf0x340パフォーマンスカウンタ
LVT LINT00x350LINT0 エントリ
LVT LINT10x360LINT1 エントリ
LVT Error0x370エラーエントリ
Timer ICR0x380タイマー初期カウント
Timer CCR0x390タイマー現在カウント
Timer DCR0x3E0タイマー分周比
# Local APIC の情報確認
cat /proc/cpuinfo | grep "apicid"

# APIC のバージョン確認
dmesg | grep -i "apic"

# 例: 出力
# [    0.000000] Local APIC disabled by BIOS -- reenabling.
# [    0.000000] Found and enabled local APIC!
# [    0.004000] APIC: Switch to symmetric I/O mode setup
# [    0.004000] x2apic: IRQ remapping doesn't support X2APIC mode

4.3.2 I/O APIC

I/O APIC はチップセット上に配置され、外部デバイスからの割り込みを各 CPU の Local APIC にルーティングする:

外部デバイス          I/O APIC              Local APIC (各CPU)
┌──────────┐      ┌──────────────┐      ┌──────────┐
│ NIC      │──────│ INTA (pin 0) │──┐   │ CPU 0    │
│ ディスク  │──────│ INTB (pin 1) │──┤   │ LAPIC 0  │
│ USB      │──────│ INTC (pin 2) │──┤   └──────────┘
│ タイマー  │──────│ INTD (pin 3) │──┤   ┌──────────┐
│ ...      │──────│ ...          │──┼──→│ CPU 1    │
│          │      │ (pin 23)     │──┤   │ LAPIC 1  │
└──────────┘      └──────────────┘  │   └──────────┘
                                    │   ┌──────────┐
                     Redirection    ├──→│ CPU 2    │
                     Table          │   │ LAPIC 2  │
                     (24 entries)   │   └──────────┘
                                    │   ┌──────────┐
                                    └──→│ CPU 3    │
                                        │ LAPIC 3  │
                                        └──────────┘

I/O APIC のリダイレクションテーブルエントリ:

/* I/O APIC Redirection Table Entry (64ビット) */
struct IO_APIC_route_entry {
    __u32   vector          :  8,   /* 割り込みベクタ (32-255) */
            delivery_mode   :  3,   /* 配信モード */
            dest_mode       :  1,   /* 宛先モード (物理/論理) */
            delivery_status :  1,   /* 配信ステータス */
            polarity        :  1,   /* 極性 (アクティブ High/Low) */
            irr             :  1,   /* リモート IRR */
            trigger         :  1,   /* トリガモード (エッジ/レベル) */
            mask            :  1,   /* マスク (1=マスク) */
            __reserved_2    : 15;
    __u32   __reserved_3    : 24,
            dest            :  8;   /* 宛先 APIC ID */
} __attribute__ ((packed));
# I/O APIC の情報確認
cat /proc/ioapic 2>/dev/null

# I/O APIC のピンマッピング確認
dmesg | grep "IOAPIC"

# 例:
# [    0.004000] IOAPIC[0]: apic_id 2, version 32, address 0xfec00000, GSI 0-23
# [    0.004000] IOAPIC[1]: apic_id 3, version 32, address 0xfec01000, GSI 24-47

# ACPI の割り込みルーティング情報
cat /proc/acpi/interrupts 2>/dev/null

4.4 MSI / MSI-X (Message Signaled Interrupts)

MSI (Message Signaled Interrupts) は PCI/PCIe デバイスが使用する割り込み方式で、専用の割り込みラインの代わりにメモリ書き込みによって割り込みを通知する。

4.4.1 MSI の仕組み

従来の割り込み:
デバイス ──[IRQ ライン]──→ I/O APIC ──→ Local APIC ──→ CPU

MSI:
デバイス ──[メモリ書き込み]──→ Local APIC ──→ CPU
          (0xFEE00000 + offset に書き込み)

MSI の利点:

  • I/O APIC のピン数制限なし
  • 割り込みラインの共有が不要(各デバイスに専用ベクタ)
  • レイテンシが低い(I/O APIC を経由しない)
  • 帯域内シグナリング(追加配線不要)

4.4.2 MSI vs MSI-X の比較

特性MSIMSI-X
最大ベクタ数322048
ベクタの割り当て連続任意
マスク機能オプション必須 (per-vector)
テーブルなしMSI-X テーブル
PBAなしPending Bit Array
/* MSI-X テーブルエントリの構造 */
struct msi_msg {
    u32 address_lo;     /* 下位32ビットアドレス (0xFEExxxxx) */
    u32 address_hi;     /* 上位32ビットアドレス (通常 0) */
    u32 data;           /* メッセージデータ (ベクタ番号等) */
};

/* MSI アドレスフォーマット (x86_64) */
/*
 * bits 31:20  = 0xFEE (固定)
 * bits 19:12  = Destination ID
 * bit  3      = Redirection Hint
 * bit  2      = Destination Mode (0=物理, 1=論理)
 */
# MSI/MSI-X の使用状況確認
lspci -vvv | grep -A 5 "MSI"

# 例:
# Capabilities: [50] MSI: Enable+ Count=1/1 Maskable- 64bit+
#         Address: 00000000fee00000  Data: 0000
# Capabilities: [70] MSI-X: Enable+ Count=64 Masked-
#         Vector table: BAR=4 offset=00000000
#         PBA: BAR=4 offset=00002000

# デバイスごとの MSI/MSI-X ベクタ数確認
for dev in /sys/bus/pci/devices/*/msi_irqs; do
    echo "=== $(dirname $dev | xargs basename) ==="
    ls $dev 2>/dev/null
done

# MSI を使用中のデバイスを一覧
grep MSI /proc/interrupts

4.4.3 カーネルでの MSI/MSI-X の有効化

/* ドライバでの MSI-X 有効化例 */
static int my_driver_enable_msix(struct pci_dev *pdev)
{
    int num_vectors;
    int ret;

    /* MSI-X ベクタの割り当てを要求 */
    num_vectors = pci_alloc_irq_vectors(pdev,
                                         1,              /* 最小ベクタ数 */
                                         MAX_VECTORS,    /* 最大ベクタ数 */
                                         PCI_IRQ_MSIX |  /* MSI-X 優先 */
                                         PCI_IRQ_MSI |   /* MSI にフォールバック */
                                         PCI_IRQ_LEGACY);/* レガシーにフォールバック */

    if (num_vectors < 0) {
        dev_err(&pdev->dev, "Failed to allocate IRQ vectors: %d\n", num_vectors);
        return num_vectors;
    }

    dev_info(&pdev->dev, "Allocated %d IRQ vectors\n", num_vectors);

    /* 各ベクタの IRQ 番号を取得してハンドラを登録 */
    for (int i = 0; i < num_vectors; i++) {
        int irq = pci_irq_vector(pdev, i);

        ret = request_irq(irq, my_irq_handler, 0,
                          "my_device", &my_device_data[i]);
        if (ret) {
            dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", irq, ret);
            goto err_free_irqs;
        }
    }

    return 0;

err_free_irqs:
    /* エラー時のクリーンアップ */
    while (--i >= 0)
        free_irq(pci_irq_vector(pdev, i), &my_device_data[i]);
    pci_free_irq_vectors(pdev);
    return ret;
}

4.5 割り込みドメインとirqchip

Linux カーネルは、irqdomain フレームワークを使用して割り込みコントローラの階層を管理する:

              ┌─────────────────────┐
              │   irq_domain        │
              │   (x86_vector_domain)│
              └─────────┬───────────┘
                        │
              ┌─────────┴───────────┐
              │   irq_domain        │
              │   (I/O APIC domain) │
              └─────────┬───────────┘
                        │
         ┌──────────────┼──────────────┐
         │              │              │
  ┌──────┴──────┐ ┌─────┴─────┐ ┌─────┴─────┐
  │ GPIO domain │ │PCI domain │ │ ... domain│
  └─────────────┘ └───────────┘ └───────────┘
/* irqdomain の作成例 */
static struct irq_domain *my_irq_domain;

my_irq_domain = irq_domain_add_linear(
    node,                    /* device_node */
    NUM_IRQS,               /* IRQ の数 */
    &my_irq_domain_ops,     /* コールバック */
    my_chip_data             /* プライベートデータ */
);

5. 割り込み処理フロー (トップハーフ / ボトムハーフ)

5.1 二段階処理モデルの必要性

Linux カーネルでは、割り込み処理を「トップハーフ」と「ボトムハーフ」の二段階に分割する設計を採用している。これは以下の理由による:

  1. 割り込み無効化時間の最小化: ハード割り込みハンドラ実行中は、同じまたは全ての割り込みが無効化される。長時間の処理は他の割り込みの応答性を損なう
  2. リアルタイム性の確保: 割り込みレイテンシを低く保つ
  3. データ整合性: ハードウェアからのデータ読み出しはタイムクリティカルだが、後続の処理は遅延可能

5.2 トップハーフ (Top Half / Hard IRQ)

トップハーフは割り込み発生直後に実行される処理で、以下の特徴を持つ:

  • 他の割り込みが無効化された状態で実行される(同じIRQライン)
  • 最小限の処理のみを行う
  • スリープ不可
  • GFP_ATOMIC でのメモリ確保のみ可能
  • 迅速に完了しなければならない

トップハーフで行うべき処理:

  • ハードウェアからのデータ読み出し・確認
  • 割り込み要因のクリア
  • ボトムハーフのスケジュール
  • タイムスタンプの取得
┌────────────────────────────────────────────────────┐
│                割り込み処理の全体フロー               │
│                                                    │
│  [デバイス] ──割り込みシグナル──→ [割り込みコントローラ] │
│                                     │              │
│                                     ▼              │
│                              [CPU 割り込み受付]     │
│                                     │              │
│                    ┌────────────────┘              │
│                    ▼                               │
│  ┌─── トップハーフ (Hard IRQ context) ───┐         │
│  │ 1. レジスタ保存                       │         │
│  │ 2. 割り込みベクタ特定                 │         │
│  │ 3. irq_enter()                       │         │
│  │ 4. ハンドラ実行                       │         │
│  │    - HW レジスタ読み出し             │         │
│  │    - 割り込み要因クリア              │         │
│  │    - ボトムハーフスケジュール        │         │
│  │ 5. irq_exit()                        │         │
│  │ 6. レジスタ復元                       │         │
│  │ 7. 割り込みリターン (IRET)            │         │
│  └────────────────────┬──────────────────┘         │
│                       │                            │
│                       ▼                            │
│  ┌─── ボトムハーフ ──────────────────────┐         │
│  │ ● Softirq     (最高優先度)           │         │
│  │ ● Tasklet     (Softirq ベース)       │         │
│  │ ● Workqueue   (プロセスコンテキスト)  │         │
│  │ ● Threaded IRQ (カーネルスレッド)     │         │
│  └──────────────────────────────────────┘         │
└────────────────────────────────────────────────────┘

5.3 ボトムハーフ (Bottom Half)

ボトムハーフはトップハーフから遅延された処理を実行する機構の総称である。以下の 4 つの機構が用意されている:

機構コンテキストスリープ優先度用途
Softirqソフト割り込み不可ネットワーク、ブロックI/O
Taskletソフト割り込み不可中高ドライバ固有の遅延処理
Workqueueプロセス汎用的な遅延処理
Threaded IRQプロセス割り込みスレッド

5.4 割り込みエントリの詳細フロー

x86_64 における割り込みエントリの詳細なアセンブリレベルのフロー:

/* arch/x86/entry/entry_64.S (概略) */

/*
 * 割り込みエントリポイント
 * CPU は自動的に以下をスタックに保存:
 * SS, RSP, RFLAGS, CS, RIP (+ エラーコード for 一部の例外)
 */

SYM_CODE_START(asm_common_interrupt)
    /* SWAPGS: ユーザー空間からの割り込み時にGSベースを交換 */
    testb   $3, 8(%rsp)    /* CS のRPLをチェック */
    jz      1f              /* カーネルモードならスキップ */
    swapgs                  /* ユーザーGS → カーネルGS */
1:
    /* pt_regs 構造体をスタック上に構築 */
    pushq   %rdi
    pushq   %rsi
    pushq   %rdx
    pushq   %rcx
    pushq   %rax
    pushq   %r8
    pushq   %r9
    pushq   %r10
    pushq   %r11
    pushq   %rbx
    pushq   %rbp
    pushq   %r12
    pushq   %r13
    pushq   %r14
    pushq   %r15

    /* C の割り込みハンドラを呼び出す */
    movq    %rsp, %rdi     /* 第1引数: pt_regs へのポインタ */
    movq    ORIG_RAX(%rsp), %rsi  /* 第2引数: ベクタ番号 */
    call    common_interrupt       /* C ハンドラ呼び出し */

    /* 復帰処理 */
    jmp     ret_from_intr
SYM_CODE_END(asm_common_interrupt)
/* arch/x86/kernel/irq.c */
/* C 側の共通割り込みハンドラ */
DEFINE_IDTENTRY_IRQ(common_interrupt)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc *desc;
    int vector = (int)error_code;  /* ベクタ番号 */

    /* 割り込みコンテキストに入る */
    entering_irq();

    /* ベクタ番号から irq_desc を取得 */
    desc = __this_cpu_read(vector_irq[vector]);

    if (likely(!IS_ERR_OR_NULL(desc))) {
        /* 実際の割り込みハンドラを実行 */
        handle_irq(desc, regs);
    } else {
        /* スプリアス割り込みの処理 */
        ack_APIC_irq();
        if (desc == VECTOR_UNUSED) {
            pr_emerg_ratelimited("Spurious interrupt (vector 0x%02x) "
                                "on CPU#%d\n", vector, smp_processor_id());
        }
    }

    /* 割り込みコンテキストから出る */
    exiting_irq();

    set_irq_regs(old_regs);
}

5.5 irq_desc 構造体

各割り込みラインは irq_desc 構造体で管理される:

/* include/linux/irqdesc.h */
struct irq_desc {
    struct irq_common_data    irq_common_data;  /* 共通データ */
    struct irq_data           irq_data;         /* チップ固有データ */
    unsigned int __percpu     *kstat_irqs;      /* 統計情報 */
    irq_flow_handler_t        handle_irq;       /* フロー制御ハンドラ */
    struct irqaction          *action;          /* アクションチェーン */
    unsigned int              status_use_accessors;
    unsigned int              core_internal_state__do_not_mess_with_it;
    unsigned int              depth;            /* ネスト無効化カウント */
    unsigned int              wake_depth;       /* ウェイクアップ深度 */
    unsigned int              tot_count;        /* 総割り込み回数 */
    unsigned int              irq_count;        /* 検出用カウント */
    unsigned long             last_unhandled;   /* 最後の未処理タイムスタンプ */
    unsigned int              irqs_unhandled;   /* 未処理カウント */
    atomic_t                  threads_handled;  /* スレッドハンドル数 */
    int                       threads_handled_last;
    raw_spinlock_t            lock;             /* ロック */
    struct cpumask            *percpu_enabled;  /* CPU ごとの有効状態 */
    const struct cpumask      *percpu_affinity; /* CPU アフィニティ */
    const struct cpumask      *affinity_hint;   /* アフィニティヒント */
    struct irq_affinity_notify *affinity_notify;
    unsigned long             threads_oneshot;
    atomic_t                  threads_active;
    wait_queue_head_t         wait_for_threads;
    unsigned int              nr_actions;       /* アクション数 */
    unsigned int              no_suspend_depth;
    unsigned int              cond_suspend_depth;
    unsigned int              force_resume_depth;
    struct proc_dir_entry     *dir;            /* /proc エントリ */
    struct callback_head      rcu;
    struct kobject            kobj;
    struct mutex              request_mutex;
    int                       parent_irq;
    struct module             *owner;
    const char                *name;           /* 名前 */
};

6. 割り込みハンドラの登録

6.1 request_irq()

request_irq() は最も基本的な割り込みハンドラ登録関数である:

/* include/linux/interrupt.h */

/**
 * request_irq - 割り込みハンドラを登録する
 * @irq: 割り込み番号
 * @handler: 割り込みハンドラ関数
 * @flags: 割り込みフラグ
 * @name: デバイス名 (/proc/interrupts に表示)
 * @dev: ハンドラに渡されるデバイス固有データ
 *
 * 戻り値: 成功時 0、失敗時 負のエラーコード
 */
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
{
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

6.2 割り込みフラグ

/* include/linux/interrupt.h */
#define IRQF_SHARED         0x00000080  /* 共有割り込み */
#define IRQF_PROBE_SHARED   0x00000100  /* 共有可能かプローブ */
#define IRQF_TIMER          0x00000200  /* タイマー割り込み */
#define IRQF_PERCPU         0x00000400  /* Per-CPU 割り込み */
#define IRQF_NOBALANCING    0x00000800  /* irqbalance 対象外 */
#define IRQF_IRQPOLL        0x00001000  /* ポーリングに使用 */
#define IRQF_ONESHOT        0x00002000  /* スレッド化時:ハードIRQ後マスク維持 */
#define IRQF_NO_SUSPEND     0x00004000  /* サスペンド時も無効化しない */
#define IRQF_FORCE_RESUME   0x00008000  /* レジューム時に強制有効化 */
#define IRQF_NO_THREAD      0x00010000  /* スレッド化禁止 */
#define IRQF_EARLY_RESUME   0x00020000  /* 早期レジューム */
#define IRQF_COND_SUSPEND   0x00040000  /* 条件付きサスペンド */
#define IRQF_NO_AUTOEN      0x00080000  /* 自動有効化しない */

6.3 割り込みハンドラの実装例

/* 基本的な割り込みハンドラの実装例 */
#include <linux/interrupt.h>
#include <linux/module.h>

#define MY_IRQ_NUM  10
#define DEVICE_NAME "my_device"

struct my_device_data {
    spinlock_t lock;
    u32 status;
    u32 data_buffer[256];
    int data_count;
};

static struct my_device_data my_dev;

/* 割り込みハンドラ (トップハーフ) */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device_data *dev = (struct my_device_data *)dev_id;
    u32 status;

    /* ステップ 1: 割り込み状態レジスタを読む */
    status = ioread32(dev->io_base + STATUS_REG);

    /* ステップ 2: このデバイスの割り込みか確認 */
    if (!(status & MY_IRQ_PENDING)) {
        /* 共有割り込みの場合、他のデバイスの割り込みかもしれない */
        return IRQ_NONE;
    }

    /* ステップ 3: 割り込み要因をクリア */
    iowrite32(status, dev->io_base + STATUS_REG);

    /* ステップ 4: 最小限のデータ読み出し */
    spin_lock(&dev->lock);
    dev->status = status;

    /* クリティカルなデータをバッファに読み出す */
    if (status & DATA_READY) {
        dev->data_buffer[dev->data_count++] =
            ioread32(dev->io_base + DATA_REG);
    }
    spin_unlock(&dev->lock);

    /* ステップ 5: ボトムハーフをスケジュール */
    tasklet_schedule(&dev->tasklet);

    return IRQ_HANDLED;
}

/* 初期化時の割り込み登録 */
static int my_device_init(void)
{
    int ret;

    spin_lock_init(&my_dev.lock);

    /* 割り込みハンドラを登録 */
    ret = request_irq(MY_IRQ_NUM,
                      my_irq_handler,
                      IRQF_SHARED,         /* 共有割り込み */
                      DEVICE_NAME,
                      &my_dev);

    if (ret) {
        pr_err("Failed to request IRQ %d: %d\n", MY_IRQ_NUM, ret);
        return ret;
    }

    pr_info("IRQ %d registered for %s\n", MY_IRQ_NUM, DEVICE_NAME);
    return 0;
}

/* クリーンアップ時の割り込み解除 */
static void my_device_exit(void)
{
    free_irq(MY_IRQ_NUM, &my_dev);
    pr_info("IRQ %d freed for %s\n", MY_IRQ_NUM, DEVICE_NAME);
}

6.4 request_threaded_irq()

request_threaded_irq() はスレッド化割り込みハンドラを登録する関数である:

/**
 * request_threaded_irq - スレッド化割り込みハンドラを登録
 * @irq: 割り込み番号
 * @handler: ハード割り込みハンドラ (トップハーフ、NULLも可)
 * @thread_fn: スレッド化ハンドラ (ボトムハーフ)
 * @irqflags: 割り込みフラグ
 * @devname: デバイス名
 * @dev_id: デバイス固有データ
 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                         irq_handler_t thread_fn, unsigned long irqflags,
                         const char *devname, void *dev_id);
/* スレッド化割り込みの実装例 */

/* ハードIRQハンドラ: 最小限の処理のみ */
static irqreturn_t my_hard_irq(int irq, void *dev_id)
{
    struct my_device_data *dev = dev_id;
    u32 status;

    status = ioread32(dev->io_base + STATUS_REG);
    if (!(status & MY_IRQ_PENDING))
        return IRQ_NONE;

    /* 割り込み要因クリア */
    iowrite32(status, dev->io_base + STATUS_REG);
    dev->irq_status = status;

    /* スレッドハンドラの実行を要求 */
    return IRQ_WAKE_THREAD;
}

/* スレッドハンドラ: プロセスコンテキストで実行 */
static irqreturn_t my_thread_handler(int irq, void *dev_id)
{
    struct my_device_data *dev = dev_id;

    /* プロセスコンテキストなのでスリープ可能 */

    /* 大量のデータ転送 */
    if (dev->irq_status & DATA_READY) {
        /* DMA 転送の完了を待つことも可能 */
        mutex_lock(&dev->data_mutex);
        process_received_data(dev);
        mutex_unlock(&dev->data_mutex);
    }

    /* メモリ確保 (GFP_KERNEL 使用可能) */
    if (dev->irq_status & NEW_BUFFER_NEEDED) {
        dev->buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
    }

    return IRQ_HANDLED;
}

/* 登録 */
ret = request_threaded_irq(irq,
                           my_hard_irq,      /* トップハーフ */
                           my_thread_handler, /* スレッドハンドラ */
                           IRQF_ONESHOT,      /* スレッドハンドラ完了までマスク維持 */
                           "my_device",
                           &my_dev);

6.5 devm_request_irq()

デバイス管理(devres)版の割り込み登録で、デバイス解放時に自動的に free_irq() が呼ばれる:

/* デバイス管理版 (推奨) */
ret = devm_request_irq(&pdev->dev,
                       irq,
                       my_irq_handler,
                       IRQF_SHARED,
                       dev_name(&pdev->dev),
                       my_dev);
if (ret) {
    dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
    return ret;
}
/* free_irq() は不要 - デバイス解放時に自動呼び出し */

6.6 共有割り込みの処理

複数のデバイスが同じ IRQ ラインを共有する場合:

/* 共有割り込みのチェーン */
/*
 * irq_desc->action リスト:
 *
 *   action_A → action_B → action_C → NULL
 *   (dev_A)    (dev_B)    (dev_C)
 *
 * 割り込み発生時、全てのハンドラが順に呼ばれる
 * 各ハンドラは IRQ_HANDLED (自分の割り込み) または
 * IRQ_NONE (他のデバイスの割り込み) を返す
 */

/* 共有割り込みハンドラの注意点 */
static irqreturn_t shared_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;

    /* 重要: 必ず自デバイスの割り込みかチェックする */
    if (!is_my_interrupt(dev))
        return IRQ_NONE;  /* 他のデバイスの割り込み */

    /* 自デバイスの割り込みを処理 */
    handle_my_interrupt(dev);

    return IRQ_HANDLED;
}

7. ソフト割り込み (Softirqs)

7.1 Softirq の概要

Softirq は Linux カーネルのボトムハーフ機構の中で最も優先度が高く、最も高速な遅延実行メカニズムである。静的に定義され、コンパイル時に種類が固定される。

7.2 Softirq の種類

/* include/linux/interrupt.h */
enum {
    HI_SOFTIRQ = 0,        /* 高優先度 tasklet */
    TIMER_SOFTIRQ,          /* タイマー */
    NET_TX_SOFTIRQ,         /* ネットワーク送信 */
    NET_RX_SOFTIRQ,         /* ネットワーク受信 */
    BLOCK_SOFTIRQ,          /* ブロックI/O */
    IRQ_POLL_SOFTIRQ,       /* IRQ ポーリング */
    TASKLET_SOFTIRQ,        /* 通常 tasklet */
    SCHED_SOFTIRQ,          /* スケジューラ */
    HRTIMER_SOFTIRQ,        /* 高精度タイマー (未使用: 直接呼び出し) */
    RCU_SOFTIRQ,            /* RCU コールバック */
    NR_SOFTIRQS             /* softirq の総数 */
};

7.3 Softirq の実行タイミング

Softirq は以下のタイミングで実行される:

  1. ハードウェア割り込みの復帰時 (irq_exit())
  2. ksoftirqd カーネルスレッド
  3. ネットワークサブシステムの local_bh_enable()
/* kernel/softirq.c */

/* irq_exit() から呼ばれる softirq 処理 */
void irq_exit_rcu(void)
{
    /* ハード割り込みカウンタをデクリメント */
    preempt_count_sub(HARDIRQ_OFFSET);

    /* pending な softirq があれば実行 */
    if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();
}

/* softirq の実際の処理 */
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME;  /* 最大実行時間 */
    int max_restart = MAX_SOFTIRQ_RESTART;           /* 最大再実行回数 (10) */
    struct softirq_action *h;
    __u32 pending;
    int softirq_bit;

    /* 現在 pending な softirq をローカル変数にコピー */
    pending = local_softirq_pending();

    /* softirq 処理ループ */
restart:
    /* pending ビットをクリア */
    set_softirq_pending(0);

    local_irq_enable();  /* 割り込みを有効化 (softirq 中にハード割り込み可) */

    h = softirq_vec;

    /* 各 softirq を優先度順に処理 */
    while ((softirq_bit = ffs(pending))) {
        unsigned int vec_nr;

        h += softirq_bit - 1;
        vec_nr = h - softirq_vec;

        /* 実際の softirq ハンドラを実行 */
        h->action(h);
        h++;
        pending >>= softirq_bit;
    }

    local_irq_disable();

    /* 処理中に新たな softirq が pending になった場合 */
    pending = local_softirq_pending();
    if (pending) {
        /* 時間制限・回数制限内なら再実行 */
        if (time_before(jiffies, end) && !need_resched() &&
            --max_restart)
            goto restart;

        /* 制限を超えた場合は ksoftirqd に委譲 */
        wakeup_softirqd();
    }
}

7.4 ksoftirqd カーネルスレッド

各 CPU には専用の ksoftirqd スレッドが存在する:

# ksoftirqd スレッドの確認
ps -eo pid,comm,psr | grep ksoftirqd
# 出力例:
#    7 ksoftirqd/0  0
#   15 ksoftirqd/1  1
#   21 ksoftirqd/2  2
#   27 ksoftirqd/3  3

# ksoftirqd の優先度確認
chrt -p $(pgrep -f "ksoftirqd/0")
# 出力例: pid 7's current scheduling policy: SCHED_OTHER
# pid 7's current scheduling priority: 0

ksoftirqd は以下の場合に起床する:

  • __do_softirq() で再実行制限に達した場合
  • raise_softirq() が呼ばれた時
/* ksoftirqd のメインループ */
static void run_ksoftirqd(unsigned int cpu)
{
    ksoftirqd_run_begin();
    if (local_softirq_pending()) {
        __do_softirq();
        ksoftirqd_run_end();
        cond_resched();
        return;
    }
    ksoftirqd_run_end();
}

7.5 Softirq の登録

Softirq は静的に定義されるため、新しい softirq の追加にはカーネルソースの変更が必要:

/* softirq ハンドラの登録 (カーネル内部) */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}

/* 初期化時の softirq 登録例 */
void __init softirq_init(void)
{
    /* タスクレット用 softirq */
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

/* ネットワーク用 softirq (net/core/dev.c) */
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

/* タイマー用 softirq (kernel/time/timer.c) */
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);

/* スケジューラ用 softirq (kernel/sched/fair.c) */
open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);

/* RCU 用 softirq (kernel/rcu/tree.c) */
open_softirq(RCU_SOFTIRQ, rcu_core_si);

7.6 Softirq のトリガー

/* softirq をトリガーする (pending ビットを設定) */
void raise_softirq(unsigned int nr)
{
    unsigned long flags;

    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}

/* 割り込み無効状態で softirq をトリガー */
inline void raise_softirq_irqoff(unsigned int nr)
{
    __raise_softirq_irqoff(nr);

    /* 割り込みコンテキスト外なら即座に処理可能 */
    if (!in_interrupt() && should_wake_ksoftirqd())
        wakeup_softirqd();
}

7.7 Softirq の統計情報

# softirq の統計情報
cat /proc/softirqs
#                     CPU0       CPU1       CPU2       CPU3
#           HI:          0          0          0          0
#        TIMER:     123456      98765      87654      76543
#       NET_TX:        567        432        321        210
#       NET_RX:     987654     876543     765432     654321
#        BLOCK:      12345       9876       8765       7654
#     IRQ_POLL:          0          0          0          0
#      TASKLET:       1234        987        876        765
#        SCHED:      45678      34567      23456      12345
#      HRTIMER:          0          0          0          0
#          RCU:      56789      45678      34567      23456

# リアルタイムで softirq の発生頻度を監視
watch -d -n 1 'cat /proc/softirqs'

8. タスクレット (Tasklets)

8.1 タスクレットの概要

タスクレットは Softirq の上に構築されたボトムハーフ機構で、ドライバ開発者が動的に作成・使用できる。Softirq とは異なり、同じタスクレットが複数の CPU で同時に実行されることはない(シリアライズが保証される)。

注意: タスクレットは将来的に非推奨になる可能性がある。新しいドライバでは Workqueue またはスレッド化割り込みの使用が推奨されている。

8.2 タスクレットの定義と使用

#include <linux/interrupt.h>

/* 静的タスクレット宣言 */
DECLARE_TASKLET(my_tasklet, my_tasklet_handler);

/* 動的タスクレット初期化 */
struct tasklet_struct my_tasklet;
tasklet_init(&my_tasklet, my_tasklet_handler, data);

/* タスクレットハンドラ関数 */
void my_tasklet_handler(struct tasklet_struct *t)
{
    struct my_device *dev = from_tasklet(dev, t, tasklet);

    /* ボトムハーフの処理 */
    /* 注意: ソフト割り込みコンテキスト - スリープ不可 */

    spin_lock(&dev->lock);
    process_data(dev);
    spin_unlock(&dev->lock);
}

/* タスクレットのスケジュール (通常はトップハーフから) */
tasklet_schedule(&my_tasklet);

/* 高優先度タスクレット (HI_SOFTIRQ で実行) */
tasklet_hi_schedule(&my_tasklet);

/* タスクレットの無効化/有効化 */
tasklet_disable(&my_tasklet);   /* 実行中なら完了を待つ */
tasklet_enable(&my_tasklet);

/* タスクレットの破棄 (ドライバ終了時) */
tasklet_kill(&my_tasklet);

8.3 タスクレットの実装詳細

/* kernel/softirq.c */

/* タスクレットの per-CPU リスト */
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

/* タスクレットのスケジュール */
void __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;

    local_irq_save(flags);
    /* per-CPU のタスクレットリストに追加 */
    t->next = NULL;
    *__this_cpu_read(tasklet_vec.tail) = t;
    __this_cpu_write(tasklet_vec.tail, &(t->next));
    /* TASKLET_SOFTIRQ をトリガー */
    raise_softirq_irqoff(TASKLET_SOFTIRQ);
    local_irq_restore(flags);
}

/* タスクレットの実行 (TASKLET_SOFTIRQ ハンドラ) */
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;

    /* リストを取得してクリア */
    local_irq_disable();
    list = __this_cpu_read(tasklet_vec.head);
    __this_cpu_write(tasklet_vec.head, NULL);
    __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
    local_irq_enable();

    /* リスト内の各タスクレットを処理 */
    while (list) {
        struct tasklet_struct *t = list;
        list = list->next;

        if (tasklet_trylock(t)) {
            if (!atomic_read(&t->count)) {
                /* タスクレットが有効 → 実行 */
                if (WARN_ON(!test_and_clear_bit(TASKLET_STATE_SCHED,
                                                &t->state)))
                    goto next;

                t->use_callback ? t->callback(t) : t->func(t->data);

                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }

next:
        /* 実行できなかった → リスケジュール */
        local_irq_disable();
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_enable();
    }
}

8.4 完全なタスクレット使用例

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>

struct my_net_device {
    void __iomem *base;
    struct tasklet_struct rx_tasklet;
    spinlock_t lock;
    struct sk_buff_head rx_queue;
    int irq;
};

/* タスクレットハンドラ - ボトムハーフ */
static void my_rx_tasklet_handler(struct tasklet_struct *t)
{
    struct my_net_device *dev = from_tasklet(dev, t, rx_tasklet);
    struct sk_buff *skb;
    int budget = 64;  /* 一度に処理する最大パケット数 */

    while (budget-- > 0) {
        spin_lock(&dev->lock);
        skb = __skb_dequeue(&dev->rx_queue);
        spin_unlock(&dev->lock);

        if (!skb)
            break;

        /* パケット処理 */
        process_packet(skb);
        kfree_skb(skb);
    }

    /* まだパケットが残っていればリスケジュール */
    if (!skb_queue_empty(&dev->rx_queue))
        tasklet_schedule(&dev->rx_tasklet);
}

/* 割り込みハンドラ - トップハーフ */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_net_device *dev = dev_id;
    u32 status;

    status = ioread32(dev->base + IRQ_STATUS);
    if (!(status & RX_IRQ))
        return IRQ_NONE;

    /* 割り込みクリア */
    iowrite32(status, dev->base + IRQ_ACK);

    /* 受信データをキューに追加 */
    spin_lock(&dev->lock);
    while (rx_data_available(dev)) {
        struct sk_buff *skb = alloc_skb(RX_BUF_SIZE, GFP_ATOMIC);
        if (skb) {
            read_rx_data(dev, skb);
            __skb_queue_tail(&dev->rx_queue, skb);
        }
    }
    spin_unlock(&dev->lock);

    /* タスクレットをスケジュール */
    tasklet_schedule(&dev->rx_tasklet);

    return IRQ_HANDLED;
}

/* デバイス初期化 */
static int my_device_probe(struct platform_device *pdev)
{
    struct my_net_device *dev;
    int ret;

    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    spin_lock_init(&dev->lock);
    skb_queue_head_init(&dev->rx_queue);

    /* タスクレット初期化 */
    tasklet_setup(&dev->rx_tasklet, my_rx_tasklet_handler);

    /* IRQ 登録 */
    ret = devm_request_irq(&pdev->dev, dev->irq,
                           my_irq_handler, IRQF_SHARED,
                           "my_net_device", dev);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, dev);
    return 0;
}

/* デバイス削除 */
static int my_device_remove(struct platform_device *pdev)
{
    struct my_net_device *dev = platform_get_drvdata(pdev);

    tasklet_kill(&dev->rx_tasklet);
    skb_queue_purge(&dev->rx_queue);

    return 0;
}

9. ワークキュー (Workqueues)

9.1 ワークキューの概要

ワークキューは、プロセスコンテキストで遅延処理を実行するためのカーネル機構である。Softirq やタスクレットと異なり、スリープ可能な処理を実行できるため、最も柔軟なボトムハーフ機構である。

9.2 ワークキューの種類

ワークキューの種類
├── system_wq           # デフォルトのシステムワークキュー (WQ_MEM_RECLAIM)
├── system_highpri_wq   # 高優先度ワークキュー
├── system_long_wq      # 長時間実行ワーク用
├── system_unbound_wq   # CPUバインドなし
├── system_freezable_wq # フリーズ可能
├── system_power_efficient_wq  # 省電力最適化
└── カスタムワークキュー  # ドライバ独自のワークキュー

9.3 基本的な使用方法

#include <linux/workqueue.h>

/* === 方法 1: システムワークキューを使用 (最も一般的) === */

/* work 構造体の定義 */
struct my_work_data {
    struct work_struct work;
    int data;
    char *buffer;
};

/* ワーク関数 */
static void my_work_handler(struct work_struct *work)
{
    struct my_work_data *my_work =
        container_of(work, struct my_work_data, work);

    /* プロセスコンテキスト - スリープ可能 */
    pr_info("Processing data: %d\n", my_work->data);

    /* メモリ確保 (GFP_KERNEL) */
    my_work->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
    if (my_work->buffer) {
        /* ファイルI/O等の長時間処理も可能 */
        process_data(my_work);
        kfree(my_work->buffer);
    }

    /* ミューテックスの使用も可能 */
    mutex_lock(&my_mutex);
    update_shared_state(my_work);
    mutex_unlock(&my_mutex);
}

/* 初期化 */
struct my_work_data my_work;
INIT_WORK(&my_work.work, my_work_handler);
my_work.data = 42;

/* スケジュール */
schedule_work(&my_work.work);  /* system_wq にキュー */

/* === 方法 2: 遅延ワーク === */

struct delayed_work my_delayed_work;
INIT_DELAYED_WORK(&my_delayed_work, my_delayed_handler);

/* 500ミリ秒後に実行 */
schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(500));

/* 遅延ワークのキャンセル */
cancel_delayed_work_sync(&my_delayed_work);

9.4 専用ワークキューの作成

/* === 方法 3: 専用ワークキューの作成 === */

/* 通常のワークキュー (CPU バインド) */
struct workqueue_struct *my_wq;
my_wq = create_workqueue("my_workqueue");
/* または alloc_workqueue を使用 (推奨) */
my_wq = alloc_workqueue("my_wq",
                         WQ_MEM_RECLAIM | WQ_HIGHPRI,
                         0);  /* max_active: 0 = デフォルト */

/* シングルスレッドワークキュー (順序保証) */
my_wq = create_singlethread_workqueue("my_ordered_wq");
/* alloc_ordered_workqueue (推奨) */
my_wq = alloc_ordered_workqueue("my_ordered_wq", WQ_MEM_RECLAIM);

/* ワークのキュー投入 */
queue_work(my_wq, &my_work.work);
queue_delayed_work(my_wq, &my_delayed_work, msecs_to_jiffies(100));

/* ワークキューの破棄 */
destroy_workqueue(my_wq);

9.5 ワークキューのフラグ

/* include/linux/workqueue.h */
enum {
    WQ_UNBOUND       = 1 << 1,  /* CPU バインドなし */
    WQ_FREEZABLE     = 1 << 2,  /* フリーズ可能 */
    WQ_MEM_RECLAIM   = 1 << 3,  /* メモリ回収時にも動作保証 */
    WQ_HIGHPRI       = 1 << 4,  /* 高優先度 */
    WQ_CPU_INTENSIVE = 1 << 5,  /* CPU 集約的 */
    WQ_SYSFS         = 1 << 6,  /* sysfs エントリ作成 */
    WQ_POWER_EFFICIENT = 1 << 7, /* 省電力最適化 */
};

/* 使用例 */

/* メモリ回収パス用 (ファイルシステムドライバ等) */
wq = alloc_workqueue("fs_wq", WQ_MEM_RECLAIM, 0);

/* 高優先度ワークキュー */
wq = alloc_workqueue("highpri_wq", WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);

/* CPU 非バインドワークキュー (NUMA 最適化なし) */
wq = alloc_workqueue("unbound_wq", WQ_UNBOUND, 0);

/* CPU 集約的ワーク用 (他のワークをブロックしない) */
wq = alloc_workqueue("cpu_intensive_wq", WQ_CPU_INTENSIVE, 0);

/* 同時実行数制限付き */
wq = alloc_workqueue("limited_wq", 0, 4);  /* 最大4ワーク同時実行 */

9.6 Concurrency Managed Workqueue (cmwq)

Linux 2.6.36 以降、ワークキューは cmwq (Concurrency Managed Workqueue) アーキテクチャに基づいている:

┌────────────────────────────────────────────────────┐
│                    cmwq アーキテクチャ               │
│                                                    │
│  Workqueue (WQ)                                    │
│  ├── pool_workqueue (pwq) [CPU 0]                  │
│  │   └── worker_pool                               │
│  │       ├── worker thread (kworker/0:0)           │
│  │       ├── worker thread (kworker/0:1)           │
│  │       └── worker thread (kworker/0:2)           │
│  ├── pool_workqueue (pwq) [CPU 1]                  │
│  │   └── worker_pool                               │
│  │       ├── worker thread (kworker/1:0)           │
│  │       └── worker thread (kworker/1:1)           │
│  └── ...                                           │
│                                                    │
│  ※ worker_pool は per-CPU で共有される             │
│  ※ worker thread は動的に作成/破棄される           │
└────────────────────────────────────────────────────┘
# ワーカースレッドの確認
ps -eo pid,comm,psr | grep kworker
# 出力例:
#    6 kworker/0:0H    0
#   22 kworker/1:0H    1
#  189 kworker/0:1     0
#  234 kworker/u8:2    2    # unbound worker

# ワークキューの統計情報
cat /sys/kernel/debug/workqueue/stats 2>/dev/null

# ワークキューの sysfs エントリ
ls /sys/bus/workqueue/devices/

9.7 完全なワークキュー使用例

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/slab.h>

struct my_driver {
    struct workqueue_struct *wq;
    struct work_struct      immediate_work;
    struct delayed_work     periodic_work;
    struct mutex            lock;
    int                     counter;
    bool                    running;
};

static struct my_driver *drv;

/* 即時ワークハンドラ */
static void immediate_work_handler(struct work_struct *work)
{
    struct my_driver *d = container_of(work, struct my_driver, immediate_work);

    mutex_lock(&d->lock);
    d->counter++;
    pr_info("Immediate work executed, counter=%d\n", d->counter);
    mutex_unlock(&d->lock);
}

/* 定期ワークハンドラ */
static void periodic_work_handler(struct work_struct *work)
{
    struct my_driver *d = container_of(work, struct my_driver,
                                       periodic_work.work);

    mutex_lock(&d->lock);
    pr_info("Periodic work: counter=%d\n", d->counter);
    mutex_unlock(&d->lock);

    /* 自身を再スケジュール (1秒後) */
    if (d->running)
        queue_delayed_work(d->wq, &d->periodic_work, HZ);
}

/* モジュール初期化 */
static int __init my_module_init(void)
{
    drv = kzalloc(sizeof(*drv), GFP_KERNEL);
    if (!drv)
        return -ENOMEM;

    mutex_init(&drv->lock);
    drv->running = true;

    /* 専用ワークキュー作成 */
    drv->wq = alloc_workqueue("my_driver_wq",
                               WQ_MEM_RECLAIM | WQ_SYSFS,
                               2);  /* 最大2ワーク同時実行 */
    if (!drv->wq) {
        kfree(drv);
        return -ENOMEM;
    }

    /* ワークの初期化 */
    INIT_WORK(&drv->immediate_work, immediate_work_handler);
    INIT_DELAYED_WORK(&drv->periodic_work, periodic_work_handler);

    /* 定期ワークの開始 */
    queue_delayed_work(drv->wq, &drv->periodic_work, HZ);

    pr_info("my_driver initialized\n");
    return 0;
}

/* モジュール終了 */
static void __exit my_module_exit(void)
{
    drv->running = false;

    /* 全てのワークの完了を待つ */
    cancel_work_sync(&drv->immediate_work);
    cancel_delayed_work_sync(&drv->periodic_work);

    /* ワークキューの破棄 */
    destroy_workqueue(drv->wq);

    kfree(drv);
    pr_info("my_driver removed\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

10. スレッド化割り込み (Threaded Interrupts)

10.1 概要

スレッド化割り込み (Threaded Interrupts) は、割り込み処理の大部分をカーネルスレッドとして実行する機構である。これにより、プロセスコンテキストでの割り込み処理が可能となり、スリープやミューテックス使用が可能になる。

10.2 スレッド化割り込みのアーキテクチャ

ハードウェア割り込み
      │
      ▼
┌─── ハード IRQ ハンドラ (トップハーフ) ───┐
│ - 最小限の処理                          │
│ - IRQ_WAKE_THREAD を返す               │
│ - IRQF_ONESHOT: IRQ マスク維持         │
└─────────────┬───────────────────────────┘
              │ スレッド起床
              ▼
┌─── IRQ スレッド (irq/%d-%s) ───────────┐
│ - プロセスコンテキスト                  │
│ - スリープ可能                         │
│ - ミューテックス使用可能               │
│ - GFP_KERNEL メモリ確保可能            │
│ - 完了後 IRQ マスク解除                │
└─────────────────────────────────────────┘

10.3 IRQ スレッドの作成

/* kernel/irq/manage.c */
static int irq_thread_create(struct irqaction *new)
{
    struct task_struct *t;

    /* IRQ スレッドを作成 */
    t = kthread_create(irq_thread, new, "irq/%d-%s",
                       new->irq, new->name);
    if (IS_ERR(t))
        return PTR_ERR(t);

    /* リアルタイム優先度を設定 (SCHED_FIFO, 優先度 50) */
    sched_setscheduler_nocheck(t, SCHED_FIFO, &param);

    /* スレッドを保存 */
    new->thread = t;

    return 0;
}
# IRQ スレッドの確認
ps -eo pid,comm,cls,rtprio,psr | grep "irq/"
# 出力例:
#   78 irq/16-ahci     FF    50    0
#  102 irq/18-eno1     FF    50    1
#  134 irq/24-nvme0q1  FF    50    2

# IRQ スレッドの優先度確認
chrt -p $(pgrep -f "irq/18")
# 出力: pid 102's current scheduling policy: SCHED_FIFO
#        pid 102's current scheduling priority: 50

10.4 IRQF_ONESHOT の重要性

IRQF_ONESHOT フラグは、スレッド化割り込みで特に重要である:

/*
 * IRQF_ONESHOT の動作:
 *
 * 1. ハード IRQ ハンドラが IRQ_WAKE_THREAD を返す
 * 2. IRQ ラインはマスクされたまま維持される
 *    (再入防止 - 同じ割り込みが再発しない)
 * 3. スレッドハンドラが実行される
 * 4. スレッドハンドラ完了後、IRQ ラインのマスクが解除される
 *
 * これがないと、レベルトリガ割り込みで無限ループが発生する可能性がある:
 * 割り込み → ハンドラ → 復帰 → まだアサート中 → 再割り込み → ...
 */

/* IRQF_ONESHOT なしでスレッド化割り込みを使おうとするとエラー */
ret = request_threaded_irq(irq, NULL, thread_handler, 0, "dev", data);
/* → -EINVAL: ハンドラなし + IRQF_ONESHOT なし は危険 */

/* 正しい使い方 */
ret = request_threaded_irq(irq, NULL, thread_handler,
                           IRQF_ONESHOT, "dev", data);
/* → OK: ハンドラなしでも IRQF_ONESHOT があればデフォルトハンドラが使用される */

10.5 RT カーネルとスレッド化割り込み

PREEMPT_RT (リアルタイム) カーネルでは、ほぼすべての割り込みが強制的にスレッド化される:

# RT カーネルの確認
uname -v | grep -i "preempt.*rt"

# threadirqs カーネルパラメータで全割り込みをスレッド化
# /etc/default/grub に追加:
GRUB_CMDLINE_LINUX="threadirqs"

# スレッド化された割り込みの確認
cat /proc/interrupts | head -5
# CPU0       CPU1
#  1:        125          0  IR-IO-APIC   1-edge      i8042
# 上記が threadirqs 有効時:
#  1:        125          0  IR-IO-APIC   1-edge      i8042
# → ps で irq/1-i8042 スレッドが見える

10.6 スレッド化割り込みの実践例

/* I2C デバイスドライバでのスレッド化割り込み使用例 */
/* (I2C 通信はスリープを伴うため、スレッド化割り込みが最適) */

struct my_i2c_sensor {
    struct i2c_client *client;
    struct mutex lock;
    u16 last_reading;
    struct completion data_ready;
};

/* スレッド化ハンドラ */
static irqreturn_t my_sensor_thread_handler(int irq, void *dev_id)
{
    struct my_i2c_sensor *sensor = dev_id;
    u8 buf[2];
    int ret;

    /* I2C 通信 (スリープ可能) */
    mutex_lock(&sensor->lock);

    ret = i2c_smbus_read_i2c_block_data(sensor->client,
                                         DATA_REG, 2, buf);
    if (ret == 2) {
        sensor->last_reading = (buf[0] << 8) | buf[1];
        complete(&sensor->data_ready);
    }

    /* 割り込みステータスクリア (I2C経由) */
    i2c_smbus_write_byte_data(sensor->client, STATUS_REG, 0xFF);

    mutex_unlock(&sensor->lock);

    return IRQ_HANDLED;
}

/* プローブ関数 */
static int my_sensor_probe(struct i2c_client *client)
{
    struct my_i2c_sensor *sensor;
    int ret;

    sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
    if (!sensor)
        return -ENOMEM;

    sensor->client = client;
    mutex_init(&sensor->lock);
    init_completion(&sensor->data_ready);

    ret = devm_request_threaded_irq(&client->dev,
                                     client->irq,
                                     NULL,    /* ハンドラなし */
                                     my_sensor_thread_handler,
                                     IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
                                     "my_sensor",
                                     sensor);
    if (ret) {
        dev_err(&client->dev, "Failed to request IRQ: %d\n", ret);
        return ret;
    }

    i2c_set_clientdata(client, sensor);
    return 0;
}

11. CPU 例外処理

11.1 例外の概要

x86_64 CPU は、命令実行中にエラーや特殊な状態を検出すると例外を発生させる。例外はベクタ 0〜31 に割り当てられ、カーネルの IDT に登録されたハンドラが呼び出される。

11.2 主要な CPU 例外

11.2.1 ページフォルト (#PF, ベクタ 14)

最も頻繁に発生する例外であり、仮想メモリ管理の中核をなす。詳細は次章で解説する。

/* arch/x86/mm/fault.c */
DEFINE_IDTENTRY_RAW_ERRORCODE(exc_page_fault)
{
    unsigned long address = read_cr2();  /* フォルトアドレス */
    irqentry_state_t state;

    /* ... */
    handle_page_fault(regs, error_code, address);
}

11.2.2 一般保護例外 (#GP, ベクタ 13)

一般保護例外は、様々なメモリアクセス違反で発生する:

/* arch/x86/kernel/traps.c */
DEFINE_IDTENTRY_ERRORCODE(exc_general_protection)
{
    /* エラーコードの解析 */
    /*
     * エラーコードのフォーマット:
     * bit 0: External (1=外部イベント)
     * bit 1-2: Descriptor Table (0=GDT, 1=IDT, 2=LDT, 3=IDT)
     * bit 3-15: Selector Index
     */

    if (user_mode(regs)) {
        /* ユーザーモードでの #GP → プロセスに SIGSEGV */
        tsk->thread.error_code = error_code;
        tsk->thread.trap_nr = X86_TRAP_GP;

        if (fixup_vdso_exception(regs, X86_TRAP_GP, error_code, 0))
            return;

        show_signal(tsk, SIGSEGV);
        force_sig(SIGSEGV);
    } else {
        /* カーネルモードでの #GP → fixup テーブルを確認 */
        if (fixup_exception(regs, X86_TRAP_GP, error_code, 0))
            return;

        /* fixup なし → カーネルパニック */
        die("general protection fault", regs, error_code);
    }
}

#GP が発生する主な原因:

  • セグメントリミットの超過
  • NULL セグメントセレクタの使用
  • 非正規アドレスへのアクセス
  • 特権命令のユーザーモードでの実行
  • 不正な MSR アクセス
  • WRMSR でのビット設定誤り
# #GP の発生を監視
dmesg | grep "general protection"
# 例:
# [  123.456789] traps: my_program[1234] general protection fault ip:7f1234567890
#                sp:7fff12345678 error:0 in libc-2.31.so[7f1234500000+200000]

# perf で例外を追跡
perf stat -e 'exceptions:*' -- sleep 10

11.2.3 ダブルフォルト (#DF, ベクタ 8)

ダブルフォルトは、例外ハンドラの実行中に別の例外が発生した場合に起きる:

/* arch/x86/kernel/traps.c */
DEFINE_IDTENTRY_DF(exc_double_fault)
{
    static const char str[] = "double fault";
    struct task_struct *tsk = current;

    /*
     * ダブルフォルトは IST スタックで実行される。
     * これにより、スタックオーバーフローでもハンドラが動作可能。
     */

#ifdef CONFIG_VMAP_STACK
    /* スタックオーバーフローの検出 */
    handle_stack_overflow(
        "kernel stack overflow (double-fault)",
        regs, address);
#endif

    /* ダブルフォルトは常に致命的 → カーネルパニック */
    die(str, regs, error_code);
    panic("Machine halted.");
}

ダブルフォルトの主な原因:

  • カーネルスタックのオーバーフロー
  • IDT の破損
  • GDT/TSS の破損
# スタックオーバーフローの検出を確認
cat /proc/config.gz | gunzip | grep VMAP_STACK
# CONFIG_VMAP_STACK=y  → スタックオーバーフロー検出が有効

# カーネルスタックサイズの確認
cat /proc/config.gz | gunzip | grep THREAD_SIZE
# → デフォルト: 16KB (x86_64)

11.2.4 NMI (Non-Maskable Interrupt, ベクタ 2)

NMI は通常の割り込みマスクでは無効化できない特殊な割り込みである:

/* arch/x86/kernel/nmi.c */
DEFINE_IDTENTRY_RAW(exc_nmi)
{
    bool irq_state;

    /*
     * NMI は IST スタックで実行される
     * NMI 中にさらに NMI が来ても安全に処理できる
     */

    this_cpu_write(nmi_cr2, read_cr2());

    nmi_enter();

    /* NMI ハンドラチェーンを呼び出す */
    default_do_nmi(regs);

    nmi_exit();
}

/* NMI の原因を判定 */
static void default_do_nmi(struct pt_regs *regs)
{
    unsigned char reason;

    reason = x86_platform.get_nmi_reason();

    if (reason & NMI_REASON_MEMPAR) {
        /* メモリパリティエラー */
        mem_parity_error(reason, regs);
        return;
    }

    if (reason & NMI_REASON_IOCHK) {
        /* I/O チャネルチェック */
        io_check_error(reason, regs);
        return;
    }

    /* perf カウンタオーバーフロー、watchdog など */
    if (unknown_nmi_panic) {
        unknown_nmi_error(reason, regs);
    }
}

NMI の用途:

  • ハードウェアウォッチドッグ (ソフトロックアップ検出)
  • パフォーマンスモニタリング (perf)
  • メモリパリティエラー
  • デバッグ (SysRq + crashdump)
  • MCE (Machine Check Exception) の通知
# NMI ウォッチドッグの設定
echo 1 > /proc/sys/kernel/nmi_watchdog

# NMI 発生回数の確認
cat /proc/interrupts | grep NMI
#            CPU0       CPU1       CPU2       CPU3
#  NMI:      12345      12346      12347      12348   Non-maskable interrupts

# NMI による panic を有効化
echo 1 > /proc/sys/kernel/unknown_nmi_panic

# カーネルパラメータでの設定
# nmi_watchdog=1           NMI ウォッチドッグ有効
# nmi_watchdog=0           NMI ウォッチドッグ無効
# unknown_nmi_panic=1      未知のNMIでパニック

11.3 例外ハンドラ一覧

/* x86_64 の全例外ハンドラ */
/* arch/x86/kernel/idt.c で定義 */

/*  0 */ INTG(X86_TRAP_DE,  asm_exc_divide_error)
/*  1 */ ISTG(X86_TRAP_DB,  asm_exc_debug, IST_INDEX_DB)
/*  2 */ ISTG(X86_TRAP_NMI, asm_exc_nmi, IST_INDEX_NMI)
/*  3 */ SYSG(X86_TRAP_BP,  asm_exc_int3)       /* ブレークポイント */
/*  4 */ SYSG(X86_TRAP_OF,  asm_exc_overflow)
/*  5 */ INTG(X86_TRAP_BR,  asm_exc_bounds)
/*  6 */ INTG(X86_TRAP_UD,  asm_exc_invalid_op)
/*  7 */ INTG(X86_TRAP_NM,  asm_exc_device_not_available)
/*  8 */ ISTG(X86_TRAP_DF,  asm_exc_double_fault, IST_INDEX_DF)
/* 10 */ INTG(X86_TRAP_TS,  asm_exc_invalid_tss)
/* 11 */ INTG(X86_TRAP_NP,  asm_exc_segment_not_present)
/* 12 */ INTG(X86_TRAP_SS,  asm_exc_stack_segment)
/* 13 */ INTG(X86_TRAP_GP,  asm_exc_general_protection)
/* 14 */ INTG(X86_TRAP_PF,  asm_exc_page_fault)
/* 16 */ INTG(X86_TRAP_MF,  asm_exc_coprocessor_error)
/* 17 */ INTG(X86_TRAP_AC,  asm_exc_alignment_check)
/* 18 */ ISTG(X86_TRAP_MC,  asm_exc_machine_check, IST_INDEX_MCE)
/* 19 */ INTG(X86_TRAP_XF,  asm_exc_simd_coprocessor_error)

12. ページフォルトハンドラの詳細

12.1 ページフォルトの概要

ページフォルト (#PF) は、CPU がページテーブルを参照した結果、要求されたメモリアクセスを即座に完了できない場合に発生する例外である。Linux カーネルのデマンドページング、Copy-on-Write (CoW)、スワッピングの基盤をなす。

12.2 ページフォルトエラーコード

/* arch/x86/include/asm/trap_pf.h */
enum x86_pf_error_code {
    X86_PF_PROT   = 1 << 0,  /* 0=ページ不在, 1=保護違反 */
    X86_PF_WRITE  = 1 << 1,  /* 0=読み取り, 1=書き込み */
    X86_PF_USER   = 1 << 2,  /* 0=カーネル, 1=ユーザー */
    X86_PF_RSVD   = 1 << 3,  /* 予約ビット違反 */
    X86_PF_INSTR  = 1 << 4,  /* 命令フェッチ */
    X86_PF_PK     = 1 << 5,  /* Protection Key 違反 */
    X86_PF_SHSTK  = 1 << 6,  /* Shadow Stack */
    X86_PF_SGX    = 1 << 15, /* SGX エンクレーブ */
};

12.3 ページフォルト処理フロー

ページフォルト発生 (CR2 = フォルトアドレス)
        │
        ▼
exc_page_fault()
        │
        ├── kmmio フォルト? → kmmio_handler()
        │
        ├── カーネルモードフォルト? ─┬→ vmalloc フォルト?
        │                           │   → vmalloc_fault()
        │                           │
        │                           ├→ カーネル空間の正当なアクセス?
        │                           │   → do_kern_addr_fault()
        │                           │
        │                           └→ fixup テーブル確認
        │                               → fixup_exception()
        │                               → die() / oops
        │
        └── ユーザーモードフォルト
                │
                ▼
        do_user_addr_fault()
                │
                ├── VMA (Virtual Memory Area) を検索
                │   find_vma()
                │
                ├── VMA が見つからない
                │   → bad_area() → SIGSEGV
                │
                ├── スタック拡張の可能性
                │   → expand_stack()
                │
                ├── アクセス権限チェック
                │   → access_error() → SIGSEGV
                │
                └── handle_mm_fault()
                        │
                        ├── PGD → P4D → PUD → PMD → PTE を辿る
                        │
                        ├── デマンドページング
                        │   → do_anonymous_page()
                        │
                        ├── Copy-on-Write
                        │   → do_wp_page()
                        │
                        ├── ファイルマッピング
                        │   → do_read_fault() / do_cow_fault()
                        │
                        ├── スワップイン
                        │   → do_swap_page()
                        │
                        └── NUMA バランシング
                            → do_numa_page()

12.4 handle_mm_fault() の詳細

/* mm/memory.c */
vm_fault_t handle_mm_fault(struct vm_area_struct *vma,
                           unsigned long address,
                           unsigned int flags,
                           struct pt_regs *regs)
{
    vm_fault_t ret;
    struct mm_struct *mm = vma->vm_mm;

    /* memcg によるメモリ使用量チェック */
    if (unlikely(mem_cgroup_oom_synchronize(false)))
        return VM_FAULT_OOM;

    /* ヒュージページの処理 */
    if (pmd_none(*vmf.pmd) && __transparent_hugepage_enabled(vma)) {
        ret = create_huge_pmd(&vmf);
        if (!(ret & VM_FAULT_FALLBACK))
            return ret;
    }

    /* 通常のページフォルト処理 */
    return __handle_mm_fault(vma, address, flags);
}

static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
                                     unsigned long address,
                                     unsigned int flags)
{
    struct vm_fault vmf = {
        .vma = vma,
        .address = address & PAGE_MASK,
        .flags = flags,
        .pgoff = linear_page_index(vma, address),
        .gfp_mask = __get_fault_gfp_mask(vma),
    };

    /* ページテーブルウォーク */
    pgd = pgd_offset(mm, address);
    p4d = p4d_alloc(mm, pgd, address);
    vmf.pud = pud_alloc(mm, p4d, address);
    vmf.pmd = pmd_alloc(mm, vmf.pud, address);

    /* PTE レベルのフォルト処理 */
    return handle_pte_fault(&vmf);
}

12.5 各種ページフォルトの処理

12.5.1 デマンドページング (Anonymous Page Fault)

/* mm/memory.c */
static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
    struct page *page;
    pte_t entry;

    /* 読み取りアクセスなら zero page を使用 */
    if (!(vmf->flags & FAULT_FLAG_WRITE) &&
        !mm_forbids_zeropage(vma->vm_mm)) {
        entry = pte_mkspecial(
            pfn_pte(my_zero_pfn(vmf->address), vma->vm_page_prot));
        vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd,
                                        vmf->address, &vmf->ptl);
        set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
        /* ... */
        return 0;
    }

    /* 書き込みアクセス → 新しいページを割り当て */
    page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
    if (!page)
        return VM_FAULT_OOM;

    /* ページをRMAP に追加 */
    __SetPageUptodate(page);
    page_add_new_anon_rmap(page, vma, vmf->address);
    mem_cgroup_commit_charge(page, memcg, false);
    lru_cache_add_active_or_unevictable(page, vma);

    /* PTE を設定 */
    entry = mk_pte(page, vma->vm_page_prot);
    entry = pte_sw_mkyoung(entry);
    if (vma->vm_flags & VM_WRITE)
        entry = pte_mkwrite(pte_mkdirty(entry));

    set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);

    return 0;
}

12.5.2 Copy-on-Write (CoW)

/* mm/memory.c */
static vm_fault_t do_wp_page(struct vm_fault *vmf)
{
    struct page *old_page = vmf->page;

    /* ページが単一参照 (他のプロセスが共有していない) */
    if (reuse_swap_page(old_page, NULL)) {
        /* PTE を書き込み可能に変更するだけ */
        wp_page_reuse(vmf);
        return VM_FAULT_WRITE;
    }

    /* ページが共有されている → コピー */
    return wp_page_copy(vmf);
}

static vm_fault_t wp_page_copy(struct vm_fault *vmf)
{
    struct page *old_page = vmf->page;
    struct page *new_page;

    /* 新しいページを割り当て */
    new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vmf->address);
    if (!new_page)
        return VM_FAULT_OOM;

    /* 内容をコピー */
    copy_user_highpage(new_page, old_page, vmf->address, vma);

    /* PTE を新しいページに更新 */
    flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte));
    entry = mk_pte(new_page, vma->vm_page_prot);
    entry = maybe_mkwrite(pte_mkdirty(entry), vma);

    ptep_clear_flush(vma, vmf->address, vmf->pte);
    set_pte_at_notify(mm, vmf->address, vmf->pte, entry);

    /* 古いページの参照カウントを減らす */
    page_remove_rmap(old_page, vma, false);
    put_page(old_page);

    return VM_FAULT_WRITE;
}

12.6 ページフォルトの監視

# プロセスのページフォルト統計
# minor fault: ディスクI/O不要 (デマンドページング、CoW)
# major fault: ディスクI/O必要 (ファイル読み込み、スワップイン)

# ps でページフォルト数を確認
ps -o pid,min_flt,maj_flt,cmd -p <PID>

# /proc/<PID>/stat からのページフォルト情報
cat /proc/<PID>/stat | awk '{print "minor_flt="$10, "major_flt="$12}'

# vmstat でシステム全体のページフォルト
vmstat 1
# procs ----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  1  0      0 3456789  12345 678901    0    0     1     2  234  567  1  0 99  0  0

# perf でページフォルトをトレース
perf stat -e page-faults,minor-faults,major-faults -- ./my_program

# perf record でページフォルトの詳細を記録
perf record -e page-faults -g -- ./my_program
perf report

# ftrace でページフォルトをトレース
echo 1 > /sys/kernel/debug/tracing/events/exceptions/page_fault_user/enable
echo 1 > /sys/kernel/debug/tracing/events/exceptions/page_fault_kernel/enable
cat /sys/kernel/debug/tracing/trace

13. タイマー割り込みと高精度タイマー (hrtimer)

13.1 タイマーサブシステムの概要

Linux カーネルのタイマーサブシステムは、システムの時間管理と遅延実行の基盤を提供する:

タイマーサブシステム
├── クロックソース (clocksource)
│   ├── TSC (Time Stamp Counter)
│   ├── HPET (High Precision Event Timer)
│   └── ACPI PM Timer
├── クロックイベント (clock_event_device)
│   ├── Local APIC Timer
│   ├── HPET
│   └── PIT (8254)
├── タイマーAPI
│   ├── 低精度タイマー (timer_list / jiffies)
│   └── 高精度タイマー (hrtimer / nanosecond)
└── tick 管理
    ├── Periodic tick
    ├── Dynamic tick (NO_HZ)
    └── Full dynamic tick (NO_HZ_FULL)

13.2 タイマー割り込みの基本

タイマー割り込み (tick) はシステムの心拍として機能する:

# タイマー割り込みの頻度確認
cat /proc/config.gz | gunzip | grep HZ
# CONFIG_HZ_250=y
# CONFIG_HZ=250

# → 250Hz = 4ミリ秒間隔

# 一般的な HZ 値:
# 100Hz  = 10ms   (サーバー向け、低オーバーヘッド)
# 250Hz  = 4ms    (デフォルト、バランス)
# 300Hz  = 3.3ms  (デスクトップ、マルチメディア)
# 1000Hz = 1ms    (リアルタイム、低レイテンシ)

# NO_HZ (tickless) の設定確認
cat /proc/config.gz | gunzip | grep NO_HZ
# CONFIG_NO_HZ_IDLE=y     # アイドル時にtick停止
# CONFIG_NO_HZ_FULL=y     # フルダイナミック tick

13.3 低精度タイマー (timer_list)

#include <linux/timer.h>

/* タイマーの定義と初期化 */
struct timer_list my_timer;

/* コールバック関数 */
void my_timer_callback(struct timer_list *t)
{
    /* ソフト割り込みコンテキスト - スリープ不可 */
    pr_info("Timer expired at jiffies=%lu\n", jiffies);

    /* 定期タイマーにするには再スケジュール */
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}

/* 初期化 */
timer_setup(&my_timer, my_timer_callback, 0);

/* タイマーの開始 (1秒後) */
mod_timer(&my_timer, jiffies + HZ);

/* タイマーのキャンセル */
del_timer(&my_timer);

/* 同期キャンセル (コールバック完了を保証) */
del_timer_sync(&my_timer);

13.4 高精度タイマー (hrtimer)

hrtimer は ナノ秒精度のタイマーを提供する:

#include <linux/hrtimer.h>
#include <linux/ktime.h>

struct hrtimer my_hrtimer;

/* hrtimer コールバック */
enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer)
{
    ktime_t now = ktime_get();
    pr_info("hrtimer fired at %lld ns\n", ktime_to_ns(now));

    /* 再スケジュールする場合 */
    hrtimer_forward_now(timer, ktime_set(0, 1000000));  /* 1ms */
    return HRTIMER_RESTART;

    /* 一回限りの場合 */
    /* return HRTIMER_NORESTART; */
}

/* 初期化 */
hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_hrtimer.function = my_hrtimer_callback;

/* タイマーの開始 (500マイクロ秒後) */
hrtimer_start(&my_hrtimer, ktime_set(0, 500000),
              HRTIMER_MODE_REL);

/* タイマーのキャンセル */
hrtimer_cancel(&my_hrtimer);

13.5 hrtimer の内部構造

hrtimer は赤黒木 (Red-Black Tree) で管理される:

hrtimer_clock_base
├── CLOCK_REALTIME
│   └── timerqueue (red-black tree)
│       ├── [earliest] timer_A (expire: T+100ns)
│       ├── timer_B (expire: T+500ns)
│       └── timer_C (expire: T+1ms)
├── CLOCK_MONOTONIC
│   └── timerqueue
│       ├── [earliest] timer_D (expire: T+50ns)
│       └── timer_E (expire: T+2ms)
├── CLOCK_BOOTTIME
│   └── timerqueue
└── CLOCK_TAI
    └── timerqueue

13.6 タイマー関連の監視

# タイマーの統計情報
cat /proc/timer_list | head -60

# 各 CPU のクロックイベントデバイス
cat /proc/timer_list | grep "Clock Event Device"

# アクティブなタイマー数
cat /proc/timer_list | grep "active timers"

# クロックソースの確認
cat /sys/devices/system/clocksource/clocksource0/available_clocksource
# tsc hpet acpi_pm

cat /sys/devices/system/clocksource/clocksource0/current_clocksource
# tsc

# hrtimer の精度確認
cat /proc/timer_list | grep "resolution"
# .resolution: 1 nsecs

# タイマー割り込みの統計
cat /proc/interrupts | grep -E "LOC|timer"

14. IRQ アフィニティと irqbalance

14.1 IRQ アフィニティの概要

IRQ アフィニティは、特定の割り込みをどの CPU で処理するかを制御する機能である。適切な IRQ アフィニティの設定は、パフォーマンス最適化の重要な要素である。

14.2 IRQ アフィニティの確認と設定

# 各 IRQ のアフィニティを確認
cat /proc/irq/<IRQ番号>/smp_affinity
# 16進数のビットマスク: f = CPU 0-3 全て

# IRQ アフィニティの一覧表示
for irq in /proc/irq/*/smp_affinity; do
    echo "$(dirname $irq | xargs basename): $(cat $irq)"
done

# 人間が読みやすい形式
cat /proc/irq/<IRQ番号>/smp_affinity_list
# 例: 0-3  (CPU 0から3)
# 例: 0,2  (CPU 0と2)

# IRQ アフィニティの設定
echo 2 > /proc/irq/<IRQ番号>/smp_affinity    # CPU 1 のみ
echo 0f > /proc/irq/<IRQ番号>/smp_affinity   # CPU 0-3
echo "0-1" > /proc/irq/<IRQ番号>/smp_affinity_list  # CPU 0-1

# 実践例: NIC の割り込みを特定の CPU に割り当て
# eth0 の IRQ 番号を確認
grep eth0 /proc/interrupts
#  32:  1234567  0  0  0  IR-PCI-MSI 524288-edge  eth0-TxRx-0
#  33:  0  2345678  0  0  IR-PCI-MSI 524289-edge  eth0-TxRx-1
#  34:  0  0  3456789  0  IR-PCI-MSI 524290-edge  eth0-TxRx-2
#  35:  0  0  0  4567890  IR-PCI-MSI 524291-edge  eth0-TxRx-3

# 各キューを異なる CPU にバインド
echo 1 > /proc/irq/32/smp_affinity   # Queue 0 → CPU 0
echo 2 > /proc/irq/33/smp_affinity   # Queue 1 → CPU 1
echo 4 > /proc/irq/34/smp_affinity   # Queue 2 → CPU 2
echo 8 > /proc/irq/35/smp_affinity   # Queue 3 → CPU 3

14.3 irqbalance デーモン

irqbalance は、IRQ をCPU間で自動的に分散するデーモンである:

# irqbalance のインストールと状態確認
systemctl status irqbalance

# irqbalance の設定ファイル
cat /etc/sysconfig/irqbalance
# または
cat /etc/default/irqbalance

# 主な設定オプション
# IRQBALANCE_ONESHOT=yes       # 一度だけ実行して終了
# IRQBALANCE_BANNED_CPUS=      # 除外するCPU (16進マスク)
# IRQBALANCE_BANNED_CPULIST=   # 除外するCPU (リスト形式)
# IRQBALANCE_ARGS=             # 追加引数

# irqbalance の実行モード
irqbalance --foreground --debug    # デバッグモード

# 特定の IRQ を irqbalance の対象から除外
# /etc/irqbalance.d/ にポリシーファイルを配置
echo "IRQBALANCE_BANNED_INTERRUPTS=32 33 34 35" >> /etc/default/irqbalance

# irqbalance のヒント設定
echo 2 > /proc/irq/<IRQ>/affinity_hint

14.4 NUMA を考慮した IRQ アフィニティ

# NUMA トポロジーの確認
numactl --hardware
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 6 7
# node 1 cpus: 8 9 10 11 12 13 14 15

# NIC がどの NUMA ノードに接続されているか確認
cat /sys/class/net/eth0/device/numa_node
# 0  → NUMA ノード 0

# ベストプラクティス: NIC の IRQ はデバイスと同じ NUMA ノードの CPU に割り当て
# eth0 は NUMA ノード 0 → CPU 0-7 に割り当て

# スクリプトによる自動設定
#!/bin/bash
DEVICE="eth0"
NUMA_NODE=$(cat /sys/class/net/$DEVICE/device/numa_node)
CPUS=$(cat /sys/devices/system/node/node${NUMA_NODE}/cpulist)

echo "Device $DEVICE is on NUMA node $NUMA_NODE (CPUs: $CPUS)"

for irq in $(grep "$DEVICE" /proc/interrupts | awk '{print $1}' | tr -d ':'); do
    echo "$CPUS" > /proc/irq/$irq/smp_affinity_list
    echo "Set IRQ $irq affinity to CPUs $CPUS"
done

14.5 RPS/RFS (Receive Packet Steering / Receive Flow Steering)

ソフトウェアレベルでのパケット処理の CPU 分散:

# RPS の設定 (受信パケットを複数CPU に分散)
echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus

# RPS のハッシュフローテーブルサイズ
echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

# RFS の設定 (フロー単位でCPU を追従)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

# XPS の設定 (送信キューとCPU のマッピング)
echo 1 > /sys/class/net/eth0/queues/tx-0/xps_cpus   # Queue 0 → CPU 0
echo 2 > /sys/class/net/eth0/queues/tx-1/xps_cpus   # Queue 1 → CPU 1

15. 割り込みコアレッシング

15.1 概要

割り込みコアレッシング (Interrupt Coalescing) は、複数の割り込みイベントをまとめて1回の割り込みとして通知する技術である。高速ネットワーク環境でCPU オーバーヘッドを削減するために重要である。

15.2 ethtool による設定

# 現在のコアレッシング設定を確認
ethtool -c eth0

# 出力例:
# Coalesce parameters for eth0:
# Adaptive RX: on  TX: on
# stats-block-usecs: 0
# sample-interval: 0
# pkt-rate-low: 0
# pkt-rate-high: 0
#
# rx-usecs: 3              # 受信: 最大3マイクロ秒遅延
# rx-frames: 0             # 受信: フレーム数の閾値 (0=無効)
# rx-usecs-irq: 0
# rx-frames-irq: 0
#
# tx-usecs: 3              # 送信: 最大3マイクロ秒遅延
# tx-frames: 0             # 送信: フレーム数の閾値 (0=無効)
# tx-usecs-irq: 0
# tx-frames-irq: 0

# コアレッシング設定の変更
# レイテンシ重視: 値を小さくする
ethtool -C eth0 rx-usecs 1 tx-usecs 1

# スループット重視: 値を大きくする
ethtool -C eth0 rx-usecs 100 tx-usecs 100 rx-frames 64 tx-frames 64

# アダプティブコアレッシングの有効化
ethtool -C eth0 adaptive-rx on adaptive-tx on

# アダプティブコアレッシングの無効化
ethtool -C eth0 adaptive-rx off adaptive-tx off

15.3 コアレッシングのトレードオフ

レイテンシ  ←──────────────────────────→  スループット/CPU効率
   低                                          高

  rx-usecs=0         rx-usecs=50         rx-usecs=500
  rx-frames=1        rx-frames=32        rx-frames=128

  割り込み頻度: 高    割り込み頻度: 中    割り込み頻度: 低
  CPU負荷: 高         CPU負荷: 中         CPU負荷: 低
  レイテンシ: 最小    レイテンシ: 中      レイテンシ: 高
# コアレッシング設定の効果測定
# 設定前後で比較

# 割り込み頻度の測定
sar -I ALL 1 5

# ネットワークレイテンシの測定
ping -c 100 target_host | tail -1

# CPU 使用率 (割り込み処理) の測定
mpstat -I ALL 1 5

16. /proc/interrupts と /proc/softirqs

16.1 /proc/interrupts の詳細

cat /proc/interrupts
#            CPU0       CPU1       CPU2       CPU3
#   0:        45          0          0          0  IR-IO-APIC   2-edge      timer
#   1:         2          0          0          0  IR-IO-APIC   1-edge      i8042
#   8:         1          0          0          0  IR-IO-APIC   8-edge      rtc0
#   9:         0          3          0          0  IR-IO-APIC   9-fasteoi   acpi
#  16:         0          0         28          0  IR-IO-APIC  16-fasteoi   ehci_hcd
#  24:         0          0          0          0  IR-PCI-MSI 327680-edge   xhci_hcd
#  25:     45678          0          0          0  IR-PCI-MSI 524288-edge   nvme0q0
#  26:         0      34567          0          0  IR-PCI-MSI 524289-edge   nvme0q1
#  27:         0          0      23456          0  IR-PCI-MSI 524290-edge   nvme0q2
#  28:         0          0          0      12345  IR-PCI-MSI 524291-edge   nvme0q3
#  29:    123456     234567     345678     456789  IR-PCI-MSI 1048576-edge  eth0
# NMI:       123        124        125        126   Non-maskable interrupts
# LOC:   1234567    1234568    1234569    1234570   Local timer interrupts
# SPU:         0          0          0          0   Spurious interrupts
# PMI:       123        124        125        126   Performance monitoring interrupts
# IWI:       456        457        458        459   IRQ work interrupts
# RTR:         0          0          0          0   APIC ICR read retries
# RES:     12345      12346      12347      12348   Rescheduling interrupts
# CAL:      1234       1235       1236       1237   Function call interrupts
# TLB:       789        790        791        792   TLB shootdowns
# TRM:         0          0          0          0   Thermal event interrupts
# THR:         0          0          0          0   Threshold APIC interrupts
# DFR:         0          0          0          0   Deferred Error APIC interrupts
# MCE:         0          0          0          0   Machine check exceptions
# MCP:        12         12         12         12   Machine check polls
# ERR:         0
# MIS:         0
# PIN:         0          0          0          0   Posted-interrupt notification event
# NPI:         0          0          0          0   Nested posted-interrupt event
# PIW:         0          0          0          0   Posted-interrupt wakeup event

各フィールドの意味:

略語正式名説明
NMINon-maskable interruptsマスク不可割り込み
LOCLocal timer interruptsローカルタイマー割り込み
SPUSpurious interruptsスプリアス割り込み
PMIPerformance monitoringパフォーマンスカウンタ
IWIIRQ work interruptsIRQ ワーク
RESRescheduling interruptsリスケジュール IPI
CALFunction call interrupts関数呼び出し IPI
TLBTLB shootdownsTLB 無効化 IPI
TRMThermal event温度イベント
MCEMachine check exceptionsマシンチェック
ERRErrorエラー (I/O APIC)
MISMisrouted誤ルーティング

16.2 /proc/softirqs の詳細

cat /proc/softirqs
#                    CPU0       CPU1       CPU2       CPU3
#          HI:          5          3          2          1
#       TIMER:     456789     345678     234567     123456
#      NET_TX:       1234        987        876        765
#      NET_RX:    2345678    1234567     987654     876543
#       BLOCK:      12345       9876       8765       7654
#    IRQ_POLL:          0          0          0          0
#     TASKLET:        567        456        345        234
#       SCHED:     234567     223456     212345     201234
#     HRTIMER:          0          0          0          0
#         RCU:     345678     334567     323456     312345

16.3 割り込み統計の監視スクリプト

#!/bin/bash
# interrupt_monitor.sh - 割り込み統計の監視

# 1秒間の割り込み増分を表示
echo "=== Interrupt Rate (per second) ==="
prev=$(cat /proc/interrupts)
sleep 1
curr=$(cat /proc/interrupts)

paste <(echo "$prev") <(echo "$curr") | while IFS=$'\t' read -r line1 line2; do
    irq=$(echo "$line1" | awk '{print $1}')
    name=$(echo "$line1" | awk '{print $NF}')
    count1=$(echo "$line1" | awk '{s=0; for(i=2;i<=NF-3;i++) s+=$i; print s}')
    count2=$(echo "$line2" | awk '{s=0; for(i=2;i<=NF-3;i++) s+=$i; print s}')

    if [[ "$count1" =~ ^[0-9]+$ ]] && [[ "$count2" =~ ^[0-9]+$ ]]; then
        rate=$((count2 - count1))
        if [ $rate -gt 0 ]; then
            printf "%-8s %-20s %10d/s\n" "$irq" "$name" "$rate"
        fi
    fi
done

echo ""
echo "=== Softirq Rate (per second) ==="
prev_soft=$(cat /proc/softirqs)
sleep 1
curr_soft=$(cat /proc/softirqs)

paste <(echo "$prev_soft") <(echo "$curr_soft") | tail -n +2 | \
while IFS=$'\t' read -r line1 line2; do
    name=$(echo "$line1" | awk '{print $1}')
    total1=$(echo "$line1" | awk '{s=0; for(i=2;i<=NF;i++) s+=$i; print s}')
    total2=$(echo "$line2" | awk '{s=0; for(i=2;i<=NF;i++) s+=$i; print s}')

    if [[ "$total1" =~ ^[0-9]+$ ]] && [[ "$total2" =~ ^[0-9]+$ ]]; then
        rate=$((total2 - total1))
        if [ $rate -gt 0 ]; then
            printf "%-12s %10d/s\n" "$name" "$rate"
        fi
    fi
done

17. 割り込みストームとその緩和策

17.1 割り込みストームとは

割り込みストーム (Interrupt Storm) は、デバイスが異常に高い頻度で割り込みを発生させ、CPU が割り込み処理以外の仕事をほとんどできなくなる状態である。

17.2 原因

割り込みストームの主な原因:

  • デバイスのハードウェア障害
  • ドライバのバグ(割り込みクリア忘れ)
  • レベルトリガ割り込みのアサート解除失敗
  • 共有割り込みの問題
  • IRQ ラインの電気的問題
  • ネットワークの高負荷(小パケットの大量着信)

17.3 検出方法

# 方法 1: /proc/interrupts をリアルタイム監視
watch -d -n 1 'cat /proc/interrupts'

# 方法 2: mpstat で割り込み負荷を確認
mpstat -I ALL 1
# 12:00:01 AM  CPU    intr/s
# 12:00:02 AM  all  500000.00     # ← 異常に高い値

# 方法 3: vmstat で確認
vmstat 1
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  0  0      0 123456  78901 234567    0    0     0     0 500000 1000  0 98  2  0  0
#                                                       ^^^^^^ 高い割り込み数

# 方法 4: perf で割り込みの内訳を確認
perf top -e irq:irq_handler_entry

# 方法 5: /proc/irq/*/spurious で未処理割り込みを確認
for f in /proc/irq/*/spurious; do
    irq=$(echo $f | cut -d/ -f4)
    content=$(cat $f)
    unhandled=$(echo $content | grep -o "unhandled=[0-9]*" | cut -d= -f2)
    if [ "$unhandled" -gt 0 ] 2>/dev/null; then
        echo "IRQ $irq: $content"
    fi
done

17.4 カーネルの自動検出メカニズム

Linux カーネルには割り込みストームの自動検出機構がある:

/* kernel/irq/spurious.c */

/*
 * カーネルの割り込みストーム検出ロジック:
 * - 100,000 回の割り込みのうち 99,900 回以上が未処理 (IRQ_NONE) の場合
 * - その IRQ ラインを自動的に無効化する
 */

#define IRQ_POLL_THRESHOLD  100000  /* チェック間隔 */
#define IRQ_UNHANDLED_RATIO 99900   /* 未処理の閾値 */

static void __report_bad_irq(struct irq_desc *desc, irqreturn_t action_ret)
{
    /* 未処理割り込みの警告メッセージ */
    printk(KERN_ERR "irq %d: nobody cared (try booting with "
           "the \"irqpoll\" parameter)\n", irq);
    /* ... */
    printk(KERN_ERR "handlers:\n");
    /* 登録されているハンドラの情報を表示 */
}

/* 未処理割り込みが閾値を超えた場合 */
static void try_misrouted_irq(unsigned int irq, struct irq_desc *desc,
                               irqreturn_t action_ret)
{
    /* irqfixup が有効なら全IRQハンドラを試す */
    /* irqpoll が有効ならタイマーベースのポーリングに切り替え */
}
# カーネルの割り込みストーム検出メッセージ
dmesg | grep "nobody cared"
# [  234.567890] irq 16: nobody cared (try booting with the "irqpoll" parameter)
# [  234.567891] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 6.1.0
# [  234.567892] Disabling IRQ #16

# 対処法: irqpoll カーネルパラメータ
# /etc/default/grub:
GRUB_CMDLINE_LINUX="irqpoll"
# → 全ての割り込みハンドラをタイマーベースでポーリング

# irqfixup: 未処理割り込みが発生した場合に全ハンドラを試す
GRUB_CMDLINE_LINUX="irqfixup"

17.5 緩和策

# 1. 問題の IRQ を特定
cat /proc/interrupts | sort -t: -k2 -rn | head -10

# 2. IRQ を一時的に無効化 (注意: デバイスが使えなくなる)
echo 0 > /proc/irq/<IRQ>/smp_affinity  # 全CPU から除外

# 3. NIC の場合はコアレッシングを調整
ethtool -C eth0 rx-usecs 100 rx-frames 64

# 4. NAPI を有効化 (ネットワークデバイスの場合)
# → ドライバレベルの対応が必要

# 5. IRQ アフィニティの変更
# 問題のIRQを他のCPUに移動
echo 2 > /proc/irq/<IRQ>/smp_affinity

# 6. デバイスの再初期化
echo 1 > /sys/class/net/eth0/device/reset
# または
modprobe -r <driver> && modprobe <driver>

18. NAPI によるネットワーク割り込み処理

18.1 NAPI の概要

NAPI (New API) は、高速ネットワーク環境でのパケット処理効率を向上させるためのカーネル機構である。割り込みとポーリングのハイブリッド方式を採用する。

18.2 従来の割り込み方式 vs NAPI

従来の方式 (パケットごとに割り込み):
パケット1 → 割り込み → 処理 → 復帰
パケット2 → 割り込み → 処理 → 復帰
パケット3 → 割り込み → 処理 → 復帰
...
問題: 高負荷時に割り込みオーバーヘッドが支配的

NAPI (割り込み + ポーリング):
パケット1 → 割り込み → 割り込み無効化
                      → ポーリング開始
                      → パケット1処理
                      → パケット2処理  (割り込みなし)
                      → パケット3処理  (割り込みなし)
                      → ...
                      → バジェット消費 or パケットなし
                      → 割り込み再有効化
利点: 高負荷時でも効率的

18.3 NAPI のアーキテクチャ

                    ┌─────────────────────────┐
                    │   ハードウェア割り込み     │
                    └───────────┬─────────────┘
                                │
                                ▼
                    ┌─────────────────────────┐
                    │ ドライバのIRQハンドラ      │
                    │ 1. 割り込み無効化          │
                    │ 2. napi_schedule()        │
                    └───────────┬─────────────┘
                                │
                                ▼
                    ┌─────────────────────────┐
                    │   NET_RX_SOFTIRQ         │
                    │   net_rx_action()        │
                    └───────────┬─────────────┘
                                │
                                ▼
                    ┌─────────────────────────┐
                    │   napi->poll() 呼び出し   │
                    │   (ドライバのポーリング関数)│
                    │                          │
                    │   while (budget > 0) {   │
                    │     パケット受信          │
                    │     napi_gro_receive()   │
                    │     budget--;            │
                    │   }                      │
                    └───────────┬─────────────┘
                                │
                    ┌───────────┴─────────────┐
                    │                          │
              budget 残あり              budget 使い切り
              (パケットなし)             (まだパケットあり)
                    │                          │
                    ▼                          ▼
            ┌──────────────┐          ┌──────────────┐
            │ napi_complete │          │ 再スケジュール │
            │ 割り込み再有効 │          │ (次のsoftirq) │
            └──────────────┘          └──────────────┘

18.4 NAPI ドライバの実装例

#include <linux/netdevice.h>
#include <linux/interrupt.h>

#define NAPI_BUDGET 64
#define RX_RING_SIZE 256

struct my_nic {
    struct net_device *netdev;
    struct napi_struct napi;
    void __iomem *base;
    struct sk_buff *rx_ring[RX_RING_SIZE];
    int rx_head;
    int rx_tail;
};

/* NAPI ポーリング関数 */
static int my_nic_poll(struct napi_struct *napi, int budget)
{
    struct my_nic *nic = container_of(napi, struct my_nic, napi);
    int work_done = 0;

    while (work_done < budget) {
        struct sk_buff *skb;
        u32 status;

        /* 受信リングからパケットを取得 */
        status = ioread32(nic->base + RX_STATUS(nic->rx_head));
        if (!(status & RX_DONE))
            break;  /* パケットなし */

        skb = nic->rx_ring[nic->rx_head];
        if (!skb)
            break;

        /* パケットの長さを設定 */
        skb_put(skb, status & RX_LENGTH_MASK);
        skb->protocol = eth_type_trans(skb, nic->netdev);

        /* GRO (Generic Receive Offload) 経由でプロトコルスタックに渡す */
        napi_gro_receive(napi, skb);

        /* 新しいバッファを割り当て */
        nic->rx_ring[nic->rx_head] = netdev_alloc_skb(nic->netdev,
                                                        RX_BUF_SIZE);

        /* リングを進める */
        nic->rx_head = (nic->rx_head + 1) % RX_RING_SIZE;
        work_done++;
    }

    /* budget を使い切らなかった → パケットがなくなった */
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        /* 割り込みを再有効化 */
        iowrite32(IRQ_ENABLE, nic->base + IRQ_MASK);
    }

    return work_done;
}

/* ハードウェア割り込みハンドラ */
static irqreturn_t my_nic_irq(int irq, void *dev_id)
{
    struct my_nic *nic = dev_id;
    u32 status;

    status = ioread32(nic->base + IRQ_STATUS);
    if (!(status & RX_IRQ))
        return IRQ_NONE;

    /* 割り込みクリア */
    iowrite32(status, nic->base + IRQ_ACK);

    /* 割り込み無効化 + NAPI スケジュール */
    if (likely(napi_schedule_prep(&nic->napi))) {
        iowrite32(IRQ_DISABLE, nic->base + IRQ_MASK);
        __napi_schedule(&nic->napi);
    }

    return IRQ_HANDLED;
}

/* デバイス初期化 */
static int my_nic_open(struct net_device *netdev)
{
    struct my_nic *nic = netdev_priv(netdev);

    /* NAPI の有効化 */
    napi_enable(&nic->napi);

    /* 割り込みの登録 */
    request_irq(nic->irq, my_nic_irq, 0, "my_nic", nic);

    /* 割り込み有効化 */
    iowrite32(IRQ_ENABLE, nic->base + IRQ_MASK);

    return 0;
}

/* デバイス停止 */
static int my_nic_stop(struct net_device *netdev)
{
    struct my_nic *nic = netdev_priv(netdev);

    /* 割り込み無効化 */
    iowrite32(IRQ_DISABLE, nic->base + IRQ_MASK);

    /* NAPI の無効化 */
    napi_disable(&nic->napi);

    free_irq(nic->irq, nic);

    return 0;
}

/* プローブ関数 */
static int my_nic_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct net_device *netdev;
    struct my_nic *nic;

    netdev = alloc_etherdev(sizeof(struct my_nic));
    nic = netdev_priv(netdev);
    nic->netdev = netdev;

    /* NAPI の初期化 */
    netif_napi_add(netdev, &nic->napi, my_nic_poll);

    register_netdev(netdev);
    return 0;
}

18.5 Busy Polling

NAPI のさらなる最適化として、ビジーポーリングがある:

# Busy Polling の有効化
# グローバル設定 (マイクロ秒)
echo 50 > /proc/sys/net/core/busy_poll
echo 50 > /proc/sys/net/core/busy_read

# ソケットごとの設定 (アプリケーション側)
# setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL, &timeout, sizeof(timeout));

18.6 GRO (Generic Receive Offload)

# GRO の状態確認
ethtool -k eth0 | grep gro
# generic-receive-offload: on

# GRO の有効化/無効化
ethtool -K eth0 gro on
ethtool -K eth0 gro off

# GRO の統計
cat /proc/net/dev | column -t

18.7 NAPI 関連の監視

# NAPI のバジェット確認
cat /proc/sys/net/core/netdev_budget
# 300 (デフォルト)

# バジェットの変更
echo 600 > /proc/sys/net/core/netdev_budget

# NAPI のバジェット使用時間制限
cat /proc/sys/net/core/netdev_budget_usecs
# 2000 (デフォルト: 2ミリ秒)

# NET_RX softirq のキューバックログ
cat /proc/sys/net/core/netdev_max_backlog
# 1000 (デフォルト)
echo 10000 > /proc/sys/net/core/netdev_max_backlog

19. 監視・デバッグツール

19.1 /proc/interrupts

# 基本的な使用法
cat /proc/interrupts

# 特定のデバイスの割り込み確認
grep "eth0\|nvme" /proc/interrupts

# 割り込み数の変化をリアルタイム監視
watch -d -n 1 'cat /proc/interrupts'

19.2 irqtop (procps-ng)

# irqtop のインストール
# RHEL/CentOS: yum install procps-ng
# Ubuntu/Debian: apt install procps

# irqtop の実行
irqtop

# 出力例:
# IRQ   TOTAL     DELTA  NAME
#  25  1234567     456   nvme0q0
#  29  2345678     789   eth0
# LOC  9876543    1234   Local timer interrupts
# RES   123456      56   Rescheduling interrupts

19.3 perf

# 割り込み関連のイベント一覧
perf list | grep -i irq

# ハードウェア割り込みのトレース
perf trace -e irq:* -- sleep 5

# 割り込みハンドラの実行時間を計測
perf trace -e irq:irq_handler_entry,irq:irq_handler_exit -- sleep 10

# softirq のトレース
perf trace -e irq:softirq_entry,irq:softirq_exit -- sleep 10

# 割り込み処理のフレームグラフ生成
perf record -a -g -e irq:irq_handler_entry -- sleep 30
perf script > irq_trace.txt
# FlameGraph ツールで可視化
stackcollapse-perf.pl irq_trace.txt | flamegraph.pl > irq_flame.svg

# 割り込みレイテンシの測定
perf stat -e irq:irq_handler_entry,irq:irq_handler_exit \
          -e irq:softirq_entry,irq:softirq_exit \
          -- sleep 60

# 特定のIRQの詳細分析
perf record -a -g -e 'irq:irq_handler_entry' \
    --filter 'irq==25' -- sleep 30

19.4 mpstat

# CPU ごとの割り込み統計
mpstat -I ALL 1 5

# 出力例:
# Linux 6.1.0 (hostname)   04/10/2026      _x86_64_
#
# 12:00:01 AM  CPU    intr/s
# 12:00:02 AM  all   12345.00
# 12:00:02 AM    0    3456.00
# 12:00:02 AM    1    2345.00
# 12:00:02 AM    2    3456.00
# 12:00:02 AM    3    3088.00
#
# 12:00:01 AM  CPU       HI/s    TIMER/s   NET_TX/s   NET_RX/s    BLOCK/s
# 12:00:02 AM  all        0.00    1000.00      5.00    8000.00     100.00
# 12:00:02 AM    0        0.00     250.00      2.00    2000.00      25.00
# 12:00:02 AM    1        0.00     250.00      1.00    2000.00      25.00
# 12:00:02 AM    2        0.00     250.00      1.00    2000.00      25.00
# 12:00:02 AM    3        0.00     250.00      1.00    2000.00      25.00

# ソフト割り込みの詳細
mpstat -I SCPU 1

# 全ての割り込み情報
mpstat -I ALL -P ALL 1

19.5 /proc/irq/ ディレクトリ

# IRQ ごとの詳細情報
ls /proc/irq/25/
# affinity_hint  effective_affinity      node    smp_affinity_list  spurious
# chip_name      effective_affinity_list  nvme0q0  smp_affinity

# 各ファイルの内容
cat /proc/irq/25/chip_name        # IR-PCI-MSI
cat /proc/irq/25/smp_affinity     # 00000001 (CPU 0)
cat /proc/irq/25/smp_affinity_list # 0
cat /proc/irq/25/effective_affinity # 00000001
cat /proc/irq/25/node             # 0 (NUMAノード)
cat /proc/irq/25/spurious         # count 0 unhandled 0 last_unhandled 0 ms

19.6 ftrace による詳細トレース

# ftrace の設定ディレクトリ
cd /sys/kernel/debug/tracing

# 利用可能な割り込みイベント
cat available_events | grep irq
# irq:irq_handler_entry
# irq:irq_handler_exit
# irq:softirq_entry
# irq:softirq_exit
# irq:softirq_raise

# 割り込みイベントのトレースを有効化
echo 1 > events/irq/irq_handler_entry/enable
echo 1 > events/irq/irq_handler_exit/enable
echo 1 > events/irq/softirq_entry/enable
echo 1 > events/irq/softirq_exit/enable

# トレースの開始
echo 1 > tracing_on

# トレース結果の表示
cat trace | head -50
# # tracer: nop
# #
# #                                _-----=> irqs-off/BH-disabled
# #                               / _----=> need-resched
# #                              | / _---=> hardirq/softirq
# #                              || / _--=> preempt-depth
# #                              ||| / _-=> migrate-disable
# #                              |||| /     delay
# #           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
# #              | |         |   |||||     |         |
#         <idle>-0     [000] d.h1   123.456789: irq_handler_entry: irq=25 name=nvme0q0
#         <idle>-0     [000] d.h1   123.456790: irq_handler_exit: irq=25 ret=handled
#         <idle>-0     [000] ..s1   123.456791: softirq_entry: vec=3 [action=NET_RX]
#         <idle>-0     [000] ..s1   123.456795: softirq_exit: vec=3 [action=NET_RX]

# トレースの停止
echo 0 > tracing_on

# 割り込みレイテンシのトレース (irqsoff tracer)
echo irqsoff > current_tracer
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
cat trace
# → 割り込みが無効化された最長の期間を表示

19.7 bpftrace による高度な監視

# 割り込みハンドラの実行時間を計測
bpftrace -e '
tracepoint:irq:irq_handler_entry {
    @start[args->irq] = nsecs;
}
tracepoint:irq:irq_handler_exit /@start[args->irq]/ {
    @irq_latency_ns[args->irq] = hist(nsecs - @start[args->irq]);
    delete(@start[args->irq]);
}
interval:s:10 { exit(); }
'

# softirq の実行時間を計測
bpftrace -e '
tracepoint:irq:softirq_entry {
    @start[args->vec] = nsecs;
}
tracepoint:irq:softirq_exit /@start[args->vec]/ {
    @softirq_us[@softirq_name[args->vec]] =
        hist((nsecs - @start[args->vec]) / 1000);
    delete(@start[args->vec]);
}
BEGIN {
    @softirq_name[0] = "HI";
    @softirq_name[1] = "TIMER";
    @softirq_name[2] = "NET_TX";
    @softirq_name[3] = "NET_RX";
    @softirq_name[4] = "BLOCK";
    @softirq_name[5] = "IRQ_POLL";
    @softirq_name[6] = "TASKLET";
    @softirq_name[7] = "SCHED";
    @softirq_name[8] = "HRTIMER";
    @softirq_name[9] = "RCU";
}
interval:s:10 { exit(); }
'

# ページフォルトの発生箇所を特定
bpftrace -e '
tracepoint:exceptions:page_fault_user {
    @page_faults[comm] = count();
}
interval:s:5 { exit(); }
'

20. 実践的なトラブルシューティング

20.1 ケース 1: 高い割り込み負荷

# 症状: CPU の %irq や %soft が異常に高い

# 1. 原因の特定
mpstat -I ALL 1 5
# → どの CPU でどの種類の割り込みが多いか確認

# 2. 具体的な IRQ の特定
watch -d 'cat /proc/interrupts | sort -t: -k2 -rn | head -20'

# 3. softirq の偏りを確認
watch -d 'cat /proc/softirqs'
# → NET_RX が特定のCPUに偏っている場合は IRQ アフィニティを調整

# 4. 対処法
# a) IRQ アフィニティの分散
for irq in 32 33 34 35; do
    echo $((1 << (irq - 32))) > /proc/irq/$irq/smp_affinity
done

# b) RPS の有効化
echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus

# c) コアレッシングの調整
ethtool -C eth0 adaptive-rx on adaptive-tx on

# d) NAPI バジェットの調整
echo 600 > /proc/sys/net/core/netdev_budget

20.2 ケース 2: ksoftirqd の CPU 使用率が高い

# 症状: ksoftirqd/N が CPU を大量消費

# 1. どの softirq が問題か特定
perf top -e irq:softirq_entry

# 2. NET_RX の場合
# → ネットワーク負荷が高い
# → NAPI バジェットの調整
echo 600 > /proc/sys/net/core/netdev_budget
echo 8000 > /proc/sys/net/core/netdev_budget_usecs

# 3. TIMER の場合
# → 大量のタイマーが登録されている
# → アプリケーションの見直し

# 4. RCU の場合
# → RCU コールバックの遅延
echo 1 > /sys/kernel/rcu_expedited

20.3 ケース 3: スプリアス割り込み

# 症状: dmesg に "nobody cared" メッセージ

# 1. ログの確認
dmesg | grep -A 5 "nobody cared"

# 2. 問題の IRQ を確認
cat /proc/irq/*/spurious | grep -v "count 0"

# 3. 共有割り込みの問題
lspci -vvv | grep -B 5 "Interrupt"
# → 複数デバイスが同じ IRQ を共有していないか確認

# 4. MSI/MSI-X への切り替え
# BIOS 設定で MSI を有効化
# または、ドライバのパラメータで MSI を強制
modprobe <driver> use_msi=1

# 5. irqpoll での応急措置
# /etc/default/grub:
# GRUB_CMDLINE_LINUX="irqpoll"
# grub2-mkconfig -o /boot/grub2/grub.cfg

20.4 ケース 4: NMI ウォッチドッグのトリガー

# 症状: "NMI watchdog: BUG: soft lockup - CPU#N stuck for Ns!"

# 1. ソフトロックアップの原因調査
dmesg | grep -A 20 "soft lockup"
# → スタックトレースを確認

# 2. 閾値の確認・変更
cat /proc/sys/kernel/watchdog_thresh
# 10 (デフォルト: 10秒)

# 閾値を緩和 (一時的な措置)
echo 30 > /proc/sys/kernel/watchdog_thresh

# 3. 割り込みが無効化されていないか確認
# ftrace の irqsoff tracer を使用
echo irqsoff > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
sleep 10
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace

# 4. perf で CPU の活動を分析
perf record -a -g -- sleep 30
perf report

20.5 包括的な診断スクリプト

#!/bin/bash
# irq_diagnostics.sh - 割り込み診断スクリプト

echo "==============================================="
echo "  Linux Interrupt Diagnostics Report"
echo "  $(date)"
echo "  $(uname -r)"
echo "==============================================="
echo ""

echo "=== 1. CPU 情報 ==="
nproc
cat /proc/cpuinfo | grep "model name" | head -1
echo ""

echo "=== 2. 割り込み統計 (上位 20) ==="
head -1 /proc/interrupts
tail -n +2 /proc/interrupts | sort -t: -k2 -rn | head -20
echo ""

echo "=== 3. Softirq 統計 ==="
cat /proc/softirqs
echo ""

echo "=== 4. IRQ アフィニティ ==="
for irq in /proc/irq/[0-9]*/smp_affinity_list; do
    dir=$(dirname $irq)
    irq_num=$(basename $dir)
    affinity=$(cat $irq)
    handler=$(ls $dir 2>/dev/null | grep -v "smp_affinity\|node\|spurious\|chip_name\|effective\|affinity_hint")
    if [ -n "$handler" ]; then
        printf "IRQ %-4s CPU %-8s %s\n" "$irq_num" "$affinity" "$handler"
    fi
done 2>/dev/null | head -30
echo ""

echo "=== 5. スプリアス割り込み ==="
for f in /proc/irq/*/spurious; do
    content=$(cat $f 2>/dev/null)
    unhandled=$(echo $content | grep -oP 'unhandled \K[0-9]+')
    if [ "${unhandled:-0}" -gt 0 ]; then
        irq=$(echo $f | grep -oP '/proc/irq/\K[0-9]+')
        echo "IRQ $irq: $content"
    fi
done
echo ""

echo "=== 6. NMI 情報 ==="
grep NMI /proc/interrupts
echo ""

echo "=== 7. irqbalance 状態 ==="
systemctl status irqbalance 2>/dev/null | head -5
echo ""

echo "=== 8. NUMA トポロジー ==="
if command -v numactl &> /dev/null; then
    numactl --hardware 2>/dev/null | head -10
fi
echo ""

echo "=== 9. NIC コアレッシング設定 ==="
for dev in $(ls /sys/class/net/ | grep -v lo); do
    echo "--- $dev ---"
    ethtool -c $dev 2>/dev/null | head -15
done
echo ""

echo "=== 10. カーネル設定 ==="
echo "HZ: $(cat /proc/config.gz 2>/dev/null | gunzip 2>/dev/null | grep '^CONFIG_HZ=' || echo 'N/A')"
echo "NO_HZ: $(cat /proc/config.gz 2>/dev/null | gunzip 2>/dev/null | grep 'NO_HZ' || echo 'N/A')"
echo "PREEMPT: $(cat /proc/config.gz 2>/dev/null | gunzip 2>/dev/null | grep '^CONFIG_PREEMPT=' || echo 'N/A')"
echo ""

echo "=== 完了 ==="

21. まとめとベストプラクティス

21.1 割り込み処理の選択指針

┌──────────────────────────────────────────────────────┐
│            割り込み処理方式の選択フローチャート         │
│                                                      │
│  処理にスリープが必要か?                              │
│  ├── Yes → Workqueue または Threaded IRQ             │
│  │         │                                         │
│  │         ├── I2C/SPI デバイス → Threaded IRQ       │
│  │         ├── 大量のメモリ確保 → Workqueue          │
│  │         └── ファイルI/O → Workqueue               │
│  │                                                   │
│  └── No → 処理時間は?                                │
│           │                                          │
│           ├── 非常に短い → Softirq (カーネル内部のみ) │
│           ├── 短い → Tasklet (非推奨) or Softirq     │
│           └── 中程度 → Threaded IRQ                   │
│                                                      │
│  新規ドライバの推奨:                                  │
│  1. request_threaded_irq() + IRQF_ONESHOT           │
│  2. 高パフォーマンス要求 → NAPI (ネットワーク)       │
│  3. 汎用的な遅延処理 → Workqueue                    │
└──────────────────────────────────────────────────────┘

21.2 パフォーマンス最適化のベストプラクティス

  1. IRQ アフィニティの最適化

    • NIC の IRQ を NUMA ローカルの CPU に割り当てる
    • マルチキュー NIC の各キューを異なる CPU にバインド
    • irqbalance の設定を環境に合わせてチューニング
  2. 割り込みコアレッシングの調整

    • レイテンシ重視: rx-usecs を小さく
    • スループット重視: rx-usecs を大きく、adaptive を有効化
    • ワークロードに応じたチューニング
  3. NAPI の活用

    • 高速ネットワーク環境では NAPI 対応ドライバを使用
    • netdev_budget のチューニング
    • Busy Polling の検討
  4. CPU isolcpus の活用

    • リアルタイム処理を行う CPU を隔離
    • 割り込み処理を特定の CPU に集約
# CPU 隔離の設定例
# /etc/default/grub:
GRUB_CMDLINE_LINUX="isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"

# 隔離された CPU にはデフォルトで割り込みが配送されない
# アプリケーションを隔離 CPU にバインド
taskset -c 2,3 ./my_latency_sensitive_app
  1. 監視体制の構築
    • /proc/interrupts の定期的な監視
    • mpstat -I ALL による割り込み負荷の把握
    • 異常検知のアラート設定

21.3 セキュリティに関する注意点

  • /proc/irq/*/smp_affinity の書き込み権限管理
  • NMI ウォッチドッグの適切な設定
  • 割り込みストームによる DoS 攻撃への対策
  • MSI/MSI-X の適切な設定(割り込みリマッピング)

21.4 トラブルシューティングのチェックリスト

□ /proc/interrupts で割り込み分布を確認
□ /proc/softirqs で softirq の偏りを確認
□ mpstat -I ALL で CPU ごとの負荷を確認
□ dmesg で割り込み関連のエラーを確認
□ IRQ アフィニティが適切か確認
□ NIC のコアレッシング設定を確認
□ NUMA トポロジーとの整合性を確認
□ irqbalance の動作状況を確認
□ MSI/MSI-X が有効か確認
□ スプリアス割り込みの有無を確認

22. 参考文献

書籍

  • Linux Kernel Development (3rd Edition) - Robert Love
    • 割り込みとボトムハーフの章が特に参考になる
  • Understanding the Linux Kernel (3rd Edition) - Daniel P. Bovet, Marco Cesati
    • IDT、例外処理、ページフォルトの詳細な解説
  • Linux Device Drivers (3rd Edition) - Jonathan Corbet et al.
    • 割り込みハンドラの実装方法
  • Professional Linux Kernel Architecture - Wolfgang Mauerer
    • カーネル内部の詳細なアーキテクチャ解説

オンラインリソース

カーネルソースコードの主要ファイル

arch/x86/kernel/idt.c           # IDT の設定
arch/x86/kernel/irq.c           # 割り込みエントリポイント
arch/x86/kernel/traps.c         # 例外ハンドラ
arch/x86/mm/fault.c             # ページフォルトハンドラ
arch/x86/kernel/apic/          # APIC 関連
arch/x86/kernel/nmi.c           # NMI ハンドラ
kernel/irq/manage.c             # IRQ 管理 (request_irq 等)
kernel/irq/chip.c               # IRQ チップ抽象化
kernel/irq/handle.c             # IRQ ハンドリング
kernel/softirq.c                # Softirq 実装
kernel/workqueue.c              # Workqueue 実装
kernel/time/timer.c             # タイマー実装
kernel/time/hrtimer.c           # 高精度タイマー
include/linux/interrupt.h       # 割り込み関連ヘッダ
include/linux/irqdesc.h         # irq_desc 構造体
include/linux/workqueue.h       # Workqueue ヘッダ

免責事項: 本記事は教育目的で作成されたものであり、実際のプロダクション環境での設定変更は十分なテストを行った上で実施してください。カーネルバージョンによって API やデフォルト値が異なる場合があります。


本記事は AI (Claude) によって生成されました。 最終更新: 2026-04-10