Go to main content
Oracle® Solaris 11.3 デバイスドライバの記述

印刷ビューの終了

更新: 2016 年 11 月
 
 

入出力要求の処理

このセクションでは、入出力要求の処理について詳細に説明します。

ユーザーアドレス

ユーザースレッドが 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 入出力があります。

デバイスに位置の概念がない場合、ドライバは次の手順を実行できます。

  1. uio_offset または uio_loffset を保存します。

  2. 入出力操作を実行します。

  3. 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 */
使用例 71   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 バイト書き込むドライバがあります。各バイトは、uwritec(9F) を使用して uio(9S) 構造体から取得されます。次に、そのバイトがデバイスに送信されます。read(9E) は、ureadc(9F) を使用して、デバイスから uio(9S) 構造体で記述された領域に 1 バイトを転送できます。

使用例 72  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);
}

DMA 転送 (同期)

文字ドライバは一般に、使用例 73 に示すように、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 エントリポイントを参照してください。

使用例 73   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) の戻り値は、bioerror(9F) によって設定される buf(9S) 構造体内のエラーフィールドによって決定されます。

DMA 転送 (非同期)

aread(9E)awrite(9E) をサポートする文字ドライバは、physio(9F) の代わりに aphysio(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);

注 - anocancel(9F) のアドレスは、現在 aphysio(9F) への 2 番目の引数として渡すことのできる唯一の値です。

aphysio(9F) では、ドライバは strategy(9E) ルーチンのアドレスを渡す必要があります。aphysio(9F) では、メモリー空間が確実にロックダウンされるため、データ転送の期間中、メモリー空間をページアウトすることはできません。DMA 転送ではページフォルトを処理できないため、DMA 転送にはこのロックダウンが必要です。aphysio(9F) ではまた、大きな転送を、より管理しやすい一連の小さな転送に分割するための自動化された方法も提供されます。詳細については、minphys エントリポイントを参照してください。

使用例 73使用例 74 は、aread(9E) エントリポイントと awrite(9E) エントリポイントが、read(9E) エントリポイントと write(9E) エントリポイントと比べてほんのわずかしか違わないことを示しています。その違いは主に、physio(9F) の代わりに aphysio(9F) を使用している点にあります。

使用例 74   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() エントリポイント

minphys() エントリポイントは、physio(9F) または aphysio(9F) から呼び出される関数へのポインタです。xxminphys の目的は、リクエストされた転送のサイズがドライバで決められた制限を超えていないことを確認することです。ユーザーがより大きな転送を要求した場合は、strategy(9E) が繰り返し呼び出され、一度に転送するサイズを決められた制限以下にするよう要求されます。DMA リソースが制限されているため、このアプローチは重要です。プリンタなどの低速なデバイスのドライバは、リソースを長時間占有しないように注意するべきです。

通常、ドライバはカーネル関数 minphys(9F) のアドレスを渡しますが、ドライバは代わりに独自の xxminphys() ルーチンを定義できます。xxminphys() の役割は、buf(9S) 構造体の b_bcount フィールドをドライバの上限以下に維持することです。ドライバは、ほかのシステム制限にも従うべきです。たとえば、ドライバの xxminphys() ルーチンは b_bcount フィールドを設定したあと戻る前に、システムの minphys(9F) ルーチンを呼び出します。

使用例 75  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() エントリポイント

strategy(9E) ルーチンは、ブロックドライバが元になっています。strategy 関数の名前は、ブロックデバイスへの入出力要求を効率的にキューに入れるための方針を実装することから来ています。また、文字指向のデバイスのドライバも strategy(9E) ルーチンを使用できます。ここで提供されている文字入出力モデルの場合、strategy(9E) はリクエストのキューを保持するのではなく、一度に 1 つのリクエストを処理します。

次の例では、文字指向の DMA デバイスの strategy(9E) ルーチンは、同期データ転送のための DMA リソースを割り当てます。strategy() は、デバイスレジスタをプログラミングすることによってコマンドを開始します。詳細については、ダイレクトメモリーアクセス (DMA)を参照してください。


注 - strategy(9E) は、パラメータとしてデバイス番号 (dev_t) を受け取りません。代わりに、デバイス番号は、strategy(9E) に渡された buf(9S) 構造体の b_edev フィールドから取得されます。
使用例 76  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() は、この割り込みを生成した可能性のあるデバイスの状態構造体へのポインタを受け取ります。

使用例 77  割り込みルーチン
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) を呼び出す必要があります。