




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、1/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 linux 下驱动开发方法 version 1.0 2/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 revision history versiondateoriginatordescription 1.02007-9-11yandong 3/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 目录目录 version 1.0.1 revision history.2 1 1linuxlinux 与与 windowswind
2、ows 下的驱动开发下的驱动开发 .4 1.11.1windowswindows 设备驱动程序设备驱动程序.4 wdm 驱动开发.4 wdf 驱动开发.6 1.2linux 设备驱动程序设备驱动程序.8 linux 系统中的设备 .8 linux 核心的设备驱动程序.9 2linux 驱动开发框架驱动开发框架.9 2.1字符设备和块设备字符设备和块设备.9 2.2设备驱动程序接口设备驱动程序接口.10 2.3设备驱动程序模块设备驱动程序模块.11 2.4设备驱动程序结构设备驱动程序结构.11 驱动程序的注册与注销.12 设备的打开与释放.12 设备的读写操作 .12 设备的控制操作 .12 设
3、备的中断和轮询处理.13 3linux 驱动调试驱动调试.13 3.1用打印信息调试用打印信息调试.13 printk .13 消息是如何记录的.14 使用预处理方便监视处理.15 3.2通过查询调试通过查询调试 .16 使用/proc 文件系统.16 ioctl 方法.17 3.3通过监视调试通过监视调试 .17 3.4调试系统故障调试系统故障 .18 oops 消息.18 使用 ksymoops.19 使用 oops.20 使用 klogd.20 系统挂起.21 3.5使用调试器使用调试器.23 使用 gdb .23 使用 kdebug.24 远程调试.25 4/26 ia divisio
4、n 2007-9-7 linux下驱动开发方法下驱动开发方法 1 1 linuxlinux 与与 windowswindows 下的驱动开发下的驱动开发 1.11.1 windowswindows设备驱动程序设备驱动程序 wdm 驱动开发驱动开发 由于需要支持新的业务和新的 pc 外部设备类型对驱动程序开发造成了新的挑战。新型总线 增加了设备的数量和对设备驱动程序的需求。设备上各种功能的不断增加使驱动程序的开发 变得越来越复杂。同时,快速反应的交互式应用程序要求将软件和硬件紧密的结合在一起。 1997 年,在用于 windows 95 和 windows nt 的统一的 win32 驱动程序模
5、型(wdm)有了进 一步的发展,将这些因素全部考虑在内。wdm 允许使用一个单一的驱动程序源(x86 二进制) 来同时在 windows 95 和 windows nt 中实现对新的总线和新设备的支持。 wdm 的关键目标是通过提供一种灵活的方式来简化驱动程序的开发,使在实现对新硬件 支持的基础上减少并降低所必须开发的驱动程序的数量和复杂性。wdm 还必须为即插即用和 设备的电源管理提供一个通用的框架结构。wdm 是实现对新型设备的简便支持和方便使用的 关键组件。 为了实现这些目标,wdm 只能以 windows nt i/o 子系统提供的一组通用服务为基础。 wdm 改进了由一组核心扩展构成
6、的功能实现对即插即用、设备电源管理、和快速反应 i/o 流 的支持。除了通用的平台服务和扩展外,wdm 还实现了一个模块化的、分层次类型的微型驱 动程序结构。类型驱动程序实现了支持通用总线、协议、或设备类所需的功能性接口。类驱 动程序的一般特性是为逻辑设备的命令设置、协议、和代码重用所需的总线接口实现标准化 提供必要的条件。wdm 对标准类接口的支持减少了 windows 95 和 windows nt 所需的设备驱 动程序的数量和复杂性。 5/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 windows 驱动程序既可以运行在用户态也可以运行在核心模态
7、。 用户态的驱动程序运行在非特权处理机模式(nonprivileged processor mode)上,其 他一些被保护的子系统代码也运行在该模式上。用户态的驱动程序不能获得系统数据的 存取权,除非调用 win32 api 或者系统服务。 核心态驱动程序作为操作系统的一个组成部分被执行支持一个或多个受保护的子系 统的操作系统底层组件。 用户态和核心态驱动程序有不同的结构,不同的入口点和不同的系统接口。一个设备是 需要一个用户态驱动程序还是需要一个核心态驱动程序依赖于该设备的类型和操作系统对它 提供的支持。 一些设备驱动程序可以完全地或部分地运行在用户态。用户态驱动程序没有堆栈空间的 限制,可
8、以访问 win32 api,并且容易调试。 大多设备驱动程序运行在核心态。核心态驱动程序可以完成某些受保护的操作,并且可 以访问用户态驱动程序不能访问的系统结构体(system sturcture)。然而,访问权限的提 高当然也要付出相应的代价调试的艰难,系统随时面临毁坏的危险。当代码运行在有特 权的核心态环境中时,操作系统对代码所请求的数据的完整性和有效性的检查将大大减少。 为了方便,应该用高级语言(high-level language)来编写驱动程序,通常,c 适合 用来编写核心态驱动程序,c 或 c+则适合用于编写用户态驱动程序。 6/26 ia division 2007-9-7 l
9、inux下驱动开发方法下驱动开发方法 wdf 驱动开发驱动开发 microsoft windows driver foundation (wdf) 是 microsoft 的下一代驱动程序开发模 型。wdf 包含一整组支持核心模式与用户模式驱动程序的开发、部署与维护的组件。就如图 表 1 所显示的,wdf 组件与现有的驱动程序开发工具一同使用于整个驱动程序的生命周期: 图图表表 1 1. . w wi in nd do ow ws s d dr ri iv ve er r f fo ou un nd da at ti io on n 与与驱驱动动程程序序之之生生命命周周期期 驱动程序模型驱动程
10、序模型 wdf 驱动程序模型支持面向对象、事件驱动的驱动程序开发。通过使用 wdf,驱动程序 撰写人员可以将精神专注在他们的硬件设备,而非操作系统。wdf 驱动程序可撰写为核心模 式或是用户模式。 架构与架构与 windowswindows driverdriver kitkit (wdk)(wdk) wdf 定义一个单一的驱动程序模型,而其中包括核心模式与用户模型驱动程序两者的开 发架构。此架构提供支持 wdf 模型的基本结构。它们建置了通同的功能、提供智能型的默 认值以及管理大部份与操作系统的交互动作。 核心模式驱动程序架构 (kmdf) 建置了 windows 所要求的核心模式驱动程序需
11、支持 的基本功能,而且通用于所有的核心模式驱动程序。 用户模式驱动程序架构 (umdf) 所提供的函数支持少于 kmdf,但是某些设备之驱动 程序允许其驱动程序执行于用户模式替代核心模式。 7/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 所有的 wdf 驱动程序都是使用 wdk 建置环境来建置。 追踪与静态分析工具追踪与静态分析工具 kmdf 与 umdf 两者皆建置检核码并支持透过 event tracing for windows (etw) 的整 合追踪。产生的追踪结果可于开发期间协助 debugging 驱动程序,并可在驱动程序发布使 用后协
12、助排解疑难问题。wdf 驱动程序亦可与现有的驱动程序检核工具一同作业。此外,编 译期间 (compile-time) 驱动程序检核工具,像是 prefast 与 static driver verifier (sdv),皆为 wdf 的一部份。 驱动程序签章驱动程序签章 wdf 驱动程序取得签章的方式与 windows driver model (wdm) 驱动程序相同。 驱动程序安装工具驱动程序安装工具 wdf 驱动程序可使用 inf 档案安装亦且可使用现有的驱动程序安装工具,包括 driver install frameworks (difx) 工具。 版本设定版本设定 因为 wdf 支持
13、版本设定,所以单一驱动程序 binary 可执行于所有版本的操作系统, 并使用与建置和测试该架构版本相同之版本。 wd 是设计来简化驱动程序的开发,并且在不牺牲效能的情况下提升驱动程序质量。它提 供单一的基础架构模型给予核心模式以及用户模式驱动程序。该模型具弹性、延展性以及可 调整性,同时采用渐进式开发、降低学习曲线并且让驱动程序撰写人员可以专注于他们的设 备硬件,而不是操作系统上。 下列为 wdf 模型中的主要设计原理: 将驱动程序模块由主要的操作系统组件中独立出来。 提供某些设备型态用户模式选项。 因为建置共同与预设驱动程序功能,所以驱动程序开发人员能够专注在他们的硬件。 使驱动程序变为事
14、件导向并将事件定义的非常详细,让驱动程序的工作变得很明确。 为所有的驱动程序简化了即插即用 (plug and play) 与电源管理的建置。 让用户模式与核心模式驱动程序支持统一的安装程序。 提供整合工具,包括内建的追踪与检核支持,来协助发现与排解 debugging 期间以 及上市发表后所发生的问题。 让单一驱动程序 binary 可运作在数种架构版本与操作系统上。 wdf 驱动程序模型定义一个对象导向、事件触发的环境于驱动程序的程序代码,来管理 设备特定的功能以及 microsoft 提供的架构呼叫驱动程序来响应事件以影响它的设备的操 作。驱动程序模型包括: 对象模型,它是经由两者架构所
15、建置。 即插即用与电源管理建置,两者架构皆可使用。 8/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 i/o 模型,是为架构处理与操作系统的交互作用以及管理 i/o 流程、即插即用和电源管 理要求之项目。 版本设定策略可应用于核心模式与用户模式驱动程序。 为核心模式与用户模式驱动程序提供一致性的安装技术。 这个设计存在着几项重要的优点: 该架构建置了共通的驱动程序功能与预设行为,让合作厂商撰写的驱动程序较小且开发 与 debug 的速度较快。 microsoft 能够变更操作系统的内部数据结构而不会造成驱动程序不兼容。 最好的是驱动程序开发人员与硬件厂
16、商可以由每个新的或更新的操作系统版本的大量变 更中解脱。 每个架构都能够追踪驱动程序、操作系统以及设备的状态,因此可以免去很多驱动程序 中经常需要的复杂逻辑,尤其是在即插即用和电源管理上。 wdf 模型提供兼容且可延展的驱动程序开发接口。两种架构在命名、参数类型与使用方 式、对象阶层架构与默认值方面均符合规范。所有设备型态需要的或共通的功能皆存在于两 种架构中,所以驱动程序撰写人员能够应用撰写一种设备类型的驱动程序的知识在撰写其它 设备类型的驱动程序上。 1.2 linux 设备驱动程序设备驱动程序 linux 通过设备驱动程序为应用程序提供了统一抽象的接口,从而隐藏了大量不同设备 之间的区别
17、和细节,在 linux 中对硬件设备的操作和通常的文件一样,利用标准的文件操作 可以对设备上进行打开、关闭、读取或者写入操作。系统中的每个设备由“设备特殊文件” 来代表。例如,/dev/hda 代表系统中的第一个 ide 硬盘,每个由相同的设备驱动程序控制 的设备具有相同的主设备号,而次设备号则用来区分同类设备中的不同设备,设备特殊文件 的虚拟文件系统(virtual file system,简称 vfs)索引节点中包含设备号信息,如果通 过系统调用访问设备,则内核通过该 vfs 索引节点汇总的设备号信息调用适当的设备驱动程 序。 linux 系统中的设备系统中的设备 linux 系统中的设备
18、分为字符设备(char device)、块设备(block)和网络设备(net device)三种,字符设备是指在存取时没有缓存,能够像文件一样被访问的设备,字符设备 驱动程序至少要实现 open、close、read 和 write 系统调用。字符终端(/dev/console)以 及系统的串口(/dev/tty0)就属于字符设备。字符设备可以通过文件系统节点来访问,它 和普通文件的之间的唯一差别在于,对普通文件的访问可以前后移动访问指针,而大多数的 字符设备是只能顺序访问的数据通道。 和字符设备一样,块设备也是通过/dev 目录下的文件系统节点被访问的。linux 允许应 用程序像字符设备
19、那样读写块设备,可以一次传递任意多字节的数据。块设备与字符设备的 9/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 区别仅仅在于内核内部管理数据的方式不同,也就是内核和驱动程序的接口不同,它们之间 的差异对用户来说是透明的。此外,块设备读写需要缓存来支持,只能以块为单位进行读写, 而且能够随机存取,既不管所需要的块处于设备的什么地方都可以读写,字符设备则没有这 个要求。块设备主要包括硬盘、软盘、cd-rom 等。 网络设备不是面向流的设备,在 linux 中作专门的处理,用户不能通过文件设备接点来 访问,而且内核和网络驱动程序之间的通信完全不同于内核和
20、字符设备以及块设备之间的通 信,内核调用一套和数据包传输相关的函数来处理网络设备。 linux 核心的设备驱动程序核心的设备驱动程序 linux 核心中虽然存在许多不同种类的设备驱动程序,但是它们都有一些共同的特点: 1) 核心代码:设备驱动程序是核心的一部分,像核心中其他的代码一样,出错将导致系 统的严重损伤。一个编写不当的设备驱动程序甚至能够使系统崩溃导致文件系统的破 坏和数据的丢失; 2) 核心接口:设备驱动程序必须为 linux 核心或者其从属的子系统提供一个标准的接口; 3) 核心机制:设备驱动程序可以使用标准的核心服务比如内存分配、中断发送和等待对 列等; 4) 动态可加载:多数的
21、 linux 设备驱动程序可以在核心模块发出加载请求时进行加载, 同时在不使用设备时进行卸载,这样核心可以有效地利用系统的资源; 5) 可配置:linux 设备驱动属于核心的一部分,用户可以根据自己的需要进行配置来选 择适合自己的驱动。 2 linux 驱动开发框架驱动开发框架 linux 将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是 linux 内核和应用程序之间的接口,那么设备驱动程序则可以看成是 linux 内核与外部设备 之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操 作普通文件一样来操作外部设备。 2.1 字符设备和块设备字
22、符设备和块设备 linux 抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以 使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和 i/o 控制操作,而 驱动程序的主要任务也就是要实现这些系统调用函数。linux 系统中的所有硬件设备都使用 一个特殊的设备文件来表示,例如,系统中的第一个 ide 硬盘使用/dev/hda 表示。每个设 备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的 驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主 10/26 ia division 2007-9-7 linux
23、下驱动开发方法下驱动开发方法 设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问 到设备驱动程序。 在 linux 操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字 符设备是以字节为单位逐个进行 i/o 操作的设备,在对字符设备发出读写请求时,实际的硬 件 i/o 紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。 块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先 查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调 用相应的请求函数来进行实际的 i/o 操作
24、。块设备主要是针对磁盘等慢速设备设计的,其目 的是避免耗费过多的 cpu 时间来等待操作的完成。一般说来,pci 卡通常都属于字符设备。 所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices 文件中得到。使用 mknod 命令可以创建指定类型的设备文件,同时为其分配相应的主设备号 和次设备号。例如,下面的命令: rootgary root# mknod /dev/lp0 c 6 0 将建立一个主设备号为 6,次设备号为 0 的字符设备文件/dev/lp0。当应用程序对某个 设备文件进行系统调用时,linux 内核会根据该设备文件的设备类型和主设备号调用相应的
25、 驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相 应硬件的操作。 2.2 设备驱动程序接口设备驱动程序接口 linux 中的 i/o 子系统向内核中的其他部分提供了一个统一的标准设备接 口,这是通过 include/linux/fs.h 中的数据结构 file_operations 来完成的: struct file_operations struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, siz
26、e_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *
27、, struct vm_area_struct *); 11/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int);
28、int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); uns
29、igned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); ; 当应用程序对设备文件进行诸如 open、close、read、write 等操作时,linux 内核将通 过 file_operations 结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操 作时,内核将调用 file_operations 结构中的 read 函数。 2.3 设备驱动程序模块设备驱动程序模块 linux 下的设备驱动程序可以按照两种方式进行编译,
30、一种是直接静态编译成内核的一 部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小, 还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。 从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户 态下的 c 或者 c+库函数,而只能调用 linux 内核提供的函数,在/proc/ksyms 中可以查看 到内核提供的所有函数。 在以模块方式编写驱动程序时,要实现两个必不可少的函数 init_module( )和 cleanup_module( ),而且至少要包含和两个头文件。在用 gcc 编译内核模块时,需要加上- dmod
31、ule -d_kernel_ -dlinux 这几个参数,编译生成的模块(一般为.o 文件)可以使用 命令 insmod 载入 linux 内核,从而成为内核的一个组成部分,此时内核会调用模块中的函 数 init_module( )。当不需要该模块时,可以使用 rmmod 命令进行卸载,此进内核会调用 模块中的函数 cleanup_module( )。任何时候都可以使用命令来 lsmod 查看目前已经加载的 模块以及正在使用该模块的用户数。 12/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 2.4 设备驱动程序结构设备驱动程序结构 了解设备驱动程序的
32、基本结构(或者称为框架) ,对开发人员而言是非常重要的,linux 的 设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设 备的读写操作、设备的控制操作、设备的中断和轮询处理。 驱动程序的注册与注销驱动程序的注册与注销 向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过 程中调用 register_chrdev( )或者 register_blkdev( )来完成。而在关闭字符设备或者块 设备时,则需要通过调用 unregister_chrdev( )或 unregister_blkdev( )从内核中注销设 备,同时释放占用的主设备
33、号。 设备的打开与释放设备的打开与释放 打开设备是通过调用 file_operations 结构中的函数 open( )来完成的,它是驱动程序用来 为今后的操作完成初始化准备工作的。在大部分驱动程序中,open( )通常需要完成下列工 作: 1.检查设备相关错误,如设备尚未准备好等。 2.如果是第一次打开,则初始化硬件设备。 3.识别次设备号,如果有必要则更新读写操作的当前位置指针 f_ops。 4.分配和填写要放在 file-private_data 里的数据结构。 5.使用计数增 1。 释放设备是通过调用 file_operations 结构中的函数 release( )来完成的,这个设备
34、方法 有时也被称为 close( ),它的作用正好与 open( )相反,通常要完成下列工作: 1.使用计数减 1。 2.释放在 file-private_data 中分配的内存。 3.如果使用计算为 0,则关闭设备。 设备的读写操作设备的读写操作 字符设备的读写操作相对比较简单,直接使用函数 read( )和 write( )就可以了。但如果是 块设备的话,则需要调用函数 block_read( )和 block_write( )来进行数据读写,这两个函 数将向设备请求表中增加读写请求,以便 linux 内核可以对请求顺序进行优化。由于是对内 存缓冲区而不是直接对设备进行操作的,因此能很大程
35、度上加快读写速度。如果内存缓冲区 中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传 输,这是通过调用数据结构 blk_dev_struct 中的函数 request_fn( )来完成的。 13/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 设备的控制操作设备的控制操作 除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数 ioctl( )来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行 具体分析。 设备的中断和轮询处理设备的中断和轮询处理 对于不支持中断的硬件设
36、备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传 输。如果设备支持中断,则可以按中断方式进行操作。 3 linux 驱动调试驱动调试 对于任何编写内核代码的人来说,最吸引他们注意的问题之一就是如何完成调试。由于 内核是一个不与某个进程相关的功能集,其代码不能很轻松地放在调试器中执行,而且也不 能跟踪。 下面将介绍可以用来监视内核代码和跟踪错误的技术。(参考 chapter 4. debugging techniques) 3.1 用打印信息调试用打印信息调试 最一般的调试技术就是监视,就是在应用内部合适的点加上 printf 调用。当你调试内 核代码的时候,你可以用 printk 完
37、成这个任务。 printk 在前些章中,我们简单假设 printk 工作起来和 printf 很类似。现在是介绍一下它们之 间不同的时候了。 其中一个不同点就是,printk 允许你根据它们的严重程度,通过附加不同的“记录级” 来对消息分类,或赋予消息优先级。你可以用宏来指示记录级。例如,kern_info,我们前 面已经看到它被加在打印语句的前面,它就是一种可能的消息记录级。记录级宏展开为一个 字串,在编译时和消息文本拼接在一起;这也就是为什么下面的例子中优先级和格式字串间 没有逗号。这有两个 printk 的例子,一个是调试信息,一个是关键信息: (代码) 在中定义了 8 种记录级别串。没
38、有指定优先级的 printk 语句默认使 用 default_message_loglevel 优先级,它是一个在 kernel/printk.c 中定义的整数。默认 记录级的具体数值在 linux 的开发期间曾变化过若干次,所以我建议你最好总是指定一个合 适的记录级。 根据记录级,内核将消息打印到当前文本控制台上:如果优先级低于 console_loglevel 14/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 这个数值的话,该消息就显示在控制台上。如果系统同时运行了 klogd 和 syslogd,无论 console_loglevel 为何值,
39、内核都将消息追加到/var/log/messages 中。 变量 console_loglevel 最初初始化为 default_console_loglevel,但可以通过 sys_syslog 系统调用修改。如 klogd 的手册所示,可以在启动 klogd 时指定-c 开关来修改 这个变量。此外,你还可以写个程序来改变控制台记录级。你可以在 oreilly 站点上的源 文件中找到我写的一个这种功能的程序,miscprogs/setlevel.c。新优先级是通过一个 1 到 8 之间的整数值指定的。 你也许需要在内核失效后降低记录级(见“调试系统故障” ) ,这是因为失效处理代码会将 co
40、nsole_loglevel 提升到 15,之后所有的消息都会出现在控制台上。为看到你的调试信息, 如果你运行的是内核 2.0.x 话,你需要提升记录级。内核 2.0 发行降低了 minimum_console_loglevel,而旧版本的 klogd 默认情况下要打印很多控制消息。如果你碰 巧使用了这个旧版本的守护进程,除非你提升记录级,内核 2.0 会比你预期的打印出更少的 消息。这就是为什么 hello.c 中使用了标记,这样可以保证消息显示在控制台上。 从 1.3.43 一来的内核版本通过允许你向指定虚控制台发送消息,藉此提供一个灵活的 记录策略。默认情况下, “控制台”是当前虚终端。
41、也可以选择不同的虚终端接收消息,你 只需向所选的虚终端调用 ioctl(tioclinux)。如下程序,setconsole,可以用来选择哪个 虚终端接收内核消息;它必须以超级用户身份运行。如果你对 ioctl 还不有把握,你可以跳 过这至下一节,等到读完第 5 章“字符设备驱动程序的扩展操作”的“ioctl”一节后,再 回到这里读这段代码。 (代码) setconsole 使用了用于 linux 专用功能的特殊的 ioctl 命令 tioclinux。为了使用 tioclinux,你要传递给它一个指向字节数组的指针。数组的第一个字节是所请求的子命令 的编码,随后的字节依命令而不同。在 set
42、console 中使用了子命令 11,后一个字节(存放 在 bytes1中)标别虚拟控制台。tioclinux 的完成介绍可以在内核源码 drivers/char/tty_io.c 中找到。 消息是如何记录的消息是如何记录的 printk 函数将消息写到一个长度为 log_buf_len 个字节的循环缓冲区中。然后唤醒任何 等待消息的进程,即那些在调用 syslog 系统调用或读取/proc/kmesg 过程中睡眠的进程。 这两个访问记录引擎的接口是等价的。不过/proc/kmesg 文件更象一个 fifo 文件,从中读 取数据更容易些。一跳简单的 cat 命令就可以读取消息。 如果循环缓冲区
43、填满了,printk 就绕到缓冲区的开始处填写新数据,覆盖旧数据。于是 记录进程就丢失了最旧的数据。这个问题与利用循环缓冲区所获得的好处相比可以忽略不计。 例如,循环缓冲区可以使系统在没有记录进程的情况下照样运行,同时又不浪费内存。 linux 处理消息的方法的另一个特点是,可以在任何地方调用 printk,甚至在中断处理函数 里也可以调用,而且对数据量的大小没有限制。这个方法的唯一缺点就是可能丢失某些数据。 如果 klogd 正在运行,它读取内核消息并将它们分派到 syslogd,它随后检查 /etc/syslog.conf 找到处理这些数据的方式。syslogd 根据一个“设施”和“优先级
44、”切分 消息;可以使用的值定义在中。内核消息根据相应 printk 中指定的优先级 记录到 log_kern 设施中。如果 klogd 没有运行,数据将保存在循环缓冲区中直到有进程来 15/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 读取数据或数据溢出。 如果你不希望因监视你的驱动程序的消息而把你的系统记录搞乱,你给 klogd 指定- f(文件)选项或修改/etc/syslog.conf 将记录写到另一个文件中。另一种方法是一种强硬 方法:杀掉 klogd,将消息打印到不用的虚终端上*,或者在一个不用的 xterm 上执行 cat /proc/km
45、esg 显示消息。 使用预处理方便监视处理使用预处理方便监视处理 在驱动程序开发早期,printk 可以对调试和测试新代码都非常有帮助。然而当你正式发 行驱动程序时,你应该去掉,或者至少关闭,这些打印语句。很不幸,你可能很快就发现, 随着你想不再需要那些消息并去掉它们时,你可能又要加新功能,你又需要这些消息了。解 决这些问题有几种方法如何从全局打开和关闭消息以及如何打开和关闭个别消息。 下面给出了我处理消息所用的大部分代码,它有如下一些功能: 可以通过在宏名字加一个字母或去掉一个字母打开或关闭每一条语句。 通过在编译前修改 cflags 变量,可以一次关闭所有消息。 同样的打印语句既可以用在内
46、核态(驱动程序)也可以用在用户态(演示或测试程序) 。 下面这些直接来自 scull.h 的代码片断实现了这些功能。 (代码) 符合 pdebug 和 pdebugg 依赖于是否定义了 scull_debug,它们都和 printf 调用很类似。 为了进一步方便这个过程,在你的 makefile 加上如下几行。 (代码) 本节所给出的代码依赖于 gcc 对 ansi c 预编译器的扩展,gcc 可以支持带可变数目参数 的宏。这种对 gcc 的依赖并不是什么问题,因为内核对 gcc 特性的依赖更强。此外, makefile 依赖于 gnu 的 gmake;基于同样的道理,这也不是什么问题。 如果
47、你很熟悉 c 预编译器,你可以将上面的定义扩展为可以支持“调试级”概念的,可 以为每级赋一个整数(或位图) ,说明这一级打印多么琐碎的消息。 但是每一个驱动程序都有它自己的功能和监视需求。好的编程技巧会在灵活性和高效之 间找到一个权衡点,这个我就不能说哪个对你最好了。记住,预编译器条件(还有代码中的 常量表达式)只到编译时运行,你必须重新编译程序来打开或关闭消息。另一种方法就是使 用 c 条件语句,它在运行时运行,因此可以让你在程序执行期间打开或关闭消息。这个功能 很好,但每次代码执行系统都要进行额外的处理,甚至在消息关闭后仍然会影响性能。有时 这种性能损失是无法接受的。 * 例如,使用命令
48、setlevel 8;set console 10 设置终端 10 显示消息。 16/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 个人观点,尽管上面给出的宏迫使你每次要增加或去掉消息时都要重新编译,重新加载 模块,但我觉得用这些宏已经很好了。 3.2 通过查询调试通过查询调试 上一节谈到了 printk 是如何工作的以及如何使用它。但没有谈及它的缺点。 由于 syslogd 会一直保持刷新它的输出文件,每打印一行都会引起一次磁盘操作,因此过量 使用 printk 会严重降低系统性能。至少从 syslogd 的角度看是这样的。它会将所有的数据 都一股脑
49、地写到磁盘上,以防在打印消息后系统崩溃;然而,你不想因为调试信息的缘故而 降低系统性能。这个问题可以通过在/etc/syslogd.conf 中记录文件的名字前加一个波折号 解决,但有时你不想修改你的配置文件。如果不这样,你还可以运行一个非 klogd 的程序 (如前面介绍的 cat /proc/kmesg) ,但这样并不能为正常操作提供一个合适的环境。 与这相比,最好的方法就是在你需要信息的时候,通过查询系统获得相关信息,而不是持续 不断地产生数据。事实上,每一个 unix 系统都提供了很多工具用来获得系统信息: ps,netstat,vmstat 等等。 有许多技术适合与驱动程序开发人员查
50、询系统,简而言之就是,在/proc 下创建文件和 使用 ioctl 驱动程序方法。 使用使用/proc 文件系统文件系统 有时你遇到的问题并不特别糟,通过在用户空间运行应用程序来查看驱动程序与系统之 间的交互过程可以帮助你捕捉到一些小问题,并可以验证驱动程序确实工作正常。例如,看 到 scull 的 read 实现如何处理不同数据量的 read 请求后,我对 scull 更有信心。 有许多方法监视一个用户态程序的工作情况。你可以用调试器一步步跟踪它的函数,插 入打印语句,或者用 strace 运行程序。在实际目的是查看内核代码时,最后一项技术非常 有用。 strace 命令是一个功能非常强大的
51、工具,它可以现实程序所调用的所有系统调用。它不 仅可以显示调用,而且还能显示调用的参数,以符号方式显示返回值。当系统调用失败时, 错误的符号值(如,enomem)和对应的字串(out of memory)同时显示。strace 还有许多 命令行选项;最常用的是-t,它用来显示调用发生的时间,-t,显示调用所花费的时间,以 及-o,将输出重定向到一个文件中。默认情况下,strace 将所有跟踪信息打印到 stderr 上。 strace 从内核接收信息。这意味着一个程序无论是否按调试方式编译(用 gcc 的-g 选 项)或是被去掉了符号信息都可以被跟踪。与调试器可以连接到一个运行进程并控制它类似
52、, 你还可以跟踪一个已经运行的进程。 跟踪信息通常用来生成错误报告报告给应用开发人员,但是对内核编程人员来说也一样非常 有用。我们可以看到系统调用是如何执行驱动程序代码的;strace 允许我们检查每一次调 用输入输出的一致性。 17/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 例如,下面的屏幕输出给出了命令 ls /dev /dev/scull0 的最后几行: (代码) 很明显,在 ls 完成目标目录的检索后首次对 write 的调用中,它试图写 4kb。很奇怪, 只写了 4000 个字节,接着重试这一操作。然而,我们知道 scull 的 writ
53、e 实现每次只写一 个量子,我在这里看到了部分写。经过若干步骤之后,所有的东西都清空了,程序正常退出。 另一个例子,让我们来读 scull 设备: (代码) 正如所料,read 每次只能读到 4000 个字节,但是数据总量是不变的。注意本例中重试 工作是如何组织的,注意它与上面写跟踪的对比。wc 专门为快速读数据进行了优化,它绕 过了标准库,以便每次用一个系统调用读取更多的数据。你可以从跟踪的 read 行中看到 wc 每次要读 16kb。 unix 专家可以在 strace 的输出中找到很多有用信息。如果你被这些符号搞得满头雾水, 我可以只看文件方法(open,read 等等)是如何工作的。
54、 个人认为,跟踪工具在查明系统调用的运行时错误过程中最有用。通常应用或演示程序中的 perror 调用不足以用来调试,而且对于查明到底是什么样的参数触发了系统调用的错误也 很有帮助。 ioctl 方法方法 ioctl,下一章将详细讨论,是一个系统调用,它可以操做在文件描述符上;它接收一 个“命令”号和(可选的)一个参数,通常这是一个指针。 做为替代/proc 文件系统的方法,你可以为调试实现若干 ioctl 命令。这些命令从驱动程序 空间复制相关数据到进程空间,在进程空间里检查这些数据。 只有使用 ioctl 获取信息比起/proc 来要困难一些,因为你一个程序调用 ioctl 并显示结果。
55、必须编写这样的程序,还要编译,保持与你测试的模块间的一致性等。 不过有时候这是最好的获取信息的方法,因为它比起读/proc 来要快得多。如果在数据 写到屏幕前必须完成某些处理工作,以二进制获取数据要比读取文本文件有效得多。此外, ioctl 不限制返回数据的大小。 ioctl 方法的一个优点是,当调试关闭后调试命令仍然可以保留在驱动程序中。/proc 文件对任何查看这个目录的人都是可见的,然而与/proc 文件不同,未公开的 ioctl 命令通 常都不会被注意到。此外,如果驱动程序有什么异常,它们仍然可以用来调试。唯一的缺点 就是模块会稍微大一些。 3.3 通过监视调试通过监视调试 有时你遇到
56、的问题并不特别糟,通过在用户空间运行应用程序来查看驱动程序与系统之 间的交互过程可以帮助你捕捉到一些小问题,并可以验证驱动程序确实工作正常。例如,看 到 scull 的 read 实现如何处理不同数据量的 read 请求后,我对 scull 更有信心。 有许多方法监视一个用户态程序的工作情况。你可以用调试器一步步跟踪它的函数,插 入打印语句,或者用 strace 运行程序。在实际目的是查看内核代码时,最后一项技术非常 有用。 18/26 ia division 2007-9-7 linux下驱动开发方法下驱动开发方法 strace 命令是一个功能非常强大的工具,它可以现实程序所调用的所有系统调
57、用。它不 仅可以显示调用,而且还能显示调用的参数,以符号方式显示返回值。当系统调用失败时, 错误的符号值(如,enomem)和对应的字串(out of memory)同时显示。strace 还有许多 命令行选项;最常用的是-t,它用来显示调用发生的时间,-t,显示调用所花费的时间,以 及-o,将输出重定向到一个文件中。默认情况下,strace 将所有跟踪信息打印到 stderr 上。 strace 从内核接收信息。这意味着一个程序无论是否按调试方式编译(用 gcc 的-g 选 项)或是被去掉了符号信息都可以被跟踪。与调试器可以连接到一个运行进程并控制它类似, 你还可以跟踪一个已经运行的进程。
58、跟踪信息通常用来生成错误报告报告给应用开发人员,但是对内核编程人员来说也一样 非常有用。我们可以看到系统调用是如何执行驱动程序代码的;strace 允许我们检查每一 次调用输入输出的一致性。 3.4 调试系统故障调试系统故障 即便你用了所有监视和调试技术,有时候驱动程序中依然有错误,当这样的驱动程序执 行会会造成系统故障。当这种情况发生时,获取足够多的信息来解决问题是至关重要的。 注意, “故障”不意味着“panic” 。linux 代码非常鲁棒,可以很好地响应大部分错误:故 障通常会导致当前进程的终止,但系统继续运行。如果在进程上下文之外发生故障,或是组 成系统的重要部件发生故障时,系统可能
59、 panic。但问题出在驱动程序时,通常只会导致产 生故障的进程终止即那个使用驱动程序的进程。唯一不可恢复的损失就是当进程被终止 时,进程上下文分配的内存丢失了;例如,由驱动程序通过 kmalloc 分配的动态链表可能丢 失。然而,由于内核会对尚是打开的设备调用 close,你的驱动程序可以释放任何有 open 方法分配的资源。 我们已经说过,当内核行为异常时会在控制台上显示一些有用的信息。下一节将解释如 何解码和使用这些消息。尽管它们对于初学者来说相当晦涩,处理器的给出数据都是些很有 意思的信息,通常无需额外测试就可以查明程序错误。 oops 消息消息 大部分错误都是 null 指针引用或使
60、用其他不正确的指针数值。这些错误通常会导致一个 oops 消息。 由处理器使用的地址都是“虚”地址,而且通过一个复杂的称为页表的结构映射为物理 地址。当引用一个非法指针时,页面映射机制就不能将地址映射到物理地址,并且处理器向 操作系统发出一个“页面失效” 。如果地址确实是非法的,内核就无法从失效地址上“换页” ; 如果此时处理在超级用户太,系统于是就产生一个“oops” 。值得注意的是,在版本 2.1 中 内核处理失效的方式有所变化,它可以处理在超级用户态的非法地址引用了 oops 显示故障时的处理器状态,模块 cpu 寄存器内容,页描述符表的位置,以及其他似 乎不能理解的信息。这些是由失效处
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025秋统编版(2024)新教材三年级语文上册第七单元《语文园地七》练习题及答案
- 特种玻璃电子束切割超硬涂层工艺考核试卷及答案
- 印染烘干操作工综合考核试卷及答案
- 电机铁芯叠装工异常处理考核试卷及答案
- 印后成型工数字化技能考核试卷及答案
- 信息技术考试ps试题及答案
- 有限空间作业及企业安全管理风险管控与隐患治理试卷
- 银行综合试题及答案
- 银行债务员面试题目及答案
- 银行押运员面试题及答案
- 2025年医疗工作人员定向招聘考试笔试试题(含答案)
- 第二单元混合运算单元测试卷(含答案) 2025-2026学年人教版三年级数学上册
- 超声引导下经支气管针吸活检术核心组织采集率的影响因素分析介绍演示培训课件
- 绘本《其实我很喜欢你》冯玉梅
- 铸牢中华民族共同体意识主题班会
- 公司内部审计制度范本(四篇)
- 绿色建筑材料和建筑设备
- 可靠性试验管理办法
- 蓄电池组充放电记录表格格式模板
- 智慧交通典型城市案例及启示
- 国家开放大学《人文英语4》边学边练参考答案
评论
0/150
提交评论