首页 Linux操作系统源代码详细分析

Linux操作系统源代码详细分析

举报
开通vip

Linux操作系统源代码详细分析Linux操作系统源代码详细分析 内容简介: Linux 拥有现代操作系统所有的功能,如真正的抢先式多任务处理、支持多用户,内存保护,虚拟内存,支持SMP、UP,符合POSIX标准,联网、图形用户接口和桌面环境。具有快速性、稳定性等特点。本书通过分析Linux的内核源代码,充分揭示了Linux作为操作系统的内核是如何完成保证系统正常运行、协调多个并发进程、管理内存等工作的。现实中,能让人自由获取的系统源代码并不多,通过本书的学习,将大大有助于读者编写自己的新程序。 第一部分 Linux 内核源代码 ar...

Linux操作系统源代码详细分析
Linux操作系统源代码详细分析 内容简介: Linux 拥有现代操作系统所有的功能,如真正的抢先式多任务处理、支持多用户,内存保护,虚拟内存,支持SMP、UP,符合POSIX 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 ,联网、图形用户接口和桌面环境。具有快速性、稳定性等特点。本书通过分析Linux的内核源代码,充分揭示了Linux作为操作系统的内核是如何完成保证系统正常运行、协调多个并发进程、管理内存等工作的。现实中,能让人自由获取的系统源代码并不多,通过本书的学习,将大大有助于读者编写自己的新程序。 第一部分 Linux 内核源代码 arch/i386/kernel/entry.S 2 arch/i386/kernel/init_task.c 8 arch/i386/kernel/irq.c 8 arch/i386/kernel/irq.h 19 arch/i386/kernel/process.c 22 arch/i386/kernel/signal.c 30 arch/i386/kernel/smp.c 38 arch/i386/kernel/time.c 58 arch/i386/kernel/traps.c 65 arch/i386/lib/delay.c 73 arch/i386/mm/fault.c 74 arch/i386/mm/init.c 76 fs/binfmt-elf.c 82 fs/binfmt_java.c 96 fs/exec.c 98 include/asm-generic/smplock.h 107 include/asm-i386/atomic.h 108 include/asm-i386/current.h 109 include/asm-i386/dma.h 109 include/asm-i386/elf.h 113 include/asm-i386/hardirq.h 114 include/asm-i386/page.h 114 include/asm-i386/pgtable.h 115 include/asm-i386/ptrace.h 122 include/asm-i386/semaphore.h 123 include/asm-i386/shmparam.h 124 include/asm-i386/sigcontext.h 125 include/asm-i386/siginfo.h 125 include/asm-i386/signal.h 127 include/asm-i386/smp.h 130 include/asm-i386/softirq.h 132 include/asm-i386/spinlock.h 133 include/asm-i386/system.h 137 include/asm-i386/uaccess.h 139 include/linux/binfmts.h 146 include/linux/capability.h 147 include/linux/elf.h 150 include/linux/elfcore.h 156 include/linux/interrupt.h 157 include/linux/kernel.h 158 include/linux/kernel_stat.h 159 include/linux/limits.h 160 include/linux/mm.h 160 include/linux/module.h 164 include/linux/msg.h 168 include/linux/personality.h 169 include/linux/reboot.h 169 include/linux/resource.h 170 include/linux/sched.h 171 include/linux/sem.h 179 include/linux/shm.h 180 include/linux/signal.h 181 include/linux/slab.h 184 include/linux/smp.h 184 include/linux/smp_lock.h 185 include/linux/swap.h 185 include/linux/swapctl.h 187 include/linux/sysctl.h 188 include/linux/tasks.h 194 include/linux/time.h 194 include/linux/timer.h 195 include/linux/times.h 196 include/linux/tqueue.h 196 include/linux/wait.h 198 init/main.c 198 init/version.c 212 ipc/msg.c 213 ipc/sem.c 218 ipc/shm.c 227 ipc/util.c 236 kernel/capability.c 237 kernel/dma.c 240 kernel/exec_domain.c 241 kernel/exit.c 242 kernel/fork.c 248 kernel/info.c 255 kernel/itimer.c 255 kernel/kmod.c 257 kernel/module.c 259 kernel/panic.c 270 kernel/printk.c 271 kernel/sched.c 275 kernel/signal.c 295 kernel/softirq.c 307 kernel/sys.c 307 kernel/sysctl.c 318 kernel/time.c 330 mm/memory.c 335 mm/mlock.c 345 mm/mmap.c 348 mm/mprotect.c 358 mm/mremap.c 361 mm/page_alloc.c 363 mm/page_io.c 368 mm/slab.c 372 mm/swap.c 394 mm/swap_state.c 395 mm/swapfile.c 398 mm/vmalloc.c 406 mm/vmscan.c 409 第二部分 Linux 内核源代码分析 第1章 Linux 简介 让用户很详细地了解大多数现有操作系统的实际工作方式是不可能的,因为大多数操作系统的源代码都是严格保密的。除了一些研究用的及为操作系统教学而设计的系统外。尽管研究和教学目的都很好,但是这类系统很少能够通过对正式操作系统的小部分实现来体现操作系统的实际功能。对于操作系统的一些特殊问题,这种折衷系统所能够表现的就更是少得可怜了。 在以实际使用为目标的操作系统中,让任何人都可以自由获取系统源代码,无论目的是要了解、学习还是改进,这样的现实系统并不多。本书的主题就是这些少数操作系统中的一个:Linux。 Linux的工作方式类似于Uinx,它是免费的,源代码也是开放的,符合标准 规范 编程规范下载gsp规范下载钢格栅规范下载警徽规范下载建设厅规范下载 的32位(在64位CPU上是64位)操作系统。Linux拥有现代操作系统的所具有的内容,例如: * 真正的抢先式多任务处理,支持多用户。 * 内存保护。 * 虚拟内存。 * 支持对称多处理机SMP(symmetric multiprocessing),即多个CPU机器以及通常的单CPU(UP)机器。 * 符合POSIX标准。 * 联网。 * 图形用户接口和桌面环境(实际上桌面环境并不只一个)。 * 速度和稳定性。 严格说来,Linux并不是一个完整的操作系统。当我们在安装通常所说的Linux时,我们实际安装的是很多工具的集合。这些工具协同工作以组成一个功能强大的实用系统。Linux本身只是这个操作系统的内核,是操作系统的心脏、灵魂、指挥中心(整个系统应该称为GNU/Linux,其原因在本章的后续内容中将会给以介绍)。内核以独占的方式执行最底层任务,保证系统正常运行—协调多个并发进程,管理进程使用的内存,使它们相互之间不产生冲突,满足进程访问磁盘的请求等等。 在本书中,我们给大家揭示的就是Linux是如何完成这一具有挑战性的工作的。 1.1 Linux和Unix的简明历史 为了让大家对本书所讨论的内容有更清楚的了解,让我们先来简要回顾一下Linux的历史。由于Linux是在Unix的基础上发展而来的,我们的话题就从Unix开始。 Unix是由AT&T贝尔实验室的Ken Thompson和Dennis Ritchie于1969年在一台已经废弃了的PDP-7上开发的;它最初是一个用汇编语言写成的单用户操作系统。不久,Thompson和Ritchie成功地说服管理部门为他们购买更新的机器,以便该开发小组可以实现一个文本处理系统,Unix就在PDP-11上用C语言重新编写(发明C语言的部分目的就在于此)。它果真变成了一个文本处理系统—不久之后。只不过问题是他们先实现了一个操作系统而已…… 最终,他们实现了该文本处理工具,而且Unix(以及Unix上运行的工具)也在AT&T得到广泛应用。在1973年,Thompson和Ritchie在一个操作系统会议上就这个系统发表了一篇 论文 政研论文下载论文大学下载论文大学下载关于长拳的论文浙大论文封面下载 ,该论文引起了学术界对Unix系统的极大兴趣。 由于1956年反托拉斯法案的限制,AT&T不能涉足计算机业务,但允许它象征性地收取费用发售该系统。就这样,Unix被广泛发布,首先是学术科研用户,后来又扩展到政府和商业用户。 伯克利加州大学是学术用户中的一个。在这里,Unix得到了计算机系统研究小组(CSRG)的广泛应用。并且在这里所进行的修改引发了Unix的一大系列,这就是广为人知的伯克利软件开发(BSD)Unix。除了AT&T所提供的Unix系列之外,BSD是最有影响力的Unix系列。BSD在Unix中增加了很多显著特性,例如TCP/IP网络,更好的用户文件系统(UFS),工作控制,并且改进了AT&T的内存管理代码。 多年以来,BSD版本的Unix一直在学术环境中占据主导地位,但最终发展成为System V版本的AT&T的Unix则成为商业领域的领头羊。从某种程度上来说,这是有社会原因的:学校倾向于使用非正式但通常更好用的BSD风格的Unix,而商业界则倾向于从AT&T获取Unix。 在用户需求和用户编程改进特性的促进下,BSD风格的Unix一般要比AT&T的Unix更具有创新性,而且改进也更为迅速。但是,在AT&T发布最后一个正式版本System V Release 4(SVR4)时,System V Unix已经吸收了BSD的大多数重要的优点,并且还增加了一些自己的优势。这部分由于从1984年开始,AT&T逐渐可以将Unix商业化,而伯克利Unix的开发工作在1993年BSD4.4版本完成以后就逐渐收缩,以至终止了。然而,BSD的进一步改进由外界开发者延续下来,到今天还在继续进行。正在进行的Unix系列开发中至少有四个独立的版本是直接起源于BSD4.4,这还不包括几个厂商的Unix版本,例如惠普的HP-UX,都是部分地或者全部基于BSD而发展起来的。 实际上Unix的变种并不止BSD和System V。由于Unix主要使用C语言来编写,这就使得它移植到新的机器上相对比较容易,它的简单性也使其重新设计与开发相对比较容易。Unix的这些特点大受商业界硬件供应商的欢迎,比如Sun、SGI、HP、IBM、DEC、Amdahl等等;IBM还不止一次对Unix进行了再开发。厂商们设计开发出新的硬件,并简单地将Unix移植到新的硬件上,这样新的硬件一经发布便具备一定的功能。经过一段时间之后,这些厂商都拥有了自己的专有Unix版本。而且为了占有市场,这些版本故意以不同的侧重点发布出来,以更好地占有用户。 版本混乱的状态促进了标准化工作的进行。其中最主要的就是POSIX系列标准,它定义了一套标准的操作系统接口和工具。从理论上说,POSIX标准代码很容易移植到任何遵守POSIX标准的操作系统中,而且严格的POSIX测试已经把这种理论上的可移植性转化为现实。直到今天,几乎所有的正式操作系统都以支持POSIX标准为目标。 现在让我们回顾一下,在1984年,杰出的电脑黑客Richard Stallman独立开发出一个类Unix的操作系统,该操作系统具有完全的内核、开发工具和终端用户应用程序。在GNU(“GNU誷 Not Unix”首字母的缩写) 计划 项目进度计划表范例计划下载计划下载计划下载课程教学计划下载 的配合下,Stallman开发这个产品有自己的技术理想:他想开发出一个质量高而且自由的操作系统。Stallman使用了“自由”(free)这个词,不仅意味着用户可以免费获取软件;而且更重要的是,它将意味着某种程度的“解放”:用户可以自由使用、拷贝、查询、重用、修改甚至是分发这份软件,完全没有软件使用协议的限制。这也正是Stallman创建自由软件基金会(FSF)资助GNU软件开发的本意(FSF也在资助其他科研方面的开发工作)。 15年来,GNU工程已经吸收、产生了大量的程序,这不仅包括Emacs、gcc(GNU的C编译器)、bash(shell命令),还有大部分Linux用户所熟知的许多应用程序。现在正在进行开发的项目是GNU Hurd内核,这是GNU操作系统的最后一个主要部件(实际上Hurd内核早已能够使用了,不过当前的版本号为0.3的系统在什么时候能够完成,还是未知数)。 尽管Linux大受欢迎,但是Hurd内核还在继续开发。原因有几个方面,其一是Hurd的体系结构十分清晰地体现了Stallman关于操作系统工作方式的思想,例如,在运行期间,任何用户都可以部分地改变或替换Hurd(这种替换不是对每个用户都是可见的,而是只对申请修改的用户可见,而且还必须符合安全规范)。另一个原因是据介绍Hurd对于多处理器的支持比Linux本身的内核要好。还有一个简单的原因是兴趣的驱动,因为程序员们希望能够自由地进行自己所喜欢的工作。只要有人希望为Hurd工作,Hurd的开发就不会停止。如果他们能够如愿以偿,Hurd有朝一日将成为Linux的强劲对手。不过在今天,Linux还是自由内核王国里无可争议的统治者。 在GNU发展的中期,也就是1991年,一个名叫Linus Torvalds的芬兰大学生想要了解Intel的新CPU—80386。他认为比较好的学习方法是自己编写一个操作系统的内核。出于这种目的,加上他对当时Unix变种版本对于80386类机器的脆弱支持十分不满,他决定要开发出一个全功能的、支持POSIX标准的、类Unix的操作系统内核,该系统吸收了BSD和System V的优点,同时摒弃了它们的缺点。Linus(虽然我知道我应该称他为Torvalds,但是所有人都称他为Linus)独立把这个内核开发到0.02版,这个版本已经可以运行gcc、bash和很少的一些应用程序。这些就是他开始的全部工作了。后来,他又开始在因特网上寻求广泛的帮助。 不到三年,Linus的Unix—Linux,已经升级到1.0版本。它的源代码量也呈指数形式增长,实现了基本的TCP/IP功能(网络部分的代码后来重写过,而且还可能会再次重写)。此时Linux就已经拥有大约10万用户了。 现在的Linux内核由150多万行代码组成,Linux也已经拥有了大约1000万用户(由于Linux可以自由获取和拷贝,获取具体的统计数字是不可能的)。Linux内核GNU/Linux附同GNU工具已经占据了Unix 50%的市场。一些公司正在把内核和一些应用程序同安装软件打包在一起,生产出Linux的发行版本,这些公司包括Red Hat和Caldera 公司。现在的GNU/Linux已经备受瞩目,得到了诸如Sun、IBM、SGI等公司的广泛支持。SGI最近决定在其基于Intel的Merced的系列机器上不再搭载自己的Unix变种版本IRIX,而是直接采用GNU/Linux;Linux甚至被指定为Amiga将要发布的新操作系统的基础。 1.2 GNU通用公共许可证 中国 这样一个如此流行的操作系统当然值得我们学习。按照通用公共许可证(GPL,General Public License)的规定,Linux的源代码可以自由获取,这满足了我们学习该系统的强烈愿望。GPL这份非同寻常的软件许可证,充分体现了上面提到的Stallman的思想:只要用户所做的修改是同等自由的,用户可以自由地使用、拷贝、查询、重用、修改甚至重新发布这个软件。通过这种方式,GPL保证了Linux(以及同一许可证保证下的大量其他软件)不仅现在自由可用,而且以后经过任何修改之后都仍然可以自由使用。 请注意这里的自由并不是说没有人靠这个软件盈利,有一些日益兴起的公司,比如发行最流行的Linux发行版本的Red Hat就是一个例子(Red Hat自从上市以来,市值已经突破数十亿美元,每年盈利数十万美元,而且这些数字还在不断增长)。但是任何人都不能限制其他用户涉足本软件领域,而且所做的修改不能减少其自由程度。 本书的附录B中收录了GNU通用公共许可证协议的全文。 1.3 Linux开发过程 中国 如上所述,由于Linux是一个自由软件,它可以免费获取以供学习研究。Linux之所以值得学习研究,是因为它是相当优秀的操作系统。如果Linux操作系统相当糟糕,那它就根本不值得我们使用,也就没有必要去研究相关的书籍。Linux是一个十分优秀的操作系统还在于几个相互关联的原因。 原因之一在于它是基于天才的思想开发而成的。在学生时代就开始推动整个系统开发的Linus Torvalds是一个天才,他的才能不仅展现在编程能力方面,而且组织技巧也相当杰出。Linux的内核是由世界上一些最优秀的程序员开发并不断完善的,他们通过Internet相互协作,开发理想的操作系统;他们享受着工作中的乐趣,而且也获得了充分的自豪感。 中国 Linux优秀的另外一个原因在于它是基于一组优秀的概念。Unix是一个简单却非常优秀的模型。在Linux创建之前,Unix已经有20年的发展历史。Linux从Unix的各个流派中不断吸取成功经验,模仿Unix的优点,抛弃Unix的缺点。这样做的结果是Linux 成为了Unix系列中的佼佼者:高速、健壮、完整,而且抛弃了历史包袱。 然而,Linux最强大的生命力还在于其公开的开发过程。每个人都可以自由获取内核源程序,每个人都可以对源程序加以修改,而后他人也可以自由获取你修改后的源程序。如果你发现了缺陷,你可以对它进行修正,而不用去乞求不知名的公司来为你修正。如果你有什么最优化或者新特点的创意,你也可以直接在系统中增加功能,而不用向操作系统供应商解释你的想法,指望他们将来会增加相应的功能。当发现一个安全漏洞后,你可以通过编程来弥补这个漏洞,而不用关闭系统直到你的供应商为你提供修补程序。由于你拥有直接访问源代码的能力,你也可以直接阅读代码来寻找缺陷,或是效率不高的代码,或是安全漏洞,以防患于未然。 除非你是一个程序员,否则这一点听起来仿佛没有多少吸引力。实际上,即使你不是程序员,这种开发模型也将使你受益匪浅,这主要体现在以下两个方面: * 可以间接受益于世界各地成千上万的程序员随时进行的改进工作。 * 如果你需要对系统进行修改,你可以雇用程序员为你完成工作。这部分人将根据你的需求定义单独为你服务。可以设想,这在源程序不公开的操作系统中将是什么样子。 Linux这种独特的自由流畅的开发模型已被命名为bazaar(集市模型),它是相对于cathedral(教堂)模型而言的。在cathedral模型中,源程序代码被锁定在一个保密的小范围内。只有开发者(很多情况下是市场)认为能够发行一个新版本,这个新版本才会被推向市场。这些术语在Eric S. Raymond的《教堂与集市》(The Cathedral and the Bazaar)一文中有所介绍,大家可以在http://www.tuxedo.org/~esr/writings/找到这篇文章。bazaar开发模型通过重视实验,征集并充分利用早期的反馈,对巨大数量的脑力资源进行平衡配置,可以开发出更优秀的软件。(顺便说一下,虽然Linux是最为明显的使用bazaar开发模型的例子,但是它却远不是第一个使用这个模型的系统。) 为了确保这些无序的开发过程能够有序地进行,Linux采用了双树系统。一个树是稳定树(stable tree),另一个树是非稳定树(unstable tree)或者开发树(development tree)。一些新特性、实验性改进等都将首先在开发树中进行。如果在开发树中所做的改进也可以应用于稳定树,那么在开发树中经过测试以后,在稳定树中将进行相同的改进。按照Linus的观点,一旦开发树经过了足够的发展,开发树就会成为新的稳定树,如此周而复始的进行下去。 源程序版本号的形式为x.y.z。对于稳定树来说,y是偶数;对于开发树来说,y比相应的稳定树大一(因此,是奇数)。截至到本书截稿时,最新的稳定内核版本号是2.2.10,最新的开发内核的版本号是2.3.12。对2.3树的缺陷修正会回溯影响(back-propagated)2.2树,而当2.3树足够成熟的时候会发展成为2.4.0。(顺便说一下,这种开发会比常规惯例要快,因为每一版本所包含的改变比以前更少了,内核开发人员只需花很短的时间就能够完成一个实验开发周期。) http://www.kernel.org及其镜像站点提供了最新的可供下载的内核版本,而且同时包括稳定和开发版本。如果你愿意的话,不需要很长时间,这些站点所提供的最新版本中就可能包含了你的一部分源程序代码。 中国 第2章 代 码 初 识 本章首先从较高层次介绍Linux内核源程序的概况,这些都是大家关心的一些基本特点。随后将简要介绍一些实际代码。最后介绍如何编译内核。 2.1 Linux内核源程序的部分特点 在过去的一段时期,Linux内核同时使用C语言和汇编语言来实现。这两种语言需要一定的平衡:C语言编写的代码移植性较好、易于维护,而汇编语言编写的程序则速度较快。一般只有在速度是关键因素或者一些因平台相关特性而产生的特殊要求(例如直接和内存管理硬件进行通讯)时才使用汇编语言。 正如实际中所做的,即使内核并未使用C++的对象特性,部分内核也可以在g++(GNU的C++编译器)下进行编译。同其他面向对象的编程语言相比较,相对而言C++的开销是较低的,但是对于内核开发人员来说,这已经是太多了。 内核开发人员不断发展编程风格,形成了Linux代码独有的特色。本节将讨论其中的一些问题。 2.1.1 gcc特性的使用 Linux内核被设计为必须使用GNU的C编译器gcc来编译,而不是任何一种C编译器都可以使用。内核代码有时要使用gcc特性,本书将陆续介绍其中的一部分。 一些gcc特有代码只是简单地使用gcc语言扩展,例如允许在C(不只是C++)中使用inline关键字(注释:一、inline 关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。 表达式形式的宏定义一例:#define ExpressionName(Var1,Var2) ((Var1)+(Var2))*((Var1)-(Var2))   为什么要取代这种形式呢,且听我道来:   1. 首先谈一下在C中使用这种形式宏定义的原因,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成 等一系列的操作,因此,效率很高,这是它在C中被使用的一个主要原因。   2. 这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。   3. 在C++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。   4. inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。   为什么inline能很好地取代表达式形式的预定义呢?   对应于上面的1-3点,阐述如下:   1. inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。   2. 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。   3. inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。   在何时使用inline函数:   首先,你可以使用inline函数完全取代表达式形式的宏定义。   另外要注意,内联函数一般只会用在函数内容非常简单的时候,这是因为,内联函数的代码会在任何调用它的地方展开,如果函数太复杂,代码膨胀带来的恶果很可能会大于效率的提高带来的益处。内联函数最重要的使用地方是用于类的存取函数。   如何使用类的inline函数:   简单提一下inline 的使用吧:   1.在类中定义这种函数:   class ClassName{   .....   ....   GetWidth(){return m_lPicWidth;}; // 如果在类中直接定义,可以不使用inline修饰   ....   ....   }   2.在类中声明,在类外定义:   class ClassName{   .....   ....   GetWidth(); // 如果在类中直接定义,可以不使用inline修饰   ....   ....   }   inline return_type ClassName::GetWidth(){   return m_lPicWidth;   }  二、使用inline内联函数替代宏调用   对于频繁使用的函数,C语言建议使用宏调用代替函数调用以加快代码执行,减少调用开销。但是宏调用有许多的弊端,可能引起不期望的副作用。例如宏:#define abs(a) ((a)<0?(-a):(a)), 当使用abs(i++)时,这个宏就会出错。   所以在C++中应该使用inline内联函数替代宏调用,这样既可达到宏调用的目的,又避免了宏调用的弊端。   使用内联函数只须把inline关键字放在函数返回类型的前面。例如:   inline int Add(int a,int b);//声明Add()为内联函数   这样编译器在遇到Add()函数时,就不再进行函数调用,而是直接嵌入函数代码以加快程序的执行。 )指示内联函数。也就是说,代码中被调用的函数在每次函数调用时都会被扩充,因而就可以节约实际函数调用的开销。 一般情况下,代码的编写方式比较复杂。因为对于某些类型的输入,gcc能够产生比其他输入效率更高的执行代码。从理论上讲,编译器可以优化具有相同功能的两种对等的方法,并且得到相同的结果。因此,代码的编写方式是无关紧要的。但在实际上,用某种方法编写所产生的代码要比用另外一些方法编写所产生的代码执行速度快许多。内核开发人员知道怎样才能产生更高效的执行代码,这不断地在他们编写的代码中反映出来。 例如,考虑内核中经常使用的goto语句—为了提高速度,内核中经常大量使用这种一般要避免使用的语句。在本书中所包含的不到40 000行代码中,一共有500多条goto语句,大约是每80行一个。除汇编文件外,精确的统计数字是接近每72行一个goto语句。公平地说,这是选择偏向的结果:比例如此高的原因之一是本书中涉及的是内核源程序的核心,在这里速度比其他因素都需要优先考虑。整个内核的比例大概是每260行一个goto语句。然而,这仍然是我不再使用Basic进行编程以来见过的使用goto频率最高的地方。 代码必需受特定编译器限制的特性不仅与普通应用程序的开发有很大不同,而且也不同于大多数内核的开发。大多数的开发人员使用C语言编写代码来保持较高的可移植性,即使在编写操作系统时也是如此。这样做的优点是显而易见的,最为重要的一点是一旦出现更好的编译器,程序员们可以随时进行更换。 内核对于gcc特性的完全依赖使得内核向新的编译器上移植更加困难。最近Linus对这一问题在有关内核的邮件列表上表明了自己的观点:“记住,编译器只是一个工具。”这是对依赖于gcc特性的一个很好的基本思想的表述:编译器只是为了完成工作。如果通过遵守标准还不能达到工作要求,那么就不是工作要求有问题,而是对于标准的依赖有问题。 在大多数情况下,这种观点是不能被人所接受的。通常情况下,为了保证和程序语言标准的一致,开发人员可能需要牺牲某些特性、速度或者其他相关因素。其他的选择可能会为后期开发造成很大的麻烦。 但是,在这种特定的情况下,Linus是正确的。Linux内核是一个特例,因为其执行速度要比向其他编译器的可移植性远为重要。如果设计目标是编写一个可移植性好而不要求快速运行的内核,或者是编写一个任何人都可以使用自己喜欢的编译器进行编译的内核,那么结论就可能会有所不同了;而这些恰好不是Linux的设计目标。实际上,gcc几乎可以为所有能够运行Linux的CPU生成代码,因此,对于gcc的依赖并不是可移植性的严重障碍。 在第3章中我们将对内核设计目标进行详细说明。 2.1.2 内核代码习惯用语 内核代码中使用了一些显著的习惯用语,本节将介绍常用的几个。当通读源代码时,真正重要的问题并不在这些习惯用语本身,而是这种类型的习惯用语的确存在,而且是不断被使用和发展的。如果你需要编写内核代码,你应该注意到内核中所使用的习惯用语,并把这些习惯用语应用到你的代码中。当通读本书(或者代码)时,看看你还能找到多少习惯用语。 为了讨论这些习惯用语,我们首先需要对它们进行命名。为了便于讨论,笔者创造了这些名字。而在实际中,大家不一定非要参考这些用语,它们只是对内核工作方式的描述而已。 一个普通的习惯用语,笔者称之为“资源获取”(resource acquisition idiom)。在这个用语中,一个函数必须实现一系列资源的获取,包括内存、锁等等(这些资源的类型未必相同)。只有成功地获取当前所需要的资源之后,才能处理后面的资源请求。最后,该函数还必须释放所有已经获取的资源,而不必考虑没有获取的资源。 我采用“错误变量”这一用语(error variable idiom)来辅助说明资源获取用语,它使用一个临时变量来记录函数的期望返回值。当然,相当多的函数都能实现这个功能。但是错误变量的不同点在于它通常是用来处理由于速度的因素而变得非常复杂的流程控制中的问题。错误变量有两个典型的值,0(表示成功)和负数(表示有错)。 如果执行到标号out2,则都已经获取了r1和r2资源,而且也都需要进行释放。如果执行到标号out1(不管是顺序执行还是使用goto语句进行跳转到),则r2资源是无效的(也可能刚被释放),但是r1资源却是有效的,而且必需在此将其释放。同理,如果标号out能被执行,则r1和r2资源都无效,err所返回的是错误或成功标志。 在这个简单的例子中,对err的一些赋值是没有必要的。在实践中,实际代码必须遵守这种模式。这样做的原因主要在于同一行中可能包含有多种测试,而这些测试应该返回相同的错误代码,因此对错误变量统一赋值要比多次赋值更为简单。虽然在这个例子中对于这种属性的必要性并不非常迫切,但是我还是倾向于保留这种特点。有关的实际应用可以参考sys_shmctl(第21654行),在第9章中还将详细介绍这个例子。 2.1.3 减少#if和#ifdef的使用 现在的Linux内核已经移植到不同的平台上,但是我们还必须解决移植过程中所出现的问题。大部分支持各种不同平台的代码由于包含许多预处理代码而已经变得非常不规范,例如: 这个例子试图实现操作系统的可移植性,虽然Linux关注的焦点很明显是实现代码在各种CPU上的可移植性,但是二者的基本原理是一致的。对于这类问题来说,预处理器是一种错误的解决方式。这些杂乱的问题使得代码晦涩难懂。更为糟糕的是,增加对新平台的支持有可能要求重新遍历这些杂乱分布的低质量代码段(实际上你很难能找到这类代码段的全部)。 中国 与现有方式不同的是,Linux一般通过简单函数(或者是宏)调用来抽象出不同平台间的差异。内核的移植可以通过实现适合于相应平台的函数(或宏)来实现。这样不仅使代码的主体简单易懂,而且在移植的过程中还可以比较容易地自动检测出你没有注意到的内容:如引用未声明函数时会出现链接错误。有时用预处理器来支持不同的体系结构,但这种方式并不常用,而相对于代码风格的变化就更是微不足道了。 顺便说一下,我们可以注意到这种解决方法和使用用户对象(或者C语言中充满函数指针的struct结构)来代替离散的switch语句处理不同类型的方法十分相似。在某些层次上,这些问题和解决方法是统一的。 可移植性的问题并不仅限于平台和CPU的移植,编译器也是一个重要的问题。此处为了简化,假设Linux只使用gcc来编译。由于Linux只使用同一个编译器,所以就没有必要使用#if块(或者#ifdef块)来选择不同的编译器。 内核代码主要使用#ifdef来区分需要编译或不需要编译的部分,从而对不同的结构提供支持。例如,代码经常测试SMP宏是否定义过,从而决定是否支持SMP机。 2.2 代码样例 了解Linux代码风格最好的方法就是实际研究一下它的部分代码。即使你不完全理解本节所讨论代码的细节也无关紧要,毕竟本节的主要目的不是理解代码,一些读者可以只对本节进行浏览。本节的主要目的是让读者对Linux代码进行初步了解,为今后的工作提供必要基础。该讨论将涉及部分广泛使用的内核代码。 2.2.1 printk printk(25836行)是内核内部消息日志记录函数。在出现诸如内核检测到其数据结构出现不一致的事件时,内核会使用printk把相关信息打印到系统控制台上。对于printk的调用一般分为如下几类: * 紧急事件(emergency)—例如,panic函数(25563行)多次使用了printk。当内核检测到发生不可恢复的内部错误时就会调用panic函数,然后尽其所能地安全关闭计算机。这个函数中调用printk以提示用户系统将要关闭。 * 调试—从3816行开始的#ifdef块使用printk来打印SMP逻辑单元(box)中每一个处理器的相关配置信息,但是此过程只有在使用SMP_DEBUG标志编译代码的情况下才能够被执行。 * 普通信息—例如,当机器启动时,内核必须估计系统速度以确保设备驱动程序能够忙等待(busy-wait)一个精确的极短周期。计算这种估计值的函数名为calibrate_delay(19654行),它既在19661行使用printk声明马上开始计算,又在19693行报告计算结果。另外,在第4章将详细的介绍calibrate_delay函数。 如果你已经浏览过这些参照行,你可能已经注意到printk和printf的参数十分类似:一个格式化字符串,后跟零个或者多个参数加入字符串中。格式化字符串可能是以一组“”开始,这里的N是从0到7的数字,包括0和7在内。数字区分了消息的日志等级(log level),只有当日志等级高于当前控制台定义的日志等级(console_loglevel,25650行)时,才会打印消息。root可以通过适当减小控制台的日志等级来过滤不是很紧急的消息。如果内核在格式化字符串中检测不到日志等级序列,那么就会一直打印消息(实际上,日志等级序列并不一定要在格式化字符串中出现,可以在格式化文本中查找到它的代码)。 从14946行开始的#define块说明了这些特殊序列,这些定义可以帮助调用者正确区分对printk的调用。简单地说,我称日志等级0到4为“紧急事件”,等级5到等级6为“普通信息”,等级7自然就是我所说的“调试”(这种分类方法并不意味着其他更好的分类方法没有用处,而只是目前我们还不关心它而已)。 在上面讨论的基础上,我们研究一下代码本身。 printk 25836:参数fmt是printf类型的格式化字符串。如果你对“...”部分的内容不熟悉,那就 需要参阅一本好的C语言参考书(在其索引中查找“变参函数,variadic function”)。另外,在安装的GNU/Linux中的stdarg帮助里也包含了一个有关变参函数的简明描述,在这儿只需要敲入“man stdarg”就可以看到。 简单地说,“...”部分提示编译器fmt后面可能紧跟着数量不定的任何类型的参数。由于这些参数在编译的时候还没有类型和名字,内核使用由三个宏va_start、va_arg和va_end组成的特殊组及一个特殊类型—va_list对它们进行处理。 25842:msg_level记录了当前消息的日志等级。它是静态的,这看起来可能会有些奇怪—为什么下一次对printk的调用需要记录日志等级呢?问题的答案是只有打印出新行(\n)或者赋给一个新的日志等级序列以后,当前消息才会结束。这样,通过在包含消息结束的新行里调用printk,就保证了在多个短期冲突的情况下,调用者只打印唯一一个长消息。 25845:在SMP逻辑单元中,内核可能试图从不同的CPU向控制台同时打印信息(有时在单处理机(UP)逻辑单元中也会发生同样问题,但由于中断还未被覆盖掉,所以问题也并不十分明显)。如果不进行任何协同的话,结果就将处于完全无法让人了解的杂乱无章的状态,每个消息的各个部分都和其他消息的各个部分混杂交织在一起。 相反,内核使用旋转锁(spin-lock)来控制对控制台的访问。旋转锁将在第10章进行深入介绍。 如果你对flags 在传送给spin_lock_irqsave之前为什么不对它初始化感到疑惑,请不要担心:spin_lock_irqsave(对于不同的版本请分别参看12614行,12637行,12716行和12837行)是一个宏,而不是一个函数。该宏实际上是将值写入flags中,而不是从flags中读出值(在25895行中,存储在flags中的信息被spin_unlock_irqrestore回读,请参看12616行,12639行,12728行和12841行)。 25846:初始化变量args,该变量代表printk参数中的“...”部分。 25848:调用内核自身的vsprintf(为节省空间而省略)实现。该函数的功能与标准vsprintf函数非常相似,向buf中写入格式化文本(25634行)并返回写入字符串的长度(长度不包括最后一位终止字符0字节)。很快,你将可以看到为什么这种机制会忽略buf的前三个字符。 (正如25847行的注释中所述)我们应该注意到在这里并没有采取严格的措施来保证缓冲器不会过载。这里系统假定1024个字符长度的buf已经足够使用(参阅25634行)。如果内核在这里能够使用vsnprintf函数的话,情况就会好许多。然而,vsnprintf还有另外一个参数限制了它能够写入缓冲器的字符长度。 25849:计算buf中最近使用的元素,调用va_end终止对“...”参数的处理。 25851:开始格式化消息的循环。其中存在一个内部循环能够处理更多内容(这一点随后就能看到),因此,每次内循环开始,都开始一个新的打印行。由于通常情况下printk只用于打印单行,所以在每次调用中,这种循环通常只执行一次。 25853:如果预先不知道消息的日志等级,printk会检查当前行是否以日志等级序列开头。 25860:如果不是,buf中开始未使用的三个字符就能够起作用了(第一次以后的每次循环,都会覆盖部分消息文本,但是这样并不会引起问题,因为这里的文本只是前面行中的一部分,它们已经被打印过,而且以后也不再需要了)。这样,就可以将日志等级插入buf中。 中国 25866:此处有如下属性:p指向日志等级序列(消息文本紧随其后),msg指向消息文本—请注意25852行和25865行中对msg的赋值。 中国 由于已知p用来指示日志等级序列的开头—该日志等级序列可能是由函数自身所创建的,日志等级可以从p中抽出并存到msg_level中。 25868:没有检测到新行,清空line_feed标志。 25869:这是前面谈到过的内循环,循环将运行到本行结束(也就是检测到新行标志)或者缓冲器的末尾为止。 25870:除了将消息打印到控制台之外,printk还能够记录最近打印的长度为LOG_ BUF_LEN的字符组(LOG_BUF_LEN为16K,请参看25632行)。如果在控制台打开之前,内核就已经调用printk,则显然不能在控制台上正确打印消息,但是这些消息将被尽可能地存储到log_buf中(25656行)。当控制台打开以后,缓存在log_buf中的数据就可以转储并在控制台上打印出来,请参看25988行。 log_buf是一个循环缓冲器,log_start和log_size变量(25657行和25646行)分别记录当前缓冲器的开始位置和长度。本行中的按位与(AND)操作实际上是快速求模(%)运算,它的正确性依赖于LOG_BUF_LEN的值是2的幂。 中国 25872:保存变量跟踪记录循环日志的值。显然,日志大小会不断增长,直至达到LOG_BUF_LEN的值为止。此后,log_size将保持不变,而插入新字符将导致log_start的增长。 25878:请注意logged_chars(25658行)记录从机器启动之后由printk写入的所有字符的长度,它在每次循环中都会被更新,而不是在循环结束后才改变一次。基于同样的道理,log_start和log_size的处理方式也是一样。这实际上是一种优化的时机,本书将在结束对函数的介绍之后再对它进行详细讨论。 25879:消息被分为若干行,这当然要使用新行标志符来进行分割。一旦内核检测到新行标志符,就写入一个完整行,从而内循环的执行也可以提前终止。 25884:在这里我们先不考虑内部循环是否会提前退出,从msg到p的字符序列是专门提供给控制台使用的(这种字符序列我称之为行,但是不要忘了,这里的行可能并不意味着新行终止,因为buf也许还没有终止)。如果该行的日志等级高于系统控制台定义的日志等级,而且当前又有控制台可供打印,那么就能够正确打印该行。(记住,printk可能在所有控制台打开之前就已经被调用过了。) 如果在该消息块中没有发现日志等级序列,并且在前面的printk调用中也没有对msg_level赋值,那么本行中的msg_level就是-1。由于console_loglevel总不小于1(除非root通过sysctl接口锁定),于是总是可以打印这些行。 25886:本行应该能够被打印。printk通过遍历打开的控制台驱动链表告知每一个控制台驱动去打印当前行设备驱动在本书的讨论范围之外,因此,控制台驱动代码则并不包含在内)。 25888:请注意这里消息文本的开头使用的是msg而不是p,这样就在没有日志等级序列的情况下写入消息了。然而,日志等级序列已经被存储到log_buf缓冲器中了。这样就使后来能够访问log_buf以获取消息日志等级的代码(请参看25998行),不会再产生显示混乱信息序列的现象。 25892:如果内层for循环发现一新行,那么buf中的剩余字符(如果有的话)将被认为是新的消息,因此msg_level会被重置。但是无论怎样,外层循环都会持续到buf清空为止。 25895:释放在25845行获取的控制台锁(console lock)。 25896:唤醒等待被写入控制台日志的所有进程。注意即使没有文本被实际写入任何控制台,这个过程也仍然会发生。这样处理是正确的,因为无论是否要往控制台中写入文本,等待进程实际上都是在等待从log_buf中读出信息。在25748行,进程被转入休眠状态以等待log_buf的活动。在休眠、唤醒和等待队列中所使用的机制将在下一节中进行讨论。 25897:返回日志中写入的字符长度。 如果对于每个字符的处理工作都能减少一点,那么从25869行开始的for循环就执行得更快一点。当循环存在时,我们可以通过只在循环退出时将logged_chars更新一次来稍微提高运行速度。然而我们还可以通过其他努力来提高速度。由于我们可以预知消息的长度,因此log_size和log_start可以到最后再增长。让我们来实验一下这样能否提高速度,下面是一段经过理想优化的代码: 中国 请注意循环通常只需要执行一次,只有在log_buf末尾写入信息需要折行时才会多次执行。因而log_size和log_buf只需要更新一次(或者当写入需要换行时是两次)。 这时速度的确提高了,但是有两个原因使我们并不能这样做。首先,内核可能有自己特有的memcpy函数,我们必须确保对memcpy的调用不会再次进入对printk的调用(有一部分内核移植版定义了自己特有的速度较快的memcpy函数版本,因此所有的移植都要在这一点上保持一致)。如果memecpy调用printk来报告失败,那么就有可能触发无限循环。 然而在这一点上也并不是真的无药可救。使用这种解决 方案 气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载 的最大问题在于该内核循环的形式中也要留意新行标志符,因此使用memcpy将整个消息拷贝到log_buf中是不正确的:如果此处存在新行,我们将无法对其进行处理。 我们可以试验一个一箭双雕的办法。下面这种替代的尝试虽然可能比前面那种初步解决方法速度要慢,但是它保持了内核版本的语意: (请注意gcc的优化器十分灵敏,它足以能检测到循环内部的表达式log_buf+LOG_BUF_LEN并没有改变,因此在上面的循环中试图手工加速计算是没有任何效果的。) 不幸的是,这种方法并不能比现在的内核版本在速度上快许多,而且那样会使得代码晦涩难懂(如果你编写过更新log_size和log_start的代码,你就能清楚地了解这一点)。你可以自己决定这种折衷是否值得。然而无论怎样,我们学到了一些东西,通常,不管成功与否,改进内核代码都可以加深你对内核工作原理的理解。 2.2.2 等待队列 前一节我们曾简要的提到进程(也就是正在运行的程序)可以转入休眠状态以等待某个特定事件,当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的技术要点是把等待队列(wait queue)和每一个事件联系起来。需要等待事件的进程在转入休眠状态后插入到队列中。当事件发生之后,内核遍历相应队列,唤醒休眠的任务让它投入运行状态。任务负责将自己从等待队列中清除。 等待队列的功能强大得令人吃惊,它们被广泛应用于整个内核中。更重要的是,实现等待队列的代码量并不大。 中国 1. wait_queue结构 18662:简单的数据结构就是等待队
本文档为【Linux操作系统源代码详细分析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_130577
暂无简介~
格式:doc
大小:142KB
软件:Word
页数:27
分类:工学
上传时间:2012-07-23
浏览量:204