4章 linuxカーネル - 割り込み・例外 4
TRANSCRIPT
4 章 Linux カーネル – 割り込み・例外 4・例外ベクタと例外ハンドラ・割り込みディスクリプタテーブル・割り込み信号発生からハンドラ実行まで・ハンドラ実行終了後の処理・割り込み / 例外処理のネスト
maoWeb >https://www.pridact.comTwitter >https://twitter.com/rivartenMail >[email protected]
はじめに
割り込み・例外の話
視点・概要・ハードウェアの動作・データ構造・データ構造の操作の仕方・ソースコード
ソースコードの情報が載っている場合は ,実際にソースコードに目を通しながら資料を見ていったほうがいいかもしれません。
今回はハードウェアの話が多いかもしれません
この資料について
・間違っていたらご指摘をお願い致します。・ Linux Kernel のバージョンは 4.9.16 です。・ページタイトルに * 印が付いているページは、 少し踏み込んだ内容になっています。 ざっと全体に目を通したければ、読み飛ばして もらっても構いません。
カーネル参考文献
< 参考書 > 〜基本的に古い情報だが、基本は学べる〜
・詳解 LINUX カーネル 第 3 版 (O’REILLY・ Linux カーネル 2.6 解読室 (Softbank Creative・ Linux カーネル解析入門 ( 工学社・ Modern Operating Systems 3e International
(Pearson, Andrew S. Tanenbaum) 〜初期化部分を知りたいなら〜
・新装改訂版 Linux のブートプロセスを見る( アスキー・メディアワークス )
< 参考 Web>・ Linux Cross Reference[http://lxr.free-electrons.com]・ Wikipedia
例外ベクタと例外ハンドラ (1)
・例外ベクタ (x86)
ベクタ番号 名前 種類 説明
0 除算エラー (Devide Error) フォルト プログラムが 0 で整数除算すると発生
1
デバッグ例外 (Debug) トラップ ,フォルト
eflags レジスタの TF=1 で発生 ( デバッガでのステップ実行に有用 ). 有効化したデバッグレジスタのアドレス範囲内にある命令を実行したり ,オペランドへアクセスしたりした時もデバッグ例外が発生 .
2マスク不可割り込み (Non-Maskable Interrupt) ---
マスク不可割り込み用に予約(NMIピンを使用 )
3ブレークポイント (Breakpoint) トラップ int3( ブレークポイント )命令によって発生 .
通常はデバッガがこの命令を埋め込む .
4オーバーフロー (Overflow) トラップ into(オーバーフローを調べる )命令を実行し
た時に ,eflags レジスタの OF(overflow) レジスタ フラグが設定されている場合に発生
5Bound 範囲超過 (Bounds check) フォルト bound( アドレス範囲を調べる )命令を , アドレ
スの有効範囲外にあるオペランドとともに実行すると発生 .
6無効オペコード (Invalid opcode) フォルト CPU が無効なオペコード ( 実行する動作を決
定するマシン語の一部 ) を検出すると発生
7デバイス使用不可能 (Device not available)
フォル ESCAPE命令 ,MMX命令 ,SSE/SSE2命令を実行した時に ,cr0 レジスタの TS フラグが設定されている場合に発生
例外ベクタと例外ハンドラ (2)
・例外ベクタ (x86)
ベクタ番号 名前 種類 説明
8
ダブルフォルト (Double falult) アボート CPU が例外ハンドラを呼び出す時に別の例外を検出すると , 通常 2 つの例外は順次処理されるが ,稀にプロセッサが連続して処理できない場合があり ,その時に発生 .
9コプロセッサセグメントオーバーラン(Coprocessor segment overrun)
アボート 外部算術演算コプロセッサで問題が起こると発生 . 古い 80386 プロセッサの場合にのみ発生
10無効 TSS(Invalid TSS) フォルト 無効な TSS を持つプロセスに切り替えようと
した場合に発生
11セグメント不在 (Segment not present) フォルト メモリ中に存在しない ( セグメントディスクリプ
タの P フラグが設定されていない ) セグメントを参照した場合に発生
12スタックセグメントフォルト (Stack segment fault)
フォルト 命令がスタックセグメントの範囲を越えようとした場合 , または ss レジスタが指すセグメントがメモリ中に存在しない場合に発生 .
13一般保護 (General protection) フォルト x86 のプロテクトモードの保護規則に違反し
た場合に発生
14
ページフォルト (Page fault) フォルト 参照されたページがメモリ上に存在しないか ,対応するページテーブルエントリが NULLか , ページング保護機構に違反した場合に発生
例外ベクタと例外ハンドラ (3)
・例外ベクタ (x86)
ベクタ番号 名前 種類 説明
15 Intel が予約済み ---
16
浮動小数点エラー (Floating point error) フォルト CPU に組み込まれている浮動小数点回路がエラーを検出した場合に発生 . 算術オーバーフローやゼロ除算など .x86 では ,符号付き除算の結果が符号付き整数にならない場合 (-2,147,483,648/-1 など ) にも発生 .
17アラインメントチェック (Alignment check) フォルト オペランドのアドレスが正しくアラインされて
いない場合に発生
18マシンチェック アボート マシンチェック機構が CPUやバスのエラーを
検出した場合に発生 .
19SIMD浮動小数点例外 (SIMD floating point exception)
フォルト CPU に組み込まれている SSEや SSE2 回路が浮動小数点演算でエラーを検出した場合に発生 .
20~31将来のために Intel が予約
---
例外ベクタと例外ハンドラ (4)
・例外ハンドラ (x86)
ベクタ番号 例外 ハンドラ シグナル
0 除算エラー (Divide Error) divide_error() SIGFPE
1 デバッグ例外 (Debug) debug() SIGTRAP
2マスク不可割り込み (Non-Maskable Interrupt)
nmi()---
3 ブレークポイント (Breakpoint) int3() SIGTRAP
4 オーバーフロー (Overflow) overflow() SIGSEGV
5 Bound 範囲超過 (Bounds check) bounds() SIGSEGV
6 無効オペコード (Invalid opcode) invalid_op() SIGILL
7デバイス使用不可能 (Device not available)
device_not_available()---
8 ダブルフォルト (Double falult) doublefault_fn() ---
9コプロセッサセグメントオーバーラン(Coprocessor segment overrun)
coprocessor_segment_overrun()
SIGFPE
10 無効 TSS(Invalid TSS) invalid_TSS() SIGSEGV
11 セグメント不在 (Segment not present) segment_not_present() SIGBUS
12スタックセグメントフォルト (Stack segment fault)
stack_segment() SIGBUS
例外ベクタと例外ハンドラ (5)
・例外ハンドラ (x86)
ベクタ番号 例外 ハンドラ シグナル
13 一般保護 (General protection) general_protection() SIGSEGV
14 ページフォルト (Page fault) page_fault() SIGSEGV
15 Intel が予約済み --- ---
16 浮動小数点エラー (Floating point error) coprocessor_error() SIGFPE
17 アラインメントチェック (Alignment check) alignment_check() SIGBUS
18 マシンチェック (Machine check) machine_check() ---
19SIMD浮動小数点例外 (SIMD floating point exception)
simd_coprocessor_error() SIGFPE
割り込みディスクリプタテーブル (1)
・割り込みディスクリプタテーブル IDT (Interrupt Descriptor Table)
・割り込みや例外のベクタとハンドラのアドレスの対応を保持 .・形式はGDTや LDT とほぼ同じ .・ IDT最大サイズは 256×8byte=2KB・ IDT はメモリ上の任意の位置に置くことができる .・ CPU の idtr レジスタに IDT のベースリニアアドレスとリミットを指定 .・割り込みを使用する前に lidt アセンブリ命令で初期化する .・ 40~43bit(type) の内容がディスクリプタの種類を表している .
・ディスクリプタの種類
・タスクゲート・割り込みゲート・トラップゲート
Linux では , 割り込み処理に割り込みゲートを使用し ,例外処理にトラップゲートを使用している .
割り込みディスクリプタテーブル (2)
・タスクゲート
プロセスの TSS セレクタを置く .割り込み信号が発生した時に実行中のプロセスの TSS と置き換えられる .ダブルフォルト例外は , カーネルの不正処理を表していて ,Linux においてタスクゲートを用いる唯一の例外 .
割り込みディスクリプタテーブル (3)
・割り込みゲート
割り込み・例外のハンドラがあるセグメントのセグメントセレクタとセグメント内でのオフセットを置く .このセグメントに制御を移す間は ,プロセッサは eflags.if=0 にしてマスク可能割り込みを禁止する .
割り込みディスクリプタテーブル (4)
・トラップゲート
割り込みゲートと同様だが ,別のセグメントに制御を移す間でも ,プロセッサが eflags.if フラグを変更しない点で異なる .
割り込み信号発生からハンドラ実行まで (1)
・前提条件 ・カーネルの初期化が完了し ,CPU がプロテクトモードで動作している .
0.ある 1 つの命令を実行後 ,cs/eip レジスタは次に実行すべき命令のアドレ スが入っている . これを実行する前に ,制御回路は前の命令の実行中に 割り込み・例外が発生していないかを調べる (INTR,NMI を調べる ) 発生していれば ,制御回路は以降の処理を進める .1. 割り込み・例外に対応するベクタ (0≦i≦255) を取得する .2.idtr レジスタが指す IDT から i 番目のエントリを読み取る . (該当エントリには割り込みゲートかトラップゲートが存在する )3.gdtr レジスタから GDT ベースアドレスを取得し , GDT 内から IDT エントリのセレクタがさすセグメントディスクリプタを取得 . このディスクリプタに , 割り込み / 例外ハンドラがあるセグメントのベースア ドレスがある .
割り込み信号発生からハンドラ実行まで (2)
4. 割り込み元の正当性の確認 .・ cs レジスタの CPL とGDT 内のセグメントディスクリプタの DPL を比較 . CPL: 割り込みを発生させたプログラムの現行特権レベル DPL: セグメントアクセスに必要な特権レベル
・ DPL の特権レベルより CPL の特権レベルの方が低い (値としては ,CPL>DPL) ⇒一般保護例外 . 割り込みハンドラは , 割り込みを発生させたプログラムより 低い特権レベルでは動作できない .・ OK ソフトウェア的に生成された割り込み / 例外の場合 (int n,int 3,into 命令等 ),さらなるセキュリティチェックを行う .
・ CPL と IDT 内のセグメントディスクリプタの DPL を比較・ DPL の特権レベルより CPL の特権レベルの方が低い (値としては ,CPL>DPL) ⇒一般保護例外 . ユーザアプリケーションによるトラップゲートや割り込み ゲートへのアクセスを防ぐ .
割り込み信号発生からハンドラ実行まで (3)
5.特権レベルが変更されているかを調べる .CPL ≠ セグメントディスクリプタの DPL⇒制御回路は新しい特権レベル用のスタックを用意する .
a. tr レジスタを読み取り , 実行中のプロセスの TSS にアクセス .b. ss/esp レジスタに ,TSS に置かれている新しい特権レベル用の 論理アドレスを ss/esp に読み込む .c. 古い特権レベル用スタックの論理アドレスを表す ss/esp を , 新しいスタックに退避 .
6. フォルトが発生した場合 , 例外を発生させた命令の論理アドレスを cs/eip に読み込む . これで再実行が可能になる .7. スタックに eflags,cs,eip レジスタの内容を退避する .8. 例外がハードウェアエラーコードを持っていれば , スタックに退避する .9.IDT 内の i 番目のエントリにあるセグメントセレクタとゲートディスクリプタの オフセットフィールドを ,cs/eip レジスタに読み込む . これが割り込み・例外ハンドラの最初の命令の論理アドレスを表す .
push順 :[ss,esp] → [eflags,cs,eip] → ( ハードウェアエラーコード )
ハンドラ実行終了後の処理
iret命令を実行して , 割り込まれたプロセスに制御を移す .
pop順 :[ss,esp] ← [eflags,cs,eip] ← ( ハードウェアエラーコード )
1. スタックに退避されている cs,eip,eflags レジスタの値を読み込む .2. ハンドラの CPL が ,cs の CPL と同じかどうか調べる . ( 割り込まれたプロセスがハンドラと同じ特権レベルで動作していたか ) ・同じ⇒ iret は実行を終了 ・違う⇒次のステップに進む .3. スタックから ss/esp レジスタを読み込む . ( 古い特権レベル用のスタックに戻る )4.ds,es,fs,gs セグメントレジスタを調べる . CPL の特権レベル > DPL の特権レベル (値は ,CPL<DPL) となるセグメン トディスクリプタを参照するセレクタがあれば , そのセグメントレジスタをクリア .
カーネルルーチンが実行したセグメントを ,それより特権レベルが低いプロセスが使用してしまう事を防ぐ .クリアしなければ , カーネル空間にアクセス可能になってしまう .
割り込み・例外処理のネスト (1)
・割り込み / 例外のカーネル実行パスがネストすることもある .・カーネル実行パスのネストを許可する条件 「割り込みハンドラ実行中に絶対にプロセスを切り替えないこと」
カーネル実行パスを再開させる為に必要なデータは ,カレントプロセスのカーネルスタックに積まれる為 ,割り込みハンドラ動作中に ,他のプロセスに切り替えられない .
・例外のほとんどはユーザーモードの時だけ発生 . ページフォルト例外はカーネルモードでも発生 .
システムコールのカーネル実行パス + ページフォルト例外のカーネル実行パス
・割り込みは , カレントプロセスのデータを参照しない .・割り込みハンドラは ,別の割り込みハンドラや例外ハンドラに 割り込むことがある .・例外ハンドラが割り込みハンドラに割り込むことはない .・マルチプロセッサにおいて , 例外用のカーネル実行パスは , ある CPU で処理開始された後 , プロセスの切り替えによって , 別の CPU に移動することもある .
割り込み・例外処理のネスト (2)
・ Linux がカーネル実行パスのネストを許可する理由1.PICやデバイスコントローラのスループット向上 .
PICやデバイスコントローラは ,CPU から ACK を受取るまで処理を止めたまま .カーネル実行パスを切り替えて動作させると ,他の割り込み処理の実行中であっても , カーネルが ACK を返すことができる .
2.優先度レベルがない割り込みモデルを実現できる .割り込みハンドラは ,他の割り込みハンドラの処理を遅延できる為 ,デバイスに優先順位をあらかじめ与えておく必要がない .これにより , カーネルコードが単純化でき ,移植性が向上 .