Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1255629
  • 博文数量: 404
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 5382
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-03 16:29
文章存档

2010年(40)

2009年(140)

2008年(224)

我的朋友

分类: LINUX

2008-09-28 16:04:59

第 1 章 第一章 设备驱动简介

以 Linux 为代表的自由操作系统的很多优点之一, 是它们的内部是开放给所有人看的. 操作系统, 曾经是一个隐藏的神秘的地方, 它的代码只局限于少数的程序员, 现在已准备好让任何具备必要技能的人来检查, 理解以及修改. Linux 已经帮助使操作系统民主化. Linux 内核保留有大量的复杂的代码, 但是, 那些想要成为内核 hacker 的人需要一个入口点, 这样他们可以进入代码中, 不会被代码的复杂性压倒. 通常, 设备驱动提供了这样的门路.

驱动程序在 Linux 内核里扮演着特殊的角色. 它们是截然不同的"黑盒子", 使硬件的特殊的一部分响应定义好的内部编程接口. 它们完全隐藏了设备工作的细节. 用户的活动通过一套标准化的调用来进行, 这些调用与特别的驱动是独立的; 设备驱动的角色就是将这些调用映射到作用于实际硬件的和设备相关的操作上. 这个编程接口是这样, 驱动可以与内核的其他部分分开建立, 并在需要的时候在运行时"插入". 这种模块化使得 Linux 驱动易写, 以致于目前有几百个驱动可用.

编写 Linux 设备驱动有许多理由让人感兴趣. 可用的新硬件出现的速率(以及陈旧的速率)就确保了驱动编写者在可见的将来内是忙碌的. 个别人可能需要了解驱动以便存取一个他们感兴趣的特殊设备. 硬件供应商, 通过为他们的产品开发 Linux 驱动, 可以给他们的潜在市场增加大量的正在扩张的 Linux 用户基数. 还有 Linux 系统的开放源码性质意味着如果驱动编写者愿意, 驱动源码能够快速地散布到几百万用户.

本书指导你如何编写你自己的驱动, 以及如何利用内核相关的部分. 我们采用一种设备-独立的方法; 编程技术和接口, 在任何可能的时候, 不会捆绑到任何特定的设备. 每一个驱动都是不同的; 作为一个驱动编写者, 你需要深入理解你的特定设备. 但是大部分的原则和基本技术对所有驱动都是一样的. 本书无法教你关于你的设备的东西, 但是它给予你所需要的使你的设备运行起来的背景知识的指导.

在你学习编写驱动时, 你通常会发现大量有关 Linux 内核的东西. 这也许会帮助你理解你的机器是如何工作的, 以及为什么事情不是如你所愿的快, 或者不是如你所要的进行. 我们会逐步介绍新概念, 由非常简单的驱动开始并建立它们; 每一个新概念都伴有例子代码, 这样的代码不需要特别的硬件来测试.

本章不会真正进入编写代码. 但是, 我们介绍一些 Linux 内核的背景概念, 这样在以后我们动手编程时, 你会感到乐于知道这些.

1.1. 驱动程序的角色

作为一个程序员, 你能够对你的驱动作出你自己的选择, 并且在所需的编程时间和结果的灵活性之间, 选择一个可接受的平衡. 尽管说一个驱动是"灵活"的, 听起来有些奇怪, 但是我们喜欢这个字眼, 因为它强调了一个驱动程序的角色是提供机制, 而不是策略.

机制和策略的区分是其中一个在 Unix 设计背后的最好观念. 大部分的编程问题其实可以划分为 2 部分:" 提供什么能力"(机制) 和 "如何使用这些能力"(策略). 如果这两方面由程序的不同部分来表达, 或者甚至由不同的程序共同表达, 软件包是非常容易开发和适应特殊的需求.

例如, 图形显示的 Unix 管理划分为 X 服务器, 它理解硬件以及提供了统一的接口给用户程序, 还有窗口和会话管理器, 它实现了一个特别的策略, 而对硬件一无所知. 人们可以在不同的硬件上使用相同的窗口管理器, 而且不同的用户可以在同一台工作站上运行不同的配置. 甚至完全不同的桌面环境, 例如 KDE 和 GNOME, 可以在同一系统中共存. 另一个例子是 TCP/IP 网络的分层结构: 操作系统提供 socket 抽象层, 它对要传送的数据而言不实现策略, 而不同的服务器负责各种服务( 以及它们的相关策略). 而且, 一个服务器, 例如 ftpd 提供文件传输机制, 同时用户可以使用任何他们喜欢的客户端; 无论命令行还是图形客户端都存在, 并且任何人都能编写一个新的用户接口来传输文件.

在驱动相关的地方, 机制和策略之间的同样的区分都适用. 软驱驱动是不含策略的--它的角色仅仅是将磁盘表现为一个数据块的连续阵列. 系统的更高级部分提供了策略, 例如谁可以存取软驱驱动, 这个软驱是直接存取还是要通过一个文件系统, 以及用户是否可以加载文件系统到这个软驱. 因为不同的环境常常需要不同的使用硬件的方式, 尽可能对策略透明是非常重要的.

在编写驱动时, 程序员应当特别注意这个基础的概念: 编写内核代码来存取硬件, 但是不能强加特别的策略给用户, 因为不同的用户有不同的需求. 驱动应当做到使硬件可用, 将所有关于如何使用硬件的事情留给应用程序. 一个驱动, 这样, 就是灵活的, 如果它提供了对硬件能力的存取, 没有增加约束. 然而, 有时必须作出一些策略的决定. 例如, 一个数字 I/O 驱动也许只提供对硬件的字符存取, 以便避免额外的代码处理单个位.

你也可以从不同的角度看你的驱动: 它是一个存在于应用程序和实际设备间的软件层. 驱动的这种特权的角色允许驱动程序员严密地选择设备应该如何表现: 不同的驱动可以提供不同的能力, 甚至是同一个设备. 实际的驱动设计应当是在许多不同考虑中的平衡. 例如, 一个单个设备可能由不同的程序并发使用, 驱动程序员有完全的自由来决定如何处理并发性. 你能在设备上实现内存映射而不依赖它的硬件能力, 或者你能提供一个用户库来帮助应用程序员在可用的原语之上实现新策略, 等等. 一个主要的考虑是在展现给用户尽可能多的选项, 和你不得不花费的编写驱动的时间之间做出平衡, 还有需要保持事情简单以避免错误潜入.

对策略透明的驱动有一些典型的特征. 这些包括支持同步和异步操作, 可以多次打开的能力, 利用硬件全部能力, 没有软件层来"简化事情"或者提供策略相关的操作. 这样的驱动不但对他们的最终用户好用, 而且证明也是易写易维护的. 成为策略透明的实际是一个共同的目标, 对软件设计者来说.

许多设备驱动, 确实, 是与用户程序一起发行的, 以便帮助配置和存取目标设备. 这些程序包括简单的工具到完全的图形应用. 例子包括 tunelp 程序, 它调整并口打印机驱动如何操作, 还有图形的 cardctl 工具, 它是 PCMCIA 驱动包的一部分. 经常会提供一个客户库, 它提供了不需要驱动自身实现的功能.

本书的范围是内核, 因此我们尽力不涉及策略问题, 应用程序, 以及支持库. 有时我们谈论不同的策略以及如何支持他们, 但是我们不会进入太多有关使用设备的程序的细节, 或者是他们强加的策略的细节. 但是, 你应当理解, 用户程序是一个软件包的构成部分, 并且就算是对策略透明的软件包在发行时也会带有配置文件, 来对底层的机制应用缺省的动作.

1.2. 划分内核

在 Unix 系统中, 几个并发的进程专注于不同的任务. 每个进程请求系统资源, 象计算能力, 内存, 网络连接, 或者一些别的资源. 内核是个大块的可执行文件, 负责处理所有这样的请求. 尽管不同内核任务间的区别常常不是能清楚划分, 内核的角色可以划分(如同图)成下列几个部分:

进程管理

内核负责创建和销毁进程, 并处理它们与外部世界的联系(输入和输出). 不同进程间通讯(通过信号, 管道, 或者进程间通讯原语)对整个系统功能来说是基本的, 也由内核处理. 另外, 调度器, 控制进程如何共享 CPU, 是进程管理的一部分. 更通常地, 内核的进程管理活动实现了多个进程在一个单个或者几个 CPU 之上的抽象.

内存管理

计算机的内存是主要的资源, 处理它所用的策略对系统性能是至关重要的. 内核为所有进程的每一个都在有限的可用资源上建立了一个虚拟地址空间. 内核的不同部分与内存管理子系统通过一套函数调用交互, 从简单的 malloc/free 对到更多更复杂的功能.

文件系统

Unix 在很大程度上基于文件系统的概念; 几乎 Unix 中的任何东西都可看作一个文件. 内核在非结构化的硬件之上建立了一个结构化的文件系统, 结果是文件的抽象非常多地在整个系统中应用. 另外, Linux 支持多个文件系统类型, 就是说, 物理介质上不同的数据组织方式. 例如, 磁盘可被格式化成标准 Linux 的 ext3 文件系统, 普遍使用的 FAT 文件系统, 或者其他几个文件系统.

设备控制

几乎每个系统操作最终都映射到一个物理设备上. 除了处理器, 内存和非常少的别的实体之外, 全部中的任何设备控制操作都由特定于要寻址的设备相关的代码来进行. 这些代码称为设备驱动. 内核中必须嵌入系统中出现的每个外设的驱动, 从硬盘驱动到键盘和磁带驱动器. 内核功能的这个方面是本书中的我们主要感兴趣的地方.

网络

网络必须由操作系统来管理, 因为大部分网络操作不是特定于某一个进程: 进入系统的报文是异步事件. 报文在某一个进程接手之前必须被收集, 识别, 分发. 系统负责在程序和网络接口之间递送数据报文, 它必须根据程序的网络活动来控制程序的执行. 另外, 所有的路由和地址解析问题都在内核中实现.

1.2.1. 可加载模块

Linux 的众多优良特性之一就是可以在运行时扩展由内核提供的特性的能力. 这意味着你可以在系统正在运行着的时候增加内核的功能( 也可以去除 ).

每块可以在运行时添加到内核的代码, 被称为一个模块. Linux 内核提供了对许多模块类型的支持, 包括但不限于, 设备驱动. 每个模块由目标代码组成( 没有连接成一个完整可执行文件 ), 可以动态连接到运行中的内核中, 通过 insmod 程序, 以及通过 rmmod 程序去连接.

图 表示了负责特定任务的不同类别的模块, 一个模块是根据它提供的功能来说它属于一个特别类别的. 图 中模块的安排涵盖了最重要的类别, 但是远未完整, 因为在 Linux 中越来越多的功能被模块化了.

图 1.1. 内核的划分

内核的划分
 

1.3. 设备和模块的分类

以 Linux 的方式看待设备可区分为 3 种基本设备类型. 每个模块常常实现 3 种类型中的 1 种, 因此可分类成字符模块, 块模块, 或者一个网络模块. 这种将模块分成不同类型或类别的方法并非是固定不变的; 程序员可以选择建立在一个大块代码中实现了不同驱动的巨大模块. 但是, 好的程序员, 常常创建一个不同的模块给每个它们实现的新功能, 因为分解是可伸缩性和可扩张性的关键因素.

3 类驱动如下:

字符设备

一个字符( char ) 设备是一种可以当作一个字节流来存取的设备( 如同一个文件 ); 一个字符驱动负责实现这种行为. 这样的驱动常常至少实现 open, close, read, 和 write 系统调用. 文本控制台( /dev/console )和串口( /dev/ttyS0 及其友 )是字符设备的例子, 因为它们很好地展现了流的抽象. 字符设备通过文件系统结点来存取, 例如 /dev/tty1 和 /dev/lp0. 在一个字符设备和一个普通文件之间唯一有关的不同就是, 你经常可以在普通文件中移来移去, 但是大部分字符设备仅仅是数据通道, 你只能顺序存取.然而, 存在看起来象数据区的字符设备, 你可以在里面移来移去. 例如, frame grabber 经常这样, 应用程序可以使用 mmap 或者 lseek 存取整个要求的图像.

块设备

如同字符设备, 块设备通过位于 /dev 目录的文件系统结点来存取. 一个块设备(例如一个磁盘)应该是可以驻有一个文件系统的. 在大部分的 Unix 系统, 一个块设备只能处理这样的 I/O 操作, 传送一个或多个长度经常是 512 字节( 或一个更大的 2 的幂的数 )的整块. Linux, 相反, 允许应用程序读写一个块设备象一个字符设备一样 -- 它允许一次传送任意数目的字节. 结果就是, 块和字符设备的区别仅仅在内核在内部管理数据的方式上, 并且因此在内核/驱动的软件接口上不同. 如同一个字符设备, 每个块设备都通过一个文件系统结点被存取的, 它们之间的区别对用户是透明的. 块驱动和字符驱动相比, 与内核的接口完全不同.

网络接口

任何网络事务都通过一个接口来进行, 就是说, 一个能够与其他主机交换数据的设备. 通常, 一个接口是一个硬件设备, 但是它也可能是一个纯粹的软件设备, 比如环回接口. 一个网络接口负责发送和接收数据报文, 在内核网络子系统的驱动下, 不必知道单个事务是如何映射到实际的被发送的报文上的. 很多网络连接( 特别那些使用 TCP 的)是面向流的, 但是网络设备却常常设计成处理报文的发送和接收. 一个网络驱动对单个连接一无所知; 它只处理报文.

既然不是一个面向流的设备, 一个网络接口就不象 /dev/tty1 那么容易映射到文件系统的一个结点上. Unix 提供的对接口的存取的方式仍然是通过分配一个名子给它们( 例如 eth0 ), 但是这个名子在文件系统中没有对应的入口. 内核与网络设备驱动间的通讯与字符和块设备驱动所用的完全不同. 不用 read 和 write, 内核调用和报文传递相关的函数.

有其他的划分驱动模块的方式, 与上面的设备类型是正交的. 通常, 某些类型的驱动与给定类型设备的其他层的内核支持函数一起工作. 例如, 你可以说 USB 模块, 串口模块, SCSI 模块, 等等. 每个 USB 设备由一个 USB 模块驱动, 与 USB 子系统一起工作, 但是设备自身在系统中表现为一个字符设备( 比如一个 USB 串口 ), 一个块设备( 一个 USB 内存读卡器 ), 或者一个网络设备( 一个 USB 以太网接口 ).

另外的设备驱动类别近来已经添加到内核中, 包括 FireWire 驱动和 I2O 驱动. 以它们处理 USB 和 SCSI 驱动相同的方式, 内核开发者集合了类别范围内的特性, 并把它们输出给驱动实现者, 以避免重复工作和 bug, 因此简化和加强了编写类似驱动的过程.

在设备驱动之外, 别的功能, 不论硬件和软件, 在内核中都是模块化的. 一个普通的例子是文件系统. 一个文件系统类型决定了在块设备上信息是如何组织的, 以便能表示一棵目录与文件的树. 这样的实体不是设备驱动, 因为没有明确的设备与信息摆放方式相联系; 文件系统类型却是一种软件驱动, 因为它将低级数据结构映射为高级的数据结构. 文件系统决定一个文件名多长, 以及在一个目录入口中存储每个文件的什么信息. 文件系统模块必须实现最低级的系统调用, 来存取目录和文件, 通过映射文件名和路径( 以及其他信息, 例如存取模式 )到保存在数据块中的数据结构. 这样的一个接口是完全与数据被传送来去磁盘( 或其他介质 )相互独立, 这个传送是由一个块设备驱动完成的.

如果你考虑一个 Unix 系统是多么依赖下面的文件系统, 你会认识到这样的一个软件概念对系统操作是至关重要的. 解码文件系统信息的能力处于内核层级中最低级, 并且是最重要的; 甚至如果你为你的新 CD-ROM 编写块驱动, 如果你对上面的数据不能运行 ls 或者 cp 就毫无用处. Linux 支持一个文件系统模块的概念, 其软件接口声明了不同操作, 可以在一个文件系统节点, 目录, 文件和超级块上进行操作. 对一个程序员来说, 居然需要编写一个文件系统模块是非常不常见的, 因为官方内核已经包含了大部分重要的文件系统类型的代码.

1.4. 安全问题

安全是当今重要性不断增长的关注点. 我们将讨论安全相关的问题, 在它们在本书中出现时. 有几个通用的概念, 却值得现在提一下.

系统中任何安全检查都由内核代码强加上去. 如果内核有安全漏洞, 系统作为一个整体就有漏洞. 在官方的内核发布里, 只有一个有授权的用户可以加载模块; 系统调用 init_module 检查调用进程是否是有权加载模块到内核里. 因此, 当运行一个官方内核时, 只有超级用户[]或者一个成功获得特权的入侵者, 才可以利用特权代码的能力.

在可能时, 驱动编写者应当避免将安全策略编到他们的代码中. 安全是一个策略问题, 最好在内核高层来处理, 在系统管理员的控制下. 但是, 常有例外.

作为一个设备驱动编写者, 你应当知道在什么情形下, 某些类型的设备存取可能反面地影响系统作为一个整体, 并且应当提供足够地控制. 例如, 会影响全局资源的设备操作( 例如设置一条中断线 ), 可能会损坏硬件( 例如, 加载固件 ), 或者它可能会影响其他用户( 例如设置一个磁带驱动的缺省的块大小 ), 常常是只对有足够授权的用户, 并且这种检查必须由驱动自身进行.

驱动编写者也必须要小心, 当然, 来避免引入安全 bug. C 编程语言使得易于犯下几类的错误. 例如, 许多现今的安全问题是由于缓冲区覆盖引起, 它是由于程序员忘记检查有多少数据写入缓冲区, 数据在缓冲区结尾之外结束, 因此覆盖了无关的数据. 这样的错误可能会危及整个系统的安全, 必须避免. 幸运的是, 在设备驱动上下文中避免这样的错误经常是相对容易的, 这里对用户的接口经过精细定义并被高度地控制.

一些其他的通用的安全观念也值得牢记. 任何从用户进程接收的输入应当以极大的怀疑态度来对待; 除非你能核实它, 否则不要信任它. 小心对待未初始化的内存; 从内核获取的任何内存应当清零或者在其对用户进程或设备可用之前进行初始化. 否则, 可能发生信息泄漏( 数据, 密码的暴露等等 ). 如果你的设备解析发送给它的数据, 要确保用户不能发送任何能危及系统的东西. 最后, 考虑一下设备操作的可能后果; 如果有特定的操作( 例如, 加载一个适配卡的固件或者格式化一个磁盘 ), 能影响到系统的, 这些操作应该完全确定地要限制在授权的用户中.

也要小心, 当从第三方接收软件时, 特别是与内核有关: 因为每个人都可以接触到源码, 每个人都可以分拆和重组东西. 尽管你能够信任在你的发布中的预编译的内核, 你应当避免运行一个由不能信任的朋友编译的内核 -- 如果你不能作为 root 运行预编译的二进制文件, 那么你最好不要运行一个预编译的内核. 例如, 一个经过了恶意修改的内核可能会允许任何人加载模块, 这样就通过 init_module 开启了一个不想要的后门.

注意, Linux 内核可以编译成不支持任何属于模块的东西, 因此关闭了任何模块相关的安全漏洞. 在这种情况下, 当然, 所有需要的驱动必须直接建立到内核自身内部. 在 2.2 和以后的内核, 也可以在系统启动之后, 通过 capability 机制来禁止内核模块的加载.



[] 从技术上讲, 只有具有 CAP_SYS_MODULE 权利的人才可以进行这个操作. 我们第 6 章讨论 capabilities .

1.5. 版本编号

在深入编程之前, 我们应当对 Linux 使用的版本编号方法和本书涉及的版本做些说明.

首先, 注意的是在 Linux 系统中使用的每一个软件包有自己的发行版本号, 它们之间存在相互依赖性: 你需要一个包的特别的版本来运行另外一个包的特别版本. Linux 发布的创建者常常要处理匹配软件包的繁琐问题, 这样用户从一个已打包好的发布中安装就不需要处理版本号的问题了. 另外, 那些替换和更新系统软件的人, 就要自己处理这个问题了. 幸运的是, 几乎所有的现代发布支持单个软件包的更新, 通过检查软件包之间的依赖性; 发布的软件包管理器通常不允许更新, 直到满足了依赖性.

为了运行我们在讨论过程中介绍的例子, 你除了 2.6 内核要求的之外不需要任何工具的特别版本; 任何近期的 Linux 发布都可以用来运行我们的例子. 我们不详述特别的要求, 因为你内核源码中的文件 Document/Changes 是这种信息的最好的来源, 如果你遇到任何问题.

至于说内核, 偶数的内核版本( 就是说, 2.6.x )是稳定的, 用来做通用的发布. 奇数版本( 例如 2.7.x ), 相反, 是开发快照并且是非常短暂的; 它们的最新版本代表了开发的当前状态, 但是会在几天内就过时了.

本书涵盖内核 2.6 版本. 我们的目标是为设备驱动编写者展示 2.6.10 内核的所有可用的特性, 这是我们在编写本书时的内核版本. 本书的这一版不涉及内核的其他版本. 你们有人感兴趣的话, 本书第 2 版详细涵盖 2.0 到 2.4 版本. 那个版本依然在 htt://lwn.net/Kernel/LDD2 在线获取到.

内核程序员应当明白到 2.6 内核的开发过程的改变. 2.6 系列现在接受之前可能认为对一个"稳定"的内核太大的更改. 在其他的方面, 这意味着内核内部编程接口可能改变, 因此潜在地会使本书某些部分过时; 基于这个原因, 伴随着文本的例子代码已知可以在 2.6.10 上运行, 但是某些模块没有在之前的版本上编译. 想紧跟内核编程变化的程序员最好加入邮件列表, 并且利用列在参考书目中的网站. 也有一个网页在 上维护, 它包含自本书出版以来的 API 改变的信息.

本文不特别地谈论奇数内核版本. 普通用户不会有理由运行开发中的内核. 试验新特性的开发者, 但是, 想运行最新的开发版本. 他们常常不停更新到最新的版本, 来收集 bug 的修正和新的特性实现. 但是注意, 试验中的内核没有任何保障[], 如果你由于一个非当前的奇数版本内核的一个 bug 而引起的问题, 没人可以帮你. 那些运行奇数版本内核的人常常是足够熟练的深入到代码中, 不需要一本教科书, 这也是我们为什么不谈论开发中的内核的另一个原因.

Linux 的另一个特性是它是平台独立的操作系统, 并非仅仅是" PC 克隆体的一种 Unix 克隆 ", 更多的: 它当前支持大约 20 种体系. 本书是尽可能地平台独立, 所有的代码例子至少是在 x86 和 x86-64 平台上测试过. 因为代码已经在 32-bit 和 64-bit 处理器上测试过, 它应当能够在所有其他平台上编译和运行. 如同你可能期望地, 依赖特殊硬件的代码例子不会在所有支持的平台上运行, 但是这个通常在源码里说明了.



[] 注意, 对于偶数版本的内核也不存在保证, 除非你依靠一个同意提供它自己的担保的商业供应商.

.6. 版权条款

Linux 是以 GNU 通用公共版权( GPL )的版本 2 作为许可的, 它来自自由软件基金的 GNU 项目. GPL 允许任何人重发布, 甚至是销售, GPL 涵盖的产品, 只要接收方对源码能存取并且能够行使同样的权力. 另外, 任何源自使用 GPL 产品的软件产品, 如果它是完全的重新发布, 必须置于 GPL 之下发行.

这样一个许可的主要目的是允许知识的增长, 通过同意每个人去任意修改程序; 同时, 销售软件给公众的人仍然可以做他们的工作. 尽管这是一个简单的目标, 关于 GPL 和它的使用存在着从未结束的讨论. 如果你想阅读这个许可证, 你能够在你的系统中几个地方发现它, 包括你的内核源码树的目录中的 COPYING 文件.

供应商常常询问他们是否可以只发布二进制形式的内核模块. 对这个问题的答案已是有意让它模糊不清. 二进制模块的发布 -- 只要它们依附预已公布的内核接口 -- 至今已是被接受了. 但是内核的版权由许多开发者持有, 并且他们不是全都同意内核模块不是衍生产品. 如果你或者你的雇主想在非自由的许可下发布内核模块, 你真正需要的是和你的法律顾问讨论. 请注意内核开发者不会对于在内核发行之间破坏二进制模块有任何疑虑, 甚至在一个稳定的内核系列之间. 如果它根本上是可能的, 你和你的用户最好以自由软件的方式发行你的模块.

如果你想你的代码进入主流内核, 或者如果你的代码需要对内核的补丁, 你在发行代码时, 必须立刻使用一个 GPL 兼容的许可. 尽管个人使用你的改变不需要强加 GPL, 如果你发布你的代码, 你必须包含你的代码到发布里面 -- 要求你的软件包的人必须被允许任意重建二进制的内容.

至于本书, 大部分的代码是可自由地重新发布, 要么是源码形式, 要么是二进制形式, 我们和 O' Reilly 都不保留任何权利对任何的衍生的工作. 所有的程序都可从 ftp://ftp.ora.com/pub/examples/linux/drivers/ 得到, 详尽的版权条款在相同目录中的 LICENSE 文件里阐述.

1.7. 加入内核开发社团

在你开始为 Linux 内核编写模块时, 你就成为一个开发者大社团的一部分. 在这个社团中, 你不仅会发现有人忙碌于类似工作, 还有一群特别投入的工程师努力使 Linux 成为更好的系统. 这些人可以是帮助, 理念, 以及关键的审查的来源, 以及他们将是你愿意求助的第一类人, 当你在寻找一个新驱动的测试者.

对于 Linux 内核开发者, 中心的汇聚点是 Linux 内核邮件列表. 所有主要的内核开发者, 从 Linus Torvalds 到其他人, 都订阅这个列表. 请注意这个列表不适合心力衰弱的人: 每天或者几天内的书写流量可能多至 200 条消息. 但是, 随这个列表之后的是对那些感兴趣于内核开发的人重要的东西; 它也是一个最高品质的资源, 对那些需要内核开发帮助的人.

为加入 Linux 内核列表, 遵照在 Linux 内核邮件列表 FAQ: 中的指示. 阅读这个 FAQ 的剩余部分, 当你熟悉它时; 那里有大量的有用的信息. Linux 内核开发者都是忙碌的人, 他们更多地愿意帮助那些已经清楚地首先完成了属于自己的那部分工作的人.

1.8. 本书的内容

从这里开始, 我们进入内核编程的世界. 第 2 章介绍了模块化, 解释了内部的秘密以及展示了运行模块的代码. 第 2 章谈论字符驱动以及展示一个基于内存的设备驱动的代码, 出于乐趣对它读写. 使用内存作为设备的硬件基础使得任何人可以不用要求特殊的硬件来运行代码.

调试技术对程序员是必备的工具, 第 4 章介绍它. 对那些想分析当前内核的人同样重要的是并发的管理和竞争情况. 第 5 章关注的是由于并发存取资源而导致的问题, 并且介绍控制并发的 Linux 机制.

在具备了调试和并发管理的能力下, 我们转向字符驱动的高级特性, 例如阻塞操作, selet 的使用, 以及重要的 ioctl 调用; 这是第 6 章的主题.

在处理硬件管理之前, 我们研究多一点内核软件接口: 第 7 章展示了内核中是如何管理时间的, 第 8 章讲解了内存分配.

接下来我们集中到硬件. 第 9 章描述了 I/O 口的管理和设备上的内存缓存; 随后是中断处理, 在 第 10 章. 不幸的是, 不是每个人都能运行这些章节中的例子代码, 因为确实需要某些硬件来测试软件接口中断. 我们尽力保持需要的硬件支持到最小程度, 但是你仍然需要某些硬件, 例如标准并口, 来使用这些章节的例子代码.

第 11 章涉及内核数据类型的使用, 以及编写可移植代码.

本书的第 2 半专注于更高级的主题. 我们从深入硬件内部开始, 特别的, 是特殊外设总线功能. 第 12 章涉及编写 PCI 设备驱动, 第 13 章检验使用 USB 设备的 API.

具有了对外设总线的理解, 我们详细看一下 Linux 设备模型, 这是内核使用的抽象层来描述它管理的硬件和软件资源. 第 14 章是一个自底向上的设备模型框架的考察, 从 kobject 类型开始以及从那里进一步进行. 它涉及设备模型与真实设备的集成; 接下来是利用这些知识来接触如热插拔设备和电源管理等主题.

在第 15 章, 我们转移到 Linux 的内存管理. 这一章显示如何映射系统内存到用户空间( mmap 系统调用 ), 映射用户内存到内核空间( 使用 get_user_pages ), 以及如何映射任何一种内存到设备空间( 进行 直接内存存取 [DMA] 操作 ).

我们对内存的理解将对下面两章是有用的, 它们涉及到其他主要的驱动类型. 第 16 章介绍了块驱动, 并展示了与我们到现在为止已遇到过的字符驱动的区别. 第 17 章进入网络驱动的编写. 我们最后是讨论串行驱动(第 18 章)和一个参考书目.

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