本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
前回を読んでいない方はこちらからどうぞ.
Linuxカーネルの記事一覧はこちらからどうぞ.
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
今回のテーマは割り込みです.
割り込みを理解すると,ハードウェアとOSがどのようにやり取りしているかがわかります.
目次
割り込みとは
割り込みとは,抽象化・多重化を実現するための仕組みのことです.
OSの割り込みは,カーネルにサービスを要求することです.
ソフトウェア(例:int命令)またはハードウェア(例:キーボード)によりカーネルにサービスを要求します.
Linuxにおける割り込み処理は,Top HalfとBottom Halfがありますので,それぞれ解説していきます.
ストレージであるHDD,SSD,CPUのキャッシュやメインメモリのアクセス時間は以下のようになります.
- HDDのアクセス時間:数ms~10ms
- SSDのアクセス時間:10μs~100μs
- CPUのキャッシュやメインメモリのアクセス時間:数ns~100ns
参考までに,HDDとSSDの解説は以下の動画がわかりやすいです.
HDDやSSD(特にHDD)に頻繁にアクセスするとCPUのキャッシュやメインメモリよりアクセス時間がとても長いので,コンピュータの性能を最大限発揮することができません.
また,カーネルは,ハードウェアの処理が完了してから,他の処理を行うことができるようにしなければなりません.
ハードウェアの処理完了を知る方法として以下の2つがあります.
- ポーリング:カーネルは定期的にハードウェアの状態をチェックする.
- 割り込み:ハードウェアがプロセッサに完了を知らせる.
ポーリングは定期的にチェックするため,一定の割合でCPUを消費する必要があります.
チェック時間が短くなる可能性が高い場合はポーリングを採用するメリットがありますが,チェック時間が長い場合は他の処理を妨害してしまう可能性があります.
そこで,ハードウェアがプロセッサに完了を知らせる「割り込み」を採用することで,この問題を解決します.
割り込みの例として,以下があります.
- ディスクの読み込み完了
- キーボードのキー入力
- ネットワークパケット到着
割り込みの種類
割り込みの種類はハードウェア割り込みとソフトウェア割り込みがありますので,それぞれ解説していきます.
ハードウェア割り込み
ハードウェア割り込みは,ハードウェア(デバイス)の割り込み要求端子の変化によりCPU外部から発生させる割り込みのことです.
ハードウェア割り込みには,以下の2種類があります.
- ノンマスカブル割り込み(NMI:Non-Maskable Interrupt)
- マスク可能な割り込み(MI:Maskable Interrupt)(狭義の割り込み要求(IRQ: Interrupt ReQuest)(または割り込みライン)を意味する)
NMIは電源障害,メモリエラー等の決して無視できない割り込みを処理します.
IRQは,割り込み要求番号を表します.
x86-64のMIはEFLAGSのIFビットが0の場合はIRQを無視します.
x86-64のsti命令でIRQを有効にし,cli命令でIRQを無効にします.
ARM64のMIには,通常のIRQと高速な割り込みをサポートするFast Interrupt reQuest(FIQ)があります.
ARM64のmsr命令でDAIF(割り込みマスクビットを管理するレジスタ)のIRQやFIQのビットを設定すると割り込みを無効,クリアすると割り込みを有効にします.
CPUが割り込みを受け付けるとINTerrupt Acknowledgement(INTA)をセットします.
そして,割り込みが終了する際,End of Interrupt(EOI)命令を発行します.
EOI命令はx86-64の場合はiret命令,ARM64の場合はeret命令になります.
ソフトウェア割り込み
これに対して,ソフトウェアが発生させる割り込みのことをソフトウェア割り込みと呼びます.
また,CPUが何らかのコードを実行することによって発行されるソフトウェア割り込みのことを例外と呼びます.
つまり,ハードウェア(デバイス)割り込みに対して,例外はソフトウェアで発生させます.
例外はプログラム異常,(一般的な)ソフトウェア割り込みは自発的例外と呼ばれます.
- プログラム異常(一般的な例外):ゼロ除算(Division by Zero),ページフォールト(Page Fault),一般保護違反(General Protection Fault)
- 自発的例外(CPU内部においてCPU命令によって要求される一般的なソフトウェア割り込み):intアセンブリ命令,例えばsyscallの呼び出し等
これらのソフトウェア割り込みはハードウェア割り込みと同様にカーネルで管理されます.
割り込みコントローラ(PIC:Programmable Interrupt Controller)
割り込みは,割り込みコントローラによって多重化された電気信号で,CPUの特定のピンで送信されます.
割り込みを受信すると,専用の関数(割込みハンドラ)が実行されます.
ユーザ空間やカーネル空間では,(ほぼ)いつでも割り込みをかけて処理することができます.
x86,x86-64,ARM64の割り込みコントローラについて解説します.
x86(32ビット)の割り込みコントローラ「Intel 8259」
x86(32ビット)の割り込みコントローラ「Intel 8259」の割り込みラインでは,Master 8259とSlave 8259があります.
Master 8259のIRQ番号は以下になります.
- IRQ 0:システムタイマ
- IRQ 1:キーボード
- IRQ 2:Slave 8259にカスケード接続
- IRQ 3:シリアルポートCOM2及びCOM4
- IRQ 4:シリアルポートCOM1及びCOM3
- IRQ 5:ハードディスクコントローラ
- IRQ 6:フロッピーディスクコントローラ
- IRQ 7:LPT1
Slave 8259のIRQ番号は以下になります.
- IRQ 8:リアルタイムクロック
- IRQ 9:割り当てなし
- IRQ 10:割り当てなし
- IRQ 11:割り当てなし
- IRQ 12:PS/2マウス
- IRQ 13:数値演算コプロセッサ
- IRQ 14:ハードディスクコントローラ1
- IRQ 15:ハードディスクコントローラ2
割り込みベクタテーブル(例外ベクタテーブル)とは,割り込み(例外を含む)が発生した際に,処理を開始するプログラム(後述する割り込みハンドラ)を実行するための命令や,アドレスを保存するテーブルのことです.
x86の例外ベクタテーブルは下表になります(詳細な解説は以下の記事).
例外は以下の3種類に分類されます.
- Fault:修正することで,何事もなかったかのようにプログラムを継続することができます.
- Trap:トラップ命令の実行後,直ちに報告されます.
- Abort:回復不可能な重大なエラーです.
例外番号 | ニーモニック | 名前 | 種類 |
---|---|---|---|
0 | #DE | Divide-by-zero Error | Fault |
1 | #DB | Debug | Fault/Trap |
2 | - | Non-maskable Interrupt | - |
3 | #BP | Breakpoint | Trap |
4 | #OF | Overflow | Trap |
5 | #BR | Bound Range Exceeded | Fault |
6 | #UD | Invalid Opcode | Fault |
7 | #NM | Device Not Available | Fault |
8 | #DF | Double Fault | Abort |
9 | - | Coprocessor Segment Overrun | Fault |
10 | #TS | Invalid TSS | Fault |
11 | #NP | Segment Not Present | Fault |
12 | #SS | Stack-Segment Fault | Fault |
13 | #GP | General Protection Fault | Fault |
14 | #PF | Page Fault | Fault |
15 | - | Reserved | - |
16 | #MF | x87 Floating-Point Exception | Fault |
17 | #AC | Alignment Check | Fault |
18 | #MC | Machine Check | Abort |
19 | #XM/#XF | SIMD Floating-Point Exception | Fault |
20 | #VE | Virtualization Exception | Fault |
21 | #CP | Control Protection Exception | Fault |
22~27 | - | Reserved | - |
28 | #HV | Hypervisor Injection Exception | Fault |
29 | #VC | VMM Communication Exception | Fault |
30 | #SX | Security Exception | Fault |
31 | - | Reserved | - |
x86-64(64ビット)のマルチプロセッサ対応の割り込みコントローラ「Advanced PIC(APIC)」
x86-64(64ビット)の割り込みコントローラ「Advanced PIC(APIC)」を解説します.
APICは,Intel 8259のようなPICに対して,以下の点が改良されています.
- マルチプロセッサ対応
- 優先度制御
※x86-64でAPICはIntel 8259と共存していますが.APICを利用する際はIntel 8259を無効にします.
APICは,Local APICとI/O APICで構成されています.
Local APICは,プロセッサチップの内部にあり,タイマ割り込みを発生させるタイマやプロセッサ間割り込み(IPI:Inter-Processor Interrupt)を発生させます.
I/O APICは,システムチップセット(またはサウスブリッジ)にあり,ローカルAPICへの割り込みを再分配します.
私のx86-64のLinux環境におけるIRQ毎の割り込み数「/proc/interrupts」の表示結果は以下になります.
CPU毎にIRQ番号と割り込み数が表示されています.
3~13行目はI/O APICが管理しているIRQ番号0~19になります.
ここで,IRQ 0はシステムタイマ(timer),IRQ 1はキーボード(i8042),IRQ 8はリアルタイムクロック(rtc0)で,Intel 8259の割り当てと同様です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
$ cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 CPU8 CPU9 CPU10 CPU11 CPU12 CPU13 CPU14 CPU15 0: 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 IO-APIC 2-edge timer 1: 0 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0 IO-APIC 1-edge i8042 8: 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 IO-APIC 8-edge rtc0 9: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 IO-APIC 9-fasteoi acpi 12: 0 0 0 0 0 0 15 0 0 0 0 0 0 0 0 0 IO-APIC 12-edge i8042 14: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 IO-APIC 14-edge ata_piix 15: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 IO-APIC 15-edge ata_piix 16: 0 0 0 0 0 0 0 0 0 1015 537 0 0 0 0 0 IO-APIC 16-fasteoi vmwgfx, snd_ens1371 17: 0 0 0 0 8526 178732 0 0 0 0 0 0 0 0 0 0 IO-APIC 17-fasteoi ehci_hcd:usb1, ioc0 18: 0 0 0 0 0 151 0 0 0 0 0 0 0 0 0 0 IO-APIC 18-fasteoi uhci_hcd:usb2 19: 0 0 0 0 0 0 0 0 0 0 0 71778 0 195 0 0 IO-APIC 19-fasteoi ens33 24: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 344064-edge PCIe PME, pciehp 25: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 346112-edge PCIe PME, pciehp 26: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 348160-edge PCIe PME, pciehp 27: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 350208-edge PCIe PME, pciehp 28: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 352256-edge PCIe PME, pciehp 29: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 354304-edge PCIe PME, pciehp 30: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 356352-edge PCIe PME, pciehp 31: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 358400-edge PCIe PME, pciehp 32: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 360448-edge PCIe PME, pciehp 33: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 362496-edge PCIe PME, pciehp 34: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 364544-edge PCIe PME, pciehp 35: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 366592-edge PCIe PME, pciehp 36: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 368640-edge PCIe PME, pciehp 37: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 370688-edge PCIe PME, pciehp 38: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 372736-edge PCIe PME, pciehp 39: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 374784-edge PCIe PME, pciehp 40: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 376832-edge PCIe PME, pciehp 41: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 378880-edge PCIe PME, pciehp 42: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 380928-edge PCIe PME, pciehp 43: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 382976-edge PCIe PME, pciehp 44: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 385024-edge PCIe PME, pciehp 45: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 387072-edge PCIe PME, pciehp 46: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 389120-edge PCIe PME, pciehp 47: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 391168-edge PCIe PME, pciehp 48: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 393216-edge PCIe PME, pciehp 49: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 395264-edge PCIe PME, pciehp 50: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 397312-edge PCIe PME, pciehp 51: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 399360-edge PCIe PME, pciehp 52: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 401408-edge PCIe PME, pciehp 53: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 403456-edge PCIe PME, pciehp 54: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 405504-edge PCIe PME, pciehp 55: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 407552-edge PCIe PME, pciehp 56: 0 0 0 0 0 0 0 12846 0 82 0 0 0 0 0 0 PCI-MSI 1130496-edge ahci[0000:02:05.0] 57: 0 0 0 0 0 0 0 0 0 0 0 3109 0 0 0 0 PCI-MSI 129024-edge vmw_vmci 58: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 129025-edge vmw_vmci NMI: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Non-maskable interrupts LOC: 99494 149752 137392 135550 137006 104972 199541 141711 89160 105069 42396 107336 89018 91747 55317 54473 Local timer interrupts SPU: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Spurious interrupts PMI: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Performance monitoring interrupts IWI: 5358 1692 3682 3040 1860 1942 12464 2106 1184 1613 1330 2128 768 5849 8375 8745 IRQ work interrupts RTR: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 APIC ICR read retries RES: 46266 26020 44715 38315 33343 18022 34742 32388 32521 23353 26448 28805 22387 20366 28994 19323 Rescheduling interrupts CAL: 96442 15580 4523 3516 3581 3597 3798 3616 3700 3499 3677 3617 3583 3550 3643 3546 Function call interrupts TLB: 199 232 197 106 156 241 168 145 373 185 303 229 224 142 211 243 TLB shootdowns TRM: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Thermal event interrupts THR: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Threshold APIC interrupts DFR: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Deferred Error APIC interrupts MCE: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Machine check exceptions MCP: 84 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 Machine check polls ERR: 0 MIS: 0 PIN: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Posted-interrupt notification event NPI: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Nested posted-interrupt event PIW: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Posted-interrupt wakeup event |
ARM64の割り込みコントローラ「The Generic Interrupt Controller(GIC)」
ARM64の「The Generic Interrupt Controller(GIC)」は,アーキテクチャ共通の割り込みコントローラです.
ARM64の例外ベクタテーブルは以下になります.
※VBAR_ELnは例外レベルnにおける例外ベクタテーブルのベースアドレスになります.
アドレス (VBAR_ELnからのオフセット) | 例外タイプ | 説明 |
---|---|---|
VBAR_ELn + 0x000 | Synchronous | SP0(例外レベル0のスタックポインタ)を利用して現在の例外レベルで処理 |
VBAR_ELn + 0x080 | IRQ/vIRQ | |
VBAR_ELn + 0x100 | FIQ/vFIQ | |
VBAR_ELn + 0x180 | SError/vSError | |
VBAR_ELn + 0x200 | Synchronous | SPx(例外レベルxのスタックポインタ)を利用して現在の例外レベルで処理 |
VBAR_ELn + 0x280 | IRQ/vIRQ | |
VBAR_ELn + 0x300 | FIQ/vFIQ | |
VBAR_ELn + 0x380 | SError/vSError | |
VBAR_ELn + 0x400 | Synchronous | ARM64の低い例外レベル |
VBAR_ELn + 0x480 | IRQ/vIRQ | |
VBAR_ELn + 0x500 | FIQ/vFIQ | |
VBAR_ELn + 0x580 | SError/vSError | |
VBAR_ELn + 0x600 | Synchronous | ARM32の低い例外レベル |
VBAR_ELn + 0x680 | IRQ/vIRQ | |
VBAR_ELn + 0x700 | FIQ/vFIQ | |
VBAR_ELn + 0x780 | SError/vSError |
ARM64にはアーキテクチャ共通のGICだけでなく,ボード固有の割り込みコントローラを持つ場合があります.
例えば,Raspberry Pi固有の割り込みコントローラ「BCM2835,BCM2836,BCM2837,BCM2837B0,BCM2711,BCM2710A1」を知りたいあなたは,「Raspberry Pi Documentation Processors」を読みましょう!
割り込みハンドラ(割り込みサービスルーチン(ISR:Interrupt Service Routine))
割り込みハンドラ(割り込みサービスルーチン(ISR:Interrupt Service Routine))は,特定の割り込みに応答してCPUが実行する関数のことです.
Linuxでは,通常のC言語の関数が特定のプロトタイプにマッチして,割り込みハンドラの情報を渡します.
割り込みハンドラは,割り込みコンテキスト(またはアトミックコンテキスト)で実行されます.
通常のタスク実行やシステムコールによるプロセスコンテキストとは逆の操作になります.
割込みコンテキストはスケジューリング可能なエンティティではないので,タスクは割り込みハンドラでスリープできません.
Top HalfとBottom Halfの割り込み処理
割り込みハンドラには2つの相反する目標があります.
- 割り込み処理は高速でなければならない.
- 実行中のユーザプロセス(ユーザ/カーネル空間)に割り込みするため.
- 割り込み処理中に他の割り込みを禁止する必要がある場合あり.
- かなりの量の作業を行うことがある.
- そのため,時間がかかる.
- 例:ネットワークカードからのネットワークパケットを処理する.
そこで,Linuxを含む多くの最新OSでは,割り込み処理をTop HalfとBottom Halfの2つに分けています.
- Top Half
- (一部または全部の割り込みを無効に設定して)割り込みを受けたら即実行
- タイムクリティカルな処理のみ
- 例:割り込みの受信を確認やハードウェアのリセット
- Bottom Half
- あまり重要でなく,時間のかかる作業
- 他の割り込みを有効にして後で実行
Top HalfとBottom Halfの割り込み処理をネットワークのパケット処理を例にして説明します.
- Top Half:割り込みハンドラ
- ハードウェアを認識する.
- 新しいネットワークパケットをメインメモリにコピーする.
- ネットワークカードにパケットを追加できるようにする.
- ネットワークカードのパケットバッファは容量に制限があるため(パケットドロップ),時間的な制約がある.
- Bottom Half:コピーされたパケットを処理
- softirq,tasklet,work queue
- ユーザ空間のスレッドプールに似ている.
Bottom Halfの種類「softirq,tasklet,work queue」
どのBottom Halfを使うかの決定は性能の観点において重要です.
LinuxカーネルにおけるBottom Halfは,softirq,tasklet,work queueの3種類があります.
taskletはsoftirqの上に構築されており,よく似ています.
これに対して,work queueは,カーネルスレッド上に構築されています.
softirqは,高度にマルチスレッド化されたコード向けに最小限の割り込みのシリアライズ(Serialize)を提供します.
ここで,割り込みのシリアライズとは,割り込みの1つの処理が終了するごとに次の処理を開始するように,順番に実行することです.
このため,softirqハンドラは共有データの安全性を確保するために余分なステップを踏む必要があります.
なぜなら,同じタイプのsoftirqが異なるプロセッサ上で2つ以上同時に実行される可能性があるからです.
もし,問題のコードがすでに高度にマルチスレッド化されている場合,例えば,プロセッサ毎の変数に深く入り込んでいるネットワークサブシステムでは,softirqは良い選択となります.
タイミングクリティカルで高頻度な用途では,(Top Half以外のBottom Halfの種類において)softirqは最速の選択肢となります.
taskletは,主にマルチスレッド化されていないコード向けのシンプルなインターフェースです.
taskletは,同じタイプの2つのtaskletは同時に実行しない(可能性が高い)ので,実装がより簡単です.
ドライバ開発者は,softirqが複数のプロセッサで安全に同時実行できることを保証するために,プロセッサ毎の変数等を利用しない場合は,softirqよりもtaskletを常に選択すべきです.
work queueは,プロセスコンテキストで実行する必要がある場合に利用されます.
プロセスコンテキストが要件でない場合(ブロックやスリープする必要がない場合),work queueよりsoftirqやtaskletの方が適しています.
work queueは,カーネルスレッドを含むため,コンテキストスイッチが必要なり,最もオーバヘッドが大きくなります.
なので,ネットワークサブシステムのような1秒間に数千回の割り込みを処理することを考慮すると,work queueよりsoftirqやtaskletの方が理にかなっています.
しかし,ほとんどの場合において,work queueで十分な性能を発揮できて,使いやすいです.
項目 | softirq | tasklet | work queue |
---|---|---|---|
コンテキスト | 割り込み | 割り込み | プロセス |
シリアライズの継承 | なし | softirqに対して継承 | なし |
実行速度 | 速い | 普通 | 遅い |
使いやすさ | 難しい | 普通 | 易しい |
softirq,tasklet,work queueの比較は上表になります.
これらはすべて,一般的に割り込みが有効な状態で実行されます.
割り込みハンドラとの共有データ(Top Half)がある場合は,割り込みを無効にするか,ロックを使用する必要があります.
割り込みハンドラの関数
linux/include/linux/interrupt.hに割り込みハンドラの関数があります.
- irq_handler_t:割り込みハンドラ関数のtypedef定義で,割り込みハンドラ関数を書く時に利用する.
- request_irq関数:割り込みハンドラを登録する.
- free_irq関数:割り込みハンドラを削除する.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
typedef irqreturn_t (*irq_handler_t)(int, void *); /** * request_irq - Add a handler for an interrupt line * @irq: The interrupt line to allocate * @handler: Function to be called when the IRQ occurs. * Primary handler for threaded interrupts * If NULL, the default primary handler is installed * @flags: Handling flags * @name: Name of the device generating this interrupt * @dev: A cookie passed to the handler function * * This call allocates an interrupt and establishes a handler; see * the documentation for request_threaded_irq() for details. */ 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); } extern const void *free_irq(unsigned int, void *); |
request_irq関数のflags引数でIRQF_SHAREDフラグを設定しなければならないことに注意して下さい.
また,dev引数は,登録された各ハンドラに一意でなければなりません.
デバイス毎の構造体(例:struct device構造体)へのポインタで十分です.
カーネルは割り込みを受けると,その行に登録されているハンドラを順次呼び出します.
そのため,ハンドラは自分が発生させた割り込みかどうかを識別できることが重要です.
割り込みハンドラで処理する割り込みコンテキストは,プロセスコンテキスト(通常のタスク実行,システムコール)とは異なり,以下の制約があります.
- スケジュール・エンティティではないので,スリープやブロック不可
- kmalloc(size, GFP_KERNEL)は利用できず(GFP_KERNELはスリープ可),引数にGFP_ATOMIC(高優先度,スリープ不可)を利用
- ブロッキングロック(mutex等)は利用できないため,spinlockを利用
- printk関数は利用できないため,trace_printk関数を利用
割り込みコンテキストは,小さいスタックサイズ(1ページ,4KB)を持ちます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
/** * struct irq_desc - interrupt descriptor * @irq_common_data: per irq and chip data passed down to chip functions * @kstat_irqs: irq stats per cpu * @handle_irq: highlevel irq-events handler * @action: the irq action chain * @status_use_accessors: status information * @core_internal_state__do_not_mess_with_it: core internal status information * @depth: disable-depth, for nested irq_disable() calls * @wake_depth: enable depth, for multiple irq_set_irq_wake() callers * @tot_count: stats field for non-percpu irqs * @irq_count: stats field to detect stalled irqs * @last_unhandled: aging timer for unhandled count * @irqs_unhandled: stats field for spurious unhandled interrupts * @threads_handled: stats field for deferred spurious detection of threaded handlers * @threads_handled_last: comparator field for deferred spurious detection of threaded handlers * @lock: locking for SMP * @affinity_hint: hint to user space for preferred irq affinity * @affinity_notify: context for notification of affinity changes * @pending_mask: pending rebalanced interrupts * @threads_oneshot: bitfield to handle shared oneshot threads * @threads_active: number of irqaction threads currently running * @wait_for_threads: wait queue for sync_irq to wait for threaded handlers * @nr_actions: number of installed actions on this descriptor * @no_suspend_depth: number of irqactions on a irq descriptor with * IRQF_NO_SUSPEND set * @force_resume_depth: number of irqactions on a irq descriptor with * IRQF_FORCE_RESUME set * @rcu: rcu head for delayed free * @kobj: kobject used to represent this struct in sysfs * @request_mutex: mutex to protect request/free before locking desc->lock * @dir: /proc/irq/ procfs entry * @debugfs_file: dentry for the debugfs file * @name: flow handler name for /proc/interrupts output */ 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; /* IRQ action list */ unsigned int status_use_accessors; unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int tot_count; unsigned int irq_count; /* For detecting broken IRQs */ unsigned long last_unhandled; /* Aging timer for unhandled count */ unsigned int irqs_unhandled; atomic_t threads_handled; int threads_handled_last; raw_spinlock_t lock; struct cpumask *percpu_enabled; const struct cpumask *percpu_affinity; #ifdef CONFIG_SMP const struct cpumask *affinity_hint; struct irq_affinity_notify *affinity_notify; #ifdef CONFIG_GENERIC_PENDING_IRQ cpumask_var_t pending_mask; #endif #endif unsigned long threads_oneshot; atomic_t threads_active; wait_queue_head_t wait_for_threads; #ifdef CONFIG_PM_SLEEP unsigned int nr_actions; unsigned int no_suspend_depth; unsigned int cond_suspend_depth; unsigned int force_resume_depth; #endif #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif #ifdef CONFIG_GENERIC_IRQ_DEBUGFS struct dentry *debugfs_file; const char *dev_name; #endif #ifdef CONFIG_SPARSE_IRQ struct rcu_head rcu; struct kobject kobj; #endif struct mutex request_mutex; int parent_irq; struct module *owner; const char *name; } ____cacheline_internodealigned_in_smp; |
各割り込みは,linux/inlucde/linux/irqdesc.hで定義されている割り込みディスクリプタ構造体irq_descによって記述されます.
割り込みは,記述子構造体配列の中から対応する割り込み記述子構造体を選択します.
ディスクリプタ構造体には,ステータス情報と,この割込みに割り当てられた割込みフローメソッドと割込みチップ構造体へのポインタが含まれます.
割り込みが発生すると,低レベルアーキテクチャのコードは,desc->handle_irq()を呼び出すことによって,汎用割り込みコードを呼び出します.
この高レベルのIRQ処理機能は,割り当てられたチップ記述子構造体によって参照されるdesc->irq_data.chipプリミティブのみを利用します.
※古いLinuxカーネルではdo_IRQ関数/__do_IRQ関数によるスーパーハンドラを使用しており,あらゆるタイプの割り込みロジックに対応することが可能でしたが,現在のLinuxカーネルでは割り込みロジック毎(レベルトリガ,エッジトリガ等)に分けて処理します.
割り込み制御
カーネルコードは,コードのセクションのアトミックな実行を保証するために,割り込みを無効にする必要がある場合があります.
割り込みを無効にすることで,割り込みハンドラが現在のコードをプリエンプションしないことが保証されます.
また,割り込みを無効にすることで,カーネルのプリエンプションも無効になります.
ただし,割り込みを無効しても,他のコアからの同時アクセスは防げないことに注意して下さい.
この場合はロックが必要になり,割り込み無効と併用されることが多いです.
割り込みの無効/有効マクロ
linux/include/linux/irqflags.hに割り込みを無効/有効にするための以下のマクロを提供しています.
- local_irq_disableマクロ:割り込みを無効にする.
- local_irq_enableマクロ:割り込みを有効にする.
- local_irq_saveマクロ:現在の割り込みの無効/有効状態を保存した後に割り込みを無効にする.
- local_irq_restoreマクロ:保存した割り込み無効/有効状態に戻す.(必ず割り込み有効になるとは限りません.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#define local_irq_disable() \ do { \ bool was_disabled = raw_irqs_disabled();\ raw_local_irq_disable(); \ if (!was_disabled) \ trace_hardirqs_off(); \ } while (0) #define local_irq_enable() \ do { \ trace_hardirqs_on(); \ raw_local_irq_enable(); \ } while (0) #define local_irq_save(flags) \ do { \ raw_local_irq_save(flags); \ if (!raw_irqs_disabled_flags(flags)) \ trace_hardirqs_off(); \ } while (0) #define local_irq_restore(flags) \ do { \ if (!raw_irqs_disabled_flags(flags)) \ trace_hardirqs_on(); \ raw_local_irq_restore(flags); \ } while (0) |
Bottom Halfの割り込みの無効/有効関数
linux/include/linux/bottom_half.hにBottom Halfのsoftirqとtaskletの処理の無効/有効関数を提供しています.
- local_bh_disable関数:Bottom Halfのsoftirqとtaskletの処理を無効にする.
- local_bh_enable関数:Bottom Halfのsoftirqとtaskletの処理を有効にする.
※これらの関数は,work queueの処理を無効/有効にしないことに注意してください.(work queueの処理は常に有効になります.)
1 2 3 4 5 6 7 8 9 |
static inline void local_bh_disable(void) { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); } static inline void local_bh_enable(void) { __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); } |
まとめ
今回は割り込みを紹介しました.
具体的には,以下の内容を解説しました.
- 割り込みとは
- 割り込みの種類
- 割り込みコントローラ(PIC:Programmable Interrupt Controller)
- 割り込みハンドラ(割り込みサービスルーチン(ISR:Interrupt Service Routine))
- 割り込み制御
割り込みを深掘りしたいあなたは以下の記事を読みましょう!
- Linux generic IRQ handling
- Debugging the kernel using Ftrace - part 1,Debugging the kernel using Ftrace - part 2
- Interrupts and Interrupt Handling
- Modernizing the tasklet API
- Moving interrupts to threads
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
次回はこちらからどうぞ.