精通Linux设备驱动程序开发(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-17 02:10:02

点击下载

作者:(印)斯里克里斯汉·温卡特斯瓦兰(Sreekrishnan Venkateswaran)

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

精通Linux设备驱动程序开发

精通Linux设备驱动程序开发试读:

前言

20世纪90年代末,我们IBM的一群同事将Linux内核移植到了一种智能手表上。目标设备虽然微不足道,但是移植Linux的任务却相当艰巨。在当时,内核中还不存在MTD(Memory Technology Device,内存技术设备)子系统,这意味着为了让文件系统能够运行在这种手表的闪存中,我们不得不从头开发必要的存储驱动程序。由于当时内核的输入事件驱动程序接口尚未诞生,因此手表的触摸屏与用户应用程序的接口非常复杂。让X Windows运行在手表的LCD上十分困难,因为X Windows和帧缓冲设备驱动程序搭配得并不好。如果你戴着一块防水的Linux智能手表,却不能躺在浴缸里实时获得股票行情,那么这块手表还有什么用呢?Linux几年前就已集成了蓝牙技术,而当时我们却花费了数月的时间将一种专有的蓝牙协议栈移植到手表上,从而使得这种手表可以联上因特网。电源管理系统虽然只能从手表的电池中多“榨出”短短几个小时时间,但也算够意思了;实际上,为了解决这个棘手的问题,我们也没少花心思。那时候,Linux红外项目Linux-Infrared还不稳定,而为了使用红外键盘输入数据,我们不得不与其协议栈小心翼翼地周旋。最后,由于当时还没有能应用于消费类电子产品的成型的编译器发行版,我们也只能自己编个编译器,并交叉编译出一个紧凑的应用程序集。

时光飞逝,当年的小企鹅已经成长为一名健壮的少年。过去我们编写了成千上万行代码并耗时一年完成的任务,若采用现在的内核,只需要几天就可以完成。但是,要成为一名能巧妙地解决多种问题的高级内核工程师,就必须理解今天的Linux内核提供的各种功能和设施。关于本书

在Linux内核源代码树提供的各个子系统中,drivers/目录是其中最大的一个分支,它比其他子系统大数倍。随着各种新技术的广泛应用,内核中新的设备驱动程序的开发工作正在稳步加速。最新的Linux内核支持多达70余种设备驱动程序。

本书主要讲解如何编写Linux设备驱动程序,介绍了目前内核所支持的主要设备类型的设计与开发,其中包括当年我在将Linux移植到手表中时未遇到的设备。本书在讲解每类设备驱动程序的时候,都会先介绍与该驱动程序相关的技术,接着给出一个实际的开发例子,最后列出相关的内核源代码文件。在介绍Linux设备驱动程序之前,本书先介绍了内核以及Linux 2.6的重要特性,重点介绍了设备驱动程序编写者感兴趣的内核知识。读者对象

本书面向渴望在Linux内核上开发新设备驱动程序的中级程序员。要阅读本书,读者需要具备与操作系统相关的基本概念。比如,要知道什么是系统调用,理解为什么在内核开发中需要关注并发问题。本书假定读者已经下载了Linux,浏览过Linux内核源代码,并至少浏览过一些相关的文档。另外,读者必须能非常熟练地使用C语言。各章概述

前4章为阅读本书其他部分打下了基础,接下来的16章讨论了不同类型的Linux设备驱动程序,之后的第21章描述了设备驱动程序的调试技术,第22章讲解了维护和交付设备驱动程序的相关事宜,最后一章给出了当接到一个新设备驱动程序开发任务的时候,要首先查验的项目清单。

第1章是引言,简单介绍了Linux系统,讲解了下载内核源代码、进行小的代码修改以及建立可启动的Linux内核映像。

第2章引导读者轻松地进入Linux内核的内部结构,讲解了一些必要的内核概念。首先讲述了内核的启动进程,接下来描述了与驱动程序开发相关的内核API,譬如内核定时器、并发管理以及内存分配等。

第3章讲解了对驱动程序开发有用的一系列内核API。首先介绍了内核线程(它提供了一种在内核空间运行后台任务的能力),接下来讲解了一系列的辅助API(如链表、工作队列、完成函数、通知链等)。这些辅助API能简化代码,剔除内核中的冗余,有助于内核的长期维护。

第4章为掌握Linux设备驱动程序开发艺术打基础。这一章首先呈现一般的PC兼容系统和嵌入式设备的体系结构的鸟瞰图,介绍了设备和驱动程序,并讲解了中断处理和内核设备模型等基本的驱动程序概念。

第5章介绍了Linux字符设备驱动程序的体系架构,引入了几个新概念,譬如轮询、异步通知和I/O控制等。由于本书后面介绍的大多数设备都可以看作“超级”字符设备,所以这些概念也与后续章节密切相关。

第6章讲解了内核串行设备驱动程序的层次结构。

第7章讨论了内核中为键盘、鼠标和触摸屏控制器等输入设备服务的输入子系统。2

第8章讲解了通过IC总线或SMBus总线与系统连接的设备(如EEPROM)的驱动程序,同时也介绍了SPI总线和1-wire总线等其他串行接口。

第9章分析了PCMCIA子系统,讲授了如何编写含PCMCIA或CF组件的设备的驱动程序。

第10章描述了内核对PCI及其衍生总线设备的支持。

第11章探讨了USB的体系架构,并讲解了如何利用Linux内核USB子系统的API来开发USB设备驱动程序。

第12章讲解了Linux视频子系统,分析了内核提供的帧缓冲结构的优点,并给出了帧缓冲设备驱动程序的编写方法。

第13章描述了Linux音频子系统的架构,并给出了音频设备驱动程序的实现方法。

第14章集中描述存储设备(如硬盘)的驱动程序,并介绍Linux块子系统所支持的几种不同的I/O调度策略。

第15章分析了网络设备驱动程序,介绍内核中与网络相关的数据结构以及网络设备驱动程序与协议层接口的实现方法。

第16章描述了各种无线网络设备的驱动程序,如蓝牙、红外、无线局域网WiFi和蜂窝通信等。

第17章讲解了如何让闪存在嵌入式设备上运行起来,这一章最后讲解了PC上的FWH(FirmWare Hub,固件集线器)的驱动程序。

第18章介绍了嵌入式Linux,包括嵌入式设备中的引导装入程序、内核以及设备驱动程序等主要的固件组成。由于Linux在嵌入式领域越来越受欢迎,本书中介绍的Linux驱动程序开发技能极有可能应用于嵌入式领域。

第19章讲解了如何在用户空间驱动各种设备。一些设备驱动程序(尤其是那些重策略、轻性能的设备)更适合在用户空间被驱动。这一章也分析了Linux进程调度对用户空间设备驱动程序响应时间的影响。

第20章描述了之前尚未论及的设备驱动程序系统,如错误侦测和校验(EDAC)、火线接口以及ACPI等。

第21章讲解了用来调试Linux内核代码的各种调试工具,包括跟踪工具、内核探测器、崩溃转储和性能剖析器的使用方法。在开发Linux驱动程序的时候就可用到本章所学的驱动调试技能。

第22章给出了设备驱动程序软件开发生命周期的概况。

第23章给出了当开始进行一个新设备驱动程序开发工作时,应该查验的工作项目清单。本书最后是对“下一步该做什么”的思考。

设备驱动程序中有时候需要以汇编语言实现一些代码片段,因此,附录A介绍了Linux汇编编程的一些内容。x86系统上的一些设备驱动程序直接或间接地依赖于BIOS,因此,附录B讲解了Linux如何与BIOS交互。附录C描述了2.6内核提供的seq文件,它是用于监控和追踪数据点的辅助接口。

本书大体上根据设备和总线的复杂度进行组织,同时也结合了章与章之间互相关联的客观情况。我们从讲解基本的设备类型开始(如2字符设备、串口和输入设备),紧接着介绍简单的串行总线(如IC和SMBus),之后介绍PCMCIA、PCI和USB等外部I/O总线。由于视频、音频、块和网络设备通常通过这些I/O总线与处理器连接,因此在介绍完这些总线之后,还讲解了这些设备的驱动程序。之后面向嵌入式Linux,讲述了无线连网和闪存等技术。最后讨论了用户空间的设备驱动程序。内核版本

本书总体上紧跟2.6.23/2.6.24内核版本,书中列出的大部分代码都在2.6.23上测试过。如果读者使用的是更新的版本,请通过类似lwn.net的Linux网站了解内核自2.6.23/2.6.24后进行了哪些更改。本书网站

我特意建立了elinuxdd.com网站,提供与本书相关的更新和勘误等信息。本书约定

源代码、函数名和shell命令使用代码体。shell提示符为bash>。新名词使用楷体表示。

为了实现代码示例,一些章节修改了原始的内核源代码。为标识出这些修改,新添加的代码前添加了+,删除的代码前则添加了−。

为简单起见,本书有时使用了通用的路径名。因此,当遇到arch/your-arch/目录时,应该根据当前的编译情况进行转换。例如,如果你正在为x86体系结构编译内核,它应该转换为arch/x86/。类似地,如果你正在为ARM体系结构编译内核,include/asm-your-arch/就应该转换为include/asm-arm/。本书偶尔在文件名中使用*和X作为通配符。因此,如果书中要求查看include/linux/time*.h文件,读者就应该查看include/linux/下的time.h、timer.h、times.h和timex.h所有这些头文件。同样地,如果书中包含类似/dev/input/eventX或/sys/devices/platform/i8042/serioX/这样的文件名,要知道其中的X是指在当前系统配置情况下内核分配给设备的接口号。

→符号有时候会插入在命令或内核的输出之间,目的是附加注释。

为了紧凑地列出函数原型,本书偶尔使用了一些简单的正则表达式。例如,在10.4节中就用pci_[map|unmap|dma_sync]_single()替代了pci_map_single()、pci_unmap_single()和pci_dma_sync_single()。

有几章提到了用户空间的配置文件。例如,第2章打开了/etc/rc.sysinit文件,第16章引用了/etc/bluetooth/pin文件。这些文件的确切名称和位置都有可能因Linux发行版本的不同而有所变化。致谢

首先感谢Prentice Hall出版社负责本书的编辑们:Debra Williams Cauley、Anne Goebel和Keith Cline。没有她们的支持,本书就不可能完成。感谢Mark Taub,他最早对本书的主题产生兴趣并策划了这本书。

过去的10年里,很多人和事对我的研究帮助巨大:在各个Linux项目里一起共事的同事们、强健的内核源代码、邮件列表和互联网。这些都对我完成本书起了重大作用。

Linux Magazine的Martin Streicher邀请我参与该杂志的“超级发烧友”内核栏目,从而把我从一名全职程序员变成了一名兼职作者。我从他身上学习到了许多技术写作技巧,在此表示感谢!

我要特别感谢我的技术审稿人。Vamsi Krishna耐心地读完了初稿的每一章,提出了很多建设性的建议,令本书增色不少。Jim Lieb对书中的几章提供了有价值的反馈意见。Arnold Robbins浏览了开始的几章并且提供了富有见地的意见。

最后,我要感谢我的父母和妻子,感谢他们的爱与支持。我还要感谢我幼小的乖女儿,正是她不断地提醒我把时间投入到这本书上来:她摇摇晃晃地走来走去,那离奇的步伐极似企鹅。第1章引言

本章内容

演进

GNU Copyleft

kernel.org

邮件列表和论坛

Linux发行版

查看源代码

编译内核

可加载的模块

整装待发

Linux具有诱人的魅力,它是一个由全世界不同民族、不同信仰、不同性别的人共同参与和协作的国际性项目。Linux免费提供源代码,并且具有与Unix类似的为人们所熟悉的应用程序编程环境,这一切造就了它今天的巨大成功。通过互联网从专家处即时获得的高质量的免费支持也发挥了重要作用,它促成了一个庞大的Linux社区。

在技术方面,开发人员可以获得所有源码,并由此得出一些创新方案,他们因此感到无比振奋。譬如,你可以修改(hack)[1]Linux的源码,并做定制,让设备在几秒钟之内启动,而使用一个有专利的商业操作系统则很难完成这样的壮举。1.1 演进

1991年,一位名为Linus Torvalds的芬兰大学生开发了Linux操作系统。起初这只是他个人的爱好,但它很快就发展成为在全世界范围内广受欢迎的先进的操作系统。Linux第一次发布时仅支持Intel 386处理器,但是后来,它的内核复杂性逐步增加,可以支持众多的体系架构、多处理器硬件和高性能集群。Linux所支持的体系结构非常多,主要支持的一些硬件架构是x86、IA64、ARM、PowerPC、Alpha、s390、MIPS和SPARC。Linux已经被移植到成千上万的基于这些处理器的硬件平台之上。与此同时,其内核还在不断完善,系统性能也在飞速提升。

虽然开始的时候Linux只是一个桌面操作系统,但目前它已经进入嵌入式和企业级计算领域,并融入了我们的日常生活。当你按动掌上电脑的按键,用遥控器把电视切换到天气频道,或者在医院接受体检的时候,很有可能就在享受某些Linux代码提供的服务。技术优势和开源特性促进了Linux的演进。无论是试图开发不到100美元的计算机以改善世界贫困地区的教育状况,还是要降低消费类电子产品的价格,Linux如今都已成为一个绝好的选择,因为商业操作系统的价格有时候比计算机本身的价格更贵。1.2 GNU Copyleft

GNU工程比Linux更早诞生,发起它的目标是定制一个免费的类Unix操作系统(GNU是GNU’s Not Unix的递归缩写,意为“GNU不是Unix”。一个完整的GNU操作系统基于Linux内核构建,但也包含一些其他组件,如库、编译器和实用程序(utility)。因此,基于Linux的计算机的更准确称呼应该是GNU/Linux系统。GNU/Linux系统的所有组成部分都建立在免费软件之上。

免费软件有许多种,其中的一种是公共领域(public domain)软件。公共领域发布的软件没有版权,对于它的使用也不会强加任何限制。你可以免费使用它,随意修改它,甚至限制别人发布你修改后的代码。发现了吗?所谓“没有限制”条款居然暗含了对下游施加限制的权力。

GNU工程的主要发起者——自由软件基金会——创造了GNU公共许可证(GPL),它也被称为“版权左派”(copyleft)[2],以防止有人中途将免费软件转化为商业软件。谁修改了copyleft的软件,就必须以copyleft的方式分享他的软件。GNU系统中Linux内核以及大部分组件(如GNU编译器GCC)都以GPL发布。因此,如果你修改了内核,你就必须在社区分享此修改。实际上,你必须以copyleft的形式将授予你的权利传递出去。

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

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

Linux内核源代码主要存放在www.kernel.org。该网站包含所有已经发布的内核版本,世界各地有大量的kernel.org镜像网站。

除了已经发布的内核以外,kernel.org还包含了由一线开发人员提供的补丁,这些补丁可以作为未来稳定版本的试验平台。补丁是一种文本文件,包含了新开发版本和开发之初制订的原始版本之间的源码差异。由Linux内核第一维护人Andrew Morton定期提供的-mm补丁是一种很受欢迎的补丁。在该补丁中,我们可以找到在主线源代码树中尚未提供的实验性的功能。另一个会定期公布的补丁是由Ingo Molnar维护的-rt(realtime,实时)补丁。-rt补丁的数个功能已经被纳入Linux主线内核。1.4 邮件列表和论坛

LKML(Linux Kernel Mailing List,内核邮件列表)是开发人员就开发问题进行辩论并决定Linux未来要包含哪些功能的论坛。你可以在www.lkml.org看到实时的邮件列表。Linux内核目前包含世界各地的成千上万的开发人员贡献的数百万行代码,正是LKML将这些开发人员联结在一起。

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

LKML中的一些讨论甚至比某些《纽约时报》畅销书更有意思,花几个小时浏览LKML的压缩包有助于洞察Linux内核背后的理念。

内核的大部分子项目都拥有自己的邮件列表。因此,如果你正在开发闪存设备的驱动程序,就可以订阅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系统除了内核以外,还包括大量的实用程序、程序、库和工具,因此,获得和正确安装所有的组件是一项艰巨的任务。而Linux发行版有序地将这些组件进行了分类,并捆绑成相应的包,从而分担了这一艰巨任务。一个常见的发行版包含数以千计的捆绑好的包。这使得用户无需担心下载不到正确版本的程序,也无需关心程序间的依赖问题。

因为打包是GNU许可证范围内的一种有效的赚钱方式,因此,目前的市场上诞生了数个Linux发行版。其中,Red Hat/Fedora、Debian、SuSE、Slackware、Gentoo、Ubuntu和Mandriva这些发行版面向桌面用户,而MontaVista、TimeSys和Wind River发行版则面向嵌入式系统开发。嵌入式Linux的发行版还包括一套可动态配置的紧凑的应用程序集,以便针对资源的限制为系统进行量体裁衣。

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

遵守设备行业领域标准的Linux发行版更适合作为开发的起点。特殊兴趣组(SIG)已经成立,其目的是促进Linux在各个领域的应用。消费电子产品Linux论坛(CELF,网址为www.celinuxforum.org)主要讨论消费类电子领域的Linux应用。CELF标准定义了一些功能的支持等级,如可扩展性、快速启动、片上执行以及电源管理等。开源开发实验室(OSDL,网址为www.osdl.org)则致力于讨论电信级设备。OSDL的电信级Linux(CGL)标准包含了对可靠性、高可用性、运行时补丁、增强的错误恢复能力的诠释,这些问题在电信领域非常重要。

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

在内核开发过程中你计划使用的调试工具可能不包含在主线内核中。例如,内核并不包含内嵌的调试器支持。如果想在内核开发过程中使用内嵌的调试器,用户必须下载并打上相应的补丁。如果针对用户内核版本的补丁并不齐备,用户将必须忍受更多的麻烦。

而发行版则包装了很多有用的调试功能,所以你可以立即开始使用它们。

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

 发行版往往会对它们发布的内核进行较多的测试[3]。

用户可以从内核发行版的供应商处购买它们提供的服务以及软件包支持。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

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

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

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

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

(1)arch。该目录包含了与体系结构相关的文件。可以在arch/目录下看到针对ARM、Motorola 68K、s390、MIPS、Alpha、SPARC和IA64等处理器的子目录。

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

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

(4)Documentation。该目录包含了内核中各个子系统的简要描述,它是你探究内核方面问题的第一站。

(5)drivers。这个目录包含了大量设备类和外设控制器的驱动,包括字符、串口、内置集成电路(I2C)、个人计算机存储卡国际联盟(PCMCIA)、外围组件互连(PCI)、通用串行总线(USB)、视频、音频、块、集成驱动电子设备(IDE)、小型计算机系统接口(SCSI)、CD-ROM、网络适配器、异步传输模式(ATM)、蓝牙和内存技术设备(MTD)等。每一类设备对应drivers/下面的一个子目录,譬如PCMCIA驱动程序的源代码位于drivers/pcmcia/目录,MTD驱动程序位于drivers/mtd/目录。drivers/下的这些子目录是本书的主要议题。

(6)fs。这个目录包含了EXT3、EXT4、reiserfs、FAT、VFAT、sysfs、procfs、isofs、JFFS2、XFS、NTFS和NFS等文件系统的实现。

(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)、IPv6、网际互联交换协议(IPX)、蓝牙、ATM、红外、链路访问过程平衡(LAPB)以及逻辑链路控制(LLC)。

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

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

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

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

统一的x86架构源码树

从2.6.24内核版本开始,i386和x86_64(与32位的i386系统对应的64位系统)架构源码树已被统一纳入公共的arch/x86/目录。如果你使用的是比2.6.24老的内核,请用arch/i386 /代替本书中所说的arch/x86 /目录。同样地,也请将include/asm-x86/替换为include/asm-i386/。此外,这些目录中的一些文件名也会有所不同。

在这么庞大的目录树中查找符号和代码是一项艰巨的任务,表1-1中的一些工具可以帮助你更方便地浏览内核源码树。表1-1 源码树浏览工具(续)1.7 编译内核

了解了内核源码树布局后,现在我们来对代码稍做修改,并编译和运行它。进入位于顶层的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文件作为起点。因此,如果正在为32位x86体系架构编译内核,运行如下命令:

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

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

bash> make bzImage

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

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

也许需要根据新的启动映像更新引导程序。如果正在使用GRUB这个引导程序,它将自动完成配置;如果正在使用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。

如下工具可用于操纵模块:insmod、rmmod、lsmod、modprobe、modinfo和depmod。前两个工具用于加载和移除模块,lsmod用于列出目前已经加载的模块,modprobe是insmod的一个更智能的版本,它先分析/lib/modules/X.Y.Z/modules.dep文件再加载它所依赖的模块。例如,假定你需要挂载一个USB笔式驱动器上的VFAT(Virtual File Allocation Table,虚拟文件分配表)分区,可使用modprobe加载VFAT文件系统驱动程序[4]:

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.ko和nls_base.ko这2个模块,之后加载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

为了将内核驱动程序编译为模块,在配置内核的时候,请将相应的菜单选择按钮置为。本书中的大部分设备驱动程序例子都以内核模块的形式实现。为了从mymodule.c源文件构造mymodule.o模块,可以创建一个一行的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已经涉及的领域十分广泛,代表着最新的技术水平,所以可以基于它来学习操作系统的概念、处理器体系架构,甚至了解各种行业领域。在学习某一设备驱动程序子系统用到的技术时,不妨在更深层次上探索其背后的设计动机。

在没有明确指明的情况下,本书默认的都是32位x86架构。但是,本书也考虑到你更有可能要为嵌入式设备而非传统的PC兼容的系统编写驱动程序。因此,第6章讲解了两种设备:一个PC衍生器件上的触摸控制器和一个手机上的UART。第8章则讲解了PC系统中的EEPROM和嵌入式设备中的实时钟。本书也介绍了内核为大多数设备驱动程序类所提供的基础设施,它们隐藏了设备驱动程序与体系架构的相关性。

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

本书基于2.6内核,它包含了对2.4内核的大量更新,覆盖了所有主要的子系统。因此,希望你已经在系统中安装了基于2.6的内核并开始研究内核源代码。基于以下两个主要的原因,本书的每一章都反复要求读者去阅读相关的内核源文件。

(1)因为内核中的每个驱动程序子系统都包含数万行源代码,以本书的篇幅只能列出相对简单的部分内容,对照查看源代码中与书中例子相关的真实驱动程序会让你豁然开朗。

(2)在开发驱动程序之前,先参考一个drivers目录中与你的要求相似的现成的驱动程序,把它作为起点是一个好方法。

因此,为了能更好地消化本书内容,请频繁地浏览源码树并仔细研究代码来熟悉内核。在探索代码的过程中,也请跟踪邮件列表的进展。

[1].意指对源码进行一些有针对性的修改。——译者注

[2].版权为copyright,这里故意用copyleft。但是,copyleft作品是有版权的,只是加入了法律上的分发条款。——译者注

[3].因为需要将内核冻结在一个版本上进行测试(而这个版本不是最新的),所以发行版内核经常会引入比其版本更新的官方内核的一些功能。

[4].这个例子假定这个模块没有被内核自动加载。如果你在配置过程中启用了自动内核模块加载(CONFIG_KMOD)选项,当侦测到缺失的子系统时,内核将自动以相应的参数运行modprobe。第4章将介绍模块自动加载的知识。第2章内核

本章内容

启动过程

内核模式和用户模式

进程上下文和中断上下文

内核定时器

内核中的并发

proc文件系统

内存分配

查看源代码

在开始步入Linux设备驱动程序的神秘世界之前,让我们从驱动程序开发人员的角度看几个内核构成要素,熟悉一些基本的内核概念。我们将学习内核定时器、同步机制以及内存分配方法。不过,我们还是得从头开始这次探索之旅。因此,本章要先浏览一下内核发出的启动信息,然后再逐个讲解一些有意思的点。2.1 启动过程

图2-1显示了基于x86计算机Linux系统的启动顺序。第一步是BIOS从启动设备中导入主引导记录(MBR),接下来MBR中的代码查看分区表并从活动分区读取GRUB、LILO或SYSLINUX等引导装入程序(Bootloader),之后引导装入程序会加载压缩后的内核映像并将控制权传递给它。内核取得控制权后,会将自身解压缩并投入运转。

基于x86的处理器有两种操作模式:实模式和保护模式。在实模式下,用户仅可以使用1 MB内存,并且没有任何保护。保护模式要复杂得多,用户可以使用更多的高级功能(如分页)。CPU必须中途将实模式切换为保护模式。但是,这种切换是单向的,即不能从保护模式再切换回实模式。

内核初始化的第一步是执行实模式下的汇编代码,之后执行保护模式下init/main.c文件(上一章修改的源文件)中的start_kernel()函数。start_kernel()函数首先会初始化CPU子系统,之后让内存和进程管理系统就位,接下来启动外部总线和I/O设备,最后一步是激活init进程,它是所有Linux进程的父进程。init进程执行启动必要的内核服务的用户空间脚本,并且最终派生控制台终端程序以及显示登录(login)提示。图2-1 基于x86硬件上的Linux的启动过程

本节内的3级标题都是图2-2中的一条打印信息,这些信息来源于基于x86的笔记本电脑的Linux启动过程。如果在其他体系架构上启动内核,消息以及语义可能会有所不同。如果感觉本节中的一些内容非常晦涩,请不要担心。当前的目的仅是让你有一个大概的印象,让你先品尝内核甜点的味道。接下来要提到的许多概念都会在以后的章节中进行更深入的论述。2.1.1 BIOS-provided physical RAM map

内核会解析从BIOS中读取到的系统内存映射,并率先将以下信息打印出来:

BIOS-provided physical RAM map:

BIOS-e820: 0000000000000000 - 000000000009f000 (usable)

...

BIOS-e820: 00000000ff800000 - 0000000100000000 (reserved)

实模式下的初始化代码通过使用BIOS的int 0x15服务并执行0xe820号函数(即上面的BIOS-e820字符串)来获得系统的内存映射信息。内存映射信息中包含了预留的和可用的内存,内核将随后使用这些信息创建其可用的内存池。在附录B的B.1节,我们会对BIOS提供的内存映射问题进行更深入的讲解。图2-2 内核启动信息2.1.2 758MB LOWMEM available

896 MB以内的常规的可被寻址的内存区域被称作低端内存。内存分配函数kmalloc()就是从该区域分配内存的。高于896 MB的内存区域被称为高端内存,只有在采用特殊的方式进行映射后才能被访问。

在启动过程中,内核会计算并显示这些内存区内总的页数,本章稍后会对这些内存区进行更深入的分析。2.1.3 Kernel command line:ro root=/dev/hda1

Linux的引导装入程序通常会给内核传递一个命令行。命令行中的参数类似于传递给C程序中main()函数的argv[]列表,唯一的不同在于它们是传递给内核的。可以在引导装入程序的配置文件中增加命令行参数,当然,也可以在运行过程中通过引导装入程序修改Linux的命令行[1]。如果使用的是GRUB这个引导装入程序,由于发行版本的不同,其配置文件可能是/boot/grub/grub.conf或者是/boot/grub/menu.lst。如果使用的是LILO,配置文件为/etc/lilo.conf。下面给出了一个grub.conf文件的例子(增加了一些注释),看了紧接着title kernel 2.6.23的那行代码之后,你会明白前述打印信息的由来。

default 0 #Boot the 2.6.23 kernel by default

timeout 5 #5 second to alter boot order or parameters

title kernel 2.6.23   #Boot Option 1

#The boot image resides in the first partition of the first disk

#under the /boot/ directory and is named vmlinuz-2.6.23.'ro'

#indicates that the root partition should be mounted read-only.

kernel (hd0,0)/boot/vmlinuz-2.6.23 ro root=/dev/hda1

#Look under section "Freeing initrd memory:387k freed"

initrd (hd0,0)/boot/initrd

#...

命令行参数将影响启动过程中的代码执行路径。举一个例子,假设某命令行参数为bootmode,如果该参数被设置为1,意味着你希望在启动过程中打印一些调试信息并在启动结束时切换到runlevel的第3级(初始化进程的启动信息打印后就会了解runlevel的含义);如果bootmode参数被设置为0,意味着你希望启动过程相对简洁,并且设置runlevel为2。既然已经熟悉了init/main.c文件,下面就在该文件中增加如下修改:

static unsigned int bootmode = 1;

static int __init

is_bootmode_setup(char *str)

{

get_option(&str, &bootmode);

return 1;

}

/* Handle parameter "bootmode=" */

__setup("bootmode=", is_bootmode_setup);

if (bootmode) {

/* Print verbose output */

/* ...*/

}

/* ...*/

/* If bootmode is 1, choose an init runlevel of 3, else

switch to a run level of 2 */

if (bootmode) {

argv_init[++args] = "3";

} else {

argv_init[++args] = "2";

}

/* ...*/

请重新编译内核并尝试运行新的修改。另外,本书18.5节也将对内核命令行参数进行更详细的讲解。2.1.4 Calibrating delay...1197.46 BogoMIPS(lpj=2394935)

在启动过程中,内核会计算处理器在一个jiffy时间内运行一个内部的延迟循环的次数。jiffy的含义是系统定时器2个连续的节拍之间的间隔。正如所料,该计算必须被校准到所用CPU的处理速度。校准的结果被存储在称为loops_per_jiffy的内核变量中。使用loops_per_jiffy的一种情况是某设备驱动程序希望进行小的微秒级别的延迟的时候。

为了理解延迟—循环校准代码,让我们看一下定义于init/calibrate.c文件中的calibrate_delay()函数。该函数灵活地使用整型运算得到了浮点的精度。如下的代码片段(有一些注释)显示了该函数的开始部分,这部分用于得到一个loops_per_jiffy的粗略值:

loops_per_jiffy = (1 << 12); /* Initial approximation = 4096 */

printk(KERN_DEBUG “Calibrating delay loop...“);

while ((loops_per_jiffy <<= 1) != 0) {

ticks = jiffies; /* As you will find out in the section, “Kernel

Timers," the jiffies variable contains the

number of timer ticks since the kernel

started, and is incremented in the timer

interrupt handler */

while (ticks == jiffies); /* Wait until the start of the next jiffy */

ticks = jiffies;

/* Delay */

__delay(loops_per_jiffy);

/* Did the wait outlast the current jiffy? Continue if it didn't */

ticks = jiffies - ticks;

if (ticks) break;

}

loops_per_jiffy >>= 1; /* This fixes the most significant bit and is

the lower-bound of loops_per_jiffy */

上述代码首先假定loops_per_jiffy大于4096,这可以转化为处理器速度大约为每秒100万条指令,即1 MIPS。接下来,它等待jiffy被刷新(1个新的节拍的开始),并开始运行延迟循环__delay(loops_per_jiffy)。如果这个延迟循环持续了1个jiffy以上,将使用以前的loops_per_jiffy值(将当前值右移1位)修复当前loops_per_jiffy的最高位;否则,该函数继续通过左移loops_per_jiffy值来探测出其最高位。在内核计算出最高位后,它开始计算低位并微调其精度:

loopbit = loops_per_jiffy;

/* Gradually work on the lower-order bits */

while (lps_precision-- && (loopbit >>= 1)) {

loops_per_jiffy |= loopbit;

ticks = jiffies;

while (ticks == jiffies); /* Wait until the start of the next jiffy */

ticks = jiffies;

/* Delay */

__delay(loops_per_jiffy);

if (jiffies != ticks) /* longer than 1 tick */

loops_per_jiffy &= ~loopbit;

}

上述代码计算出了延迟循环跨越jiffy边界时loops_per_jiffy的低位值。这个被校准的值可被用于获取BogoMIPS(其实它是一个并非科学的处理器速度指标)。可以使用BogoMIPS作为衡量处理器运行速

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载