JavaScript is required to for searching.
跳过导航链接
退出打印视图
编写设备驱动程序     Oracle Solaris 10 1/13 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

第 1 部分针对 Oracle Solaris 平台设计设备驱动程序

1.  Oracle Solaris 设备驱动程序概述

2.  Oracle Solaris 内核和设备树

3.  多线程

4.  属性

5.  管理事件和排队任务

6.  驱动程序自动配置

7.  设备访问:程控 I/O

8.  中断处理程序

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

10.  映射设备和内核内存

11.  设备上下文管理

12.  电源管理

13.  强化 Oracle Solaris 驱动程序

14.  分层驱动程序接口 (Layered Driver Interface, LDI)

第 2 部分设计特定种类的设备驱动程序

15.  字符设备驱动程序

16.  块设备驱动程序

17.  SCSI 目标驱动程序

18.  SCSI 主机总线适配器驱动程序

19.  网络设备驱动程序

20.  USB 驱动程序

21.  SR-IOV 驱动程序

第 3 部分生成设备驱动程序

22.  编译、装入、打包和测试驱动程序

23.  调试、测试和调优设备驱动程序

测试驱动程序

启用 Deadman 功能以避免硬挂起

使用串行连接进行测试

针对 tip 连接设置主机系统

在 SPARC 平台上设置目标系统

在 x86 平台上设置目标系统

设置测试模块

设置内核变量

装入和卸载测试模块

设置 kmem_flags 调试标志

避免测试系统中发生数据丢失

备份关键系统文件

使用替代内核进行引导

考虑替代备份计划

捕获系统故障转储

恢复设备目录

调试工具

事后调试

使用 kmdb 内核调试器

在 SPARC 平台上使用替代内核引导 kmdb

在 x86 平台上使用替代内核引导 kmdb

kmdb 中设置断点

为驱动程序开发者提供的 kmdb

使用 mdb 模块调试器

模块调试器入门

使用 kmdbmdb 执行的有用调试任务

使用 kmdb 查找系统寄存器

检测内核内存泄漏

使用 mdb 编写调试器命令

获取内核数据结构信息

获取设备树信息

检索驱动程序软状态信息

修改内核变量

调优驱动程序

内核统计信息

内核统计信息结构成员

内核统计信息结构

内核统计信息函数

Oracle Solaris 以太网驱动程序的内核统计信息

用于动态检测过程的 DTrace

24.  推荐的编码方法

第 4 部分附录

A.  硬件概述

B.  Solaris DDI/DKI 服务汇总

C.  使设备驱动程序支持 64 位

D.  控制台帧缓存器驱动程序

E.  pci.conf 文件

索引

调试工具

本节介绍可以应用于设备驱动程序的两个调试器。《Solaris 模块调试器指南》中详细介绍了这两个调试器。

kmdbmdb 调试器的用户界面大部分是一样的。因此,许多调试方法都可在这两种工具中使用相同命令来应用。这两种调试器都支持宏、dcmd 和 dmod。dcmd(读作为 dee-command)是调试器中的例程,它可以访问当前目标程序的任何属性。dcmd 可在运行时动态装入。dmod(调试器模块的缩写)是可以装入以提供非标准行为的 dcmd 包。

mdbkmdb 都可向后兼容传统调试器(如 adbkadb)。mdb 调试器可以执行可用于 kmdb 的所有宏以及用于 adb 的任何用户定义的传统宏。有关在何处查找标准宏集的信息,请参见《Oracle Solaris 模块调试器指南》。

事后调试

事后分析为驱动程序开发者提供了许多益处。多个开发者可以并行检查一个问题。可以针对单个故障转储使用调试器的多个实例。可以脱机执行分析,以便在可能的情况下使崩溃的系统恢复运行。事后分析允许以 dmod 形式使用用户开发的调试器功能。Dmod 可为实时调试器(如 kmdb)捆绑内存密集程度过高的功能。

如果装入 kmdb 时系统出现紧急情况,则控制权会传递给调试器,以便立即进行检查。如果不适合使用 kmdb 分析当前问题,则使用 :c 继续执行并保存故障转储不失为一种好的策略。系统重新引导时,可以使用 mdb 对已保存的故障转储执行事后分析。此过程类似于从进程核心文件调试应用程序崩溃。


注 - 在 Solaris 操作系统的早期版本中,adb(1) 是推荐用于事后分析的工具。在当前的 Oracle Solaris 操作系统中,mdb(1) 是推荐用于事后分析的工具。mdb() 功能集不仅包含传统 crash (1M) 实用程序中的命令集,还具有更多功能。Oracle Solaris 操作系统中不再提供 crash 实用程序。


使用 kmdb 内核调试器

kmdb 调试器是可提供以下功能的交互式内核调试器:

本节假定您已熟悉 kmdb 调试器。本节重点介绍在设备驱动程序设计中非常有用的 kmdb 功能。要详细了解如何使用 kmdb,请参阅kmdb(1) 手册页和《Solaris 模块调试器指南》。如果您熟悉 kadb,请参阅 kadb(1M) 手册页以了解 kadbkmdb 的主要区别。

可以任意装入和卸载 kmdb 调试器。《Oracle Solaris 模块调试器指南》中包含有关装入和卸载 kmdb 的说明。为了安全和方便起见,强烈建议使用替代内核进行引导。如本节中所述,在 SPARC 平台与 x86 平台上引导过程略有不同。


注 - 缺省情况下,当 kmdb 运行时,kmdb 使用 CPU ID 作为提示符。在本章的示例中,除非另有指定,否则使用 [0] 作为提示符。


在 SPARC 平台上使用替代内核引导 kmdb

使用以下任一命令通过 kmdb 和替代内核引导 SPARC 系统:

boot kmdb -D kernel.test/sparcv9/unix 
boot kernel.test/sparcv9/unix -k

在 x86 平台上使用替代内核引导 kmdb

使用以下任一命令通过 kmdb 和替代内核引导 x86 系统:

b kmdb -D kernel.test/unix 
b kernel.test/unix -k

kmdb 中设置断点

使用 bp 命令设置断点,如以下示例中所示。

示例 23-7 在 kmdb 中设置标准断点

[0]> myModule`myBreakpointLocation::bp
        

如果尚未装入目标模块,则会显示指示这一情况的错误消息,并且不会创建断点。在这种情况下,可以使用延迟断点。装入指定的模块时,会自动激活延迟断点。通过在 bp 命令后面指定目标位置可以设置延迟断点。以下示例对延迟断点进行了说明。

示例 23-8 在 kmdb 中设置延迟断点

[0]>::bp myModule`myBreakpointLocation       

有关使用断点的更多信息,请参见《Oracle Solaris 模块调试器指南》。也可以通过键入以下任意一行来获取帮助:

> ::help bp
> ::bp dcmd

为驱动程序开发者提供的 kmdb

kmdb(1M) 调试器支持可用于显示内核数据结构的宏。可以使用 $M 来显示 kmdb 宏。宏的使用形式为:

[ address ] $<macroname

注 - 这些宏所显示的信息以及显示信息所用的格式都不构成接口。因此,该信息和格式可以随时更改。


下表中的 kmdb 宏对于设备驱动程序的开发者特别有用。为方便起见,给出了传统的宏名称(如果适用)。

表 23-1 kmdb

Dcmd
传统宏
说明
::devinfo
devinfo

devinfo_brief

devinfo.prop

列显设备节点的摘要
::walk devinfo_parents
devinfo.parent
遍历设备节点的祖先
::walk devinfo_sibling
devinfo.sibling
遍历设备节点的同级节点
::minornodes
devinfo.minor
列显与给定设备节点对应的次要节点
::major2name
列显绑定到给定设备节点的设备的名称。
::devbindings
列显绑定到给定设备节点或主设备号的设备节点。

::devinfo dcmd 显示节点状态,其值为以下所列之一:

DS_ATTACHED

驱动程序的 attach(9E) 例程成功返回。

DS_BOUND

节点已绑定到驱动程序,但尚未调用驱动程序的 probe(9E) 例程。

DS_INITIALIZED

父结点已为驱动程序指定总线地址。特定于实现的初始化已完成。此时尚未调用驱动程序的 probe(9E) 例程。

DS_LINKED

设备节点已链接至内核的设备树中,但系统尚未找到用于此节点的驱动程序。

DS_PROBED

驱动程序的 probe(9E) 例程成功返回。

DS_READY

设备已完全配置。

使用 mdb 模块调试器

mdb(1) 模块调试器可以应用于以下文件类型:

mdb 调试器可为分析内核问题提供复杂的调试支持。本节概述 mdb 功能。有关 mdb 的完整讨论,请参阅《Solaris 模块调试器指南》

尽管 mdb 可用来改变实时内核状态,但 mdb 缺少 kmdb 提供的内核执行控制。因此,kmdb 是进行运行时调试的首选调试器,而 mdb 调试器更多用于静态情况。


注 - mdb 的提示符为 >


模块调试器入门

mdb 为实现调试器模块提供了大量编程 API,从而使驱动程序开发者可以实现定制调试支持。mdb 调试器还提供了许多可用功能,如命令行编辑、命令历史记录、输出页面调度程序和联机帮助。


注 - 不应再使用 adb 宏。该功能已被 mdb 中的 dcmd 替代。


mdb 调试器提供了一组丰富的模块和 dcmd。借助这些工具,可以调试 Oracle Solaris 内核、任何关联的模块以及设备驱动程序。通过这些功能可以执行一些任务,如:

首先切换到崩溃目录,键入 mdb 并指定系统故障转储,如以下示例所示。

示例 23-9 针对故障转储调用 mdb

% cd /var/crash/testsystem
% ls
bounds     unix.0    vmcore.0
% mdb unix.0 vmcore.0
Loading modules: [ unix krtld genunix ufs_log ip usba s1394 cpc nfs ]
> ::status
debugging crash dump vmcore.0 (64-bit) from testsystem
operating system: 5.10 Generic (sun4u)
panic message: zero
dump content: kernel pages only

mdb> 提示符进行响应时,便可运行命令。

要检查实时系统中正在运行的内核,请按如下所示从系统提示符处运行 mdb

示例 23-10 针对正在运行的内核调用 mdb

# mdb -k
Loading modules: [ unix krtld genunix ufs_log ip usba s1394 ptm cpc ipc nfs ]
> ::status
debugging live kernel (64-bit) on testsystem
operating system: 5.10 Generic (sun4u)

使用 kmdbmdb 执行的有用调试任务

本节提供了有用的调试任务示例。除非特别说明,否则本节中的任务均可使用 mdbkmdb 来执行。本节假定您已了解 kmdbmdb 的基本使用知识。请注意,此处提供的信息取决于所使用系统的类型。这些示例是使用运行 64 位内核的 Sun Blade 100 工作站生成的。


注意

注意 - 由于修改内核结构中的数据会导致无法恢复的数据损毁,因此务必要格外谨慎。请勿修改或依赖于不属于 Oracle Solaris DDI 结构中的数据。有关属于 Oracle Solaris DDI 的结构的信息,请参见 Intro(9S) 手册页。


使用 kmdb 查找系统寄存器

kmdb 调试器可按组或单独显示计算机寄存器。要按组显示所有寄存器,请按以下示例所示使用 $r

示例 23-11 使用 kmdb 读取 SPARC 处理器中的所有寄存器

[0]: $r

g0    0                                 l0      0
g1    100130a4      debug_enter         l1      edd00028
g2    10411c00      tsbmiss_area+0xe00  l2      10449c90
g3    10442000      ti_statetbl+0x1ba   l3      1b
g4    3000061a004                       l4      10474400     ecc_syndrome_tab+0x80
g5    0                                 l5      3b9aca00
g6    0                                 l6      0
g7    2a10001fd40                       l7      0
o0    0                                 i0      0
o1    c                                 i1      10449e50
o2    20                                i2      0
o3    300006b2d08                       i3      10
o4    0                                 i4      0
o5    0                                 i5      b0
sp    2a10001b451                       fp      2a10001b521
o7    1001311c      debug_enter+0x78    i7      1034bb24     zsa_xsint+0x2c4
y     0
tstate: 1604  (ccr=0x0, asi=0x0, pstate=0x16, cwp=0x4)
pstate: ag:0 ie:1 priv:1 am:0 pef:1 mm:0 tle:0 cle:0 mg:0 ig:0
winreg: cur:4 other:0 clean:7 cansave:1 canrest:5 wstate:14
tba   0x10000000
pc    edd000d8 edd000d8:        ta      %icc,%g0 + 125
npc   edd000dc edd000dc:        nop

调试器会将每个寄存器值导出到与寄存器同名的一个变量中。如果读取该变量,则返回对应寄存器的当前值。如果写入该变量,则会更改关联的计算机寄存器值。以下示例将一台 x86 计算机上 %o0 寄存器的值由 0 更改为 1。

示例 23-12 使用 kmdb 读/写 x86 计算机中的寄存器

[0]> &lt;eax=K
        c1e6e0f0
[0]> 0>eax
[0]> &lt;eax=K
        0
[0]>  c1e6e0f0>eax

如果需要检查不同处理器的寄存器,则可使用 ::cpuregs dcmd。要检查的处理器的 ID 可以作为 dcmd 的地址或 -c 选项的值来提供,如以下示例所示。

示例 23-13 检查不同处理器的寄存器

[0]> 0::cpuregs
   %cs = 0x0158            %eax = 0xc1e6e0f0 kmdbmod`kaif_dvec
   %ds = 0x0160            %ebx = 0x00000000

以下示例从 SPARC 计算机上的处理器 0 切换到处理器 3。检查了寄存器 %g3,然后将其清除。为确认新值,再次读取 %g3

示例 23-14 从指定的处理器中检索单个寄存器值

[0]> 3::switch
[3]> <g3=K
        24
[3]> 0>g3
[3]> <g3
        0

检测内核内存泄漏

::findleaks dcmd 可对内核故障转储中的内存泄漏提供强大、有效的检测。必须启用一整套内核内存调试功能,::findleaks 才会有效。有关更多信息,请参见设置 kmem_flags 调试标志。在驱动程序开发和测试期间运行 ::findleaks,以检测泄漏内存从而浪费内核资源的代码。有关 ::findleaks 的完整讨论,请参见《Solaris 模块调试器指南》中的第 9  章 "使用内核内存分配器进行调试"


注 - 泄漏内核内存的代码会使系统容易受到拒绝服务攻击。


使用 mdb 编写调试器命令

mdb 调试器提供了一个功能强大的 API,用于实现为调试驱动程序而定制的调试器功能。《Oracle Solaris 模块调试器指南》详细介绍了该编程 API。

SUNWmdbdm 软件包将 mdb 源代码示例安装在目录 /usr/demo/mdb 中。可以使用 mdb 来自动完成冗长的调试日常事务,或帮助验证驱动程序是否正常工作。还可以将 mdb 调试模块与驱动程序产品一起打包。通过打包,服务人员可在客户站点处使用这些功能。

获取内核数据结构信息

Oracle Solaris 内核在可用 kmdbmdb 检查的结构中提供数据类型信息。


注 - kmdbmdb dcmd 只能用于包含设计用于 mdb 的压缩符号调试信息的对象。此信息当前只能用于某些 Oracle Solaris 内核模块。必须安装 SUNWzlib 软件包,才能处理符号调试信息。


以下示例说明如何显示 scsi_pkt 结构中的数据。

示例 23-15 使用调试器显示内核数据结构

> 7079ceb0::print -t 'struct scsi_pkt'
{
    opaque_t pkt_ha_private = 0x7079ce20
    struct scsi_address pkt_address = {
        struct scsi_hba_tran *a_hba_tran = 0x70175e68
        ushort_t a_target = 0x6
        uchar_t a_lun = 0
        uchar_t a_sublun = 0
    }
    opaque_t pkt_private = 0x708db4d0
    int (*)() *pkt_comp = sd_intr
    uint_t pkt_flags = 0
    int pkt_time = 0x78
    uchar_t *pkt_scbp = 0x7079ce74
    uchar_t *pkt_cdbp = 0x7079ce64
    ssize_t pkt_resid = 0
    uint_t pkt_state = 0x37
    uint_t pkt_statistics = 0
    uchar_t pkt_reason = 0
}

数据结构的大小在调试中很有用。使用 ::sizeof dcmd 可获取结构的大小,如以下示例所示。

示例 23-16 显示内核数据结构的大小

> ::sizeof struct scsi_pkt
sizeof (struct scsi_pkt) = 0x58

结构中特定成员的地址在调试中也很有用。有几种方法可用来确定成员的地址。

使用 ::offsetof dcmd 可以获取结构中给定成员的偏移,如以下示例所示。

示例 23-17 显示内核数据结构的偏移

> ::offsetof struct scsi_pkt pkt_state
offsetof (struct pkt_state) = 0x48

使用带 -a 选项的 ::print dcmd 可以显示结构中所有成员的地址,如以下示例所示。

示例 23-18 显示内核数据结构的相对地址

> ::print -a struct scsi_pkt
{
    0 pkt_ha_private
    8 pkt_address {
    ...
    }
    18 pkt_private
    ...
}

如果结合使用 ::print-a 选项来指定地址,则会显示每个成员的绝对地址。

示例 23-19 显示内核数据结构的绝对地址

> 10000000::print -a struct scsi_pkt
{
    10000000 pkt_ha_private
    10000008 pkt_address {
    ...
    }
    10000018 pkt_private
    ...
}

使用 ::print::sizeof::offsetof dcmd,可在驱动程序与 Oracle Solaris 内核交互时调试问题。


注意

注意 - 通过此功能可访问原始内核数据结构。您可以检查任何结构,无论该结构是否显示为 DDI 的一部分。因此,应避免依赖于未显式构成 DDI 的任何数据结构。



注 - 这些 dcmd 只能用于包含设计用于 mdb 的压缩符号调试信息的对象。符号调试信息当前只能用于某些 Oracle Solaris 内核模块。必须安装 SUNWzlib(32 位)或 SUNWzlibx(64 位)解压缩软件,才能处理符号调试信息。无论是否包含 SUNWzlibSUNWzlibx 软件包,kmdb 调试器均可处理符号类型数据。


获取设备树信息

mdb 调试器提供了用于显示内核设备树的 ::prtconf dcmd。::prtconf dcmd 的输出与 prtconf(1M) 命令的输出相似。

示例 23-20 使用 ::prtconf Dcmd

> ::prtconf
300015d3e08      SUNW,Sun-Blade-100
    300015d3c28      packages (driver not attached)
        300015d3868      SUNW,builtin-drivers (driver not attached)
        300015d3688      deblocker (driver not attached)
        300015d34a8      disk-label (driver not attached)
        300015d32c8      terminal-emulator (driver not attached)
        300015d30e8      obp-tftp (driver not attached)
        300015d2f08      dropins (driver not attached)
        300015d2d28      kbd-translator (driver not attached)
        300015d2b48      ufs-file-system (driver not attached)
    300015d3a48      chosen (driver not attached)
    300015d2968      openprom (driver not attached)

可以使用宏(如 ::devinfo dcmd)来显示节点,如以下示例所示。

示例 23-21 显示单个节点的设备信息

> 300015d3e08::devinfo
300015d3e08      SUNW,Sun-Blade-100
        System properties at 0x300015abdc0:
            name='relative-addressing' type=int items=1
                value=00000001
            name='MMU_PAGEOFFSET' type=int items=1
                value=00001fff
            name='MMU_PAGESIZE' type=int items=1
                value=00002000
            name='PAGESIZE' type=int items=1
                value=00002000
        Driver properties at 0x300015abe00:
            name='pm-hardware-state' type=string items=1
                value='no-suspend-resume'

使用 ::prtconf 可以查看驱动程序在设备树中连接的位置,以及显示设备属性。还可以为 ::prtconf 指定详细 (-v) 标志,以显示每个设备节点的属性,如下所示。

示例 23-22 在详细模式下使用 ::prtconf Dcmd

> ::prtconf -v
DEVINFO          NAME
300015d3e08      SUNW,Sun-Blade-100
        System properties at 0x300015abdc0:
            name='relative-addressing' type=int items=1
                value=00000001
            name='MMU_PAGEOFFSET' type=int items=1
                value=00001fff
            name='MMU_PAGESIZE' type=int items=1
                value=00002000
            name='PAGESIZE' type=int items=1
                value=00002000
        Driver properties at 0x300015abe00:
            name='pm-hardware-state' type=string items=1
                value='no-suspend-resume'
        ...
        300015ce798      pci10b9,5229, instance #0
                Driver properties at 0x300015ab980:
                    name='target2-dcd-options' type=any items=4
                        value=00.00.00.a4
                    name='target1-dcd-options' type=any items=4
                        value=00.00.00.a2
                    name='target0-dcd-options' type=any items=4
                        value=00.00.00.a4

另一种查找驱动程序实例的方法是使用 ::devbindings dcmd。在给定驱动程序名称的情况下,该命令会显示指定驱动程序的所有实例的列表,如以下示例所示。

示例 23-23 使用 ::devbindings Dcmd 查找驱动程序实例

> ::devbindings dad
300015ce3d8      ide-disk (driver not attached)
300015c9a60      dad, instance #0
        System properties at 0x300015ab400:
            name='lun' type=int items=1
                value=00000000
            name='target' type=int items=1
                value=00000000
            name='class_prop' type=string items=1
                value='ata'
            name='type' type=string items=1
                value='ata'
            name='class' type=string items=1
                value='dada'
...
300015c9880      dad, instance #1
        System properties at 0x300015ab080:
            name='lun' type=int items=1
                value=00000000
            name='target' type=int items=1
                value=00000002
            name='class_prop' type=string items=1
                value='ata'
            name='type' type=string items=1
                value='ata'
            name='class' type=string items=1
                value='dada'

检索驱动程序软状态信息

调试驱动程序的常见问题是检索特定驱动程序实例的软状态。软状态使用 ddi_soft_state_zalloc(9F) 例程来分配。驱动程序可以通过 ddi_get_soft_state(9F) 获取软状态。软状态指针的名称是 ddi_soft_state_init (9F) 的第一个参数。根据名称,可以使用 mdb 通过 ::softstate dcmd 检索特定驱动程序实例的软状态:

> *bst_state::softstate 0x3
702b7578

在此示例中,::softstate 用来获取 bst 示例驱动程序的实例 3 的软状态。此指针引用由驱动程序使用的 bst_soft 结构,以便跟踪该实例的状态。

修改内核变量

可以使用 kmdbmdb 来修改内核变量或其他内核状态。使用 mdb 修改内核状态时要格外谨慎,因为 mdb 在进行修改前不会停止内核。使用 kmdb 可以原子方式进行成组修改,因为 kmdb 会在允许用户访问之前停止内核。mdb 调试器只能进行单个原子修改。

务必要使用正确的格式指示符来进行修改。格式可以为:

使用 ::sizeof dcmd 可以确定要修改的变量的大小。

以下示例使用值 0x80000000 覆盖 moddebug 的值。

示例 23-24 使用调试器修改内核变量

> moddebug/W 0x80000000
    moddebug:       0 = 0x80000000