块设备自动配置
第15 章• 块设备驱动程序309
示例15–1 块驱动程序attach() 例程(续)
分配状态结构并将其初始化
映射设备寄存器
添加设备驱动程序的中断处理程序
初始化所有互斥锁和条件变量
读标号信息(如果设备是磁盘)
创建电源可管理的组件
/*
* Create the device minor node. Note that the node_type
* argument is set to DDI_NT_BLOCK.
*/
if (ddi_create_minor_node(dip, "minor_name", S_IFBLK,
instance, DDI_NT_BLOCK, 0) == DDI_FAILURE) {
释放迄今为止分配的资源
/* Remove any previously allocated minor nodes */
ddi_remove_minor_node(dip, NULL);
return (DDI_FAILURE);
}
/*
* Create driver properties like "Nblocks". If the device
* is a disk, the Nblocks property is usually calculated from
* information in the disk label. Use "Nblocks" instead of
* "nblocks" to ensure the property works for large disks.
*/
块设备自动配置
310 编写设备驱动程序• 2006 年11 月
示例15–1 块驱动程序attach() 例程(续)
xsp->Nblocks = 设备的大小(以512 字节的块为单位);
maj_number = ddi_driver_major(dip);
if (ddi_prop_update_int64(makedevice(maj_number, instance), dip,
"Nblocks", xsp->Nblocks) != DDI_PROP_SUCCESS) {
cmn_err(CE_CONT, "%s: cannot create Nblocks property\n",
ddi_get_name(dip));
释放迄今为止分配的资源
return (DDI_FAILURE);
}
xsp->open = 0;
xsp->nlayered = 0;
[...]
return (DDI_SUCCESS);
case DDI_RESUME:
有关信息,请参见本书的第12 章,“电源管理”。
default:
return (DDI_FAILURE);
}
}
块设备自动配置
第15 章• 块设备驱动程序311
控制设备访问
本节介绍块设备驱动程序中open() 和close() 函数的入口点。有关open(9E) 和close(9E) 的
更多信息,请参见第14 章。
open() 入口点(块驱动程序)
open(9E) 入口点用于访问给定的设备。当用户线程对与次要设备相关的块特殊文件发出
open(2) 或mount(2) 系统调用时,或者当分层驱动程序调用open(9E) 时,均会调用块驱动程
序的open(9E) 例程。有关更多信息,请参见第308 页中的“文件I/O”。
open() 入口点会检查下列条件:
设备可以打开,即设备已联机并准备就绪。
设备可以应请求而打开。设备支持该操作。设备的当前状态与请求不冲突。
调用方具有打开设备的权限。
以下示例说明了块驱动程序open(9E) 入口点。
示例15–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
控制设备访问
312 编写设备驱动程序• 2006 年11 月
示例15–2 块驱动程序open(9E) 例程(续)
* 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 类型的每个
控制设备访问
第15 章• 块设备驱动程序313
打开,分层驱动程序都会发出类型为OTYP_LYR 的对应关闭。此示例跟踪每种类型的打开,
因此驱动程序可以确定何时设备不用于close(9E)。
close() 入口点(块驱动程序)
close(9E) 入口点使用与open(9E) 相同的参数,但有一个例外,dev 是设备编号而不是指向
设备编号的指针。
close() 例程应采用与前面所述的open(9E) 入口点相同的方式来检验otyp。在以下示例中,
close() 必须确定何时可以真正关闭设备。关闭受块打开数和分层打开数的影响。
示例15–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;
控制设备访问
314 编写设备驱动程序• 2006 年11 月
示例15–3 块设备close(9E) 例程(续)
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(9E) 来一次处理一个请求,这就是同步传输。也可以写入strategy() 来
对发送给设备的多个请求进行排队,这就是异步传输。选择方法时,应当考虑设备的能力
和限制。
将向strategy(9E) 例程传递一个指向buf(9S) 结构的指针。此结构描述传输请求,并包含有
关返回的状态信息。buf(9S) 和strategy(9E) 是块设备操作的焦点。
控制设备访问
第15 章• 块设备驱动程序315
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_forw 和av_back 驱动程序可用以管理其使用的一组缓冲区的指针。有关av_forw 和
av_back 指针的讨论,请参见第323 页中的“异步数据传输(块驱动
程序)”。
b_bcount 指定要由设备传输的字节数。
b_un.b_addr 数据缓冲区的内核虚拟地址。仅在进行bp_mapin(9F) 调用后有效。
b_blkno 设备上用于数据传输的起始32 位逻辑块编号,以DEV_BSIZE(512 字
节)为单位。驱动程序应使用b_blkno 或b_lblkno,但不能同时使用
两者。
b_lblkno 设备上用于数据传输的起始64 位逻辑块编号,以DEV_BSIZE(512 字
节)为单位。驱动程序应使用b_blkno 或b_lblkno,但不能同时使用
两者。
控制设备访问
316 编写设备驱动程序• 2006 年11 月
b_resid 由驱动程序设置的用于表明由于发生错误而未传输的字节数。有关
设置b_resid 的示例,请参见示例15–7。b_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) 将删除设备驱动程序之上的层
所依赖的任何内核映射。
控制设备访问
第15 章• 块设备驱动程序317
同步数据传输(块驱动程序)
本节介绍一种执行同步I/O 传输的简单方法。此方法假设硬件是使用DMA一次只能传输一
个数据缓冲区的简单磁盘设备。另一个假设是磁盘可以通过软件命令启动和停止。设备驱
动程序的strategy(9E) 例程等待当前请求完成,然后再接受新的请求。当传输完成时,设
备中断。如果发生错误,设备也中断。
执行块驱动程序的同步数据传输的步骤如下:
1. 检查是否有无效的buf(9S) 请求。
检查传递到strategy(9E) 的buf(9S) 结构是否有效。所有驱动程序应检查以下条件:
请求起始于有效的块。驱动程序将b_blkno 字段转换为正确的设备偏移,然后确定该
偏移对设备而言是否有效。
请求不能超出设备上的最后一个块。
满足特定于设备的要求。
如果遇到错误,驱动程序应该使用bioerror(9F) 指示相应的错误。然后,驱动程序通过
调用biodone(9F) 来完成请求。biodone() 会通知strategy(9E) 的调用方传输已完成。在
本例中,传输因错误而停止。
2. 检查此设备是否忙。
同步数据传输允许对设备进行单线程访问。设备驱动程序通过两种方式执行这种访问:
驱动程序保持由互斥锁保护的忙标志。
当设备忙时,驱动程序等待cv_wait(9F) 的条件变量。
如果设备忙,线程会一直等待,直到中断处理程序指示设备不再忙。cv_broadcast(9F)
或cv_signal(9F) 函数可以指示可用的状态。有关条件变量的详细信息,请参见
第3 章。
当设备不再忙时,strategy(9E) 例程将设备标记为可用。然后strategy() 为传输准备缓
冲区和设备。
3. 为DMA设置缓冲区。
通过使用ddi_dma_alloc_handle(9F) 为DMA传输准备数据缓冲区,以便分配DMA句
柄。使用ddi_dma_buf_bind_handle(9F) 将数据缓冲区绑定到该句柄。有关设置DMA资
源及相关数据结构的信息,请参见第9 章。
4. 开始传输。
此时,指向buf(9S) 结构的指针将保存在设备的状态结构中。然后中断例程通过调用
biodone(9F) 来完成传输。
设备驱动程序随后访问设备寄存器以启动数据传输。在大多数情况下,驱动程序应通过
使用互斥锁来保护设备寄存器免受其他线程干扰。在本例中,由于strategy(9E) 是单线
程的,因此没有必要保护设备寄存器。有关数据锁定的详细信息,请参见第3 章。
当执行线程已经启动设备的DMA引擎时,驱动程序可以将执行控制权返回到正在调用
的例程,如下所示:
同步数据传输(块驱动程序)
318 编写设备驱动程序• 2006 年11 月
static int
xxstrategy(struct buf *bp)
{
struct xxstate *xsp;
struct device_reg *regp;
minor_t instance;
ddi_dma_cookie_t cookie;
instance = getminor(bp->b_edev);
xsp = ddi_get_soft_state(statep, instance);
if (xsp == NULL) {
bioerror(bp, ENXIO);
biodone(bp);
return (0);
}
/* validate the transfer request */
if ((bp->b_blkno >= xsp->Nblocks) || (bp->b_blkno < 0)) {
bioerror(bp, EINVAL);
biodone(bp);
return (0);
}
/*
* Hold off all threads until the device is not busy.
*/
mutex_enter(&xsp->mu);
同步数据传输(块驱动程序)
第15 章• 块设备驱动程序319
while (xsp->busy) {
cv_wait(&xsp->cv, &xsp->mu);
}
xsp->busy = 1;
mutex_exit(&xsp->mu);
如果设备具有电源可管理的组件,
则使用pm_busy_components(9F) 将该设备标记为忙,
然后确保通过调用ddi_dev_is_needed(9F)
打开该设备的电源。
使用ddi_dma_alloc_handle(9F) 和ddi_dma_buf_bind_handle(9F) 设置DMA资源。
xsp->bp = bp;
regp = xsp->regp;
ddi_put32(xsp->data_access_handle, ®p->dma_addr,
cookie.dmac_address);
ddi_put32(xsp->data_access_handle, ®p->dma_size,
(uint32_t)cookie.dmac_size);
ddi_put8(xsp->data_access_handle, ®p->csr,
ENABLE_INTERRUPTS | START_TRANSFER);
return (0);
}
5. 处理中断的设备。
同步数据传输(块驱动程序)
320 编写设备驱动程序• 2006 年11 月
当设备完成数据传输时,驱动程序会产生中断,最终导致驱动程序的中断例程被调用。
注册中断时,大多数驱动程序将设备的状态结构指定为中断例程的参数。请参见
ddi_add_intr(9F) 手册页和第131 页中的“注册中断”。随后,中断例程可以访问正在
被传输的buf(9S) 结构,以及状态结构提供的任何其他信息。
中断处理程序会检查设备的状态寄存器,来确定是否在未发生任何错误的情况下完成传
输。如果发生错误,处理程序应该使用bioerror(9F) 指示相应的错误。处理程序还应该
清除设备的挂起中断,然后通过调用biodone(9F) 来完成传输。
最后一项任务是处理程序清除忙标志。处理程序随后对条件变量调用cv_signal(9F) 或
cv_broadcast(9F),发出设备不再忙的信号。此通知会使在strategy(9E) 中等待设备的
其他线程继续进行下一个数据传输。
以下示例说明了一个同步中断例程。
示例15–4块驱动程序的同步中断例程
static u_int
xxintr(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
uint8_t status;
mutex_enter(&xsp->mu);
status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
if (!(status & INTERRUPTING)) {
mutex_exit(&xsp->mu);
return (DDI_INTR_UNCLAIMED);
}
/* Get the buf responsible for this interrupt */
bp = xsp->bp;
xsp->bp = NULL;
/*
同步数据传输(块驱动程序)
第15 章• 块设备驱动程序321
示例15–4 块驱动程序的同步中断例程(续)
* This example is for a simple device which either
* succeeds or fails the data transfer, indicated in the
* command/status register.
*/
if (status & DEVICE_ERROR) {
/* failure */
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
} else {
/* success */
bp->b_resid = 0;
}
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
CLEAR_INTERRUPT);
/* The transfer has finished, successfully or not */
biodone(bp);
如果该设备具有在strategy(9F) 中标记为忙的电源可管理的组件,
则现在使用pm_idle_component(9F) 将它们标记为空闲
释放传输中使用的所有资源,如DMA资源(ddi_dma_unbind_handle(9F) 和
ddi_dma_free_handle(9F))。
/* Let the next I/O thread have access to the device */
xsp->busy = 0;
同步数据传输(块驱动程序)
322 编写设备驱动程序• 2006 年11 月
示例15–4 块驱动程序的同步中断例程(续)
cv_signal(&xsp->cv);
mutex_exit(&xsp->mu);
return (DDI_INTR_CLAIMED);
}
异步数据传输(块驱动程序)
本节介绍一种执行异步I/O 传输的方法。驱动程序将对I/O 请求进行排队,然后将控制权返
回到调用方。还是假设硬件是一次可以传输一个缓冲区的简单磁盘设备。当数据传输完成
时,设备中断。如果发生错误,也会产生中断。执行异步数据传输的基本步骤如下:
1. 检查是否有无效的buf(9S) 请求。
2. 对请求进行排队。
3. 开始第一个传输。
4. 处理中断的设备。
检查是否有无效的buf 请求
正如同步传输示例中所示,设备驱动程序会检查传递到strategy(9E) 的buf(9S) 结构是否有
效。有关更多详细信息,请参见第318 页中的“同步数据传输(块驱动程序)”。
对请求进行排队
与同步数据传输不同,驱动程序不等待异步请求完成,而是向队列中添加请求。队列的开
头可以是当前传输,也可以是保留活动请求的状态结构中的独立字段,如示例15–5 中所
示。
如果队列开始是空的,那么硬件不忙,并且strategy(9E) 在返回之前开始传输。否则,如
果在队列非空的情况下完成一个传输,则中断例程会开始一个新的传输。为方便起见,示
例15–5 仅在一个单独的例程中决定是否开始新的传输。
驱动程序可以使用buf(9S) 结构中的av_forw 和av_back 成员来管理传输请求列表。可以使
用单个指针管理单链接表,也可以同时使用两个指针建立双链接表。设备硬件规格指定哪
种类型的列表管理(如插入策略)用于优化设备的性能。传输列表是按设备提供的列表,
因此列表的头和尾都存储在状态结构中。
异步数据传输(块驱动程序)
第15 章• 块设备驱动程序323
以下示例提供了对驱动程序共享数据(如传输列表)有访问权限的多个线程。您必须标识
共享数据,并且必须用互斥锁保护这些数据。有关互斥锁的更多详细信息,请参见
第3 章。
示例15–5对块驱动程序的数据传输请求进行排队
static int
xxstrategy(struct buf *bp)
{
struct xxstate *xsp;
minor_t instance;
instance = getminor(bp->b_edev);
xsp = ddi_get_soft_state(statep, instance);
[...]
变量传输请求
[...]
将请求添加到队列的末尾。可根据设备的不同使用排序算法,
如果该算法能够改进设备性能的话(如disksort(9F))。
mutex_enter(&xsp->mu);
bp->av_forw = NULL;
if (xsp->list_head) {
/* Non-empty transfer list */
xsp->list_tail->av_forw = bp;
xsp->list_tail = bp;
} else {
/* Empty Transfer list */
xsp->list_head = bp;
异步数据传输(块驱动程序)
324 编写设备驱动程序• 2006 年11 月
示例15–5 对块驱动程序的数据传输请求进行排队(续)
xsp->list_tail = bp;
}
mutex_exit(&xsp->mu);
/* Start the transfer if possible */
(void) xxstart((caddr_t)xsp);
return (0);
}
开始第一个传输
可实现排队的设备驱动程序通常具有start() 例程。start() 可将下一个请求从队列中删
除,并开始出入设备的数据传输。在以下示例中,不管设备状态是忙还是空闲,start() 将
处理所有请求。
注– 必须写入start() 以便从任何上下文都可以对其进行调用。内核上下文中的策略例程和
中断上下文中的中断例程都可以调用start()。
每次strategy() 对请求排队时,将由strategy(9E) 调用start(),以便可以启动空闲设备。
如果设备忙,则start() 立即返回。
在处理程序从声明的中断返回之前,也可以由中断处理程序调用start(),以便可以为非空
队列提供服务。如果队列是空的,则start() 立即返回。
由于start() 是一个专用驱动程序例程,因此start() 可以采用任何参数并返回任何类型。
将写入以下代码样例用作DMA回调,尽管没有显示那一部分。相应地,此示例必须采用
caddr_t 作为参数并返回int。有关DMA回调例程的更多信息,请参见第170 页中的“处理
资源分配故障”。
示例15–6开始块驱动程序的第一个数据请求
static int
xxstart(caddr_t arg)
{
异步数据传输(块驱动程序)
第15 章• 块设备驱动程序325
示例15–6 开始块驱动程序的第一个数据请求(续)
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
mutex_enter(&xsp->mu);
/*
* If there is nothing more to do, or the device is
* busy, return.
*/
if (xsp->list_head == NULL || xsp->busy) {
mutex_exit(&xsp->mu);
return (0);
}
xsp->busy = 1;
/* Get the first buffer off the transfer list */
bp = xsp->list_head;
/* Update the head and tail pointer */
xsp->list_head = xsp->list_head->av_forw;
if (xsp->list_head == NULL)
xsp->list_tail = NULL;
bp->av_forw = NULL;
mutex_exit(&xsp->mu);
如果该设备具有电源可管理的组件,
异步数据传输(块驱动程序)
326 编写设备驱动程序• 2006 年11 月
示例15–6 开始块驱动程序的第一个数据请求(续)
则使用pm_busy_components 将该设备标记为忙,
然后确保通过调用ddi_dev_is_needed 打开该设备的电源。
使用ddi_dma_alloc_handle(9F) 和ddi_dma_buf_bind_handle(9F)
设置DMA资源。
xsp->bp = bp;
ddi_put32(xsp->data_access_handle, &xsp->regp->dma_addr,
cookie.dmac_address);
ddi_put32(xsp->data_access_handle, &xsp->regp->dma_size,
(uint32_t)cookie.dmac_size);
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
ENABLE_INTERRUPTS | START_TRANSFER);
return (0);
}
处理中断的设备
中断例程与异步版本类似,只是增加了对start() 的调用并删除了对cv_signal(9F) 的调
用。
示例15–7异步中断的块驱动程序例程
static u_int
xxintr(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
异步数据传输(块驱动程序)
第15 章• 块设备驱动程序327
示例15–7 异步中断的块驱动程序例程(续)
uint8_t status;
mutex_enter(&xsp->mu);
status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
if (!(status & INTERRUPTING)) {
mutex_exit(&xsp->mu);
return (DDI_INTR_UNCLAIMED);
}
/* Get the buf responsible for this interrupt */
bp = xsp->bp;
xsp->bp = NULL;
/*
* This example is for a simple device which either
* succeeds or fails the data transfer, indicated in the
* command/status register.
*/
if (status & DEVICE_ERROR) {
/* failure */
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
} else {
/* success */
bp->b_resid = 0;
}
异步数据传输(块驱动程序)
328 编写设备驱动程序• 2006 年11 月
示例15–7 异步中断的块驱动程序例程(续)
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
CLEAR_INTERRUPT);
/* The transfer has finished, successfully or not */
biodone(bp);
如果该设备具有在strategy(9F)(9E) 中标记为忙的电源可管理的组件,
则现在使用pm_idle_component(9F) 将它们标记为空闲
释放传输中使用的所有资源,如DMA资源
ddi_dma_unbind_handle(9F) 和
ddi_dma_free_handle(9F)
/* Let the next I/O thread have access to the device */
xsp->busy = 0;
mutex_exit(&xsp->mu);
(void) xxstart((caddr_t)xsp);
return (DDI_INTR_CLAIMED);
}
dump() 和print() 入口点
本节讨论dump(9E) 和print(9E) 入口点。
dump() 入口点(块驱动程序)
dump(9E) 入口点用于在系统发生故障时将虚拟地址空间的一部分直接复制到指定的设备。
在检查点操作期间,dump() 还用于将内核状态复制到磁盘。有关更多信息,请参见cpr(7)
和dump(9E) 手册页。由于在检查点操作期间中断被禁用,因此该入口点必须能够在不使用
中断的情况下执行此操作。
dump() 和print() 入口点
第15 章• 块设备驱动程序329
int dump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)
其中:
dev 接收转储的设备的设备编号。
addr 开始转储的基本内核虚拟地址。
blkno 开始转储的块。
nblk 转储的块的编号。
转储依赖于现有的驱动程序是否工作正常。
print() 入口点(块驱动程序)
int print(dev_t dev, char *str)
系统调用print(9E) 入口点以显示有关已检测到的异常的消息。print(9E) 会调用
cmn_err(9F) 将消息写入系统日志,该系统日志随后可以在控制台上显示。以下示例说明了
一个典型的print() 入口点。
static int
xxprint(dev_t dev, char *str)
{
cmn_err(CE_CONT, “xx: %s\n”, str);
return (0);
}
磁盘设备驱动程序
磁盘设备是一类重要的块设备驱动程序。
磁盘ioctl
Solaris 磁盘驱动程序至少需要支持一组特定于Solaris 磁盘驱动程序的ioctl 命令。在
dkio(7I) 手册页中指定了这些I/O 控制。磁盘I/O 控制用于将磁盘信息传入/传出设备驱动程
序。磁盘实用程序命令(如format(1M) 和newfs(1M))支持Solaris 磁盘设备。强制性的Sun
磁盘I/O 控制如下:
磁盘设备驱动程序
330 编写设备驱动程序• 2006 年11 月
DKIOCINFO 返回描述磁盘控制器的信息
DKIOCGAPART 返回磁盘的分区映射
DKIOCSAPART 设置磁盘的分区映射
DKIOCGGEOM 返回磁盘的几何参数
DKIOCSGEOM 设置磁盘的几何参数
DKIOCGVTOC 返回磁盘的卷目录
DKIOCSVTOC 设置磁盘的卷目录
磁盘性能
Solaris DDI/DKI 提供了优化I/O 传输以提高文件系统性能的工具。它是一种管理I/O 请求列
表以便优化文件系统磁盘访问的机制。有关对I/O 请求进行排队的说明,请参见第323 页中
的“异步数据传输(块驱动程序)”。
diskhd 结构用于管理I/O 请求链接表。
struct diskhd {
long b_flags; /* not used, needed for consistency*/
struct buf *b_forw, *b_back; /* queue of unit queues */
struct buf *av_forw, *av_back; /* queue of bufs for this unit */
long b_bcount; /* active flag */
};
diskhd 数据结构具有驱动程序可处理的两个buf 指针。av_forw 指针指向第一个活动I/O 请
求。第二个指针av_back 指向列表中的上一个活动请求。
一个指向此结构的指针以及一个指向要处理的当前buf 结构的指针作为参数传递给
disksort(9F)。disksort() 例程对buf 请求排序以优化磁盘查找。然后此例程将buf 指针插
入diskhd 列表。disksort() 程序将buf 结构的b_resid 中的值用作排序关键字。驱动程序负
责设置此值。大多数Sun 磁盘驱动程序使用柱面组作为排序关键字。此方法优化了文件系
统读前访问。
将数据添加到diskhd 列表后,设备需要传输这些数据。如果设备未忙于处理请求,则
xxstart() 例程会将第一个buf 结构拉出diskhd 列表并开始传输。
磁盘设备驱动程序
第15 章• 块设备驱动程序331
如果设备正忙,则驱动程序会从xxstrategy() 入口点返回。当硬件执行完数据传输时,便
会产生中断。随后会调用驱动程序的中断例程为设备提供服务。在提供中断服务后,驱动
程序可以调用start() 例程来处理diskhd 列表中的下一个buf 结构。
磁盘设备驱动程序
332 编写设备驱动程序• 2006 年11 月
SCSI 目标驱动程序
Solaris DDI/DKI 将SCSI 设备的软件接口分成以下两个主要部分:目标驱动程序和主机总线
适配器(host bus adapter, HBA) 驱动程序。目标驱动程序指SCSI 总线上的设备(如磁盘或磁
带机)的驱动程序。主机总线适配器驱动程序指主机上的SCSI 控制器的驱动程序。SCSA定
义了这两个组件之间的接口。本章仅讨论目标驱动程序。有关主机总线适配器驱动程序的
信息,请参见第17 章。
注– 术语“主机总线适配器”和"HBA" 等效于SCSI 规范中定义的“主机适配器”。
本章提供了有关以下主题的信息:
第333 页中的“目标驱动程序介绍”
第334 页中的“Sun 公用SCSI 体系结构概述”
第336 页中的“硬件配置文件”
第337 页中的“声明和数据结构”
第340 页中的“SCSI 目标驱动程序的自动配置”
第350 页中的“资源分配”
第352 页中的“生成和传输命令”
第362 页中的“SCSI 选项”
目标驱动程序介绍
目标驱动程序可以为字符设备驱动程序,也可以为块设备驱动程序,具体取决于设备。磁
带机的驱动程序通常为字符设备驱动程序,而磁盘则由块设备驱动程序处理。本章介绍如
何编写SCSI 目标驱动程序。本章还讨论了SCSA对SCSI 目标设备的块驱动程序和字符驱动
程序的其他要求。
以下参考文档提供了目标驱动程序和主机总线适配器驱动程序的设计者需要的补充信息。
《Small Computer System Interface 2 (SCSI-2), ANSI/NCITS X3.131-1994》,Global Engineering
Documents 发布,1998 年,ISBN 1199002488。
《The Basics of SCSI》(第四版),ANCOT Corporation 出版,1998 年,ISBN 0963743988。
16 第1 6 章
333
另请参阅硬件供应商所提供的关于目标设备的SCSI 命令规范。
Sun 公用SCSI 体系结构概述
Sun 公用SCSI 体系结构(Sun Common SCSI Architecture, SCSA) 是Solaris DDI/DKI 编程接
口,用于将SCSI 命令从目标驱动程序传输到主机总线适配器驱动程序。该接口与主机总线
适配器硬件的类型、平台、处理器体系结构以及通过该接口传输的SCSI 命令无关。
只要符合SCSA,目标驱动程序便可以将SCSI 命令传送给目标设备,而无需了解主机总线
适配器的硬件实现。
SCSA在概念上将生成SCSI 命令与通过SCSI 总线传送数据的命令传输分开。该体系结构定
义了高级软件组件与低级软件组件之间的软件接口。较高级别的软件组件由一个或多个
SCSI 目标驱动程序组成,这些驱动程序可将I/O 请求转换为适用于外围设备的SCSI 命令。
以下示例说明了SCSI 体系结构。
图16–1SCSA块图
较低级别的软件组件由SCSA接口层和一个或多个主机总线适配器驱动程序组成。目标驱动
程序负责生成执行所需功能需要的正确SCSI 命令,并负责处理结果。
常规控制流程
以下步骤介绍读取或写入请求的常规控制流程(假定没有发生传输错误)。
1. 调用目标驱动程序的read(9E) 或write(9E) 入口点。使用physio(9F) 锁定内存,准备buf
结构并调用策略例程。
Sun 公用SCSI 体系结构概述
334 编写设备驱动程序• 2006 年11 月
2. 目标驱动程序的strategy(9E) 例程将检查请求。然后,strategy() 通过使用
scsi_init_pkt(9F) 来分配scsi_pkt(9S)。目标驱动程序初始化包,并使用
scsi_setup_cdb(9F) 函数设置SCSI 命令描述符块(command descriptor block, CDB)。目标
驱动程序还指定超时。然后,该驱动程序提供一个指向回调函数的指针。完成命令后,
主机总线适配器驱动程序将调用该回调函数。buf(9S) 指针应保存在SCSI 包的目标专用
空间中。
3. 目标驱动程序使用scsi_transport(9F) 将包提交给主机总线适配器驱动程序。然后,目
标驱动程序可以自由接受其他请求。目标驱动程序不应在包的传输过程中对其进行访
问。如果主机总线适配器驱动程序或目标支持排队,则可以在传输包时提交新请求。
4. 一旦SCSI 总线空闲而且目标不繁忙,主机总线适配器驱动程序便会选择该目标并传递
CDB。目标驱动程序将执行命令。然后,目标执行所请求的数据传输。
5. 目标发送完成状态并且命令完成后,主机总线适配器驱动程序会通知目标驱动程序。要
执行通知,主机应调用SCSI 包中指定的完成函数。此时,主机总线适配器驱动程序不
再负责处理该包,同时目标驱动程序重新获得了对该包的拥有权。
6. SCSI 包的完成例程将分析返回的信息。然后,完成例程会确定SCSI 操作是否成功。如
果出现故障,目标驱动程序将再次调用scsi_transport(9F) 以重试命令。如果主机总线
适配器驱动程序不支持自动请求检测,则目标驱动程序必须在出现检查条件时,提交请
求检测包才能检索检测数据。
7. 成功完成上述操作后或者如果无法重试命令,目标驱动程序将调用
scsi_destroy_pkt(9F)。scsi_destroy_pkt() 将同步数据。然后,scsi_destroy_pkt() 释
放包。如果在释放包之前目标驱动程序需要访问数据,则调用scsi_sync_pkt(9F)。
8. 最后,目标驱动程序将通知请求应用程序读取或写入事务已完成。该通知是在执行了字
符设备的驱动程序中的read(9E) 入口点并返回后进行的。否则,会通过biodone(9F) 间
接发出通知。
SCSA允许以重叠方式和排队方式在进程的各个点执行多个此类操作。在该模型中,系统资
源由主机总线适配器驱动程序负责管理。借助软件接口并使用不同复杂程度的SCSI 总线适
配器,可以在主机总线适配器驱动程序中执行目标驱动程序函数。
SCSA函数
SCSA定义了多种函数,用于管理资源的分配和释放、控制状态的检测和设置以及SCSI 命令
的传输。下表中列出了这些函数。
表16–1 标准SCSA函数
函数名类别
scsi_abort(9F) 错误处理
scsi_alloc_consistent_buf(9F)
scsi_destroy_pkt(9F)