Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1283990
  • 博文数量: 554
  • 博客积分: 10425
  • 博客等级: 上将
  • 技术积分: 7555
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-09 09:49
文章分类

全部博文(554)

文章存档

2012年(1)

2011年(1)

2009年(8)

2008年(544)

分类:

2008-04-08 16:56:23

第 1 章 设备驱动程序简介

本章概述了 Solaris 操作系统和内核。要设计和编写设备驱动程序,您需要非常熟悉 Solaris 系统的内核和 I/O 系统。

Solaris 操作系统的定义

Solaris 操作系统(Solaris OS)作为引导时运行的可执行文件来实现。Solaris OS 指的是内核。内核包含了系统运行所必需的所有例程。因为内核对于机器正常运行非常重要,所以内核运行在特殊的保护模式,我们称之为内核模式。与之对比的是用户级应用程序运行在受限制的模式,我们称之为用户模式。在用户模式下,程序无权访问内核指令或内核地址空间。设备驱动程序运行在内核模式下,不允许在用户模式下直接访问进程。

内核概述

内核管理系统资源,包括文件系统、进程和物理设备。内核为应用程序提供了系统服务,例如 I/O 管理、虚拟内存和调度。内核协调所有用户进程和系统资源的交互。内核分配优先级、服务资源请求和服务硬件中断与异常。内核调度和切换线程,将内存分页和交换进程。

内核模块与用户程序之间的差异

本节讨论内核模块与用户程序之间的一些重要差异。

内核模块与用户程序之间的执行差异

内核模块的以下特性突出说明了内核模块与用户程序在执行上的重要差异:

  • 内核模块具有独立的地址空间。模块运行在内核空间中。应用程序运行在用户空间中。系统软件受到保护,不允许用户程序访问。内核空间和用户空间有各自独立的内存地址空间。请参阅 了解更多关于地址空间的重要信息。

  • 内核模块具有更高的执行特权。运行在内核空间中的代码要比运行在用户空间中的代码具有更大的特权。由于驱动程序模块对系统的影响要远远大于对用户程序的影响,所以请认真、全面地测试驱动程序模块,以避免为系统带来负面的影响。请参阅

  • 内核模块不按顺序执行。用户程序通常按顺序执行并且从头到尾地执行单独的任务。内核模块并不按顺序执行,它注册自己是为了服务将来的请求。

  • 内核模块可以被中断。在同一时刻,可能有许多进程同时向驱动程序发出请求。中断程序可以在驱动程序正在响应系统调用时,向驱动程序发出请求。在对称多处理器(SMP)系统中,驱动程序可能在多个 CPU 上并发地执行。

  • 内核模块必须是可抢占的。您不能仅仅因为驱动程序代码不会造成阻塞,就认为驱动程序代码是安全的。应该在假设驱动程序可能会被抢占的情况下设计驱动程序。

  • 内核模块能够共享数据。一个应用程序的不同线程常常不会共享数据。与之相对应的是,组成驱动程序的数据结构和例程被所有使用驱动程序的线程所共享。驱动程序必须能够处理由多个请求导致的竞争问题。请仔细设计驱动程序数据结构以保持多个线程的独立执行。驱动程序必须在不破坏共享数据的条件下访问共享数据。请参阅 和 一书。

内核模块与用户模块之间的结构差异

内核模块的以下特性突出说明了内核模块与用户程序在结构上的重要差异:

  • 内核模块不定义主程序。内核模块,包括设备驱动程序在内,没有 main() 例程。取而代之的是,内核模块含有许多子例程和数据。设备驱动程序是构成指向输入/输出(I/O)设备软件接口的内核模块。设备驱动程序中的子例程提供了通向设备的入口点。内核使用设备编号属性来定位 open() 例程和其他正确的设备驱动程序例程。请参阅了解关于入口点的更多信息。请参阅了解关于设备编号的更多信息

  • 内核模块只连接到内核。内核模块不在用户程序连接的同一个库中进行连接。内核模块唯一可以调用的函数只是那些内核提供给外部的函数。如果驱动程序引用了未在内核中定义的符号,虽然驱动程序可以编译但是无法加载。Solaris OS 驱动程序模块应该使用规定的 DDI/DKI (设备驱动程序接口/驱动程序内核接口)接口。 当您使用了这些标准的接口,您无需重新编译驱动程序就可以升级到新的 Solaris 版本或者迁移到一个新的平台。了解更多关于 DDI 的信息,请参阅。在连接编辑期间,通过使用 -N 选项,内核模块可以依赖其他内核模块。请参阅手册页了解更多相关信息

  • 内核模块使用不同的头文件。与用户程序相比,内核模块需要不同的头文件集合。手册页为每一个函数列出了所需的头文件。请参阅 获取 DDI/DKI 函数的更多信息,参阅 获取入口点的更多信息,参阅 获取结构的更多信息。内核模块可以包含用户程序所共享的头文件,如果用户和内核接口在这种共享头文件中,则需要使用 _KERNEL 宏有条件地进行定义

  • 内核模块应该避免使用全局变量。在内核模块中避免使用全局变量要比在用户程序中避免使用全局变量更加重要。请尽可能地将符号声明为 static。如果必须使用全局符号,请为它们增加一个内核中独一无二的前缀。为模块中的私有符号使用前缀也是一个好的实践。

  • 内核模块可以针对硬件进行定制。内核模块可以将进程注册表用于特定角色。内核代码可以针对特定处理器进行优化。

  • 内核模块可以动态加载。组成设备驱动程序的子例程和数据的集合可以编译成单独的可加载目标代码模块。然后这个可加载模块可以静态或动态地与内核连接或脱离。当系统运行时,您可以直接向内核添加功能。无需重新引导系统,您就可以测试新版本的驱动程序。

内核模块与用户程序之间的数据传输差异

设备与系统间的传输速度通常低于 CPU 内部的数据传输速度。因此,驱动程序通常将正在调用的线程挂起,直到数据传输完毕。当调用驱动程序的线程挂起时,CPU 可以有时间执行其他线程。数据传输完毕时,设备会发送一个中断。驱动程序处理从该设备接收的中断。驱动程序随后通知 CPU 恢复执行刚才正在调用的线程。请参阅

驱动程序必须与用户进程(虚拟的)地址、系统(内核)地址和 I/O 总线地址协同工作。驱动程序有时将数据从一个地址空间拷贝到另一个地址空间,有时仅操纵地址映射表。请参阅

x86 和 SPARC 机器的用户和内核地址空间

在x86 机器上的 Solaris 系统,驱动程序可以直接访问用户地址空间。

在 SPARC 机器上,当内核模块试图直接访问用户地址空间时,系统会发生无法挽救的错误。必须确保驱动程序不会企图直接访问 SPARC 机器上的用户地址空间。


Caution ?警告-

x86 机器上运行的驱动程序可能无法在 SPARCA 机器上运行,因为 x86 的驱动程序可能会访问非法地址。


不要直接访问用户数据。可以使用 和 例程传送数据到用户地址空间或从用户地址空间获取数据。必须在 SPARC 机器的驱动程序中使用这两个例程来传输数据。如果在 x86 机器的驱动程序中使用这两个例程,那么可以非常轻松地将该驱动程序移植到 SPARC 机器上。 一节展示了一个使用 ddi_copyin(9F) 和 ddi_copyout(9F) 的示例驱动程序。

系统调用在进程地址空间和文件或共享的内存对象之间映射内存页面。为了响应 mmap(2) 系统调用,系统调用 devmap(9E) 入口点将设备内存映射到用户空间。然后该信息可以直接由用户应用程序访问。

设备驱动程序

设备驱动程序是管理设备与 OS 之间数据传输的可加载内核模块。可加载模块在引导时或通过请求进行加载,然后通过请求卸载内核。设备驱动程序是其他内核可以访问的 C 例程和数据结构的集合。这些例程必须使用称为入口点的标准接口。通过使用入口点,正在调用的模块保护了驱动程序的内部细节。请参阅了解更多关于入口点的信息。

设备驱动程序在自己的 dev_ops(9S) 结构中声明它的通用入口点。驱动程序为 结构中与字符或数据块相关的例程声明入口点。对于大多数驱动程序通用的一些入口点和结构,显示在了下面的图中。

图 1-1 典型的设备驱动程序入口点
Diagram shows entry points that are common to most drivers and
how the entry points are used.

Solaris OS 提供了许多驱动程序入口点。在驱动程序中,不同类型的设备需要不同的入口点。下图展示了一些可用的入口点,它们根据驱动程序类型进行了分组。没有哪个单独的设备驱动程序会使用图中展示的所有入口点。

图 1-2 不同类型的驱动程序入口点
Diagram shows subsets of entry points that are used by various
types of device drivers.

在 Solaris OS 中,驱动程序可以管理物理设备,例如磁盘设备,也可以管理软件设备(伪设备),例如总线连接设备或随机磁盘设备。对于硬件设备,设备驱动程序与管理该设备的硬件控制器进行通信。设备驱动程序使用户应用层无需接触具体设备的细节,这样应用级或系统调用就可以是通用或者设备独立的。

在以下情况中,需要访问驱动程序:

  • 系统初始化。内核在系统初始化期间调用设备驱动程序,确定那些设备可用并初始化这些设备。

  • 来自用户进程的系统调用。内核调用设备驱动程序执行该设备的 I/O 操作。例如 open(2)、read(2) 和 ioctl(2)

  • 用户级请求。内核调用设备驱动程序,服务来自命令的请求。例如 prtconf(1M)

  • 设备中断。内核调用设备驱动程序处理设备产生的中断。

  • 总线复位。当总线复位时,内核调用设备驱动程序重新初始化驱动程序、设备或者两者。总线是 CPU 到设备的路径。

下图说明了设备驱动程序是如何与系统的其他部分进行交互的。

图 1-3 典型的设备驱动程序交互
Diagram shows typical interactions between a device driver and
other elements in the operating system.

驱动程序的目录组织结构

在 Solaris OS 中,设备驱动程序和其他内核模块按以下目录组织。请参阅 和 手册页了解更多关于内核组织结构和如何将目录添加到内核模块搜索路径方面的信息。

/kernel

这些模块是绝大多数平台通用的。用来引导或者进行系统初始化的模块都属于这个目录。

/platform/`uname -i`/kernel

这些模块是专门针对可由 uname -i 命令识别的平台。

/platform/`uname -m`/kernel

这些模块是专门针对可由 uname -m 命令识别的平台。这些模块针对于硬件类,要比 uname -i 内核目录中的模块更加通用。

/usr/kernel

这些是用户模块。那些对于引导并不是必要的模块属于这个目录。本教程建议您将所有驱动程序放入 /usr/kernel 目录。

将这些驱动程序按不同目录组织的一大好处是,当您在以下例子的引导提示符中进行交互式引导时,可以有选择地加载启动时的不同组驱动程序。请参阅 手册页了解更多信息。


Type    b [file-name] [boot-flags]       to boot with options
or      i                                to enter boot interpreter
or                                       to boot with defaults

                  <<< timeout in 5 seconds >>>

Select (b)oot or (i)nterpreter: b -a
bootpath: /pci@0,0/pci8086,2545@3/pci8086,
Enter default directory for modules [/platform/i86pc/kernel /kernel 
/usr/kernel]: /platform/i86pc/kernel /kernel

这个例子中, /usr/kernel 并不存在于用来搜索加载模块的目录列表当中。如果在 /usr/kernel 中,存在一个启动或连接期间导致内核发生严重错误的驱动程序,您或许会按照例子中的方法进行处理。取代忽略所有 /usr/kernel 中模块的更好方法是,将要测试的驱动程序放到他们自己的目录中。使用 moddir 内核变量将测试目录添加到内核模块搜索路径。在 kernel(1M) 和 system(4) 中描述了 moddir 内核变量。一节中描述了另一种用于处理可能存在启动问题的驱动程序的方法。

将设备作为文件

在 UNIX 中,几乎将所有东西都作为文件对待。UNIX 用户应用程序访问设备就像访问文件一样。代表设备的文件称为特殊文件设备节点。设备特殊文件分为两类:设备和字符设备。请参阅 了解更多信息。

每个 I/O 服务开始都请求引用一个指定的文件。大多数读写数据的 I/O 操作,对普通或者特殊文件的执行都同样出色。例如,从文本编辑器创建的文件读取字节和从终端设备读取字节,都使用同一个 系统调用。

控制信号也作为文件处理。使用 函数操纵控制信号。

设备目录

Solaris OS 包含了 /dev 和 /devices 两个目录用于设备驱动程序。/dev 目录中的绝大多数驱动程序都是到 /devices 目录的链接。/dev 目录是 UNIX 的标准配置。/devices 目录专门针对 Solaris OS 存在。

根据约定,/dev 目录中的文件名称很容易读懂。例如,/dev 目录可能包含如 kdb mouse 名称的文件,它们是到 /devices/pseudo/conskbd@0:kbd 文件和 /devices/pseudo/consms@0:mouse 文件的链接。 命令显示了与 /devices 目录中文件名称非常相似的设备名称。在下面的例子中,只显示了选定的命令输出。


% prtconf -P
        conskbd, instance #0
        consms, instance #0

/dev 目录中没有链接到 /devices 目录的入口是通过 或创建的设备节点或特殊文件。它们是长度为 0 的文件,只包含一个主设备编号和次设备编号。指向 /devices 目录中设备的物理名称链接要优于使用 mknod(1M)。

Solaris 10 OS 以前,/devices 是由子目录和文件组成的磁盘文件系统。在 Solaris 10 OS 中,/devices 是一个虚拟文件系统,按照需求创建了这些子目录和特殊文件。

了解更多关于设备文件系统的信息,请参阅 devfs(7FS) 手册页。

设备树

/devices 目录中的设备文件也称为设备树

设备树展示了设备间的相互关系。在设备树中,目录代表一个节点设备。节点设备是可以作为其他设备父节点的设备。在下面的例子中,pci@1f,0 是一个节点设备。下面只显示了选定的命令输出。


# ls -l /devices
drwxr-xr-x   4 root     sys          512 date time pci@1f,0/
crw-------   1 root     sys      111,255 date time pci@1f,0:devctl

您可以使用 或 查看设备树的图示。请参阅了解关于设备树的更多信息。

字符设备和块设备

设备树中的一个文件(不是目录)代表一个字符设备或一个设备。

块设备可以包含可寻址和可复用数据。块设备的一个例子是文件系统。任何设备都可以是字符设备。大多数块设备也有字符接口。磁盘具有块和字符两个接口。在 /devices/pseudo 目录中,您可能会发现如下设备:

brw-r-----   1 root     sys       85,  0 Nov  3 09:43 md@0:0,0,blk
crw-r-----   1 root     sys       85,  0 Nov  3 09:43 md@0:0,0,raw
brw-r-----   1 root     sys       85,  1 Nov  3 09:43 md@0:0,1,blk
crw-r-----   1 root     sys       85,  1 Nov  3 09:43 md@0:0,1,raw
brw-r-----   1 root     sys       85,  2 Nov  3 09:43 md@0:0,2,blk
crw-r-----   1 root     sys       85,  2 Nov  3 09:43 md@0:0,2,raw

块设备在它们的文件模式中使用一个 b 作为第一个字符。字符设备在它们的文件模式中使用一个 c 作为第一个字符。在本例中,块设备名称中有 blk,字符设备名称中有 raw

设备是提供磁盘服务的元设备。块设备使用系统的普通缓冲机制访问磁盘。字符设备提供磁盘和用户读写缓冲区间的直接传输。

设备名称

本节展示了一个复杂的设备名称并解释了 /dev/devices 中名称的每一个部分的意义。下面的例子是磁盘分区的名称:


/dev/dsk/c0t0d0s7 -> ../../devices/pci@1c,600000/scsi@2/sd@0,0:h

首先,检查 /dev 目录中的文件名称。这些名称由 devfsadmd(1M) 守护进程来管理

c0

控制器 0

t0

目标 0。对于 SCSI 控制器,该值是磁盘的编号。

d0

SCSI LUN。该值指明这是一个目标或单独物理设备的虚拟分区。

s7

目标 0 磁盘上的 7 号分区。

对于相同的设备,可以比较 /devices 目录中的文件名称。这些名称展示了物理结构和实际设备名称。注意,/devices 目录中设备名称的一些组成部分是子目录。

pci@1c,600000

PCI 总线地址 1c,600000。这些地址只对父设备有意义。

scsi@2

SCSI 控制器地址是位于 1c,600000 地址的PCI 总线上的地址 2。这个名称对应 /dev/dsk/c0t0d0s7 中的 c0

sd@0,0

SCSI 磁盘地址是位于 2 地址的 SCSI 控制器上的地址 0,0。这个名称对应 /dev/dsk/c0t0d0s7 中的 t0sd 名称和驱动程序也可以应用到 IDE CD-ROM 设备。

sd@0,0:h

SCSI 磁盘上的次设备节点 h,地址是 0,0。这个名称对应 /dev/dsk/c0t0d0s7 中的 s7。

设备编号

设备编号用来区别设备树中的特殊设备和次要设备。许多 DDI/DKI例程需要的 dev_t 参数就是这个设备编号。

每个设备有一个主设备编号和一个次设备编号。设备编号由一对主设备编号/次设备编号组成。列出的长文件在位于通常列出文件大小的一列中展示了设备编号。在下面的例子中,设备编号是 86,255。设备主编号是 86,设备次编号是 255。


% ls -l /devices/pci@0,0:devctl
crw-------   1 root     sys       86,255 date time /devices/pci@0,0:devctl

在 Solaris OS 中,主设备编号在您安装驱动程序时被选定,这样就不会与其他主设备编号冲突。内核使用主设备编号将 I/O 请求与正确地驱动程序代码关联起来。然后内核使用这种关联,在用户读写设备文件时,决定执行那个驱动程序。所有设备和它们的主设备编号都罗列在 /etc/name_to_major 文件中。


% grep 86 /etc/name_to_major
pci 86

次设备编号在驱动程序中分配。次设备编号必须将每个设备驱动程序映射到一个具体的设备实例。次设备编号通常指子设备。例如,磁盘驱动程序可以与具有多个磁盘驱动器的硬件控制器设备通信。次设备节点没有必要使用物理表示。

下面的例子展示了 md 设备的实例 0、1 和 2。数字 0、1 和 2 是次设备编号。

brw-r-----   1 root     sys       85,  0 Nov  3 09:43 md@0:0,0,blk
crw-r-----   1 root     sys       85,  0 Nov  3 09:43 md@0:0,0,raw
brw-r-----   1 root     sys       85,  1 Nov  3 09:43 md@0:0,1,blk
crw-r-----   1 root     sys       85,  1 Nov  3 09:43 md@0:0,1,raw
brw-r-----   1 root     sys       85,  2 Nov  3 09:43 md@0:0,2,blk
crw-r-----   1 root     sys       85,  2 Nov  3 09:43 md@0:0,2,raw

sd@0,0:h 名称中,h 代表次设备节点。当驱动程序接收到一个次设备节点 h 的请求时,驱动程序实际接收的是对应的次设备编号。sd 节点的驱动程序将这个次设备编号解释为磁盘的一个具体部分,例如挂载在 /export 的分区 7。

展示了如何在驱动程序中使用 例程来获得正在使用的设备实例编号。

 

以上文章转自于 : http://developers.sun.com.cn/

阅读(983) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~