ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
デバイスドライバの記述 Oracle Solaris 10 8/11 Information Library (日本語) |
パート I Solaris プラットフォーム用デバイスドライバの設計
ddi_log_sysevent() を使用したイベントのロギング
21. ドライバのコンパイル、ロード、パッケージ化、およびテスト
この節では、タスクキューを使用して一部のタスクの処理を延期し、その実行を別のカーネルスレッドに委任する方法について説明します。
カーネルプログラミングの一般的な操作は、タスクがあとで別のスレッドによって実行されるようにスケジュールすることです。次の例は、タスクを別のスレッドにあとで実行する理由をいくつか列挙したものです。
現在のコードパスはタイムクリティカルである。実行する追加タスクはタイムクリティカルでない。
追加タスクで、別のスレッドが現在保持しているロックを獲得しなければならない可能性がある。
現在のコンテキストではブロックできない。追加タスクは、メモリー待機などの理由でブロックしなければならない可能性がある。
ある条件のためにコードパスが完了できない状態になっているが、現在のコードパスをスリープさせたり失敗させたりできない。条件の解消後に実行できるよう、現在のタスクをキューに入れる必要がある。
複数のタスクを並列して起動する必要がある。
これらの各ケースでは、別のコンテキスト内でタスクが実行されます。別のコンテキストとは通常、異なる一連のロックを保持し、優先順位もおそらく異なる別のカーネルスレッドのことです。タスクキューは、非同期タスクをスケジュールするための汎用カーネル API を提供します。
タスクキューとは、タスクリストを処理するためのスレッドを 1 つ以上備えたタスクリストのことです。タスクキューのサービススレッドが 1 つの場合、リストに追加された順番ですべてのタスクが実行されることが保証されます。タスクキューのサービススレッドが複数存在する場合、タスクの実行順番は不明になります。
注 - タスクキューのサービススレッドが複数存在する場合には、あるタスクの実行がほかのどのタスクの実行にも依存しないことを確認してください。タスク間に依存関係があると、デッドロックが発生する可能性があります。
次の DDI インタフェースはタスクキューを管理します。これらのインタフェースは sys/sunddi.h ヘッダーファイル内で定義されています。これらのインタフェースの詳細については、taskq(9F) のマニュアルページを参照してください。
|
ドライバでの典型的な使用方法は、attach(9E) でタスクキューを作成することです。taskq_dispatch() 呼び出しの大部分は、割り込みコンテキストから行われます。
Solaris ドライバで使用されるタスクキューについて学ぶには、http://hub.opensolaris.org/bin/view/Main/ にアクセスします。右上隅にある「Source Browser」をクリックします。検索領域の「Symbol」フィールドに ddi_taskq_create と入力します。「File Path」フィールドに amr と入力します。「Project」リストで onnv を選択します。「Search」ボタンをクリックします。検索結果に、Dell PERC 3DC/4SC/4DC/4Di RAID デバイス用の SCSI HBA ドライバ (amr.c) が表示されるはずです。
ファイル名 amr.c をクリックします。ddi_taskq_create () 関数は、amr_attach() エントリポイント内で呼び出されています。ddi_taskq_destroy() 関数は、amr_detach() エントリポイント内で呼び出されているほか、amr_attach() エントリポイントのエラー処理セクション内でも呼び出されています。ddi_taskq_dispatch() 関数は amr_done() 関数内で呼び出されていますが、この関数は amr_intr() 関数内で呼び出されています。amr_intr() 関数は割り込み処理関数であり、amr_attach() エントリポイント内で ddi_add_intr(9F) 関数の引数として渡されています。
この節では、タスクキューで消費されるシステムリソースを監視するために使用可能な 2 つの手法について説明します。タスクキューは、タスクキュースレッドによるシステム時間の使用量に関する統計情報をエクスポートします。さらにタスクキューは、DTrace SDT プローブを使用してタスクキューがタスクの実行を開始した時刻と終了した時刻を判定します。
すべてのタスクキューに一連の kstat カウンタが関連付けられます。次の kstat(1M) コマンドの出力を確認してください。
$ kstat -c taskq module: unix instance: 0 name: ata_nexus_enum_tq class: taskq crtime 53.877907833 executed 0 maxtasks 0 nactive 1 nalloc 0 priority 60 snaptime 258059.249256749 tasks 0 threads 1 totaltime 0 module: unix instance: 0 name: callout_taskq class: taskq crtime 0 executed 13956358 maxtasks 4 nactive 4 nalloc 0 priority 99 snaptime 258059.24981709 tasks 13956358 threads 2 totaltime 120247890619
上で示した kstat 出力には、次の情報が含まれています。
タスクキューの名前とそのインスタンス番号
スケジュールされたタスクの数 (tasks) と実行されたタスクの数 (executed)
タスクキューを処理するカーネルスレッドの数 (threads) とその優先順位 (priority)
すべてのタスクの処理に費やされた合計時間 (ナノ秒) (totaltime)
次の例は、kstat コマンドを使用してあるカウンタ (スケジュールされたタスクの数) が時間の経過とともに増加する様子を監視する方法について示したものです。
$ kstat -p unix:0:callout_taskq:tasks 1 5 unix:0:callout_taskq:tasks 13994642 unix:0:callout_taskq:tasks 13994711 unix:0:callout_taskq:tasks 13994784 unix:0:callout_taskq:tasks 13994855 unix:0:callout_taskq:tasks 13994926
タスクキューは便利な SDT プローブをいくつか提供します。この節で説明するプローブはすべて、次の 2 つの引数を持ちます。
ddi_taskq_create() から返されるタスクキューポインタ
taskq_ent_t 構造体へのポインタ。このポインタを D スクリプト内で使用することで関数や引数を抽出します。
これらのプローブを使用すると、個々のタスクキューやそれらを通じて実行される個々のタスクに関する高精度のタイミング情報を収集できます。たとえば、次のスクリプトは、タスクキュー経由でスケジュールされた関数を 10 秒に 1 度出力します。
# !/usr/sbin/dtrace -qs sdt:genunix::taskq-enqueue { this->tq = (taskq_t *)arg0; this->tqe = (taskq_ent_t *) arg1; @[this->tq->tq_name, this->tq->tq_instance, this->tqe->tqent_func] = count(); } tick-10s { printa ("%s(%d): %a called %@d times\n", @); trunc(@); }
特定のマシン上で上の D スクリプトを実行すると、次のような出力が生成されます。
callout_taskq(1): genunix`callout_execute called 51 times callout_taskq(0): genunix`callout_execute called 701 times kmem_taskq(0): genunix`kmem_update_timeout called 1 times kmem_taskq(0): genunix`kmem_hash_rescale called 4 times callout_taskq(1): genunix`callout_execute called 40 times USB_hid_81_pipehndl_tq_1(14): usba`hcdi_cb_thread called 256 times callout_taskq(0): genunix`callout_execute called 702 times kmem_taskq(0): genunix`kmem_update_timeout called 1 times kmem_taskq(0): genunix`kmem_hash_rescale called 4 times callout_taskq(1): genunix`callout_execute called 28 times USB_hid_81_pipehndl_tq_1(14): usba`hcdi_cb_thread called 228 times callout_taskq(0): genunix`callout_execute called 706 times callout_taskq(1): genunix`callout_execute called 24 times USB_hid_81_pipehndl_tq_1(14): usba`hcdi_cb_thread called 141 times callout_taskq(0): genunix`callout_execute called 708 times