编写设备驱动程序

设备驱动程序设计注意事项

从服务的使用方和提供方角度来看,设备驱动程序都必须与 Solaris OS 兼容。本节所讨论的以下问题是设计设备驱动程序过程中需要考虑的问题:

DDI/DKI 功能

为了使驱动程序具有可移植性,提供了 Solaris DDI/DKI 接口。利用 DDI/DKI,开发者可采用标准方式编写驱动程序代码,而不必担心硬件或平台差异。本节介绍 DDI/DKI 接口的各个方面。

设备 ID

利用 DDI 接口,驱动程序可以为设备提供永久、唯一的标识符。可以使用设备 ID 标识或查找设备。此 ID 与设备的名称或编号 (dev_t) 无关。应用程序可以使用 libdevid(3LIB) 中定义的函数来读取和处理由驱动程序注册的设备 ID。

设备属性

设备或设备驱动程序的特性 (attribute) 通过属性 (property) 指定。属性是一个名称/值对。名称是标识具有关联值的属性的字符串。属性可以由自标识设备的 FCode、硬件配置文件(请参见 driver.conf(4) 手册页)或驱动程序自身使用 ddi_prop_update(9F) 系列例程进行定义。

中断处理

DDI/DKI 解决了设备中断处理的以下方面的问题:

设备中断源包含在称为 interrupt 的属性中,此属性既可由自标识设备的 PROM(位于硬件配置文件中)提供,也可由 x86 平台上的引导系统提供。

回调函数

某些 DDI 机制提供回调机制。DDI 函数提供一种在满足某个条件时调度回调的机制。在以下典型的情况下,可以使用回调函数:

回调函数在某种程度上与入口点(例如,中断处理程序)类似。允许回调的 DDI 函数期望回调函数执行特定任务。如果使用 DMA 例程,则回调函数必须返回一个值,指示是否需要在出现故障时重新调度此函数。

回调函数作为单独的中断线程执行。回调必须处理所有常见的多线程问题。


注 –

驱动程序在分离设备之前必须先取消所有已调度的回调函数。


软件状态管理

为了帮助设备驱动程序编写人员分配状态结构,DDI/DKI 提供了一组称为软件状态管理例程的内存管理例程,也称为软状态例程。这些例程可动态分配、检索以及销毁指定大小的内存项,并可隐藏列表管理的详细信息。可使用实例编号来标识所需内存项。此编号通常为系统指定的实例编号。

这些例程用于实现以下任务:

有关如何使用这些例程的示例,请参见可装入驱动程序接口

程控 I/O 设备访问

程控 I/O 设备访问是指通过主机 CPU 读/写设备寄存器或设备内存的行为。Solaris DDI 提供通过内核映射设备寄存器或内存的接口,以及从驱动程序读/写设备内存的接口。使用这些接口,通过自动管理设备和主机字节存储顺序中的任何差异,以及强制执行设备所强加的任何内存存储顺序要求,可以开发与平台和总线无关的驱动程序。

直接内存访问 (Direct Memory Access, DMA)

Solaris 平台可定义与体系结构无关的高级模型,以支持具备 DMA 功能的设备。Solaris DDI 可防止驱动程序使用特定于平台的详细信息。使用此概念,通用驱动程序可以在多个平台和体系结构上运行。

分层驱动程序接口

DDI/DKI 提供一组称为分层设备接口 (layered device interfaces, LDI) 的接口。利用这些接口,可以从 Solaris 内核中访问设备。该功能使开发者可以编写查看内核设备使用情况的应用程序。例如,prtconf(1M)fuser(1M) 命令都可以使用 LDI,以便使系统管理员可以跟踪设备使用情况的各方面。第 14 章对 LDI 进行了更详细的说明。

驱动程序上下文

驱动程序上下文是指驱动程序的当前运行环境。上下文会限制驱动程序可执行的操作。驱动程序上下文取决于调用的执行代码。驱动程序代码在以下四种上下文中执行:

手册页的第 9F 节介绍了每个函数所允许的上下文。例如,在内核上下文中,驱动程序不得调用 copyin(9F)

返回错误

除列显数据损坏之类的意外错误外,设备驱动程序通常不会列显消息。相反,驱动程序入口点应返回错误代码,以便应用程序可以确定如何处理错误。可以使用 cmn_err(9F) 函数将消息写入随后会在控制台上显示的系统日志中。

由 cmn_err(9F) 解释的格式字符串说明符与 printf(3C) 格式字符串说明符类似,前者还添加了可列显位字段的格式 %b。格式字符串的第一个字符可能具有特殊意义。在对 cmn_err(9F) 的调用中,还会指定消息 level,用于指示要列显的严重性标签。有关更多详细信息,请参见 cmn_err(9F) 手册页。

级别 CE_PANIC 具有使系统崩溃的负面影响。仅当系统处于不稳定状态以至继续运行将导致更多问题时,才应使用此级别。此外,调试时可以使用此级别来获取系统核心转储。不应使用 CE_PANIC 生成设备驱动程序。

动态内存分配

必须将设备驱动程序设计为可以同时处理驱动程序声明要驱动的所有连接设备。驱动程序处理的设备数不应受到限制。必须动态分配所有每设备信息。

void *kmem_alloc(size_t size, int flag);

标准内核内存分配例程为 kmem_alloc(9F)kmem_alloc() 与 C 库例程 malloc(3C) 类似,前者添加了 flag 参数。flag 参数可以是 KM_SLEEPKM_NOSLEEP,用于指示没有所需大小的内存空间时调用者是否要阻塞。如果设置了 KM_NOSLEEP 并且内存不可用,kmem_alloc(9F) 将返回 NULL

kmem_zalloc(9F)kmem_alloc(9F) 类似,但前者还可以清除已分配内存的内容。


注 –

内核内存是有限资源,并且不可分页,它还会与用户应用程序和内核其余部分争用物理内存。分配大量内核内存的驱动程序可导致系统性能降低。


void kmem_free(void *cp, size_t size);

可使用 kmem_free(9F) 将通过 kmem_alloc(9F)kmem_zalloc(9F) 分配的内存返回到系统。kmem_free() 与 C 库例程 free(3C) 类似,但前者添加了 size 参数。驱动程序必须跟踪每个已分配对象的大小,以便在以后调用 kmem_free(9F)

热插拔

本手册没有重点介绍热插拔信息。如果按照本书中介绍的规则和建议编写设备驱动程序,则您的应用程序应该能够处理热插拔。需要特别指出的是,请确保您的驱动程序中的自动配置(请参见第 6 章)和 detach(9E) 都能正常工作。此外,如果要设计使用电源管理的驱动程序,则应遵循第 12 章中介绍的信息。SCSI HBA 驱动程序可能需要向其 dev_ops 结构中添加 cb_ops 结构(请参见第 18 章),以利用热插拔功能。

早期版本的 Solaris OS 要求可热插拔的驱动程序包括 DT_HOTPLUG 属性,但现在已不再需要该属性。不过,驱动程序编写人员可视情况自由加入和使用 DT_HOTPLUG 属性。