240 likes | 584 Views
LINUX カーネル読書会. ● デバイスドライバサポート □ 割込み制御 ☆ 割込みハンドラ ☆ 割込みハンドラの登録 ☆ 割込みハンドラの起動 ☆ 割込み禁止 □ 遅延処理 ☆ ソフトウェア割込みハンドラ ☆ BHハンドラ ☆ タスクキュー □ 時計 ☆ クロックハンドラ ☆ タイマーリスト ☆ その他のタイマ関連機能. プロセス. 典型的な割り込み動作. 1. 待ち. 4. ソフト割り込み ハンドラ. 起床. 割り込み ハンドラ. 3. 2. ハードウェア割り込み.
E N D
LINUXカーネル読書会 ●デバイスドライバサポート □ 割込み制御 ☆ 割込みハンドラ ☆ 割込みハンドラの登録 ☆ 割込みハンドラの起動 ☆ 割込み禁止 □ 遅延処理 ☆ ソフトウェア割込みハンドラ ☆ BHハンドラ ☆ タスクキュー □ 時計 ☆ クロックハンドラ ☆ タイマーリスト ☆ その他のタイマ関連機能
プロセス 典型的な割り込み動作 1. 待ち 4. ソフト割り込み ハンドラ 起床 割り込み ハンドラ 3. 2. ハードウェア割り込み
■割り込みハンドラ 割り込みハンドラ 割り込みは、タイマーの起動以外にもフォライバの制御、プロセッサ間通信にも使用される。 Linuxでは、CPUに発生した割り込み要因ごとに起動するハンドラを登録することが可能で ある。以下に割り込み管理の特徴を示す。 ●割り込みレベルは存在しない。 ●割り込みハンドラは、いくらでもネストできる(同一IRQの割り込みは禁止)。 ●Intel CPUの場合は、割り込みスタックを用意せず、カーネルスタック上で動作する。 割り込みハンドラの登録 デバイスドライバは、初期化時にrequest_irq()で割り込みハンドラを登録しなければならない。 デバイスドライバ 割り込みハンドラ 登録 複数登録に対応した 構造であれば可能 初期化処理 request_irq() 登録 登録 デバイスドライバ用 割り込みハンドラ デバイスドライバ用 割り込みハンドラ
割り込みハンドラの起動 割り込みハンドラは以下のようにして起動される。 ●割り込みエントリ関数であるdo_IRQ()から、割り込みコントローラに対して発生した 割り込みレベルのマスクと割り込みに対するACKを返す。 ※処理方式は割り込み制御コントローラごとに固有の方法がとられる。 割り込みレベルマスク 割り込み処理中に同じIRQレベルの割り込みを防ぐ 割り込みに対するACK 割り込み受付を通知し、次の割り込みの発生を防ぐ ●handle_IRQ_event()により、発生した割り込みレベルに対応したドライバの 割り込みハンドラが起動される。 ●全てのドライバの割り込みハンドラの実行が終了した後、割り込み制御コントローラ に対して、発生した割り込みレベルに対するマスクを解除する。
foo_interrupt() bar_interrupt() ???_interrupt() do_interrupt() irqaction handle_irq_event() *handler *handler *handler timer_interrupt() *action *handler irqaction ioapci_level_irq_type *handler *action *handler IOAPIC (*ack)() (*end)() *action *handler i8259A_irq_type (*ack)() (*end)() Irq_desc[] du_irq() ハードウェア割り込み
do_foo_irq (割り込み番号IRQ、…) 同種の割り込みが発生しないよう割り込みのマスクを行う。 割り込みに対するACKを割り込みコントローラに返す。 Handle_IRQ_event(割り込み番号IRQ, irq_desc[]に登録されているハンドラのリスト) 割り込みマスク解除 Handle_IRQ_event (割り込み番号IRQ、ハンドラリスト) 割り込みハンドラ開始宣言(irq_enter()) if (割り込みのネストを許すハンドラの場合) CPUの割り込みを許可 ※登録されている全ての割り込みハンドラを呼び出す CPU割り込みを禁止する 割り込みハンドラ終了宣言(irq_exit()) 問題点 Intelの場合、割り込みスタックを設けていない事と、デバイスの数だけネストを許可して いるため、割り込みハンドラの作りによってはカーネルスタックを突き破る可能性がある。
割り込み禁止 - CPUレベルでの割り込み制御割り込み禁止 - CPUレベルでの割り込み制御 割り込みハンドラ間で競合する資源を操作する場合、CPUへの割り込みを禁止する必要 がある。Linuxでは、割り込みに関する以下の関数が用意されている。 cli ()割り込みを禁止する sti ()割り込みの禁止を解除する save_flags ()現在の割り込み禁止状態を取り出す restore_flags ()割り込み禁止状態を設定する ※Interl CPUでは、eflagsレジスタの割り込み禁止ビットを操作して実現している (シングルプロセッサの場合)。 通常、割り込みが禁止されている区間がネストしても大丈夫なように、以下の使い方をする。 Save_flags (); cli (); /* 割り込みのクリティカルなコード */ restore_flags (); ※cli (), sti () の代わりに、local_irq_disable (), local_irq_enable () が利用される場合がある。 マルチプロセッサの場合、spin_lock_irq (), spin_unlock_irq ()が使用される。
割り込み禁止 - 割り込みコントローラレベルでの割り込み制御割り込み禁止 - 割り込みコントローラレベルでの割り込み制御 CPUでの割り込み制御とは別に、割り込みコントローラレベルでの割り込みの禁止と解除 を制御することができ、特定のデバイスからの割り込みを制御が可能である。 enable_irq (割り込み番号IRQ) 指定した割り込み番号IRQの割り込みをマスクする disable_irq (割り込み番号IRQ) 指定した割り込み番号IRQの割り込みマスクを解除
割り込みエントリ 割り込みが発生するとCPUは自動的にカーネルモードに移行し、割り込みエントリの先頭 に制御を移す。その後、IRQに対応した割り込みハンドラをdo_IRQ() で呼び出す。 割り込みハンドラの実行により、遅延処理要求が発生したり、割り込まれたプロセスの状態 に影響が与えられている場合があるときは対応した処理を行う必要がある。 ●ソフトウェア割り込み要求がある場合、do_softirq () でソフトウェア割り込みを実行する ●割り込みからの復帰先がユーザモードで、プロセスに対して再スケジューリング要求が 出ている場合、schedule() でCPUを明け渡す。 ●割り込みからの復帰先がユーザモードで、プロセスがシグナルを受信している場合は、 do_signal() でシグナル処理の準備を行う。 common_interrupt () レジスタの状態を保存 割り込みハンドラを実行 do_irq() if (ユーザモードへの復帰) goto ret_with_reschedule system_call ()のラベルへ分岐 レジスタの状態を復元 do_irq () 割り込み種別に対応した割り込みハンドラの呼び出し if (ソフトウェア割り込み要求あり) ソフトウェア割り込みを実行 do_doftirq ()
BH(ボトムハーフ)ハンドラメカニズム → 遅延処理用にソフトウェア割り込みハンドラBH(ボトムハーフ)ハンドラメカニズム → 遅延処理用にソフトウェア割り込みハンドラ tasklet_bi_vec[] tasklet_struct tasklet_struct CPU0 (*func)() (*func)() CPU1 execute bh_action() bh_action() softireq_action[] tasklet_hi_action() bb_bhaction execute HI_SOFTIRQ execute TCP/PROTOCOL ntask net_rx_action() net_tx_action() NET_TX_SOFTIRQ NET_RX_SOFTIRQ BH_handler(Old type) TASKLET_SOFTIRQ tasklet_action() execute Task_let_vec[] tasklet_struct tasklet_struct CPU0 (*func)() (*func)() CPU1 Driver A Driver B
現時点では、4種類のソフトウェア割り込みレベルが定義されている。現時点では、4種類のソフトウェア割り込みレベルが定義されている。 ソフトウェア割り込み要求(__cpu_raise_softirq)を行うと、do_softirq()が呼び出され、その中で 各レベルに登録されたソフトウェア割り込みハンドラが起動される。 ●TASKLET_SOFTIRQ ・汎用的なハンドラの登録・実行メカニズムで、複数登録することができる(登録 数に上限はない)。 ・ハンドラはtasklet_schedule()によりtask_letvec[]テーブルにキューイング登録。 ・登録されたハンドラ群はtasklet_action()によって実行される。 ・各CPU別に登録することができる。 ●HI_SOFTIRQ ・構造はTASKLET_SOFTIRQと同様。 TASKLET_SOFTIRQより優先的に動作させる機構だが、現時点ではBH ハンドラ処理のみが利用している。 ●NET_TX_SOFTIRQ TCP/IPプロトコルスタック送信処理 ●NET_RX_SOFTIRQ TCP/IPプロトコルスタック受信処理
ソフトウェア割り込みハンドラ操作関数 open_softirq() 指定されたソフトウェア割り込みレベルに、指定されたハンドラを登録する。 (登録先はsoftirq_action[]テーブル) cpu_raise_softirq() 指定されたレベルのソフトウェア割り込みを発生させる。 do_softirq() 登録されているソフトウェア割り込みハンドラを実行する。 システムコール出口、割り込みハンドラ出口、例外ハンドラ出口、プロセス 切り替え時にソフトウェア割り込み要求があると呼び出される。 tasklet_schedule() tasklet_struct構造体を登録側で用意し、ハンドラ情報を設定して tasklet_schedule()を呼び出す。 Tasklet_struct構造体はtask_letvec[]テーブルにキューイングされる。 tasklet_schedule() TASKLET_SOFTIRQレベルに登録されているハンドラ群を実行する。 Task_letvec[]テーブルにキューイングされているtasklet_struct構造体に登 録されたハンドラを呼び出す
BH(ボトムハーフ)ハンドラ BHハンドラ登録テーブルbh_base[]は固定長で、32個のハンドラを登録可能。 BHハンドラはソフトウェア割り込みハンドラから呼び出され、マルチプロセッサシステムで あっても同時に一つのプロセッサ上でしか実行を許されていない。 timer_bh() tq_timer task_queue task_queue TIMER_BH TQUEUE_BH : : SCSI_BH SCSI driver : task_queue task_queue IMMEDIATE_B tq_immedia_b :
BHハンドラ操作関数 init_bh (BH_NO, bh_hdr) BH_NOで指定された番号のハンドラとして、関数bh_hdrを登録する。 登録されるのはBHハンドラテーブルであるbh_base[]である。 remove_bh (BH_NO) BH_NOで指定された番号のハンドラを削除する。 bh_mark(BH_NO) BH_NOで指定された番号のハンドラに対して起動要求をだす。 指定されたハンドラを実行するbh_action()をtasklet_schedule()でソフ トウェア割り込みハンドラとして実行するように登録する。登録に必要 なtasklet_struct構造体はbh_task_vec[]として静的に確保される。 bh_action() BHハンドラを実行する。Tasklet_action()の延長で呼び出される。 ※v2.2以前との互換性を保つため、BHハンドラ実行中は他のBHハ ンドラが動作しないように禁止フラグを立てる(SMPのみ意味がある)。
タスクキュー BHハンドラは32個しかなく、その殆どが予約されているので、ボトムハーフを直接使用 することは容易ではない。このため、ボトムハーフの概念を動的に拡張したタスクキュー が用意されている(一つのBHハンドラに複数の処理を登録できる)。 BHハンドラ 予約 予約 タスクキュー 予約 次のエントリ 32個 : タスクキュー TQUEUE_BH 次のエントリ 処理関数 : 関数の引数 IMMEDIATE_BH 処理関数 予約 処理関数 関数の引数 処理関数
目的のキューに処理を登録するにはqueue_task()を用いる。登録された処理は目的のキューに処理を登録するにはqueue_task()を用いる。登録された処理は run_task_queue()によって実行される。 do_timer() tq_timer task_queue task_queue task_queue task_queue task_queue tq_immediate system-call entry task_queue task_queue tq_scheduler scheduler task_queue task_queue task_queue tq_disk filesystem
タスクキューを操作する関数 queue_task (hdr, tskq) hdrで指定した遅延ハンドラをタスクキューtskqへ登録する。 run_task_queue (tskq) タスクキューtskqに登録された遅延処理ハンドラを実行する。 タスクキューには以下の種類があり、目的に合わせて登録先を選ぶことができる。 ●tq_immediate カーネルの処理がなくなったら即座に実行される。 ●tq_timer クロック処理のタイミングで実行される。 ●tq_scheduler プロセスを切り替えるタイミングで実行される。 ●tq_disk ファイルシステムが適当なタイミングで実行される。 ドライバへのI/O要求を遅延させ、効率的なI/O順に変更すること ができる。
■時計 クロックハンドラ Linuxの時計処理は ●ハードウェア割り込みハンドラとして動作するもの jiffiesの更新を行うdo_timer() (jiffiesは、システムが起動してからの経過時間をティックという単位で保持じて いる。1ティックは10ミリ秒) ●BHハンドラとして動作するもの jiffiesの更新以外の処理 do_timer() ・システム起動からの時刻(jiffies)更新 ・カレントプロセスへの処理 update_process_time() (プロファイリング、統計情報収集、再スケジューリング要求 ・時計処理の本体であるtimer_bh()の起動要求 ・タスクキューtq_timerの起動要求
クロック処理本体のtimer_bh()は、BHハンドラとして起動されるが、割り込みハンドラよりクロック処理本体のtimer_bh()は、BHハンドラとして起動されるが、割り込みハンドラより 大きく遅延されることもあり、この場合、数クロック分の処理をまとめて行わなければなら ない(timer_bhが起動される前にdo_timerが数回動く場合がある)。 timer_bh do_timer() jiffies BHハンドラ task_queue task_queue tq_timer CPU クロック処理本体 ・カレンダの更新 update_wall_time() ・ロードアベレージの計算 calc_load() ・タイマーリストの管理と実行 run_timer_list() タイマ割り込み
タイマーリスト Linuxにも、伝統的なUNIXと同様に、指定した時間後にコールバックされるハンドラを登録 することが可能である(TCP/IPのタイムアウト処理や再送信処理に利用されている)。 登録されたリストをクロック処理毎に参照し、ハンドラがあれば実行する。 timer_bh timer_vec_root timer_list 1tick計 timer_list timer_list 256tick毎に 1エントリ 繰り上げ Expire時刻 callback関数 毎tick 1エントリ づつ実行 timer_vec timer_list timer_list 256tick計 timer_list 256×64tick毎に 1エントリ 繰り上げ timer_vec timer_list 256×64 tick計 timer_list timer_list 256×64×64tick毎に 1エントリ 繰り上げ
タイマリストを操作する関数 add_timer () タイマーリストにハンドラを登録する expire時間までの長さに応じて登録するtimer_vecを選択する。 del_timer (), del_timer_sync() タイマーリストからハンドラを削除する。Del_timer_sync()はマルチプロセッ サの場合で、指定されたタイマーが動作中の時にはターマーの完了を待ち 合わせてから削除する。 mod_timer () タイマーリストに登録されているハンドラの起動時間を変更する。 run_timer () ・タイマーリストに登録されているハンドラで、expireの時間に達したものを 呼び出す。毎クロックごとにtimer_vec_rootのエントリを一つ実行する。 ・timer_vec_rootエントリは一周するたびに、一つ上のtimer_vecカウンタも 一つ進めて、1エントリ分を読み出してtimer_vec_rootに再展開する。 ・一つ目のtimer_vecエントリが一周するたびに、一つ上のtimer_vecカウン タも進め、 1エントリ分を読み出してtimer_vecに再展開する。
その他のタイマ関連機能 - カーネル内時限待ちその他のタイマ関連機能 - カーネル内時限待ち Linuxでは、カーネルの内部で一定時間だけ待ち合わせをすることが可能である。これは タイマーリストとスケジューラを組み合わせて実現している。 schedule_timeout (timeout) timeout時間だけCPUを放棄する。add_timer()で、timeout時間後に process_timeout関数が呼び出されるように登録し、スケジューラを呼び出す。 ※nanosleepシステムコールの実現にしようされている。 process_timeout () schedule_timeout()でtimeout時間が経過したプロセスを起床する。 ※処理内容はwake_up_process()と同様 sleep_on_timeout () sleep_on ()にタイムアウト機能を持たせたもので、CPU放棄時に schedule_timeout ()を利用している。 ※interruptible_sleep_on()に対応したinterruptible_sleep_timeout ()も 存在する。
その他のタイマ関連機能 - setitimerシステムコールその他のタイマ関連機能 - setitimerシステムコール setitimerシステムコールを実現しるために、各システムコールはtask_struct内部にタイマー リスト登録用のテーブル(real_timerメンバ)を持っている。 add_timer() タイマーリスト プロセス setitimer() task_struct 登録 指定時間 経過 real_timer メンバ 再登録 ハンドラ シグナル送信(SIGALRM, SIGVTALRM, SIGPROF)
setitimerに関連する関数 do_setitimer() ・実時間指定でsetitimer()が指定された場合、カレントプロセスのreal_timeを タイマーリストに登録する。 ・相対時間指定でsetitimer()が制定された場合は、タイムアウト時間の設定 のみを行って、タイマーリストを使用しない。 ※タイムアウト時間はクロック処理の中からポーリングで実現する。 it_real_fn () ・実時間指定でsetitimerが指定された場合、タイウアウト時に呼び出される 関数 ・SIGALRMを発生したあと、add_timer()で再度ハンドラを登録する。 do_it_vrit () ・timer_bh ()のクロック処理の延長で呼び出される。 ・カレントプロセスの実行時間を計算し、タイムアウト時間に達した場合に SIGVTALRMシグナルを発生させる。その後、告ぎのタイムアウト時間を 設定する。 do_it_prof () ・do_it_virt ()と同様で、SIGVTALRMの代わりにSIGPROFシグナルを発生 させる。