编写设备驱动程序

测试驱动程序

为了避免数据丢失和出现其他问题,在对新设备驱动程序进行测试时应特别注意。本节将对各种测试策略进行讨论。例如,设置可通过串行连接来控制的单独系统是对新驱动程序进行测试的最安全方法。可用不同的内核变量设置来装入测试模块,以便在不同的内核条件下测试性能。如果系统崩溃,应准备好恢复备份数据、分析任何崩溃转储并重新生成设备目录。

启用 Deadman 功能以避免硬挂起

如果系统处于硬挂起状态,则不能中断调试程序。如果启用 deadman 功能,系统将出现紧急情况,而不是无限期挂起。您随后可以使用 kmdb(1) 内核调试程序来分析您的问题。

Deadman 功能每秒检查一次系统时钟是否正在更新。如果系统时钟未在更新,则表明您处于无限期的挂起状态中。如果系统时钟在 50 秒内未更新,则 deadman 功能将导致出现紧急情况并会将您置于调试程序中。

    执行以下步骤以启用 deadman 功能:

  1. 确保正在使用 dumpadm(1M) 捕获崩溃映像。

  2. /etc/system 文件中设置 snooping 变量。有关 /etc/system 文件的信息,请参见 system(4) 手册页。

    set snooping=1
  3. 重新引导系统,以便再次读取 /etc/system 文件,从而使 snooping 设置生效。

请注意,系统中的所有区域也都会继承 deadman 设置。

如果系统在启用 deadman 功能的情况下挂起,则应在控制台上看到类似如下示例的输出:

panic[cpu1]/thread=30018dd6cc0: deadman: timed out after 9 seconds of
clock inactivity

panic: entering debugger (continue to save dump)

在调试程序内,使用 ::cpuinfo 命令调查时钟中断无法触发的原因并使系统时间提前。

使用串行连接进行测试

使用串行连接是测试驱动程序的一种好方法。使用 tip(1) 命令可在主机系统和测试系统之间建立串行连接。借助此方法,可将主机控制台上的 tip 窗口用作测试计算机的控制台。有关其他信息,请参见 tip(1) 手册页。

tip 窗口具有如下优点:


注 –

尽管调试 Solaris 设备驱动程序时不要求使用 tip 连接和另一台计算机,但仍建议使用此方法。


Procedure针对 tip 连接设置主机系统

  1. 使用主机系统和测试计算机上的串行端口 A 将两台计算机连接起来。

    必须使用空调制解调器电缆建立此连接。

  2. 在主机系统中,确保 /etc/remote 中存在对应于该连接的项。有关详细信息,请参见 remote(4) 手册页。

    终端项必须与使用的串行端口匹配。Solaris 操作系统附带对应于串行端口 B 的适当项,但必须为串行端口 A 添加一个终端项:


    debug:\
            :dv=/dev/term/a:br#9600:el=^C^S^Q^U^D:ie=%$:oe=^D:

    注 –

    波特率必须设置为 9600。


  3. 在主机上的 shell 窗口中,运行 tip(1) 并指定项的名称:


    % tip debug
    connected

    现在,shell 窗口是一个 tip 窗口,具有到测试计算机的控制台的连接。


    注意 – 注意 –

    请勿在主机上使用 STOP-A(针对 SPARC 计算机)或 F1-A(针对 x86 体系结构计算机)来停止测试计算机。此操作实际上会停止主机。要向测试计算机发送中断,请在 tip 窗口中键入 ~#。仅当诸如 ~# 命令的这些字符位于行首才能识别这些命令。如果命令无效,请按回车键或 Ctrl-U 组合键。


在 SPARC 平台上设置目标系统

一种在 SPARC 平台上快速设置测试计算机的方法是在打开计算机之前拔除键盘。然后,该计算机将自动使用串行端口 A 作为控制台。

另一种设置测试计算机的方法是使用引导 PROM 命令使串行端口 A 成为控制台。在测试计算机上,在引导 PROM ok 提示符处,将控制台 I/O 定向到串行线路。要使测试计算机始终以串行端口 A 作为控制台,请设置环境变量 input-deviceoutput-device


示例 22–1 使用引导 PROM 命令设置 input-deviceoutput-device


ok setenv input-device ttya
ok setenv output-device ttya

也可以使用 eeprom 命令使串行端口 A 成为控制台。以超级用户身份,执行以下命令使 input-deviceoutput-device 参数指向串行端口 A。以下示例对 eeprom 命令进行了说明。


示例 22–2 使用 eeprom 命令设置 input-deviceoutput-device


# eeprom input-device=ttya
# eeprom output-device=ttya

eeprom 命令会导致在以后每次系统引导时都将控制台重定向到串行端口 A。

在 x86 平台上设置目标系统

在 x86 平台上,使用 eeprom 命令可使串行端口 A 成为控制台。此过程与 SPARC 平台过程相同。请参见在 SPARC 平台上设置目标系统eeprom 命令会使控制台在重新引导期间切换到串行端口 A (COM1)。


注 –

除非 BIOS 支持控制台重定向到串行端口,否则 x86 计算机在引导过程的早期阶段之前不会将控制台控制权转交给 tip 连接。在 SPARC 计算机中,tip 连接在整个引导过程中都维护着控制台控制权。


设置测试模块

使用 /etc 目录中的 system(4) 文件可在引导时设置内核变量的值。使用内核变量,可在驱动程序中切换不同的行为,并利用内核提供的调试功能。内核变量 moddebugkmem_flags 在调试中非常有用,本节稍后将对其进行讨论。另请参见启用 Deadman 功能以避免硬挂起

由于仅在内核引导时读取 /etc/system 一次,因此引导后对内核变量所做的更改不可靠。修改此文件后,必须重新引导系统,更改才能生效。如果文件中的更改导致系统无法工作,请使用询问功能 (-a) 选项进行引导。然后,将 /dev/null 指定为系统文件。


注 –

后续发行版中不一定存在内核变量。


设置内核变量

set 命令可以更改模块变量或内核变量的值。要设置模块变量,请指定模块名称和变量:


set module_name:variable=value

例如,要在名为 myTest 的驱动程序中设置变量 test_debug,请按如下方式使用 set


% set myTest:test_debug=1

要设置由内核自身导出的变量,可忽略模块名称。

还可以使用按位 OR 运算设置值,例如:


% set moddebug | 0x80000000

装入和卸载测试模块

使用命令 modload(1M)modunload(1M)modinfo(1M) 可以添加测试模块,在对驱动程序进行调试和压力测试时,这是一种非常有用的方法。正常操作中通常不需要这些命令,因为内核会自动装入需要的模块并卸载未使用的模块。moddebug 内核变量可与这些命令一起使用,以提供信息并设置控制。

使用 modload() 函数

使用 modload(1M) 可将模块强制装入内存。modload 命令可验证装入驱动程序时该驱动程序是否具有未解析的引用。装入驱动程序并表明该驱动程序一定可以连接。驱动程序成功装入时,将调用该驱动程序的 _info(9E) 入口点,但不一定调用 attach() 入口点。

使用 modinfo() 函数

使用 modinfo(1M) 可以确认驱动程序已装入。


示例 22–3 使用 modinfo 确认已装入的驱动程序


$ modinfo
 Id Loadaddr   Size Info Rev Module Name
  6 101b6000    732   -   1  obpsym (OBP symbol callbacks)
  7 101b65bd  1acd0 226   1  rpcmod (RPC syscall)
  7 101b65bd  1acd0 226   1  rpcmod (32-bit RPC syscall)
  7 101b65bd  1acd0   1   1  rpcmod (rpc interface str mod)
  8 101ce8dd  74600   0   1  ip (IP STREAMS module)
  8 101ce8dd  74600   3   1  ip (IP STREAMS device)
...
$ modinfo | grep mydriver
169 781a8d78   13fb   0   1  mydriver (Test Driver 1.5)

info 字段中的数字是为驱动程序选择的主设备号。如果提供了模块 ID,则可使用 modunload(1M) 命令来卸载模块。模块 ID 位于 modinfo 输出的左列中。

有时,发出 modunload 后驱动程序不会按预期卸载,因为该驱动程序被确定处于忙状态。由于驱动程序确实繁忙或 detach 入口点未正确实现而导致驱动程序无法执行 detach(9E) 时,会出现上述情况。

使用 modunload()

要从内存中删除所有当前未使用的模块,请使用模块 ID 0 运行 modunload(1M):


# modunload -i 0

设置 moddebug 内核变量

moddebug 内核变量可控制模块装入过程。moddebug 的可能值包括:

0x80000000

装入或卸载模块时向控制台列显消息。

0x40000000

提供更详细的错误消息。

0x20000000

装入或卸载时列显更多详细信息,如包含地址和大小。

0x00001000

不自动卸载驱动程序。系统资源变少时,系统不尝试卸载设备驱动程序。

0x00000080

不自动卸载流。系统资源变少时,系统不尝试卸载 STREAMS 模块。

0x00000010

不自动卸载任何类型的内核模块。

0x00000001

如果与 kmdb 一起运行,moddebug 会导致执行断点,并在调用每个模块的 _init() 例程之前立即返回到 kmdb。此设置还会在执行模块的 _info()_fini() 例程时生成其他调试消息。

设置 kmem_flags 调试标志

kmem_flags 内核变量用于启用内核的内存分配器中的调试功能。将 kmem_flags 设置为 0xf 即启用分配器的调试功能。这些功能包括运行时检查,以找出以下代码条件:

《Solaris 模块调试器指南》介绍了如何使用内核内存分配器来分析此类问题。


注 –

在将 kmem_flags 设置为 0xf 的情况下进行测试和开发有助于检测潜在的内存损坏错误。由于将 kmem_flags 设置为 0xf 会更改内核内存分配器的内部行为,因此最好应不使用 kmem_flags 而执行全面测试。


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

驱动程序错误有时会导致系统无法引导。通过采取预防措施(如本节中所述),可以避免在此情况下重新安装系统。

备份关键系统文件

许多与驱动程序相关的系统文件都很难重新构造(如果可以重新构造)。如果安装期间驱动程序使系统崩溃,则诸如 /etc/name_to_major /etc/driver_aliases/etc/driver_classes/etc/minor_perm 之类的文件会损坏。请参见 add_drv(1M) 手册页。

为安全起见,请在正确配置测试计算机后生成根文件系统的副本。如果打算修改 /etc/system 文件,请在修改之前生成该文件的副本。

Procedure使用替代内核进行引导

为避免系统无法运行,应从内核副本和关联的二进制文件进行引导,而不是从缺省内核进行引导。

  1. /platform/* 中生成驱动程序的副本。


    # cp -r /platform/`uname -i`/kernel /platform/`uname -i`/kernel.test
    
  2. 将驱动程序模块放在 /platform/`uname -i`/kernel.test/drv 中。

  3. 引导替代内核,而非缺省内核。

    创建并存储替代内核后,可通过许多方法来引导此内核。

    • 可通过重新引导来引导替代内核:


      # reboot -- kernel.test/unix
      
    • 在基于 SPARC 的系统中,还可以从 PROM 进行引导:


      ok boot kernel.test/sparcv9/unix
      

      注 –

      要使用 kmdb 调试程序进行引导,请按模块调试程序入门中所述使用 -k 选项。


    • 在基于 x86 的系统中,当引导过程中显示 Select (b)oot or (i)nterpreter: 消息时,请键入以下命令:


      boot kernel.test/unix
      

示例 22–4 引导替代内核

以下示例说明如何使用替代内核进行引导。


ok boot kernel.test/sparcv9/unix
Rebooting with command: boot kernel.test/sparcv9/unix
Boot device: /sbus@1f,0/espdma@e,8400000/esp@e,8800000/sd@0,0:a File and \
    args:
kernel.test/sparcv9/unix


示例 22–5 使用 -a 选项引导替代内核

也可以使用询问功能 (-a) 选项进行引导来更改模块路径。使用此选项会生成用于配置引导方法的一系列提示。


ok boot -a
Rebooting with command: boot -a
Boot device: /sbus@1f,0/espdma@e,8400000/esp@e,8800000/sd@0,0:a File and \
args: -a
Enter filename [kernel/sparcv9/unix]: kernel.test/sparcv9/unix
Enter default directory for modules
[/platform/sun4u/kernel.test /kernel /usr/kernel]: <CR>
Name of system file [etc/system]: <CR>
SunOS Release 5.10 Version Generic 64-bit
Copyright 1983-2002 Sun Microsystems, Inc. All rights reserved.
root filesystem type [ufs]: <CR>
Enter physical name of root device
[/sbus@1f,0/espdma@e,8400000/esp@e,8800000/sd@0,0:a]: <CR>

考虑替代备份计划

如果将系统连接到网络,则可将测试计算机添加为服务器的客户机。如果出现问题,可从网络引导系统。然后,便可挂载本地磁盘,并进行任何修复。也可以直接从 Solaris 系统 CD-ROM 引导系统。

另一种从灾难中恢复的方法是获取另一个可引导的根文件系统。使用 format(1M) 创建一个大小与原始分区完全相同的分区。然后,使用 dd(1M) 复制可引导的根文件系统。创建副本后,在新文件系统上运行 fsck(1M) 以确保其完整性。

以后,如果系统无法从原始根分区进行引导,可引导备份分区。可以使用 dd(1M) 将备份分区复制到原始分区。可能会遇到这样的情况,即使根文件系统未损坏,也无法引导系统。例如,只有引导块或引导程序损坏。在这种情况下,可使用询问功能 (-a) 选项从备份分区进行引导。然后,将原始文件系统指定为根文件系统。

捕获系统崩溃转储

当系统出现紧急情况时,系统会将内核内存的映像写入转储设备。缺省情况下,该转储设备是最合适的交换设备。该转储是系统崩溃转储,它与应用程序生成的核心转储类似。在系统出现紧急情况后进行重新引导时,savecore(1M) 会检查转储设备中是否存在崩溃转储。如果找到转储,savecore 将生成名为 unix.n 的内核符号表的副本。然后,savecore 实用程序将在核心映像目录中转储名为 vmcore.n 的核心转储文件。缺省情况下,核心映像目录为 /var/crash/machine_name。如果 /var/crash 没有足够的空间用于核心转储,系统将显示所需的空间,但不实际保存转储。然后,可针对核心转储和已保存的内核使用 mdb(1)

在 Solaris 操作系统中,缺省情况下会启用崩溃转储。dumpadm(1M) 命令用于配置系统崩溃转储。使用 dumpadm 命令可以验证崩溃转储是否已启用,并确定保存核心转储文件的位置。


注 –

可以阻止 savecore 实用程序填满文件系统,即在要保存转储的目录中添加一个名为 minfree 的文件。在此文件中,指定在 savecore 运行后保持可用的千字节数。如果没有足够的可用空间,则不保存核心转储文件。


恢复设备目录

如果驱动程序在执行 attach(9E) 期间崩溃,可能会损坏 /devices/dev 目录。如果任一目录被损坏,则可引导系统并运行 fsck(1M) 以修复损坏的根文件系统,从而重新生成目录。然后,便可挂载根文件系统。通过运行 devfsadm(1M) 并在已挂载的磁盘上指定 /devices 目录可以重新创建 /devices/dev 目录。

以下示例说明如何在 SPARC 系统上修复损坏的根文件系统。在此示例中,损坏的磁盘为 /dev/dsk/c0t3d0s0,替代引导磁盘为 /dev/dsk/c0t1d0s0


示例 22–6 恢复损坏的设备目录


ok boot disk1
...
Rebooting with command: boot kernel.test/sparcv9/unix
Boot device: /sbus@1f,0/espdma@e,8400000/esp@e,8800000/sd@31,0:a File and \
    args:
kernel.test/sparcv9/unix
...
# fsck /dev/dsk/c0t3d0s0** /dev/dsk/c0t3d0s0
** Last Mounted on /
** Phase 1 - Check Blocks and Sizes
** Phase 2 - Check Pathnames
** Phase 3 - Check Connectivity
** Phase 4 - Check Reference Counts
** Phase 5 - Check Cyl groups
1478 files, 9922 used, 29261 free
     (141 frags, 3640 blocks, 0.4% fragmentation)
# mount /dev/dsk/c0t3d0s0 /mnt
# devfsadm -r /mnt


注 –

/devices/dev 目录进行修复后,就可以在系统的其他部分仍处于损坏状态时引导系统。此类修复只是在重新安装系统前进行的临时修复,作用是保存信息(如系统崩溃转储)。