编写设备驱动程序

控制设备访问

本节介绍块设备驱动程序中 open()close() 函数的入口点。有关 open(9E)close(9E) 的更多信息,请参见第 15 章

open() 入口点(块驱动程序)

open(9E) 入口点用于访问给定的设备。当用户线程对与次要设备相关的块特殊文件发出 open(2)mount(2) 系统调用时,或者当分层驱动程序调用 open(9E) 时,均会调用块驱动程序的 open(9E) 例程。有关更多信息,请参见文件 I/O

open() 入口点会检查下列条件:

以下示例说明了块驱动程序 open(9E) 入口点。


示例 16–2 块驱动程序 open(9E) 例程

static int
xxopen(dev_t *devp, int flags, int otyp, cred_t *credp)
{
       minor_t         instance;
       struct xxstate        *xsp;

     instance = getminor(*devp);
     xsp = ddi_get_soft_state(statep, instance);
     if (xsp == NULL)
           return (ENXIO);
     mutex_enter(&xsp->mu);
     /*
    * only honor FEXCL. If a regular open or a layered open
    * is still outstanding on the device, the exclusive open
    * must fail.
    */
     if ((flags & FEXCL) && (xsp->open || xsp->nlayered)) {
       mutex_exit(&xsp->mu);
       return (EAGAIN);
     }
     switch (otyp) {
       case OTYP_LYR:
         xsp->nlayered++;
         break;
      case OTYP_BLK:
         xsp->open = 1;
         break;
     default:
         mutex_exit(&xsp->mu);
         return (EINVAL);
     }
   mutex_exit(&xsp->mu);
      return (0);
}

otyp 参数用于指定设备的打开类型。OTYP_BLK 是块设备的典型打开类型。将 otyp 设置为 OTYP_BLK 后,可以多次打开设备。最后关闭设备的 OTYP_BLK 类型时,仅调用一次 close(9E)。如果将设备用作分层设备,则 otyp 设置为 OTYP_LYR。每次打开 OTYP_LYR 类型,分层驱动程序都会发出类型为 OTYP_LYR 的对应关闭。此示例跟踪每种类型的打开,因此驱动程序可以确定何时设备不用于 close(9E)

close() 入口点(块驱动程序)

close(9E) 入口点使用与 open(9E) 相同的参数,但有一个例外。dev 是设备编号,而不是指向设备编号的指针。

close() 例程应采用与前面所述的 open(9E) 入口点所采用的相同的方式来检验 otyp。在以下示例中,close() 必须确定何时可以真正关闭设备。关闭受块打开数和分层打开数的影响。


示例 16–3 块设备 close(9E) 例程

static int
xxclose(dev_t dev, int flag, int otyp, cred_t *credp)
{
     minor_t instance;
     struct xxstate *xsp;

     instance = getminor(dev);
     xsp = ddi_get_soft_state(statep, instance);
       if (xsp == NULL)
          return (ENXIO);
     mutex_enter(&xsp->mu);
     switch (otyp) {
       case OTYP_LYR:
       xsp->nlayered--;
       break;
      case OTYP_BLK:
       xsp->open = 0;
       break;
     default:
       mutex_exit(&xsp->mu);
       return (EINVAL);
       }

     if (xsp->open || xsp->nlayered) {
       /* not done yet */
       mutex_exit(&xsp->mu);
       return (0);
     }
       /* cleanup (rewind tape, free memory, etc.) */
   /* wait for I/O to drain */
     mutex_exit(&xsp->mu);

     return (0);
}

strategy() 入口点

strategy(9E) 入口点用于从块设备读取数据缓冲区以及向块设备写入数据缓冲区。名称 strategy 指的是该入口点可以实现一些优化策略以对设备请求进行排序。

可以将 strategy(9E) 编写为一次处理一个请求,即同步传输。也可以将 strategy() 编写为对发送给设备的多个请求进行排队,即异步传输。选择方法时,应当考虑设备的能力和限制。

将向 strategy(9E) 例程传递一个指向 buf(9S) 结构的指针。此结构描述传输请求,并包含有关返回的状态信息。buf(9S)strategy(9E) 是块设备操作的焦点。

buf 结构

以下 buf 结构成员对块驱动程序很重要:

     int          b_flags;     /* Buffer Status */
     struct buf       *av_forw;    /* Driver work list link */
     struct buf       *av_back;    /* Driver work list link */
     size_t       b_bcount;    /* # of bytes to transfer */
     union {
     caddr_t      b_addr;      /* Buffer's virtual address */
     } b_un;
     daddr_t      b_blkno;     /* Block number on device */
     diskaddr_t       b_lblkno;    /* Expanded block number on device */
     size_t       b_resid;     /* # of bytes not transferred */
                       /* after error */
     int          b_error;     /* Expanded error field */
     void         *b_private;      /* “opaque” driver private area */
     dev_t        b_edev;      /* expanded dev field */

其中:

av_forwav_back

驱动程序可用以管理其使用的一组缓冲区的指针。有关 av_forwav_back 指针的讨论,请参见异步数据传输(块驱动程序)

b_bcount

指定要由设备传输的字节数。

b_un.b_addr

数据缓冲区的内核虚拟地址。仅在进行 bp_mapin(9F) 调用后有效。

b_blkno

设备上用于数据传输的起始 32 位逻辑块编号,以 DEV_BSIZE(512 字节)为单位。驱动程序应使用 b_blknob_lblkno,但不能同时使用两者。

b_lblkno

设备上用于数据传输的起始 64 位逻辑块编号,以 DEV_BSIZE(512 字节)为单位。驱动程序应使用 b_blknob_lblkno,但不能同时使用两者。

b_resid

由驱动程序设置的用于表明由于发生错误而未传输的字节数。有关设置 b_resid 的示例,请参见示例 16–7b_resid 成员会过载。此外,disksort(9F) 也会使用 b_resid

b_error

当发生传输错误时,由驱动程序设置为错误编号。b_error 应与 b_flags B_ERROR 位一起设置。有关错误值的详细信息,请参见 Intro(9E) 手册页。驱动程序应使用 bioerror(9F),而不是直接设置 b_error

b_flags

表示 buf 结构的状态属性和传输属性的标志。如果设置了 B_READ,则 buf 结构指明从设备到内存的传输。否则,此结构指明从内存到设备的传输。如果在数据传输期间驱动程序遇到错误,则该驱动程序应设置 b_flags 成员中的 B_ERROR 字段。此外,该驱动程序还应在 b_error 中提供一个更明确的错误值。驱动程序应使用 bioerror(9F),而不是设置 B_ERROR


注意 – 注意 –

驱动程序绝不能清除 b_flags


b_private

专供驱动程序存储驱动程序专用数据。

b_edev

包含用于传输的设备的设备编号。

bp_mapin 结构

可以将 buf 结构指针传递到设备驱动程序的 strategy(9E) 例程。但是,b_un.b_addr 引用的数据缓冲区不一定映射到内核地址空间中。因此,驱动程序无法直接访问数据。大多数面向块的设备具有 DMA 功能,因此不需要直接访问数据缓冲区。这些设备改为使用 DMA 映射例程以使设备的 DMA 引擎进行数据传输。有关使用 DMA 的详细信息,请参见第 9 章

如果驱动程序需要直接访问数据缓冲区,则该驱动程序必须首先使用 bp_mapin(9F) 将缓冲区映射到内核地址空间。当驱动程序不再需要直接访问数据时,应使用 bp_mapout(9F)


注意 – 注意 –

只应对已分配且由设备驱动程序拥有的缓冲区调用 bp_mapout(9F)。不得对通过 strategy(9E) 入口点传递到驱动程序的缓冲区(如文件系统)调用 bp_mapout()bp_mapin(9F) 不保留引用计数。bp_mapout(9F) 将删除设备驱动程序之上的层所依赖的任何内核映射。