気になったことのあれこれ。この広告は、90日以上更新していないブログに表示しています。2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelのx86とする。今回はソフト割り込みについて見ていく。(*nは参考文献のn番目に対応する)ソフト割り込みとタスクレット割り込みハンドラであるISRは順次処理されているのに対して、遅延可能な処理は割り込みを許可した状態で行うことが可能である。遅延可能な処理を割り込みハンドラから切り離すことでカーネルの応答時間を短縮することができる。遅延処理の実装としてはソフト割り込みとタスクレットがあり、その他にもワークキューで処理されるものもある。タスクレットはソフト割り込みをベースに実装されており、コードにあるsoftirqは多くの場合両方を意味する。ソフト割り込み及びタスクレットの比較ソフト割り込みはコンパイル時に定義し静的に割り当てられる、対してタスクレットは動的な割り当てと初期化が可能となっている(カーネルモジュールなど)。ソフト割り込みは同じ物であっても複数のCPU上で実行が可能で、再入可能ではあるがスピンロックを用いて明示的にデータを保護する必要がある。反対にタスクレットでは同一の物は順次処理され、複数のCPU上で同時に実行はできない(異なるタスクレットであれば並列実行可能)。カーネルの初期化若しくはモジュールの組み込み時に行う。処理を保留状態(pending)にする。次回のスケジューリングで遅延処理が実行される。起動要求があってもカーネルが当該処理を実行しない。保留中の遅延処理を実行する。保留中の同じ種類の処理を同時に実行する。あるCPUで起動要求された遅延処理は同じCPU上で実行する。起動要求で使用したデータを遅延処理も利用すると考えられ、システム性能面で効果的と言える(ハードウェアキャッシュ)。ソフト割り込みソフト割り込みは以下のような種類がある。TASKLET_SOFTIRQ};詳細は以下(優先度は小さい方が高い)。タスクレットの処理データ構造ソフト割り込みのデータ構造体はsoftirq_vec配列で以下のように定義されている。__cacheline_aligned_in_smp;上記のようにsoftirq_vecはsoftirq_action構造体の配列で、32個の要素を持っておりソフト割り込みの優先度は当該配列のインデックスに対応している。ソフト割り込みは上記の表に示した通り先頭の6個のエントリを使用する。softirq_action構造体は以下のように定義されており、actionがソフト割り込みで実行される関数、dataがその関数が使用するデータとなる。*data;};カーネル内プリエンプションとカーネル実行パスの入れ子状態の把握のためにthread_info構造体のpreemt_countメンバを使用している。supervisor_stack[0];};preemt_countは以下のようにビットで情報を保持している。PREEMPT_ACTIVEフラグコメントにも上記の表と同様の記述がある。*/マクロとして以下のように定義されている。SOFTIRQ_MASK))カーネルプリエンプションは明示的に禁止している(プリエンプションカウンタが0でない)場合、及びカーネルが割り込みコンテキストを実行している場合には禁止する必要がある。カーネルはプリエンプションを行うことができるかどうかを確認するためにpreempt_countの値を調べる。カーネルがハード割り込みコンテキストにいるのか、若しくはソフト割り込みコンテキストにいるのか、割り込みコンテキストにいるのかというのは以下のマクロを使用して判断する。ti;}カーネルがカレントプロセスのカーネルスタックを使用している場合にはthread_infoのpreempt_countを確認する。ハード割り込み/ソフト割り込み専用のスタックを使用する場合には以下のようにpreempt_countが設定されている。cpu,hardirq_ctx[cpu],softirq_ctx[cpu]);}遅延処理の禁止に用いてるマスク値はirq_cpustat_t構造体の__softirq_pendingメンバが保持している。当該構造体はCPU毎に存在する。irq_cpustat_t;__softirq_pendingメンバの取得及び設定には以下のlocal_softirq_pendingマクロを用いる。当該メンバは32bitのマスクとなっている。__softirq_pending)ソフト割り込みの初期化ソフト割り込みの初期化にはopen_softirq()を使用する。action;}引数にはソフト割り込みのインデックス、ソフト割り込み関数ポインタ、当該関数が使用するデータへのポインタを渡す。割り込み処理の起動要求はraise_softirq()を用いて行う。(0)当該関数ではまずlocal_irq_save()マクロでEFLAGSレジスタを値をスタックに退避し(pushfl)、当該レジスタのIFフラグをクリアし割り込みを禁止する。raise_softirq_irqoff()を呼び出し後、local_irq_restore()マクロでEFLAGSレジスタ値をスタックからリストアする。raise_softirq_irqoff()の定義は以下。(0)まずlocal_softirq_pending()マクロでソフト割り込みのビットマスク値を更新する。次にin_interrupt()マクロで割り込みが禁止されているかを確認する。禁止されていなければwakeup_softirqd()でローカルCPUのksoftirqdを起床させる。wake_up_process(tsk);}ソフト割り込み発生の確認カーネルは定期的にソフト割り込みが発生していないかを以下のような箇所で確認する。local_bh_enable()を呼び出し、ローカルCPUのソフト割り込み許可時。do_IRQ()がirq_exit()を呼び出し時。smp_apic_timer_interrupt()がローカルタイマ割り込み終了時。CALL_FUNCTION_VECTORプロセッサ割り込みで起動した関数の終了時。ksoftirqdが起動した時。)した場合、カーネルはdo_softirq()を呼び出してソフト割り込みを処理する。以下のように定義されている。local_irq_restore(flags);}in_interrupt()はthread_info構造体のpreempt_countメンバを参照しており1を返す(割り込みコンテキストでの呼び出しまたはソフト割り込みが禁止されている)場合、当該関数の実行を終了する。local_irq_save(flags)でEFLAGレジスタのIFフラグを退避し、ローカルCPUに対する割り込みを禁止する。local_softirq_pending()でソフト割り込みの発生を確認し必要であれば処理する。その後local_irq_restore(flags)でEFLAGレジスタのIFフラグをリストアする。__do_softirq()CPUのビットマスクを確認し対応する遅延処理を行う。複数のソフト割り込みが発生している場合は一度で複数の処理を実行するがその回数はMAX_SOFTIRQ_RESTARTで設定されている(ユーザプロセスの実行が遅れるため)。実行できなかった割り込みはksoftirqdが処理する。以下のように定義されている。__local_bh_enable();}まず最大の繰り返し回数として定義されているMAX_SOFTIRQ_RESTARTをmax_restartに代入し、local_softirq_pending()で割り込みのビットマスクを取得する。次にlocal_bh_disable()でソフト割り込みの実行を禁止する。当該関数は以下のように定義されており、preemp_count()を通してthread_indo構造体のpreempt_countを増加している。(0)上記によりdo_softirq()のin_interrupt()で1が返るようになり、ソフト割り込み処理の実行を禁止できるのがわかる。遅延処理はCPU上で順に処理していく必要があるため複数スレッドで動作することを回避しなければならない。softirq_vecでソフト割り込み用のベクタテーブルを取得する。以下の反復処理ではビットマスクに対応した関数を順次実行していく。(pending);上記の反復処理後である以下の箇所では再度割り込みの発生を確認している。最大反復回数であるmax_restartが0でなく且つ割り込みが存在すれば再度処理する。割り込みが発生しているが最大反復回数の処理が行われていた場合にはソフト割り込みを処理するためのカーネルスレッドがwakeup_softirqd()で呼ばれる。wakeup_softirqd();最後に__local_bh_enable()で割り込みを許可する。ksoftirqdソフト割り込み処理で完了しなかったソフト割り込みを処理するカーネルスレッドであり、CPU毎に当該スレッドを保持している。当該スレッドはksoftirqd()関数を実行する。:}上記のpreempt_disable()およびpreempt_enable()は以下のように定義されており、内部でpreempt_count()を使用しているのがわかる。(0)仮にksoftirqカーネルスレッドがない場合、大量の割り込みが発生した際には2つの解決策が考えられる。1つ目はdo_softirq()中に発生した割り込みを無視する方法。これは大量の通信をする際にパケットをこぼしてしまうという問題が発生する。2つ目は発生している割り込みが存在する限り常にソフト割り込み処理を実行し続けるという方法。これは大量の通信をさばくという観点は効率が良いがその間ユーザプロセスは動作しなくなってしまう。この2つの問題を両方解決するのがksoftirqdという事になる。優先度(nice値)も低く設定されているためたとえ割り込みが大量に発生した場合においてもユーザプロセスの実行を妨げるようなことにはならない。タスクレットタスクレットはソフト割り込みの種類のうち、HI_SOFTIRQ及びTASKLET_SOFTIRQから実装されている。通常のソフト割り込みはsoftirq_vec配列をイテレートし関数を実行していくのに対してタスクレットでは前述の2種類に対してのみ処理を行う。タスクレットはI/Oドライバが利用する遅延処理に適している。優先度の高いタスクレットはtasklet_hi_vecに、低いタスクレットはtasklet_vecがリストとして保持している。};タスクレットの構造体は以下のように定義されている。タスクレット関数の引数};stateの状態は以下のように定義されている。*/};初期化タスクレットを使用する際には上記の構造体を作成し、tasklet_init()にその構造体及び関数、その関数で使用するデータを渡す。data;}タスクレットの使用を禁止するにはtasklet_disable_nosync()またはtasklet_disable()を用いる。smp_mb();}上記を見てもcountメンバを使用し実装しているのがわかる。使用を許可する場合にはtasklet_enable()を使用する。atomic_dec(&t->count);}起動及び実行タスクレットの起動にはtasklet_schedule()またはtasklet_hi_schedule()を使用する。__tasklet_hi_schedule(t);}内部で呼び出されている関数__tasklet_schedule()の定義は以下。local_irq_restore(flags);}raise_softirq_irqoff()でksoftirqdをスケジュールすることがわかる。前述のksoftirqdが実行するdo_softirq()ではソフト割り込みに対応する関数を実行するため、HI_SOFTIRQではtasklet_hi_action()、TASKLET_SOFTIRQではtasklet_action()が実行される。どちらも類似した処理を行なっている。ここではtasklet_action()の定義を示す。TASKLET_STATE_RUNビットが立っている場合他のCPUで当該タスクレットが実行されている。&(t)->state);}ワークキューワークキューに存在するカーネル関数を、起動要求を行い起動したワーカスレッドというカーネルスレッドに実行させる機能である。遅延処理とは異なりワークキューはカーネルスレッドで実行されるためプロセスコンテキストで動作する、そのため実行が中断する可能性がある関数を実行することができる。データ構造ワークキュー用の主なデータ構造はworkqueue_struct。*/};workqueue_structはCPUの個数cpu_workqueue_structを保持している。____cacheline_aligned;worklistは保留中の関数をリスト形式で登録しておくメンバで保留中の関数はwork_struct構造体で管理される。遅延実行のためのソフトウェアタイマ};ワークキューの作成create_workqueue()を用いて行う。wq;}実行関数の登録work_structが保持している関数をキューに登録するにはqueue_work()を使用する。プリエンプトを回避するためスピンロックを取得。flags);}ワーカスレッドワーカスレッドは以下の関数を実行する。処理はシンプルでリストに作業が登録されていれば実行するというものになっている。0;}実際に作業を実行する関数であるrun_workqueue()は以下。リストから実際に作業を取り出し実行する。flags);}既存のワークキューカーネルはeventsというワークキューをあらかじめ用意しており、そのworkqueue_structディスクリプタはkeventd_wqが保持している。*keventd_wq;この既存のワークキューに処理を登録するにはschedule_work()を使用する。work);}定義を見ると前述のqueue_work()が用いられており第一引数に来るワークキューにkeventd_wqを指定しているのがわかる。あらかじて用意されているワークキューを用いてることで滅多に呼び出すことのない関数の実行などに対してシステム資源の消費を抑えることが可能となる。復帰処理は主に割り込みの発生によって中断した処理を再開することだが、ここにはいくつか考慮すべき点がある。復帰後の遷移対象ユーザプロセスがデバッガによってトレースされていないか上記の項目の確認にはthread_info構造体のflagsメンバの値を参照すれば良い。ret_from_exception割り込みからの復帰はret_from_intrに処理が遷移する。ret_from_intrret_from_exception及びret_from_intrこれらの処理は以下のように定義されている。割り込みから復帰時のみ割り込みを禁止する。%eaxの値が0の場合カーネルモードでの処理を続ける。eflags)処理としてはthread_info構造体を取得した後efagsレジスタとcsレジスタの値をチェックし0(割り込み元がカーネルモードでの処理)である場合にはカーネルモードの処理へと復帰し。0でない場合にはユーザモードの処理へと復帰する。以下の処理ではプリエンプションが可能でなければそのまま復帰し(後述)、必要であれば再度スケジューリングを行う。need_resched実際にスケジューリングを行うのはpreempt_schedule_irq()となる。schedule();ユーザプロセスへの復帰eflagレジスタを確認しデバッガが存在すれば必要な処理(work_pending)を実行し、そうでなければそのまま復帰する(restore_all)。~(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP))work_pendingは以下のように定義されている。work_reschedwork_pendingはスケジューリングの必要がなければwork_notifysigへと遷移する。必要があれば処理はwork_reschedへと続きschedule()を呼び出した後、必要があれば再度スケジューリングを行う。work_notifysigは以下のように定義されている。restore_alldo_notify_resume()を呼び出しシグナルの処理とシングルステップ実行を行う。»Profile低レイヤ好きのソフトウェアエンジニア。(1)はてなブログをはじめよう!k--onishiさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。