精通Linux设备驱动程序开发

978-7-115-40251-6
作者: 【印】Sreekrishnan Venkateswaran(斯里克里斯汉 温卡特斯瓦兰)
译者: 宋宝华何昭然史海滨吴国成
编辑: 傅道坤

图书目录:

详情

本书阐述了Linux设备驱动程序所涉及的所有概念和技术,主要内容包括Linux 2.6内核的功能、简单驱动类、串行总线、外部总线(如PCMCIA、PCI和USB)、视频、音频、网络驱动程序、用户空间驱动程序,等等。在解释每一个技术时,均讲解了相关的内核源码文件,并给出了完整的开发实例。

图书摘要

版权信息

书名:精通Linux设备驱动程序开发

ISBN:978-7-115-40251-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [印]Sreekrishnan Venkateswaran

  译    宋宝华 何昭然 史海滨 吴国成

  责任编辑 傅道坤

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Authorized translation from the English language edition, entitled Essential Linux Device Drivers, 9780132396554 by Sreekrishnan Venkateswaran, published by Pearson Education, Inc., publishing as Prentice Hall, Copyright © 2008 by Pearson Education, Inc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD. and POSTS & TELECOM PRESS Copyright © 2016.

本书中文简体字版由Pearson Education Asia Ltd。授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


本书是Linux设备驱动程序开发领域的权威著作。全书基于2.6内核,不仅透彻讲解了基本概念和技术,更深入探讨了其他书没有涵盖或浅尝辄止的许多重要主题和关键难点,如PCMCIA、I2C和USB等外部总线以及视频、音频、无线连网和闪存等驱动程序的开发,并讲解了相关的内核源码文件,给出了完整的开发实例。

本书适合中高级Linux开发人员阅读。


Sreekrishnan Venkateswaran,世界顶级Linux开发技术专家。在IBM工作多年,有丰富的嵌入式Linux和驱动程序开发经验,曾将Linux移植到了手表、音乐播放器、VoIP电话、心脏起搏器以及远程门诊监控系统等设备上。目前是IBM印度公司的嵌入式解决方案组负责人。他曾担任Linux Magazine的特邀编辑,主持内核技术专栏。


拿到这本书,你也许会问:为什么还要写一本Linux设备驱动程序的书呢?这样的书不是已经有一堆了吗?

答案是:相对于其他同类书,本书实现了一个巨大的突破。

首先,本书与时俱进,基于最新的2.6内核进行讲解。其次,也是更重要的,本书对驱动程序的讲解非常透彻。大多数设备驱动程序的图书仅仅讲解与标准Unix内核或操作系统相关的主题,譬如串口、磁盘驱动和文件系统等,如果你运气好,可能也会碰到讲解网络协议栈的内容。

本书前进了一大步,它没有避重就轻,而是知难而上,探讨了在现代PC和嵌入式系统中必须面对的难点,比如PCMCIA、USB、I2C、视频、音频、闪存、无线通信等。你可以这样定位本书:Linux内核包含了什么,本书就会告诉你什么。

它毫无遗漏,应有尽有。

何况,作者早就取得了不凡的成就:仅仅是看到他在20世纪90年代末将Linux移植到了手表中的相关介绍就让人激动不已。

本书能成为Prentice Hall开源软件开发系列丛书中的一本,我感到非常激动和欣慰。开源领域从来不乏振奋人心的事件,但本书的面世无疑更加引人瞩目。我希望你能从本书中找到你在进行内核开发时需要的东西,并且也能享受这一过程。

Arnold Robbins

资深Linux技术专家,gawk维护者,Prentice Hall开源软件开发系列丛书编者


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章讨论了内核中为键盘、鼠标和触摸屏控制器等输入设备服务的输入子系统。

第8章讲解了通过I2C总线或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文件,它是用于监控和追踪数据点的辅助接口。

本书大体上根据设备和总线的复杂度进行组织,同时也结合了章与章之间互相关联的客观情况。我们从讲解基本的设备类型开始(如字符设备、串口和输入设备),紧接着介绍简单的串行总线(如I2C和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浏览了开始的几章并且提供了富有见地的意见。

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


本章内容

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

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

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

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

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下的库动态链接。

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

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

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技术,就要一直跟踪邮件列表、网站和论坛。

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

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

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

补丁提交

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

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

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

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

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

在研究内核源代码之前,让我们先下载Linux源代码,学会打补丁,并查看内核源码树的布局。

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

bash> diff –Nur /path/to/original/kernel /path/to/your/kernel > changes.patch
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)启动一些实验性测试特性。运行如下命令:

Signed-off-by: Name <Email>
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补丁。

现在,你打好补丁后的/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 源码树浏览工具

工  具

描  述

lxr

Linux cross-referencer(Linux交叉引用程序),可从http://lxr.sourceforge.net/下载。它可以让你通过网页浏览器遍历内核源码树,因为它为内核符号的定义和使用提供了超链接

cscope

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

ctags/etags

ctags(网址为http://ctags.sourceforge.net/)可用于为许多语言产生交叉引用的标签。通过它,你可以在vi等编辑器中找到源码树中的符号和函数定义。从内核源码树的根目录运行make tags可以为所有文件建立标签。etags为emacs编辑器产生相似的索引信息。运行make TAGS可以采用etags为内核源文件创建标签

实用程序

grep、find、sdiff、strace、od、dd、make、tar、file和objdump等工具

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

了解了内核源码树布局后,现在我们来对代码稍做修改,并编译和运行它。进入位于顶层的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

启动后的第一条信息显示了你添加的喜爱北极熊的那句话。

由于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

为了将内核驱动程序编译为模块,在配置内核的时候,请将相应的菜单选择按钮置为<M>。本书中的大部分设备驱动程序例子都以内核模块的形式实现。为了从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章中,我们将学习模块调试技术。

将驱动程序设计为内核模块也有一些缺陷。与内建的驱动程序不同,模块无法在系统启动时预留资源,因为首要的是必须保证启动成功。 

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章将介绍模块自动加载的知识。


相关图书

Linux常用命令自学手册
Linux常用命令自学手册
庖丁解牛Linux操作系统分析
庖丁解牛Linux操作系统分析
Linux后端开发工程实践
Linux后端开发工程实践
轻松学Linux:从Manjaro到Arch Linux
轻松学Linux:从Manjaro到Arch Linux
Linux高性能网络详解:从DPDK、RDMA到XDP
Linux高性能网络详解:从DPDK、RDMA到XDP
跟老韩学Linux架构(基础篇)
跟老韩学Linux架构(基础篇)

相关文章

相关课程