admin管理员组

文章数量:1531447

2024年7月19日发(作者:)

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

分析

疯狂代码 / ĵ:Linux/

内容介绍: Linux 拥有现代操作系统所有功能如真正抢先式多任务处理、支持多用户内存保护虚拟内存支持

SMP、UP符合POSIX标准联网、图形用户接口和桌面环境具有快速性、稳定性等特点本书通过分析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 /asm-generic/smplock.h 107 /asm-i386/atomic.h 108 /asm-

i386/current.h 109 /asm-i386/dma.h 109 /asm-i386/elf.h 113 /asm-i386/hardirq.h 114 /asm-

i386/page.h 114 /asm-i386/pgtable.h 115 /asm-i386/ptrace.h 122 /asm-i386/semaphore.h 123 /asm-

i386/shmparam.h 124 /asm-i386/sigcontext.h 125 /asm-i386/siginfo.h 125 /asm-i386/signal.h 127

/asm-i386/smp.h 130 /asm-i386/softirq.h 132 /asm-i386/spinlock.h 133 /asm-i386/system.h 137

/asm-i386/uaccess.h 139 //binfmts.h 146 //capability.h 147 /linux/elf.h 150 /linux/elfcore.h 156

/linux/errupt.h 157 /linux/kernel.h 158 /linux/kernel_stat.h 159 /linux/limits.h 160 /linux/mm.h 160

/linux/module.h 164 /linux/msg.h 168 /linux/personality.h 169 /linux/reboot.h 169 /linux/resource.h

170 /linux/sched.h 171 /linux/sem.h 179 /linux/shm.h 180 /linux/signal.h 181 /linux/slab.h 184

/linux/smp.h 184 /linux/smp_lock.h 185 /linux/swap.h 185 /linux/swapctl.h 187 /linux/sysctl.h 188

/linux/tasks.h 194 /linux/time.h 194 /linux/timer.h 195 /linux/times.h 196 /linux/tqueue.h 196

/linux/wait.h 198 init/.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_do.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/prk.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

第 2部分 Linux 内核源代码分析 第1章 Linux 介绍 让用户很详细地了解大多数现有操作系统实际工作方式是

不可能大多数操作系统源代码都是严格保密除了些研究用及为操作系统教学而设计系统外尽管研究和教学目都

很好但是这类系统很少能够通过对正式操作系统小部分实现来体现操作系统实际功能对于操作系统些特殊问题

这种折衷系统所能够表现就更是少得可怜了 在以实际使用为目标操作系统中让任何人都可以自由获取系统源代

码无论目是要了解、学习还是改进这样现实系统并不多本书主题就是这些少数操作系统中个:Linux Linux工作

方式类似于Uinx它是免费源代码也是开放符合标准规范标准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大系列这就是广为人知伯克利软件Software开发(BSD)Unix除了AT&T所提供Unix系列的外BSD是最有影

响力Unix系列BSD在Unix中增加了很多显著特性例如TCP/IP网络更好用户文件系统(UFS)工作控制并且改进了

AT&T内存管理代码 多年以来BSD版本Unix直在学术环境中占据主导地位但最终发展成为 V版本AT&TUnix则

成为商业领域领头羊从某种程度上来说这是有社会原因:学校倾向于使用非正式但通常更好用BSD风格Unix而商

业界则倾向于从AT&T获取Unix 在用户需求和用户编程改进特性促进下BSD风格Unix般要比AT&TUnix更具有

创新性而且改进也更为迅速但是在AT&T发布最后个正式版本 V Release 4(SVR4)时 V Unix已经吸收了BSD大

多数重要优点并且还增加了些自己优势这部分由于从1984年开始AT&T逐渐可以将Unix商业化而伯克利Unix开

发工作在1993年BSD4.4版本完成以后就逐渐收缩以至终止了然而BSD进步改进由外界开发者延续下来到今天还

在继续进行正在进行Unix系列开发中至少有 4个独立版本是直接起源于BSD4.4这还不包括几个厂商Unix版本例

如惠普HP-UX都是部分地或者全部基于BSD而发展起来 实际上Unix变种并不止BSD和 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)这个词不仅意味着用户可以免费获取软件

Software;而且更重要是它将意味着某种程度“解放”:用户可以自由使用、拷贝、查询、重用、修改甚至是分

发这份软件Software完全没有软件Software使用限制这也正是Stallman创建自由软件Software基金会(FSF)资

助GNU软件Software开发本意(FSF也在资助其他科研方面开发工作) 15年来GNU工程已经吸收、产生了大量

这不仅包括Emacs、gcc(GNUC编译器)、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和 V优点同时摒弃了它们缺点Linus(虽然我知道我应该称他为Torvalds但是所有人都称他为

Linus)独立把这个内核开发到0.02版这个版本已经可以运行gcc、bash和很少些应用这些就是他开始全部工作

了后来他又开始在因特网上寻求广泛帮助 不到 3年LinusUnix—Linux已经升级到1.0版本它源代码量也呈指数

形式增长实现了基本TCP/IP功能(网络部分代码后来重写过而且还可能会再次重写)此时Linux就已经拥有大约

10万用户了 现在Linux内核由150多万行代码组成Linux也已经拥有了大约1000万用户(由于Linux可以自由获

取和拷贝获取具体统计数字是不可能)Linux内核GNU/Linux附同GNU工具已经占据了Unix 50%市场些公司正

在把内核和些应用同安装软件Software打包在起生产出Linux发行版本这些公司包括Red Hat和Caldera 公司现

在GNU/Linux已经备受瞩目得到了诸如Sun、IBM、SGI等公司广泛支持SGI最近决定在其基于IntelMerced系

列机器上不再搭载自己Unix变种版本IRIX而是直接采用GNU/Linux;Linux甚至被指定为Amiga将要发布新操

作系统基础

1.2 GNU通用公共许可证 这样个如此流行操作系统当然值得我们学习按照通用公共许可证(GPLGeneral

Public License)规定Linux源代码可以自由获取这满足了我们学习该系统强烈愿望GPL这份非同寻常软件

Software许可证充分体现了上面提到Stallman思想:只要用户所做修改是同等自由用户可以自由地使用、拷贝、

查询、重用、修改甚至重新发布这个软件Software通过这种方式GPL保证了Linux(以及同许可证保证下大量其

他软件Software)不仅现在自由可用而且以后经过任何修改的后都仍然可以自由使用 请注意这里自由并不是说

没有人靠这个软件Software盈利有些日益兴起公司比如发行最流行Linux发行版本Red Hat就是个例子(Red

Hat自从上市以来市值已经突破数十亿美元每年盈利数十万美元而且这些数字还在不断增长)但是任何人都不能

限制其他用户涉足本软件Software领域而且所做修改不能减少其自由程度 本书附录B中收录了GNU通用公共

许可证全文

1.3 Linux开发过程 如上所述由于Linux是个自由软件Software它可以免费获取以供学习研究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)文中有所介绍大

家可以在/~esr/writings/找到这篇文章bazaar开发模型通过重视实验征集并充分利用

早期反馈对巨大数量脑力资源进行平衡配置可以开发出更优秀软件Software(顺便说下虽然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(顺便说下这种开发会比常规惯例要快每

版本所包含改变比以前更少了内核开发人员只需花很短时间就能够完成个实验开发周期)

及其镜像站点提供了最新可供内核版本而且同时包括稳定和开发版本如果你愿意话不需

要很长时间这些站点所提供最新版本中就可能包含了你部分源代码

第2章 代 码 初 识 本章首先从较高层次介绍Linux内核源概况这些都是大家关心些基本特点随后将简要介绍些

实际代码最后介绍如何编译内核 2.1 Linux内核源部分特点 在过去段时期Linux内核同时使用C语言和汇编语言

来实现这两种语言需要定平衡:C语言编写代码移植性较好、易于维护而汇编语言编写则速度较快般只有在速度

是关键原因或者些因平台相关特性而产生特殊要求(例如直接和内存管理硬件进行通讯)时才使用汇编语言 正如

实际中所做即使内核并未使用C对象特性部分内核也可以在g(GNUC编译器)下进行编译同其他面向对象编程语

言相比较相对而言C开销是较低但是对于内核开发人员来说这已经是太多了 内核开发人员不断发展编程风格形

成了Linux代码独有特色本节将讨论其中些问题 2.1.1 gcc特性使用 Linux内核被设计为必须使用GNUC编译器

gcc来编译而不是任何种C编译器都可以使用内核代码有时要使用gcc特性本书将陆续介绍其中部分 些gcc特有

代码只是简单地使用gcc语言扩展例如允许在C(不只是C)中使用inline关键字指示内联也就是说代码中被在每次

时都会被扩充因而就可以节约实际开销 般情况下代码编写方式比较复杂对于某些类型输入gcc能够产生比其他

输入效率更高执行代码从理论上讲编译器可以优化具有相同功能两种对等思路方法并且得到相同结果因此代码

编写方式是无关紧要但在实际上用某种思路方法编写所产生代码要比用另外些思路方法编写所产生代码执行速

度快许多内核开发人员知道怎样才能产生更高效执行代码这不断地在他们编写代码中反映出来 例如考虑内核中

经常使用goto语句—为了提高速度内核中经常大量使用这种般要避免使用语句在本书中所包含不到40 000行代

码中共有500多条goto语句大约是每80行个除汇编文件外精确统计数字是接近每72行个goto语句公平地说这是

选择偏向结果:比例如此高原因的是本书中涉及是内核源核心在这里速度比其他原因都需要优先考虑整个内核比

例大概是每260行个goto语句然而这仍然是我不再使用Basic进行编程以来见过使用goto频率最高地方 代码必

需受特定编译器限制特性不仅和普通应用开发有很大区别而且也区别于大多数内核开发大多数开发人员使用C语

言编写代码来保持较高可移植性即使在编写操作系统时也是如此这样做优点是显而易见最为重要点是旦出现更

好编译器员们可以随时进行更换 内核对于gcc特性完全依赖使得内核向新编译器上移植更加困难最近Linus对

这问题在有关内核邮件列表上表明了自己观点:“记住编译器只是个工具”这是对依赖于gcc特性个很好基本思

想表述:编译器只是为了完成工作如果通过遵守标准还不能达到工作要求那么就不是工作要求有问题而是对于标

准依赖有问题 在大多数情况下这种观点是不能被人所接受通常情况下为了保证和语言标准致开发人员可能需要

牺牲某些特性、速度或者其他相关原因其他选择可能会为后期开发造成很大麻烦 但是在这种特定情况下

Linus是正确Linux内核是个特例其执行速度要比向其他编译器可移植性远为重要如果设计目标是编写个可移植

性好而不要求快速运行内核或者是编写个任何人都可以使用自己喜欢编译器进行编译内核那么结论就可能会有

所区别了;而这些恰好不是Linux设计目标实际上gcc几乎可以为所有能够运行LinuxCPU生成代码因此对于

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 减少#和#def使用 现在Linux内核已经移植

到区别平台上但是我们还必须解决移植过程中所出现问题大部分支持各种区别平台代码由于包含许多预处理代

码而已经变得非常不规范标准例如: 这个例子试图实现操作系统可移植性虽然Linux关注焦点很明显是实现代码

在各种CPU上可移植性但是 2者基本原理是致对于这类问题来说预处理器是种解决方式这些杂乱问题使得代码

晦涩难懂更为糟糕是增加对新平台支持有可能要求重新遍历这些杂乱分布低质量代码段(实际上你很难能找到这

类代码段全部) 和现有方式区别是Linux般通过简单(或者是宏)来抽象出区别平台间差异内核移植可以通过实现

适合于相应平台(或宏)来实现这样不仅使代码主体简单易懂而且在移植过程中还可以比较容易地自动检测出你没

有注意到内容:如引用未声明时会出现链接有时用预处理器来支持区别体系结构但这种方式并不常用而相对于代

码风格变化就更是微不足道了 顺便说下我们可以注意到这种解决思路方法和使用用户对象(或者C语言中充满指

针struct结构)来代替离散switch语句处理区别类型思路方法十分相似在某些层次上这些问题和解决思路方法是

统 可移植性问题并不仅限于平台和CPU移植编译器也是个重要问题此处为了简化假设Linux只使用gcc来编译

由于Linux只使用同个编译器所以就没有必要使用#块(或者#def块)来选择区别编译器 内核代码主要使用

#def来区分需要编译或不需要编译部分从而对区别结构提供支持例如代码经常测试SMP宏是否定义过从而决定

是否支持SMP机

2.2 代码样例 了解Linux代码风格最好思路方法就是实际研究下它部分代码即使你不完全理解本节所讨论代码

细节也无关紧要毕竟本节主要目不是理解代码些读者可以只对本节进行浏览本节主要目是让读者对Linux代码进

行初步了解为今后工作提供必要基础该讨论将涉及部分广泛使用内核代码 2.2.1 prk prk(25836行)是内核内部

消息日志记录在出现诸如内核检测到其数据结构出现不致事件时内核会使用prk把相关信息打印到系统控制台上

对于prk般分为如下几类: * 紧急事件(emergency)—例如panic(25563行)多次使用了prk当内核检测到发生不

可恢复内部时就会panic然后尽其所能地安全关闭计算机这个中prk以提示用户系统将要关闭 * 调试—从

3816行开始#def块使用prk来打印SMP逻辑单元(box)中每个处理器相关配置信息但是此过程只有在使用

SMP_DEBUG标志编译代码情况下才能够被执行 * 普通信息—例如当机器启动时内核必须估计系统速度以确保

设备驱动能够忙等待(busy-wait)个精确极短周期计算这种估计值名为calibrate_delay(19654行)它既在

19661行使用prk声明马上开始计算又在19693行报告计算结果另外在第4章将详细介绍calibrate_delay 如果你

已经浏览过这些参照行你可能已经注意到prk和prf参数十分类似:个格式化串后跟零个或者多个参数加入串中格

式化串可能是以组“”开始这里N是从0到7数字包括0和7在内数字区分了消息日志等级(log level)只有当日志

等级高于当前控制台定义日志等级(console_loglevel25650行)时才会打印消息root可以通过适当减小控制台日

志等级来过滤不是很紧急消息如果内核在格式化串中检测不到日志等级序列那么就会直打印消息(实际上日志等

级序列并不定要在格式化串中出现可以在格式化文本中查找到它代码) 从14946行开始#块介绍说明了这些特殊

序列这些定义可以帮助者正确区分对prk简单地说我称日志等级0到4为“紧急事件”等级5到等级6为“普通信

息”等级7自然就是我所说“调试”(这种分类思路方法并不意味着其他更好分类思路方法没有用处而只是目前

我们还不关心它而已)

在上面讨论基础上我们研究下代码本身 prk 25836:参数fmt是prf类型格式化串如果你对“...”部分内容不熟

悉那就 需要参阅本好C语言参考书(在其索引中查找“变参variadic function”)另外在安装GNU/Linux中

stdarg帮助里也包含了个有关变参简明描述在这儿只需要敲入“man stdarg”就可以看到 简单地说“...”部

分提示编译器fmt后面可能紧跟着数量不定任何类型参数由于这些参数在编译时候还没有类型和名字内核使用由

3个宏va_start、va_arg和va_end组成特殊组及个特殊类型—va_list对它们进行处理 25842:msg_level记录了

当前消息日志等级它是静态这看起来可能会有些奇怪—为什么下次对prk需要记录日志等级呢?问题答案是只有

打印出新行(n)或者赋给个新日志等级序列以后当前消息才会结束这样通过在包含消息结束新行里prk就保证了

在多个短期冲突情况下者只打印唯个长消息 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该变量代表prk参数中“...”部分 25848:内核自身vsprf(为节省空间而省略)实现

该功能和标准vsprf非常相似向buf中写入格式化文本(25634行)并返回写入串长度(长度不包括最后位终止0字节

)很快你将可以看到为什么这种机制会忽略buf前 3个 (正如25847行注释中所述)我们应该注意到在这里并没有

采取严格措施来保证缓冲器不会过载这里系统假定1024个长度buf已经足够使用(参阅25634行)如果内核在这里

能够使用vsnprf话情况就会好许多然而vsnprf还有另外个参数限制了它能够写入缓冲器长度 25849:计算buf中

最近使用元素va_end终止对“...”参数处理 25851:开始格式化消息循环其中存在个内部循环能够处理更多内

容(这点随后就能看到)因此每次内循环开始都开始个新打印行由于通常情况下prk只用于打印单行所以在每次中

这种循环通常只执行次 25853:如果预先不知道消息日志等级prk会检查当前行是否以日志等级序列开头

25860:如果不是buf中开始未使用 3个就能够起作用了(第次以后每次循环都会覆盖部分消息文本但是这样并不

会引起问题这里文本只是前面行中部分它们已经被打印过而且以后也不再需要了)这样就可以将日志等级插入

buf中 25866:此处有如下属性:p指向日志等级序列(消息文本紧随其后)msg指向消息文本—请注意25852行和

25865行中对msg赋值 由于已知p用来指示日志等级序列开头—该日志等级序列可能是由自身所创建日志等级

可以从p中抽出并存到msg_level中 25868:没有检测到新行清空line_feed标志 25869:这是前面谈到过内循环

循环将运行到本行结束(也就是检测到新行标志)或者缓冲器末尾为止

25870:除了将消息打印到控制台的外prk还能够记录最近打印长度为LOG_ BUF_LEN组(LOG_BUF_LEN为

16K请参看25632行)如果在控制台打开的前内核就已经prk则显然不能在控制台上正确打印消息但是这些消息将

被尽可能地到log_buf中(25656行)当控制台打开以后缓存Cache在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行)记录从机器启动的后由prk写入所

有长度它在每次循环中都会被更新而不是在循环结束后才改变次基于同样道理log_start和log_size处理方式也

是样这实际上是种优化时机本书将在结束对介绍的后再对它进行详细讨论 25879:消息被分为若干行这当然要

使用新行标志符来进行分割旦内核检测到新行标志符就写入个完整行从而内循环执行也可以提前终止

25884:在这里我们先不考虑内部循环是否会提前退出从msg到p序列是专门提供给控制台使用(这种序列我称的

为行但是不要忘了这里行可能并不意味着新行终止buf也许还没有终止)如果该行日志等级高于系统控制台定义

日志等级而且当前又有控制台可供打印那么就能够正确打印该行(记住prk可能在所有控制台打开的前就已经被

过了) 如果在该消息块中没有发现日志等级序列并且在前面prk中也没有对msg_level赋值那么本行中

msg_level就是-1由于console_loglevel总不小于1(除非root通过sysctl接口锁定)于是总是可以打印这些行

25886:本行应该能够被打印prk通过遍历打开控制台驱动链表告知每个控制台驱动去打印当前行设备驱动在本

书讨论范围的外因此控制台驱动代码则并不包含在内) 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不会再次进入对prk(有部分内核移植版定

义了自己特有速度较快memcpy版本因此所有移植都要在这点上保持致)如果memecpyprk来报告失败那么就有

可能触发无限循环

然而在这点上也并不是真无药可救使用这种解决方案最大问题在于该内核循环形式中也要留意新行标志符因此

使用memcpy将整个消息拷贝到log_buf中是不正确:如果此处存在新行我们将无法对其进行处理 我们可以试验

个箭双雕办法下面这种替代尝试虽然可能比前面那种初步解决思路方法速度要慢但是它保持了内核版本语意:

(请注意gcc优化器十分灵敏它足以能检测到循环内部表达式log_buf+LOG_BUF_LEN并没有改变因此在上面循

环中试图手工加速计算是没有任何效果) 不幸是这种思路方法并不能比现在内核版本在速度上快许多而且那样

会使得代码晦涩难懂(如果你编写过更新log_size和log_start代码你就能清楚地了解这点)你可以自己决定这种折

衷是否值得然而无论怎样我们学到了些东西通常不管成功和否改进内核代码都可以加深你对内核工作原理理解

2.2.2 等待队列 前节我们曾简要提到进程(也就是正在运行)可以转入休眠状态以等待某个特定事件当该事件发

生时这些进程能够被再次唤醒内核实现这功能技术要点是把等待队列(wait queue)和每个事件联系起来需要等

待事件进程在转入休眠状态后插入到队列中当事件发生的后内核遍历相应队列唤醒休眠任务让它投入运行状态

任务负责将自己从等待队列中清除 等待队列功能强大得令人吃惊它们被广泛应用于整个内核中更重要是实现等

待队列代码量并不大 1. wait_queue结构 18662:简单数据结构就是等待队列节点它包含两个元素: * task—指

向struct task_struct结构指针它代表个进程从16325行开始struct task_struct结构将在第7章中进行介绍 *

next—指向队列中下节点指针因而等待队列实际上是个单链表 通常我们用指向等待队列队首指针来表示等待

队列例如prk使用等待队列log_wait(25647行) 2. wait_event 16840:通过使用这个宏内核代码能够使当前执行

进程在等待队列wq中等待直至给定condition(可能是任何表达式)得到满足 16842:如果条件已经为真当前进程

显然也就无需等待了 16844:否则进程必须等待给定条件转变为真这可以通过__wait_event来实现(16824行)我

们将在下节介绍它由于__wait_event已经同wait_event分离已知条件为假部分内核代码可以直接

__wait_queue而不用通过宏来进行冗余(特别是在这些情况下)测试实际上也没有代码会真正这样处理更为重要

是如果条件已经为真wait_event会跳过将进程插入等待队列代码 注意wait_event主体是用个比较特殊结构封

闭起来: 奇怪是这个小窍门技巧并没有得到应有重视这里主要思路是使被封闭代码能够像个单句样使用考虑下

面这个宏该宏目是如果p是个非空指针则free: 除非你在如下所述情况下使用FREE1否则所有都是正确有效:

FREE1经扩展以后就和(FREE1)联系在起 有些员通过如下途径解决这种问题: 这两种思路方法都不尽人意员在

宏以后自然而然使用分号会把扩展信息弄乱以FREE2为例在宏展开的后为了使编译器能更准确地识别我们还需

要进行定缩进调节最终代码如下所示: 这样就会引起语法—和任何个都不匹配FREE3从本质上讲也存在同样问

题而且在研究问题产生原因同时就能够明白为什么宏体里是否包含是无关紧要不管宏体内部内容如何只要使用

组括号来指定宏体就会碰到相同问题 引入do/while(0)窍门技巧能够克服前面所出现所有问题现在我们可以编

写FREE4 将FREE4和其他宏样插入相同代码的后这段代码当然可以正确执行编译器能够优化这个伪循环舍弃循

环控制因此执行代码并没有速度损失我们也从而得到了能够实现理想功能宏 虽然这是个可以接受解决方案但是

我们不能不提到是编写要比编写宏好得多不过如果你不能提供所需开销那么就需要使用内联这种情况虽然在内

核中经常出现但是在其他地方就要少得多(不可否认当使用C、gcc或者任何实现了将要出现修正版ISO标准C编

译器时这种方案只是种选择就是最后为C增加内联) 3. __wait_event 16824:__wait_event使当前进程在等待队

列wq中等待直至condition为真 16829:通过add_wait_queue(16791行)局部变量__wait可以被链接到队列上

注意__wait是在堆栈中而不是在内核堆中分配空间这是内核中常用种窍门技巧在宏运行结束的前__wait就已经

被从等待队列中移走了因此等待队列中指向它指针总是有效 16830:重复分配CPU给另个进程直至条件满足这

点将在下面几节中讨论 16831:进程被置为TASK_UNINTERRUPTIBLE状态(16190行)这意味着进程处于休眠状

态不应被唤醒即使是信号也不能打断该进程休眠信号在第6章中介绍而进程状态则在第7章中介绍 16832:如果

条件已经满足则可以退出循环 请注意如果在第次循环时条件就已经满足那么前面行赋值就浪费了(在循环结束

的后进程状态会立刻被再次赋值)__wait_event假定宏开始执行时条件还没有得到满足而且这种对进程状态变量

state延迟赋值也并没有什么害处在某些特殊情况下这种思路方法还十分有益例如当__wait_event开始执行时条

件为假但是在执行到16832行时就为真了这种变化只有在为有关进程状态代码计算condition变量值时才会出现

问题但是在代码中这种情况我没有发现

16834:schedule(26686行在第7章中讨论)将CPU转移给另个进程直到进程再次获得CPU时对schedule才会返

回这种情况只有当等待队列中进程被唤醒时才会发生 16836:进程已经退出了因此条件必定已经得到了满足进

程重置TASK_RUNNING状态(16188行)使其适合CPU运行 16837:通过remove_wait_queue(16814行)将进程

从等待队列中移去wait_event_erruptible和__wait_event_erruptible(分别参见16868行和16847)基本上和

wait_event和__wait_event相同但区别是它们允许休眠进程可以被信号中断信号将在第6章中介绍 请注意

wait_event是被如下结构所包含 和do/while(0)窍门技巧样这样可以使被封闭起来代码能够像个单元样运行这

样封闭代码就是个独立表达式而不是个独立语句也就是说它可以求值以供其他更复杂表达式使用发生这种情况

原因主要在于些不可移植gcc特有代码存在通过使用这类窍门技巧个块中最后个表达式值将定义为整个块最终值

当在表达式中使用wait_event_erruptible时执行宏体后赋__ret值为宏体值(参见16873行)对于有Lisp背景知识

员来说这是个很常见概念但是如果你仅仅了解点C和其他些相关过程性设计语言你可能就会觉得比较奇怪

__wake_up 26829:该用来唤醒等待队列中正在休眠进程它由wake_up和wake_up_ erruptible(请分别参见

16612行和16614行)这些宏提供mode参数只有状态满足mode所包含状态的进程才可能被唤醒 26833:正如将

在第10章中详细讨论那样锁(lock)是用来限制对资源访问这在SMP逻辑单元中尤其重要在这种情况下当个

CPU在修改某数据结构时另个CPU可能正在从该数据结构中读取数据或者也有可能两个CPU同时对同个数据结

构进行修改等等在这种情况下受保护资源显然是等待队列非常有趣是所有等待队列都使用同个锁来保护虽然这

种思路方法要比为每个等待队列定义个新锁简单得多但是这就意味着SMP逻辑单元可能经常会发现自己正在等

待个实际上并不必须锁 26838:本段代码遍历非空队列为队列中正确状态每个进程wake_up_process(26356行

)如前所述进程(队列节点)在此可能并没有从队列中移走这在很大程度上是由于即使队列中进程正在被唤醒它仍

然可能希望继续存在于等待队列中这点正如我们在__wait_event中发现问题样

2.2.3 内核模块 整个内核并不需要同时装入内存应该确认为保证系统能够正常运行些特定内核必须总是驻留在

内存中例如进程调度代码就必须常驻内存但是内核其他部分例如大部分设备驱动就应该仅在内核需要时候才装

载而在其他情况下则无需占用内存 举例来说只有在内核真正和CD-ROM通讯时才需要使用完成内核和CD-

ROM通讯设备驱动因此内核可以被设置为在和设备通讯的前才装载相应代码内核完成和设备通讯的后可以将这

部分代码丢弃也就是说旦代码不再需要就可以从内存中移走系统运行过程中可以增减这部分内核称为内核模块

内核模块优点是可以简化内核自身开发假设你购买了个新高速CD-ROM驱动器但是现有CD-ROM驱动并不支持

该设备你自然就希望增加对这种高速模式支持以提高系统光驱设备性能如果作为内核模块来编译驱动你工作将

会方便得多:编译驱动、加载到内核、测试、卸载驱动、修改驱动、再次加载驱动到内核、测试如此周而复始如

果你驱动是直接编辑在内核中那么你就必须重新编译整个内核并且在每次修改驱动的后重新启动机器这样慢得

很多 自然你也必须留意内核模块对于指明其他内核模块在磁盘上驻留位置那些模块定不能从内存中卸载否则内

核将只能通过访问磁盘来装载处理磁盘访问内核模块这是不可能实现这也是我们要选择把部分内核作为模块编

译还是直接编译进内核使其常驻内存又个原因知道自己系统设置方式因而也就可以选择正确使用方式(如果为了

确保安全可以简单忽略内核模块系统优点而把所有内容都编译到内核里面) 内核模块会带来些速度上损失这是

些必需代码现在并不在RAM中必需要从磁盘读入但是整个系统性能通常会有所提高这主要是通过丢弃暂时不使

用模块可以释放出额外RAM供应用使用如果这部分内存被内核所占用应用将只能更加频繁地进行磁盘而这种磁

盘会显著地降低应用性能(磁盘交换将在第8章中讨论) 内核模块还会带来因复杂度增加所造成开销这是在系统

运行过程中移进移出部分内核需要额外代码然而复杂度开销是可以管理通过使用外部来代理些必需工作还可以

更进步降低复杂度开销(更为确切说法是这样做不是减少了复杂度开销而是把复杂度开销重新分配了下)这是对内

核模块原理个小小扩展:即使是内核支持模块对于内核来说也只是外部、部分可用只有在需要时候才被装入内存

通常用于这种目称为modprobe有关modprobe代码超出了本书范围但是在Linux每个发行版本中都包含有它本

节剩余部分将讨论同modprobe协同工作以装载内核模块内核代码 1. request_module 24432:作为介绍说明

的前注释request_module是个内核其他模块在需要装载其他内核模块时候都必须这个就像内核处理其他工作

样这种也是为当前运行进程进行从进程角度来看这种请求通常是隐含—正在执行进程其他请求内核可能会发现

必须调入个模块才能够完成该请求例如请参见10070行这里是些将在第7章中讨论代码 24446:以内核中个独立

进程形式执行exec_modprobe(24384行)这并不能只通过简单实现exec_modprobe要继续exec来执行个因此

对exec_modprobe简单将永远不会有返回 这和使用fork以准备exec十分类似你可以认为kernel_thread对内

核来说就是较低版本fork虽然两者有很大区别fork是从指定开始执行新进程而不是从者当前位置开始运行正如

fork样kernel_thread返回值是新进程进程号 24448:和fork样从kernel_thread返回负值表示内部 24455:正如

中论述样大部分信号将因当前进程而被暂时阻塞 24462:等待exec_modprobe执行完毕同时指出所需要模块是

已经成功装入内存还是装载失败了 24465:结束运行恢复信号如果exec_modprobe返回代码则打印消息 2.

exec_modprobe 24384:exec_modprobe运行为内核增加内核模块这里模块名是个void*指针而不是char*指

针原因简单说来就是kernel_thread 产生通常都使用void*指针参数 24386:设置modprobe参数列表和环境

modprobe_path(24363行)用来定位modprobe位置它可以通过内核sysctl特性来修改这点将在第11章中介绍

(参见30388行)这意味着root可以动态选择区别于/sbin/modprobe来运行以适应当modprobe被安装到其他地

方或者使用修改过modprobe替换掉了原有modprobe的类情况

24400:(正如代码中描述样)出于安全性考虑丢弃所有挂起信号和信号句柄(handl-ers)这里最重要部分是对

flush_signal_handlers(28041行)它使用内核默认信号句柄代替所有用户定义信号句柄如果在此时有信号被传送

到内核它将获得默认响应—通常是忽略信号或杀死进程但是不管怎样都不会引起安全风险由于该从触发它进程

中分离出来(如前所述)所以不管原始进程在此处是否改变其原来分配信号句柄都不会产生任何影响 24405:关闭

进程打开所有文件最重要是这意味着modprobe不再从进程中继承标准输入输出和标准这很有可能会引起安全

漏洞(这可能是在替代modprobe中引起问题但是modprobe本身实际上并不关心这个差异)

24413:modprobe作为root运行它拥有root所拥有所有权限和整个内核中其他地方样请注意root使用用户ID号

0假定在这里已经被写入用户ID号和权能系统(capability system在接下来几行中会用到)将在第7章中介绍

24421:试图执行modprobe如果尝试失败内核将使用prk打印消息并返回代码这里是可能产生prk缓冲器过载地

点的module_name长度并没有明确限制就我们对该看法而言它可能长达百万个为防止prk缓冲器过载你必需遍

历所有对于该(实际上是对request_module)以保证每个者使用足够短、不会为prk造成麻烦模块名 24427:当

execve成功执行时它不会返回任何结果因此本处是不可能执行到但是编译器却并不知道这点因此此处使用了语

句以保证gcc不出错 对于内核进步讨论将超出本章既定范围因此在这个问题上我们到此为止然而本书中也包括

了其他必需内核代码在读完第4章和第5章的后也许你会希望再次仔细研读下这部分内容有关这个问题两个文件

是/linux/module.h(从15529行开始)和/kernel/module.c(从24476行开始)和sys_create_module(24586行)、

sys_init_module(24637行)、sys_delete_module(24860行)和sys_query_module(25148行) 4个需要特别注

意样struct module(15581行)也要特别引起注意这些实现了modprobe及insmod、lsmod和rmmod所使用系

统以完成模块装载、定位和卸载 内核触发直接回调内核现象看起来很令人奇怪但是实际上进行工作不止于此例

如modprobe必须实际访问磁盘以搜寻要装载模块而且更为重要点是这种思路方法赋予root对内核模块系统更

多控制能力这主要是root也可以运行modprobe及相关因此root既可以手工装载、查询、卸载模块也可以由内

核自动完成

2.3 配置和编译内核 你可能仅仅研读、欣赏而并不修改Linux内核源代码但是更普遍情况是用户有强烈愿望去

改进内核代码并完成相应测试这样我们就需要知道如何重建内核本节就是要告诉你如何实现这点而最终则归结

于如何把你所做修改发行给别人以使得每个人都能从你工作中受益 2.3.1 配置内核 编译内核第步就是配置内

核这是增加或者减少对内核特性支持及修改内核些特性必要步骤例如你可以要求内核为自己声卡指定个区别

DMA通道如果内核配置和你需要相同那么你可以直接跳过本节否则请继续阅读以下内容 为了完成内核配置请

先切换到root用户然后转入如下内核源目录: cd /usr/src/linux

接着敲入如下命令组: make config make menuconfig make xconfig

这 3条命令都可以用来配置内核但它们发挥作用方式各不相同: * make config— 3种思路方法中最简单也是最

枯燥种但是最基本点是它可以适应任何情况通过为每个内核支持特性向用户提问方式来决定在内核中需要包含

哪些特性对于大多数问题你只要回答y(yes把该特性编译进内核中)、m(作为模块编译)或者n(no根本不对该特性

提供支持)在决定的前用户应该考虑清楚这个过程是不可逆如果你在该过程中犯了就只能按Ctrl+C退出你也可以

敲入?以获取帮助图2-1显示了这种思路方法在X终端上运行情况 图2-1 运行中make config 幸运是这种思路

方法还有些智能例如如果你对SCSI支持回答no那么系统就不会再询问你有关SCSI细节问题了而且你可以只按回

车键以接受默认选择也就是当前设置(因此如果当前内核将对于SCSI支持编译进了内核在这个问题上按回车键就

意味着继续把对SCSI支持编译进内核中)即使是这样大部分用户还是宁愿使用另外两种思路方法 * make

menuconfig—种基于终端配置机制用户拥有通过移动光标来进行浏览等功能图2-2显示了在X终端上运行make

menuconfig虽然在控制台上显示是彩色但是在终端上显示仍然相当单调使用menuconfig必须要有相应

ncurses类库 * make xconfig—这是我最喜欢种配置方式只有你能够在X server上用root用户身份运行X应用

时这种配置方式才可以使用(有些偏执用户就不愿意使用这种方式)你还必须拥有Tcl窗口系统这实际上还意味着

你必须拥有Tcl、Tk以及个正在运行X安装作为补偿用户获得是更漂亮、基于X系统以及和menuconfig功能相同

配置思路方法图2-3显示了在这种思路方法运行过程中打开“可装载模块支持(Loadable module support)”子

窗口情况 如上所述这 3种思路方法都实现了相同功能:它们都生成在构建内核时使用.config文件而唯区别在于

创建这个文件时难易程度区别

2.3.2 构建内核 构建内核要做工作要比配置内核所做工作少得多虽然有几种方式都能实现这功能但是选择哪

种依赖于你希望怎样对系统进行设置长期以来我已经形成了如下习惯虽然这种习惯比我所必须要做略微多些但

是它包含了所有基本问题首先如果你还不在内核源目录中请先再次转入这目录: cd /usr/src/linux

现在切换到root用户使用下面显示命令生成内核现在在shell中敲入下面命令注意make命令空间关系分成了两

行但实际上这在shell输入时是个只有行命令: make dep clean zlilo boot modules modules_

当给出了如上多个目标时除非前面所有目标都成功了否则make能够知道没有必要继续尝试下面目标因此如果

make能够运行结束成功退出那么这就意味着所有目标都正确构建了现在你可以重新启动机器以运行新内核

2.3.3 备份重要性 当修改(fooling)内核时你必须准备个能够启动备用内核实现该目种方式是通过配置Linux加

载(LILO)以允许用户选择启动内核映象其中的是从没有修改过内核备份(我总是这样做) 如果你比较有耐心那么

你就可以使用zdisk目标而不使用zlilo目标;它可以把能够启动内核映象写入软盘中这样你就可以通过在启动时

插入软盘方式启动你测试内核;如果没有插入软盘则启动正常内核 但是请注意:内核模块并没有被装载到软盘

中它们实际上是装在硬盘中(除非你愿意承担更多麻烦)因此如果你弄乱了内核模块即使是zdisk目标也救不了你

实际上上面提到这两种思路方法都存在这个问题虽然有比较好解决思路方法可用但是最简单思路方法(也就是我

所使用思路方法)是把备份内核作为严格独立内核来编译而不使用可装载模块支持通过这种思路方法即使我弄乱

了内核而不得不使用备份启动系统那么不管问题是实验性内核不正确还是内核模块原因都无关紧要不管怎样在

备份内核中已经有我需要所有东西了 由于用户所做修改可能导致系统崩溃如损坏磁盘上数据等等并不仅仅只是

打乱设备驱动或文件系统在测试新内核的前备份系统最新数据也是个英明决策(虽然设备驱动开发不是本书主题

但是必需指出是设备驱动缺陷可能会引起系统物理损坏例如显示器是不能备份而且因价格昂贵而不易替换)作为

个潜在内核黑客你最佳投资(当然是读过本书以后)是个磁带驱动器和充足磁带 2.3.4 发布你改进 下面是有关发

布你所做修改些基本规则: * 检查最新发行版本确保你所处理不是已经解决了问题 * 遵守Linux 内核代码编写

风格简要说就是8缩进以及K&R括号风格(forwhileswitch或者do后面同行中紧跟着开括号)在内核源目录下面文

档编写和代码风格文件给出了完整规则不过我们已经介绍了其中关键部分注意本书中包含源代码为节省空间而

进行了大量重新编辑在该过程中我可能打破了其中些规则 * 独立发行相对无关修改这样只想使用你所做某部分

修改人就可以十分方便地获得想要东西而不用次检验所有修改内容 * 让使用你所做修改用户清楚他们可以从你

修改中获取什么同样你也应该给出这些问题可信度你是15min的前才匆匆完成你修改甚至还没有时间对它们进

行编译还是已经在你和你朋友系统中已长期稳定地运行过这个修改? 假设现在你已经准备好发行自己修改版本

了那么要做第步是建立个介绍说明你所做修改文件你可以使用df自动创建这个文件结果或者被称为dfs或者在

Linux中更普遍被称为补丁(patch) 发布过程十分简单假设原来没有修改过源代码在linux-2.2.5目录下而你修改

过源代码在linux-my目录下那么只要进行如下简单工作就可以了(只有在链接不存在情况下才需要执行ln): 现在

输出文件包含了其他用户应用这个修改时所需要切内容(警告:如上所述两个源间所有差别都会包含在

这个补丁文件中df不能区分修改部分的间关系所以就把它们都罗列了出来)如果补丁文件相对较小你可以使用邮

件直接发往内核邮件列表如果补丁很大那么就需要通过FTP或者Web站点发布这时发给邮件列表信件中就只需

要包含个URL Linux内核邮件列表常见问题解答(FAQ)文件位于/~rreilova/

linux/该FAQ中包含了邮件列表订阅、邮件发布及阅读邮件列表注意事项等等

2009-2-12 3:38:30

疯狂代码 /

本文标签: 内核使用代码进程系统