《Essential Linux Device Drivers》第1章

来源:互联网 发布:淘宝客app怎么下载 编辑:程序博客网 时间:2024/04/30 15:06
 

1章 简介

Linux具有诱人的魅力,它是一个由全世界不同民族、不同信仰、不同性别的人共同参与和协作的国际性项目。免费提供源代码以及容易理解的类UNIX应用程序编程环境,促成了Linux的重大成功。通过因特网从专家处即时获得的质量的免费支持发挥了重要作用,这促使了一个庞大的Linux社区的形成

由于在技术方面开发人员可以获得所有源码,并由此给出一些创新方案,因此他们感到无比振奋例如,你可以hack[]Linux的源码,并对其进行定制,以让其在你的设备上于几秒钟之内启动使用一个专有操作系统难完成这样的壮举。

1.1 演化

Linux1991年起源于一位名为Linus Torvalds的芬兰大学生的业余爱好,但很快就发展成为受全球欢迎的先进的操作系统。Linux第一次发布时仅支持Intel 386处理器,但是后来,内核在复杂性上逐步增加,可以支持众多的体系结构、多处理机系统和高性能集群。Linux支持很多CPU,主要支持的一些体系结构有x86IA64 ARMPowerPC Alphas390MIPSSPARCLinux已经被移植到成千上万个基于这些处理器的硬件平台之上。与此同时,内核还在不断完善,并以飞快的脚步发展。

虽然开始的时候只是一个桌面操作系统,但目前Linux已经进入嵌入式和企业领域,并渗入我们的日常生活。当你按掌上电脑的按键、调节你的遥控器到天气频道或者在医院接受体检的时候,很有可能某些Linux代码正在为你提供服务。一方面,Linux有很大的技术优势,另一方面,Linux可以免费获得,这两方面对它的变革发挥着重大的作用。由于能够降低消费类电子产品的价格,Linux已成为该领域一个很好的选择,因为专有操作系统的价格有时候比硬件本身的价格还贵。

1.2 GNU copyleft

GNU项目(GNUGNUs Not UNIX的递归缩写,即GNU不是UNIXLinux诞生得更早,它发起的目标是定制一个免费的UNIX操作系统。一个完整的GNU操作系统包含Linux内核,但也包含一些其他组件,如库、编译器和实用工具(utility因此,基于Linux的计算机更准确称呼应该是 GNU /Linux系统。GNU/Linux系统所有组成部分建立自由软件之上

自由软件的种类有很多,其中一种是公共领域public domain的软件。公共领域发布的软件没有版权,对它的使用也不会强加任何限制。可以免费使用它,随意修改它,甚至限制你修改后代码的发布你会发现没有限制”在下行过程中反而引入了限制

GNU项目的主要支撑者——自由软件基金会,创造了GNU公共许可GNU Public LicenseGPL),称为copyleft[]防止有人中途将自由软件转化为专有软件如果某人修改copyleft的软件,就必须以copyleft的方式分享他的软件GNU系统中的Linux内核以及像GNU编译器(GNU Compiler CollectionGCC等大部分组件都以GPL发布因此,如果你修改内核,你就必须在社区分享此修改基本上,你必须以copyleft的形式将授予你的权利传递出去。

 

Linux内核基于GPL2版。在内核社区,人们一直在争论是否应该支持GPL的最新版本GPLv3。目前的趋势似乎是反对采取GPLv3

 

通过系统调用访问内核服务的Linux应用程序没有被看作衍生的工作,因此并不受限于GPL而库则采用GNU轻量级通用公共许可证GNU Lesser General Public LicenseLGPL,其限制要少于GPL专有软件也允许LGPL下的库动态链接

1.3 kernel.org

Linux内核源代码的主要存放仓库是www.kernel.org。该网站包含所有已经发布的内核版本,世界上许多地方都有kernel.org的镜像网站。

除了已经发布的版本以外,kernel.org还包含了由一线开发人员提供的补丁,这些补丁可以作为未来稳定版本的试验床(test bed)。补丁是一种文本文件,其包含了开发树中原始版本和开发人员提供的新版本之间的源码差异。由Linux内核维护领袖Andrew Morton定期提供的-mm补丁是一种流行的补丁(可在kernel.org上获得),在该补丁中,我们可以找到在主线源代码树中尚未提供的实验性的功能。kernel.org上另一个会定期公布的补丁是由Ingo Molnar维护的-rtreal time,实时)补丁。-rt补丁的一些功能已经被融入Linux内核主线中。

1.4 邮件列表和论坛

Linux内核邮件列表Linux Kernel Mailing ListLKML)是开发人员就设计问题进行辩论并决定Linux未来要包含的功能的论坛。你可以在www.lkml.org看到实时的邮件列表。Linux内核目前包含由遍布于世界各地的成千上万的开发人员贡献的数百万行代码,正是lkml将他们连在一起。

LKML并不是为了解答一般的Linux问题,其基本规则是只能张贴与内核有关的、以前没有被回答过并且在众所周知的文档中没有提及的问题。如果在你编译Linux应用程序的时候C编译器崩溃了,你应该在其他地方张贴这样的问题。

LKML中讨论的一些问题线索甚至比畅销的《纽约时报》更有趣,花几个小时浏览LKML的压缩包将有助于你洞察Linux内核背后的理念。

内核的大部分的子项目都拥有自己的邮件列表。因此,如果你正在开发Flash设备的驱动,就可以订阅linux-mtd邮件列表;如果你发现了Linux USB存储设备驱动的bug,就可以在linux-usb-devel 邮件列表发起一个线索。在以后的几章中,我们就参考了相关的邮件列表。

在各种论坛上,来自世界各地的内核专家会聚集于同一个屋檐下共同商讨Linux技术。加拿大渥太华每年举行一次的Linux Symposium就是这样的一个会议。其他的还包括在德国举行的Linux Kongress和在澳大利亚组织举行的linux.conf.au。也有一些Linux论坛聚集了众多的商界领袖,他们在论坛上分享真知灼见,其中的一个例子就是每年在北美举行的LinuxWorld Conference and Expo

http://lwn.net/上可以获得Linux开发社区的最新消息。如果你只是想简单地了解内核的最新发布版,不想阅读太多的资料,http://lwn.net/可能是一个好地方。另一个网络社区http://kerneltrap.org/则讨论当前的内核议题。

在每个主线Linux内核版本发布中,你都会看到重大的改进,如内核抢占、不受限于锁(lock-free)的读操作、分担中断处理工作的新服务或者新体系结构的支持。因此,请一直跟踪邮件列表、网站和论坛,以保证自己在Linux技术上厚重的份量。

1.5 Linux发行版

一个GNU/Linux系统除了内核以外,还由大量的实用工具(utility)、程序、库和工具(tool)组成,因此,获得和正确安装所有的组件将是一项艰巨的任务。而Linux发行版则有序地将这些组件进行了分类,并捆绑成相应的包。一个典型的发行版包含了数以千计现成的包。这使得用户无需担心下载不到正确版本的程序,也无需关心程序间的依赖问题。

因为打包是GNU许可证范围内的一种有效的赚钱方式,因此,目前的市场上诞生了不少Linux发行版。其中,Red Hat/FedoraDebianSuSESlackwareGentooUbuntuMandriva这些发行版定位于桌面用户;而MontaVistaTimeSysWind River发行版则面向嵌入式系统开发。通常,嵌入式Linux的发行版还包括一套可动态配置的紧凑的应用程序集,以便针对资源的限制为系统进行量体裁衣。

除了打包以外,发行版还为内核的开发提供了增值服务。因此,许多项目都开始于发行版提供的内核而非kernel.org发布的官方内核,之所以要这样做,理由有如下几个。

n         Linux发行版遵守设备工业领域的标准,因此是开发的更好起点。特殊兴趣组Special Interest GroupSIG)已经成立,其目的是促进Linux在各个领域的应用。消费电子产品Linux论坛Consumer Electronics Linux ForumCELF,网址为www.celinuxforum.org)集中于消费类电子领域的Linux应用。CELF规范定义了一些功能的支持等级,如可扩展性、快速启动、片上执行以及电源管理等。开源开发实验室Open Source Development LabOSDL,网址为www.osdl.org)则集中于电信级设备。OSDL电信级LinuxCarrier Grade LinuxCGL)规范包含了对稳定性、高可用性、运行时补丁以及增强的错误恢复能力的诠释,这些问题在电信领域非常重要。

n         主线内核版本可能并未包含对用户所选择的嵌入式控制器的充分支持,即使控制器建立在内核所支持的CPU核心之上。但是,Linux发行版可能包含了控制器内部所有外设的设备驱动程序。

n         在内核开发过程中你计划使用的调试工具可能不包含在主线内核中。例如,内核并不包含内建的调试器支持。如果想在内核开发过程中使用内建的调试器,用户必须单独下载并打上相应的补丁。如果针对用户内核版本的测试过的补丁并不易用,用户必须忍受更多的麻烦。而发行版则包含了很多有用的调试功能,所以你可以立即开始使用它们。

n         一些发行版提供了法律补偿,让你的公司无须为任何由于内核bug所产生的诉讼承担责任。

n         发行版往往会对他们发布的内核进行较多的测试[]

n         用户可以从内核发行版的供应商处购买到他们提供的服务以及软件包支持。

1.6 查看源代码

在进入内核领域进行热身之前,让我们先下载Linux源代码,学会打补丁,并查看内核源码树的分布。

首先,到www.kernel.org下载最新的稳定的源代码(源代码以gzip.zip)和bzip2 .bz2)两种压缩格式提供),然后进行解压缩。在下列命令中,请用最新的内核版本号(例如2.6.23)代替X.Y.Z

bash> cd /usr/src

bash> wget www.kernel.org/pub/linux/kernel/vX.Y/linux-X.Y.Z.tar.bz2

...

bash> tar xvfj linux-X.Y.Z.tar.bz2

 

现在,你已经拥有位于/usr/src/linux-X.Y.Z/目录的源代码树,下面将通过打-mm补丁Andrew Morton进行一项增加一些实验性功能的实验。

运行如下命令:

bash> cd /usr/src

bash> wget www.kernel.org/pub/linux/kernel/people/akpm/patches/X.Y/X.Y.Z/X.Y.Z-

mm2/X.Y.Z-mm2.bz2

 

打上这个补丁:

bash> cd /usr/src/linux-X.Y.Z/

bash> bzip2 -dc ../X.Y.Z-mm2.bz2 | patch -p1

 

命令中的-dc选项意味着让bzip2将指定的文件解压缩到标准输出。它将以管道方式输送到补丁程序,补丁程序会将补丁中的代码改变应用到源码树中每个需要修改的文件。

如果你需要打多个补丁,则要注意打补丁的顺序。为了生成一个包含X.Y.Z-aa-bb补丁的内核,应首先下载X.Y.Z内核的完整源代码,再打上X.Y.Z-aa补丁,最后打上X.Y.Z-aa-bb补丁。

补丁提交

使用diff命令可以为你完成的内核更改产生补丁:

bash> diff –Nur /path/to/original/kernel /path/to/your/kernel > changes.patch

要注意的是,在diff命令中,原始内核的路径应该放在修改后内核路径的前面。基于2.6内核补丁提交约定,你需要在补丁的最后加上这样的一行:

Signed-off-by: Name <Email>

有了这一行,你阐明了这些代码是由你编写的,你拥有贡献它的权利。

你现在就可以在相关的邮件列表(如LKML)中张贴你的补丁了

文档Documentation/SubmittingPatches包含了一个创建和提交补丁的向导,而Documentation/applying-patches.txt是一个教你如何打补丁的教程。

 

 

 

 

 

 

 

 

 

 

 

 


现在,打好补丁后的/usr/src/linux-X.Y.Z/已经准备好投入使用了,接下来,我们花一些时间来查看内核源代码树的结构。进入内核源代码树的根目录并列出它的子目录:

(1) arch。该目录包含了体系结构相关的文件。可以在arch/目录下看到针对ARMMotorola 68Ks390MIPSAlphaSPARCIA64等处理器的子目录。

(2) block.。该目录主要包含块存储设备I/O调度算法的实现。

(3) cryto。该目录实现了密码操作以及加密相关的API,它们可被应用于WiFi设备驱动的加密算法等场合。

(4) Documentation该目录包含了内核中各个子系统的简描述,它将是你挖掘内核相关问题答案的第一站。

(5) drivers。这个目录包含了大量设备类和外设控制器的驱动,包括字符、串口、内置集成电路Inter-Integrated CircuitI2C)、个人计算机存储卡国际联盟Personal Computer Memory Card International AssciationPCMCIA)、外围组件互连Peripheral Component InterconnectPCI)、通用串行总线Universal Serial BusUSB)、视频、音频、块、集成驱动电子设备Integrated Drive ElectronicsIDE)、小型计算机系统接口Small Computer System InterfaceSCSI)、CD-ROM、网络适配器、异步传输模式Asynchronous Transfer ModeATM)、蓝牙和内存技术设备Memory Technology DeviceMTD)等。每一类设备对应drivers/下面的一个子目录,例如PCMCIA驱动的源代码位于drivers/pcmcia/目录,MTD驱动位于drivers/mtd/目录。drivers/下的这些子目录是本书的主要议题。

(6) fs。这个目录包含了EXT3EXT4reiserfsFATVFATsysfsprocfsisofsJFFS2XFSNTFSNFS等文件系统的实现。

(7) include。内核头文件位于此目录。该目录下以asm开头的子目录包含了体系结构相关的头文件,比如include/asm-x86/子目录包含了X86体系结构的头文件,include/asm-arm/包含了ARM体系结构的头文件。

(8) init。这个目录包含了从高层角度看到的初始化和启动代码。

(9) ipc。这个目录包含了对消息队列、信号、共享内存等进程间通信(IPC)机制的支持。

(10) kernel。本目录包含基本内核中体系结构无关的部分。

(11) lib。通用内核对象(kobject)处理、循环冗余校验(CRC)算法等库函数的实现位于此目录。

(12) mm。这个目录包含了内存管理的实现。

(13) net。该目录包含了网络协议,包括Internet协议第4版(IPv4)、IPv6Internet网络分组交换协议(IPX)、蓝牙、ATM、红外、链路访问过程平衡(LAPB)以及逻辑链路控制(LLC)。

(14) scripts。内核编译过程中要使用的脚本位于此目录。

(15) security。这个目录包含了针对安全的框架。

(16) soundLinux音频子系统位于此目录。

(17) usr。此目录包含了initramfs 的实现。

统一的x86体系结构源码树

2.6.24内核开始,i386x86_64 32位的i386系统的64位的堂弟)体系结构源码树已被统一纳入共同的arch/x86/目录。如果你使用的是比2.6.24老的内核,请用arch/i386 /代替本书中所说的arch/x86 /目录。同样地,用include/asm-x86/ 代替include/asm-i386 / 。此外,这些目录中的一些文件名也发生了变化。

 

 

 

 

 

 


阅读这些庞大的目录是一项艰巨的任务,表1-1中的一些工具可以帮助你更方便地浏览内核源码。

 

 

1-1 源码浏览工具

工具

描述

lxr

Linux交叉引用(lxr),可从http://lxr.sourceforge.net/下载,它可以让你通过Web浏览器浏览源代码,它为内核符号的定义和使用提供了超链接。

cscope

cscope(网址为http://cscope.sourceforge.net/)可用于为内核源码树建立一个符号数据库,通过它,你可以快速地定位到声明、定义以及正则表达式等。cscope可能不如lxr这般多才多艺,但是它很灵活,使用你最喜欢的文本编辑器(而不是浏览器)你就可以使用搜索功能。在内核源代码的根目录,运行cscope - qkRv命令就可建立交叉引用数据库。- q选项将产生更多的索引信息以加快搜索速度,但是它会消耗更多的额外的初始启动时间。-k要求cscope调整它的行为以适应内核源代码,- R选项意味着递归遍历子目录。通过man 手册页,你可以获得详细的调用语法。

ctags/etags

Ctags实用工具(网址为http://ctags.sourceforge.net/)可用于为许多语言产生交叉引用的标签。通过它,你可以在vi等编辑器中定位源码树中的符号和函数定义。从内核源码树的根目录运行“make tags”可以为所有源文件建立标签。

Etags实用工具为emacs编辑器产生相似的索引信息。运行“make TAGS 可以为内核源文件创建标签。

实用工具

grepfindsdiffstraceodddmaketarfileobjdump等工具。

GCC选项

实用-E选项可以让GCC产生预处理源代码。预处理代码包含头文件的扩展,并减少了为了扩展多层宏定义在多个嵌套的头文件间进行跳跃的需要。下面是一个预处理drivers/char/mydrv.c并产生扩展后的输出文件mydrv.i的例子:

bash> gcc -E drivers/char/mydrv.c -D__KERNEL__ -Iinclude

-Iinclude/asm-x86/mach-default > mydrv.i

使用-I选项可以指定你的代码说依赖的include的路径。

使用-S选项可以让GCC产生汇编列表。下面的命令可以为drivers/char/mydrv.c产生汇编文件mydrv.s

bash> gcc -S drivers/char/mydrv.c -D__KERNEL__ -Iinclude

-Ianother/include/path

1.7 编译内核

现在,你已经拥有了内核源代码布局的理念,现在,让我们进行一项细小的内核代码修改,并编译和运行Linux。进入顶层的init /目录,对初始化文件main.c进行一项小的修改,即在start_kernel()函数的开头加上一个打印语句,宣布你对北极熊的喜爱:

asmlinkage void __init start_kernel(void)

{

    char *command_line;

    extern struct kernel_param __start___param[],

           __stop___param[];

 

+   printk("Penguins are cute, but so are polar bears/n");

 

    /* ... */

 

    rest_init();

}

       

你现在已经为编译内核揭开了序幕,进入内核源代码并运行清除命令:

bash> cd /usr/src/linux-X.Y.Z/

bash> make clean

       

接下来,进行内核配置工作。这一步的主要工作是选择你要编译的组件,可以选择需要的组件以静态还是动态链接的方式编译进内核:

bash> make menuconfig

 

menuconfig是内核配置菜单的文本界面,使用make xconfig可以产生一个图形界面。你所选择的配置信息被存放在内核源码树根目录的.config文件中。如果你不想从头开始进行配置,可以使用 arch/your-arch/defconfig或者arch/your-arch/configs/your-machine_defconfig(如果你的体系结构中包含一些可被支持的平台的话)文件作为起点。因此,如果你正在为32x86体系结构编译内核,则运行如下命令:

bash> cp arch/x86/configs/i386_defconfig .config

 

编译内核并产生一个压缩的启动映像:

bash> make bzImage

 

现在,内核映像将位于arch/x86/boot/bzImage,更新你的启动分区:

bash> cp arch/x86/boot/bzImage /boot/vmlinuz

 

你也许需要修改根据新的启动映像更新你的bootloader。如果你正在使用GRUB这个bootloader,它将自动完成配置;如果你正在使用LILO,则增加一个标记:

bash> /sbin/lilo

Added linux *

 

最后,重新启动Linux并启动到你的新内核:

bash> reboot

 

启动后的第一条信息显示了你对北极熊的热爱。

1.8 可加载的模块

由于Linux可运行于各种各样的体系结构,并支持无数的I/O设备,因此把所有要支持的设备都直接编译进内核并不可行。发行版通常包含一个最小的内核映像,而将其他的功能以模块的形式提供。在运行的时候,模块会按需动态加载。

要产生模块,则进入内核源码树根目录并编译:

bash> cd /usr/src/linux-X.Y.Z/

bash> make modules

 

运行如下命令安装编译生成的模块:

bash> make modules_install

 

此命令将在/lib/modules/X.Y.Z/kernel/目录下创建一个内核源代码目录结构,并将可加载的模块放入其中。它也将激活depmod实用工具,以便产生模块依赖文件/lib/modules/X.Y.Z/modules.dep

如下实用工具可用于操纵模块:insmodrmmodlsmodmodprobemodinfodepmod。前两个实用工具用于加载和移除模块,而lsmod用于列出目前已经加载的模块,modprobeinsmod的一个更智能的版本,它通过分析/lib/modules/X.Y.Z/modules.dep文件来加载相关模块。例如,假定你需要挂载一个USB笔驱动器上的VFAT分区,使用modprobe加载VFAT文件系统驱动[]

bash> modprobe vfat

bash> lsmod

Module       Size     Used by

vfat         14208    0

fat          49052    1 vfat

nls_base     9728     2 vfat, fat

 

lsmod命令的输出可以看出,modprobe加载了3个模块而不是1个。Modprobe首先发现它不得不加载/lib/modules/X.Y.Z/kernel/fs/vfat/vfat.ko,当它查看/lib/modules/X.Y.Z/modules.dep模块依赖文件的时候,它发现了如下内核并由此意识到自己必须首先加载另外2个模块:

/lib/modules/X.Y.Z/kernel/fs/vfat/vfat.ko:

/lib/modules/X.Y.Z/kernel/fs/fat/fat.ko

/lib/modules/X.Y.Z/kernel/fs/nls/nls_base.ko

 

于是它首先加载了fat.konls_base.ko2个模块,之后加载vfat.ko,这样,你挂载VFAT分区时所需要的所有模块都被自动加载了。

使用modinfo实用工具可以提取模块的详细信息:    

bash> modinfo vfat

filename:      /lib/modules/X.Y.Z/kernel/fs/vfat/vfat.ko

license:       GPL

description:   VFAT filesystem support

...

depends:       fat, nls_base

 

为了将内核驱动编译为模块,在配置内核的时候,请将相应的菜单选择按钮置为<M>。本书中的大部分设备驱动例子以内核模块的形式实现。为了从mymodule.c源文件构造mymodule.ko模块,可以创建一个一行的Makefile文件,并且以如下方式执行它:

bash> cd /path/to/module-source/

bash> echo "obj-m += mymodule.ko" > Makefile

bash> make –C /path/to/kernel-sources/ M=`pwd` modules

make: Entering directory '/path/to/kernel-sources'

  Building modules, stage 2.

  MODPOST

  CC /path/to/module-sources/mymodule.mod.o

  LD [M] /path/to/module-sources/mymodule.ko

make: Leaving directory '/path/to/kernel-sources'

bash> insmod ./mymodule.ko

 

内核模块减小了内核的尺码,并缩短了开发—编译—测试的周期。对于一次修改而言,你仅仅需要重新编译特定的模块并重新加载它。在第21章中,我们将学习模块调试技术。将驱动设计为内核模块也有一些缺陷。不像内建的驱动,模块无法在系统启动时预留资源,在加载时预留资源成功的几率会减小。

1.9 整装待发

Linux已经跋涉了许多的地域,成为一门艺术。所以你可以基于它来学习操作系统的概念、处理器体系结构甚至工业领域。当你学习某一设备驱动子系统所使用的技术时,可以更深入地探索其背后潜在的设计由来。

在没有明确说明的情况下,书中都假定为32x86体系结构。但是,本书也考虑到了一个事实:你更有可能为嵌入式设备而非传统的PC兼容的系统编写驱动程序。因此,对于串口驱动一章,讲解了2个设备:一个PC衍生器件上的触摸控制器和一个手机上的UART。对于I2C设备驱动程序一章,则讲解了PC系统中的EEPROM和嵌入式设备中的实时钟。本书也将介绍内核为大多数设备驱动类所提供的核心基础设施,它们隐藏了设备驱动程序的体系结构相关性。

在本书接近尾声的第21章讨论了设备驱动的调试技术。你可能会发现,在阅读本书的过程中,开发驱动时提前阅读该章会很有用。

本书基于2.6内核,它包含了对2.4内核的大量改变,覆盖了所有的主要的子系统。因此,希望你已经在你的系统中安装了基于2.6Linux,并开始用内核源代码进行实验。基于以下两个主要的原因,本书的每一章都充分地指出了相关的内核源文件:

(1) 因为内核中的每个驱动子系统包含成千上万行的源代码,所以本书只可能进行一个相对简单的呈现,查看源代码中与书中例子相关的真实的驱动将为你提供更广阔的视角。

(2) 在开发一个驱动之前,参考drivers/目录中现存的、与你的要求相似的驱动并以之作为起点是一个好主意。

因此,为了从本书中获得最大的益处,要通过频繁地浏览源码树和啃代码来熟悉内核。在代码探索的过程中,也要跟踪邮件列表的进展。

 

 


[] 意指对源码进行一些有针对性的修改。——译者注
[] 版权为copyright,这里故意用copyleft。——译者注
[]因为需要将内核冻结在一个版本上进行测试(而这个版本不是最新的),所以发行版内核经常会引入比其版本更新的官方内核的一些功能。
[]这个例子假定这个模块没有被内核自动加载。如果你在配置过程中启用了自动内核模块加载(CONFIG_KMOD) 选项,那么当侦测到缺失的子系统时,内核将自动以相应的参数运行modprobe。在第4章中,你将学习到模块自动加载的知识。