ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
64 ビットに対応したデバイスドライバに対する入出力制御のサポート
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
このセクションでは、入出力要求の処理について詳細に説明します。
ユーザースレッドが write(2) システムコールを発行する場合、そのスレッドはユーザー空間内のバッファーのアドレスを渡します。
char buffer[] = "python"; count = write(fd, buffer, strlen(buffer) + 1);
システムは、iovec(9S) 構造体を割り当て、 iov_base フィールドを write(2) に渡されたアドレス (この場合は、buffer) に設定することによって、この転送を記述するための uio(9S) 構造体を構築します。この uio(9S) 構造体は、ドライバの write(9E) ルーチンに渡されます。uio(9S) 構造体の詳細については、「ベクトル化された入出力」を参照してください。
iovec(9S) 内のアドレスはカーネル空間ではなく、ユーザー空間内にあります。そのため、このアドレスは現在メモリー内に存在することも、有効なアドレスであることも保証されません。いずれの場合も、デバイスドライバまたはカーネルから直接ユーザーアドレスにアクセスすると、システムがクラッシュする可能性があります。そのため、デバイスドライバは、直接ユーザーアドレスにアクセスしてはいけません。代わりに、Oracle Solaris DDI/DKI にあるデータ転送ルーチンを使用してカーネルとの間のデータ転送を行うべきです。これらのルーチンは、ページフォルトを処理できます。DDI/DKI のルーチンは、コピーを透過的に続行するための適切なユーザーページを取得できます。あるいは、無効なアクセスに対してエラーを返すこともできます。
copyout(9F) を使用すると、カーネル空間からユーザー空間にデータをコピーできます。copyin(9F) は、ユーザー空間からカーネル空間にデータをコピーできます。ddi_copyout(9F) と ddi_copyin(9F) も同様に動作しますが、ioctl(9E) ルーチンで使用されることを想定しています。copyin(9F) と copyout(9F) は、各 iovec(9S) 構造体で記述されたバッファーに対して使用できます。または、uiomove(9F) は、隣接するドライバの領域またはデバイスメモリーとの間の転送全体を実行できます。
文字ドライバでは、転送は uio(9S) 構造体で記述されます。uio(9S) 構造体には、転送の方向とサイズに関する情報に加え、転送の一方の端のためのバッファーの配列が含まれています。もう一方の端はデバイスです。
uio(9S) 構造体には、次のメンバーが含まれています。
iovec_t *uio_iov; /* base address of the iovec */ /* buffer description array */ int uio_iovcnt; /* the number of iovec structures */ off_t uio_offset; /* 32-bit offset into file where */ /* data is transferred from or to */ offset_t uio_loffset; /* 64-bit offset into file where */ /* data is transferred from or to */ uio_seg_t uio_segflg; /* identifies the type of I/O transfer */ /* UIO_SYSSPACE: kernel <-> kernel */ /* UIO_USERSPACE: kernel <-> user */ short uio_fmode; /* file mode flags (not driver setTable) */ daddr_t uio_limit; /* 32-bit ulimit for file (maximum */ /* block offset). not driver settable. */ diskaddr_t uio_llimit; /* 64-bit ulimit for file (maximum block */ /* block offset). not driver settable. */ int uio_resid; /* amount (in bytes) not */ /* transferred on completion */
uio(9S) 構造体は、ドライバの read(9E) エントリポイントと write(9E) エントリポイントに渡されます。この構造体は、gather 書き込みおよびscatter 読み取りと呼ばれるものをサポートするために一般化されています。デバイスに書き込む場合、書き込まれるデータバッファーがアプリケーションメモリー内で隣接している必要はありません。同様に、デバイスからメモリーに転送されるデータは隣接ストリームで受信されますが、アプリケーションメモリーの不連続領域に格納できます。scatter/gather 入出力の詳細については、readv(2)、writev(2)、pread(2)、および pwrite(2) のマニュアルページを参照してください。
各バッファーは、iovec(9S) 構造体で記述されます。この構造体には、データ領域へのポインタと転送されるバイト数が含まれています。
caddr_t iov_base; /* address of buffer */ int iov_len; /* amount to transfer */
uio 構造体には、iovec(9S) 構造体の配列へのポインタが含まれています。この配列の基底アドレスは uio_iov 内に保持されており、要素の数は uio_iovcnt 内に格納されています。
uio_offset フィールドには、アプリケーションが転送を開始する必要がある、デバイスへの 32 ビットオフセットが含まれています。 uio_loffset は、64 ビットのファイルオフセットのために使用されます。デバイスがオフセットの概念をサポートしていない場合は、これらのフィールドを安全に無視できます。ドライバは、uio_offset と uio_loffset の両方ではなく、このどちらかを解釈します。ドライバが cb_ops(9S) 構造体内の D_64BIT フラグを設定した場合、そのドライバは uio_loffset を使用します。
uio_resid フィールドは、開始時は転送されるバイト数、つまり、uio_iov 内のすべての iov_len フィールドの合計に設定されます。このフィールドは、戻る前にドライバによって、転送されなかったバイト数に設定される必要があります。read(2) システムコールと write(2) システムコールは、read(9E) エントリポイントと write(9E) エントリポイントからの戻り値を使用して、失敗した転送を判定します。失敗が発生した場合、これらのルーチンは -1 を返します。戻り値が成功を示している場合、システムコールは、要求されたバイト数から uio_resid を引いた値を返します。uio_resid がドライバによって変更されない場合、read(2) と write(2) の呼び出しは 0 を返します。すべてのデータが転送されたにもかかわらず、0 の戻り値によってファイルの終わりが示されます。
サポートルーチン uiomove(9F)、physio(9F)、および aphysio(9F) は、uio(9S) 構造体を直接更新します。これらのサポートルーチンは、デバイスオフセットをデータ転送を考慮して更新します。ドライバが、位置の概念を使用するシーク可能なデバイスで使用されている場合は、uio_offset フィールドと uio_loffset フィールドのどちらも調整する必要はありません。この方法でデバイスに対して実行される入出力は、uio_offset または uio_loffset の取り得る最大値によって制約されます。このような使用法の例として、ディスク上の raw 入出力があります。
デバイスに位置の概念がない場合、ドライバは次の手順を実行できます。
uio_offset または uio_loffset を保存します。
入出力操作を実行します。
uio_offset または uio_loffset をフィールドの初期値に復元します。
この方法でデバイスに対して実行される入出力は、uio_offset または uio_loffset の取り得る最大値によって制約されません。このような使用法の例として、シリアル回線上の入出力があります。
次の例は、read(9E) 関数で uio_loffset を保持するための 1 つの方法を示しています。
static int xxread(dev_t dev, struct uio *uio_p, cred_t *cred_p) { offset_t off; /* ... */ off = uio_p->uio_loffset; /* save the offset */ /* do the transfer */ uio_p->uio_loffset = off; /* restore it */ }
データ転送は、同期または非同期のどちらかで行われます。この決定要因は、転送をスケジュールするエントリポイントがただちに戻るか、または入出力が完了するまで待つかのどちらであるかです。
read(9E) エントリポイントと write(9E) エントリポイントは、同期エントリポイントです。転送は、入出力が完了するまで復帰してはいけません。ルーチンから復帰すると、プロセスは、転送が成功したかどうかを認識できます。
aread(9E) エントリポイントと awrite(9E) エントリポイントは、非同期エントリポイントです。非同期エントリポイントは入出力をスケジュールし、ただちに戻ります。復帰すると、要求を発行したプロセスは、入出力がスケジュールされたこと、およびあとで入出力のステータスを判定する必要があることを認識できます。その間に、プロセスはほかの操作を実行できます。
カーネルへの非同期入出力要求では、その入出力が進行している間、プロセスは待機する必要がありません。プロセスは複数の入出力要求を実行することができ、カーネルでデータ転送の詳細を処理できます。非同期入出力要求を使用すると、トランザクション処理などのアプリケーションは、並行プログラミングの方法を使用してパフォーマンスや応答時間を向上させることができます。ただし、非同期入出力を使用するアプリケーションのパフォーマンス向上はすべて、プログラミングの複雑さの増加を犠牲にして得られます。
データは、プログラム式入出力または DMA のどちらかを使用して転送できます。これらのデータ転送方法は、デバイスの機能に応じて、同期エントリポイントまたは非同期エントリポイントのどちらかで使用できます。
プログラム式入出力デバイスは、データ転送を実行する CPU に依存します。プログラム式入出力データ転送は、デバイスレジスタに対するほかの読み取りおよび書き込み操作と同一です。デバイスメモリーの値の読み取りまたは格納のために、さまざまなデータアクセスルーチンが使用されます。
uiomove(9F) を使用すると、一部のプログラム式入出力デバイスにデータを転送できます。uiomove(9F) は、uio(9S) 構造体で定義されたユーザー空間とカーネルの間でデータを転送します。uiomove() はページフォルトを処理できるため、データの転送先のメモリーをロックダウンする必要はありません。uiomove() はまた、uio(9S) 構造体内の uio_resid フィールドも更新します。次の例は、RAM ディスクの read(9E) ルーチンを記述するための 1 つの方法を示しています。このルーチンは同期入出力を使用し、RAM ディスクの状態構造体内に次のフィールドが存在することを前提にしています。
caddr_t ram; /* base address of ramdisk */ int ramsize; /* size of the ramdisk */
例 15-3 uiomove(9F) を使用した RAM ディスクの read(9E) ルーチン
static int rd_read(dev_t dev, struct uio *uiop, cred_t *credp) { rd_devstate_t *rsp; rsp = ddi_get_soft_state(rd_statep, getminor(dev)); if (rsp == NULL) return (ENXIO); if (uiop->uio_offset >= rsp->ramsize) return (EINVAL); /* * uiomove takes the offset into the kernel buffer, * the data transfer count (minimum of the requested and * the remaining data), the UIO_READ flag, and a pointer * to the uio structure. */ return (uiomove(rsp->ram + uiop->uio_offset, min(uiop->uio_resid, rsp->ramsize - uiop->uio_offset), UIO_READ, uiop)); }
プログラム式入出力の別の例として、デバイスのメモリーに直接データを一度に 1 バイト書き込むドライバがあります。各バイトは、uio(9S) を使用して uwritec(9F) 構造体から取得されます。次に、そのバイトがデバイスに送信されます。read(9E) は、ureadc(9F) を使用して、デバイスから uio(9S) 構造体で記述された領域に 1 バイトを転送できます。
例 15-4 uwritec(9F) を使用したプログラム式入出力の write(9E) ルーチン
static int xxwrite(dev_t dev, struct uio *uiop, cred_t *credp) { int value; struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); /* if the device implements a power manageable component, do this: */ pm_busy_component(xsp->dip, 0); if (xsp->pm_suspended) pm_raise_power(xsp->dip, normal power); while (uiop->uio_resid > 0) { /* * do the programmed I/O access */ value = uwritec(uiop); if (value == -1) return (EFAULT); ddi_put8(xsp->data_access_handle, &xsp->regp->data, (uint8_t)value); ddi_put8(xsp->data_access_handle, &xsp->regp->csr, START_TRANSFER); /* * this device requires a ten microsecond delay * between writes */ drv_usecwait(10); } pm_idle_component(xsp->dip, 0); return (0); }
文字ドライバは一般に、例 15-5 に示すように、physio(9F) を使用して read(9E) と write(9E) で DMA 転送のための設定作業を行います。
int physio(int (*strat)(struct buf *), struct buf *bp, dev_t dev, int rw, void (*mincnt)(struct buf *), struct uio *uio);
physio(9F) では、ドライバは strategy(9E) ルーチンのアドレスを指定する必要があります。physio(9F) では、メモリー空間が確実にロックダウンされます。つまり、データ転送の期間中、メモリーをページアウトすることはできません。DMA 転送ではページフォルトを処理できないため、DMA 転送にはこのロックダウンが必要です。physio(9F) ではまた、大きな転送を、より管理しやすい一連の小さな転送に分割するための自動化された方法も提供されます。詳細については、「minphys() エントリポイント」を参照してください。
例 15-5 physio(9F) を使用した read(9E) ルーチンと write(9E) ルーチン
static int xxread(dev_t dev, struct uio *uiop, cred_t *credp) { struct xxstate *xsp; int ret; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); ret = physio(xxstrategy, NULL, dev, B_READ, xxminphys, uiop); return (ret); } static int xxwrite(dev_t dev, struct uio *uiop, cred_t *credp) { struct xxstate *xsp; int ret; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); ret = physio(xxstrategy, NULL, dev, B_WRITE, xxminphys, uiop); return (ret); }
physio(9F) の呼び出しで、xxstrategy はドライバの strategy() ルーチンへのポインタです。buf(9S) 構造体ポインタとして NULL を渡した場合は、physio(9F) に buf(9S) 構造体を割り当てるよう指示します。ドライバが physio(9F) に buf(9S) 構造体を提供する必要がある場合は、getrbuf(9F) を使用してこの構造体を割り当てるべきです。転送が正常に完了した場合、physio(9F) は 0 を返し、失敗した場合は、エラー番号を返します。strategy(9E) を呼び出したあと、physio(9F) は、転送の完了または失敗までブロックするために biowait(9F) を呼び出します。physio(9F) の戻り値は、buf(9S) によって設定される bioerror(9F) 構造体内のエラーフィールドによって決定されます。
aread(9E) と awrite(9E) をサポートする文字ドライバは、aphysio(9F) の代わりに physio(9F) を使用します。
int aphysio(int (*strat)(struct buf *), int (*cancel)(struct buf *), dev_t dev, int rw, void (*mincnt)(struct buf *), struct aio_req *aio_reqp);
aphysio(9F) では、ドライバは strategy(9E) ルーチンのアドレスを渡す必要があります。aphysio(9F) では、メモリー空間が確実にロックダウンされます。つまり、データ転送の期間中、メモリー空間をページアウトすることはできません。DMA 転送ではページフォルトを処理できないため、DMA 転送にはこのロックダウンが必要です。aphysio(9F) ではまた、大きな転送を、より管理しやすい一連の小さな転送に分割するための自動化された方法も提供されます。詳細については、「minphys() エントリポイント」を参照してください。
例 15-5 と例 15-6 は、aread(9E) エントリポイントと awrite(9E) エントリポイントが、read(9E) エントリポイントと write(9E) エントリポイントと比べてほんのわずかしか違わないことを示しています。その違いは主に、aphysio(9F) の代わりに physio(9F) を使用している点にあります。
例 15-6 aphysio(9F) を使用した aread(9E) ルーチンと awrite(9E) ルーチン
static int xxaread(dev_t dev, struct aio_req *aiop, cred_t *cred_p) { struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); return (aphysio(xxstrategy, anocancel, dev, B_READ, xxminphys, aiop)); } static int xxawrite(dev_t dev, struct aio_req *aiop, cred_t *cred_p) { struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); return (aphysio(xxstrategy, anocancel, dev, B_WRITE, xxminphys,aiop)); }
aphysio(9F) の呼び出しで、xxstrategy() はドライバの strategy ルーチンへのポインタです。aiop は、aio_req(9S) 構造体へのポインタです。aiop は、aread(9E) と awrite(9E) に渡されます。aio_req(9S) には、データをユーザー空間内のどこに格納するかが記述されます。入出力リクエストが正常にスケジュールされた場合、aphysio(9F) は 0 を返し、失敗した場合は、エラー番号を返します。strategy(9E) を呼び出したあと、aphysio(9F) は、入出力の完了または失敗を待機することなく復帰します。
minphys() エントリポイントは、physio(9F) または aphysio(9F) から呼び出される関数へのポインタです。xxminphys の目的は、リクエストされた転送のサイズがドライバで決められた制限を超えていないことを確認することです。ユーザーがより大きな転送をリクエストした場合は、strategy(9E) が繰り返し呼び出され、一度に転送するサイズを決められた制限以下にするようリクエストされます。DMA 資源が制限されているため、このアプローチは重要です。プリンタなどの低速なデバイスのドライバは、リソースを長時間占有しないように注意するべきです。
通常、ドライバはカーネル関数 minphys(9F) のアドレスを渡しますが、ドライバは代わりに独自の xxminphys() ルーチンを定義できます。xxminphys() の役割は、buf(9S) 構造体の b_bcount フィールドをドライバの制限以下に維持することです。ドライバは、ほかのシステム制限にも従うべきです。たとえば、ドライバの xxminphys() ルーチンは b_bcount フィールドを設定したあと戻る前に、システムの minphys(9F) ルーチンを呼び出します。
例 15-7 minphys(9F) ルーチン
#define XXMINVAL (512 << 10) /* 512 KB */ static void xxminphys(struct buf *bp) { if (bp->b_bcount > XXMINVAL) bp->b_bcount = XXMINVAL minphys(bp); }
strategy(9E) ルーチンは、ブロックドライバが元になっています。strategy 関数の名前は、ブロックデバイスへの入出力要求を効率的にキューに入れるための方針を実装することから来ています。また、文字指向のデバイスのドライバも strategy(9E) ルーチンを使用できます。ここで提供されている文字入出力モデルの場合、strategy(9E) はリクエストのキューを保持するのではなく、一度に 1 つのリクエストを処理します。
次の例では、文字指向の DMA デバイスの strategy(9E) ルーチンは、同期データ転送のための DMA 資源を割り当てます。strategy() は、デバイスレジスタをプログラミングすることによってコマンドを開始します。詳細については、第 9 章ダイレクトメモリーアクセス (DMA)を参照してください。
注 - strategy(9E) は、パラメータとしてデバイス番号 (dev_t) を受け取りません。代わりに、デバイス番号は、strategy(9E) に渡された buf(9S) 構造体の b_edev フィールドから取得されます。
例 15-8 strategy(9E) ルーチン
static int xxstrategy(struct buf *bp) { minor_t instance; struct xxstate *xsp; ddi_dma_cookie_t cookie; instance = getminor(bp->b_edev); xsp = ddi_get_soft_state(statep, instance); /* ... */ * If the device has power manageable components, * mark the device busy with pm_busy_components(9F), * and then ensure that the device is * powered up by calling pm_raise_power(9F). */ /* Set up DMA resources with ddi_dma_alloc_handle(9F) and * ddi_dma_buf_bind_handle(9F). */ xsp->bp = bp; /* remember bp */ /* Program DMA engine and start command */ return (0); }
注 - strategy() は int を返すように宣言されますが、strategy() は常に 0 を返す必要があります。
DMA 転送が完了すると、デバイスは割り込みを生成して、割り込みルーチンが呼び出されるようにします。次の例では、xxintr() は、この割り込みを生成した可能性のあるデバイスの状態構造体へのポインタを受け取ります。
例 15-9 割り込みルーチン
static u_int xxintr(caddr_t arg) { struct xxstate *xsp = (struct xxstate *)arg; if ( /* device did not interrupt */ ) { return (DDI_INTR_UNCLAIMED); } if ( /* error */ ) { /* error handling */ } /* Release any resources used in the transfer, such as DMA resources. * ddi_dma_unbind_handle(9F) and ddi_dma_free_handle(9F) * Notify threads that the transfer is complete. */ biodone(xsp->bp); return (DDI_INTR_CLAIMED); }
このドライバは、bioerror(9F) を呼び出すことによってエラーを示します。ドライバは、転送が完了したときか、または biodone(9F) でエラーを示したあとに bioerror(9F) を呼び出す必要があります。