QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 11126|回复: 18
打印 上一主题 下一主题

[分享]《Linux内核注释》

[复制链接]
字体大小: 正常 放大
ilikenba 实名认证       

1万

主题

49

听众

2万

积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    跳转到指定楼层
    1#
    发表于 2005-3-4 21:40 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    <  align=center><B >介  绍<p></p></B></P>2 @3 `' I+ e- F! M! K: c
    <  align=left><FONT size=3>    《Linux内核注释》旨在给程序员和学生提供比以前更详细和更易理解的Linux内核代码注释。作者分析了核心代码,并对重要的函数、系统调用和数据结构提供了大量的注释。<p></p></FONT></P>  ?; |: ^% J; H
    <  align=left><FONT size=3>    对《注释》系列丛书的写作灵感都来源于John Lions所著的大量流行的《Lions' Commentary on Unix》一书。无数的计算机专业的学生在复制和使用这本书。这本书对AT&amp;T的Unix操作系统的早期版本的内幕进行了深刻的剖析。<p></p></FONT></P>
    * H( J9 s" z( t7 S5 a0 f- @<  align=left><FONT size=3>    《Liunx内核注释》同样提供了对流行的功能强大的Liunx操作系统的结构和函数实现的内幕介绍。本书的主要目标是:<p></p></FONT></P>+ q) A! M5 E5 L& v6 m
    <  align=left><FONT size=3>    1.提供一个最新的和完整的服务器版本的完整源代码。(这本书分析的版本是<st1:chsdate w:st="on" IsROCDate="False" IsLunarDate="False" Day="30" Month="12" Year="1899">2.2.5</st1:chsdate>版,也是写这本书时发布的最新版本。)<p></p></FONT></P>+ r' F9 }, w& F7 Z& f( r
    <  align=left><FONT size=3>    2.提供一个对每个子系统功能的一般性概述。<p></p></FONT></P>
    * }* S7 C" c- u4 L9 q<  align=left><FONT size=3>    3.研究各个子系统主要的函数和数据结构。<p></p></FONT></P>/ X' z" f' R$ Q  h( b/ I; }
    <  align=left><FONT size=3>    4.对开发者应怎样通过修改源代码来改进和扩展内核提出建议。<p></p></FONT></P>9 h4 p1 R! X9 C3 @5 r2 E
    <  align=left><FONT size=3>    本书的最后一项目标<FONT face="Times New Roman">—</FONT>定制--是你学习内核代码的最有说服力的原因。通过理解内核是怎样工作的,你能够编写自己的代码用以在你的操作系统中实现所需要的功能。如果允许其他人共享你的改进,你的代码甚至会在官方发行的内核代码中出现,被全世界数百万计的人们所使用。<p></p></FONT></P>
    $ n7 M% n* _9 M0 w% s4 e<  align=left><FONT size=3>    开放源代码是指让开发者研究源代码并实现功能性扩展。Linux是全世界成长最快的操作系统,开放源代码是其主要的原因之一。从玩游戏,到网上冲浪,到为大大小小的ISP们提供稳定的Web服务器平台以至解决最庞大的科学难题,Linux都能胜任全部工作。它之所以能如此强大是因为有像你一样的开发者在研究、学习并且扩充这个系统。<p></p></FONT></P>
    * {5 \/ k8 r5 o$ Y3 M, N<  align=left><B >你能从本书中学到什么</B><B ><p></p></B></P>! ]. S; v, {4 W( E
    <  align=left><FONT size=3>    这本书集中解释了Linux内核源代码的核心中专用代码行是如何运行的。你将学习到内核最内部的子系统是怎样构造和这种构造能够实现系统功能的理由。<p></p></FONT></P>
    * @6 ~. \1 Q+ o9 U( `! o0 Q) f; z<  align=left><FONT size=3>    本书的第一部分以易于阅读和交叉引用的格式复制了一个经过筛选的linux 内核源代码的子集。在这本书稍后的注释中,无论一行代码在何处被引用,你都会在这一行前面发现一个小箭头。这个箭头指出了对此行进行注释处的页号。<p></p></FONT></P>  k6 l/ M9 D) V& O8 J- @( k: P
    <  align=left><FONT size=3>    源代码后是这本书的第二部分,即注释部分,注释部分对源代码进行了讨论。注释部分的每一章讨论了一个不同的内核子系统,或者是其它的功能性逻辑组件,例如系统调用或内存管理。注释部分大量的行号引用为你指明了所讨论代码行的确切行号。<p></p></FONT></P>0 j+ R0 L$ X* t5 K8 Z
    <  align=left><FONT size=3>    在本书正文后的附录部分,简洁地覆盖了自本书主要部分完成以后内核的变化。在附录中还包含了被内核用做软件许可证的完整的GNU常规公众许可证。最后,本书为你提供了一个索引。通过该索引你可以查询术语或主题。这将让你更快更有效的使用这本参考工具书。<p></p></FONT></P>
    ' s) i9 x) _" w2 f- [( }<  align=left><B >本书的使用对象</B><p></p></P>
    - Z, i+ o  ^! D! W; j* k) y<  align=left><FONT size=3>    本书假设你能阅读C语言的代码,不怕偶尔读一些汇编语言代码。并且你想知道一个快速的、坚固的、可靠的、健壮的、现代的、实用的操作系统是如何工作的。一些读者也许是这样的程序员,他们想为前进中的Linux内核发展工作提供他们自己的改进和添加内容。<p></p></FONT></P>8 Q4 {: g: U& G6 Y9 `+ J5 L2 H
    <  align=left><B >如何使用本书</B><p></p></P>- Y; A- u  }1 I! L7 O
    <  align=left><FONT size=3>    用最适合你自己的方法放松地去看这本《linux 内核注释》。因为写这本书的目的是为提供一个参考资料,你不必从头看到尾。因为注释和代码是一一对应的,你可以从另外一个方向接近内核。<p></p></FONT></P>
    - @% U2 g+ {- O8 r: D9 Z$ O<  align=left><FONT size=3>    欢迎你对我的第一本书提出意见。你可以通过e-mail和我联系。地址是:<a href="mailtlckc@ScottMaxwell.org" target="_blank" >lckc@ScottMaxwell.org</A>。 勘误表、更新和其它一些有用信息可以通过访问 <a href="http://www.scottmaxwell.org/lckc.html" target="_blank" >http://www.ScottMaxwell.org/lckc.html</A> 得到。<p></p></FONT></P>$ i3 ~: Y* w: s6 |; i2 `
    <  align=left><p><FONT face="Times New Roman" size=3> </FONT></p></P>
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman">第1章  Linux</FONT>简介</H1>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">让用户很详细地了解现有操作系统的实际工作方式是不可能的,因为大多数操作系统的源代码都是严格保密的。其例外是一些研究用的系统,另外一些是明确为操作系统教学而设计的系统。(还有一些系统则是同时出于这两种目的。)尽管研究和教学这两个目的都很好,但是这类系统很少能够通过对正式操作系统的小部分实现来体现操作系统的实际功能。对于操作系统的一些特殊问题,这种折衷系统所能够表现的就更是少得可怜了。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在以实际使用为目标的操作系统中,让任何人都可以自由获取系统源代码,无论目的是要了解、学习还是改进,这样的现实系统并不多。本书的主题就是这些少数操作系统中的一个:<FONT face="Times New Roman">Linux</FONT>。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Linux</FONT>的工作方式类似于<FONT face="Times New Roman">Uinx</FONT>,是免费的,源代码也是开放的,符合标准规范的<FONT face="Times New Roman">32</FONT>位(在<FONT face="Times New Roman">64</FONT>位<FONT face="Times New Roman">CPU</FONT>上是<FONT face="Times New Roman">64</FONT>位)操作系统。<FONT face="Times New Roman">Linux</FONT>拥有现代操作系统的所具有的内容,例如:</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          真正的抢先式多任务处理,支持多用户</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          内存保护</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          虚拟内存</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          支持对称多处理机<FONT face="Times New Roman">SMP</FONT>(<FONT face="Times New Roman">symmetric multiprocessing</FONT>),即多个<FONT face="Times New Roman">CPU</FONT>机器,以及通常的单<FONT face="Times New Roman">CPU</FONT>(<FONT face="Times New Roman">UP</FONT>)机器</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          符合<FONT face="Times New Roman">OSIX</FONT>标准</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          联网</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          图形用户接口和桌面环境(实际上桌面环境并不只一个)</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          速度和稳定性</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">严格说来,<FONT face="Times New Roman">Linux</FONT>并不是一个完整的操作系统。当我们在安装通常所说的<FONT face="Times New Roman">Linux</FONT>时,我们实际安装的是很多工具的集合。这些工具协同工作以组成一个功能强大的实用系统。<FONT face="Times New Roman">Linux</FONT>本身只是这个操作系统的内核,是操作系统的心脏、灵魂、指挥中心。(整个系统应该称为<FONT face="Times New Roman">GNU/Linux</FONT>,其原因在本章的后续内容中将会给以介绍。)内核以独占的方式执行最底层任务,保证系统正常运行——协调多个并发进程,管理进程使用的内存,使它们相互之间不产生冲突,满足进程访问磁盘的请求等等。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在本书中,我们给大家揭示的就是<FONT face="Times New Roman">Linux</FONT>是如何完成这一具有挑战性的工作的。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">Linux(和Unix)的简明历史</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">为了让大家对本书所讨论的内容有更清楚的了解,让我们先来简要回顾一下<FONT face="Times New Roman">Linux</FONT>的历史。由于<FONT face="Times New Roman">Linux</FONT>是在<FONT face="Times New Roman">Unix</FONT>的基础上发展而来的,我们的话题就从<FONT face="Times New Roman">Unix</FONT>开始。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Unix</FONT>是由<FONT face="Times New Roman">AT&amp;T</FONT>贝尔实验室的<FONT face="Times New Roman">Ken Thompson</FONT>和<FONT face="Times New Roman">Dennis Ritchie</FONT>于<FONT face="Times New Roman">1969</FONT>年在一台已经废弃了的<FONT face="Times New Roman">DP-7</FONT>上开发的;它最初是一个用汇编语言写成的单用户操作系统。不久,<FONT face="Times New Roman">Thompson</FONT>和<FONT face="Times New Roman">Ritchie</FONT>成功地说服管理部门为他们购买更新的机器,以便该开发小组可以实现一个文本处理系统,<FONT face="Times New Roman">Unix</FONT>就在<FONT face="Times New Roman">DP-11</FONT>上用<FONT face="Times New Roman">C</FONT>语言重新编写(发明<FONT face="Times New Roman">C</FONT>语言的部分目的就在于此)。它果真变成了一个文本处理系统——不久之后。只不过问题是他们先实现了一个操作系统而已…</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">最终,他们实现了该文本处理工具,而且<FONT face="Times New Roman">Unix</FONT>(以及<FONT face="Times New Roman">Unix</FONT>上运行的工具)也在<FONT face="Times New Roman">AT&amp;T</FONT>得到广泛应用。在<FONT face="Times New Roman">1973</FONT>年,<FONT face="Times New Roman">Thompson</FONT>和<FONT face="Times New Roman">Ritchie</FONT>在一个操作系统会议上就这个系统发表了一篇论文,该论文引起了学术界对<FONT face="Times New Roman">Unix</FONT>系统的极大兴趣。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">由于<FONT face="Times New Roman">1956</FONT>年反托拉斯法案的限制,<FONT face="Times New Roman">AT&amp;T</FONT>不能涉足计算机业务,但允许它可以以象征性的费用发售该系统。就这样,<FONT face="Times New Roman">Unix</FONT>被广泛发布,首先是学术科研用户,后来又扩展到政府和商业用户。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">伯克利(<FONT face="Times New Roman">Berkeley</FONT>)的加州大学是学术用户中的一个。在这里<FONT face="Times New Roman">Unix</FONT>得到了计算机系统研究小组(<FONT face="Times New Roman">CSRG</FONT>)的广泛应用。并且在这里所进行的修改引发了<FONT face="Times New Roman">Unix</FONT>的一大系列,这就是广为人知的伯克利软件开发(<FONT face="Times New Roman">BSD</FONT>)<FONT face="Times New Roman">Unix</FONT>。除了<FONT face="Times New Roman">AT&amp;T</FONT>所提供的<FONT face="Times New Roman">Unix</FONT>系列之外,<FONT face="Times New Roman">BSD</FONT>是最有影响力的<FONT face="Times New Roman">Unix</FONT>系列。<FONT face="Times New Roman">BSD</FONT>在<FONT face="Times New Roman">Unix</FONT>中增加了很多显著特性,例如<FONT face="Times New Roman">TCP/IP</FONT>网络,更好的用户文件系统(<FONT face="Times New Roman">UFS</FONT>),工作控制,并且改进了<FONT face="Times New Roman">AT&amp;T</FONT>的内存管理代码。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">多年以来,<FONT face="Times New Roman">BSD</FONT>版本的<FONT face="Times New Roman">Unix</FONT>一直在学术环境中占据主导地位,但最终发展成为<FONT face="Times New Roman">System V</FONT>版本的<FONT face="Times New Roman">AT&amp;T</FONT>的<FONT face="Times New Roman">Unix</FONT>则成为商业领域的主宰。从某种程度上来说,这是有社会原因的:学校倾向于使用非正式但通常更好用的<FONT face="Times New Roman">BSD</FONT>风格的<FONT face="Times New Roman">Unix</FONT>,而商业界则倾向于从<FONT face="Times New Roman">AT&amp;T</FONT>获取<FONT face="Times New Roman">Unix</FONT>。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在用户需求驱动和用户编程改进特性的促进下,<FONT face="Times New Roman">BSD</FONT>风格的<FONT face="Times New Roman">Unix</FONT>一般要比<FONT face="Times New Roman">AT&amp;T</FONT>的<FONT face="Times New Roman">Unix</FONT>更具有创新性,而且改进也更为迅速。但是,在<FONT face="Times New Roman">AT&amp;T</FONT>发布最后一个正式版本<FONT face="Times New Roman">System V Release 4</FONT>(<FONT face="Times New Roman">SVR4</FONT>)时,<FONT face="Times New Roman">System V Unix</FONT>已经吸收了<FONT face="Times New Roman">BSD</FONT>的大多数重要的优点,并且还增加了一些自己的优势。这种现象的部分原因在于从<FONT face="Times New Roman">1984</FONT>年开始,<FONT face="Times New Roman">AT&amp;T</FONT>逐渐可以将<FONT face="Times New Roman">Unix</FONT>商业化,而伯克利<FONT face="Times New Roman">Unix</FONT>的开发工作在<FONT face="Times New Roman">1993</FONT>年<FONT face="Times New Roman">BSD4.4</FONT>版本完成以后就逐渐收缩以至终止了。然而,<FONT face="Times New Roman">BSD</FONT>的进一步改进由外界开发者延续下来,到今天还在继续进行。正在进行的<FONT face="Times New Roman">Unix</FONT>系列开发中至少有四个独立的版本是直接起源于<FONT face="Times New Roman">BSD4.4</FONT>,这还不包括几个厂商的<FONT face="Times New Roman">Unix</FONT>版本,例如惠普的<FONT face="Times New Roman">HP-UX</FONT>,都是部分地或者全部地基于<FONT face="Times New Roman">BSD</FONT>而发展起来的。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">实际上<FONT face="Times New Roman">Unix</FONT>的变种并不止<FONT face="Times New Roman">BSD</FONT>和<FONT face="Times New Roman">System V</FONT>。由于<FONT face="Times New Roman">Unix</FONT>主要使用<FONT face="Times New Roman">C</FONT>语言来编写,这就使得它相对比较容易地移植到新的机器上,它的简单性也使其相对比较容易重新设计与开发。<FONT face="Times New Roman">Unix</FONT>的这些特点大受商业界硬件供应商的欢迎,比如<FONT face="Times New Roman">Sun</FONT>、<FONT face="Times New Roman">SGI</FONT>、惠普、<FONT face="Times New Roman">IBM</FONT>、<FONT face="Times New Roman">DEC</FONT>(数字设备公司)、<FONT face="Times New Roman">Amdahl</FONT>等等;<FONT face="Times New Roman">IBM</FONT>还不止一次对<FONT face="Times New Roman">Unix</FONT>进行了再开发。厂商们设计开发出新的硬件并简单地将<FONT face="Times New Roman">Unix</FONT>移植到新的硬件上,这样新的硬件一经发布便具备一定的功能。经过一段时间之后,这些厂商都拥有了自己的专有<FONT face="Times New Roman">Unix</FONT>版本。而且为了占有市场,这些版本故意以不同的侧重点发布出来以更好的占有用户。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">版本混乱的状态促进了标准化工作的进行。其中最主要的就是<FONT face="Times New Roman">OSIX</FONT>系列标准,它定义了一套标准的操作系统接口和工具。从理论上说,<FONT face="Times New Roman">OSIX</FONT>标准代码很容易移植到任何遵守<FONT face="Times New Roman">OSIX</FONT>标准的操作系统中,而且严格的<FONT face="Times New Roman">OSIX</FONT>测试已经把这种理论上的可移植性转化为现实。直到今天,几乎所有的正式操作系统都以支持<FONT face="Times New Roman">OSIX</FONT>标准为目标。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">现在让我们回顾一下,在<FONT face="Times New Roman">1984</FONT>年,杰出的电脑黑客<FONT face="Times New Roman">Richard Stallman</FONT>独立开发出一个类<FONT face="Times New Roman">Unix</FONT>的操作系统,该操作系统具有完全的内核、开发工具和终端用户应用程序。在<FONT face="Times New Roman">GNU</FONT>(“<FONT face="Times New Roman">GNU’s Not Unix</FONT>”首字母的缩写)计划的配合下,<FONT face="Times New Roman">Stallman</FONT>开发这个产品有自己的技术理想:他想开发出一个质量高而且自由的操作系统。<FONT face="Times New Roman">Stallman</FONT>使用了“自由”(<FONT face="Times New Roman">free</FONT>)这个词,不仅意味着用户可以免费的获取软件;而且更重要的是,它将意味着某种程度的“解放”:用户可以自由使用、拷贝、查询、重用、修改甚至是分发这份软件,完全没有软件使用协议的限制。这也正是<FONT face="Times New Roman">Stallman</FONT>创建自由软件基金会(<FONT face="Times New Roman">FSF</FONT>)资助<FONT face="Times New Roman">GNU</FONT>软件开发的本意(<FONT face="Times New Roman">FSF</FONT>也在资助其它科研方面的开发工作)。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">15</FONT>年以来,<FONT face="Times New Roman">GNU</FONT>工程已经吸收、产生了大量的程序,这不仅包括<FONT face="Times New Roman">Emacs</FONT>,<FONT face="Times New Roman">gcc</FONT>(<FONT face="Times New Roman">GNU</FONT>的<FONT face="Times New Roman">C</FONT>编译器),<FONT face="Times New Roman">bash</FONT>(<FONT face="Times New Roman">shell</FONT>命令),还有大部分<FONT face="Times New Roman">Linux</FONT>用户所熟知的许多应用程序。现在正在进行开发的项目是<FONT face="Times New Roman">GNU Hurd</FONT>内核,这是<FONT face="Times New Roman">GNU</FONT>操作系统的最后一个主要部件(实际上<FONT face="Times New Roman">Hurd</FONT>内核早已能够使用了,不过当前的版本号为<FONT face="Times New Roman">0.3</FONT>的系统在什么时候能够完成,还是未知数)。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">尽管<FONT face="Times New Roman">Linux</FONT>大受欢迎,但是<FONT face="Times New Roman">Hurd</FONT>内核还在继续开发。这种情况的原因有几个方面,其一是<FONT face="Times New Roman">Hurd</FONT>的体系结构十分清晰的体现了<FONT face="Times New Roman">Stallman</FONT>关于操作系统工作方式的思想,例如,在运行期间,任何用户都可以部分的改变或替换<FONT face="Times New Roman">Hurd</FONT>(这种替换不是对每个用户都是可见的,而是只对申请修改的用户可见,而且还必须符合安全规范)。另一个原因是据介绍<FONT face="Times New Roman">Hurd</FONT>对于多处理器的支持比<FONT face="Times New Roman">Linux</FONT>本身的内核要好。还有一个简单的原因是兴趣的驱动,因为程序员们希望能够自由地进行自己所喜欢的工作。只要有人希望为<FONT face="Times New Roman">Hurd</FONT>工作,<FONT face="Times New Roman">Hurd</FONT>的开发就不会停止。如果他们能够如愿以偿,<FONT face="Times New Roman">Hurd</FONT>有朝一日将成为<FONT face="Times New Roman">Linux</FONT>的强劲对手。不过在今天,<FONT face="Times New Roman">Linux</FONT>还是自由内核王国里无可争议的主宰。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在<FONT face="Times New Roman">GNU</FONT>发展的中期,也就是<FONT face="Times New Roman">1991</FONT>年,一个名叫<FONT face="Times New Roman">Linus Torvalds</FONT>的芬兰大学生想要了解<FONT face="Times New Roman">Intel</FONT>的新<FONT face="Times New Roman">CPU</FONT>——<FONT face="Times New Roman">80386</FONT>。他认为比较好的学习方法是自己编写一个操作系统的内核。出于这种目的,加上他对当时<FONT face="Times New Roman">Unix</FONT>变种版本对于<FONT face="Times New Roman">80386</FONT>类机器的脆弱支持十分不满,他决定要开发出一个全功能的、支持<FONT face="Times New Roman">POSIX</FONT>标准的、类<FONT face="Times New Roman">Unix</FONT>的操作系统内核,该系统吸收了<FONT face="Times New Roman">BSD</FONT>和<FONT face="Times New Roman">System V</FONT>的优点,同时摒弃了它们的缺点。<FONT face="Times New Roman">Linus</FONT>(虽然我知道我应该称他为<FONT face="Times New Roman">Torvalds</FONT>,但是所有人都称他为<FONT face="Times New Roman">Linus</FONT>)独立把这个内核开发到<FONT face="Times New Roman">0.02</FONT>版,这个版本已经可以运行<FONT face="Times New Roman">gcc</FONT>,<FONT face="Times New Roman">bash</FONT>和很少的一些应用程序。这些就是他开始的全部工作了。后来,他又开始在因特网络上寻求广泛的帮助。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">不到三年,<FONT face="Times New Roman">Linus</FONT>的<FONT face="Times New Roman">Unix</FONT>—<FONT face="Times New Roman">Linux</FONT>—已经升级到<FONT face="Times New Roman">1.0</FONT>版本。它的源代码量也呈指数形式增长,实现了基本的<FONT face="Times New Roman">TCP/IP</FONT>功能(网络部分的代码后来重写过,而且还可能会再次重写)。此时<FONT face="Times New Roman">Linux</FONT>就已经拥有大约<FONT face="Times New Roman">10</FONT>万用户了。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">现在的<FONT face="Times New Roman">Linux</FONT>内核由<FONT face="Times New Roman">150</FONT>多万行代码组成,<FONT face="Times New Roman">Linux</FONT>也已经拥有了大约<FONT face="Times New Roman">1000</FONT>万用户(由于<FONT face="Times New Roman">Linux</FONT>可以自由获取和拷贝,获取具体的统计数字是不可能的)。<FONT face="Times New Roman">Linux</FONT>内核<FONT face="Times New Roman">GNU/Linux</FONT>附同<FONT face="Times New Roman">GNU</FONT>工具已经占据了<FONT face="Times New Roman">Unix 50%</FONT>的市场。一些公司正在把内核和一些应用程序同安装软件打包在一起,生产出<FONT face="Times New Roman">Linux</FONT>的<FONT face="Times New Roman">distribution</FONT>(发行版本),这些公司包括<FONT face="Times New Roman">Red Hat</FONT>和<FONT face="Times New Roman">Calera prominent</FONT>公司。现在的<FONT face="Times New Roman">GNU/Linux</FONT>已经备受注目,得到了诸如<FONT face="Times New Roman">Sun</FONT>、<FONT face="Times New Roman">IBM</FONT>、<FONT face="Times New Roman">SGI</FONT>等公司的广泛支持。<FONT face="Times New Roman">SGI</FONT>最近决定在其基于<FONT face="Times New Roman">Intel</FONT>的<FONT face="Times New Roman">Merced</FONT>的系列机器上不再搭载自己的<FONT face="Times New Roman">Unix</FONT>变种版本<FONT face="Times New Roman">IRIX</FONT>,而是直接采用<FONT face="Times New Roman">GNU/Linux</FONT>;<FONT face="Times New Roman">Linux</FONT>甚至被指定为<FONT face="Times New Roman">Amiga</FONT>将要发布的新操作系统的基础。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">GNU通用公共许可证</H2><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">这样一个如此流行大受欢迎的操作系统当然值得我们学习。按照通用公共许可证<FONT face="Times New Roman">(GPL</FONT>,(<FONT face="Times New Roman">General Public License</FONT>)<FONT face="Times New Roman">)</FONT>的规定,<FONT face="Times New Roman">Linux</FONT>的源代码可以自由获取,这使得我们学习该系统的强烈愿望得以实现。<FONT face="Times New Roman">GPL</FONT>这份非同寻常的软件许可证,充分体现了上面提到的<FONT face="Times New Roman">Stallman</FONT>的思想:只要用户所做的修改是同等自由的,用户可以自由地使用、拷贝、查询、重用、修改甚至重新发布这个软件。通过这种方式,<FONT face="Times New Roman">GPL</FONT>保证了<FONT face="Times New Roman">Linux</FONT>(以及同一许可证保证下的大量其它软件)不仅现在自由可用,而且以后经过任何修改之后都仍然可以自由使用。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">请注意这里的自由并不是说没有人靠这个软件盈利,有一些日益兴起的公司,比如发行最流行的<FONT face="Times New Roman">Linux</FONT>发行版本的<FONT face="Times New Roman">Red Hat</FONT>,就是一个例子。(<FONT face="Times New Roman">Red Hat</FONT>自从面世以来,市值已经突破数十亿美元,每年盈利数十万美元,而且这些数字还在不断增长)。但是任何人都不能限制其它用户涉足本软件领域,而且所作的修改不能减少其自由程度。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">本书的附录<FONT face="Times New Roman">B</FONT>中收录有<FONT face="Times New Roman">GNU</FONT>通用公共许可证协议的全文。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">Linux开发过程</H2><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">如上所述,由于<FONT face="Times New Roman">Linux</FONT>是一款自由软件,它可以免费获取以供学习研究。<FONT face="Times New Roman">Linux</FONT>之所以值得学习研究,是因为它是相当优秀的操作系统。如果<FONT face="Times New Roman">Linux</FONT>操作系统相当糟糕,那它就根本不值得被我们使用,也就没有必要去研究相关的书籍。(除非一种可能,为了追求刺激)。<FONT face="Times New Roman">Linux</FONT>是一款十分优秀的操作系统还在于几个相互关联的原因。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Linux</FONT>优秀的原因之一在于它是基于天才的思想开发而成的。在学生时代就开始推动整个系统开发的<FONT face="Times New Roman">Linus Torvads</FONT>是一个天才,他的才能不仅展现在编程能力方面,而且组织技巧也相当杰出。<FONT face="Times New Roman">Linux</FONT>的内核是由世界上一些最优秀的程序员开发并不断完善的,他们通过<FONT face="Times New Roman">Internet</FONT>相互协作,开发理想的操作系统;他们享受着工作中的乐趣,而且也获得了充分的自豪感。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Linux</FONT>优秀的另外一个原因在于它是基于一组优秀的概念。<FONT face="Times New Roman">Unix</FONT>是一个简单却非常优秀的模型。在<FONT face="Times New Roman">Linux</FONT>创建之前,<FONT face="Times New Roman">Unix</FONT>已经有<FONT face="Times New Roman">20</FONT>年的发展历史。<FONT face="Times New Roman">Linux</FONT>从<FONT face="Times New Roman">Unix</FONT>的各个流派中不断吸取成功经验,模仿<FONT face="Times New Roman">Unix</FONT>的优点,抛弃<FONT face="Times New Roman">Unix</FONT>的缺点。这样做的结果是<FONT face="Times New Roman">Linux </FONT>成为了<FONT face="Times New Roman">Unix</FONT>系列中的佼佼者:高速、健壮、完整,而且抛弃了历史包袱。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">然而,<FONT face="Times New Roman">Linux</FONT>最强大的生命力还在于其公开的开发过程。每个人都可以自由获取内核源程序,每个人都可以对源程序加以修改,而后他人也可以自由获取你修改后的源程序。如果你发现了缺陷(<FONT face="Times New Roman">bug</FONT>),你可以对它进行修正,而不用去乞求不知名的公司来为你修正。如果你有什么最优化或者新特点的创意,你也可以直接在系统中增加功能,而不用向操作系统供应商解释你的想法,指望他们将来会增加相应的功能。当发现一个安全漏洞后,你可以通过编程来弥补这个漏洞,而不用关闭系统直到你的供应商为你提供修补程序。由于你拥有直接访问源代码的能力,你也可以直接阅读代码来寻找缺陷,或是效率不高的代码,或是安全漏洞,以防患于未然。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">除非你是一个程序员,否则这一点听起来仿佛没有多少吸引力。实际上即使你不是程序员,这种开发模型也将使你受益匪浅,这主要体现在以下两个方面:</P><P 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 42.5pt">l          可以间接受益于世界各地成千上万的程序员随时进行的改进工作。</P><P 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 42.5pt">l          如果你需要对系统进行修改,你可以雇用程序员为你完成工作。这部分人将根据你的需求定义单独为你服务。可以设想,这在源程序不公开的操作系统中它将是什么样子。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Linux</FONT>这种独特的自由流畅的开发模型已被命名为<FONT face="Times New Roman">bazaar</FONT>(集市模型),它是相对于<FONT face="Times New Roman">cathedral</FONT>(教堂)模型而言的。在<FONT face="Times New Roman">cathedral</FONT>模型中,源程序代码被锁定在一个保密的小范围内。只有开发者(很多情况下是市场)认为能够发行一个新版本,这个新版本才会被推向市场。这些术语在<FONT face="Times New Roman">Eric S. Raymond</FONT>的<FONT face="Times New Roman">The Cathedral and the Bazaar</FONT>一文中有所介绍,大家可以在<B normal"><FONT face="Times New Roman">http://www.tuxedo.org/~esr/writings/</FONT></B>找到这篇文章。<FONT face="Times New Roman">Bazaar</FONT>开发模型通过重视实验,征集并充分利用早期的反馈,对巨大数量的脑力资源进行平衡配置,可以开发出更优秀的软件。(顺便说一下,虽然<FONT face="Times New Roman">Linux</FONT>是最为明显的使用<FONT face="Times New Roman">bazaar</FONT>开发模型的例子,但是它却远不是第一个使用这个模型的系统。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">为了确保这些无序的开发过程能够有序地进行,<FONT face="Times New Roman">Linux</FONT>采用了双树系统。一个树是稳定树(<FONT face="Times New Roman">stable tree</FONT>),另一个树是非稳定树(<FONT face="Times New Roman">unstable tree</FONT>)或者开发树(<FONT face="Times New Roman">development tree</FONT>)。一些新特性、实验性改进等都将首先在开发树中进行。如果在开发树中所做的改进也可以应用于稳定树,那么在开发树中经过测试以后,在稳定树中将进行相同的改进。按照<FONT face="Times New Roman">Linus</FONT>的观点,一旦开发树经过了足够的发展,开发树就会成为新的稳定树,如此周而复始的进行下去。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">源程序版本号的形式为<FONT face="Times New Roman">x.y.z</FONT>。对于稳定树来说,<FONT face="Times New Roman">y</FONT>是偶数;对于开发树来说,<FONT face="Times New Roman">y</FONT>比相应的稳定树大一(因此,是奇数)。截至到本书截稿时,最新的稳定内核版本号是<st1:chsdate w:st="on" IsROCDate="False" IsLunarDate="False" Day="30" Month="12" Year="1899"><FONT face="Times New Roman">2.2.10</FONT></st1:chsdate>,最新的开发内核的版本号是<FONT face="Times New Roman">2.3.12</FONT>。对<FONT face="Times New Roman">2.3</FONT>树的缺陷修正会回溯影响(<FONT face="Times New Roman">back-propagated</FONT>)<FONT face="Times New Roman">2.2</FONT>树,而当<FONT face="Times New Roman">2.3</FONT>树足够成熟的时候会发展成为<FONT face="Times New Roman">2.4.0</FONT>。(顺便说一下,这种开发会比常规惯例要快,因为每一版本所包含的改变比以前更少了,内核开发人员只需花很短的时间就能够完成一个实验开发周期。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><B normal"><FONT face="Times New Roman">http://www.kernel.org</FONT></B>及其镜像站点提供了最新的可供下载的内核版本,而且同时包括稳定和开发版本。如果你愿意的话,不需要很长时间,这些站点所提供的最新版本中就可能包含了你的一部分源程序代码。</P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman">第2章  </FONT>代码初识</H1>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">本章首先从较高层次介绍<FONT face="Times New Roman">Linux</FONT>内核源程序的概况,这些都是大家关心的一些基本特点。随后将简要介绍一些实际代码。最后以如何编译内核来检验个人所进行的修改的讨论来作为本章的收尾。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">Linux内核源程序的部分特点</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在过去的一段时期,<FONT face="Times New Roman">Linux</FONT>内核同时使用<FONT face="Times New Roman">C</FONT>语言和汇编语言实现的。这两种语言需要一定的平衡:<FONT face="Times New Roman">C</FONT>语言编写的代码移植性较好、易于维护,而汇编语言编写的程序则速度较快。一般只有在速度是关键因素或者一些因平台相关特性而产生的特殊要求(例如直接和内存管理硬件进行通讯)时才使用汇编语言。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">正如同实际中所做的,即使内核并未使用<FONT face="Times New Roman">C++</FONT>的对象特性,部分内核也可以在<FONT face="Times New Roman">g++</FONT>(<FONT face="Times New Roman">GNU</FONT>的<FONT face="Times New Roman">C++</FONT>编译器)下进行编译。同其它面向对象的编程语言相比较,相对而言<FONT face="Times New Roman">C++</FONT>的开销是较低的,但是对于内核开发人员来说,这已经足够甚至太多了。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">内核开发人员不断发展编程风格,形成了<FONT face="Times New Roman">Linux</FONT>代码独有的特色。本节将讨论其中的一些问题。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">gcc特性的使用</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Linux</FONT>内核被设计为必须使用<FONT face="Times New Roman">GNU</FONT>的<FONT face="Times New Roman">C</FONT>编译器<FONT face="Times New Roman">gcc</FONT>来编译,而不是任何一种<FONT face="Times New Roman">C</FONT>编译器都可以使用。内核代码有时要使用<FONT face="Times New Roman">gcc</FONT>特性,伴随着本书的进程,我们将陆续介绍其中的一部分。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">一些<FONT face="Times New Roman">gcc</FONT>特有代码只是简单地使用<FONT face="Times New Roman">gcc</FONT>语言扩展,例如允许在<FONT face="Times New Roman">C</FONT>(不只是<FONT face="Times New Roman">C++</FONT>)中使用<B normal"><FONT face="Times New Roman">inline</FONT></B>关键字指示内联函数。也就是说,代码中被调用的函数在每次函数调用时都会被扩充,因而就可以节约实际函数调用的开销。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">更为普遍的情况是代码的编写方式比较复杂。因为对于某些类型的输入,<FONT face="Times New Roman">gcc</FONT>能够产生比其它输入效率更高的执行代码。从理论上讲,编译器可以优化具有相同功能的两种对等的方法,并且得到相同的结果。因此,代码的编写方式是无关紧要的。但在实际上,用一些方法编写所产生的代码要比用其它方法编写所产生的代码的执行速度快得多。内核开发人员清楚如何才能产生更高效的执行代码的方法,而且这种知识也不断在他们编写的代码中反映出来。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">例如,考虑内核中经常使用的<B normal"><FONT face="Times New Roman">goto</FONT></B>语句——为了提高速度,内核中经常大量使用这种一般要避免使用的语句。在本书中所包含的不到<FONT face="Times New Roman">40,000</FONT>行代码中,一共有<FONT face="Times New Roman">500</FONT>多条<B normal"><FONT face="Times New Roman">goto</FONT></B>语句,大约是每<FONT face="Times New Roman">80</FONT>行一个。除汇编文件外,精确的统计数字是接近每<FONT face="Times New Roman">72</FONT>行一个<B normal"><FONT face="Times New Roman">goto</FONT></B>语句。公平的说,这是选择偏向的结果:比例如此高的原因之一是本书中涉及的是内核源程序的核心,在这里速度比其它因素都需要优先考虑。整个内核的比例大概是每<FONT face="Times New Roman">260</FONT>行一个<B normal"><FONT face="Times New Roman">goto</FONT></B>语句。然而,这仍然是我不再使用<FONT face="Times New Roman">Basic</FONT>进行编程以来见过的使用<B normal"><FONT face="Times New Roman">goto</FONT></B>频率最高的地方。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">代码必需受特定编译器限制的特性不仅与普通应用程序的开发有很大不同,而且也不同于大多数内核的开发。大多数的开发人员使用<FONT face="Times New Roman">C</FONT>语言编写代码来保持较高的可移植性,即使在编写操作系统时也是如此。这样做的优点是显而易见的,最为重要的一点是一旦出现更好的编译器,程序员们可以随时进行更换。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">内核对于<FONT face="Times New Roman">gcc</FONT>特性的完全依赖使得内核向新的编译器上的移植工作更加困难。最近<FONT face="Times New Roman">Linus</FONT>对这一问题在有关内核的邮件列表上表明了自己的观点。“记住,编译器只是一个工具。”这是对依赖于<FONT face="Times New Roman">gcc</FONT>特性的一个很好的基本思想的表述:编译器只是为了完成工作。如果通过遵守标准还不能达到工作要求,那么就不是工作要求有问题,而是对于标准的依赖有问题。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在大多数情况下,这种观点是不能够被人所接受的。通常情况下,为了保证和程序语言标准的一致,开发人员可能需要牺牲某些特性、速度或者其它相关因素。其它的选择可能会为后期开发造成很大的麻烦。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">但是,在这种特定的情况下,<FONT face="Times New Roman">Linus</FONT>是正确的。<FONT face="Times New Roman">Linux</FONT>内核是一个特例,因为其执行速度要比向其它编译器的可移植性远为重要。如果设计目标是编写一个可移植性好而不要求快速运行的内核,或者是编写一个任何人都可以使用自己喜欢的编译器进行编译的内核,那么结论就可能会有所不同了;而这些恰好不是<FONT face="Times New Roman">Linux</FONT>的设计目标。实际上,<FONT face="Times New Roman">gcc</FONT>几乎可以为所有能够运行<FONT face="Times New Roman">Linux</FONT>的<FONT face="Times New Roman">CPU</FONT>生成代码,因此,对于<FONT face="Times New Roman">gcc</FONT>的依赖并不是可移植性的严重障碍。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在第<FONT face="Times New Roman">3</FONT>章中我们将对内核设计目标进行详细说明。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">内核代码习惯用语</H2>< 0cm 0cm 0pt">内核代码中使用了一些显著的习惯用语,本节将介绍常用的几个。当你通读源程序代码时,真正重要的问题是并不在这些习惯用语本身,而是这种类型的习惯用语的确存在,而且是不断被使用和发展的。如果你需要编写内核代码,你应该注意到内核中所使用的习惯用语,并把这些习惯用语应用到你的代码中。当通读本书(或者代码)时,注意你还能找到多少习惯用语。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">为了讨论这些习惯用语,我们首先需要对它们进行命名。为了便于讨论,笔者创造了这些名字。而在实际中,大家不一定非要参考这些用语,它们只是对内核工作方式的描述而已。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">一个普通的习惯用语笔者称之为“资源获取”(<FONT face="Times New Roman">resource acquisition idiom</FONT>)。在这个用语中,一个函数必须实现一系列资源的获取,包括内存、锁等等(这些资源的类型未必相同)。只有成功地获取当前所需要的资源之后,才能处理后面的资源请求。最后,该函数还必须释放所有已经获取的资源,而不必对没有获取的资源进行考虑。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">我采用“错误变量”这一用语(<FONT face="Times New Roman">error variable idiom</FONT>)来辅助说明资源获取用语,它使用一个临时变量来记录函数的期望返回值。当然,相当多的函数都能实现这个功能。但是错误变量的不同点在于它通常是用来处理由于速度的因素而变得非常复杂的流程控制中的问题。错误变量有两个典型的值,<FONT face="Times New Roman">0</FONT>(表示成功)和负数(表示有错)。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">这两个用语结合使用,我们就可以十分自然地得到符合模式的代码如下:</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">Int f (void)<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">{<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  int err;<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  resource *r1, *r2;<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  err = -ERR1   /*assume failure*/<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  r1=acquire_ resource();<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  if (!r1)       /*not aquired*/<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">     goto out      /*returns  -ERR1*/<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  Got resource r1,try for r2.*/<p></p></FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  err = - ERR2;<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  r2 = acquire_ resource2();<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  if (!r2)       /*not aquired*/<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">     goto out1      /*returns –ERR2*/<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  /*have both r1 and r2.*/<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  err = 0;<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  <p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  /* … use r1 and r2 … */<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">out2:<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  release_resource(r2)<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">out1:<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  release_resource(r1)<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">out:<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">    return err;<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">}<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">  </FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">(注意变量<B normal"><FONT face="Times New Roman">err</FONT></B>是使用错误变量的一个明确实例,同样,诸如<B normal"><FONT face="Times New Roman">out</FONT></B>之类的标号则指明了资源获取用语的使用。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">如果执行到标号<B normal"><FONT face="Times New Roman">out2</FONT></B>,则都已经获取了<B normal"><FONT face="Times New Roman">r1</FONT></B>和<B normal"><FONT face="Times New Roman">r2</FONT></B>资源,而且也都需要进行释放。如果执行到标号<B normal"><FONT face="Times New Roman">out1</FONT></B>(不管是顺序执行还是使用<B normal"><FONT face="Times New Roman">goto</FONT></B>语句进行跳转到),则<B normal"><FONT face="Times New Roman">r2</FONT></B>资源是无效的(也可能刚被释放),但是<B normal"><FONT face="Times New Roman">r1</FONT></B>资源却是有效的,而且必需在此将其释放。同理,如果标号<B normal"><FONT face="Times New Roman">out</FONT></B>能被执行,则<B normal"><FONT face="Times New Roman">r1</FONT></B>和<B normal"><FONT face="Times New Roman">r2</FONT></B>资源都无效,<B normal"><FONT face="Times New Roman">err</FONT></B>所返回的是错误或成功标志。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在这个简单的例子中,对于<B normal"><FONT face="Times New Roman">err</FONT></B>的一些赋值是没有必要的。在实践中,实际代码必须遵守这种模式。这样做的原因主要在于同一行中可能包含有多种测试,而这些测试应该返回相同的错误代码,因此对错误变量统一赋值要比多次赋值更为简单。虽然在这个例子中对于这种属性的必要性并不非常迫切,但是我还是倾向于保留这种特点。有关的实际应用可以参考<B normal"><FONT face="Times New Roman">sys_shmctl</FONT></B>(第<FONT face="Times New Roman">21654</FONT>行),在第<FONT face="Times New Roman">9</FONT>章中还将详细介绍这个例子。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">减少#if和#ifdef的使用</H2><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">现在的<FONT face="Times New Roman">Linux</FONT>内核已经移植到不同的平台上,但是我们还必须解决移植过程中所出现的问题。大部分支持各种不同平台的代码由于包含许多预处理代码现都已变得非常不规范,例如:</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#if defined(SOLARIS)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">/* … do things the solaris way … */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#elif defined(HPUX)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">/* … do things the HP-UX way … */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#elif defined(LINUX)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">/* … do things the right way … */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#endif</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">这个例子试图实现操作系统的可移植性,虽然<FONT face="Times New Roman">Linux</FONT>关注的焦点很明显是实现代码在各种<FONT face="Times New Roman">CPU</FONT>上的可移植性,但是二者的基本原理是一致的。对于这类问题来说,预处理器是一种错误的解决方式。这些杂乱的问题使得代码晦涩难懂。更为糟糕的是,增加对新平台的支持有可能要求重新遍历这些杂乱分布的低质量代码段(实际上你很难能找到这类代码段的全部)。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">与现有方式不同的是,<FONT face="Times New Roman">Linux</FONT>一般通过简单函数(或者是宏)调用来抽象出不同平台间的差异。内核的移植可以通过实现适合于相应平台的函数(或宏)来实现。这样不仅使代码的主体简单易懂,而且在移植的过程中还可以比较容易地自动检测出你没有注意到的内容:如引用未声明函数时会出现链接错误。有时用预处理器来支持不同的体系结构,但这种方式并不常用,而相对于代码风格的变化就更是微不足道了。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">顺便说一下,我们可以注意到这种解决方法和使用用户对象(或者<FONT face="Times New Roman">C</FONT>语言中充满函数指针的<B normal"><FONT face="Times New Roman">struct</FONT></B>结构)来代替离散的<B normal"><FONT face="Times New Roman">switch</FONT></B>语句处理不同类型的方法十分相似。在某些层次上,这些问题和解决方法是统一的。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">可移植性的问题并不仅限于平台和<FONT face="Times New Roman">CPU</FONT>的移植,编译器也是一个重要的问题。此处为了简化,假设<FONT face="Times New Roman">Linux</FONT>只使用<FONT face="Times New Roman">gcc</FONT>来编译。由于<FONT face="Times New Roman">Linux</FONT>只使用同一个编译器,所以就没有必要使用<B normal"><FONT face="Times New Roman">#if</FONT></B>块(或者<B normal"><FONT face="Times New Roman">#ifdef</FONT></B>块)来选择不同的编译器。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">内核代码主要使用<B normal"><FONT face="Times New Roman">#ifdef</FONT></B>来区分需要编译或不需要编译的部分,从而对不同的结构提供支持。例如,代码经常测试<B normal"><FONT face="Times New Roman">SMP</FONT></B>宏是否定义过,从而决定是否支持<FONT face="Times New Roman">SMP</FONT>机。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">代码样例</H2><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">上一节仅仅是一些讨论,了解<FONT face="Times New Roman">Linux</FONT>代码风格最好的方法就是实际研究一下它的部分代码。即使你不完全理解本节所讨论代码的细节也无关紧要,毕竟本节的主要目的不是理解代码,一些读者可以只对本节进行浏览。本节的主要目的是让读者对<FONT face="Times New Roman">Linux</FONT>代码进行初步了解,对今后的工作提供必要基础。而讨论将涉及部分广泛使用到的内核代码。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman" size=5>printk</FONT></H3><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><B normal"><FONT face="Times New Roman">printk</FONT></B>(<FONT face="Times New Roman">25836</FONT>行)是内核内部消息日志记录函数。在出现诸如内核检测到其数据结构出现不一致的事件时,内核会使用<B normal"><FONT face="Times New Roman">printk</FONT></B>把相关信息打印到系统控制台上。对于<B normal"><FONT face="Times New Roman">printk</FONT></B>的调用一般分为如下几类:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo2; tab-stops: list 42.25pt">l         紧急事件(<FONT face="Times New Roman">emergency</FONT>)――例如,<B normal"><FONT face="Times New Roman">panic</FONT></B>函数(<FONT face="Times New Roman">25563</FONT>行)多次使用了<B normal"><FONT face="Times New Roman">printk</FONT></B>。当内核检测到发生不可恢复的内部错误时就会调用<B normal"><FONT face="Times New Roman">panic</FONT></B>函数,然后尽其所能的安全关闭计算机。这个函数中调用<B normal"><FONT face="Times New Roman">printk</FONT></B>以提示用户系统将要关闭。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo2; tab-stops: list 42.25pt">l         调试――从<FONT face="Times New Roman">3816</FONT>行开始的<B normal"><FONT face="Times New Roman">#ifdef</FONT></B>块使用<B normal"><FONT face="Times New Roman">printk</FONT></B>来打印<FONT face="Times New Roman">SMP</FONT>逻辑单元(<FONT face="Times New Roman">box</FONT>)中每一个处理器的相关配置信息,但是此过程只有在使用<B normal"><FONT face="Times New Roman">SMP_DEBUG</FONT></B>标志编译代码的情况下才能够被执行。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo2; tab-stops: list 42.25pt">l         普通信息――例如,当机器启动时,内核必需估计系统速度以确保设备驱动程序能够忙等待(<FONT face="Times New Roman">busy-waiting</FONT>)一个精确的极短周期。计算这种估计值的函数名为<B normal"><FONT face="Times New Roman">calibrate_delay</FONT></B>(<FONT face="Times New Roman">19654</FONT>行),它既在<FONT face="Times New Roman">19661</FONT>行使用<B normal"><FONT face="Times New Roman">printk</FONT></B>声明马上开始计算,又在<FONT face="Times New Roman">19693</FONT>行报告计算结果。另外,在第<FONT face="Times New Roman">4</FONT>章将详细的介绍<B normal"><FONT face="Times New Roman">calibrate_delay</FONT></B>函数。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21pt">如果你已经浏览过这些参照代码,你可能已经注意到<B normal"><FONT face="Times New Roman">printk</FONT></B>和<B normal"><FONT face="Times New Roman">printf</FONT></B>的参数十分类似:一个格式化字符串,后跟零个或者多个参数加入字符串中。格式化字符串可能是以一组“<FONT face="Times New Roman">&lt;N&gt;</FONT>”开始,这里的<FONT face="Times New Roman">N</FONT>是从<FONT face="Times New Roman">0</FONT>到<FONT face="Times New Roman">7</FONT>的数字,包括<FONT face="Times New Roman">0</FONT>和<FONT face="Times New Roman">7</FONT>在内。数字区分了消息的日志等级(<FONT face="Times New Roman">log level</FONT>),只有当日志等级高于当前控制台定义的日志等级(<B normal"><FONT face="Times New Roman">console_loglevel</FONT></B>,<FONT face="Times New Roman">25650</FONT>行)时,才会打印消息。<FONT face="Times New Roman">root</FONT>可以通过适当减小控制台的日志等级来过滤不是很紧急的消息。如果内核在格式化字符串中检测不到日志等级序列,那么就会一直打印消息。(实际上,日志等级序列并不一定要在格式化字符串中出现,可以在格式化文本中查找到它的代码。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21pt">从<FONT face="Times New Roman">14946</FONT>行开始的<B normal"><FONT face="Times New Roman">#define</FONT></B>块说明了这些特殊序列,这些定义可以帮助调用者正确区分对<B normal"><FONT face="Times New Roman">printk</FONT></B>的调用。简单的说,我称日志等级<FONT face="Times New Roman">0</FONT>到<FONT face="Times New Roman">4</FONT>为“紧急事件”,从等级<FONT face="Times New Roman">5</FONT>到等级<FONT face="Times New Roman">6</FONT>为“普通信息”,等级<FONT face="Times New Roman">7</FONT>自然就是我所说的“调试”。(这种分类方法并不意味着其它更好的分类方法没有用处,而只是目前我们还不关心它而已。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21pt">在上面讨论的基础上,我们研究一下代码本身。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">printk</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25836</FONT>:参数<B normal"><FONT face="Times New Roman">fmt</FONT></B>是<B normal"><FONT face="Times New Roman">printf</FONT></B>类型的格式化字符串。如果你对“<FONT face="Times New Roman">…</FONT>”部分的内容不熟悉,那就<FONT face="Times New Roman">             </FONT>需要参阅一本好的<FONT face="Times New Roman">C</FONT>语言参考书(在其索引中查找“变参函数,<FONT face="Times New Roman">variadic function</FONT>”)。另外,在安装的<FONT face="Times New Roman">GNU/Linux</FONT>中的<B normal"><FONT face="Times New Roman">stdarg</FONT></B>帮助里也包含了一个有关变参函数的简明描述,在这儿只需要敲入“<FONT face="Times New Roman">man stdarg</FONT>”就可以看到。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>简单的说,“<FONT face="Times New Roman">…</FONT>”部分提示编译器<B normal"><FONT face="Times New Roman">fmt</FONT></B>后面可能紧跟着数量不定的任何类型的参数。由于这些参数在编译的时候还没有类型和名字,内核使用由三个宏<B normal"><FONT face="Times New Roman">va_start</FONT></B>,<B normal"><FONT face="Times New Roman">va_arg</FONT></B>和<B normal"><FONT face="Times New Roman">va_end</FONT></B>组成的特殊组以及一个特殊类型――<B normal"><FONT face="Times New Roman">va_list</FONT></B>对它们进行处理。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25842</FONT>:<B normal"><FONT face="Times New Roman">msg_level</FONT></B>记录了当前消息的日志等级。它是静态的,这看起来可能会有些奇怪――为什么下一次对<B normal"><FONT face="Times New Roman">printk</FONT></B>的调用需要记录日志等级呢?问题的答案是只有打印出新行(<FONT face="Times New Roman">\n</FONT>)或者赋给一个新的日志等级序列以后,当前消息才会结束。这样通过在包含消息结束的新行里调用<B normal"><FONT face="Times New Roman">printk</FONT></B>,就保证了在多个短期冲突的情况下,调用者只打印唯一一个长消息。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25845</FONT>:在<FONT face="Times New Roman">SMP</FONT>逻辑单元中,内核可能试图从不同的<FONT face="Times New Roman">CPU</FONT>向控制台同时打印信息。(有时在单处理机(<FONT face="Times New Roman">UP</FONT>)逻辑单元中也会发生同样问题,但由于中断还未被覆盖掉,所以问题也并不十分明显。)如果不进行任何协同的话,结果就将处于完全无法让人了解的杂乱无章的状态,每个消息的各个部分都和其它消息的各个部分混杂交织在一起。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: 0cm">相反,内核使用旋转锁(<FONT face="Times New Roman">spin-lock</FONT>)来控制对控制台的访问。旋转锁将在第<FONT face="Times New Roman">10</FONT>章对它进行深入的介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: 0cm">如果你对<B normal"><FONT face="Times New Roman">flags </FONT></B>在传送给<B normal"><FONT face="Times New Roman">spin_lock_irqsave</FONT></B>之前为什么不对它初始化感到疑惑,请不要担心:<B normal"><FONT face="Times New Roman">spin_lock_irqsave</FONT></B>(对于不同的版本请分别参看<FONT face="Times New Roman">12614</FONT>行,<FONT face="Times New Roman">12637</FONT>行,<FONT face="Times New Roman">12716</FONT>行,和<FONT face="Times New Roman">12837</FONT>行)是一个宏,而不是一个函数。该宏实际上是将值写入<B normal"><FONT face="Times New Roman">flags</FONT></B>中,而不是从<B normal"><FONT face="Times New Roman">flags</FONT></B>中读出值。(在<FONT face="Times New Roman">25895</FONT>行中,存储在<B normal"><FONT face="Times New Roman">flags</FONT></B>中的信息被<B normal"><FONT face="Times New Roman">spin_lock_irqsave</FONT></B>回读,请参看<FONT face="Times New Roman">12616</FONT>行,<FONT face="Times New Roman">12639</FONT>行,<FONT face="Times New Roman">12728</FONT>行和<FONT face="Times New Roman">12841</FONT>行)</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">25846</FONT>:初始化变量<B normal"><FONT face="Times New Roman">args</FONT></B>,该变量代表<B normal"><FONT face="Times New Roman">printk</FONT></B>参数中的“<FONT face="Times New Roman">…</FONT>”部分。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25848</FONT>:调用内核自身的<B normal"><FONT face="Times New Roman">vsprintf</FONT></B>(为节省空间而省略)实现。该函数的功能与标准<B normal"><FONT face="Times New Roman">vsprintf</FONT></B>函数非常相似,向<B normal"><FONT face="Times New Roman">buf</FONT></B>中写入格式化文本(<FONT face="Times New Roman">25634</FONT>行)并返回写入字符串的长度(长度不包括最后一位终止字符<FONT face="Times New Roman">0</FONT>字节)。很快,你将可以看到为什么这种机制会忽略<B normal"><FONT face="Times New Roman">buf</FONT></B>的前三个字符。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: 0cm">(正如<FONT face="Times New Roman">25847</FONT>行的注释中所述)我们应该注意到在这里并没有采取严格的措施来保证缓冲器不会过载。这里系统假定<FONT face="Times New Roman">1024</FONT>个字符长度的<B normal"><FONT face="Times New Roman">buf</FONT></B>已经足够使用(参阅<FONT face="Times New Roman">25634</FONT>行)。如果内核在这里能够使用<B normal"><FONT face="Times New Roman">vsnprintf</FONT></B>函数的话,情况就会好许多。然而,<B normal"><FONT face="Times New Roman">vsnprintf</FONT></B>还有另外一个参数限制了它能够写入缓冲器的字符长度。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">25849</FONT>:计算<B normal"><FONT face="Times New Roman">buf</FONT></B>中最近使用的元素,调用<B normal"><FONT face="Times New Roman">va_end</FONT></B>终止对“<FONT face="Times New Roman">…</FONT>”参数的处理。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25851</FONT>:开始格式化消息的循环。其中存在一个内部循环能够处理更多内容(这一点随后就能看到),因此,每次内循环开始,都开始一个新的打印行。由于通常情况下<B normal"><FONT face="Times New Roman">printk</FONT></B>只用于打印单行,所以在每次调用中这种循环通常只执行一次。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25853</FONT>:如果预先不知道消息的日志等级,<B normal"><FONT face="Times New Roman">printk</FONT></B>会检查当前行是否以日志等级序列开头。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25860</FONT>:如果不是,<B normal"><FONT face="Times New Roman">buf</FONT></B>中开始未使用的三个字符就能够起作用了。(第一次以后的每次循环,都会覆盖部分消息文本,但是这样并不会引起问题,因为这里的文本只是前面行中的一部分,它们已经被打印过,而且以后也不再需要了。)这样,就可以将日志等级插入<B normal"><FONT face="Times New Roman">buf</FONT></B>中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25866</FONT>:此处有如下属性:<B normal"><FONT face="Times New Roman">p</FONT></B>指向日志等级序列(消息文本紧随其后),<B normal"><FONT face="Times New Roman">msg</FONT></B>指向消息文本——请注意<FONT face="Times New Roman">25852</FONT>行和<FONT face="Times New Roman">25865</FONT>行中对<B normal"><FONT face="Times New Roman">msg</FONT></B>的赋值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>由于已知<B normal"><FONT face="Times New Roman">p</FONT></B>用来指示日志等级序列的开头――该日志等级序列可能是由函数自身所创建的――日志等级可以从<B normal"><FONT face="Times New Roman">p</FONT></B>中抽出并存到<B normal"><FONT face="Times New Roman">msg_level</FONT></B>中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25868</FONT>:没有检测到新行,清空<B normal"><FONT face="Times New Roman">line_feed</FONT></B>标志。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25869</FONT>:这是前面谈到过的内循环,循环将运行到本行结束(也就是检测到新行标志)或者缓冲器的末尾为止。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25870</FONT>:除了将消息打印到控制台之外,<B normal"><FONT face="Times New Roman">printk</FONT></B>还能够记录最近打印的长度为<B normal"><FONT face="Times New Roman">LOG_BUF_LEN</FONT></B>的字符组。(<B normal"><FONT face="Times New Roman">LOG_BUF_LEN</FONT></B>为<FONT face="Times New Roman">16K</FONT>,请参看<FONT face="Times New Roman">25632</FONT>行。)如果在控制台打开之前,内核就已经调用<B normal"><FONT face="Times New Roman">printk</FONT></B>,则显然不能在控制台上正确打印消息,但是这些消息将被尽可能的存储到<B normal"><FONT face="Times New Roman">log_buf</FONT></B>中(<FONT face="Times New Roman">25656</FONT>行)。当控制台打开以后,缓存在<B normal"><FONT face="Times New Roman">log_buf</FONT></B>中的数据就可以转储并在控制台上打印出来,请参看<FONT face="Times New Roman">25988</FONT>行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            <B normal">log_buf</B></FONT>是一个循环缓冲器,<B normal"><FONT face="Times New Roman">log_start</FONT></B>和<B normal"><FONT face="Times New Roman">log_size</FONT></B>变量(<FONT face="Times New Roman">25657</FONT>行和<FONT face="Times New Roman">25646</FONT>行)分别记录当前缓冲器的开始位置和长度。本行中的按位与(<FONT face="Times New Roman">AND</FONT>)操作实际上是快速求模(%)运算,它的正确性依赖于<B normal"><FONT face="Times New Roman">LOG_BUF_LEN</FONT></B>的值是<FONT face="Times New Roman">2</FONT>的幂。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25872</FONT>:保存变量跟踪记录循环日志的值。显然,日志大小会不断增长,直至达到<B normal"><FONT face="Times New Roman">LOG_BUF_LEN</FONT></B>的值为止。此后,<B normal"><FONT face="Times New Roman">log_size</FONT></B>将保持不变,而插入新字符将导致<B normal"><FONT face="Times New Roman">log_start</FONT></B>的增长。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25878</FONT>:请注意<B normal"><FONT face="Times New Roman">logged_chars</FONT></B>(<FONT face="Times New Roman">25658</FONT>行)记录从机器启动之后<B normal"><FONT face="Times New Roman">printk</FONT></B>写入的所有字符的长度,它在每次循环中都会被更新,而不是在循环结束后才改变一次。基于同样的道理,<B normal"><FONT face="Times New Roman">log_start</FONT></B>和<B normal"><FONT face="Times New Roman">log_size</FONT></B>的处理方式也是一样。这实际上是一种优化的时机,但是我们将在结束对函数的介绍之后再对它详细讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25879</FONT>:消息被分为若干行,这当然要使用新行标志符来进行分割。一旦内核检测到新行标志符,就写入一个完整行,从而内循环的执行也可以提前终止。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25884</FONT>:在这里我们先不考虑内部循环是否会提前退出,从<B normal"><FONT face="Times New Roman">msg</FONT></B>到<B normal"><FONT face="Times New Roman">p</FONT></B>的字符序列是专门提供给控制台使用的。(这种字符序列我称之为行,但是不要忘了,这里的行可能并不意味着新行终止,因为<B normal"><FONT face="Times New Roman">buf</FONT></B>也许还没有终止。)如果该行的日志等级高于系统控制台定义的日志等级,而且当前又有控制台可供打印,那么就能够正确打印该行。(记住,<B normal"><FONT face="Times New Roman">printk</FONT></B>可能在所有控制台打开之前就已经被调用过了。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>如果在该信息块中没有发现日志等级序列,并且在前面的<B normal"><FONT face="Times New Roman">printk</FONT></B>调用中也没有对<B normal"><FONT face="Times New Roman">msg_level</FONT></B>赋值,那么本行中的<B normal"><FONT face="Times New Roman">msg_level</FONT></B>就是<FONT face="Times New Roman">-1</FONT>。由于<B normal"><FONT face="Times New Roman">console_leglevel</FONT></B>总不小于<FONT face="Times New Roman">1</FONT>(除非<FONT face="Times New Roman">root</FONT>通过<FONT face="Times New Roman">sysctl</FONT>接口锁定),于是总是可以打印这些行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25886</FONT>:本行应该能够被打印。<B normal"><FONT face="Times New Roman">printk</FONT></B>通过遍历打开的控制台驱动链表告知每一个控制台驱动去打印当前行。(因为虽然设备驱动在本书的讨论范围之外,但是控制台驱动代码则并不包含在内。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25888</FONT>:请注意这里消息文本的开头使用的是<B normal"><FONT face="Times New Roman">msg</FONT></B>而不是<B normal"><FONT face="Times New Roman">p</FONT></B>,这样就在没有日志等级序列的情况下写入消息了。然而,日志等级序列已经被存储到<B normal"><FONT face="Times New Roman">log_buf</FONT></B>缓冲器中了。这样就可以使后来访问<B normal"><FONT face="Times New Roman">log_buf</FONT></B>以获取信息日志等级的代码(请参看<FONT face="Times New Roman">25998</FONT>行)能够正确执行,不会再产生显示混乱信息序列的现象。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25892</FONT>:如果内层<B normal"><FONT face="Times New Roman">for</FONT></B>循环发现一新行,那么<B normal"><FONT face="Times New Roman">buf</FONT></B>中的剩余字符(如果有的话)将被认为是新的消息,因此<B normal"><FONT face="Times New Roman">msg_level</FONT></B>会被重置。但是无论怎样,外层循环都会持续到<B normal"><FONT face="Times New Roman">buf</FONT></B>清空为止。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25895</FONT>:释放在<FONT face="Times New Roman">25845</FONT>行获取的控制台锁(<FONT face="Times New Roman">console lock</FONT>)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25896</FONT>:唤醒等待被写入控制台日志的所有进程。注意即使没有文本被实际写入任何控制台,这个过程也仍然会发生。这样处理是正确的,因为无论是否要往控制台中写入文本,等待进程实际上都是在等待从<B normal"><FONT face="Times New Roman">log_buf</FONT></B>中读出信息。在<FONT face="Times New Roman">25748</FONT>行,进程被转入休眠状态以等待<B normal"><FONT face="Times New Roman">log_buf</FONT></B>的活动。在休眠、唤醒和等待队列中所使用的机制将在下一节中进行讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">25897</FONT>:返回日志中写入的字符长度。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>如果对于每个字符的处理工作都能减少一点,那么从<FONT face="Times New Roman">25869</FONT>行开始的<B normal"><FONT face="Times New Roman">for</FONT></B>循环就能执行得更快一点。当循环存在时,我们可以通过只在循环退出时将<B normal"><FONT face="Times New Roman">logged_chars</FONT></B>更新一次来稍微提高运行速度。然而我们还可以通过其它努力来提高速度。由于我们可以预知消息的长度,因此<B normal"><FONT face="Times New Roman">log_size</FONT></B>和<B normal"><FONT face="Times New Roman">log_start</FONT></B>可以到最后再增长。让我们来实验一下这样能否提高速度,下面是一段经过理想优化的代码:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       do {</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">static int wrapped = 0;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">const int x = wrapped</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  ? log_start</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  : log_size;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">const int lim = LOG_BUF_LEN – x;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">int n = buf_end – p;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">if ( n&gt;= lim)</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  n = lim;</FONT></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">memcpy(log_buf + x, p, n);</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">p += n;</FONT></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">if(log_size &lt; LOG_BUF_LEN)</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  log_size += n;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">else {</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  wrapped = 1;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  log_start += n;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  ;og_start &amp;= LOG_BUF_LEN – 1;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">}</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">} while (p &lt; buf_end);</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>请注意循环通常只需要执行一次,只有在<B normal"><FONT face="Times New Roman">log_buf</FONT></B>末尾写入信息需要折行时才会多次执行。因而<B normal"><FONT face="Times New Roman">log_size</FONT></B>和<B normal"><FONT face="Times New Roman">log_buf</FONT></B>只需要更新一次(或者当写入需要换行时是两次)。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>这时速度的确提高了,但是有两个原因使我们并不能这样做。首先,内核可能有自己特有的<B normal"><FONT face="Times New Roman">memcpy</FONT></B>函数,我们必须确保对<B normal"><FONT face="Times New Roman">memcpy</FONT></B>的调用不会再次进入对<FONT face="Times New Roman">printk</FONT>的调用。(有一部分内核移植版定义了自己特有的速度较快的<B normal"><FONT face="Times New Roman">memcpy</FONT></B>函数版本,因此所有的移植都要在这一点上保持一致。)如果<B normal"><FONT face="Times New Roman">memecpy</FONT></B>调用<B normal"><FONT face="Times New Roman">printk</FONT></B>来报告失败,那么就有可能触发无限循环。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>然而在这一点上也并不是真的无药可救。使用这种解决方案的最大问题在于该内核循环的形式中也要留意新行标志符,因此使用<B normal"><FONT face="Times New Roman">memcpy</FONT></B>将整个消息拷贝到<B normal"><FONT face="Times New Roman">log_buf</FONT></B>中是不正确的:如果此处存在新行,我们将无法对其进行处理。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>我们可以试验一个一箭双雕的办法。下面这种替代的尝试虽然可能比前面那种初步解决方法速度要慢,但是它保持了内核版本的语意:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">/* in declaration section:*/</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">int n;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">char *start;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">static char *log = log_buf;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">/*……*/</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">for (start = p;p &lt; buf_end;p++) {</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  *log++ = *p;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  if  (log &gt;= (log_buf + LOG_BUF_LEN))</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">log = log_buf ;  /* warp*/</FONT></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  if (*p == ‘/n’) {</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">line_feed = 1;</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">break;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  }</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">}</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">/* p - start is number of chars copied. */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">n = p – start;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">logged_chars += n ;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">/*</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">*exercise for the reader:</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">*also use n to update log</FONT> <FONT face="Times New Roman">size and log_ start.</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">*(it’s not as simple as may look.)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">*/</FONT></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>(请注意<FONT face="Times New Roman">gcc</FONT>的优化器十分灵敏,它足以能检测到循环内部的表达式<B normal"><FONT face="Times New Roman">log_buf+LOG_BUF_LEN</FONT></B>并没有改变,因此在上面的循环中试图手工加速计算是没有任何效果的。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>不幸的是,这种方法并不能比现在内核版本在速度上快许多,而且那样会使得代码晦涩难懂(如果你编写过更新<B normal"><FONT face="Times New Roman">log_size</FONT></B>和<B normal"><FONT face="Times New Roman">log_start</FONT></B>的代码,你就能清楚地了解这一点)。你可以自己决定这种折衷是否值得。然而无论怎样,我们学到了一些东西,这是通常的成果:不管成功与否,改进内核代码都可以加深你对内核工作原理的理解。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>等待队列</FONT></H3><P 0cm 0cm 0pt">前一节我们曾简要的提到进程(也就是正在运行的程序)可以转入休眠状态以等待某个特定事件,当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的技术要点是把等待队列(<FONT face="Times New Roman">wait queue</FONT>)和每一个事件联系起来。需要等待事件的进程在转入休眠状态后插入到队列中。当事件发生之后,内核遍历相应队列,唤醒休眠的任务让它投入运行状态。任务负责将自己从等待队列中清除。</P><P 0cm 0cm 0pt">等待队列的功能强大得令人吃惊,它们被广泛应用于整个内核中。更重要的是,实现等待队列的代码量并不大。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">wait_queue结构</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">18662</FONT>:简单的数据结构就是等待队列节点,它包含两个元素:</P><P 0cm 0cm 0pt 64pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 64.0pt">l         <B normal"><FONT face="Times New Roman">tast</FONT></B>――指向<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的指针,它代表一个进程。从<FONT face="Times New Roman">16325</FONT>行开始的<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构将在第<FONT face="Times New Roman">7</FONT>章中进行介绍。</P><P 0cm 0cm 0pt 64pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 64.0pt">l         <B normal"><FONT face="Times New Roman">next</FONT></B>――指向队列中下一节点的指针。因而,等待队列实际上是一个单链表。</P><P 0cm 0cm 0pt">通常,我们用指向等待队列队首的指针来表示等待队列。作为一个例子,请参看<B normal"><FONT face="Times New Roman">printk</FONT></B>使用的等待队列<B normal"><FONT face="Times New Roman">log_wait</FONT></B>(<FONT face="Times New Roman">25647</FONT>行)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">wait_event</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16840</FONT>:通过使用这个宏,内核代码能够使当前执行的进程在等待队列<B normal"><FONT face="Times New Roman">wq</FONT></B>中等待直至给定<B normal"><FONT face="Times New Roman">condition</FONT></B>(可能是任何的表达式)得到满足。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16842</FONT>:如果条件已经为真,当前进程显然也就无需等待了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16844</FONT>:否则,进程必须等待给定条件转变为真。这可以通过调用<B normal"><FONT face="Times New Roman">__wait_event</FONT></B>来实现(<FONT face="Times New Roman">16824</FONT>行),我们将在下一节介绍它。由于<FONT face="Times New Roman"> <B normal">__wait_event</B></FONT>已经同<B normal"><FONT face="Times New Roman">wait_event</FONT></B>分离,已知条件为假的部分内核代码可以直接调用<B normal"><FONT face="Times New Roman">__wait_queue</FONT></B>,而不用通过宏来进行冗余的(特别是在这些情况下)测试,实际上也没有代码会真正这样处理。更为重要的是,如果条件已经为真,<B normal"><FONT face="Times New Roman">wait_event</FONT></B>会跳过将进程插入等待队列的代码。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>注意<FONT face="Times New Roman">wait_event</FONT>的主体是用一个比较特殊的结构封闭起来的:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       do {</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">         /* … */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       } while (0)       </FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>使我惊奇的是,这个小技巧并没有得到应有的重视。这里的主要思路是使被封闭的代码能够像一个单句一样使用。考虑下面这个宏,该宏的目的是如果<B normal"><FONT face="Times New Roman">p</FONT></B>是一个非空指针,则调用<B normal"><FONT face="Times New Roman">free</FONT></B>:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #define FREE1(p)  if  (p)  free (p)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">除非你在如下所述的情况下使用<B normal"><FONT face="Times New Roman">FREE1</FONT></B>,否则所有调用都是正确有效的:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       if  (expression)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">              FREE1(p)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       else</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">              printf(“expression was false.\n”) ;  </FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><B normal"><FONT face="Times New Roman">FREE1</FONT></B>经扩展以后,<B normal"><FONT face="Times New Roman">else</FONT></B>就和错误的<B normal"><FONT face="Times New Roman">if</FONT></B>――<B normal"><FONT face="Times New Roman">FREE1</FONT></B>的<B normal"><FONT face="Times New Roman">if</FONT></B>――联系在一起。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>我曾经发现有些程序员通过如下途径解决这种问题:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #define FREE2(p)  if (p) { free(p); }</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #define FREE3(p)  { if (p) { free(p); } }</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">这两种方法都不尽人意,程序员在调用宏以后自然而然使用的分号会把扩展信息弄乱。以<B normal"><FONT face="Times New Roman">FREE2</FONT></B>为例,在宏展开之后,为了使编译器能更准确的识别,我们还需要进行一定的缩进调节,最终代码如下所示:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">if (expression)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  if (p) { free(p);}</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">else</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  printf(“expression was false./n”);</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">这样就会引起语法错误――<B normal"><FONT face="Times New Roman">else</FONT></B>和任何一个<B normal"><FONT face="Times New Roman">if</FONT></B>都不匹配。<B normal"><FONT face="Times New Roman">FREE3</FONT></B>从本质上讲也存在同样的问题。而且在研究问题产生原因的同时,你也能够明白为什么宏体里是否包含<B normal"><FONT face="Times New Roman">if</FONT></B>是无关紧要的。不管宏体内部内容如何,只要你使用一组括号来指定宏体,你就会碰到相同的问题。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>这里是我们能够引入<B normal"><FONT face="Times New Roman">do/while(0)</FONT></B>技巧的地方。现在我们可以编写<B normal"><FONT face="Times New Roman">FREE4</FONT></B>,它能够克服前面所出现的所有问题。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">#define FREE4(P)  \</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">do {             \</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">if (p)        \</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  free(p);    \ </FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">while (0)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman"> <p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">将<B normal"><FONT face="Times New Roman">FREE4</FONT></B>和其它宏一样插入相同代码之后,宏展开后其代码如下所示(为清晰起见,我们再次调整了缩进格式):</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">if (expression)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  do {</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">if (p)        </FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  free(p);   </FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">  } while (0);   /* “;” following macro.*/</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">这段代码当然可以正确执行。编译器能够优化这个伪循环,舍弃循环控制,因此执行代码并没有速度的损失,我们也从而得到了能够实现理想功能的宏。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>虽然这是一个可以接受的解决方案,但是我们不能不提到的是编写函数要比编写宏好得多。不过如果你不能提供函数调用所需的开销,那么就需要使用内联函数。这种情况虽然在内核中经常出现,但是在其它地方就要少得多。(无可否认,当使用<FONT face="Times New Roman">C++</FONT>,<FONT face="Times New Roman">gcc</FONT>或者任何实现了将要出现的修正版<FONT face="Times New Roman">ISO</FONT>标准<FONT face="Times New Roman">C</FONT>的编译器时,这种方案只是一种选择,就是最后为<FONT face="Times New Roman">C</FONT>增加内联函数。)</P><H4 6pt 0cm; TEXT-INDENT: 0cm">__wait_event</H4><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">16842</FONT>:<B normal"><FONT face="Times New Roman">__wait_event</FONT></B>使当前进程在等待队列<B normal"><FONT face="Times New Roman">wq</FONT></B>中等待直至<B normal"><FONT face="Times New Roman">condition</FONT></B>为真。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16829</FONT>:通过调用<B normal"><FONT face="Times New Roman">add_wait_queue</FONT></B>(<FONT face="Times New Roman">16791</FONT>行),局部变量<B normal"><FONT face="Times New Roman">__wait</FONT></B>可以被链接到队列上。注意<B normal"><FONT face="Times New Roman">__wait</FONT></B>是在堆栈中而不是在内核堆中分配空间,这是内核中常用的一种技巧。在宏运行结束之前,<B normal"><FONT face="Times New Roman">__wait</FONT></B>就已经被从等待队列中移走了,因此等待队列中指向它的指针总是有效的。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">16830</FONT>:重复分配<FONT face="Times New Roman">CPU</FONT>给另一个进程直至条件满足,这一点将在下面几节中讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16831</FONT>:进程被置为<B normal"><FONT face="Times New Roman">TASK_UNINTERRUPTIBLE</FONT></B>状态(<FONT face="Times New Roman">16190</FONT>行)。这意味着进程处于休眠状态,不应被唤醒,即使是信号量也不能打断该进程的休眠。信号量在第<FONT face="Times New Roman">6</FONT>章中介绍,而进程状态则在第<FONT face="Times New Roman">7</FONT>章中介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16832</FONT>:如果条件已经满足,则可以退出循环。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>请注意如果在第一次循环时条件就已经满足,那么前面一行的赋值就浪费了(因为在循环结束之后进程状态会立刻被再次赋值)。<B normal"><FONT face="Times New Roman">__wait_event</FONT></B>假定宏开始执行时条件还没有得到满足。而且,这种对进程状态变量<B normal"><FONT face="Times New Roman">state</FONT></B>的延迟赋值也并没有什么害处。在某些特殊情况下,这种方法还十分有益。例如当<B normal"><FONT face="Times New Roman">__wait_event</FONT></B>开始执行时条件为假,但是在执行到<FONT face="Times New Roman">16832</FONT>行时就为真了。这种变化只有在为有关进程状态的代码计算<B normal"><FONT face="Times New Roman">condition</FONT></B>变量值时才会出现问题。但是在代码中这种情况我一处也没有发现。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16834</FONT>:调用<B normal"><FONT face="Times New Roman">schedule</FONT></B>(<FONT face="Times New Roman">26686</FONT>行,在第<FONT face="Times New Roman">7</FONT>章中讨论)将<FONT face="Times New Roman">CPU</FONT>转移给另一个进程。直到进程再次获得<FONT face="Times New Roman">CPU</FONT>时,对<B normal"><FONT face="Times New Roman">schedule</FONT></B>的调用才会返回。这种情况只有当等待队列中的进程被唤醒时才会发生。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16836</FONT>:进程已经退出了,因此条件必定已经得到了满足。进程重置<B normal"><FONT face="Times New Roman">TASK_RUNNING</FONT></B>的状态(<FONT face="Times New Roman">16188</FONT>行),使其适合<FONT face="Times New Roman">CPU</FONT>运行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16837</FONT>:通过调用<B normal"><FONT face="Times New Roman">remove_wait_queue</FONT></B>(<FONT face="Times New Roman">16814</FONT>行)将进程从等待队列中移去。<B normal"><FONT face="Times New Roman">wait_event_interruptible</FONT></B>和<B normal"><FONT face="Times New Roman">__wait_event_interruptible</FONT></B>(分别参见<FONT face="Times New Roman">16868</FONT>行和<FONT face="Times New Roman">16847</FONT>)基本上与<B normal"><FONT face="Times New Roman">wait_event</FONT></B>和<B normal"><FONT face="Times New Roman">__wait_event</FONT></B>相同,但不同的是它们允许休眠的进程可以被信号量中断。如前所述,信号量将在第<FONT face="Times New Roman">6</FONT>章中介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>请注意<B normal"><FONT face="Times New Roman">wait_event</FONT></B>是被如下结构所包含的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            ({</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">              /* … */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            })</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>和<B normal"><FONT face="Times New Roman">do/while(0)</FONT></B>技巧一样,这样可以使被封闭起来的代码能够像一个单元一样运行。这样的封闭代码就是一个独立的表达式,而不是一个独立的语句。也就是说,它可以求值以供其它更复杂的表达式使用。发生这种情况的原因主要在于一些不可移植的<FONT face="Times New Roman">gcc</FONT>特有代码的存在。通过使用这类技巧,一个程序块中的最后一个表达式的值将定义为整个程序块的最终值。当在表达式中使用<B normal"><FONT face="Times New Roman">wait_event_interruptible</FONT></B>时,执行宏体后赋<B normal"><FONT face="Times New Roman">__ret</FONT></B>的值为宏体的值(参看<FONT face="Times New Roman">16873</FONT>行)。对于有<FONT face="Times New Roman">Lisp</FONT>背景知识的程序员来说,这是个很常见的概念。但是如果你仅仅了解一点<FONT face="Times New Roman">C</FONT>和其它一些相关的过程性程序设计语言,那么你可能就会觉得比较奇怪。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">__wake_up</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">26829</FONT>:该函数用来唤醒等待队列中正在休眠的进程。它由<B normal"><FONT face="Times New Roman">wake_up</FONT></B>和<B normal"><FONT face="Times New Roman">wake_up_interruptible</FONT></B>调用(请分别参看<FONT face="Times New Roman">16612</FONT>行和<FONT face="Times New Roman">16614</FONT>行)。这些宏提供<B normal"><FONT face="Times New Roman">mode</FONT></B>参数,只有状态满足<B normal"><FONT face="Times New Roman">mode</FONT></B>所包含的状态之一的进程才可能被唤醒。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">26833</FONT>:正如将在第<FONT face="Times New Roman">10</FONT>章中详细讨论的那样,锁(<FONT face="Times New Roman">lock</FONT>)是用来限制对资源的访问,这在<FONT face="Times New Roman">SMP</FONT>逻辑单元中尤其重要,因为在这种情况下当一个<FONT face="Times New Roman">CPU</FONT>在修改某数据结构时,另一个<FONT face="Times New Roman">CPU</FONT>可能正在从该数据结构中读取数据,或者也有可能两个<FONT face="Times New Roman">CPU</FONT>同时对同一个数据结构进行修改,等等。在这种情况下,受保护的资源显然是等待队列。非常有趣的是所有的等待队列都使用同一个锁来保护。虽然这种方法要比为每一个等待队列定义一个新锁简单得多,但是这就意味着<FONT face="Times New Roman">SMP</FONT>逻辑单元可能经常会发现自己正在等待一个实际上并不必须的锁。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">26838</FONT>:本段代码遍历非空队列,为队列中正确状态的每一个进程调用<B normal"><FONT face="Times New Roman">wake_up_process</FONT></B>(<FONT face="Times New Roman">26356</FONT>行)。如前所述,进程(队列节点)在此可能并没有从队列中移走。这在很大程度上是由于即使队列中的进程正在被唤醒,它仍然可能希望继续存在于等待队列中,这一点正如我们在<B normal"><FONT face="Times New Roman">__wait_event</FONT></B>中发现的问题一样。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>内核模块(<FONT face="Times New Roman">Kernel Modules</FONT>)</FONT></H3><P 0cm 0cm 0pt">整个内核并不需要同时装入内存。应该确认,为保证系统能够正常运行,一些特定的内核必须总是驻留在内存中,例如,进程调度代码就必须常驻内存。但是内核其它部分,例如大部分的设备驱动就应该仅在内核需要的时候才装载,而在其它情况下则无需占用内存。</P><P 0cm 0cm 0pt">举例来说,只有在内核真正和<FONT face="Times New Roman">CD-ROM</FONT>通讯时才需要使用完成内核与<FONT face="Times New Roman">CD-ROM</FONT>通讯的设备驱动程序,因此内核可以被设置为在和设备通讯之前才装载相应代码。内核完成和设备的通讯之后可以将这部分代码丢弃。也就是说,一旦代码不再需要,就可以从内存中移走。系统运行过程中可以增减的这部分内核称为内核模块。</P><P 0cm 0cm 0pt">内核模块的优点是可以简化内核自身的开发。假设你购买了一个新的高速<FONT face="Times New Roman">CD-ROM</FONT>驱动器,但是现有的<FONT face="Times New Roman">CD-ROM</FONT>驱动程序并不支持该设备。你自然就希望增加对这种高速模式的支持以提高系统光驱设备的性能。如果作为内核模块来编译驱动程序,你的工作将会方便得多:编译驱动程序,加载到内核,测试,卸载驱动程序,修改驱动程序,再次加载驱动程序到内核,测试,如此周而复始。如果你的驱动程序是直接编辑在内核中的,那么你就必须重新编译整个内核并且在每次修改驱动程序之后重新启动机器。这样慢得很多。</P><P 0cm 0cm 0pt">自然,你也必须留意内核模块。对于指明其它内核模块在磁盘上的驻留位置的那些模块,一定不能从内存中卸载,否则,内核将只能通过访问磁盘来装载处理磁盘访问的内核模块,这是不可能实现的。这也是我们要选择把部分内核作为模块编译还是直接编译进内核使其常驻内存的又一个原因。你知道自己系统的设置方式,因而也就可以自己选择正确使用的方式。(如果为了确保安全,你可以简单的忽略内核模块系统的优点,而把所有的内容都编译到内核里面。)</P><P 0cm 0cm 0pt">内核模块会带来一些速度上的损失,这是因为一些必需的代码现在并不在<FONT face="Times New Roman">RAM</FONT>中,必需要从磁盘读入。但是整个系统的性能通常会有所提高,这主要是因为通过丢弃暂时不使用的模块可以释放出额外的<FONT face="Times New Roman">RAM</FONT>供应用程序使用。如果这部分内存被内核所占用,应用程序将只能更加频繁地进行磁盘交换(<FONT face="Times New Roman">swap</FONT>),而这种磁盘交换会显著的降低应用程序的性能。(磁盘交换将在第<FONT face="Times New Roman">8</FONT>章中讨论。)</P><P 0cm 0cm 0pt">内核模块还会带来因复杂度的增加所造成的开销,这是因为在系统运行的过程中移进移出部分内核需要额外的代码。然而,正如你将在本节中看到的,复杂度的开销是可以管理的。通过使用外部程序来代理一些必需的工作还可以更进一步降低复杂度的开销。(更为确切的说法是,这样做不是减少了复杂度的开销,而是把复杂度的开销重新分配了一下。)这是对内核模块原理的一个小小的扩展:即使是内核的支持模块对于内核来说也只是外部的,部分可用的,只有在需要的时候才被装入内存。</P><P 0cm 0cm 0pt">通常用于这种目的程序称为<FONT face="Times New Roman">modprobe</FONT>。有关的<FONT face="Times New Roman">modprobe</FONT>代码超出了本书的范围,但是在<FONT face="Times New Roman">Linux</FONT>的每个发行版本中都有包含有它。本节的剩余部分将讨论同<FONT face="Times New Roman">modprobe</FONT>协同工作以装载内核模块的内核代码。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">request_module</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24432</FONT>:作为函数说明之前的注释,<B normal"><FONT face="Times New Roman">request_module</FONT></B>是一个函数。内核的其它模块在需要装载其它内核模块的时候,都必须调用这个函数。就像内核处理其它工作一样,这种调用也是为当前运行的进程进行的。从进程的角度来看,这种调用的请求通常是隐含的――正在执行进程其它请求的内核可能会发现必须调入一个模块才能够完成该请求。例如,请参看<FONT face="Times New Roman">10070</FONT>行,这里是一些将在第<FONT face="Times New Roman">7</FONT>章中讨论的代码。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24446</FONT>:以内核中的一个独立进程的形式执行<B normal"><FONT face="Times New Roman">exec_modprobe</FONT></B>函数(<FONT face="Times New Roman">24384</FONT>行,马上就会讨论到)。这并不能只通过函数的简单调用实现,因为<B normal"><FONT face="Times New Roman">exec_modprobe</FONT></B>要继续调用<B normal"><FONT face="Times New Roman">exec</FONT></B>来执行一个程序。因此,对函数<B normal"><FONT face="Times New Roman">exec_modprobe</FONT></B>的简单调用将永远不会有返回。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这和使用<B normal"><FONT face="Times New Roman">fork</FONT></B>以准备<B normal"><FONT face="Times New Roman">exec</FONT></B>调用十分类似,你可以认为<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>对内核来说就是较低版本的<B normal"><FONT face="Times New Roman">fork</FONT></B>,虽然两者有很大不同。<B normal"><FONT face="Times New Roman">fork</FONT></B>是从指定函数开始执行新的进程,而不是从调用者的当前位置开始运行。正如<B normal"><FONT face="Times New Roman">fork</FONT></B>一样,<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>返回的值是新进程的进程号。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24448</FONT>:和<B normal"><FONT face="Times New Roman">fork</FONT></B>一样,从<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>返回的负值表示内部错误。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24455</FONT>:正如函数中论述的一样,大部分的信号量将因当前进程而被暂时阻塞。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24462</FONT>:等待<B normal"><FONT face="Times New Roman">exec_modprobe</FONT></B>执行完毕,同时指出所需要的模块是已经成功装入内存还是装载失败了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24465</FONT>:结束运行,恢复信号量。如果<B normal"><FONT face="Times New Roman">exec_modpro</FONT></B>返回错误代码,则打印错误消息。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">exec_modprobe</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24384</FONT>:<B normal"><FONT face="Times New Roman">exec_modprobe</FONT></B>运行为内核增加内核模块的程序。这里的模块名是一个<B normal"><FONT face="Times New Roman">void*</FONT></B>的指针,而不是<B normal"><FONT face="Times New Roman">char*</FONT></B>的指针。原因简单说来就是<FONT face="Times New Roman"><B normal">kernel_thread</B> </FONT>产生的函数通常都使用<B normal"><FONT face="Times New Roman">void*</FONT></B>指针参数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24386</FONT>:设置<FONT face="Times New Roman">modprobe</FONT>的参数列表和环境。<B normal"><FONT face="Times New Roman">Modprobe_path</FONT></B>(<FONT face="Times New Roman">24363</FONT>行)用来定位<FONT face="Times New Roman">modprobe</FONT>程序的位置。它可以通过内核的<FONT face="Times New Roman">sysctl</FONT>特性来修改,这一点将在第<FONT face="Times New Roman">11</FONT>章中介绍(请参看<FONT face="Times New Roman">30388</FONT>行)。这意味着<FONT face="Times New Roman">root</FONT>可以动态选择不同于<FONT face="Times New Roman">/sbin/modprobe</FONT>的程序来运行,以适应当<FONT face="Times New Roman">modprobe</FONT>被安装到其它地方或者使用修改过的<FONT face="Times New Roman">modprobe</FONT>替换掉了原有的<FONT face="Times New Roman">modprobe</FONT>之类的情况。<p></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24400</FONT>:(正如代码中描述的一样)出于安全性考虑,丢弃所有挂起的信号量和信号量句柄(<FONT face="Times New Roman">handlers</FONT>)。这里最重要的部分是对<B normal"><FONT face="Times New Roman">flush_signal_handlers</FONT></B>的调用(<FONT face="Times New Roman">28041</FONT>行),它使用内核默认的信号量句柄代替所有用户定义的信号量句柄。如果在此时有信号量被传送到内核,它将获得默认响应——通常是忽略信号量或杀死进程。但是不管怎样都不会引起安全风险。由于该函数从触发它的进程中分离出来(如前所述),所以不管原始进程在此处是否改变其原来分配的信号量句柄都不会产生任何影响。<p></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24405</FONT>:关闭调用进程打开的所有文件。最重要的是,这意味着<FONT face="Times New Roman">modprobe</FONT>程序不再从调用进程中继承标准输入输出和标准错误。这很有可能会引起安全漏洞。(这可能在替代<FONT face="Times New Roman">modprobe</FONT>的程序中引起的问题,但是<FONT face="Times New Roman">modprobe</FONT>本身实际上并不关心这个差异。)<p></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24413</FONT>:<FONT face="Times New Roman">modprobe</FONT>程序作为<FONT face="Times New Roman">root</FONT>运行,它拥有<FONT face="Times New Roman">root</FONT>所拥有的所有权限。和整个内核中其它地方一样,请注意<FONT face="Times New Roman">root</FONT>使用用户<FONT face="Times New Roman">ID</FONT>号<FONT face="Times New Roman">0</FONT>的假定在这里已经被写入程序。用户<FONT face="Times New Roman">ID</FONT>号和权能系统(<FONT face="Times New Roman">capability system</FONT>)(在接下来的几行中会用到)将在第<FONT face="Times New Roman">7</FONT>章中介绍。<p></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24421</FONT>:试图执行<FONT face="Times New Roman">modprobe</FONT>程序。如果尝试失败,内核将使用<B normal"><FONT face="Times New Roman">printk</FONT></B>打印错误消息并返回错误代码。这里是可能产生<B normal"><FONT face="Times New Roman">printk</FONT></B>的缓冲器过载的地点之一。<B normal"><FONT face="Times New Roman">module_name</FONT></B>的长度并没有明确限制,就我们对该调用的看法而言,它可能长达一百万个字符。为防止<B normal"><FONT face="Times New Roman">printk</FONT></B>缓冲器过载,你必需遍历所有对于该函数的调用(实际上是对<B normal"><FONT face="Times New Roman">request_module</FONT></B>的调用)以保证每个调用者使用足够短的不会为<FONT face="Times New Roman">printk</FONT>造成麻烦的模块名。<p></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24427</FONT>:当<B normal"><FONT face="Times New Roman">execve</FONT></B>成功执行时,它不会返回任何结果,因此本处是不可能执行到的。但是编译器却并不知道这一点,因此此处使用了<B normal"><FONT face="Times New Roman">return</FONT></B>语句以保证<FONT face="Times New Roman">gcc</FONT>不出错。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>对于内核的进一步讨论将超出本章的既定范围,因此在这个问题上我们到此为止。然而本书中也包括了其它必需的内核代码。在读完第<FONT face="Times New Roman">4</FONT>章和第<FONT face="Times New Roman">5</FONT>章之后,也许你会希望再次仔细研读一下这部分内容。有关这个问题的两个文件是<FONT face="Times New Roman">include/linux/module.h</FONT>(从<FONT face="Times New Roman">15529</FONT>行开始)和<FONT face="Times New Roman">/kernel/module.c</FONT>(从<FONT face="Times New Roman">24476</FONT>行开始)。和<B normal"><FONT face="Times New Roman">sys_create_module</FONT></B>(<FONT face="Times New Roman">24586</FONT>行),<B normal"><FONT face="Times New Roman">sys_init_module</FONT></B>(<FONT face="Times New Roman">24637</FONT>行),<B normal"><FONT face="Times New Roman">sys_delete_module</FONT></B>(<FONT face="Times New Roman">24860</FONT>行)和<B normal"><FONT face="Times New Roman">sys_query_module</FONT></B>(<FONT face="Times New Roman">25148</FONT>行)四个函数需要特别注意一样,<B normal"><FONT face="Times New Roman">struct module</FONT></B>(<FONT face="Times New Roman">15581</FONT>行)也要特别引起注意。这些函数实现了<FONT face="Times New Roman">modprobe</FONT>以及<FONT face="Times New Roman">insmod</FONT>,<FONT face="Times New Roman">lsmod</FONT>和<FONT face="Times New Roman">rmmod</FONT>所使用的系统调用以完成模块的装载、定位和卸载。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       </FONT>内核触发直接回调内核程序的现象看起来很令人奇怪。但是,实际上进行的工作不止于此。例如,<FONT face="Times New Roman">modprobe</FONT>必须实际访问磁盘以搜寻要装载的模块。而且更为重要的一点是这种方法赋予<FONT face="Times New Roman">root</FONT>对内核模块系统更多的控制能力。这主要是因为<FONT face="Times New Roman">root</FONT>也可以运行<FONT face="Times New Roman">modprobe</FONT>以及相关程序。因此,<FONT face="Times New Roman">root</FONT>既可以手工装载、查询、卸载模块,也可以由内核自动完成。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">配置与编译内核</H2><P 0cm 0cm 0pt">你可能仅仅研读、欣赏而并不修改<FONT face="Times New Roman">Linux</FONT>内核源代码。但是,更普遍的情况是,用户有强烈的愿望去改进内核代码并完成相应的测试,这样我们就需要知道如何重建内核。本节就是要告诉你如何实现这一点,而最终则归结于如何把你所做的修改发行给别人,以使得每个人都能从你的工作中受益。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>配置内核</FONT></H3><P 0cm 0cm 0pt">编译内核的第一步就是配置内核,这是增加或者减少对内核特性的支持以及修改内核的一些特性发挥作用的方式的必要步骤。例如,你可以要求内核为自己的声卡指定一个不同的<FONT face="Times New Roman">DMA</FONT>通道。如果内核配置和你的需要相同,那么你可以直接跳过本节,否则请继续阅读以下内容。</P><P 0cm 0cm 0pt">为了完成内核的配置,请先切换到<FONT face="Times New Roman">root</FONT>用户,然后转入如下内核源程序目录:</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">cd /usr/src/linux</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">接着敲入如下命令组:</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">make config</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">make menuconfig</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">make xconfig</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">这三条命令都可以让你来配置内核,但它们发挥作用的方式各不相同:</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo4; tab-stops: list 21.25pt">l         <B normal"><FONT face="Times New Roman">make config</FONT></B>――三种方法中最简单也是最枯燥的一种。但是最基本的一点是,它可以适应任何情况。这种方法通过为每一个内核支持的特性向用户提问的方式来决定在内核中需要包含哪些特性。对于大多数问题,你只要回答<FONT face="Times New Roman">y</FONT>(<FONT face="Times New Roman">yes</FONT>,把该特性编译进内核中),<FONT face="Times New Roman">m</FONT>(作为模块编译)或者<FONT face="Times New Roman">n</FONT>(<FONT face="Times New Roman">no</FONT>,根本不对该特性提供支持)。在决定之前用户应该考虑清楚,因为这个过程是不可逆的。如果你在该过程中犯了错误,就只能按<FONT face="Times New Roman">Ctrl+C</FONT>退出。你也可以敲入<FONT face="Times New Roman">?</FONT>以获取帮助。图<FONT face="Times New Roman">2.1</FONT>显示了这种方法正在<FONT face="Times New Roman">X</FONT>终端上运行的情况。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><wrapblock><v:shapetype><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path connecttype="rect" gradientshapeok="t" extrusionok="f"></v:path><lock aspectratio="t" v:ext="edit"></lock></v:shapetype><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all><B normal"><p></p></B></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">2.1 </FONT>运行中的<B normal"><FONT face="Times New Roman">make config</FONT></B></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 18pt; TEXT-INDENT: 18pt">幸运的是,这种方法还有一些智能。例如,如果你对<FONT face="Times New Roman">SCSI</FONT>支持回答<FONT face="Times New Roman">no</FONT>,那么系统就不会再询问你有关<FONT face="Times New Roman">SCSI</FONT>的细节问题了。而且你可以只按回车键以接受缺省的选择,也就是当前的设置(因此,如果当前内核将对于<FONT face="Times New Roman">SCSI</FONT>的支持编译进了内核,在这个问题上按回车键就意味着继续把对<FONT face="Times New Roman">SCSI</FONT>的支持编译进内核中)。即使是这样,大部分用户还是宁愿使用另外的两种方法。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l5 level1 lfo5; tab-stops: list 21.25pt">l         <B normal"><FONT face="Times New Roman">make menuconfig</FONT></B>―一种基于终端的配置机制,用户拥有通过移动光标来进行浏览等功能。图<FONT face="Times New Roman">2.2</FONT>显示了在<FONT face="Times New Roman">X</FONT>终端上运行的<B normal"><FONT face="Times New Roman">make menuconfig</FONT></B>。虽然在控制台上显示的是彩色,但是在终端上的显示仍然相当单调。使用<FONT face="Times New Roman">menuconfig</FONT>必须要有相应的<FONT face="Times New Roman">ncurses</FONT>类库。</P><P 0cm 0cm 0pt"><wrapblock><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all><B normal"><p></p></B></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">2.2 </FONT>运行中的<B normal"><FONT face="Times New Roman">make menuconfig</FONT></B></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l5 level1 lfo5; tab-stops: list 21.25pt">l         <wrapblock><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all><B normal"><FONT face="Times New Roman">make xconfig</FONT></B>――这是我最喜欢的一种配置方式。只有你能够在<FONT face="Times New Roman">X server</FONT>上用<FONT face="Times New Roman">root</FONT>用户身份运行<FONT face="Times New Roman">X</FONT>应用程序时,这种配置方式才可以使用(有些偏执的用户就不愿意使用这种方式)。你还必须拥有<FONT face="Times New Roman">Tcl</FONT>窗口系统(<FONT face="Times New Roman">Tcl windowing system</FONT>),这实际上还意味着你必须拥有<FONT face="Times New Roman">Tcl</FONT>,<FONT face="Times New Roman">Tk</FONT>以及一个正在运行的<FONT face="Times New Roman">X</FONT>安装程序。作为补偿,用户获得的是更漂亮的,基于<FONT face="Times New Roman">X</FONT>系统的以及和<FONT face="Times New Roman">menuconfig</FONT>功能相同的配置方法。图<FONT face="Times New Roman">2.3</FONT>显示这种方法运行过程中打开“可装载模块支持(<FONT face="Times New Roman">Loadable module support</FONT>)”子窗口的情况。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><B normal"><p><FONT face="Times New Roman"> </FONT></p></B></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><B normal"><p><FONT face="Times New Roman"> </FONT></p></B></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">2.3 </FONT>运行中的<B normal"><FONT face="Times New Roman">make xconfig</FONT></B></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt">如上所述,这三种方法都实现了相同的功能:它们都生成在构建内核时使用的<FONT face="Times New Roman">.config</FONT>文件。而唯一的区别是在于创建这个文件时的难易程度不同。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>构建内核</FONT></H3><P 0cm 0cm 0pt">构建内核要做的工作要比配置内核所做的工作少得多。虽然有几种方式都能实现这一功能,但是选择哪一种依赖于你希望怎样对系统进行设置。长期以来,我已经形成了如下的习惯。虽然这种习惯比我所必须要做的略微多一些,但是它包含了所有基本的问题。首先,如果你还不在内核源程序目录中,请先再次转入这一目录:</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">cd  /usr/src/linux</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">现在,切换到<FONT face="Times New Roman">root</FONT>用户,使用下面显示的命令生成内核。现在在<FONT face="Times New Roman">shell</FONT>中敲入下面的命令,注意<FONT face="Times New Roman">make</FONT>命令因为空间关系分成了两行,但实际上这在<FONT face="Times New Roman">shell</FONT>输入时是一个只有一行的命令:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       make dep clean zlilo boot</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">              modules modules_install</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">当给出了如上多个目标时,除非前面所有的目标都成功了,否则<FONT face="Times New Roman">make</FONT>能够知道没有必要继续尝试下面的目标。因此,如果<FONT face="Times New Roman">make</FONT>能够运行结束,成功退出,那么这就意味着所有的目标都正确构建了。现在你可以重新启动机器以运行新的内核。</P><H4 6pt 0cm; TEXT-INDENT: 0cm"><I normal">备份的重要性<p></p></I></H4><P 0cm 0cm 0pt">当修改(<FONT face="Times New Roman">fooling</FONT>)内核时,你必须准备一个能够启动的备用内核。实现该目的的一种方式是通过配置<FONT face="Times New Roman">Linux</FONT>加载程序(<FONT face="Times New Roman">LILO</FONT>)以允许用户选择启动的内核映象,其中之一是从没有修改过的内核的备份(我总是这样做的)。</P><P 0cm 0cm 0pt">如果你比较有耐心,那么你就可以使用<FONT face="Times New Roman">zdisk</FONT>目标而不使用<FONT face="Times New Roman">zlilo</FONT>目标;它可以把能够启动的内核映象写入软盘中。这样你就可以通过在启动时插入软盘的方式启动你的测试内核;如果没有插入软盘,则启动正常的内核。</P><P 0cm 0cm 0pt">但是请注意:内核模块并没有被装载到软盘中,它们实际上是装在硬盘中的(除非你愿意承担更多的麻烦)。因此,如果你弄乱了内核模块,即使是<FONT face="Times New Roman">zdisk</FONT>目标也救不了你。实际上,上面提到的这两种方法都存在这个问题。虽然有比较好的解决方法可用,但是最简单的方法(也就是我所使用的方法)是把备份内核作为严格独立的内核来编译,而不使用可装载模块的支持。通过这种方法,即使我弄乱了内核而不得不使用备份启动系统,那么不管问题是实验性内核不正确还是内核模块的原因都无关紧要。不管怎样,在备份的内核中已经有我需要的所有东西了。</P><P 0cm 0cm 0pt">由于用户所作的修改可能导致系统的崩溃,如损坏磁盘上的数据等等,并不仅仅只是打乱设备驱动程序或文件系统,在测试新内核之前,备份系统的最新数据也是一个英明的决策。(虽然设备驱动程序的开发不是本书的主题,但是必需指出的是,设备驱动程序的缺陷可能会引起系统的物理损坏。例如显示器是不能备份的,而且因价格昂贵而不易替换。)作为一个潜在的内核黑客,你的最佳投资(当然是读过本书以后)是一个磁带驱动器和充足的磁带。</P><H4 6pt 0cm; TEXT-INDENT: 0cm"><I normal">发行你的改进<p></p></I></H4><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">下面是有关发行你所做修改的一些基本规则:</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo6; tab-stops: list 21.25pt">l         检查最新发行版本,确保你所处理的不是已经解决了的问题</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo6; tab-stops: list 21.25pt">l         遵守<FONT face="Times New Roman">Linux </FONT>内核代码编写的风格。简要的说就是<FONT face="Times New Roman">8</FONT>字符缩进以及<FONT face="Times New Roman">K&amp;R</FONT>括号风格(<B normal"><FONT face="Times New Roman">if</FONT></B>,<B normal"><FONT face="Times New Roman">else</FONT></B>,<B normal"><FONT face="Times New Roman">for</FONT></B>,<B normal"><FONT face="Times New Roman">while</FONT></B>,<B normal"><FONT face="Times New Roman">switch</FONT></B>或者<B normal"><FONT face="Times New Roman">do</FONT></B>后面同一行中紧跟着开括号)。在内核源程序目录下面的文档编写和代码风格文件给出了完整的规则,不过我们已经介绍了其中的关键部分。注意本书中包含的源程序代码为节省空间而进行了大量的重新编辑,在该过程中我可能打破了其中的一些规则。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo6; tab-stops: list 21.25pt">l         独立发行相对无关的修改。这样,只想使用你所做的某部分修改的人就可以十分方便地获得想要的东西,而不用一次检验所有的修改内容。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo6; tab-stops: list 21.25pt">l         让使用你所做修改的用户清楚他们可以从你的修改中获取什么。同样地,你也应该给出这些问题的可信度。你是<FONT face="Times New Roman">15</FONT>分钟之前才匆匆完成你的修改,甚至还没有时间对它们进行编译,还是已经在你和你的朋友的系统中从去年<FONT face="Times New Roman">3</FONT>月开始就长期稳定的运行过这个修改?</P><P 0cm 0cm 0pt">假设现在你已经准备好发行自己的修改版本了,那么要做的第一步是建立一个说明你所做的修改的文件。你可以使用<FONT face="Times New Roman">diff</FONT>程序自动创建这个文件。结果或者被称为<FONT face="Times New Roman">diffs</FONT>,也或者在<FONT face="Times New Roman">Linux</FONT>中更普遍的被称为补丁(<FONT face="Times New Roman">patch</FONT>)。</P><P 0cm 0cm 0pt">发布的过程十分简单。假设原来没有修改过的源程序代码在<FONT face="Times New Roman">linux-2.2.5</FONT>目录下,而你修改过的源程序代码在<FONT face="Times New Roman">linux-my</FONT>目录下,那么只要进行如下的简单工作就可以了(只有在链接不存在的情况下才需要执行<FONT face="Times New Roman">ln</FONT>):</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">ln –s linux-my linux</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">make –C linux-2.2.5 distclean</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">make –C linux distclean</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">diff –urN linux-2.2.5 linux &gt;my.patch</FONT></P><P 0cm 0cm 0pt">现在,输出文件<FONT face="Times New Roman">my.patch</FONT>包含了其它用户应用这个修改程序时所必须的一切内容。(警告:如上所述,两个源程序间的所有差别都会包含在这个补丁文件中。<FONT face="Times New Roman">Diff</FONT>不能区分修改部分之间的关系,所以就把它们都罗列了出来。)如果补丁文件相对较小,你可以使用邮件直接发往内核邮件列表。如果补丁很大,那么就需要通过<FONT face="Times New Roman">FTP</FONT>或者<FONT face="Times New Roman">Web</FONT>站点发布。这时发给邮件列表的信件中就只需要包含一个<FONT face="Times New Roman">URL</FONT>。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>内核邮件列表的常见问题解答(<FONT face="Times New Roman">FAQ</FONT>)文件位于<B normal"><FONT face="Times New Roman">http://www.ececs.uc.edu/~rreilova/linux/lkmlfaq.html</FONT></B>。该<FONT face="Times New Roman">FAQ</FONT>中包含了邮件列表的订阅,邮件发布以及阅读邮件列表的注意事项等等。</P><P 0cm 0cm 0pt">顺便提一下,如果你想随时了解内核更新开发的进程,我向你强烈推荐下面这个具有很高价值的内核交流站点<FONT face="Times New Roman">Kernel Traffic</FONT>:<B normal"><FONT face="Times New Roman">http://www.kt.opensrc.org</FONT></B>。</P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman">第3章  </FONT>内核体系结构概述</H1>< 0cm 0cm 0pt">本章从较高层次上对内核进行说明。从顺序上来说,本章首先介绍内核设计目标,接下来介绍内核体系结构,最后介绍内核源程序目录结构。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">内核设计目标</H2>< 0cm 0cm 0pt"><FONT face="Times New Roman">Linux </FONT>的内核展现出了几个相互关联的设计目标,它们依次是:清晰性(<FONT face="Times New Roman">clarity</FONT>),兼容性(<FONT face="Times New Roman">compatibility</FONT>),可移植性(<FONT face="Times New Roman">portability</FONT>),健壮性(<FONT face="Times New Roman">robustness</FONT>),安全性(<FONT face="Times New Roman">security</FONT>)和速度(<FONT face="Times New Roman">speed</FONT>)。这些目标有时是互补的,有时则是矛盾的。但是它们被尽可能的保持在相互一致的状态,内核设计和实现的特性通常都要回归到这些问题上来。本节接下来的部分将分别讨论这些设计目标,同时还将对它们之间的取舍与平衡进行简要的说明。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>清晰性</FONT></H3>< 0cm 0cm 0pt">稍微过于简化的说,内核目标是在保证速度和健壮性的前提下尽量清晰。这和现在的大多数应用程序的开发有所区别,后者的目标通常是在保证清晰性和健壮性的基础上尽量提高速度。因而在内核内部,速度和清晰性经常是一对矛盾。</P>< 0cm 0cm 0pt">在某种程度上,清晰性是健壮性的必要补充:一个很容易理解的实现方法比较容易证明是正确的;或者即使不正确,也能比较容易的找出其问题所在。从而这两个目标很少会发生冲突。</P>< 0cm 0cm 0pt">但是清晰性和速度通常却是一对矛盾。经过仔细手工优化的算法通常都使用了编译器生成代码的类似技术,很少可能是最清晰的解决方案。当内核中清晰性和速度要求不一致时,通常都是以牺牲清晰性来保证速度的。即便如此,程序员仍然清楚的知道清晰性的重要性,而且他们也做了大量完美的工作以使用最清晰的方法保证速度。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>兼容性</FONT></H3>< 0cm 0cm 0pt">正如第<FONT face="Times New Roman">1</FONT>章中所述,<FONT face="Times New Roman">Linux</FONT>最初的编写目的是为了实现一个完整的、与<FONT face="Times New Roman">Unix</FONT>兼容的操作系统内核。随着开发过程的展开,它也开始以符合<FONT face="Times New Roman">OSIX</FONT>标准为目标。就内核而言,兼容<FONT face="Times New Roman">Unix</FONT>(至少是同某一现代的<FONT face="Times New Roman">Unix</FONT>实现相兼容)和符合<FONT face="Times New Roman">OSIX</FONT>标准并没有什么区别,因此我们也不会在这个问题上详细追究。</P>< 0cm 0cm 0pt">内核提供了另外一种类型的兼容性。基于<FONT face="Times New Roman">Linux </FONT>的系统能够提供可选择的对<FONT face="Times New Roman">Java.class</FONT>文件的本地运行支持。(据说<FONT face="Times New Roman">Linux</FONT>是第一个提供这种支持的操作系统。)尽管实际负责<FONT face="Times New Roman">Java</FONT>程序解释执行的是另外一个<FONT face="Times New Roman">Java</FONT>虚拟机进程,该虚拟机并没有内置到内核中。但是内核提供的这种机制可以使得这种支持对用户是透明的。通过内核本身提供的程度不同的支持(这并不代表大部分工作像<FONT face="Times New Roman">Java</FONT>的解决方式一样能够通过外部进程实现),对其它可执行文件格式的支持也能够以同样的方式插入内核中。这方面的内容将在第<FONT face="Times New Roman">7</FONT>章中详细介绍。</P>< 0cm 0cm 0pt">另外需要说明的是,<FONT face="Times New Roman">GNU/Linux</FONT>系统作为一个整体通过<FONT face="Times New Roman">DOSEMU</FONT>仿真机器提供了对<FONT face="Times New Roman">DOS</FONT>可执行程序的支持,而且也通过<FONT face="Times New Roman">WINE</FONT>设计提供了对<FONT face="Times New Roman">Windows</FONT>可执行程序的部分支持。系统还以同样的方式通过<FONT face="Times New Roman">SAMBA</FONT>提供了对<FONT face="Times New Roman">Windows</FONT>兼容文件和打印服务的支持。但是这些都不是同内核密切相关的问题,因此在本书中我们不再对它们进行讨论。</P>< 0cm 0cm 0pt">兼容性的另外一个方面是兼容异种文件系统,本章中稍后会有更为详细的介绍,但是大部分内容已经超出了本书的范围。<FONT face="Times New Roman">Linux</FONT>能够支持很多文件系统,例如<FONT face="Times New Roman">ext2</FONT>(“本地”文件系统),<FONT face="Times New Roman">ISO-9660</FONT>(<FONT face="Times New Roman">CD-ROM</FONT>使用的文件系统),<FONT face="Times New Roman">MS-DOS</FONT>,网络文件系统(<FONT face="Times New Roman">NFS</FONT>)等许多其它文件系统。如果你有使用其它操作系统格式的磁盘或者一个网络磁盘服务器,那么<FONT face="Times New Roman">Linux</FONT>将能够和这些不同的文件系统进行交互。</P>< 0cm 0cm 0pt">兼容性的另外一个问题是网络,这在当今<FONT face="Times New Roman">Internet</FONT>流行的时代尤为重要。作为<FONT face="Times New Roman">Unix</FONT>的一个变种,<FONT face="Times New Roman">Linux</FONT>自然从很早就开始提供对<FONT face="Times New Roman">TCP/IP</FONT>的支持。内核还支持其它许多网络协议,它们包括<FONT face="Times New Roman">AppleTalk</FONT>协议的代码,这使得<FONT face="Times New Roman">Linux</FONT>单元(<FONT face="Times New Roman">box</FONT>)可以和<FONT face="Times New Roman">Macintosh</FONT>机自由通讯;<FONT face="Times New Roman">Novell</FONT>的网络协议,也就是网络报文交换(<FONT face="Times New Roman">IPX</FONT>),分组报文交换(<FONT face="Times New Roman">SPX</FONT>),和<FONT face="Times New Roman">NetWare</FONT>核心协议(<FONT face="Times New Roman">NCP</FONT>);<FONT face="Times New Roman">IP</FONT>协议的新版本<FONT face="Times New Roman">IPv6</FONT>;以及其它一些不太出名的协议。</P>< 0cm 0cm 0pt">兼容性考虑的最后一个方面是硬件兼容性。似乎每个不常见的显卡,市场份额小的网卡,非标准的<FONT face="Times New Roman">CD-ROM</FONT>接口和专用磁带设备都有<FONT face="Times New Roman">Linux</FONT>的驱动程序。(只要它不是专为特定操作系统设计的专用硬件。)而且只要越来越多的厂商也逐渐认识到<FONT face="Times New Roman">Linux</FONT>的优势,并能够为更容易地实现向<FONT face="Times New Roman">Linux</FONT>上移植而开放相应的源程序代码,<FONT face="Times New Roman">Linux</FONT>对硬件支持会越来越好。</P>< 0cm 0cm 0pt">这些兼容性必须通过一个重要的子目标:模块度(<FONT face="Times New Roman">Modularity</FONT>)来实现。在可能的情况下,内核只定义子系统的抽象接口,这种抽象接口可以通过任何方法来实现。例如,内核对于新文件系统的支持将简化为对虚拟文件系统(<FONT face="Times New Roman">VFS</FONT>)接口的代码实现。第<FONT face="Times New Roman">7</FONT>章中介绍的是另外一个例子,内核对二进制句柄的抽象支持是实现对诸如<FONT face="Times New Roman">Java</FONT>之类的新可执行格式的支持的方法。增加新的可执行格式的支持将转变为对相应的二进制句柄接口的实现。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>可移植性</FONT></H3>< 0cm 0cm 0pt">与硬件兼容性相关的设计目标是可移植性(<FONT face="Times New Roman">portability</FONT>),也就是在不同硬件平台上运行<FONT face="Times New Roman">Linux</FONT>的能力。系统最初是为运行在标准<FONT face="Times New Roman">IBM</FONT>兼容机上的<FONT face="Times New Roman">Intel x86 CPU</FONT>而设计的,当时根本没有考虑到可移植性的问题。但是情况从那以后已经发生了很大的变化。现在正式的内核移植包括向基于<FONT face="Times New Roman">Alpha</FONT>,<FONT face="Times New Roman">ARM</FONT>,<FONT face="Times New Roman">Motorola 69x0</FONT>,<FONT face="Times New Roman">MIPS</FONT>,<FONT face="Times New Roman">owerPC</FONT>,<FONT face="Times New Roman">SPARC</FONT>以及<FONT face="Times New Roman">SPARC-64 CPU</FONT>系统的移植。因而,<FONT face="Times New Roman">Linux</FONT>可以在<FONT face="Times New Roman">Amigas</FONT>,旧版或新版的<FONT face="Times New Roman">Macintosh</FONT>,<FONT face="Times New Roman">Sun</FONT>和<FONT face="Times New Roman">SGI</FONT>工作站以及<FONT face="Times New Roman">NeXT</FONT>机等机器上运行。而且这些还只是标准内核发行版本的移植范围。从老的<FONT face="Times New Roman">DEC VAX</FONT>到<FONT face="Times New Roman">3Com</FONT>掌上系列个人数字助理(例如<FONT face="Times New Roman">alm III</FONT>)的非正式的移植工作也在不断进行中。成功的非正式移植版本后来通常都会变成正式的移植版本,因此这些非正式的移植版本很多最终都会出现在主开发树中。</P>< 0cm 0cm 0pt">广泛平台支持之所以能够成功的部分原因在于内核把源程序代码清晰地划分为体系结构无关部分和体系结构相关部分。在本章的后续部分将对这个问题进行更深入的讨论。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>健壮性和安全性</FONT></H3>< 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>必须健壮、稳定。系统自身应该没有任何缺陷,并它还应该可以保护进程(用户)以防止互相干扰,这就像把整个系统从其它系统中隔离开来加以保护一样。后一种考虑很大程度上是受信任的用户空间应用程序领域的问题,但是内核至少也应该提供支撑安全体系的原语(<FONT face="Times New Roman">primitive</FONT>)。健壮性和安全性比任何别的目标都要重要,包括速度。(系统崩溃的速度很快又有什么好处呢?)</P>< 0cm 0cm 0pt">保证<FONT face="Times New Roman">Linux</FONT>健壮性和安全性的唯一一个最重要的因素是其开放的开发过程,它可以被看作是一种广泛而严格的检查。内核中的每一行代码、每一个改变都会很快由世界上数不清的程序员检验。还有一些程序员专门负责寻找和报告潜在的缺陷――他们这样做完全是出于自己的个人爱好,因为他们也希望自己的<FONT face="Times New Roman">Linux</FONT>系统能够健壮安全。以前检查中所没有发现的缺陷可以通过这类人的努力来定位、修复,而这种修复又合并进主开发树以使所有的人都能够受益。安全警告和缺陷报告通常在几天甚至几个小时内就能够得到处理和修复。</P>< 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>可能并不一定是现有的最安全的操作系统(很多人认为这项桂冠应该属于<FONT face="Times New Roman">OpenBSD</FONT>,它是一个以安全性为主要目标的<FONT face="Times New Roman">Unix</FONT>变种),但是它是一个有力的竞争者。而且<FONT face="Times New Roman">Linux</FONT>健壮性远没有发展到尽头。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>速度</FONT></H3>< 0cm 0cm 0pt">这个术语经常自己就可以说明问题。速度几乎是最重要的衡量标准,虽然其等级比健壮性、安全性和(在有些时候)兼容性的等级要低。然而它却是代码最直观的几个方面之一。<FONT face="Times New Roman">Linux</FONT>内核代码经过了彻底的优化,而最经常使用的部分――例如调度――则是优化工作的重点。几乎在任何时候都有一些不可思议的代码,这是由于这种方式的执行速度比较快。(这并不总是很明显,但是你经常不得不通过自己的试验来对这种优化代码进行确认。)虽然有时一些更直接的实现方法速度也很快,但是我所见过的这种情况屈指可数。</P>< 0cm 0cm 0pt">在某些情况下,本书推荐用可读性更好的代码来替代那些以速度的名义而被故意扭曲了的代码。虽然速度是一个设计目标,但我基本上只在以下两种情况时才会这样做:<FONT face="Times New Roman">a) </FONT>在所考虑的问题中,速度明显不是关键问题<FONT face="Times New Roman"> b) </FONT>没有其它的办法。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">内核体系结构初始</H2>< 0cm 0cm 0pt">图<FONT face="Times New Roman">3.1</FONT>是一种类<FONT face="Times New Roman">Unix</FONT>操作系统的相当标准的视图,实际上,更细致的来说,该图能够说明所有期望具有平台无关特性的操作系统。它着重强调了内核的下面两个特性:</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l8 level1 lfo2; tab-stops: list 21.25pt">l         内核将应用程序和硬件分离开来。</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l8 level1 lfo2; tab-stops: list 21.25pt">l         部分内核是体系结构和硬件特有的,而部分内核则是可移植的。</P>< 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><wrapblock><v:shapetype><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path connecttype="rect" gradientshapeok="t" extrusionok="f"></v:path><lock aspectratio="t" v:ext="edit"></lock></v:shapetype><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all></P>< 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><p><FONT face="Times New Roman"> </FONT></p></P>< 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">3.1 </FONT>内核体系结构基本结构图</P>< 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt">第一点我们在前面章节中已经讨论清楚了,在这里没有必要重复说明。第二点,也就是与体系结构无关和与体系结构相关代码的内容对于我们的讨论比较有意义。内核通过使用与处理用户应用程序相同的技巧来实现部分可移植性。这也就是说,如同内核把用户应用程序和硬件分离一样,部分内核将会因为与硬件的联系而同其它内核分离开来。通过这种分离,用户应用程序和部分内核都成为可移植的。</P><P 0cm 0cm 0pt">虽然这通常并不能够使得内核本身更清楚,但是源程序代码的体系结构无关部分通常定义了与低层,也就是体系结构相关部分(或假定)的接口。作为一个简单的例子,内存管理代码中的体系结构无关部分假定只要包含特定的头文件就可以获得合适的<FONT face="Times New Roman"><B normal">PAGE_SIZE</B> </FONT>宏(参看<FONT face="Times New Roman">10791</FONT>行)的定义,该宏定义了系统的内存管理硬件用于分割系统地址空间的内存块的大小(参看第<FONT face="Times New Roman">8</FONT>章)。体系结构无关代码并不关心宏的确切定义,而把这些问题都留给体系结构相关代码去处理。(顺便一提,这比到处使用<B normal"><FONT face="Times New Roman">#ifdef/#endif</FONT></B>程序块来定义平台相关代码要清晰易懂得多。)</P><P 0cm 0cm 0pt">这样,内核向新的体系结构的移植就转变成为确认这些特性以及在新内核上实现它们的问题。</P><P 0cm 0cm 0pt">另外,用户应用程序的可移植性还可以通过它和内核的中间层次――标准<FONT face="Times New Roman">C</FONT>库(<FONT face="Times New Roman">libc</FONT>)――的协助来实现。应用程序实际上从不和内核直接通讯,而只通过<FONT face="Times New Roman">libc</FONT>来实现。图<FONT face="Times New Roman">3.1</FONT>中显示应用程序和内核直接通讯的唯一原因在于它们能够和内核通讯。虽然在实际上应用程序并不同内核直接通讯――这样做是毫无意义的。通过直接和内核通讯所能处理的问题都可以通过使用<FONT face="Times New Roman">libc</FONT>实现,而且更容易。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">Libc</FONT>和内核通讯的方式是体系结构相关的(这和图中有一点矛盾),<FONT face="Times New Roman">libc</FONT>负责将用户代码从实现细节中解放出来。有趣的是,甚至大部分<FONT face="Times New Roman">libc</FONT>都不了解这些细节。大部分的<FONT face="Times New Roman">libc</FONT>,例如<B normal"><FONT face="Times New Roman">atoi</FONT></B>和<B normal"><FONT face="Times New Roman">rand</FONT></B>的实现,都根本不需要和内核进行通讯。剩余部分的大部分<FONT face="Times New Roman">libc</FONT>,例如<B normal"><FONT face="Times New Roman">printf</FONT></B>函数,在涉及到内核之前或之后就已经处理大量的工作。(<B normal"><FONT face="Times New Roman">printf</FONT></B>必需首先解释格式化字符串,分析相应参数,设定打印方法,在临时内部缓冲器中记录预期输出。直到此时它才调用底层系统调用<B normal"><FONT face="Times New Roman">write</FONT></B>来实际打印该缓冲区。)其它部分的<FONT face="Times New Roman">libc </FONT>则只是相应系统调用的简单代理。因而一旦发生函数调用时,它们会立即调用内核相应函数以完成主要工作。在最低层次上,大部分<FONT face="Times New Roman">libc</FONT>通过单通道同内核进行交流,而它们所使用的机制将第<FONT face="Times New Roman">5</FONT>章中进行详细介绍。</P><P 0cm 0cm 0pt">由于这种设计,所有的用户应用程序,甚至大部分的<FONT face="Times New Roman">C</FONT>库,都是通过体系结构无关的方式和内核通讯的。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">内核体系结构的深入了解</H2><P 0cm 0cm 0pt">图<FONT face="Times New Roman">3.2</FONT>显示了内核概念化的一种可能方式。该图和区分内核的体系结构无关和体系结构相关的方法有所不同,它是一种更具有普遍性的结构视图。在“<FONT face="Times New Roman">Kernel</FONT>”框内的本书中有所涉及的内核部分都用括号注明了相应的章节编号。虽然有关对称多处理(<FONT face="Times New Roman">SMP</FONT>)的支持也属于本书的范围,但是在这里我们却没有标明章号。部分原因在于相当多的<FONT face="Times New Roman">SMP</FONT>代码广泛地分布于整个内核中<B normal">,</B>因此很难将它与某一个模块联系起来。同样的道理,对于内核初始化的支持也属于本书的范围,但是也没有标明章号。这样做仅仅是因为从设计的观点上看,该问题并不重要。最后,虽然在图中我们将第<FONT face="Times New Roman">6</FONT>章和“进程间通讯”框联系在一起,但是该章只涉及一部分进程间通讯的内容。</P><P 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><wrapblock><v:shape><FONT face="Times New Roman"><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></FONT></v:shape></wrapblock><BR vglayout" clear=all></P><P 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">3.2 </FONT>详细的内核体系结构图</P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt">进程和内核的交互通常需要通过如下步骤:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">1.         </FONT>用户应用程序调用系统调用,通常是使用<FONT face="Times New Roman">libc</FONT>。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">2.         </FONT>该调用被内核的<B normal"><FONT face="Times New Roman">system_call</FONT></B>函数截获(第<FONT face="Times New Roman">5</FONT>章,<FONT face="Times New Roman">171</FONT>行),此后该函数会将调用请求转发给另外的执行请求的内核函数。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">3.         </FONT>该函数随即和相关内部代码模块建立通讯,而这些模块还可能需要和其它的代码模块或者底层硬件通讯。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">4.         </FONT>结果按照同样的路径依次返回。</P><P 0cm 0cm 0pt">然而,并不是所有内核和进程间的交互都是由进程发起的。内核有时也会自行决定同哪个进程交互,例如通过释放信号量或者简单的采用直接杀死进程的方法终止该进程的执行(如当进程用完所有可用的<FONT face="Times New Roman">CPU</FONT>时间片),以便使其它进程有机会运行。这些交互过程在该图中并没有表示,主要是因为它们通常都只是内核对自己的内部数据结构的修改(信号量传递对于这种规则来说是一个例外)。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">是层次化(Layered),模块化(Modular)还是其它?</H2><P 0cm 0cm 0pt">解决复杂性的所有方法都基于一个基本原理:问题分解和各个击破。也就是说,都是把大型的、难以解决的问题(或系统)分解成一定数量的复杂度较低的子问题(或子系统),再根据需要重复这一过程直到每一部分都小到可以解决为止,而各种方法只是这种原理的一些不同运用而已。</P><P 0cm 0cm 0pt">计算机科学中有三种经典的方法比较适合于构建大型系统(我首先必须说明的是,这些定义都是经过我深思熟虑的讨论对象)。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo4; tab-stops: list 21.25pt">l         层次(<FONT face="Times New Roman">Layer</FONT>)――将解决方案分解成若干部分,在这些部分中存在一个问题域的最底层,它为上层的抽象层次较高的工作提供基础。较高层建立在其低层基础之上。<FONT face="Times New Roman">OSI</FONT>和<FONT face="Times New Roman">TCP/IP</FONT>协议堆栈是众所周知的层次化软件设计的成功的例子。操作系统设计的层次化解决方案可能会包含一个可以直接和硬件通讯的层次、然后在其上提供为更高层提供抽象支持的层次。这样更高层就可以对磁盘、网卡等硬件进行访问,而并不需要了解这些设备的具体细节。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">层次化设计的一个特征是要逐步构建符号集(<FONT face="Times New Roman">vocabulary</FONT>)。随着层次的升高,符号集的功能将越来越强大。层次化设计的另外一个特征是完全可以在对其上下层透明的条件下替换某一层次。在最理想的情况下,移植层次化的操作系统只需要重写最低层的代码。纯层次化模型实现的执行速度可能会很慢,因为高层必须(间接的)通过调用一系列连续的低层才能处理完自己的任务――<FONT face="Times New Roman">N</FONT>层调用<FONT face="Times New Roman">N-1</FONT>层,<FONT face="Times New Roman">N-1</FONT>层调用<FONT face="Times New Roman">N-2</FONT>层,等等,直到实际的工作在<FONT face="Times New Roman">0</FONT>层被处理完成。接着,结果当然是通过同样的路径反向传递回来。因此,层次化设计通常会包含对某些高层直接和某些低层通讯的支持;这样虽然提高了速度,但是却使得各个层次的替换工作更加困难(因为不止一个高层会直接依赖于这个你所希望进行替换的层次)。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo5; tab-stops: list 21.25pt">l         模块(<FONT face="Times New Roman">Module</FONT>)――模块将具体的一部分功能块隐藏在抽象的接口背后。模块的最大特点是将接口和其实现分离开来,这样就能够保证一个模块可以在不影响其它模块的情况下进行改变。这样也将模块之间的依赖关系仅仅限定于接口。模块的范围是试图反映求解域内一些方面的自然的概念性界限。纯模块化的操作系统因而就可能有一个磁盘子系统模块,一个内存管理子系统模块,等等。纯模块化和纯层次化的操作系统之间的主要区别是一个可以由其它模块自由调用,模块间没有上层和下层的概念。(从这个意义上来说,模块是广义的层次。按照纯粹的观点,层次是最多可供一个其它模块调用的模块,这个模块也就是它的直接上层模块。)</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo5; tab-stops: list 21.25pt">l         对象(<FONT face="Times New Roman">Object</FONT>)――对象和模块不同,因为对于初学者来说它们具有不同的问题考虑方式,实现的方法也可能各自独立。但是,就我们当前的目的来说,对象不过是结构化使用模块的方法。组件(<FONT face="Times New Roman">Component</FONT>)作为对象思想的进一步改进目前还没有在操作系统设计中广泛使用。即便如此(按照我们的观点),我们也没有足够的理由将其和模块划分在不同的范畴中。</P><P 0cm 0cm 0pt">图<FONT face="Times New Roman">3.1</FONT>强调了内核的层次化的视图,而且是体系结构无关层次位于体系结构相关层次之上。(更为精确的视图是在顶层增加一个附加的体系结构相关的层次。这是因为系统调用接口位于应用程序和内核之间,而且是体系结构相关的。)图<FONT face="Times New Roman">3.2</FONT>着重强调了更加模块化的内核视图。</P><P 0cm 0cm 0pt">从合理的表述层次上看,这两种观点都是正确的。但也可以说这两种观点都是错误的。我可以用大量的图片向你证明内核是遵从所有你所能够指出的设计原则集合的,因为它就是从众多思想中抽取出来的。简单说来,事实是<FONT face="Times New Roman">Linux</FONT>内核既不是严格层次化的,也不是严格模块化的,也不是严格意义上的任何类型,而是以实用为主要依据的。(实际上,如果要用一个词来概括<FONT face="Times New Roman">Linux</FONT>从设计到实现的所有特点,那么实用就是最确切的。)也许最保守的观点是内核的实现是模块化的,虽然这些模块有时会为了追求速度而有意跨越模块的界限。</P><P 0cm 0cm 0pt">这样,<FONT face="Times New Roman">Linux</FONT>的设计同时兼顾了理论和实际。<FONT face="Times New Roman">Linux</FONT>并没有忽视设计方法;相反,在<FONT face="Times New Roman">Linux</FONT>的开发基本思想中,设计方法的作用就像是编译器:它是完成工作的有力工具。选择一个基本的设计原则(例如对象)并完全使用这种原则,不允许有任何例外,这对于测试该原则的限制,或者构建以说明这些方法为目的的教学系统来说都是一个不错的方法。但是如果要用它来达到<FONT face="Times New Roman">Linux</FONT>的设计目标则会引起许多问题。而且<FONT face="Times New Roman">Linux</FONT>的设计目标中也并不包括要使内核成为一个完全纯化的系统。<FONT face="Times New Roman">Linux</FONT>开发者为了达到设计目标宁愿违背妨碍目标实现的原则。</P><P 0cm 0cm 0pt">实际上,如果对于<FONT face="Times New Roman">Linux</FONT>来说是正确的,那么它们对于所有最成功的设计来说都是正确的。最成功、应用最广泛的实际系统必然是实用的系统。有些开发人员试图寻找功能强大的可以解决所有问题的特殊方法。他们一旦找到了这种方法,所有的问题就都迎刃而解了。像<FONT face="Times New Roman">Linux</FONT>内核一样的成功设计通常需要为系统的不同部分和描述上的不同层次使用不同的方法。这样做的结果可能不是很清晰,也不是很纯粹,但是这种混合产物比同等功能的纯粹系统要强大而且优秀得多。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5><FONT face="Times New Roman">Linux</FONT>大部分都是单内核的</FONT></H3><P 0cm 0cm 0pt">操作系统内核可能是微内核,也可能是单内核(后者有时称之为宏内核<FONT face="Times New Roman">Macrokernel</FONT>)。按照类似封装的形式,这些术语定义如下:</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l10 level1 lfo6; tab-stops: list 21.25pt">l         微内核(<FONT face="Times New Roman">Microkernel kernel</FONT>)――在微内核中,大部分内核都作为独立的进程在特权状态下运行,它们通过消息传递进行通讯。在典型情况下,每个概念模块都有一个进程。因此,如果在设计中有一个系统调用模块,那么就必然有一个相应的进程来接收系统调用,并和能够执行系统调用的其它进程(或模块)通讯以完成所需任务。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">在这些设计中,微内核部分经常只不过是一个消息转发站:当系统调用模块要给文件系统模块发送消息时,消息直接通过内核转发。这种方式有助于实现模块间的隔离。(某些时候,模块也可以直接给其它模块传递消息。)在一些微内核的设计中,更多的功能,如<FONT face="Times New Roman">I/O</FONT>等,也都被封装在内核中了。但是最根本的思想还是要保持微内核尽量小,这样只需要把微内核本身进行移植就可以完成将整个内核移植到新的平台上。其它模块都只依赖于微内核或其它模块,并不直接直接依赖硬件。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">微内核设计的一个优点是在不影响系统其它部分的情况下,用更高效的实现代替现有文件系统模块的工作将会更加容易。我们甚至可以在系统运行时将开发出的新系统模块或者需要替换现有模块的模块直接而且迅速的加入系统。另外一个优点是不需要的模块将不会被加载到内存中,因此微内核就可以更有效的利用内存。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l9 level1 lfo7; tab-stops: list 21.25pt">l         单内核(<FONT face="Times New Roman">Monolithic kernel</FONT>)――单内核是一个很大的进程。它的内部又可以被分为若干模块(或者是层次或其它)。但是在运行的时候,它是一个独立的二进制大映象。其模块间的通讯是通过直接调用其它模块中的函数实现的,而不是消息传递。</P><P 0cm 0cm 0pt">单内核的支持者声称微内核的消息传递开销引起了效率的损失。微内核的支持者则认为因此而增加的内核设计的灵活性和可维护性可以弥补任何损失。</P><P 0cm 0cm 0pt">我并不想讨论这些问题,但必须说明非常有趣的一点是,这种争论经常会令人想到前几年<FONT face="Times New Roman">CPU</FONT>领域中<FONT face="Times New Roman">RISC</FONT>和<FONT face="Times New Roman">CISC</FONT>的斗争。现代的成功<FONT face="Times New Roman">CPU</FONT>设计中包含了所有这两种技术,就像<FONT face="Times New Roman">Linux</FONT>内核是微内核和单一内核的混合产物一样。<FONT face="Times New Roman">Linux</FONT>内核基本上是单一的,但是它并不是一个纯粹的集成内核。前面一章所介绍的内核模块系统将微内核的许多优点引入到<FONT face="Times New Roman">Linux</FONT>的单内核设计中。(顺便提一下,我考虑过一种有趣的情况,就是<FONT face="Times New Roman">Linux</FONT>的内核模块系统可以将系统内核转化成为简单的不传递消息的微内核设计。虽然我并不赞成,但是它仍然是一个有趣的想法。)</P><P 0cm 0cm 0pt">为什么<FONT face="Times New Roman">Linux</FONT>必然是单内核的呢?一个方面是历史的原因:在<FONT face="Times New Roman">Linus</FONT>的观点看来,通过把内核以单一的方式进行组织并在最初始的空间中运行是相当容易的事情。这种决策避免了有关消息传递体系结构,计算模块装载方式等方面的相关工作。(内核模块系统在随后的几年中又进行了不断地改进。)</P><P 0cm 0cm 0pt">另外一个原因是充足的开发时间的结果。<FONT face="Times New Roman">Linux</FONT>既没有开发时间的限制,也没有深受市场压力的发行进度。<FONT face="Times New Roman"> </FONT>所有的限制只有并不过分的对内核的修改与扩充。内核的单一设计在内部实现了充分的模块化,在这种条件下的修改或增加都并不怎么困难。而且问题还在于没有必要为了追求尚未证实的可维护性的微小增长而重写<FONT face="Times New Roman">Linux</FONT>的内核。(<FONT face="Times New Roman">Linus</FONT>曾多次特别强调了如下的观点:为了这点利益而损耗速度是不值得的。)后面章节中的部分内容将详细的重新考虑充足开发时间的效果。</P><P 0cm 0cm 0pt">如果<FONT face="Times New Roman">Linux</FONT>是纯微内核设计,那么向其它体系结构上的移植将会比较容易。实际上,有一些微内核,如<FONT face="Times New Roman">Mach</FONT>微内核,就已经成功的证明了这种可移植性的优点。实际的情况是,<FONT face="Times New Roman">Linux</FONT>内核的移植虽然不是很简单,但也绝不是不可能的:大约的数字是,向一个全新的体系结构上的典型的移植工作需要<FONT face="Times New Roman">30,000</FONT>到<FONT face="Times New Roman">60,000</FONT>行代码,再加上不到<FONT face="Times New Roman">20,000</FONT>行的驱动程序代码。(并不是所有的移植都需要新的驱动程序代码。)粗略的计算一下,我估计一个典型的移植平均需要<FONT face="Times New Roman">50,000</FONT>行代码。这对于一个程序员或者最多一个程序小组来说是力所能及的,可以在一年之内完成。虽然这比微内核的移植需要更多的代码,但是<FONT face="Times New Roman">Linux</FONT>的支持者将会提出,这样的<FONT face="Times New Roman">Linux</FONT>内核移植版本比微内核更能够有效的利用底层硬件,因而移植过程中的额外工作是能够从系统性能的提高上得到补偿的。</P><P 0cm 0cm 0pt">这种特殊设计的权衡也不是很轻松就可以达到的,单内核的实现策略公然违背了传统的看法,后者认为微内核是未来发展的趋势。但是由于单一模式(大部分情况下)在<FONT face="Times New Roman">Linux</FONT>中运行状态良好,而且内核移植相对来说比较困难,但没有明显地阻碍程序员团体的工作,他们已经热情高涨地把内核成功的移植到了现存的大部分实际系统中,更不用说类似掌上型电脑的一些看起来很不实际的目标了。只要<FONT face="Times New Roman">Linux</FONT>的众多特点仍然值得移植,新的移植版本就会不断涌现。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">设计和实现的关系</H2><P 0cm 0cm 0pt">接下来的部分将介绍一些内核设计和实现之间的关系。本部分最重要的内容是对于内核源程序目录结构的概述,这一点随后就会提到。本章最后以实现中体系结构无关代码和体系结构相关代码的相对大小的估算作为总结。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>内核源程序目录结构</FONT></H3><P 0cm 0cm 0pt">按照惯例,内核源程序代码安装在<FONT face="Times New Roman">/usr/src/linux</FONT>目录下。在该目录下还有几个其它目录,每一个都代表一个特定的内核功能性子集(或者非常粗略的说是高层代码模块)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">Documentation</H4><P 0cm 0cm 0pt">这个目录下面没有内核代码,只有一套有用的文档。但是这些文档的质量不一。有一部分内核文档,例如文件系统,在该目录下有相当优秀而且相当完整的文档;而另外一部分内核,例如进程调度,则根本就没有文档。但是在这里你可以不时的发现自己所最需要的东西。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">arch</H4><P 0cm 0cm 0pt"><FONT face="Times New Roman">arch</FONT>目录下的所有子目录中都是体系结构相关的代码。每个体系结构特有的子目录下都又至少包含三个子目录:<FONT face="Times New Roman">kernel</FONT>,存放支持体系结构特有的诸如信号量处理和<FONT face="Times New Roman">SMP</FONT>之类特征的实现;<FONT face="Times New Roman">lib</FONT>,存放高速的体系结构特有的诸如<B normal"><FONT face="Times New Roman">strlen</FONT></B>和<B normal"><FONT face="Times New Roman">memcpy</FONT></B>之类的通用函数的实现;以及<FONT face="Times New Roman">mm</FONT>,存放体系结构特有的内存管理程序的实现。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman"> </FONT>除了这三个子目录以外,大多数体系结构在必要的情况下还都有一个<FONT face="Times New Roman">boot</FONT>子目录,该目录中包含有在这种平台上启动内核所使用的部分或全部平台特有代码。这些启动代码中的部分或全部也可以在平台特有的内核目录下找到。</P><P 0cm 0cm 0pt">最后,大部分体系结构所特有的目录还可以根据需要包含了供附加特性或改进的组织使用的其它子目录。例如,<FONT face="Times New Roman">i386</FONT>目录包含一个<FONT face="Times New Roman">math-emu</FONT>子目录,其中包括了在缺少数学协处理器(<FONT face="Times New Roman">FPU</FONT>)的<FONT face="Times New Roman">CPU</FONT>上运行模拟<FONT face="Times New Roman">FPU</FONT>的代码。作为另外一个例子,<FONT face="Times New Roman">m68k</FONT>移植版本中为每一个该移植版本所支持的基于<FONT face="Times New Roman">680x0</FONT>的机器建立了一个子目录,从而这些机器所特有的代码都有一个自然的根目录。</P><P 0cm 0cm 0pt">下面几个是<FONT face="Times New Roman">arch</FONT>目录下的子目录:</P><P 0cm 0cm 0pt 26.5pt; TEXT-INDENT: -21.25pt; mso-list: l6 level1 lfo8; tab-stops: list 26.5pt">l         <FONT face="Times New Roman">arch/alpha/</FONT>――<FONT face="Times New Roman">Linux</FONT>内核到基于<FONT face="Times New Roman">DEC Alphs CPU</FONT>工作站的移植。</P><P 0cm 0cm 0pt 26.5pt; TEXT-INDENT: -21.25pt; mso-list: l6 level1 lfo8; tab-stops: list 26.5pt">l         <FONT face="Times New Roman">arch/arm/</FONT>――<FONT face="Times New Roman">Linux</FONT>到<FONT face="Times New Roman">ARM</FONT>系列<FONT face="Times New Roman">CPU </FONT>的移植,该类<FONT face="Times New Roman">CPU</FONT>主要用于诸如<FONT face="Times New Roman">Corel</FONT>的<FONT face="Times New Roman">NetWinder</FONT>和<FONT face="Times New Roman">Acorn RiscPC</FONT>之类的机器。</P><P 0cm 0cm 0pt 26.5pt; TEXT-INDENT: -21.25pt; mso-list: l6 level1 lfo8; tab-stops: list 26.5pt">l         <FONT face="Times New Roman">arch/i386/</FONT>――最接近于<FONT face="Times New Roman">Linux</FONT>内核原始平台或标准平台。这是为<FONT face="Times New Roman">Intel</FONT>的<FONT face="Times New Roman">80386</FONT>结构使用的,当然包括对同一系列后来的<FONT face="Times New Roman">CPU</FONT>(<FONT face="Times New Roman">80486</FONT>,<FONT face="Times New Roman">Pentium</FONT>等等)的支持。它还包括了对<FONT face="Times New Roman">AMD</FONT>,<FONT face="Times New Roman">Cyrix</FONT>和<FONT face="Times New Roman">IDT</FONT>等公司的一些兼容产品的支持。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">本书基本上将这种体系结构称为“<FONT face="Times New Roman">x86</FONT>”。即使这样,严格说来“<FONT face="Times New Roman">x86</FONT>”对于我们的目标来说还是要求得过于宽泛。早期的<FONT face="Times New Roman">Intel CPU</FONT>,例如<FONT face="Times New Roman">80286</FONT>,并没有包括<FONT face="Times New Roman">Linux</FONT>运行所需的所有特性。对于这些机器,<FONT face="Times New Roman">Linux</FONT>也没有正式的支持版本。(顺便提一下,<FONT face="Times New Roman">Linux</FONT>对这种<FONT face="Times New Roman">CPU</FONT>的独立移植版本是存在的,不过它在功能上有部分损失。)当本书中提到“<FONT face="Times New Roman">x86</FONT>平台”时,通常是指<FONT face="Times New Roman">80386</FONT>或更新的<FONT face="Times New Roman">CPU</FONT>。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l7 level1 lfo9; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">arch/m68k/</FONT>――到<FONT face="Times New Roman">Motorola</FONT>的<FONT face="Times New Roman">680x0 CPU</FONT>系列的移植。该版本可以提供对基于从<FONT face="Times New Roman">68020</FONT>(只要它同内存管理单元(<FONT face="Times New Roman">MMU</FONT>)<FONT face="Times New Roman">68851</FONT>一起使用)到<FONT face="Times New Roman">68060</FONT>的一切机器的支持。很多公司在他们的产品中使用<FONT face="Times New Roman">680x0</FONT>系列芯片,例如<FONT face="Times New Roman">Commodore</FONT>(现在是<FONT face="Times New Roman">Gateway</FONT>)的<FONT face="Times New Roman">Amiga</FONT>,<FONT face="Times New Roman">Apple</FONT>的<FONT face="Times New Roman">Macintosh</FONT>,<FONT face="Times New Roman">Atari ST</FONT>,等等。这些老机器中的很多现在正充当可靠的<FONT face="Times New Roman">Linux</FONT>工作站。另外,到<FONT face="Times New Roman">NeXT</FONT>工作站和<FONT face="Times New Roman">SUN 3</FONT>工作站的移植也正在进行中。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l7 level1 lfo9; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">arch/mips/</FONT>――到<FONT face="Times New Roman">MIPS</FONT>的<FONT face="Times New Roman">CPU</FONT>系列的移植。虽然有其它几个厂商也使用<FONT face="Times New Roman">MIPS</FONT>开发了一些系统,但是基于这种<FONT face="Times New Roman">CPU</FONT>的最出名的机器是<FONT face="Times New Roman">Silicon Graphics</FONT>(<FONT face="Times New Roman">SGI</FONT>)工作站。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l7 level1 lfo9; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">arch/ppc/</FONT>――到<FONT face="Times New Roman">Motorola/IBM</FONT>的<FONT face="Times New Roman">PowerPC</FONT>系列<FONT face="Times New Roman">CPU</FONT>的移植。这包括对基于<FONT face="Times New Roman">PowerPC</FONT>的<FONT face="Times New Roman">Macintosh</FONT>和<FONT face="Times New Roman">Amiga</FONT>以及<FONT face="Times New Roman">BeBox</FONT>、<FONT face="Times New Roman">IBM</FONT>的<FONT face="Times New Roman">RS/6000</FONT>等其它一些机器的支持。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l7 level1 lfo9; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">arch/sparc/</FONT>――到<FONT face="Times New Roman">32</FONT>位<FONT face="Times New Roman">SPARC CPU</FONT>的移植。这包括对从<FONT face="Times New Roman">Sun SPARC 1</FONT>到<FONT face="Times New Roman">SPARC 20</FONT>的全部支持。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l7 level1 lfo9; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">arch/sparc64/</FONT>——到基于<FONT face="Times New Roman">64</FONT>位<FONT face="Times New Roman">SPARC CPU</FONT>(<FONT face="Times New Roman">UltraSPARC</FONT>系)系统的移植。这里所能够支持的机器包括<FONT face="Times New Roman">Sun</FONT>的<FONT face="Times New Roman">Ultra 1</FONT>,<FONT face="Times New Roman">Ultra 2</FONT>和更高配置的机器,直到<FONT face="Times New Roman">Sun</FONT>的最新产品<FONT face="Times New Roman">Enterprise 10000</FONT>。注意<FONT face="Times New Roman">32</FONT>位和<FONT face="Times New Roman">64</FONT>位的<FONT face="Times New Roman">SPARC</FONT>的移植版本正在合并中。</P><P 0cm 0cm 0pt">不幸的是,本书必须将注意力集中在<FONT face="Times New Roman">x86</FONT>上,因此只应用到了<FONT face="Times New Roman">arch/i386/</FONT>目录下的代码,而其它体系结构所特有的代码将不再涉及了。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">drivers</H4><P 0cm 0cm 0pt">这个目录是内核中非常大的一块。实际上,<FONT face="Times New Roman">drivers</FONT>目录下包含的代码占整个内核发行版本代码的一半以上。它包括显卡、网卡、<FONT face="Times New Roman">SCSI</FONT>适配器、软盘驱动器,<FONT face="Times New Roman">PCI</FONT>设备和其它任何你可以说出的<FONT face="Times New Roman">Linux</FONT>支持的外围设备的软件驱动程序。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">Drivers</FONT>目录下的一些子目录是平台特有的,例如,<FONT face="Times New Roman">zorro</FONT>子目录中包含有和<FONT face="Times New Roman">Zorro</FONT>总线通讯的代码。而<FONT face="Times New Roman">Zorro</FONT>总线只在<FONT face="Times New Roman">Amiga</FONT>中使用过,因此这些代码必然是<FONT face="Times New Roman">Amiga</FONT>特有的。而其它一些子目录,例如<FONT face="Times New Roman">pci</FONT>子目录,则至少是部分平台无关的。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">fs</H4><P 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>支持的所有文件系统在<FONT face="Times New Roman">fs</FONT>目录下面都有一个对应的子目录。一个文件系统(<FONT face="Times New Roman">file system</FONT>)是存储设备和需要访问存储设备的进程之间的媒介。</P><P 0cm 0cm 0pt">文件系统可能是本地的物理上可访问的存储设备,例如硬盘或<FONT face="Times New Roman">CD-ROM</FONT>驱动器;在这两种情况下将分别使用<FONT face="Times New Roman">ext2</FONT>和<FONT face="Times New Roman">isofs</FONT>文件系统。文件系统也可能是可以通过网络访问的存储设备;这种情况下使用的文件系统是<FONT face="Times New Roman">NFS</FONT>。</P><P 0cm 0cm 0pt">还有一些伪文件系统,例如<FONT face="Times New Roman">proc</FONT>文件系统,可以以伪文件的形式提供其它信息(例如,在<FONT face="Times New Roman">proc</FONT>的情况下是提供内核的内部变量和数据结构)。虽然在底层并没有实际的存储设备与这些文件系统相对应,但是进程可以像有实际存储设备一样处理(<FONT face="Times New Roman">NFS</FONT>也可以作为伪文件系统来使用)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">include</H4><P 0cm 0cm 0pt">这个目录包含了<FONT face="Times New Roman">Linux</FONT>源程序树中大部分的包含(<FONT face="Times New Roman">.h</FONT>)文件。这些文件按照下面的子目录进行分组:</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo10; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">include/asm-*/</FONT>――这样的子目录有多个,每一个都对应着一个<FONT face="Times New Roman">arch</FONT>的子目录,例如<FONT face="Times New Roman">include/asm-alpha</FONT>,<FONT face="Times New Roman">include/asm-arm</FONT>,<FONT face="Times New Roman">include/asm-i386</FONT>等等。每个目录下的文件中包含了支持给定体系结构所必须的预处理器宏和短小的内联函数。这些内联函数很多都是全部或部分地使用汇编语言实现的,而且在<FONT face="Times New Roman">C</FONT>或者汇编代码中都会应用到这些文件。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">当编译内核时,系统将建立一个从<FONT face="Times New Roman">include/asm</FONT>到目标体系结构特有的目录的符号链接。结果是体系结构无关内核源程序代码可以使用如下形式的代码来实现所需功能:</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">#include &lt;asm/some-file&gt;</FONT></P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: 0cm">这样就能够将适当地体系结构特有的文件包含(<B normal"><FONT face="Times New Roman">#include</FONT></B>)进来。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo11; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">include/linux/</FONT>――内核和用户应用程序请求特定内核服务时所使用的常量和数据结构在头文件中定义,而该目录中就包含了这些头文件。这些文件大都是平台独立的。这个目录被全部复制(更多的情况是链接)到<FONT face="Times New Roman">/usr/inlude/linux</FONT>下。这样用户应用程序就可以使用<FONT face="Times New Roman">#include</FONT>包含这些头文件,而且能够保证所包含进来的头文件的内容和内核中的定义一致。第<FONT face="Times New Roman">9</FONT>章将会给出有关的一个样例。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo11; tab-stops: list 21.25pt">l         对这些文件的移植只有对于内核来说才是必须的,对用户应用程序则没有必要。移植工作可以按照如下的方式封装处理:</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">/* …  Stuff for user apps and kernel … */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#ifdef  __KERNEL__</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       /* … Stuff for kernel only … */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #endif  /* __KERNEL__ */</FONT></P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l11 level1 lfo12; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">include/net/</FONT>――这个目录供与网络子系统有关的头文件使用。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l11 level1 lfo12; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">include/scsi/</FONT>――这个目录供与<FONT face="Times New Roman">SCSI</FONT>控制器和<FONT face="Times New Roman">SCSI</FONT>设备有关的头文件使用。</P><P 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l11 level1 lfo12; tab-stops: list 21.25pt">l         <FONT face="Times New Roman">include/video/</FONT>――这个目录供与显卡和帧显示缓存有关的头文件使用。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">init</H4><P 0cm 0cm 0pt">这个目录下面的两个文件中比较重要的一个是<FONT face="Times New Roman">main.c</FONT>,它包含了大部分协调内核初始化的代码。第<FONT face="Times New Roman">4</FONT>章将详细介绍这部分代码。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">ipc</H4><P 0cm 0cm 0pt">这个目录下的文件实现了<FONT face="Times New Roman">System V</FONT>的进程间通讯(<FONT face="Times New Roman">IPC</FONT>)。在第<FONT face="Times New Roman">9</FONT>章中将会对它们进行详细介绍。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">kernel</H4><P 0cm 0cm 0pt">这个目录中包含了<FONT face="Times New Roman">Linux</FONT>中最重要的部分:实现平台独立的基本功能。这部分内容包括进程调度(<FONT face="Times New Roman">kernel/sched.c</FONT>)以及创建和撤销进程的代码(<FONT face="Times New Roman">kernel/fork.c</FONT>和<FONT face="Times New Roman">kernel/exit.c</FONT>);以上所有的以及其它部分内容将在第<FONT face="Times New Roman">7</FONT>章中有所涉及。但是我并不想给你留下这样的印象:需要了解的内容都在这个目录下。实际上在其它目录下也有很多重要的内容。但是,不管怎样说,最重要部分的代码是在这个目录下的。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">lib</H4><P 0cm 0cm 0pt"><FONT face="Times New Roman">lib</FONT>目录包含两部分的内容。<FONT face="Times New Roman">lib/inflate.c</FONT>中的函数能够在系统启动时展开经过压缩的内核(请参看第<FONT face="Times New Roman">4</FONT>章)。<FONT face="Times New Roman">lib</FONT>目录下剩余的其它文件实现一个标准<FONT face="Times New Roman">C</FONT>库的有用子集。这些实现的焦点集中在字符串和内存操作的函数(<B normal"><FONT face="Times New Roman">strlen</FONT></B>,<B normal"><FONT face="Times New Roman">mmcpy</FONT></B>和其它类似的函数)以及有关<B normal"><FONT face="Times New Roman">sprintf</FONT></B>和<B normal"><FONT face="Times New Roman">atoi</FONT></B>的系列函数上。</P><P 0cm 0cm 0pt">这些文件都是使用<FONT face="Times New Roman">C</FONT>语言编写的,因此在新的内核移植版本中可以立即使用这些文件。正如本章前面部分说明的那样,一些移植提供了它们独有的高速的函数版本,这些函数通常是经过手工调整过的汇编程序,在移植后的系统使用这些函数来代替原来的通用函数。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">mm</H4><P 0cm 0cm 0pt">该目录包含了体系结构无关的内存管理代码。正如我们前面说明的那样,为每个平台实现最低层的原语的体系结构特有的内存管理程序是存储在<FONT face="Times New Roman">arch/platform/mm</FONT>中的。大部分平台独立和<FONT face="Times New Roman">x86</FONT>特有的内存管理代码将在第<FONT face="Times New Roman">8</FONT>章中介绍。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">net</H4><P 0cm 0cm 0pt">这个目录包含了<FONT face="Times New Roman">Linux</FONT>应用的网络协议代码,例如<FONT face="Times New Roman">AppleTalk</FONT>,<FONT face="Times New Roman">TCP/IP</FONT>,<FONT face="Times New Roman">IPX</FONT>等等。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">scripts</H4><P 0cm 0cm 0pt">该目录下没有内核代码,它包含了用来配置内核的脚本。当运行<B normal"><FONT face="Times New Roman">make menuconfig</FONT></B>或者<B normal"><FONT face="Times New Roman">make xconfig</FONT></B>之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>体系结构相关和体系结构无关的代码</FONT></H3><P 0cm 0cm 0pt">现在我们来估计一下体系结构相关和体系结构无关代码的相对大小。我们首先给出一些数字。完整的<st1:chsdate w:st="on" IsROCDate="False" IsLunarDate="False" Day="30" Month="12" Year="1899"><FONT face="Times New Roman">2.2.5</FONT></st1:chsdate>的内核总共有<FONT face="Times New Roman">1,725,645</FONT>行代码。(顺便一提,请注意本书只包含了<FONT face="Times New Roman">39,000</FONT>行代码,但是我们仍然努力涵盖了相当部分的核心函数。)其中一共有<FONT face="Times New Roman">392,884</FONT>行代码在体系结构特有的目录之内,也就是<FONT face="Times New Roman">arch/*</FONT>和<FONT face="Times New Roman">include/asm-*</FONT>下面。我估计还有超过<FONT face="Times New Roman">64,000</FONT>行的代码是仅供一种体系结构专用的驱动程序。这意味着大约<FONT face="Times New Roman">26%</FONT>的代码是专用于特定体系结构的。</P><P 0cm 0cm 0pt">但是,对于单一一种体系结构,体系结构相关代码比例相对较小。不妨理想一点,如果某种体系结构所需要的特有代码约有<FONT face="Times New Roman">50,000</FONT>行,而体系结构无关代码则大约有<FONT face="Times New Roman">1,250,000</FONT>行,那么体系结构相关代码大概只占到<FONT face="Times New Roman">4%</FONT>。当然,在特定的一个内核中,并不是所有这些体系结构无关代码都会被用到,因此体系结构相关代码在特定内核中所占的比重与内核的配置有关。但是不管怎样,很显然大部分内核代码是平台独立的。</P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm">第<FONT face="Times New Roman">4</FONT>章<FONT face="Times New Roman">  </FONT>系统初始化</H1>< 0cm 0cm 0pt">当你想要运行程序时,你需要把程序的文件名敲入<FONT face="Times New Roman">shell</FONT>――或者更为流行的,在如<FONT face="Times New Roman">GNOME</FONT>或者<FONT face="Times New Roman">KDE</FONT>等之类桌面环境中点击相应的图标――这样就能将其装载进内核并运行。但是,首先必须有其它的软件来装载并运行内核;这通常是诸如<FONT face="Times New Roman">LOADLIN</FONT>或者<FONT face="Times New Roman">LILO</FONT>之类的内核引导程序。更进一步,我们还需要其它的软件来装载运行内核引导程序――称之“内核引导程序的引导程序”――而且看起来似乎运行内核引导程序的引导程序也需要内核引导程序的引导程序的引导程序,等等,这个过程是无限的。</P>< 0cm 0cm 0pt">这个无限循环的过程必然最终在某个地方终止,这就是硬件。因此,在最低的层次上,启动系统的第一步是从硬件中获得帮助。该硬件总是运行一些短小的内置程序――软件,但是这些软件是被固化在只读存储器中,存储在已知地址中。因此,在这种情况下就不需要软件引导程序了――它能够运行更大更复杂的程序,直到内核自身装载成功为止。按照这种方式,系统自己的引导过程(<FONT face="Times New Roman">bootstrap</FONT>)会引发系统的启动,当然这只是术语“系统引导(<FONT face="Times New Roman">booting</FONT>)”的一个比喻。虽然不同体系结构的引导过程的具体细节差异很大,但是它们的原则都基本相同。</P>< 0cm 0cm 0pt">前面的工作都完成以后,内核就已经成功装载了。随后内核可以初始化自身以及系统的其它部分。</P>< 0cm 0cm 0pt">本章首先将简单介绍基于<FONT face="Times New Roman">x86 PC</FONT>机的典型自启动方式,接着回顾一下每一步工作在什么时机发生,最后我们还要介绍的是内核的相应部分。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">引导PC机</H2>< 0cm 0cm 0pt">本节简要介绍<FONT face="Times New Roman">x86 PC</FONT>是如何引导的。本节的目的不是让你精通<FONT face="Times New Roman">C</FONT>是怎样引导的――这超出了本书的范围――而是向你展示特定体系结构一般的引导方式,为下文中的内核初始化进行铺垫。</P>< 0cm 0cm 0pt">首先,机器中的每个<FONT face="Times New Roman">CPU</FONT>都要自行初始化,接着可能要用几分之一秒的时间来执行自测试。在多处理器的系统中,这个过程会更复杂些――但是实际上也并不多。在双处理器的<FONT face="Times New Roman">entium</FONT>系统中,一个<FONT face="Times New Roman">CPU</FONT>总是作为主<FONT face="Times New Roman">CPU</FONT>存在,另外一个<FONT face="Times New Roman">CPU</FONT>则是辅<FONT face="Times New Roman">CPU</FONT>。主<FONT face="Times New Roman">CPU</FONT>执行启动过程中的剩余工作,随后内核才会激活辅<FONT face="Times New Roman">CPU</FONT>。在多处理器的<FONT face="Times New Roman">entium Pro</FONT>系统中,<FONT face="Times New Roman">CPU</FONT>必须根据<FONT face="Times New Roman">Intel</FONT>定义的算法“抢夺标志”――来动态决定由哪个<FONT face="Times New Roman">CPU</FONT>启动系统。取得标志的<FONT face="Times New Roman">CPU</FONT>启动系统,随后内核激活其它的<FONT face="Times New Roman">CPU</FONT>。无论是哪种情况,启动程序的剩余部分只与一个<FONT face="Times New Roman">CPU</FONT>有关。这样,在随后的一段时间内,我们可以认为该系统中只有一个<FONT face="Times New Roman">CPU</FONT>是可用的,而不考虑其它的<FONT face="Times New Roman">CPU</FONT>,或者说这些<FONT face="Times New Roman">CPU</FONT>被暂时隐藏了。另一方面,内核还需要明确的激活所有其它的<FONT face="Times New Roman">CPU</FONT>――这一点你可以在本章后续部分看到。</P>< 0cm 0cm 0pt">接下来,<FONT face="Times New Roman">       CPU</FONT>从<FONT face="Times New Roman">0xfffffff0</FONT>单元中取得指令并执行,这个地址非常接近于<FONT face="Times New Roman">32</FONT>位<FONT face="Times New Roman">CPU</FONT>的最后可用的地址。因为大多数<FONT face="Times New Roman">C</FONT>都没有<FONT face="Times New Roman">4GB</FONT>的<FONT face="Times New Roman">RAM</FONT>,所以通常在这个地址上并没有实际内存的。内存硬件可以虚拟使用它。对那些确实有<FONT face="Times New Roman">4GB</FONT>内存的机器来说,它们也只是仅仅损失了供<FONT face="Times New Roman">BIOS</FONT>使用的顶端地址空间末尾的少量内存(实际上<FONT face="Times New Roman">BIOS</FONT>在这里只保留了<FONT face="Times New Roman">64K</FONT>的空间――这种损失在<FONT face="Times New Roman">4GB</FONT>的机器中是可以忽略的)。</P>< 0cm 0cm 0pt">该地址单元中存储的指令是一条跳转指令,这条指令跳转到基本输入输出(<FONT face="Times New Roman">BIOS</FONT>)代码的首部。<FONT face="Times New Roman">BIOS</FONT>内置在主板中,它主要负责控制系统的启动。请注意<FONT face="Times New Roman">CPU</FONT>实际上并不真正关心<FONT face="Times New Roman">BIOS</FONT>是否存在,这样就使得在诸如用户定制的嵌入系统之类的非<FONT face="Times New Roman">C</FONT>体系结构的计算机中使用<FONT face="Times New Roman">Intel</FONT>的<FONT face="Times New Roman">CPU</FONT>成为可能。<FONT face="Times New Roman">CPU</FONT>执行在目标地址中发现的任何指令,在这里使用跳转指令转移到<FONT face="Times New Roman">BIOS</FONT>只是<FONT face="Times New Roman">C</FONT>体系结构的一部分。(实际上,跳转指令自己是<FONT face="Times New Roman">BIOS</FONT>的一部分,但是这不是考虑这个问题的最方便的方法。)</P>< 0cm 0cm 0pt"><FONT face="Times New Roman">BIOS</FONT>使用内置的规则来选择启动设备。通常情况下,这些规则是可以改变的,方法是在启动过程开始时按下一个键(例如,在我的系统中是<FONT face="Times New Roman">Delete</FONT>键)并通过一些菜单选项浏览选择。但是,通常的过程是<FONT face="Times New Roman">BIOS</FONT>首先试图从软盘启动,如果失败了,就再试图从主硬盘上启动。如果又失败了,就再试图从<FONT face="Times New Roman">CD-ROM</FONT>上启动。为了使问题更具体,这里讨论的情况假定是最普通的,也就是启动设备是硬盘。</P>< 0cm 0cm 0pt">从这种启动设备上启动,<FONT face="Times New Roman">BIOS</FONT>读取第一个扇区的信息――首<FONT face="Times New Roman">512</FONT>个字节――称之为主引导记录(<FONT face="Times New Roman">MBR</FONT>)。接下来发生的内容有赖于<FONT face="Times New Roman">Linux</FONT>是怎样在系统上安装的。为使讨论形象具体,我们假定<FONT face="Times New Roman">LILO</FONT>是内核的载入程序。在典型的设置中,<FONT face="Times New Roman">BIOS</FONT>检测<FONT face="Times New Roman">MBR</FONT>中的关键数字(为了确认该数据段的确是<FONT face="Times New Roman">MBR</FONT>)并在<FONT face="Times New Roman">MBR</FONT>中检测引导扇区的位置。这一扇区包含了<FONT face="Times New Roman">LILO</FONT>的开始部分,然后<FONT face="Times New Roman">BIOS</FONT>将其装入内存,开始执行。</P>< 0cm 0cm 0pt">注意我们现在已经实现了从硬件和内置软件的范围到实际软件范围的转变,从有形范围到无形范围,也就是说从你可以接触的部分到不可接触的部分。</P>< 0cm 0cm 0pt">下面就是<FONT face="Times New Roman">LILO</FONT>的责任了。它把自己其余的部分装载进来,在磁盘上找到配置数据,这些数据指明从什么地方可以得到内核,启动时要通过什么选项。<FONT face="Times New Roman">LILO</FONT>接着装载内核到内存并跳转到内核。</P>< 0cm 0cm 0pt">通常,内核以压缩形式存储,只有少量指令足以完成解压缩的任务,也就是自解压可执行文件,是以非压缩形式存储的。因此,内核的下一步工作是自解压缩内核镜像。到这里,内核就已经完成了装载的过程。</P>< 0cm 0cm 0pt">下面是到现在进行的步骤地简要描述:</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 21.25pt 42.25pt"><FONT face="Times New Roman">1.         CPU</FONT>初始化自身,接着在固定位置执行一条指令。</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 21.25pt 42.25pt"><FONT face="Times New Roman">2.         </FONT>这条指令跳转到<FONT face="Times New Roman">BIOS</FONT>中。</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 21.25pt 42.25pt"><FONT face="Times New Roman">3.         BIOS</FONT>找到启动设备并获取<FONT face="Times New Roman">MBR</FONT>,该<FONT face="Times New Roman">MBR</FONT>指向<FONT face="Times New Roman">LILO</FONT>。</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 21.25pt 42.25pt"><FONT face="Times New Roman">4.         BIOS</FONT>装载并把控制权转交给<FONT face="Times New Roman">LILO</FONT>。</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 21.25pt 42.25pt"><FONT face="Times New Roman">5.         LILO</FONT>装载压缩内核。</P>< 0cm 0cm 0pt 21.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 21.25pt 42.25pt"><FONT face="Times New Roman">6.         </FONT>压缩内核自解压并把控制权转交给解压的内核</P>< 0cm 0cm 0pt">正如你所见到的,引导过程每一步都将你带入更大量更复杂的代码块中,一直到最后成功地运行了内核为止。</P>< 0cm 0cm 0pt">依赖于你计算层次的方式,<FONT face="Times New Roman">CPU</FONT>成为内核引导程序的引导程序的引导程序的引导程序(<FONT face="Times New Roman">CPU</FONT>装载<FONT face="Times New Roman">BIOS</FONT>,<FONT face="Times New Roman">BIOS</FONT>装载<FONT face="Times New Roman">LILO</FONT>,<FONT face="Times New Roman">LILO</FONT>装载压缩内核,压缩内核装载解压内核;但是你可以合理的考虑是否这些步骤都满足引导程序的定义)。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">初始化Linux内核</H2>< 0cm 0cm 0pt">在内核成功装入内存(如果需要就解压缩)以及一些关键硬件,例如已经在低层设置过的内存管理器(<FONT face="Times New Roman">MMU</FONT>,请参看第<FONT face="Times New Roman">8</FONT>章)之后,内核将跳转到<B normal"><FONT face="Times New Roman">start_kernel</FONT></B>(<FONT face="Times New Roman">19802</FONT>行)。这个函数完成其余的系统初始化工作――实际上,几乎所有的初始化工作都是由这个函数实现的。因此,<B normal"><FONT face="Times New Roman">start_kernel</FONT></B>就是本节的核心。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">start_kernel</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19802</FONT>:<B normal"><FONT face="Times New Roman">__init</FONT></B>标示符在<FONT face="Times New Roman">gcc</FONT>编译器中指定将该函数置于内核的特定区域。在内核完成自身初始化之后,就试图释放这个特定区域。实际上,内核中存在两个这样的区域,<FONT face="Times New Roman">.text.init</FONT>和<FONT face="Times New Roman">.data.init</FONT>――第一个是代码初始化使用的,另外一个是数据初始化使用的。(诸如可以在进程间共享的代码和字符串常量之类的“文本(<FONT face="Times New Roman">Text</FONT>)”是在可执行程序中的“纯区域”中使用的一个术语。)另外你也可以看到<B normal"><FONT face="Times New Roman">__initfunc</FONT></B>和<B normal"><FONT face="Times New Roman">__initdata</FONT></B>标志,前者和<B normal"><FONT face="Times New Roman">__init</FONT></B>类似,标志初始化专用代码,后者则标志初始化专用数据。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19807</FONT>:如前所述,即使在多处理器系统中,在启动时也只使用一个<FONT face="Times New Roman">CPU</FONT>。<FONT face="Times New Roman">Intel</FONT>称之为引导程序处理器(<FONT face="Times New Roman">Bootstrap Processor</FONT>,简称为<FONT face="Times New Roman">BSP</FONT>),它在内核代码的某些地方有时也称之为<FONT face="Times New Roman">BP</FONT>。<FONT face="Times New Roman">BSP</FONT>首次运行这一行时,跳过后面的<B normal"><FONT face="Times New Roman">if</FONT></B>语句,并减小<B normal"><FONT face="Times New Roman">boot_cpu</FONT></B>标志,从而当其它<FONT face="Times New Roman">CPU</FONT>运行到此处时,都要运行<FONT face="Times New Roman">if</FONT>语句。等到其它<FONT face="Times New Roman">CPU</FONT>被激活执行到这里时,<FONT face="Times New Roman">BSP</FONT>已经在<FONT face="Times New Roman">idle</FONT>循环中了(本章稍后会更详细的讨论这个问题),<B normal"><FONT face="Times New Roman">initialize_secondary</FONT></B>(<FONT face="Times New Roman">4355</FONT>行)负责把其它<FONT face="Times New Roman">CPU</FONT>加入到<FONT face="Times New Roman">BSP</FONT>中。这样,其它<FONT face="Times New Roman">CPU</FONT>就不用执行<B normal"><FONT face="Times New Roman">start_kernel</FONT></B>的剩余部分了――这也是一件好事,因为这意味着不用再进行对许多硬件进行冗余初始化等工作了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>顺便说一下,这种奇异的小小的改动只有对于<FONT face="Times New Roman">x86</FONT>是必需的;对于其它平台,调用<B normal"><FONT face="Times New Roman">smp_init</FONT></B>完全可以处理<FONT face="Times New Roman">SMP</FONT>设置的其它部分,这一点马上就会讨论。因此,其它平台的<B normal"><FONT face="Times New Roman">initialize_secondary</FONT></B>的定义都是空的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19816</FONT>:打印内核标题信息(<FONT face="Times New Roman">20099</FONT>行),这里显示了有关内核如何编译的信息,包括在什么机器上编译,什么时间编译,使用什么版本的编译器,等等。如果中间任何一步发生了错误,在寻找机器不能启动的原因时查明内核的来源是一个有用的线索。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19817</FONT>:初始化内核自身的部分组件――内存,硬件中断,调度程序,等等。尤其是<B normal"><FONT face="Times New Roman">setup_arch</FONT></B>函数(<FONT face="Times New Roman">19765</FONT>行)完成体系结构相关的设置,此后在<B normal"><FONT face="Times New Roman">command_line</FONT></B>(传递到内核的参数,在下面讨论)、<B normal"><FONT face="Times New Roman">memory_start</FONT></B>和<B normal"><FONT face="Times New Roman">memory_end</FONT></B>(内核可用物理地址范围)中返回结果。下面这些函数都希望驻留在内存低端的;它们使用<B normal"><FONT face="Times New Roman">memory_start</FONT></B>和<B normal"><FONT face="Times New Roman">memory_end</FONT></B>来传递该信息。在函数获得所希望的值后,返回值指明了新的<B normal"><FONT face="Times New Roman">memory_start</FONT></B>的值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19823</FONT>:分析传给内核的各种选项。<B normal"><FONT face="Times New Roman">parse_options</FONT></B>函数(<FONT face="Times New Roman">19707</FONT>行,在随后的分析内核选项一节中讨论)也设置了<B normal"><FONT face="Times New Roman">argv</FONT></B>和<B normal"><FONT face="Times New Roman">envp</FONT></B>的初值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19833</FONT>:内核运行过程中也可以自行对所进行的工作进行记录,周期性地对所执行的指令进行抽样,并使用所获得的结果更新表格。这在定时器中断过程中通过调用<B normal"><FONT face="Times New Roman">x86_do_profile</FONT></B>(<FONT face="Times New Roman">1896</FONT>行)来实现,该部分将在第<FONT face="Times New Roman">6</FONT>章中介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>如图<FONT face="Times New Roman">4.1</FONT>中说明的那样,这个表格把内核划分为几个大小相同的范围,并简单跟踪在一次中断的时间内每个范围中运行多少条指令。这种记录当然是非常粗糙的――甚至不是依据函数和行号进行划分的,而只是使用近似的地址――但是这样代价很低、快速、短小,而且有助于专家判断最关键的问题要点。每个表格条目所涉及到地址的多少――还有问题发生地点的不确定性――可以通过简单修改<B normal"><FONT face="Times New Roman">prof_shift</FONT></B>(<FONT face="Times New Roman">26142</FONT>行)来调节。<B normal"><FONT face="Times New Roman">profile_setup</FONT></B>(<FONT face="Times New Roman">19076</FONT>行,在本章中后面讨论)可以让你在启动的时候设置<B normal"><FONT face="Times New Roman">prof_shift</FONT></B>的值,这样比为修改这个数字而重新编译内核要清晰方便得多。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><wrapblock><v:shapetype><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path connecttype="rect" gradientshapeok="t" extrusionok="f"></v:path><lock aspectratio="t" v:ext="edit"></lock></v:shapetype><v:shape><FONT face="Times New Roman"><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></FONT></v:shape></wrapblock><BR vglayout" clear=all></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">4.1 </FONT>描述用缓存(<FONT face="Times New Roman">profiling buffer</FONT>)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt; TEXT-ALIGN: center" align=center><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这个<B normal"><FONT face="Times New Roman">if</FONT></B>程序块为记录表格分配内存,并把所有项都清零。注意到如果<B normal"><FONT face="Times New Roman">prof_shift</FONT></B>是<FONT face="Times New Roman">0</FONT>(缺省值),那么记录功能就被关掉了,<B normal"><FONT face="Times New Roman">if</FONT></B>程序段不再被执行,也不为表格分配空间。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19846</FONT>:内核通过调用<B normal"><FONT face="Times New Roman">sti</FONT></B>(<FONT face="Times New Roman">13104</FONT>行是<FONT face="Times New Roman">UP</FONT>版本的――注意该主题在第<FONT face="Times New Roman">6</FONT>章中有更详细的介绍)开始接收硬件中断。首先需要激活定时器中断,以便后来对<B normal"><FONT face="Times New Roman">calibrate_delay</FONT></B>(<FONT face="Times New Roman">19654</FONT>行)的调用可以计算机器的<FONT face="Times New Roman">BogoMIPS</FONT>的值(在下一节“<FONT face="Times New Roman">BogoMIPS</FONT>”中介绍)。因为一些设备驱动程序需要<FONT face="Times New Roman">BogoMIPS</FONT>的值,所以内核必需在大部分硬件、文件系统等等初始化之前计算出这个值来。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19876</FONT>:测试该<FONT face="Times New Roman">CPU</FONT>的各种缺陷,比如<FONT face="Times New Roman">Pentium F00F</FONT>缺陷(请参看第<FONT face="Times New Roman">8</FONT>章),记录检测到的缺陷,以便于内核的其它部分以后可以使用它们的工作。(为了节省空间起见,我们省略掉了<B normal"><FONT face="Times New Roman">check_bugs</FONT></B>函数。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19882</FONT>:调用<B normal"><FONT face="Times New Roman">smp_init</FONT></B>(<FONT face="Times New Roman">19787</FONT>行),它又调用了其它的函数来激活<FONT face="Times New Roman">SMP</FONT>系统中其它<FONT face="Times New Roman">CPU</FONT>:在<FONT face="Times New Roman">x86</FONT>的平台上,<B normal"><FONT face="Times New Roman">smp_boot_cpus</FONT></B>(<FONT face="Times New Roman">4614</FONT>行)初始化一些内核数据结构,这些数据结构跟踪检测另外的<FONT face="Times New Roman">CPU</FONT>并简单的将其改为保持模式;最后<B normal"><FONT face="Times New Roman">smp_commence</FONT></B>(<FONT face="Times New Roman">4195</FONT>行)使这些<FONT face="Times New Roman">CPU</FONT>继续执行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19883</FONT>:把<FONT face="Times New Roman">init</FONT>函数作为内核线程终止,这比较复杂;请参看本章后面有关<B normal"><FONT face="Times New Roman">init</FONT></B>的讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19885</FONT>:增加<FONT face="Times New Roman">idle</FONT>进程的<B normal"><FONT face="Times New Roman">need_resched</FONT></B>标志,这样做的原因在此时可能还比较模糊。直到读完了第<FONT face="Times New Roman">5</FONT>、<FONT face="Times New Roman">6</FONT>、<FONT face="Times New Roman">7</FONT>章以后,才能有个清楚的概念;但是,在下一个定时器中断结束之前(在第<FONT face="Times New Roman">6</FONT>章中讨论),<B normal"><FONT face="Times New Roman">system_call</FONT></B>(<FONT face="Times New Roman">171</FONT>行,在第<FONT face="Times New Roman">5</FONT>章中讨论)函数中会注意到<FONT face="Times New Roman">idle</FONT>进程的<B normal"><FONT face="Times New Roman">need_fesched</FONT></B>标志增加了,并且调用<B normal"><FONT face="Times New Roman">schedule</FONT></B>(<FONT face="Times New Roman">26686</FONT>行,第<FONT face="Times New Roman">7</FONT>章)释放<FONT face="Times New Roman">CPU</FONT>,并将其赋给更应该获取<FONT face="Times New Roman">CPU</FONT>的进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19886</FONT>:已经完成了内核初始化的工作――或者不管怎样,已经把需要完成的少量责任传递给了<B normal"><FONT face="Times New Roman">init</FONT></B>――所剩余的工作不过是进入<FONT face="Times New Roman">idle</FONT>循环以消耗空闲的<FONT face="Times New Roman">CPU</FONT>时间片。因此,本行调用<B normal"><FONT face="Times New Roman">cpu_idle</FONT></B>(<FONT face="Times New Roman">2014</FONT>行)――<FONT face="Times New Roman">idle</FONT>循环。正如你可以从<B normal"><FONT face="Times New Roman">cpu_idle</FONT></B>本身可以发现的一样,该函数从不返回。然而,当有实际工作要处理时,该函数就会被抢占。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>注意到<B normal"><FONT face="Times New Roman">cpu_idle</FONT></B>只是反复调用<B normal"><FONT face="Times New Roman">idle</FONT></B>系统调用(下一章将讨论系统调用),它通过<B normal"><FONT face="Times New Roman">sys_idle</FONT></B>(<FONT face="Times New Roman">2064</FONT>行)实现真正的<FONT face="Times New Roman">idle</FONT>循环――<FONT face="Times New Roman">2014</FONT>行对应<FONT face="Times New Roman">UP</FONT>版本,<FONT face="Times New Roman">2044</FONT>行针对<FONT face="Times New Roman">SMP</FONT>版本。它们通过执行<B normal"><FONT face="Times New Roman">hlt</FONT></B>(对应“<FONT face="Times New Roman">halt</FONT>”)指令把<FONT face="Times New Roman">CPU</FONT>转入低功耗的“睡眠”状态。只要没有实际的工作处理,<FONT face="Times New Roman">CPU</FONT>都将转入这种状态。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">BogoMIPS</H2><P 0cm 0cm 0pt"><FONT face="Times New Roman">BogoMIPS</FONT>的数字由内核计算并在系统初始化的时候打印。它近似的给出了每秒钟<FONT face="Times New Roman">CPU</FONT>可以执行一个短延迟循环的次数。在内核中,这个结果主要用于需要等待非常短周期的设备驱动程序――例如,等待几微秒并查看设备的某些信息是否已经可用。</P><P 0cm 0cm 0pt">由于没有正确理解<FONT face="Times New Roman">BogoMIPS</FONT>的含义,<FONT face="Times New Roman">BogoMIPS</FONT>在各处都被滥用,就仿佛它可以满足人类最原始、最深层次的需求:把所有计算机性能的信息简化为一个数字。“<FONT face="Times New Roman">BogoMIPS</FONT>”中的“<FONT face="Times New Roman">Bogo</FONT>”部分来源于“伪(<FONT face="Times New Roman">bogus</FONT>)”,就正是为了防止这种用法:虽然这个数字比大多数性能比较有效很多,但是它仍然是不准确的、容易引起误解的、无用的和不真实的,根本不适合将它用于机器间差别的对比。但是这个数字仍然非常吸引人,这也正是我们在这里讨论这个问题的原因。(顺便说一下,<FONT face="Times New Roman">BogoMIPS </FONT>中“<FONT face="Times New Roman">MIPS</FONT>”部分是“<FONT face="Times New Roman">millions of instructions per second</FONT>(百万条指令每秒)”的缩写,这是计算机性能对比中的一个常用单位。)</P><H4 6pt 0cm; TEXT-INDENT: 0cm">calibrate_delay</H4><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">19654</FONT>:<B normal"><FONT face="Times New Roman">calibrate_delay</FONT></B>是近似计算<FONT face="Times New Roman">BogoMIPS</FONT>数字的内核函数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19622</FONT>:作为第一次估算,<B normal"><FONT face="Times New Roman">calibrate_delay</FONT></B>计算出在每一秒内执行多少次<B normal"><FONT face="Times New Roman">__delay</FONT></B>循环(<FONT face="Times New Roman">6866</FONT>行),也就是每个定时器滴答(<FONT face="Times New Roman">timer tick</FONT>)――百分之一秒――内延时循环可以执行多少次。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19664</FONT>:计算一个定时器滴答内可以执行多少次循环需要在滴答开始时就开始计数,或者应该尽可能与它接近。全局变量<B normal"><FONT face="Times New Roman">jiffies</FONT></B>(<FONT face="Times New Roman">16588</FONT>行)中存储了从内核开始保持跟踪时间开始到现在已经经过的定时器滴答数;第<FONT face="Times New Roman">6</FONT>章中将介绍它的实现方式。<B normal"><FONT face="Times New Roman">jiffies</FONT></B>保持异步更新,在一个中断内——每秒一百次,内核暂时挂起正在处理的内容,更新变量,然后继续刚才的工作。如果不这样处理,下一行的循环就永远不可能退出。从而,如果<B normal"><FONT face="Times New Roman">jiffies</FONT></B>不声明为<B normal"><FONT face="Times New Roman">volatile</FONT></B>――简单的说,这个值变化的原因对于编译器是透明的――<FONT face="Times New Roman">gcc</FONT>仍然可能对该循环进行优化,并引起该循环进入不能退出的状态。虽然目前的<FONT face="Times New Roman">gcc</FONT>还没有如此高的智能,然而它的维护者应该完全能够为它实现这种智能。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19669</FONT>:定时器又前移了一个滴答,因此又产生一个新的滴答。下一步是要等待<B normal"><FONT face="Times New Roman">loops_per_sec</FONT></B>延时循环调用定时器循环,接着检测是否最少有一个完整的滴答已经完成。如果是这样,就退出首次近似估算循环;如果没有,就把<B normal"><FONT face="Times New Roman">loops_per_sec</FONT></B>的值加倍,然后重新启动这个过程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这个循环的正确性依赖于如下的事实:现有的机器在任何地方都不能每秒执行<FONT face="Times New Roman">2<SUP>32</SUP></FONT>次延时循环――对于<FONT face="Times New Roman">64</FONT>位机来说则远低于每秒<FONT face="Times New Roman">2<SUP>64</SUP></FONT>次――虽然这只是一个微不足道的问题。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19677</FONT>:现在内核已经清楚<B normal"><FONT face="Times New Roman">loops_per_sec</FONT></B>循环调用延时循环在这台机器上要花费超过百分之一秒的时间才能完成,因此,内核将重新开始进行估算。为了提高效率,内核使用折半查找算法计算<B normal"><FONT face="Times New Roman">loops_per_sec</FONT></B>的实际值,我们假定开始的时候,实际值在现在计算结果和其一半之间――实际值不可能比现在计算值还大,但是可以(而且可能)稍微小一点。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19681</FONT>:和前面使用的方式一样,<B normal"><FONT face="Times New Roman">calibrate_delay</FONT></B>查看是否这个<B normal"><FONT face="Times New Roman">loops_per_sec</FONT></B>已经减小了的值还是比较大,需要耗费一个完整的定时器间隔。如果还是相当大,实际值应该小于当前计算值或者就是当前值,因此,使用更小的值继续查询;如果不够大,就使用一个更大的值继续查询。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19691</FONT>:内核有一种很好的方法来计算一个定时器滴答中执行延时循环的次数。这个数字乘以一秒内滴答的数量就得到了每秒内可以执行的延时循环的次数。这种计算只是一种估算,乘法也累积了误差,因此结果并不能精确到纳秒。但是这个数字供内核使用已经足够精确了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19693</FONT>:为了让用户感到激动,内核打印出这个数字。注意这里明显省略了<FONT face="Times New Roman">%f</FONT>的格式限定――内核尽量避免浮点数运算。这个计算过程中最有用的常量是<FONT face="Times New Roman">500,000</FONT>;它是用一百万除以<FONT face="Times New Roman">2</FONT>得来,理由是每秒钟执行一百万条指令,而每个<FONT face="Times New Roman">delay</FONT>循环的核心是<FONT face="Times New Roman">2</FONT>条指令(<B normal"><FONT face="Times New Roman">decl</FONT></B>和一条跳转指令)。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">分析内核选项</H2><P 0cm 0cm 0pt"><B normal"><FONT face="Times New Roman">parse_options</FONT></B>函数分析由内核引导程序发送给内核的启动选项,在初始化过程中按照某些选项运行,并将剩余部分传送给<FONT face="Times New Roman">init</FONT>进程(在本章后面部分提到)。这些选项可能已经存储在配置文件中了,也可能是由用户在系统启动时敲入的――内核并不关心这些。类似的细节全部是内核引导程序应该关注的内容。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">parse_options</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19707</FONT>:参数已经收集在一条长的命令行中,内核被赋给指向该命令行头部的一个指针;内核引导程序在前面已经将该行存储在一个指定地址中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19718</FONT>:中断下一个参数,保持指向下一个参数的指针以供下一次循环使用。注意系统使用空格而不是通常的空白来分隔内核参数;制表符并不能把当前参数和下一个参数分隔开。如果发现了分隔字符空格,下一行就使用字节<FONT face="Times New Roman">0</FONT>覆盖,这样<B normal"><FONT face="Times New Roman">line</FONT></B>可以作为包含有唯一一个内核选项的标准<FONT face="Times New Roman">C</FONT>字符串来使用了。如果没有发现空格,就该函数关心的内容而言,其余的部分都具有相同的属性――这只有在处理<B normal"><FONT face="Times New Roman">line</FONT></B>中最后一个选项的情况下才会发生,循环就会在下次开始时结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>注意该代码不会跳过多个空格。假设<B normal"><FONT face="Times New Roman">line</FONT></B>值如下所述(两个空格):</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            rw  debug</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这会被当作三个选项:“<FONT face="Times New Roman">rw</FONT>”,“”(空字符串)和“<FONT face="Times New Roman">debug</FONT>”。因为空字符串不是有效的内核选项,它将会被传递到初始化的过程(这一点随后就可以看到)――这当然不是用户所希望的。因此,内核引导程序应该负责对多个空格进行压缩。<FONT face="Times New Roman">LILO</FONT>通过忽略用户多敲的空格,完美的解决了这个问题。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19721</FONT>:现在开始解释这些选项。最前面的两个选项――<B normal"><FONT face="Times New Roman">ro</FONT></B>和<B normal"><FONT face="Times New Roman">rw</FONT></B>――指明内核要装载根文件系统,也就是根目录(<FONT face="Times New Roman"> / </FONT>目录)所在的位置,而分别处于只读和读<FONT face="Times New Roman">/</FONT>写模式。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19729</FONT>:第三种可能性,<B normal"><FONT face="Times New Roman">debug</FONT></B>,增加了调试信息的数量;这些调试信息要通过调用<B normal"><FONT face="Times New Roman">do_syslog</FONT></B>打印出来(<FONT face="Times New Roman">25724</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19733</FONT>:开始几个选项是简单的独立标志,它们并不使用参数。内核也可以辨认形为<B normal"><FONT face="Times New Roman">option=value</FONT></B>的选项。本行就是一个例子,这里内核引导程序定义了一个命令来代替<FONT face="Times New Roman">init</FONT>运行;它使用<B normal"><FONT face="Times New Roman">init=<I normal">/some/other/program</I></FONT></B>的形式。这里的代码舍弃了<FONT face="Times New Roman"><B normal">init=</B> </FONT>部分,为随后<B normal"><FONT face="Times New Roman">init</FONT></B>的使用而把剩余部分在<B normal"><FONT face="Times New Roman">execute_command</FONT></B>中保存起来(<FONT face="Times New Roman">20044</FONT>行,后面会讨论到)。和其它大部分参数的处理方法不同,本处功能不能在<B normal"><FONT face="Times New Roman">checksetup</FONT></B>(<FONT face="Times New Roman">19612</FONT>行,马上就讨论到)中实现,这是因为它改变了该函数的局部变量。很快,你就可以看到前面三个选项之所以也在这里处理而不是在<B normal"><FONT face="Times New Roman">checksetup</FONT></B>中处理的原因。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19745</FONT>:大部分内核选项都是由<B normal"><FONT face="Times New Roman">checksetup</FONT></B>函数分析的。如果<B normal"><FONT face="Times New Roman">checksetup</FONT></B>处理了某个选项,就返回真值,循环继续进行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19750</FONT>:否则,<B normal"><FONT face="Times New Roman">line</FONT></B>中没有已经被辨认的内核选项。在这种情况下,它被作为一个供<FONT face="Times New Roman">init</FONT>进程使用的选项或者环境变量来处理――如果其形式为<B normal"><FONT face="Times New Roman">envar=<I normal">value</I></FONT></B>,就作为环境变量处理;否则,就作为选项处理。如果<B normal"><FONT face="Times New Roman">argv_init</FONT></B>和<B normal"><FONT face="Times New Roman">envp_init</FONT></B>(分别见<FONT face="Times New Roman">19057</FONT>和<FONT face="Times New Roman">19059</FONT>行)数组中有足够的空间,选项和环境变量就存储在里面供以后<B normal"><FONT face="Times New Roman">init</FONT></B>函数使用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这解释了从<FONT face="Times New Roman">19736</FONT>行开始的注释。字符串<B normal"><FONT face="Times New Roman">auto</FONT></B>并不是任何内核选项的前缀,因此它应该被作为<FONT face="Times New Roman">init</FONT>的一个参数存储在<B normal"><FONT face="Times New Roman">argv_init</FONT></B>数组中――这在大多数情况下都是可行的,因为<B normal"><FONT face="Times New Roman">auto</FONT></B>是<FONT face="Times New Roman">init</FONT>可以识别的选项。但是,当使用<B normal"><FONT face="Times New Roman">init=</FONT></B>的形式给出内核选项时,通常是执行<FONT face="Times New Roman">shell</FONT>而不是<FONT face="Times New Roman">init</FONT>,<B normal"><FONT face="Times New Roman">auto</FONT></B>会使<FONT face="Times New Roman">shell</FONT>混淆;因此,安全一点的方法是,<B normal"><FONT face="Times New Roman">parse_options</FONT></B>在此处忽略所有与此有关的<FONT face="Times New Roman">init</FONT>参数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>奇怪的是,当<B normal"><FONT face="Times New Roman">argv_init</FONT></B>或者<B normal"><FONT face="Times New Roman">envp_init</FONT></B>空间用完时,整个循环就结束了。仅仅因为<B normal"><FONT face="Times New Roman">argv_init</FONT></B>的空间用完了并不意味着<FONT face="Times New Roman">line</FONT>中就不再含有<FONT face="Times New Roman">init</FONT>使用的环境变量,反之亦然。此外,可能还剩下许多内核选项没有处理。当你考虑到<B normal"><FONT face="Times New Roman">MAX_INIT_ARGX</FONT></B>(<FONT face="Times New Roman">19029</FONT>行)和<B normal"><FONT face="Times New Roman">MAX_INIT_ENVS</FONT></B>(<FONT face="Times New Roman">19030</FONT>行)都通过使用<B normal"><FONT face="Times New Roman">#define</FONT></B>被预定义为<FONT face="Times New Roman">8</FONT>――这是一个很容易超过的下限――这种行为就更奇怪了。如果在<FONT face="Times New Roman">19752</FONT>行和<FONT face="Times New Roman">19756</FONT>行的<B normal"><FONT face="Times New Roman">break</FONT></B>改成<B normal"><FONT face="Times New Roman">continue</FONT></B>,那么循环可以继续处理内核选项,而不会写入超过<B normal"><FONT face="Times New Roman">argv_init</FONT></B>和<B normal"><FONT face="Times New Roman">envp_init</FONT></B>数组界限的空间。如果<B normal"><FONT face="Times New Roman">command_line</FONT></B>中仍然包含有并不是为<FONT face="Times New Roman">init</FONT>而定义的内核选项,那么这一点就是非常重要的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19760</FONT>:所有的内核选项都处理完成了。最后一步是要使用<B normal"><FONT face="Times New Roman">NULL</FONT></B>填充<B normal"><FONT face="Times New Roman">argv_init</FONT></B>和<B normal"><FONT face="Times New Roman">envp_init</FONT></B>数组的末尾,从而使得<FONT face="Times New Roman">init</FONT>可以知道在哪里终止。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">checksetup</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19612</FONT>:<B normal"><FONT face="Times New Roman">checksetup</FONT></B>函数负责进行大部分内核选项的处理过程。它把这些内核选项分为三类:一类使用内核普通参数来分析<FONT face="Times New Roman">=sign</FONT>之后的部分;另一类自行分析<FONT face="Times New Roman">=sign</FONT>之后的部分;还有一类自行分析整个行,包括<FONT face="Times New Roman"><B normal">=</B> sign</FONT>前面的部分和<FONT face="Times New Roman"><B normal">=</B> sign</FONT>后面的部分。第一类被认为是使用“现成”的参数,这与为第二类提供的“原始”参数相对应。最后一类只由一个<FONT face="Times New Roman">IDE</FONT>驱动程序组成;内核首先在<FONT face="Times New Roman">19619</FONT>行检查并处理这种情况,以使其不会在随后的处理中造成麻烦。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19625</FONT>:接下来,<B normal"><FONT face="Times New Roman">checksetup</FONT></B>扫描整个<B normal"><FONT face="Times New Roman">raw_params</FONT></B>数组(<FONT face="Times New Roman">19552</FONT>行)并判断是否该内核选项应该不加处理的保留。<B normal"><FONT face="Times New Roman">raw_params</FONT></B>中的元素是<B normal"><FONT face="Times New Roman">struct kernel_param</FONT></B>类型(<FONT face="Times New Roman">19223</FONT>行)的,它把内核选项前缀和装载选项时调用的函数联系起来。如果数组中的某些项的<B normal"><FONT face="Times New Roman">str</FONT></B>成员以<B normal"><FONT face="Times New Roman">line</FONT></B>为前缀,就会调用<B normal"><FONT face="Times New Roman">line</FONT></B>后面的相应函数(也就是前缀之后的部分),随后<B normal"><FONT face="Times New Roman">checksetup</FONT></B>会返回一个非零值以表明它已经对该内核选项进行了处理。<B normal"><FONT face="Times New Roman">raw_params</FONT></B>数组以两个<B normal"><FONT face="Times New Roman">NULL</FONT></B>结束,因此在检测到<B normal"><FONT face="Times New Roman">str</FONT></B>成员是<B normal"><FONT face="Times New Roman">NULL</FONT></B>时,循环就可以结束了。在这种情况下,显然循环已经到达了<B normal"><FONT face="Times New Roman">raw_params</FONT></B>数组的结尾,但是仍然没有找到匹配的情况。当然,测试<B normal"><FONT face="Times New Roman">setup_func</FONT></B>成员也可以取得同样好的效果。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这个循环说明了一点:与大多数内核非常不同的是,这里的初始化并不需要尽可能的快。如果内核比从前多用几微秒来启动,这并没有什么实际的损失――毕竟用户应用程序还没有开始运行,所以他们并没有损失什么东西。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>最终结果是代码效率很低,而且存在很多优化的可能。例如,<B normal"><FONT face="Times New Roman">raw_params</FONT></B>数组中字符串的长度可以在<B normal"><FONT face="Times New Roman">raw_params</FONT></B>中暂存,而不用在<FONT face="Times New Roman">19626</FONT>行多次重复计算。更好的解决方法是,可以把<FONT face="Times New Roman">raw_params</FONT>数组中的项按照字符顺序排序,这样<B normal"><FONT face="Times New Roman">checksetup</FONT></B>就可以进行折半查找。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>在<B normal"><FONT face="Times New Roman">raw_params</FONT></B>的情况中实现排序并没有什么障碍,但是这样也可能并不能获得很大的优势,因为折半查找的优点只有在比较大的数组中才能充分表现出来(所谓比较大的确切值在不同的环境中也有所不同)。<B normal"><FONT face="Times New Roman">raw_params</FONT></B>的姊妹数组<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>(<FONT face="Times New Roman">19228</FONT>行)当然是足够大的,可以显示出折半查找的优势;但是这样就引发了一个新的问题:对<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>进行排序比较难用,因为这可能需要分隔一些<B normal"><FONT face="Times New Roman">#ifdef</FONT></B>程序段――请参看从<FONT face="Times New Roman">19268</FONT>行到<FONT face="Times New Roman">19272</FONT>行的例子。进一步说,因为算法只是查找前缀,而不使用完全匹配,在遍历数组中的各个项时对遍历次序比较敏感,所以这种特性在使用不同的查找次序时就很难再保持了。然而,这些问题并不是不可克服的(程序员可以预先静态地为引导程序建立一颗前缀树),如果性能在这里是主要因素,那么这种努力也是值得的。但是,由于性能在这里并不是主要问题,所以简单性才被作为最重要的因素体现出来。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>即使这样,在类似的<B normal"><FONT face="Times New Roman">root_dev_names</FONT></B>数组(<FONT face="Times New Roman">19085</FONT>行)中――这个数组把硬件设备名的前缀映射到它们的主<FONT face="Times New Roman">ID</FONT>号上――开发者仍然可以简单地通过把比较常用的项(<FONT face="Times New Roman">IDE</FONT>和<FONT face="Times New Roman">SCSI</FONT>磁盘)放在不太常用的项(串口<FONT face="Times New Roman">IDE CDs</FONT>)的前面以节省出一点性能。但是我在<B normal"><FONT face="Times New Roman">raw_params</FONT></B>或<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>中并没有发现与之类似的模式。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>另外一件需要注意的事是:现在你可以猜想一下为什么<B normal"><FONT face="Times New Roman">ro</FONT></B>,<B normal"><FONT face="Times New Roman">rw</FONT></B>和<B normal"><FONT face="Times New Roman">debug</FONT></B>选项在<B normal"><FONT face="Times New Roman">parse_options</FONT></B>中测试而不在这里测试――<B normal"><FONT face="Times New Roman">parse_options</FONT></B>要检测精确的匹配,但是<B normal"><FONT face="Times New Roman">checksetup</FONT></B>只检测前缀。作为一个特殊的情况,<B normal"><FONT face="Times New Roman">ro</FONT></B>选项碰巧正好是<B normal"><FONT face="Times New Roman">root=</FONT></B>(<FONT face="Times New Roman">19553</FONT>行)的前缀,这样如果这三个选项彼此合并,就需要仔细处理了。这似乎仍然是一个相当无力的原因。考虑一下<B normal"><FONT face="Times New Roman">noinitrd</FONT></B>选项(<FONT face="Times New Roman">19251</FONT>行)。这是<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>的一个项,因而只需要匹配前缀,而且与之相关联的设置函数(<B normal"><FONT face="Times New Roman">no_initrd</FONT></B>,<FONT face="Times New Roman">19902</FONT>行)将忽略所有可能已经传递给它们的参数――这正像<B normal"><FONT face="Times New Roman">ro</FONT></B>,<B normal"><FONT face="Times New Roman">rw</FONT></B>和<B normal"><FONT face="Times New Roman">debug</FONT></B>被包含在<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>中时所可能进行的工作一样。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19632</FONT>:这个循环为<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>数组的处理工作和前面一个循环为<B normal"><FONT face="Times New Roman">raw_params</FONT></B>数组的处理工作相同。这两个循环(当然不包括循环使用的数组)间的唯一区别是本循环在调用设置函数之前,使用<B normal"><FONT face="Times New Roman">get_options</FONT></B>(<FONT face="Times New Roman">19062</FONT>行)处理<B normal"><FONT face="Times New Roman">line</FONT></B>中<FONT face="Times New Roman">=sign</FONT>后面的部分。简单的说,<B normal"><FONT face="Times New Roman">get_options</FONT></B>使用<FONT face="Times New Roman">10</FONT>个负整数填充<B normal"><FONT face="Times New Roman">ints[1]</FONT></B>到<B normal"><FONT face="Times New Roman">ints[10]</FONT></B>。<B normal"><FONT face="Times New Roman">ints[0]</FONT></B>中是<B normal"><FONT face="Times New Roman">ints</FONT></B>中使用元素的个数――也就是,它记录了存储在<B normal"><FONT face="Times New Roman">ints</FONT></B>中的<B normal"><FONT face="Times New Roman">intsget_options</FONT></B>数量。接着这个数组将被传递给设置函数,该设置函数则会按照自己喜欢的方式对该数组内容进行解释。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19640</FONT>:返回<FONT face="Times New Roman">0</FONT>,说明<B normal"><FONT face="Times New Roman">line</FONT></B>中所包含的内核选项不能被函数理解。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">profile_setup</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19076</FONT>:<B normal"><FONT face="Times New Roman">profile_setup</FONT></B>是<B normal"><FONT face="Times New Roman">checksetup</FONT></B>调用的设置函数的一个完美的例子:这个函数十分短小,使用<B normal"><FONT face="Times New Roman">ints</FONT></B>参数处理了部分内容。而且到目前为止你也应该对它的目的有了一定了解。正如前面提到的一样,用户可以在启动的时候设置<B normal"><FONT face="Times New Roman">prof_shift</FONT></B>的值――好,这里正是它的实现方式。当内核启动过程提供<B normal"><FONT face="Times New Roman">profile=</FONT></B>选项时,就调用<B normal"><FONT face="Times New Roman">profile_setup</FONT></B>函数。前缀字符串和函数在<FONT face="Times New Roman">19235</FONT>行被联系在一起。注意这是在<B normal"><FONT face="Times New Roman">cooked_params</FONT></B>中,因此<B normal"><FONT face="Times New Roman">profile_setup</FONT></B>取得的是处理过的参数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19079</FONT>:如果参数中存在<B normal"><FONT face="Times New Roman">profile=</FONT></B>的形式,就使用<B normal"><FONT face="Times New Roman">profile=</FONT></B>后面的第一个数字作为<B normal"><FONT face="Times New Roman">prof_shift</FONT></B>的新值。选项给出的其它参数都被简单的忽略了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">19081</FONT>:如果给出了<B normal"><FONT face="Times New Roman">profile=</FONT></B>选项,但是没有为它提供参数,<FONT face="Times New Roman"><B normal">prof_shif</B>t</FONT>的缺省值就是<FONT face="Times New Roman">2</FONT>。这个缺省值有些奇怪,因为我们已经知道,这意味着使用四分之一的内核可用内存来配置其余部分――这是一个很大的开销。但是另一方面,使用这些内存有助于更精确的定位问题热点――只有很少的几条指令存在不确定性,这样应该比较容易地把问题限制在一两行源程序代码内。那张图也并不是像我所画的那样简单:因为图中只描述了内核代码,这种开销还不到内核所有内存空间的<FONT face="Times New Roman">25%</FONT>,但是对于所覆盖的代码量来说却并不止<FONT face="Times New Roman">25%</FONT>。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">init</H2><P 0cm 0cm 0pt"><FONT face="Times New Roman">init</FONT>从许多方面看都是一个非常特殊的进程。这是内核运行的第一个用户进程,它要负责触发其它必需的进程以使系统作为一个整体进入可用的状态。这些工作由<FONT face="Times New Roman">/etc/inittab</FONT>文件控制,通常包括设置<FONT face="Times New Roman">getty</FONT>进程以接受用户登录;建立网络服务,例如<FONT face="Times New Roman">FTP</FONT>和<FONT face="Times New Roman">HTTP</FONT>守护进程;等等。如果没有这些进程,用户就不可能完成多少工作,这样成功启动内核就显得没有多大意义了。</P><P 0cm 0cm 0pt">这种设计的另外一个重要的副作用是<FONT face="Times New Roman">init</FONT>是系统中所有进程的祖先。<FONT face="Times New Roman">init</FONT>产生<FONT face="Times New Roman">getty</FONT>进程,<FONT face="Times New Roman">getty</FONT>进程产生<FONT face="Times New Roman">login</FONT>进程,<FONT face="Times New Roman">login</FONT>进程产生你自己的<FONT face="Times New Roman">shell</FONT>,使用自己的<FONT face="Times New Roman">shell</FONT>,可以产生每一个你运行的进程。在所有的结果中,这有助于确保内核进程表中的所有项最终都能够得到处理。进程结束以后将其清除(回收)的工作首先应由其父进程完成;如果父进程已经退出,那么祖父进程就要担负起这种责任;如果祖父进程已经退出,那么曾祖父进程就要担负起这种责任,周而复始。通过这种方式,从不退出的<FONT face="Times New Roman">init</FONT>进程就可能要负责回收其它进程。</P><P 0cm 0cm 0pt">因此,为了确保这些重要的工作都能正确执行,内核初始化进程所需要做的最后一步工作就是创建<FONT face="Times New Roman">init</FONT>进程,接下来就加以描述。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">init</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20044</FONT>:<B normal"><FONT face="Times New Roman">unused</FONT></B>参数来源于该函数的非常规调用。<B normal"><FONT face="Times New Roman">init</FONT></B>函数――不要和<I normal"><FONT face="Times New Roman">init</FONT></I><I normal">进程</I>搞混了,后者是它随后要创建的――作为内核线程开始生命周期,一个作为内核的一部分运行的进程。(如果你编写过多线程的程序,这里的内核线程可能会同你所已经知道的线程意义有所不同――在那种意义下,它不是一个内核线程。)实际上,<B normal"><FONT face="Times New Roman">init</FONT></B>函数就像是新进程使用的剥离出来了的<B normal"><FONT face="Times New Roman">main</FONT></B>函数,<B normal"><FONT face="Times New Roman">unused</FONT></B>参数是一个独立的指针,其值指向为给定进程所提供的信息――这比通常使用<B normal"><FONT face="Times New Roman">argc</FONT></B>,<B normal"><FONT face="Times New Roman">argv</FONT></B>和<B normal"><FONT face="Times New Roman">envp</FONT></B>参数传递的信息要少得多。<B normal"><FONT face="Times New Roman">init</FONT></B>函数碰巧不需要额外的信息,因此这个参数命名为<B normal"><FONT face="Times New Roman">unused</FONT></B>,就是要强调这一点。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>为了确保在这一点上你不会产生困惑,我们在这里再对整个机制进行扼要重复:<B normal"><FONT face="Times New Roman">init</FONT></B>函数是内核的一部分;它在内核中作为内核的一个独立的执行部分运行;也就是说,无论从哪个方面看它都是内核代码。但是,<FONT face="Times New Roman">init</FONT>进程就不是这样了。在某些方面,<FONT face="Times New Roman">init</FONT>进程是一个特殊的进程,但是不属于内核本身;其代码存储在磁盘上单独的可执行映像中,这和其它程序一样。因为<B normal"><FONT face="Times New Roman">init</FONT></B>函数后来产生<FONT face="Times New Roman">init</FONT>进程,而它自己又恰好作为进程运行,这样就很容易产生混淆。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>因为<FONT face="Times New Roman">idle</FONT>进程已经占据了进程<FONT face="Times New Roman">ID</FONT>号(<FONT face="Times New Roman">PID</FONT>)<FONT face="Times New Roman">0</FONT>,<B normal"><FONT face="Times New Roman">init</FONT></B>(当然是<FONT face="Times New Roman">init</FONT>)就被赋值为下一个可用的<FONT face="Times New Roman">PID</FONT>,也就是<FONT face="Times New Roman">1</FONT>。(进程<FONT face="Times New Roman">ID</FONT>在第<FONT face="Times New Roman">7</FONT>章中讨论。)内核重复假定<FONT face="Times New Roman">PID</FONT>为<FONT face="Times New Roman">1</FONT>的进程是<FONT face="Times New Roman">init</FONT>,因此这种特性在没有充分地相互作用,也就是没有同步地进行修改的情况下是不能改变的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20046</FONT>:调用<B normal"><FONT face="Times New Roman">lock_kernel</FONT></B>(<FONT face="Times New Roman">17492</FONT>行对应<FONT face="Times New Roman">UP</FONT>版本;<FONT face="Times New Roman">10174</FONT>行对应<FONT face="Times New Roman">SMP</FONT>版本)执行后续几行,而不会受到其它会受到随后工作的影响的内核模块的干扰。内核锁随后在<FONT face="Times New Roman">20053</FONT>行被释放。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20047</FONT>:调用<B normal"><FONT face="Times New Roman">do_basic_setup</FONT></B>(<FONT face="Times New Roman">19916</FONT>行)初始化总线并随同其它工作产生一些其它内核线程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20052</FONT>:内核已完全完成初始化了,因此<B normal"><FONT face="Times New Roman">free_initmen</FONT></B>(<FONT face="Times New Roman">7620</FONT>行)可以舍弃内核的<FONT face="Times New Roman">.text.init</FONT>节的函数和<FONT face="Times New Roman">.data.init</FONT>节的数据。所有使用<B normal"><FONT face="Times New Roman">__initfunc</FONT></B>标记过的函数和使用<B normal"><FONT face="Times New Roman">__initdata</FONT></B>标记过的数据现在都不能使用了,它们曾经获得的内存现在也可能重新用于其它目的了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20055</FONT>:如果可能,打开控制台设备,这样<FONT face="Times New Roman">init</FONT>进程就拥有一个控制台,可以向其中写入信息,也可以从其中读取输入信息。实际上<FONT face="Times New Roman">init</FONT>进程除了打印错误信息以外,并不使用控制台,但是如果调用的是<FONT face="Times New Roman">shell</FONT>或者其它需要交互的进程,而不是<FONT face="Times New Roman">init</FONT>,那么就需要一个可以交互的输入源。如果成功执行<B normal"><FONT face="Times New Roman">open</FONT></B>,<FONT face="Times New Roman">/dev/console</FONT>就成为<FONT face="Times New Roman">init</FONT>的标准输入源(文件描述符<FONT face="Times New Roman">0</FONT>)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20059</FONT>:调用<B normal"><FONT face="Times New Roman">dup</FONT></B>打开<FONT face="Times New Roman">/dev/console</FONT>文件描述符两次,这样,<FONT face="Times New Roman">init</FONT>就也使用它供标准输出和标准错误使用(文件描述符<FONT face="Times New Roman">1</FONT>和<FONT face="Times New Roman">2</FONT>)。假设<FONT face="Times New Roman">20055</FONT>行的<B normal"><FONT face="Times New Roman">open</FONT></B>成功执行(正常情况),<FONT face="Times New Roman">init</FONT>现在就有三个文件描述符――标准输入、标准输出以及标准错误――全都加载在系统控制台之上。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20067</FONT>:如果内核命令行中给出了到<FONT face="Times New Roman">init</FONT>的直接路径(或者别的可替代的程序),现在就试图执行<B normal"><FONT face="Times New Roman">init</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>因为当<B normal"><FONT face="Times New Roman">execve</FONT></B>成功执行目标程序时并不返回,只有当前面的所有处理过程都失败时,才能执行相关的表达式。接下来的几行在几个地方查找<FONT face="Times New Roman">init</FONT>,按照可能性由高到低的顺序依次是:首先是<FONT face="Times New Roman">/sbin/init</FONT>,这是<FONT face="Times New Roman">init</FONT>标准的位置;接下来是两个可能的位置,<FONT face="Times New Roman">/etc/init</FONT>和<FONT face="Times New Roman">/bin/init</FONT>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20072</FONT>:这些是<FONT face="Times New Roman">init</FONT>可能出现的所有地方。如果现在还没有出现,<B normal"><FONT face="Times New Roman">init</FONT></B>就无法找到它的这个同名者了,机器可能就崩溃了。因此,它就会试图建立一个交互的<FONT face="Times New Roman">shell</FONT>(<FONT face="Times New Roman">/bin/sh</FONT>)来代替。现在<B normal"><FONT face="Times New Roman">init</FONT></B>最后的希望就是<FONT face="Times New Roman">root</FONT>用户可以修复这种错误并重新启动机器。(可以肯定,<FONT face="Times New Roman">root</FONT>也正是希望如此。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">20073</FONT>:<B normal"><FONT face="Times New Roman">init</FONT></B>甚至不能创建<FONT face="Times New Roman">shell</FONT>――一定是发生了什么问题!好,按照它们所说的,当所有其它情况都失败时,调用<B normal"><FONT face="Times New Roman">panic</FONT></B>(<FONT face="Times New Roman">25563</FONT>行)。这样内核就会试图同步磁盘,确保其状态一致,然后暂停进程的执行。如果超过了内核选项中定义的时间,它也可能会重新启动机器。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><p><FONT face="Times New Roman"> </FONT></p></P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman"> </FONT>第<FONT face="Times New Roman">5</FONT>章<FONT face="Times New Roman"> </FONT>系统调用</H1>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">大部分介绍<FONT face="Times New Roman">Unix</FONT>内核的书籍都没有仔细说明系统调用,我认为这是一个失误。实际上,我们实际需要的系统调用现在已经十分完美。因此,从某种意义上来说,研究系统调用的实现是无意义的——如果你想为<FONT face="Times New Roman">Linux</FONT>内核的改进贡献自己的力量,还有其它许多方面更值得投入精力。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">然而,对于我们来说,仔细研究少量系统调用是十分值得的。这样就有机会初步了解一些概念,这些概念将随本书发展而进行详细介绍,例如进程处理和内存。这使得你可以趁机详细了解一下<FONT face="Times New Roman">Linux</FONT>内核编程的特点。这包括一些和你过去在学校里(或工作中)所学的内容不同的方法。和其它编程任务相比,<FONT face="Times New Roman">Linux</FONT>内核编程的一个显著特点是它不断同三个成见进行斗争——这三个成见就是速度、正确和清晰——我们不可能同时获取这三个方面<FONT face="Times New Roman">…</FONT>至少并不总是能够。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">什么是系统调用</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">系统调用发生在用户进程(比如<FONT face="Times New Roman">emacs</FONT>)通过调用特殊函数(例如<B normal"><FONT face="Times New Roman">open</FONT></B>)以请求内核提供服务的时候。在这里,用户进程被暂时挂起。内核检验用户请求,尝试执行,并把结果反馈给用户进程,接着用户进程重新启动,随后我们就将详细讨论这种机制。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">系统调用负责保护对内核所管理的资源的访问,系统调用中的几个大类主要有:处理<FONT face="Times New Roman">I/O</FONT>请求(<B normal"><FONT face="Times New Roman">open</FONT></B>,<B normal"><FONT face="Times New Roman">close</FONT></B>,<B normal"><FONT face="Times New Roman">read</FONT></B>,<B normal"><FONT face="Times New Roman">write</FONT></B>,<B normal"><FONT face="Times New Roman">poll</FONT></B>等等),进程(<B normal"><FONT face="Times New Roman">fork</FONT></B>,<B normal"><FONT face="Times New Roman">execve</FONT></B>,<B normal"><FONT face="Times New Roman">kill</FONT></B>,等等),时间(<B normal"><FONT face="Times New Roman">time</FONT></B>,<B normal"><FONT face="Times New Roman">settimeofday</FONT></B>等等)以及内存(<B normal"><FONT face="Times New Roman">mmap</FONT></B>,<B normal"><FONT face="Times New Roman">brk</FONT></B>,等等)的系统调用。几乎所有的系统调用都可以归入这几类中。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">然而,从根本上来说,系统调用可能和它表面上有所不同。首先,在<FONT face="Times New Roman">Linux</FONT>中,<FONT face="Times New Roman">C</FONT>库中对于一些系统调用的实现是建立在其它系统调用的基础之上的。例如,<B normal"><FONT face="Times New Roman">waitpid</FONT></B>是通过简单调用<B normal"><FONT face="Times New Roman">wait4</FONT></B>实现的,但是它们两个都是作为独立的系统调用说明的。其它的传统系统调用,如<B normal"><FONT face="Times New Roman">sigmask</FONT></B>和<B normal"><FONT face="Times New Roman">ftime</FONT></B>是由<FONT face="Times New Roman">C</FONT>库而不是由<FONT face="Times New Roman">Linux</FONT>内核本身实现的;即使不是全部,至少大部分是如此。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">当然,从技巧的一面来看这是无害的——从应用程序的观点来看,系统调用就和其它的函数调用一样。只要结果符合预计的情况,应用程序就不能确定是否真正使用到了内核。(这种处理方式还有一个潜在的优点:用户可以直接触发的内核代码越少,出现安全漏洞的机会也就越少。)但是,由于使用这种技巧所引起的困扰将会使我们的讨论更为困难。实际上,系统调用这一术语通常被演讲者用来说明在第一个<FONT face="Times New Roman">Unix</FONT>版本中的任何对系统的调用。但是在本章中我们只对“真正”的系统调用感兴趣——真正的系统调用至少包括用户进程对部分内核代码的调用。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">系统调用必须返回<B normal"><FONT face="Times New Roman">int</FONT></B>的值,并且也只能返回<B normal"><FONT face="Times New Roman">int</FONT></B>的值。为了方便起见,返回值如果为零或者为正,就说明调用成功;为负则说明发生了错误。就像老练的<FONT face="Times New Roman">C</FONT>程序员所知道的一样,当标准<FONT face="Times New Roman">C</FONT>库中的函数发生错误时会通过设置全局整型变量<B normal"><FONT face="Times New Roman">errno</FONT></B>指明发生错误的属性,系统调用的原理和它相同。然而,仅仅研究内核源程序代码并不能够获得这种系统调用方式的全部意义。如果发生了错误,系统调用简单返回自己所期望的负数错误号,其余部分则由标准<FONT face="Times New Roman">C</FONT>库实现。(正常情况下,用户代码并不直接调用内核系统函数,而是要通过标准<FONT face="Times New Roman">C</FONT>库中专门负责翻译的一个小层次(<FONT face="Times New Roman">thin layer</FONT>)实现。)我们随便举一个例子,<FONT face="Times New Roman">27825</FONT>行(<B normal"><FONT face="Times New Roman">sys_nanosleep</FONT></B>的一部分)返回<B normal"><FONT face="Times New Roman">-EINVAL</FONT></B>指明所提供的值越界了。标准<FONT face="Times New Roman">C</FONT>库中实际处理<B normal"><FONT face="Times New Roman">sys_nanosleep</FONT></B>的代码会注意到返回的负值,从而设置<B normal"><FONT face="Times New Roman">errno</FONT></B>和<B normal"><FONT face="Times New Roman">EINVAL</FONT></B>,并且自己返回<FONT face="Times New Roman">-1</FONT>给原始的调用者。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在最近的内核版本中,系统调用返回负值偶尔也不一定表示错误了。在目前的几个系统调用中(例如<B normal"><FONT face="Times New Roman">lseek</FONT></B>),即使结果正确也会返回一个很大的负值。最近,错误返回值是在<FONT face="Times New Roman">-1</FONT>到<FONT face="Times New Roman">-4095</FONT>范围之内。现在,标准<FONT face="Times New Roman">C</FONT>库实现能够以更加成熟和高级的方式解释系统调用的返回值;当返回值为负时,内核本身就不用再做任何特殊的处理了。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>中断、内核空间和用户空间</FONT></H3>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">我们将在第<FONT face="Times New Roman">6</FONT>章中介绍中断和在第<FONT face="Times New Roman">8</FONT>章中介绍内存时再次明确这些概念。但是在本章中,我们只需要粗略地了解一些术语。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">第一个术语是中断(<FONT face="Times New Roman">interrupt</FONT>),它来源于两个方面:硬件中断,例如磁盘指明其中存放一些数据(这与本章无关);和软件中断,一种等价的软件机制。在<FONT face="Times New Roman">x86</FONT>系列<FONT face="Times New Roman">CPU</FONT>中,软件中断是用户进程通知内核需要触发系统调用的基本方法(出于这种目的使用的中断号是<FONT face="Times New Roman">0x80</FONT>,对于<FONT face="Times New Roman">Intel</FONT>芯片的研究者来说更为熟悉的是<FONT face="Times New Roman">INT 80</FONT>)。内核通过<B normal"><FONT face="Times New Roman">system_call</FONT></B>(<FONT face="Times New Roman">171</FONT>行)函数响应中断,这一点我们马上就会介绍。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">另外两个术语是内核空间(<FONT face="Times New Roman">kernel space</FONT>)和用户空间(<FONT face="Times New Roman">user space</FONT>),它们分别对应内核保留的内存和用户进程保留的内存。当然,多用户进程也经常同时运行,而且各个进程之间通常不会共享它们的内存,但是,任何一个用户进程使用的内存都称为用户空间。内核在某一个时刻通常只和一个用户进程交互,因此实际上不会引起任何混乱。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">由于这些内存空间是相互独立的,用户进程根本不能直接访问内核空间,内核也只能通过<B normal"><FONT face="Times New Roman">put_user</FONT></B>(<FONT face="Times New Roman">13278</FONT>行)和<B normal"><FONT face="Times New Roman">get_user</FONT></B>(<FONT face="Times New Roman">13254</FONT>行)宏和类似的宏才可以访问用户空间。因为系统调用是进程和进程所运行的操作系统之间的接口,所以系统调用需要频繁地和用户空间交互,因此这些宏也就会不时的在系统调用中出现。在通过数值传递参数的情况下并不需要它们,但是当用户把指针——内核通过这个指针进行读写——传递给系统调用时,就需要这些宏了。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">如何激活系统调用</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">系统调用的的激活有两种方法:<B normal"><FONT face="Times New Roman">system_call</FONT></B>函数和<B normal"><FONT face="Times New Roman">lcall7</FONT></B>调用门(<FONT face="Times New Roman">call gate</FONT>)(请参看<FONT face="Times New Roman">135</FONT>行)。(你可能听说过还有一种机制,<B normal"><FONT face="Times New Roman">syscall</FONT></B>函数,是通过调用<B normal"><FONT face="Times New Roman">lcall7</FONT></B>实现的——至少在<FONT face="Times New Roman">x86</FONT>平台上是如此——因此,它并不是一个特有的方法。)本节将细致地讨论一下这两种机制。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在阅读的过程中请注意系统调用本身并不关心它们是由<B normal"><FONT face="Times New Roman">system_call</FONT></B>还是由<B normal"><FONT face="Times New Roman">lcall7</FONT></B>激活的。这种把系统调用和其实现方式区别开来的方法是十分精巧的。这样,如果出于某种原因我们不得不增加一种激活系统调用的方法,我们也不必修改系统调用本身来支持这种方法。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在你浏览这些汇编代码之前要注意这些机器指令中操作数的顺序和普通<FONT face="Times New Roman">Intel</FONT>的次序相反。虽然还有一些其它的语法区别,但是操作数反序是最令人迷惑的。如果你还记得<FONT face="Times New Roman">Intel</FONT>的语法:</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">mov eax, 0</FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 0cm">(本句代码的意思是把常数<FONT face="Times New Roman">0</FONT>传送到寄存器<FONT face="Times New Roman">EAX</FONT>中)在这里应该写作:</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">mov1 $0, %eax</FONT></P>< 0cm 0cm 0pt; TEXT-INDENT: 0cm">这样你就能够正确通过。(内核使用的语法是<FONT face="Times New Roman">AT&amp;T</FONT>的汇编语法。在<FONT face="Times New Roman">GNU</FONT>汇编文档中有更多资料。)</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman" size=5>system_call</FONT></H3>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><B normal"><FONT face="Times New Roman">system_call</FONT></B>(<FONT face="Times New Roman">171</FONT>行)是所有系统调用的入口点(这是对于内部代码来说的;<B normal"><FONT face="Times New Roman">lcall7</FONT></B>用来支持<FONT face="Times New Roman">iBCS2</FONT>,这一点我们很快就会讨论)。正如前面标题注释中说明的一样,目的是为普通情况简单地实现直接的流程,不采用跳转,因此函数的各个部分都是离散的——整体的流量控制已经因为要避免普通情况下的多分支而变得非常复杂。(分支的避免是十分值得的,因为它们引起的代价非常昂贵。它们可以清空<FONT face="Times New Roman">CPU</FONT>管道,使现存<FONT face="Times New Roman">CPU</FONT>的并行加速机制失效。)</P>< 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><wrapblock><v:shapetype><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path connecttype="rect" gradientshapeok="t" extrusionok="f"></v:path><lock aspectratio="t" v:ext="edit"></lock></v:shapetype><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all>图<FONT face="Times New Roman">5.1  <B normal">system_call</B></FONT>的流程控制</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><p><FONT face="Times New Roman"> </FONT></p></P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">图<FONT face="Times New Roman">5.1</FONT>显示了作为<B normal"><FONT face="Times New Roman">system_call</FONT></B>的一部分出现的分支目标标签以及它们之间的流程控制方向,该图可以在你阅读本部分讨论内容时提供很大的帮助。图中<B normal"><FONT face="Times New Roman">system_call</FONT></B>和<B normal"><FONT face="Times New Roman">restore_all</FONT></B>两个标签比其它标签都要大,因为这两处是该函数正常的出口点和入口点;然而,还有另外两个入口点,这一点在本章的后续内容中很快就可以看到。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><B normal"><FONT face="Times New Roman">system_call</FONT></B>是由标准<FONT face="Times New Roman">C</FONT>库激活的,该标准<FONT face="Times New Roman">C</FONT>库会把自己希望传递的参数装载到<FONT face="Times New Roman">CPU</FONT>寄存器中,并触发<FONT face="Times New Roman">0x80</FONT>软件中断。(<B normal"><FONT face="Times New Roman">system_call</FONT></B>在这里是一个中断处理程序。)内核记录了软件中断和<FONT face="Times New Roman">6828</FONT>行的<B normal"><FONT face="Times New Roman">system_call</FONT></B>函数的联系(<B normal"><FONT face="Times New Roman">SYSCALL_VECTOR</FONT></B>是在<FONT face="Times New Roman">1713</FONT>行宏定义为<FONT face="Times New Roman">0x80</FONT>的)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">system_call</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">172</FONT>:<FONT face="Times New Roman">  <B normal">system_call</B></FONT>的第一个参数是所希望激活的系统调用的数目;它存储在<FONT face="Times New Roman">EAX</FONT>寄存器中。<B normal"><FONT face="Times New Roman">system_call</FONT></B>还允许有多达四个的参数和系统调用一起传送。在一些极其罕见的情况下使用四个参数的限制是负担繁重的,通常可以建立一个指向结构的指针参数来巧妙地完成同样功能,指针指向的结构中可以包含你所需要的一切信息。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>随后可能需要<FONT face="Times New Roman">EAX</FONT>值的一个额外拷贝,因此通过将其压栈而保存起来;这个值就是<FONT face="Times New Roman">218</FONT>行的<B normal"><FONT face="Times New Roman">ORIG_EAX</FONT></B>(<FONT face="Times New Roman">%<B normal">esp</B></FONT>)表达式的值。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">173</FONT>:<FONT face="Times New Roman">   <B normal">SAVE_ALL</B></FONT>宏是在<FONT face="Times New Roman">85</FONT>行定义的;它把所有寄存器的值压入<FONT face="Times New Roman">CPU</FONT>的堆栈。随后,就在<B normal"><FONT face="Times New Roman">system_call</FONT></B>返回之前,使用<B normal"><FONT face="Times New Roman">RESTALL_ALL</FONT></B>(<FONT face="Times New Roman">100</FONT>行)把栈中的值弹出。在这中间,<B normal"><FONT face="Times New Roman">system_call</FONT></B>可以根据需要自由使用寄存器的值。更重要的是,任何它所调用的<FONT face="Times New Roman">C</FONT>函数都可以从栈中查找到所希望的参数,因为<B normal"><FONT face="Times New Roman">SAVE_ALL</FONT></B>已经把所有寄存器的值都压入栈中了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>结果栈的结构从<FONT face="Times New Roman">26</FONT>行开始描述。象<B normal"><FONT face="Times New Roman">0</FONT></B><B normal">(<FONT face="Times New Roman">%esp</FONT></B><B normal">)</B>和<B normal"><FONT face="Times New Roman">4</FONT></B><B normal">(<FONT face="Times New Roman">%esp</FONT></B><B normal">)</B>一样的表达式指明了堆栈指针(<FONT face="Times New Roman">ESP</FONT>寄存器)的一种替换形式——分别表示<FONT face="Times New Roman">ESP</FONT>上的<FONT face="Times New Roman">0</FONT>字节,<FONT face="Times New Roman">ESP</FONT>上的<FONT face="Times New Roman">4</FONT>字节,等等。特别要注意的是在前面一行中压入堆栈的<FONT face="Times New Roman">EAX</FONT>的拷贝已经变成本标题注释作为<B normal"><FONT face="Times New Roman">orig_eax</FONT></B>所描述的内容;它们是由<B normal"><FONT face="Times New Roman">SAVE_ALL</FONT></B>压入寄存器之上的堆栈的(<B normal"><FONT face="Times New Roman">orig_eax</FONT></B>之上的寄存器在这里早已就绪了)。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>还需注意:这可能有点令人迷惑——由于我们调用<B normal"><FONT face="Times New Roman">orig_eax</FONT></B>时<FONT face="Times New Roman">EAX</FONT>的拷贝已经压入了堆栈,它是否有可能在其它寄存器下面而不是在其它寄存器上面呢?答案既是肯定的,也是否定的。<FONT face="Times New Roman">x86</FONT>的堆栈指针寄存器<FONT face="Times New Roman">ESP</FONT>在有数据压入堆栈时会减少——堆栈会向内存低地址发展。因此,<B normal"><FONT face="Times New Roman">orig_eax</FONT></B>逻辑上是在其它值的下面,但是物理上却是在其它值的上面。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>从<FONT face="Times New Roman">51</FONT>行开始的一系列宏有助于使这些替换更容易理解。例如,<B normal"><FONT face="Times New Roman">EAX</FONT></B><B normal">(<FONT face="Times New Roman">%esp</FONT></B><B normal">)</B>就和<B normal"><FONT face="Times New Roman">18</FONT></B><B normal">(<FONT face="Times New Roman">%esp</FONT></B><B normal">)</B>相同——然而前一种方法通过表达式引用存储在堆栈中的<FONT face="Times New Roman">EAX</FONT>寄存器副本的决定可以使整个过程更加简单。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">174</FONT>:<FONT face="Times New Roman">   </FONT>从<FONT face="Times New Roman">EBX</FONT>寄存器中取得指向当前任务的指针。完成这个工作的宏<B normal"><FONT face="Times New Roman">GET_CURRENT</FONT></B>(<FONT face="Times New Roman">131</FONT>行)对于在大部分代码中使用的<FONT face="Times New Roman">C</FONT>函数<B normal"><FONT face="Times New Roman">get_current</FONT></B>(<FONT face="Times New Roman">10277</FONT>行)来说是一个无限循环。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>此后,当看到类似于<B normal"><FONT face="Times New Roman">foo</FONT></B><B normal">(<FONT face="Times New Roman">%ebx</FONT></B><B normal">)</B>或者<B normal"><FONT face="Times New Roman">foo</FONT></B><B normal">(<FONT face="Times New Roman">%esp</FONT></B><B normal">)</B>的表达式时,这意味着这些的代码正在引用代表当前进程的结构的字段——<FONT face="Times New Roman">16325</FONT>行的<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>——这在第<FONT face="Times New Roman">7</FONT>章中将对它进行更详细的介绍。(更确切的描述是,<FONT face="Times New Roman"> <B normal">%ebx</B></FONT>的置换在<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>中,<B normal"><FONT face="Times New Roman">%esp</FONT></B>的置换在与<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>相关联的<B normal"><FONT face="Times New Roman">struct pt_regs</FONT></B>结构中。但是这些细节在这里都并不重要。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">175</FONT>:<FONT face="Times New Roman">   </FONT>检查(<FONT face="Times New Roman">EAX</FONT>中的)系统调用的数目是否超过系统调用的最大数量。(此处<FONT face="Times New Roman">EAX</FONT>为一个无符号数,因此不可能为负值。)如果的确超过了,就向前跳转到<B normal"><FONT face="Times New Roman">badsys</FONT></B>(<FONT face="Times New Roman">223</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">177</FONT>:<FONT face="Times New Roman">   </FONT>检测系统调用是否正被跟踪。如<FONT face="Times New Roman">strace</FONT>之类的程序为有兴趣的人提供了系统调用的跟踪工具,或者额外的调试信息:如果能够监测到正在执行的系统调用,那么你就可以了解到当前程序正在处理的内容。如果系统调用正被跟踪,控制流程就向前跳转到<B normal"><FONT face="Times New Roman">tracesys</FONT></B>(<FONT face="Times New Roman">215</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">179</FONT>:<FONT face="Times New Roman">   </FONT>调用系统函数。此处有很多工作需要处理。首先,<B normal"><FONT face="Times New Roman">SYSMOL_NAME</FONT></B>宏不处理任何工作,只是简单的为参数文本所替换,因此可以将其忽略。<B normal"><FONT face="Times New Roman">sys_call_table</FONT></B>是在当前文件(<FONT face="Times New Roman">arch/i386/kernel/entry.S</FONT>)的末尾从<FONT face="Times New Roman">373</FONT>行开始定义的。这是一张由指向实现各种系统调用的内核函数的函数指针组成的表。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>本行中第二对圆括号中包含了三个使用逗号分割开的参数(第一个参数为空);这里就是实现数组索引的地方。当然,这个数组是以<B normal"><FONT face="Times New Roman">sys_call_table</FONT></B>作为索引的,这称为偏移(<FONT face="Times New Roman">displacement</FONT>)。这三个参数是数组的基地址、索引(<FONT face="Times New Roman">EAX</FONT>,系统调用的数目)和大小,或者每个数组元素中的字节数——在这里就是<FONT face="Times New Roman">4</FONT>。由于数组基地址为空,就将其当作<FONT face="Times New Roman">0</FONT>——但是它要和偏移地址,<B normal"><FONT face="Times New Roman">sys_call_table</FONT></B><B normal">,</B>相加,简单的说就是<B normal"><FONT face="Times New Roman">sys_call_table</FONT></B>被当作数组的基地址。本行基本上等同于如下的<FONT face="Times New Roman">C</FONT>表达式:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            /* Call a function in an array of functions. */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            (sys_call_table[eax])();</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>然而,<FONT face="Times New Roman"> C</FONT>当然还要处理许多繁重的工作,例如为你记录数组元素的大小。不要忘记,系统调用的参数早已经存储在堆栈中了,这主要由调用者提供给<B normal"><FONT face="Times New Roman">system_call</FONT></B>并使用<B normal"><FONT face="Times New Roman">SAVE_ALL</FONT></B>把它们压栈。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">180</FONT>:<FONT face="Times New Roman">   </FONT>系统调用已经返回。它在<FONT face="Times New Roman">EAX</FONT>寄存器中的返回值(这个值同时也是<B normal"><FONT face="Times New Roman">system_call</FONT></B>的返回值)被存储起来。返回值被存储在堆栈中的<FONT face="Times New Roman">EAX</FONT>内,以使得<B normal"><FONT face="Times New Roman">RESTORE_ALL</FONT></B>可以迅速地恢复实际的<FONT face="Times New Roman">EAX</FONT>寄存器以及其它寄存器的值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">182</FONT>:<FONT face="Times New Roman">   </FONT>接下来的代码仍然是<B normal"><FONT face="Times New Roman">system_call</FONT></B>的一部分,它是一个也可以命名为<B normal"><FONT face="Times New Roman">ret_from_sys_call</FONT></B>和<B normal"><FONT face="Times New Roman">ret_from_intr</FONT></B>的独立入口点。它们偶尔会被<FONT face="Times New Roman">C</FONT>直接调用,也可以从<B normal"><FONT face="Times New Roman">system_call</FONT></B>的其它部分跳转过来。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">185</FONT>:<FONT face="Times New Roman">   </FONT>接下来的几行检测“下半部分(<FONT face="Times New Roman">bottom half</FONT>)”是否激活;如果激活了,就跳转到<B normal"><FONT face="Times New Roman">handle_bottom_half</FONT></B>标号(<FONT face="Times New Roman">242</FONT>行)并立即开始处理。下半部分是中断进程的一部分,将在下一章中讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">189</FONT>:<FONT face="Times New Roman">   </FONT>检查该进程是否为再次调度做了标记(记住表达式<B normal"><FONT face="Times New Roman">$0</FONT></B>就是常量<FONT face="Times New Roman">0</FONT>的系统简单表示)。如果的确如此,就跳转到<B normal"><FONT face="Times New Roman">reschedule</FONT></B>标号(<FONT face="Times New Roman">247</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">191</FONT>:<FONT face="Times New Roman">   </FONT>检测是否还有挂起的信号量,如果有的话,下一行就向前跳转到<B normal"><FONT face="Times New Roman">signal_return</FONT></B>(<FONT face="Times New Roman">197</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">193</FONT>:<FONT face="Times New Roman">   <B normal">restore_all</B></FONT>标号是<B normal"><FONT face="Times New Roman">system_call</FONT></B>的退出点。其主体就是简单的<B normal"><FONT face="Times New Roman">RESTORE_ALL</FONT></B>宏(<FONT face="Times New Roman">100</FONT>行),该宏将恢复早先由<B normal"><FONT face="Times New Roman">SAVE_ALL</FONT></B>存储的参数并返回给<B normal"><FONT face="Times New Roman">system_call</FONT></B>的调用者。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">197</FONT>:<FONT face="Times New Roman">   </FONT>当<B normal"><FONT face="Times New Roman">system_call</FONT></B>从系统调用返回前,如果它检测到需要将信号量传送给当前的进程时,才会执行到<B normal"><FONT face="Times New Roman">signal_return</FONT></B>。它通过使中断再次可用开始执行,有关内容将在第<FONT face="Times New Roman">6</FONT>章中介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">199</FONT>:<FONT face="Times New Roman">   </FONT>如果返回虚拟<FONT face="Times New Roman">8086</FONT>模式(这不是本书的主题),就向前跳转到<B normal"><FONT face="Times New Roman">v86_signal_return</FONT></B>(<FONT face="Times New Roman">207</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">202</FONT>:<FONT face="Times New Roman">   <B normal">system_call</B></FONT>要调用<FONT face="Times New Roman">C</FONT>函数<B normal"><FONT face="Times New Roman">do_signal</FONT></B>(<FONT face="Times New Roman">3364</FONT>行,在第<FONT face="Times New Roman">6</FONT>章中讨论)来释放信号量。<B normal"><FONT face="Times New Roman">do_signal</FONT></B>需要两个参数,这两个参数都是通过寄存器传递的;第一个是<FONT face="Times New Roman">EAX</FONT>寄存器,另一个是<FONT face="Times New Roman">EDX</FONT>寄存器。<B normal"><FONT face="Times New Roman">system_call</FONT></B>(在<FONT face="Times New Roman">200</FONT>行)早已把第一个参数的值赋给了<FONT face="Times New Roman">EAX</FONT>;现在,就把<FONT face="Times New Roman">EDX</FONT>寄存器和寄存器本身进行<FONT face="Times New Roman">XOR</FONT>操作,从而将其清<FONT face="Times New Roman">0</FONT>,这样<B normal"><FONT face="Times New Roman">do_signal</FONT></B>就认为这是一个空指针。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">203</FONT>:<FONT face="Times New Roman">   </FONT>调用<B normal"><FONT face="Times New Roman">do_signal</FONT></B>传递信号量,并且跳回到<B normal"><FONT face="Times New Roman">restore_all</FONT></B>(<FONT face="Times New Roman">193</FONT>行)结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">207</FONT>:<FONT face="Times New Roman">   </FONT>由于虚拟<FONT face="Times New Roman">8086</FONT>模式不是本书的主题,我们将忽略大部分<B normal"><FONT face="Times New Roman">v86_signal_return</FONT></B>。然而,它和<B normal"><FONT face="Times New Roman">signal_return</FONT></B>的情况非常类似。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">215</FONT>:<FONT face="Times New Roman">   </FONT>如果当前进程的系统调用正由其祖先跟踪,就像<FONT face="Times New Roman">strace</FONT>程序中那样,那么就可以执行到<B normal"><FONT face="Times New Roman">tracesys</FONT></B>标号。这一部分的基本思想如同<FONT face="Times New Roman">179</FONT>行一样是通过<B normal"><FONT face="Times New Roman">syscall_table</FONT></B>调用系统函数,但是这里把该调用和对<B normal"><FONT face="Times New Roman">syscall_trace</FONT></B>函数的调用捆绑在一起。后面的这个函数在本书中并没有涉及到,它能够中止当前进程并通知其祖先注意当前进程将要激活一个系统调用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            EAX</FONT>操作和这些代码的交错使用最初可能容易令人产生困惑。<B normal"><FONT face="Times New Roman">system_call</FONT></B>把存储在堆栈中的<FONT face="Times New Roman">EAX</FONT>拷贝赋给<B normal"><FONT face="Times New Roman">-ENOSYS</FONT></B>,调用<B normal"><FONT face="Times New Roman">syscall_trace</FONT></B>,在<FONT face="Times New Roman">172</FONT>行再从所做的备份中恢复<FONT face="Times New Roman">EAX</FONT>的值,调用实际的系统调用,把系统调用的返回值置入堆栈中<FONT face="Times New Roman">EAX</FONT>的位置,再次调用<B normal"><FONT face="Times New Roman">syscall_trace</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这种方式背后的原因是<B normal"><FONT face="Times New Roman">syscall_trace</FONT></B>(或者更准确的说是它所要使用到的跟踪程序)需要知道在它是在实际系统调用之前还是之后被调用的。<B normal"><FONT face="Times New Roman">-ENOSYS</FONT></B>的值能够用来指示它是在实际系统调用执行之前被调用的,因为实际中所有实现的系统调用的执行都不会返回<B normal"><FONT face="Times New Roman">-ENOSYS</FONT></B>。因此,<FONT face="Times New Roman">EAX</FONT>在堆栈中的备份在第一次调用<B normal"><FONT face="Times New Roman">syscall_trace</FONT></B>之前是<B normal"><FONT face="Times New Roman">-ENOSYS</FONT></B>,但是在第二次调用<B normal"><FONT face="Times New Roman">syscall_trace</FONT></B>之前就不再是了(除非是调用<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>的时候,在这种情况下,我们并不关心是怎样跟踪的)。<FONT face="Times New Roman">218</FONT>行和<FONT face="Times New Roman">219</FONT>行中<FONT face="Times New Roman">EAX</FONT>的作用只是找出要调用的系统调用,这和无须跟踪的情况是一致的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">222</FONT>:<FONT face="Times New Roman">   </FONT>被跟踪的系统调用已经返回;流程控制跳转回<B normal"><FONT face="Times New Roman">ret_from_sys_call</FONT></B>(<FONT face="Times New Roman">184</FONT>行)并以与普通的无须跟踪的情况相同的方式结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">223</FONT>:<FONT face="Times New Roman">   </FONT>当系统调用的数目越界时,就可以执行到<B normal"><FONT face="Times New Roman">badsys</FONT></B>标号。在这种情况下,<B normal"><FONT face="Times New Roman">system_call</FONT></B>必须返回<B normal"><FONT face="Times New Roman">-ENOSYS</FONT></B>(<B normal"><FONT face="Times New Roman">ENOSYS</FONT></B>在<FONT face="Times New Roman">82</FONT>行将它赋值为<FONT face="Times New Roman">38</FONT>)。正如前面提到的一样,调用者会识别出这是一个错误,因为返回值在<FONT face="Times New Roman">-1</FONT>到<FONT face="Times New Roman">-4,095</FONT>之间。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">228</FONT>:<FONT face="Times New Roman">   </FONT>在诸如除零错误(请参看<FONT face="Times New Roman">279</FONT>行)之类的<FONT face="Times New Roman">CPU</FONT>异常中断情况下将执行到<B normal"><FONT face="Times New Roman">ret_from_exception</FONT></B>标号;但是<B normal"><FONT face="Times New Roman">system_call</FONT></B>内部的所有代码都不会执行到这个标号。如果有下半部分是激活的,现在就是它在起作用了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">233</FONT>:<FONT face="Times New Roman">   </FONT>处理完下半部分之后或者从上面的情况简单的执行下来(虽然没有下半部分是激活的,但是同样也触发了<FONT face="Times New Roman">CPU</FONT>异常),就执行到了<B normal"><FONT face="Times New Roman">ret_from_intr</FONT></B>标号。这是一个全局符号变量,因此可能在内核的其它部分也会有对它的调用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">237</FONT>:<FONT face="Times New Roman">   </FONT>被保存的<FONT face="Times New Roman">CPU</FONT>的<FONT face="Times New Roman">EFLAGS</FONT>和<FONT face="Times New Roman">CS</FONT>寄存器在此已经被并入<FONT face="Times New Roman">EAX</FONT>,因而高<FONT face="Times New Roman">24</FONT>位的值(其中恰好包含了一位在<FONT face="Times New Roman">70</FONT>行定义的非常有用的<B normal"><FONT face="Times New Roman">VM_MASK</FONT></B>)来源于<FONT face="Times New Roman">EFLAGS</FONT>,其它低<FONT face="Times New Roman">8</FONT>位的值来源于<FONT face="Times New Roman">CS</FONT>。该行隐式的同时对这两部分进行测试以判断进程到底返回虚拟<FONT face="Times New Roman">8086</FONT>模式(这是<B normal"><FONT face="Times New Roman">VM_MASK</FONT></B>的部分)还是用户模式(这是<FONT face="Times New Roman">3</FONT>的部分——用户模式的优先等级是<FONT face="Times New Roman">3</FONT>)。下面是近似的等价<FONT face="Times New Roman">C</FONT>代码:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            /* Mix eflags and cs in eax. */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            eax = eflags &amp; ~0xff;</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            eax |= cs &amp; ~0xff</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            /* Simultaneously test lower 2 bits </FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">       * and VM_MASK bit. */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            if  (eax &amp; (VM_MASK | 3))</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">               goto ret_with_reschedule;</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            goto restore_all;           </FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">238</FONT>:<FONT face="Times New Roman">   </FONT>如果这些条件中有一个能得到满足,流程控制就跳转到<B normal"><FONT face="Times New Roman">ret_with_reschedule</FONT></B>(<FONT face="Times New Roman">188</FONT>行)标号来测试在<B normal"><FONT face="Times New Roman">system_call</FONT></B>返回之前进程是否需要再次调度。否则,调用者就是一个内核任务,因此<B normal"><FONT face="Times New Roman">system_call</FONT></B>通过跳转到<FONT face="Times New Roman"><B normal">restore_all </B>(193</FONT>行<FONT face="Times New Roman">)</FONT>来跳过重新调度的内容。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">242</FONT>:<FONT face="Times New Roman">   </FONT>无论何时<B normal"><FONT face="Times New Roman">system_call</FONT></B>使用一个下半部分服务时都可以执行到<B normal"><FONT face="Times New Roman">handle_bottom_half</FONT></B>标号。它简单的调用第<FONT face="Times New Roman">6</FONT>章中介绍的<FONT face="Times New Roman">C</FONT>函数<B normal"><FONT face="Times New Roman">bottom_half</FONT></B>(<FONT face="Times New Roman">29126</FONT>行),然后跳回到<B normal"><FONT face="Times New Roman">ret_from_intrr</FONT></B>(<FONT face="Times New Roman">233</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">248</FONT>:<FONT face="Times New Roman">   <B normal">system_call</B></FONT>的最后一个部分在<B normal"><FONT face="Times New Roman">reschedule</FONT></B>标号之下。当产生系统调用的进程已经被标记为需要进行重新调度时,就可以执行到这个标号;典型地,这是因为进程的时间片已经用完了——也就是说,进程到目前为止已经尽可能的拥有<FONT face="Times New Roman">CPU</FONT>了,应该给其它进程一个机会来运行了。因此,在必要的情况下就可以调用<FONT face="Times New Roman">C</FONT>函数<B normal"><FONT face="Times New Roman">schedule</FONT></B>(<FONT face="Times New Roman">26686</FONT>行)交出<FONT face="Times New Roman">CPU</FONT>,同时流程控制转回<FONT face="Times New Roman">249</FONT>行。<FONT face="Times New Roman">CPU</FONT>调度是第<FONT face="Times New Roman">7</FONT>章中讨论的一个主题。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT face="Times New Roman" size=5>lcall7</FONT></H3><P 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>支持<FONT face="Times New Roman">Intel</FONT>二进制兼容规范标准的版本<FONT face="Times New Roman">2</FONT>(<FONT face="Times New Roman">iBCS2</FONT>)。(<FONT face="Times New Roman">iBCS2</FONT>中的小写字母<FONT face="Times New Roman">i</FONT>显然是有意的,但是该标准却没有对此进行解释;这样看来似乎和现实的<FONT face="Times New Roman">Intel</FONT>系列的<FONT face="Times New Roman">CPU</FONT>例如<FONT face="Times New Roman">i386</FONT>,<FONT face="Times New Roman">i486</FONT>等等是一致的。)<FONT face="Times New Roman">iBCS2</FONT>的规范中规定了所有基于<FONT face="Times New Roman">x86</FONT>的<FONT face="Times New Roman">Unix</FONT>系统的应用程序的标准内核接口,这些系统不仅包括<FONT face="Times New Roman">Linux</FONT>,而且还包括其它自由的<FONT face="Times New Roman">x86 Unix</FONT>(例如<FONT face="Times New Roman">FreeBSD</FONT>),也还包括<FONT face="Times New Roman">Solaris/x86</FONT>,<FONT face="Times New Roman">SCO Unix</FONT>等等。这些标准接口使得为其它<FONT face="Times New Roman">Unix</FONT>系统开发的二进制商业软件在<FONT face="Times New Roman">Linux</FONT>系统中能够直接运行,反之亦然(而且,近期新开发软件向其它<FONT face="Times New Roman">Unix</FONT>移植的情况越来越多)。例如,<FONT face="Times New Roman">Corel</FONT>公司的<FONT face="Times New Roman">WordPerfect</FONT>的<FONT face="Times New Roman">SCO Unix</FONT>的二进制代码在还没有<FONT face="Times New Roman">Linux</FONT>的本地版本的<FONT face="Times New Roman">WordPerfect</FONT>之前就可以使用<FONT face="Times New Roman">iBCS2</FONT>在<FONT face="Times New Roman">Linux</FONT>上良好地运行。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">iBCS2</FONT>标准有很多组成部分,但是我们现在关心的是这些系统调用如何协调一致来适应这些迥然不同的<FONT face="Times New Roman">Unix</FONT>系统。这是通过<B normal"><FONT face="Times New Roman">lcall7</FONT></B>调用门实现的。它是一个相当简单的汇编函数(尤其是和<B normal"><FONT face="Times New Roman">system_call</FONT></B>相比而言更是如此),仅仅定位并全权委托一个<FONT face="Times New Roman">C</FONT>函数来处理细节。(调用门是<FONT face="Times New Roman">x86 CPU</FONT>的一种特性,通过这种特性用户任务可以在安全受控的模式下调用内核代码。)这种调用门在<FONT face="Times New Roman">6802</FONT>行进行设定。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">lcall7</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">136</FONT>:<FONT face="Times New Roman"> </FONT>前面的几行将通过调整处理器堆栈以使堆栈的内容和<B normal"><FONT face="Times New Roman">system_call</FONT></B>预期的相同——<B normal"><FONT face="Times New Roman">system_call</FONT></B>中的一些代码将会完成清理工作,这样所有的内容都可以连续存放了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">145</FONT>:<FONT face="Times New Roman">   </FONT>基于同样的思想,<B normal"><FONT face="Times New Roman">lcall7</FONT></B>把指向当前任务的指针置入<FONT face="Times New Roman">EBX</FONT>寄存器,这一点和<B normal"><FONT face="Times New Roman">system_call</FONT></B>的情况是相同的。但是,它的执行方式却与<B normal"><FONT face="Times New Roman">system_call</FONT></B>不同,这就比较奇怪了。这三行可以等价地按如下形式书写:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            push1 %esp</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            GET_CURRENT</FONT>(<FONT face="Times New Roman">%ebx</FONT>)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这种实现的执行速度并不比原有的更快;在将宏展开以后,实际上这还是同样的三条指令以不同的次序组合在一起而已。这样做的优点是可以和文件中的其它代码更为一致,而且代码也许会更清晰一些。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">148</FONT>:<FONT face="Times New Roman">   </FONT>取得指向当前任务<B normal"><FONT face="Times New Roman">exec_domain</FONT></B>域的指针,使用这个域以获取指向其<B normal"><FONT face="Times New Roman">lcall7</FONT></B>处理程序的指针,接着调用这个处理程序。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>本书中并没有对执行域(<FONT face="Times New Roman">execution domains</FONT>)进行详细说明——但是简单说来,内核使用执行域实现了部分<FONT face="Times New Roman">iBCS2</FONT>标准。在<FONT face="Times New Roman">15977</FONT>行你可以找到<B normal"><FONT face="Times New Roman">struct exec_domain</FONT></B>结构。<B normal"><FONT face="Times New Roman">default_exec_domain</FONT></B>(<FONT face="Times New Roman">22807</FONT>行)是缺省的执行域,它拥有一个缺省的<B normal"><FONT face="Times New Roman">lcall7</FONT></B>处理程序。它就是<B normal"><FONT face="Times New Roman">no_lcall7</FONT></B>(<FONT face="Times New Roman">22820</FONT>行)。其基本的执行方式类似于<FONT face="Times New Roman">SVR4</FONT>风格的<FONT face="Times New Roman">Unix</FONT>,如果调用进程没有成功,就传送一个分段违例信号量(<FONT face="Times New Roman">segmentation violation signal</FONT>)给调用的进程,。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">152</FONT>:<FONT face="Times New Roman">   </FONT>跳转到<B normal"><FONT face="Times New Roman">ret_from_sys_call</FONT></B>标号(<FONT face="Times New Roman">184</FONT>行——注意这是在<B normal"><FONT face="Times New Roman">system_call</FONT></B>内部的)清除并返回,就像是正常的系统调用一样。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">系统调用样例</H2><P 0cm 0cm 0pt">现在你已经知道了系统调用是如何激活的,接下来我们将通过几个系统调用例子的剖析来了解一下它们的工作方式。注意系统调用<B normal"><FONT face="Times New Roman">foo</FONT></B>几乎都是使用名为<B normal"><FONT face="Times New Roman">sys_foo</FONT></B>的内核函数实现的,但是在某些情况下该函数也会使用一个名为<B normal"><FONT face="Times New Roman">do_foo</FONT></B>的辅助函数。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_ni_syscall</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29185</FONT>:<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>的确是最简单的系统调用;它只是简单的返回<B normal"><FONT face="Times New Roman">ENOSYS</FONT></B>错误。最初的时候这可能显得没有什么作用,但是它的确是有用的。实际上,<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>在<B normal"><FONT face="Times New Roman">sys_call_table</FONT></B>中占据了很多位置——而且其原因并不只有一个。开始的时候,<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>在位置<FONT face="Times New Roman">0</FONT>(<FONT face="Times New Roman">374</FONT>行),因为如果漏洞百出的代码错误地调用了<B normal"><FONT face="Times New Roman">system_call</FONT></B>——例如,没有初始化作为参数传递给<B normal"><FONT face="Times New Roman">system_call</FONT></B>的变量——在这种偶然的变量定义中,<FONT face="Times New Roman">0</FONT>是最可能的值。如果我们能够避免这种情况,那么在错误发生时就不用采取象杀掉进程一样的剧烈措施。(当然,只要允许有用工作的进行,就不可能防止所有的错误。)这种使用表的元素<FONT face="Times New Roman">0</FONT>作为抵御错误的手段在内核中被作为良好的经验而广泛使用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>而且,你还会发现<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>在表中明显出现的地方就多达十几处。这些条目代表了那些已经从内核中移出的系统调用——例如在<FONT face="Times New Roman">418</FONT>行,就代替了已经废弃了的<B normal"><FONT face="Times New Roman">prof</FONT></B>系统调用。我们不能简单地把另外的实际系统调用放在这里,因为老的二进制代码可能还会使用到这些已经废弃了的系统调用号。如果一个程序试图调用这些老的系统调用,但是结果却与预期的完全不同,例如打开了一个文件,这会比较令人感到奇怪的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>最后,<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>将占据表尾部所有未用的空间;这一点是在从<FONT face="Times New Roman">572</FONT>行到<FONT face="Times New Roman">574</FONT>行的代码实现的,它根据需要重复使用这些项来填充表。由于<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>只是简单返回<B normal"><FONT face="Times New Roman">ENOSYS</FONT></B>错误号,对它的调用和跳转到<B normal"><FONT face="Times New Roman">system_call</FONT></B>中的<B normal"><FONT face="Times New Roman">badsys</FONT></B>标号作用是相同的——也就是说,使用指向这些表项的系统调用号和在表外对整个表进行全部索引具有相同的作用。因此,我们不用改变<B normal"><FONT face="Times New Roman">NR_syscalls</FONT></B>就可以在表中增加(或者删除)系统调用,但是其效果与我们真的对<B normal"><FONT face="Times New Roman">NR_syscalls</FONT></B>进行了修改一样(不管怎样,这都是由<B normal"><FONT face="Times New Roman">NR_syscalls</FONT></B>所建立的限制条件所决定的)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>到现在也许你已经猜到了,<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>中的“<FONT face="Times New Roman">ni</FONT>”并不是指<FONT face="Times New Roman">Monty Python</FONT>的“说<FONT face="Times New Roman"> </FONT>‘<FONT face="Times New Roman">Ni</FONT>’<FONT face="Times New Roman"> </FONT>的骑士”;而是指“<FONT face="Times New Roman">not implemented</FONT>(没有实现)”这一相较而言并不太诙谐的短语。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>对于这个简单的函数我们需要研究的另外一个问题是<B normal"><FONT face="Times New Roman">asmlinkage</FONT></B>标签。这是为一些<FONT face="Times New Roman">gcc</FONT>功能定义的一个宏,它告诉编译器该函数不希望从寄存器中(这是一种普通的优化<FONT face="Times New Roman"> </FONT>)取得任何参数,而希望仅仅从<FONT face="Times New Roman">CPU</FONT>堆栈中取得参数。回忆一下我们前面提到过<B normal"><FONT face="Times New Roman">system_call</FONT></B>使用第一个参数作为系统调用的数目,同时还允许另外四个参数和系统调用一起传递。<B normal"><FONT face="Times New Roman">system_call</FONT></B>通过把其它参数(这些参数是通过寄存器传递过来的)滞留在堆栈中的方法简单的实现了这种技巧。所有的系统调用都使用<B normal"><FONT face="Times New Roman">asmlinkage</FONT></B>标签作了标记,因此它们都要查找堆栈以获得参数。当然,在<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>的情况下这并没有任何区别,因为<B normal"><FONT face="Times New Roman">sys_ni_syscall</FONT></B>并不需要任何参数。但是对于其它大部分系统调用来说这就是个问题了。并且,由于在其它很多函数前面都有<B normal"><FONT face="Times New Roman">asmlinkage</FONT></B>标签,我想你也应该对它有些了解。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_time</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31394</FONT>:<B normal"><FONT face="Times New Roman">sys_time</FONT></B>是包含几个重要概念的简单系统调用。它实现了系统调用<B normal"><FONT face="Times New Roman">time</FONT></B>,返回值是从某个特定的时间点(<st1:chsdate w:st="on" IsROCDate="False" IsLunarDate="False" Day="1" Month="1" Year="1970"><FONT face="Times New Roman">1970</FONT>年<FONT face="Times New Roman">1</FONT>月<FONT face="Times New Roman">1</FONT>日</st1:chsdate>午夜<FONT face="Times New Roman">UTC</FONT>)以来经过的秒数。这个数字被作为全局变量<B normal"><FONT face="Times New Roman">xtime</FONT></B>(请参看<FONT face="Times New Roman">26095</FONT>行;它被声明为<B normal"><FONT face="Times New Roman">volatile</FONT></B>型的变量,因为它可以通过中断加以修改,这一点我们在第<FONT face="Times New Roman">6</FONT>章中就会看到)的一部分,通过<B normal"><FONT face="Times New Roman">CURRENT_TIME</FONT></B>宏(请参看<FONT face="Times New Roman">16598</FONT>行)可以访问它。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31400</FONT>:该函数非常直接的实现了它的简单定义。当前时间首先被存储在局部变量<B normal"><FONT face="Times New Roman">i</FONT></B>中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31402</FONT>:如果所提供的指针<B normal"><FONT face="Times New Roman">tloc</FONT></B>是非空的,返回值也将被拷贝到指针指向的位置。该函数的一个微妙之处就在于此;它把<FONT face="Times New Roman">i</FONT>拷贝到用户空间中而不是使用<B normal"><FONT face="Times New Roman">CURRENT_TIME</FONT></B>宏来重新对其进行计算,这基于两个原因:</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">CURRENT_TIME</FONT></B>宏的定义以后可能会改变,新的实现方法可能会由于某种原因而速度比较慢,但是对于<FONT face="Times New Roman">i</FONT>的访问至少应该和<B normal"><FONT face="Times New Roman">CURRENT_TIME</FONT></B>宏展开的速度同样快。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 57.25pt">l         使用这种方式处理,确保结果的一致性:如果代码刚好执行到<FONT face="Times New Roman">31400</FONT>行和<FONT face="Times New Roman">31402</FONT>行之间时时间发生了改变,<B normal"><FONT face="Times New Roman">sys_time</FONT></B>可能把一个值拷贝到<B normal"><FONT face="Times New Roman">*tloc</FONT></B>中,但是在结束之后却返回另一个值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>另外还有一个小的方面需要注意,此处的代码不使用<B normal"><FONT face="Times New Roman">&amp;&amp;</FONT></B>来编写而是使用两个<B normal"><FONT face="Times New Roman">if</FONT></B>,这可能有一点令人奇怪。内核中采用这些看起来非常特殊的代码的一般原因都是由于速度的要求,但是<FONT face="Times New Roman">gcc</FONT>为<B normal"><FONT face="Times New Roman">&amp;&amp;</FONT></B>版本和两个<B normal"><FONT face="Times New Roman">if</FONT></B>版本的代码生成的代码是等同的,因此这里的原因就不可能是速度的要求——除非这些代码是在早期<FONT face="Times New Roman">gcc</FONT>版本下开发的,这样才有些意义。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31403</FONT>:如果<B normal"><FONT face="Times New Roman">sys_time</FONT></B>不能访问所提供的位置(一般都是因为<B normal"><FONT face="Times New Roman">tloc</FONT></B>无效),它就把<B normal"><FONT face="Times New Roman">-EFAULT</FONT></B>的值赋给<B normal"><FONT face="Times New Roman">i</FONT></B>,从而在<FONT face="Times New Roman">31405</FONT>行返回错误代码。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31405</FONT>:为调用者返回的<B normal"><FONT face="Times New Roman">i</FONT></B>或者是当前时间,或者是<B normal"><FONT face="Times New Roman">-EFAULT</FONT></B>。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_reboot</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29298</FONT>:内核中其他地方可能都没有<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>的实现方法这样先进。其原因是可以理解为:根据调用的名字我们就可以知道,<B normal"><FONT face="Times New Roman">reboot</FONT></B>系统调用可以用来重新启动机器。根据所提供的参数,它还能够挂起机器,关闭电源,允许或者禁止使用<FONT face="Times New Roman">Ctrl+Alt_Del</FONT>组合键来重启机器。如果你要使用这个函数编写代码,需要特别注意它上面的注释标题的警告:首先同步磁盘,否则磁盘缓冲区中的数据可能会丢失。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>由于它可能为系统引发的潜在后果,<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>需要几个特殊参数,这一点马上就会讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29305</FONT>:如果调用者不具有<B normal"><FONT face="Times New Roman">CAP_SYS_BOOT</FONT></B>(<FONT face="Times New Roman">14096</FONT>行)权能(<FONT face="Times New Roman">capability</FONT>),系统就会返回<B normal"><FONT face="Times New Roman">EPERM</FONT></B>错误。权能在第<FONT face="Times New Roman">7</FONT>章中会详细讨论。现在,简单的说就是:权能是检测用户是否具有特定权限的方法。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29309</FONT>:在这里,这种偏执的思想充分发挥了作用。<B normal"><FONT face="Times New Roman">syst_reboot</FONT></B>根据从<FONT face="Times New Roman">16002</FONT>到<FONT face="Times New Roman">16005</FONT>行定义的特殊数字检测参数<B normal"><FONT face="Times New Roman">magic1</FONT></B>和<B normal"><FONT face="Times New Roman">magic2</FONT></B>。这种思想是如果<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>在某种程度上是被偶然调用的,那么就不太可能再从由<B normal"><FONT face="Times New Roman">magic1</FONT></B>和<B normal"><FONT face="Times New Roman">magic2</FONT></B>组成的小集合中同时提取值。注意这并不意味着这是一个防止粗心的安全措施。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>顺便说一下,这些特殊数字并不是随机选取的。第一个参数的关系是十分明显的,它是“感受死亡(<FONT face="Times New Roman">feel dead</FONT>)”的双关语。后面的三个参数要用十六进制才能了解它们全部的意思:它们分别是<FONT face="Times New Roman">0x28121969</FONT>,<FONT face="Times New Roman">0x5121996</FONT>,<FONT face="Times New Roman">0x16041998</FONT>。这似乎代表<FONT face="Times New Roman">Linus</FONT>的妻子(或者就是<FONT face="Times New Roman">Linus</FONT>自己)和他两个女儿的生日。由此推论,当<FONT face="Times New Roman">Linus</FONT>和他的妻子养育了更多儿女的时候,重启动需要的特殊参数可能在某种程度上会增加。不过我想在他们用尽<FONT face="Times New Roman">32</FONT>位可能空间之前,他的妻子就会制止他的行为了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29315</FONT>:请求内核锁,这样能保证这段代码在某一时间只能由一个处理器执行。使用<FONT face="Times New Roman"><B normal">lock_kernel</B>/<B normal">unlock_kernel</B></FONT>函数对所保护起来的任何其它代码对其它<FONT face="Times New Roman">CPU</FONT>都同样是不可访问的。在单处理器的机器中,这只是一个<FONT face="Times New Roman">no-op</FONT>(不处理任何事情);而详细讨论它在多处理器上的作用则是第<FONT face="Times New Roman">10</FONT>章的内容。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29317</FONT>:在<B normal"><FONT face="Times New Roman">LINUX_REBOOT_CMD_RESTART</FONT></B>的情况中,<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>调用一系列基于<B normal"><FONT face="Times New Roman">reboot_notifier_list</FONT></B>的函数来通知它们系统正在重新启动。正常情况下,这些函数都是操作系统关闭时需要清除的模块的一部分。这个列表函数似乎并不在内核中的其它地方使用——至少在标准内核发行版本中是这样,也许此外的其它模块可能使用这个列表。不管怎样,这个列表的存在可以方便其他人使用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            <B normal">LINUX_REBOOT_CMD_RESTART</B></FONT>和其它<B normal"><FONT face="Times New Roman">cmd</FONT></B>识别出的值从<FONT face="Times New Roman">16023</FONT>行开始通过<B normal"><FONT face="Times New Roman">#define</FONT></B>进行宏定义。这些值并没有潜在的意义,选用它们的简单原因是它们一般不会发生意外并且相互之间各不相同。(有趣的是,<B normal"><FONT face="Times New Roman">LINUX_REBOOT_CMD_OFF</FONT></B>是零,这是在意外情况下最不可能出现的一个值。但是,由于<B normal"><FONT face="Times New Roman">LINUX_REBOOT_CMD_OFF</FONT></B>简单的禁止用户使用<FONT face="Times New Roman">Ctrl+Alt+Del</FONT>重新启动机器,它就是一种“安全”的意外了。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29321</FONT>:打印警告信息以后,<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>调用<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>(<FONT face="Times New Roman">2185</FONT>行)重启机器。正如你从<FONT face="Times New Roman">2298</FONT>行中所看到的一样,<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>函数从来不会返回。但是不管怎样,对于<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>的调用后面都跟着一个<B normal"><FONT face="Times New Roman">break</FONT></B>语句。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这仅仅是经典的良好的编程风格吗?的确如此,但是却又不仅仅如此。文件<FONT face="Times New Roman">kernel/sys.c</FONT>的代码是属于体系结构无关部分的。但是<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>,它显然是体系结构所特有的,属于代码的体系结构特有的部分(<FONT face="Times New Roman">arch/i386/kernel/process.c</FONT>)。因而对于不同的移植版本也有所不同。我们并不清楚以后内核的每个移植版本的实现都不会返回——例如,它可能调度底层硬件重启但是本身要仍然持续运行几分钟,这就需要首先从函数中返回。或者更为确切的说法是,由于某些特定的原因,系统可能并不总是能够重启;或许某些软件所控制的硬件根本就不能重启。在这种平台上,<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>就应该可以返回,因此体系结构无关的代码应该对这种可能性有所准备。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>针对这个问题,正式的发行版本中都至少包含一个退出端口,使<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>函数可以从这个端口返回:<FONT face="Times New Roman">m68k</FONT>端口。不同的基于<FONT face="Times New Roman">m68k</FONT>的机器支持的代码也各不相同,由于本书主要是针对<FONT face="Times New Roman">x86</FONT>的,我不希望花费过多的时间来解析所有的细节。但是这的确是可能的。(在其它情况下,<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>简单进入一个无限循环——既不重新启动机器,也不返回。但是这里我们担心的是需要返回的情况。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>因此,我们毕竟是需要<B normal"><FONT face="Times New Roman">break</FONT></B>的。前面看起来只是简单的习惯甚至是偏执的思想在这里为了内核的移植性已经变成必须的了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29324</FONT>:接下来的两种情况分别允许和禁止臭名卓著的<FONT face="Times New Roman">Ctrl+Alt+Del</FONT>组合键(这三个组合键也被称为“<FONT face="Times New Roman">Vulcan</FONT>神经收缩(<FONT face="Times New Roman">Vulcan nerve pinch</FONT>)”,“黑客之手(<FONT face="Times New Roman">hacker’s claw</FONT>)”,“三指之礼(<FONT face="Times New Roman">three-fingered salute</FONT>)”,我最喜欢后面这个)。这些只是简单的设置全局<B normal"><FONT face="Times New Roman">C_A_D</FONT></B>标志(在<FONT face="Times New Roman">29160</FONT>行定义,在<FONT face="Times New Roman">29378</FONT>行检测)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29332</FONT>:这种情况和<B normal"><FONT face="Times New Roman">LINUX_REBOOT_CMD_RESTART</FONT></B>类似,但只是暂停系统而不是将其重新启动。两者之间的一个区别是它调用<B normal"><FONT face="Times New Roman">machine_halt</FONT></B>(<FONT face="Times New Roman">2304</FONT>行)——这是<FONT face="Times New Roman">x86</FONT>上的一条<FONT face="Times New Roman">no-op</FONT>指令,但是在其它平台上却要完成关闭系统的实际工作——而不是调用<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>。并且它会把<B normal"><FONT face="Times New Roman">machine_halt</FONT></B>不能使之暂停的机器转入低功耗模式运行。它使用<B normal"><FONT face="Times New Roman">do_exit</FONT></B>(<FONT face="Times New Roman">23267</FONT>行)杀死内核本身。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29340</FONT>:到现在为止,这已经是一种比较熟悉的模式了。这里,<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>关闭机器电源,除了为可以使用软件自行关闭电源的系统调用<B normal"><FONT face="Times New Roman">machine_power_off</FONT></B>(<FONT face="Times New Roman">2307</FONT>行)之外,其它的应该和暂停机器情况完全相同。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29348</FONT>:<FONT face="Times New Roman"><B normal">LINUX_REBOOT_CMD_RESTART</B>2</FONT>的情况是已建立主题的一个变种。它接收命令,将其作为<FONT face="Times New Roman">ASCII</FONT>字符串传递,该字符串说明了机器应该如何关闭。字符串不会由<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>本身来解释,而是使用<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>函数来解释;因而这种模式的意义,如果有的话,就是这些代码是平台相关的。(我使用“如果有”的原因是启动机器——特别是在<FONT face="Times New Roman">x86</FONT>中——一般只有一种方法,因此其它的信息都可以被<B normal"><FONT face="Times New Roman">machine_restart</FONT></B>忽略。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29365</FONT>:调用者传递了一个无法识别的命令。<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>不作任何处理,仅仅返回一个错误。因此,即使由<B normal"><FONT face="Times New Roman">magic1</FONT></B>和<B normal"><FONT face="Times New Roman">magic2</FONT></B>传递给<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>正确的<FONT face="Times New Roman">magic</FONT>数值,它也无须处理任何内容。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29369</FONT>:一个可识别的命令被传递给<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>。如果流程执行到这里,它可能就是两个设置<B normal"><FONT face="Times New Roman">C_A_D</FONT></B>的命令之一,因为其它情况通常都是停止或者重新启动机器。在任何情况下,<B normal"><FONT face="Times New Roman">sys_reboot</FONT></B>都简单把内核解锁并返回<FONT face="Times New Roman">0</FONT>以表示成功。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_sysinfo</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24142</FONT>:一个只能返回一个整型值的系统调用。如果需要返回更多的信息,我们只需要使用类似于在系统调用中传递多于四个参数时所使用的技巧就可以了:我们通过一个指向结构的指针将结果返回。收集系统资源使用情况的<B normal"><FONT face="Times New Roman">sysinfo</FONT></B>系统调用就是这种函数的一个样例。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24144</FONT>:分配并清空一个<B normal"><FONT face="Times New Roman">struct sysinfo</FONT></B>结构(<FONT face="Times New Roman">15004</FONT>行)以暂时存储返回值。<B normal"><FONT face="Times New Roman">sys_sysinfo</FONT></B>可以把结构中的每个域都独立地拷贝出来,但是这样会速度很慢、很不方便,而且必然不容易阅读。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24148</FONT>:禁止中断。这在第<FONT face="Times New Roman">6</FONT>章中会有详细的介绍;作为目前来说,我们只要说明这种模式在使用的过程中能够确保<B normal"><FONT face="Times New Roman">sys_sysinfo</FONT></B>正在使用的值不会改变就足够了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24149</FONT>:<B normal"><FONT face="Times New Roman">struct sysinfo</FONT></B>结构的<B normal"><FONT face="Times New Roman">uptime</FONT></B>域用来指明系统已经启动并运行了的秒数。这个值是使用<B normal"><FONT face="Times New Roman">jiffies</FONT></B>(<FONT face="Times New Roman">26146</FONT>行)和<B normal"><FONT face="Times New Roman">HZ</FONT></B>来计算的。<B normal"><FONT face="Times New Roman">jiffies</FONT></B>计算了系统运行过程中时钟的滴答次数;<B normal"><FONT face="Times New Roman">HZ</FONT></B>是系统相关的一个参数,它十分简单,就是每秒内部时钟滴答的次数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24151</FONT>:数组<B normal"><FONT face="Times New Roman">avenrun</FONT></B>(<FONT face="Times New Roman">27116</FONT>行)记录了运行队列的平均长度——也就是等待<FONT face="Times New Roman">CPU</FONT>的平均进程数——在最后的<FONT face="Times New Roman">1</FONT>秒钟,<FONT face="Times New Roman">5</FONT>秒钟和<FONT face="Times New Roman">15</FONT>秒钟。<B normal"><FONT face="Times New Roman">calc_load</FONT></B>(<FONT face="Times New Roman">27135</FONT>行)周期性的重复计算它的值。由于内核中是要严格禁止浮点数运算的,所以只能通过计算变化的次数这一修正值来计算。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24155</FONT>:同样记录系统中当前运行的进程数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24158</FONT>:<B normal"><FONT face="Times New Roman">si_meminfo</FONT></B>(<FONT face="Times New Roman">07635</FONT>行)写入这个结构中的内存相关成员,<B normal"><FONT face="Times New Roman">si_swapinfo</FONT></B>(<FONT face="Times New Roman">38544</FONT>行)写入与虚拟内存相关的部分。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24161</FONT>:现在整个结构都已经全部填充了。<B normal"><FONT face="Times New Roman">sysinfo</FONT></B>试图将其拷贝回用户空间,如果失败就返回<B normal"><FONT face="Times New Roman">EFAULT</FONT></B>,如果成功就返回<FONT face="Times New Roman">0</FONT>。</P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm; tab-stops: 171.0pt"><FONT face="Times New Roman">  </FONT>第<FONT face="Times New Roman">6</FONT>章<FONT face="Times New Roman"> </FONT>信号量,中断和时间</H1>< 0cm 0cm 0pt">信号量(<FONT face="Times New Roman">Signal</FONT>)是进程间通讯(<FONT face="Times New Roman">IPC</FONT>)的一种形式——是一个进程给另一个进程发送信息的方法。但是信息不可能很多——一个信号量不可能携带详细的信息,即使是传送者的身份也不能被传递;唯一能够确定的事实是信号量的确被发送了。(然而和经典信号量不同,<FONT face="Times New Roman">OSIX</FONT>实时信号量允许传送稍微多一点的信息。)实际上,信号量对于双向通讯是没有用处的。还有,根据某些限定,信号量的接受者不必以任何方式作出响应,甚至可以直接忽略大部分信号量。</P>< 0cm 0cm 0pt">虽然有这么多的限制,然而信号量仍然是一种功能强大的十分有用的机制——勿庸置疑,这是<FONT face="Times New Roman">Unix IPC</FONT>中使用最频繁的机制。每当进程退出或者废弃一个空指针时,每当使用<FONT face="Times New Roman">Ctrl+C</FONT>键终止程序运行时,都要传递信号量。</P>< 0cm 0cm 0pt">第<FONT face="Times New Roman">9</FONT>章会更详细的讨论<FONT face="Times New Roman">IPC</FONT>机制。对于本章的讨论来说,信号量的内容就足够讨论了。</P>< 0cm 0cm 0pt">正如在<FONT face="Times New Roman">Linux</FONT>内核本身的代码注释中所说明的一样,中断(<FONT face="Times New Roman">Interrupt</FONT>)对于内核来说和信号量是类似的。中断一般都是从磁盘之类的硬件设备送往内核,用以提示内核该设备需要加以注意。一个重要的硬件中断源就是定时器设备,它周期性地通知内核已经通过的时间。如同第<FONT face="Times New Roman">5</FONT>章中阐述的一样,中断也可以由用户进程通过软件产生。</P>< 0cm 0cm 0pt">在本章中,我们首先讨论一下<FONT face="Times New Roman">Linux</FONT>中信号量和中断的实现,最后再浏览一下<FONT face="Times New Roman">Linux</FONT>的时间处理方式。</P>< 0cm 0cm 0pt">虽然内核对代码的要求标准非常严格,本章所涉及的代码仍然特别清晰明白。本章使用的一般方法是首先介绍相关的数据结构和它们之间的关系,接下来讨论操纵和查询它们的函数。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">锁的概述</H2>< 0cm 0cm 0pt">锁的基本思想是限制对共享资源的访问——共享资源包括共享的文件,共享的内存片,以及在一次只能由一个<FONT face="Times New Roman">CPU</FONT>执行的代码段。概括的说,在单处理器上运行的<FONT face="Times New Roman">Linux</FONT>内核并不需要锁,这是因为在编写<FONT face="Times New Roman">Linux</FONT>内核时就已经注意到要尽量避免各种可能需要锁的情况了。但是,在多处理器机器上,一个处理器有时需要防止其它处理器对它的有害的介入。</P>< 0cm 0cm 0pt"><FONT face="Times New Roman">include/asm-i386/spinlock.h</FONT>文件(从<FONT face="Times New Roman">12582</FONT>行开始)并不使用难看的<B normal"><FONT face="Times New Roman">#ifdef</FONT></B>把所有对锁函数的调用封装起来,它包含一系列对单处理器平台(<FONT face="Times New Roman">UP</FONT>)基本为空的宏,然而在多处理器平台(<FONT face="Times New Roman">SMP</FONT>)上这些宏将展开成为实际代码。因而内核的其它代码对<FONT face="Times New Roman">UP</FONT>和<FONT face="Times New Roman">SMP</FONT>(当涉及到这种特性时)都是相同的,但是它们两个的效果却是迥然不同的。</P>< 0cm 0cm 0pt">第<FONT face="Times New Roman">10</FONT>章中涉及<FONT face="Times New Roman">SMP</FONT>的部分会对锁做深入的介绍。但是,由于你在代码中将到处都能够看到对锁宏的调用,特别是在本章所讨论到的代码中这一点尤为明显,所以你应该首先对宏的用途有初步了解——以及为什么现在在大多数情况下我们都可以安全地将其忽略(我们将在讨论的过程中对其中的异常情况进行说明)。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">信号量</H2>< 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>内核将信号量分为两类:</P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo2; tab-stops: list 42.25pt">l         非实时的(<FONT face="Times New Roman">Nonrealtime</FONT>)——大部分是些传统的信号量,例如<B normal"><FONT face="Times New Roman">SIGSEGV</FONT></B>,<B normal"><FONT face="Times New Roman">SIGHUP</FONT></B>和<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>。</P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l5 level1 lfo3; tab-stops: list 42.25pt">l         实时的(<FONT face="Times New Roman">realtime</FONT>)——由<FONT face="Times New Roman">OSIX 1003.1b</FONT>标准规定,它们同非实时信号量有细微的区别。特别是实时信号量具有进程可以配置的意义——就像是非实时信号量<B normal"><FONT face="Times New Roman">SIGUSR1</FONT></B>和<B normal"><FONT face="Times New Roman">SIGUSR2</FONT></B>一样——额外的信息能够和这些信号量一起传送。它们也会排队,因此如果在第一个信号量处理完成之前有多个信号量实例到达,所有的信号量都能够被正确传送;这对于非实时信号量则是不可能的。</P>< 0cm 0cm 0pt">在第<FONT face="Times New Roman">7</FONT>章中我们将会对实时性对于<FONT face="Times New Roman">Linux</FONT>内核的意义进行更详细的介绍——特别是实时性所不能够说明的内容。</P>< 0cm 0cm 0pt">信号量数目的宏定义从<FONT face="Times New Roman">12048</FONT>行开始。实时信号量的数目在<B normal"><FONT face="Times New Roman">SIGRTMIN</FONT></B>和<B normal"><FONT face="Times New Roman">SIGRTMAX</FONT></B>(分别在<FONT face="Times New Roman">12087</FONT>行和<FONT face="Times New Roman">12088</FONT>行)所定义的范围之内。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5>数据结构</FONT></H3>< 0cm 0cm 0pt">本节讨论信号量代码使用的最重要的数据结构。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigset_t</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12035</FONT>:<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>表示信号量的集合。根据使用地点的不同,它的意思也不同——例如,它可能记录着正在等待某一个进程的信号量(如<FONT face="Times New Roman">16425</FONT>行<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>的<B normal"><FONT face="Times New Roman">signal</FONT></B>成员)的集合,也可能是某个进程已经请求阻塞了的信号量(如同一行中定义的同一结构的<B normal"><FONT face="Times New Roman">blocked</FONT></B>成员)的集合。随着本书的进行,我们会逐渐看到这些类似的应用。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12036</FONT>:<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>的唯一一个组成部分是一组<B normal"><FONT face="Times New Roman">unsigned long</FONT></B>(无符号长整型数),其中的每一位都代表一个信号量。注意到无符号长整型类型在整个内核代码中是作为一个字来处理的,这和你所希望的可能有所出入——即使是在当前<FONT face="Times New Roman">x86 CPU</FONT>的讨论中,有时候字也被用于说明<FONT face="Times New Roman">16</FONT>位类型。由于<FONT face="Times New Roman">Linux</FONT>是一个真<FONT face="Times New Roman">32</FONT>位操作系统,将<FONT face="Times New Roman">32</FONT>位看作是一个字在绝大多数情况下是正确的。(将<FONT face="Times New Roman">Linux</FONT>称为真<FONT face="Times New Roman">32</FONT>位操作系统也有一些不准确,因为在<FONT face="Times New Roman">64</FONT>位<FONT face="Times New Roman">CPU</FONT>上它也是一个真<FONT face="Times New Roman">64</FONT>位操作系统。)</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这个数组的大小<B normal"><FONT face="Times New Roman">_NSIG_WORDS</FONT></B>在<FONT face="Times New Roman">12031</FONT>行直接计算。(<B normal"><FONT face="Times New Roman">_NSIG_BPW</FONT></B>中的“<FONT face="Times New Roman">BPW</FONT>”是“<FONT face="Times New Roman">bits per word</FONT>(每字位数)”的缩写。)在不同的平台上,<B normal"><FONT face="Times New Roman">_NSIG_WORDS</FONT></B>的大小从<FONT face="Times New Roman">1</FONT>(<FONT face="Times New Roman">Alpha</FONT>平台中)到<FONT face="Times New Roman">4</FONT>(<FONT face="Times New Roman">MIPS</FONT>平台中)不等。如你所见,在<FONT face="Times New Roman">x86</FONT>平台中,该值正好是<FONT face="Times New Roman">2</FONT>,这意味着在<FONT face="Times New Roman">x86</FONT>平台上<FONT face="Times New Roman">2</FONT>个无符号数就可以包含足够的位数来代表所有<FONT face="Times New Roman">Linux</FONT>使用的信号量。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">struct sigaction</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12165</FONT>:<B normal"><FONT face="Times New Roman">struct sigaction</FONT></B>代表信号量到达时进程应该执行的动作。它被封装在<B normal"><FONT face="Times New Roman">struct k_sigaction</FONT></B>(<FONT face="Times New Roman">12172</FONT>行)结构中,而该结构又是被封装在<B normal"><FONT face="Times New Roman">struct signal_struct </FONT></B>结构中的,后者是<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的<B normal"><FONT face="Times New Roman">sig</FONT></B>成员所指向的一个实例(<FONT face="Times New Roman">16424</FONT>行)。如果这个指针为空,进程就会退出而不必接受任何信号量。否则,每个进程对于每个信号量数目都需要若干<FONT face="Times New Roman"><B normal">_NSIG</B> <B normal">struct sigaction</B></FONT>结构和一个<B normal"><FONT face="Times New Roman">struct sigaction</FONT></B>结构。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12166</FONT>:<B normal"><FONT face="Times New Roman">sa_handler</FONT></B>(<B normal"><FONT face="Times New Roman">__sighandler_t</FONT></B>类型——一个在<FONT face="Times New Roman">12148</FONT>行定义的函数指针类型)描述了进程希望处理信号量的方式。其值可以是下面中的一个:</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo4; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">SIG_DFL</FONT></B>(<FONT face="Times New Roman">12151</FONT>行)申请处理信号量的缺省操作,不管该操作是什么——这是由信号量所决定的。注意它和<B normal"><FONT face="Times New Roman">NULL</FONT></B>是等同的。</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo5; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">SIG_IGN</FONT></B>(<FONT face="Times New Roman">12153</FONT>行)意味着信号量应该忽略。但是,并不是所有的信号量都可以被忽略的。</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l6 level1 lfo6; tab-stops: list 57.25pt">l         所有的其它值都是在信号量到达时所需要调用的用户空间函数的地址。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12167</FONT>:<B normal"><FONT face="Times New Roman">sa_flags</FONT></B>进一步调整信号量处理代码所完成的工作。可能的标志集合从<FONT face="Times New Roman">12108</FONT>行开始定义。这些标志允许用户代码在信号量实例发送以后(或者保留用户定制的操作时)请求恢复缺省操作,等等。这一点在宏定义块前面的标签注释中已经说明了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12168</FONT>:<B normal"><FONT face="Times New Roman">sa_restorer</FONT></B>是本书中所没有涉及的一些信号量处理代码细节所使用的。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12169</FONT>:<B normal"><FONT face="Times New Roman">sa_mask</FONT></B>是一系列其它信号量的集合,进程在处理这些信号量的过程中可能需要进行锁定。例如,如果一个进程在处理<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>的时候希望锁定<B normal"><FONT face="Times New Roman">SIGHUP</FONT></B>和<FONT face="Times New Roman">      <B normal">SIGINT</B></FONT>,进程的第<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>个<B normal"><FONT face="Times New Roman">sa_mask</FONT></B>就会对与<B normal"><FONT face="Times New Roman">SIGHUP</FONT></B>和<B normal"><FONT face="Times New Roman">SIGINT</FONT></B>相关的位进行置位。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">siginfo_t</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">11851</FONT>:<B normal"><FONT face="Times New Roman">struct siginfo</FONT></B>(也称为<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>)是伴随着信号量,特别是在实时信号量,所传递的额外信息。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">11852</FONT>:勿庸置疑,<B normal"><FONT face="Times New Roman">si_signo</FONT></B>是信号量的数目。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">11853</FONT>:<B normal"><FONT face="Times New Roman">si_errno</FONT></B>应该是信号量传递时传送者的<B normal"><FONT face="Times New Roman">errno</FONT></B>的值,这样接收者就可以对它进行检测。内核本身并不关心这个值;当在某些情况下需要设置这个值时,内核将其设置为<FONT face="Times New Roman">0</FONT>。我推测如果这样,即使调用者没有设置这个值,它们仍然会发现<B normal"><FONT face="Times New Roman">si_error</FONT></B>的值被设为已知状态。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">11854</FONT>:<B normal"><FONT face="Times New Roman">si_code</FONT></B>记录了信号量的来源(不是发送者的进程<FONT face="Times New Roman">ID</FONT>号,也就是<FONT face="Times New Roman">PID</FONT>——它在别处记录)。有效的信号量来源在<FONT face="Times New Roman">11915</FONT>行及其随后部分使用宏进行了定义。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">11856</FONT>:该结构的最后一部分是<B normal"><FONT face="Times New Roman">union</FONT></B>类型的;该<B normal"><FONT face="Times New Roman">union</FONT></B>类型依赖于<B normal"><FONT face="Times New Roman">si_code</FONT></B>的值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">11857</FONT>:<B normal"><FONT face="Times New Roman">union</FONT></B>的第一部分是<B normal"><FONT face="Times New Roman">_pad</FONT></B>,它将<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>的长度扩展填充为<FONT face="Times New Roman">128*<B normal">sizeof</B></FONT><B normal">(<FONT face="Times New Roman">int</FONT></B><B normal">)</B>字节(在<FONT face="Times New Roman">x86</FONT>平台上一共是<FONT face="Times New Roman">512</FONT>个字节)。留意一下这个数组的大小,也就是<B normal"><FONT face="Times New Roman">SI_PAD_SIZE</FONT></B>(<FONT face="Times New Roman">11849</FONT>行),代表了该结构的前三个成员——如果增加了更多的成员,<B normal"><FONT face="Times New Roman">SI_PAD_SIZE</FONT></B>就需要进行相应修改。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">struct signal_queue</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17132</FONT>:<B normal"><FONT face="Times New Roman">struct signal_queue</FONT></B>结构用来确保所有的实时信号量都被正确传送了,如果可能,每一个都包含着额外信息(<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>)。如同后面你将会看到的一样,内核会为每个进程都设置一个队列,用来存放该进程的挂起的实时信号量。这个队列类型本身很小,仅仅由一个指向下一个节点的指针和<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>本身组成。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">应用函数</H2><P 0cm 0cm 0pt">有关信号量的一个最重要的数据结构是<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>,它是由一系列在<FONT face="Times New Roman">include/linux/signal.h</FONT>文件中定义的简单函数所操纵的,这些函数的定义从<FONT face="Times New Roman">17123</FONT>行开始。在<FONT face="Times New Roman">x86</FONT>平台上,这些相同的函数可以——而且已经——使用汇编语言更加有效的实现了;这些更高效的版本从<FONT face="Times New Roman">12204</FONT>行开始。(<FONT face="Times New Roman">m68k</FONT>端口是唯一一个例外的端口,它使用体系结构特有的代码实现。)由于平台无关的版本和<FONT face="Times New Roman">x86</FONT>特有的版本都很重要,我们会对两者都加以介绍。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5><I normal">平台无关的<FONT face="Times New Roman">sigset_t</FONT></I><I normal">函数<p></p></I></FONT></H3><P 0cm 0cm 0pt">配合<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>使用的平台无关的函数在<FONT face="Times New Roman">include/linux/sigal.h</FONT>文件中,从<FONT face="Times New Roman">17123</FONT>行开始。称为“<FONT face="Times New Roman">bitops</FONT>”(位级的操作)的函数将在后面介绍。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigaddset</H4><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">17145</FONT>:<B normal"><FONT face="Times New Roman">sigaddset</FONT></B>把一个信号量加入集合——也就是说,它修改了集合中的一位。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">17147</FONT>:为了便于位操作,将基于<FONT face="Times New Roman">0</FONT>的信号量转化为基于<FONT face="Times New Roman">1</FONT>的信号量。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">17149</FONT>:如果信号量中填入一个无符号长整型数,恰当的位就会被设置。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17151</FONT>:否则,<B normal"><FONT face="Times New Roman">sigaddset</FONT></B>就需要绕很多弯路,首先装入恰当的数组元素,接着设置该元素中相关位。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">17148</FONT>行的代码和该文件中后面的其它代码一样,第一次见到时可能会令人感到有些困惑。在内核代码中,速度是压倒一切的因素。从而,也许你并不会看到类似于下面的运行期间进行决定的代码:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       if (_NSIG_WORDS == 1)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">          set-&gt;sig[0] |=1UL &lt;&lt; sig;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       else</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">          set-&gt;sig[sig / <I normal">NSIG</I>BPW] |= 1UL &lt;&lt; (sig % <I normal">NSIG</I>BPW);</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm">而你看到的是类似于下面的在编译期间决定的代码:</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #if (_NSIG_WORDS == 1)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">          set-&gt;sig[0] |=1UL &lt;&lt; sig;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #else</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">          set-&gt;sig[sig / <I normal">NSIG</I>BPW] |= 1UL &lt;&lt; (sig % <I normal">NSIG</I>BPW);</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">       #endif</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">难道这样不会运行的更快些吗?不要忘了,<B normal"><FONT face="Times New Roman">if</FONT></B>条件是能够在编译期间进行计算的,因此预处理器可以使系统没有必要在运行期间执行检测工作。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">当你认识到优化工作的实现方式时,这也就没有什么神秘的了。<FONT face="Times New Roman">gcc</FONT>的优化器的敏锐程度足以注意到<B normal"><FONT face="Times New Roman">if</FONT></B>表达式只有一个出口,因此它可以把那些不必要的代码移走。作为内核“运行期间”版本的结果代码和“编译期间”的版本是等同的。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">但是在我们使用优化器很糟糕的编译器时,基于预处理器的版本还会更好吗?这一点并不确定。问题之一是,基于预处理器的(编译期间的)版本有一点更难懂。当代码的复杂程度比前面的简单例子要高时,可读性的差别就会明显的显示出来。例如,让我们考虑一下<FONT face="Times New Roman"><B normal">sigemptyse</B>t</FONT>中的从<FONT face="Times New Roman">17264</FONT>行开始的<B normal"><FONT face="Times New Roman">switch</FONT></B>。现在的<B normal"><FONT face="Times New Roman">switch</FONT></B>类似于这样:</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">swithc (_NSIG_WORDS) {</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">default:</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">  memset(set, 0, sizeof(sigset_t));</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">  break;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">case 2:  set-&gt;sig[1] = 0;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">case 1:  set-&gt;sig[0] = 0;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">  break;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">}</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">(请注意经周密考虑的<FONT face="Times New Roman">case 2</FONT>随<FONT face="Times New Roman">case 1</FONT>连续执行的情况。)为了更好的利用预处理器而将其重写,它就可能类似于:</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#if ((_NSIG_WORDS != 2)) &amp;&amp; \ (_NSIG_WORDS != 1)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">  memset(set, 0, sizeof(sigset_t));</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#else /* (_NSIG_WORDS is 2 or 1). */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#if (_NSIG_WORDS == 2)</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">  set-&gt;sig[1] = 0;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#endif</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">  set-&gt;sog[0] = 0;</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">#endif /* _NSIG_WORDS test. */</FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">gcc</FONT>的优化器为两者产生的目标代码是相同的。你更希望读哪一种版本的源程序代码呢?</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">另外,即使编译器的优化器并没有这么好——这种优化实在相当简单——那么编译器就不可能生成很好的代码。不管我们提供多少帮助都注定是不够的,因此我们可能要编写一些更容易读、更容易维护的代码——这是又一项工程技术的权衡。最后,就象我们在前面的内容中已经看到而且还要不断看到的那样,使用除<FONT face="Times New Roman">gcc</FONT>之外的编译器编译内核本身就是个挑战——增加一段<FONT face="Times New Roman">gcc</FONT>特有代码不会引起更多问题的。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigdelset</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17154</FONT>:这些代码和<B normal"><FONT face="Times New Roman">sigaddset</FONT></B>非常类似;区别在于这里从集合中删去了一位——就是把相应的位设置为关。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigismember</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17163</FONT>:这些代码和<B normal"><FONT face="Times New Roman">sigaddset</FONT></B>也非常类似;这里是要测试某一位是否被设置。注意到<FONT face="Times New Roman">17167</FONT>行可能和下面的这种写法有同样的好处:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            return set-&gt;sig[0] &amp; (1UL &lt;&lt; sig);</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这种写法与<FONT face="Times New Roman">17169</FONT>行非常相似。虽然这样能够和其它函数的编写风格更加一致,但是这并不是什么改进。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这些修改将对函数的行为方式稍有改动:它现在返回<FONT face="Times New Roman">0</FONT>或<FONT face="Times New Roman">1</FONT>,经过这种修改,就可以在一个位被设置时返回其它的非<FONT face="Times New Roman">0</FONT>值。但是,这种改变不会终止没有退出的代码,因为其调用者只关心返回值是否为<FONT face="Times New Roman">0</FONT>(它们并不特别在意是否为<FONT face="Times New Roman">1</FONT>)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigfindinword</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17172</FONT>:这个函数返回<B normal"><FONT face="Times New Roman">word</FONT></B>中设置的第一个位的位置。函数<B normal"><FONT face="Times New Roman">ffz</FONT></B>(在本书中没有涉及)返回其参数中第一个<FONT face="Times New Roman">0</FONT>位的位置。在将位求补的字中的第一个<FONT face="Times New Roman">0</FONT>的位置——这正是这个函数搜寻的内容——显然是原始顺序中的第一个<FONT face="Times New Roman">1</FONT>的位置。它从最小位<FONT face="Times New Roman">0</FONT>开始计算。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigmask</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17177</FONT>:最后,这个有用的<B normal"><FONT face="Times New Roman">sigmask</FONT></B>宏简单的把信号量数目通过一个相应的位集合转化为一个位掩码。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><FONT size=5><I normal">平台相关的<FONT face="Times New Roman">sigset_t</FONT></I><I normal">函数</I></FONT></H3><P 0cm 0cm 0pt">即使平台无关的版本已经使用了简单有效的<FONT face="Times New Roman">C</FONT>代码,它也可以通过使用<FONT face="Times New Roman">x86 CPU</FONT>家族的方便而功能强大的位集指令在<FONT face="Times New Roman">x86</FONT>平台上更加有效地实现,。这些函数中的大部分都可以减少为单独的机器指令,因此这里的讨论也都很精简。</P><P 0cm 0cm 0pt">在<FONT face="Times New Roman">x86</FONT>平台(例如<FONT face="Times New Roman">m68k</FONT>)上平台无关的函数对于编译器甚至是不可见的。<FONT face="Times New Roman">17126</FONT>行包含进了<FONT face="Times New Roman">asm/signal.h</FONT>文件,在<FONT face="Times New Roman">x86</FONT>上这个文件被分解为<FONT face="Times New Roman">include/asm-i386/signal.h</FONT>,这都应该归功于设置文件所建立的符号链接。<FONT face="Times New Roman">12202</FONT>行定义了预处理器符号<B normal"><FONT face="Times New Roman">__HAVE_ARCH_SIG_BITOPS</FONT></B>,它消除了这些平台无关的函数的定义(请参看<FONT face="Times New Roman">17140</FONT>行)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigaddset</H4><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">12204</FONT>:<FONT face="Times New Roman">x86</FONT>特有的使用<B normal"><FONT face="Times New Roman">btsl</FONT></B>指令的<B normal"><FONT face="Times New Roman">sigaddset</FONT></B>实现,它仅对操作数的一个位进行设置。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigdelset</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12210</FONT>:同样,这是<FONT face="Times New Roman">x86</FONT>特有的使用<B normal"><FONT face="Times New Roman">btrl</FONT></B>指令的<B normal"><FONT face="Times New Roman">sigdelset</FONT></B>实现,它对操作数的一个位进行重置(清除)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigismember</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12233</FONT>:<B normal"><FONT face="Times New Roman">sigismember</FONT></B>根据其<B normal"><FONT face="Times New Roman">sig</FONT></B>参数是否是一个编译期常量表达式来选择实现方法。文档中所没有说明的<FONT face="Times New Roman">gcc</FONT>编译器的强大的特殊参数<B normal"><FONT face="Times New Roman">__builtin_constant_p</FONT></B>是一个编译期操作符(就象<B normal"><FONT face="Times New Roman">sizeof</FONT></B>一样),它能够报告是否可以在编译期间计算其参数值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>如果可以,<B normal"><FONT face="Times New Roman">sigismember</FONT></B>使用<B normal"><FONT face="Times New Roman">__const_sigismember</FONT></B>函数(<FONT face="Times New Roman">12216</FONT>行)完成这项工作,因为它的大部分表达式都可以在编译期间计算。否则就使用更为普遍的版本<B normal"><FONT face="Times New Roman">__gen_sigismember</FONT></B>函数(<FONT face="Times New Roman">12224</FONT>行)来代替。更普遍的版本中使用的是<FONT face="Times New Roman">x86</FONT>的<B normal"><FONT face="Times New Roman">btl</FONT></B>指令,它需要测试其操作数中的某一位。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>注意到在编译期的常量合并和死锁代码消除通常意味着这样的完整测试只能在编译期间执行——关键是<B normal"><FONT face="Times New Roman">sigismember</FONT></B>要根据需要使用<B normal"><FONT face="Times New Roman">__const_sigismember</FONT></B>或者<B normal"><FONT face="Times New Roman">__gen_sigismember</FONT></B>替换,在作为结果的目标代码中甚至完全看不出来根本就没有对另一部分进行考虑。这样相当精简,难道不是吗?</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigmask</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12238</FONT>:<FONT face="Times New Roman">x86</FONT>特有的<B normal"><FONT face="Times New Roman">sigmask</FONT></B>的实现,这与平台无关的版本是等同的。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigfindinword</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">12240</FONT>:最后,<FONT face="Times New Roman">x86</FONT>特有的<B normal"><FONT face="Times New Roman">sigfindinword</FONT></B>实现只使用了<FONT face="Times New Roman">x86</FONT>的<B normal"><FONT face="Times New Roman">bsfl</FONT></B>指令,它在自己的操作数中寻找一个设置位。</P><H3 6pt 0cm; TEXT-INDENT: 0cm"><I normal"><FONT size=5>设置函数<p></p></FONT></I></H3><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">除了前面的那一组函数之外,还有一组对<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>执行设置操作的函数和宏。和前面一组类似,这些函数使用<B normal"><FONT face="Times New Roman">__HAVE_ARCH_SIG_SETOPS</FONT></B>预处理器符号保护起来。然而现在没有一种体系结构能够提供自己独有的这些函数的实现,正因为如此,体系结构无关的版本是现存的唯一版本。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">_SIG_SET_BINOP<p></p></H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17184</FONT>:我们希望定义的全部三个二进制操作——<B normal"><FONT face="Times New Roman">sigorsets</FONT></B>,<B normal"><FONT face="Times New Roman">sigandsets</FONT></B>和<B normal"><FONT face="Times New Roman">signandsets</FONT></B>——的实现方式从本质上来说是相同的。这个宏简单的把这三个函数的共同代码分解出来,从而只给它们提供一个操作和一个名字。当然,这同<FONT face="Times New Roman">C++</FONT>模版函数类似,不过这样我们不得不自己处理一些记录工作,而不能完全信任编译器——这是我们使用<FONT face="Times New Roman">C</FONT>工作所付出的一部分代价。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17191</FONT>:程序开始在<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>中全部四个字节的无符号长整型数的循环,同时对这些操作数进行应用。这个循环是为了速度的原因而展开的——通过减少循环控制开销来提高速度,这是很出名的一种增加速度的方法。然而,大多数情况下,这个循环根本就不执行。例如,在<FONT face="Times New Roman">x86</FONT>平台上,编译器可以在运行期间就证实不会执行该循环体,因为截断取整以后,<FONT face="Times New Roman"><B normal">_NSIG_WORDS</B>/4</FONT>的结果是<FONT face="Times New Roman">0</FONT>。(回忆一下<B normal"><FONT face="Times New Roman">_NSIG_WORDS</FONT></B>在<FONT face="Times New Roman">x86</FONT>平台上的值为<FONT face="Times New Roman">2</FONT>。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17201</FONT>:<B normal"><FONT face="Times New Roman">switch</FONT></B>从循环末尾处理剩余工作的这行开始。如果在某些平台上<B normal"><FONT face="Times New Roman">_NSIG_WORDS</FONT></B>正好为<FONT face="Times New Roman">6</FONT>,那么该循环就可以执行一次,而且<B normal"><FONT face="Times New Roman">switch</FONT></B>的情况<FONT face="Times New Roman">2</FONT>也可以被执行。在<FONT face="Times New Roman">x86</FONT>平台上,循环永远不会执行;只有<B normal"><FONT face="Times New Roman">switch</FONT></B>的情况<FONT face="Times New Roman">2</FONT>才可能执行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>顺便说一下,我并不清楚为什么<B normal"><FONT face="Times New Roman">switch</FONT></B>不和其类似的<B normal"><FONT face="Times New Roman">_SOG_SET_OP</FONT></B>一样使用直接流程的方式实现。通常情况下,现存的版本可以更充分的利用缓存(如果你试图重新编写它,那么你就可以清楚的认识到这一点)——但是如果实际原因的确如此,那么<B normal"><FONT face="Times New Roman">_SIG_SET_OP</FONT></B>也应该使用相同的参数。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">_SIG_SET_OP<I normal"><p></p></I></H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17238</FONT>:<B normal"><FONT face="Times New Roman">_SIG_SET_OP</FONT></B>和<B normal"><FONT face="Times New Roman">_SIG_SET_BINOP</FONT></B>类似,但是它使用的是一元操作而不是二元操作,因此我们并不需要详细地介绍它。但是你应该注意的是,这只能使用一次——在<FONT face="Times New Roman">17257</FONT>行生成<B normal"><FONT face="Times New Roman">signotset</FONT></B>——这和<B normal"><FONT face="Times New Roman">_SIG_SET_BINOP</FONT></B>不同。因此,在某种程度上这是不需要的——其实现者可以直接编写<B normal"><FONT face="Times New Roman">signotset</FONT></B>,而不必借助<B normal"><FONT face="Times New Roman">_SIG_SET_OP</FONT></B>,这并没有产生任何重复代码。然而,二者生成的目标代码是相同的,这样如果我们以后选择增加一元操作,意义也就不大了。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigemptyset</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17262</FONT>:<B normal"><FONT face="Times New Roman">sigemptyset</FONT></B>清空所提供的集合——要把其中的每一位都清空。(下面一个函数<B normal"><FONT face="Times New Roman">sigfillset</FONT></B>和这个函数功能相同,不过它要设置所有的位而不是清除所有的位,因此我们就不再详细介绍了。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17265</FONT>:普通情况下使用<B normal"><FONT face="Times New Roman">memset</FONT></B>把集合中的每一位都置为<FONT face="Times New Roman">0</FONT>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17268</FONT>:对于<B normal"><FONT face="Times New Roman">_NSIG_WORDS</FONT></B>的一些比较小的值来说,简单的直接设置<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>的一两个元素可能速度更快。在这里采用的就是这种直接流程实现。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sigaddsetmask</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17292</FONT>:该函数和下面的几个函数是更简单快速设置和读取最低的<FONT face="Times New Roman">32</FONT>位(或者根据字的大小)信号量的一系列函数。<B normal"><FONT face="Times New Roman">sigaddsetmask</FONT></B>简单地把<B normal"><FONT face="Times New Roman">mask</FONT></B>所指定的位置位,而不对剩余的位进行任何处理——这是一个集合的联合操作。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">siginitset</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">17310</FONT>:根据提供的掩码对最低<FONT face="Times New Roman">32</FONT>位(或者是别的)置位,并将其它位设置为<FONT face="Times New Roman">0</FONT>。下面一个函数<B normal"><FONT face="Times New Roman">siginitsetinv</FONT></B>(<FONT face="Times New Roman">17323</FONT>行)正好相反:它根据掩码的补数设置最低<FONT face="Times New Roman">32</FONT>位(或者别的),并对其余的位置位。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">传送信号量</H2><P 0cm 0cm 0pt">从用户的观点来看,传送信号量相当简单:调用系统调用<B normal"><FONT face="Times New Roman">kill</FONT></B>,该调用只需要进程<FONT face="Times New Roman">ID</FONT>号和一个信号量。但是,正如本节中所显示的那样,其实现要复杂得多。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_kill</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28768</FONT>:<B normal"><FONT face="Times New Roman">sys_kill</FONT></B>是系统调用<B normal"><FONT face="Times New Roman">kill</FONT></B>的一个具有欺骗性的实现样例;真正的实际工作是在<B normal"><FONT face="Times New Roman">kill_somethig-info</FONT></B>中实现的,我们随后就将对这个方面进行研究。<B normal"><FONT face="Times New Roman">sys_kill</FONT></B>的参数是要传递的信号量<B normal"><FONT face="Times New Roman">sig</FONT></B>和信号量的目的<B normal"><FONT face="Times New Roman">pid</FONT></B>。就象你将看到的那样,参数<B normal"><FONT face="Times New Roman">pid</FONT></B>并不仅是进程<FONT face="Times New Roman">ID</FONT>。(<FONT face="Times New Roman">PID</FONT>和进程的其它概念都在第<FONT face="Times New Roman">7</FONT>章中详细介绍。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28770</FONT>:根据提供给<B normal"><FONT face="Times New Roman">sys_kill</FONT></B>的信息声明并填充<B normal"><FONT face="Times New Roman">struct siginfo</FONT></B>结构。特别要注意的是<B normal"><FONT face="Times New Roman">si_code</FONT></B>是<B normal"><FONT face="Times New Roman">SI_USER</FONT></B>(因为只有用户进程才可以调用该系统调用;内核本身是不会调用系统调用的,它更倾向于使用低层函数)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28778</FONT>:传递这些信息给<B normal"><FONT face="Times New Roman">kill_something_info</FONT></B>,该函数处理实际的工作。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">kill_something_info</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28484</FONT>:该函数的参数和<B normal"><FONT face="Times New Roman">sys_kill</FONT></B>类似,但是增加了一项<B normal"><FONT face="Times New Roman">siginfo</FONT></B>结构的指针。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28487</FONT>:如果<B normal"><FONT face="Times New Roman">pid</FONT></B>为<FONT face="Times New Roman">0</FONT>,就意味着当前进程希望把信号量传递给整个进程组,该工作由<B normal"><FONT face="Times New Roman">kill_pg_info</FONT></B>(<FONT face="Times New Roman">28408</FONT>行)完成。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28489</FONT>:如果<B normal"><FONT face="Times New Roman">pid</FONT></B>是<FONT face="Times New Roman">-1</FONT>,信号量(几乎)被送往系统中的每一个进程,这在下面的段落中介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28494</FONT>:使用<FONT face="Times New Roman"><B normal">for_ech_tas</B>k</FONT>宏(在<FONT face="Times New Roman">16898</FONT>行宏定义,第<FONT face="Times New Roman">7</FONT>章中详细介绍)开始循环处理现存进程列表的每一项。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28496</FONT>:如果这不是<FONT face="Times New Roman">idle</FONT>进程(或<FONT face="Times New Roman">init</FONT>),就使用<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>(<FONT face="Times New Roman">28218</FONT>行,后面将会讨论)传递信号量。每次发现合适的任务时<B normal"><FONT face="Times New Roman">count</FONT></B>的值都会增加,虽然<B normal"><FONT face="Times New Roman">kill_something_info</FONT></B>并不关心<B normal"><FONT face="Times New Roman">count</FONT></B>的实际值,而是在意是否能够发现合适的进程。如果所有试图发送信号量的努力都失败了,将记录失败的过程以使得<B normal"><FONT face="Times New Roman">kill_something_info</FONT></B>可以在<FONT face="Times New Roman">28503</FONT>行返回错误代码;如果发生了多次错误,则只返回最后一次失败的情况。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28503</FONT>:如果发现了合适的候选进程,<B normal"><FONT face="Times New Roman">kill_something_info</FONT></B>就返回最近失败的错误代码,或者成功就返回<FONT face="Times New Roman">0</FONT>。如果没有发现任何合适的候选进程,就返回<B normal"><FONT face="Times New Roman">ESRCH</FONT></B>错误。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28504</FONT>:其它负的<B normal"><FONT face="Times New Roman">pid</FONT></B>(是负值,但不是<FONT face="Times New Roman">-1</FONT>)定义了接收信号量的进程组;<B normal"><FONT face="Times New Roman">pid</FONT></B>的绝对值是进程组号。和前面一样,<B normal"><FONT face="Times New Roman">kill_pg_info</FONT></B>的使用就是出于这种目的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28506</FONT>:其它的所有可能性都已经进行了说明;<B normal"><FONT face="Times New Roman">pid</FONT></B>必须为正数。在这种情况下,它是信号量传送的目的进程的<FONT face="Times New Roman">PID</FONT>。这由<B normal"><FONT face="Times New Roman">kill_proc_info</FONT></B>实现(<FONT face="Times New Roman">28463</FONT>行,很快就会讨论)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">kill_pg_info</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28408</FONT>:这个函数给进程组中的每一个进程发送一个信号量和一个<B normal"><FONT face="Times New Roman">struct siginfo</FONT></B>结构。其函数体和前面介绍的<B normal"><FONT face="Times New Roman">kill_something_info</FONT></B>类似,因此我只是简单介绍一下。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28417</FONT>:开始循环处理系统中的所有进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28418</FONT>:如果进程在正确的进程组中,那么信号量就发送给它。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28427</FONT>:如果信号量成功发送给任何进程,<B normal"><FONT face="Times New Roman">retval</FONT></B>就设置为<FONT face="Times New Roman">0</FONT>,从而在<FONT face="Times New Roman">28430</FONT>行成功返回。如果信号量不能被发往任何进程,那么要么是所给的进程组中没有进程,在这种情况下,<B normal"><FONT face="Times New Roman">reval</FONT></B>仍然会在<FONT face="Times New Roman">28415</FONT>行赋值为<B normal"><FONT face="Times New Roman">-ESRCH</FONT></B>;或者<B normal"><FONT face="Times New Roman">kill_pg_info</FONT></B>发送信号量给一个或多个进程,但是每次都失败了,在这种情况下<B normal"><FONT face="Times New Roman">retval</FONT></B>值为从<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>得到的最近错误代码。注意它和<B normal"><FONT face="Times New Roman">kill_something_info</FONT></B>的细微区别,后者如果发送信号量失败时就返回错误。但是这里的<B normal"><FONT face="Times New Roman">kill_pg_info</FONT></B>即使在某些情况下出错了,只要信号能成功地传递给任意进程,就会返回成功信息。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28430</FONT>:在<FONT face="Times New Roman">28410</FONT>行中,如果进程组号无效,<B normal"><FONT face="Times New Roman">retval</FONT></B>或者是如前所述的赋值,或者就是<B normal"><FONT face="Times New Roman">-EINVAL</FONT></B>。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">kill_proc_info</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28463</FONT>:<B normal"><FONT face="Times New Roman">kill_proc_info</FONT></B>是一个相当简单的函数,它把信号量和<FONT face="Times New Roman"><B normal">struct</B> <B normal">siginfo</B></FONT>结构传递给由<FONT face="Times New Roman">PID</FONT>定义的单个进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28469</FONT>:通过所提供的<FONT face="Times New Roman">PID</FONT>查找相应的进程;如果成功<B normal"><FONT face="Times New Roman">find_task_by_pid</FONT></B>(<FONT face="Times New Roman">16570</FONT>行)返回一个指向该进程的指针,如果没有找到该进程就返回<B normal"><FONT face="Times New Roman">NULL</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28472</FONT>:如果找到匹配进程,就使用<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>把信号量传送给目的进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28474</FONT>:返回错误指示,或者是在<FONT face="Times New Roman">28470</FONT>行由于没有发现匹配进程而返回<B normal"><FONT face="Times New Roman">-ESRCH</FONT></B>,或者是其它情况下从<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>中返回的值。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">send_sig_info</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28218</FONT>:<FONT face="Times New Roman"> </FONT>我们最后看的几个函数中最重要的显然是<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>。这个函数使用不同的方法装载进程并处理实际的工作。现在应该了解一下实际的工作是如何完成的。<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>将使用<B normal"><FONT face="Times New Roman">info</FONT></B>指针(该指针也可能为<B normal"><FONT face="Times New Roman">NULL</FONT></B>)指向额外信息的信号量<B normal"><FONT face="Times New Roman">sig</FONT></B>传送给<B normal"><FONT face="Times New Roman">t</FONT></B>指针(调用者应该保证<B normal"><FONT face="Times New Roman">t</FONT></B>不会为<B normal"><FONT face="Times New Roman">NULL</FONT></B>)指向的进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28229</FONT>:确保<B normal"><FONT face="Times New Roman">sig</FONT></B>在范围之内。注意使用的是如下的测试</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            sig &gt; _NSIG</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>而不是你可能预期的</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            sig &gt;= _NSIG</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这是因为信号量的计数是从<FONT face="Times New Roman">1</FONT>开始的,而不是从<FONT face="Times New Roman">0</FONT>开始的。因此虽然不存在对这个信号量编号的定义,有效信号量的编号的标识符<B normal"><FONT face="Times New Roman">_NSIG</FONT></B>本身也是有效的信号量编号。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28233</FONT>:这是另外一个严密性检查——实际上包含多个检验。基本的思想是检测信号量的传送是否合法。虽然内核本身可以给任何进程传送信号量,但是除了在涉及<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>的情况之外,除<FONT face="Times New Roman">root</FONT>之外的用户都不能给其它用户的进程传送信号量。总之,这个长长的<B normal"><FONT face="Times New Roman">if</FONT></B>条件说明了如下问题:</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo7; tab-stops: list 57.25pt">l         (<FONT face="Times New Roman">28233</FONT>行)如果不存在补充信息,或者虽然存在补充信息,但是信号量来源于用户而不是内核,并且<FONT face="Times New Roman">…</FONT></P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo7; tab-stops: list 57.25pt">l         (<FONT face="Times New Roman">28235</FONT>行)<FONT face="Times New Roman">…</FONT>信号量不是<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>,或者虽然信号量是<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>,但是并不是传送给同一会话过程中的其它进程,并且<FONT face="Times New Roman">…</FONT></P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo7; tab-stops: list 57.25pt">l         (<FONT face="Times New Roman">28237</FONT>行和<FONT face="Times New Roman">28238</FONT>行)<FONT face="Times New Roman">…</FONT>发送者有效的用户<FONT face="Times New Roman">ID</FONT>既不是已经存储了的目标进程的用户<FONT face="Times New Roman">ID</FONT>,也不是目标进程的当前用户<FONT face="Times New Roman">ID</FONT>,并且<FONT face="Times New Roman">…</FONT></P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo7; tab-stops: list 57.25pt">l         (<FONT face="Times New Roman">28239</FONT>行和<FONT face="Times New Roman">28240</FONT>行)<FONT face="Times New Roman">…</FONT>发送者的当前用户<FONT face="Times New Roman">ID</FONT>既不是已经存储了的目标进程的用户<FONT face="Times New Roman">ID</FONT>,也不是目标进程的当前用户<FONT face="Times New Roman">ID</FONT>,并且<FONT face="Times New Roman">…</FONT></P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo7; tab-stops: list 57.25pt">l         (<FONT face="Times New Roman">28241</FONT>行)<FONT face="Times New Roman">…</FONT>此处不会允许用户超越普通许可(例如,由于用户是<FONT face="Times New Roman">root</FONT>)<FONT face="Times New Roman">…</FONT>那么就不应该发送信号量了;可以跳过这段发送信号量的代码。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>对于前面的<B normal"><FONT face="Times New Roman">if</FONT></B>条件必须明白两点。首先,当将<B normal"><FONT face="Times New Roman">info</FONT></B>映射为无符号长整型数的时候,如果它为<FONT face="Times New Roman">1</FONT>,这就不是一个实际指向<B normal"><FONT face="Times New Roman">struct siginfo</FONT></B>结构的指针。相反的,它是说明信号量来自于内核的特殊值,但是并没有进一步的附加信息可供使用。内核本身在最低的页(内存页在第<FONT face="Times New Roman">8</FONT>章中讨论)中并不分配空间,因此在<FONT face="Times New Roman">4,096</FONT>之下的任何地址(除了<FONT face="Times New Roman">0</FONT>,<B normal"><FONT face="Times New Roman">NULL</FONT></B>之外)都可以作为这种特殊值使用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>其次,在这几种条件的情况中,位<FONT face="Times New Roman">XOR</FONT>运算操作符(<B normal"><FONT face="Times New Roman">^</FONT></B>)比不等运算操作符(<B normal"><FONT face="Times New Roman">!=</FONT></B>)使用得更为普遍。在这些情况下,两个操作符意义相同,因为如果两个相比较的正数之间有一位不同,在<FONT face="Times New Roman">XOR</FONT>运算的结构中就至少有一位被置位,所以结果非空,逻辑值为真。推测起来,<FONT face="Times New Roman">cc</FONT>的早期版本为<B normal"><FONT face="Times New Roman">^</FONT></B>生成的代码比为<B normal"><FONT face="Times New Roman">!=</FONT></B>生成的代码更为有效,但是在现在的编译器版本中就不是这样了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28248</FONT>:忽略信号量<FONT face="Times New Roman">0</FONT>并拒绝将信号量传送给僵进程(已经退出但是还尚未从系统的数据结构中移走的进程;请参看第<FONT face="Times New Roman">7</FONT>章的“进程状态”一节,它讨论了函数<B normal"><FONT face="Times New Roman">exit</FONT></B>)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28252</FONT>:对于一些信号量,在实际发送之前必须进行一些额外的工作。这些工作是在这里的<B normal"><FONT face="Times New Roman">switch</FONT></B>中处理的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28253</FONT>:如果正在发送<B normal"><FONT face="Times New Roman">SIGKLL</FONT></B>或者<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>就唤醒进程(也就是说,如果当前被停止了就允许它再次运行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28257</FONT>:设置进程的返回代码为<FONT face="Times New Roman">0</FONT>——如果进程已经使用<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>停止了,返回代码域就被用来在停止等待的信号量和其祖先间建立通讯。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28258</FONT>:取消任何挂起的<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>(被调试器阻塞),<B normal"><FONT face="Times New Roman">SIGSTP</FONT></B>(由键盘输入的<FONT face="Times New Roman">Ctrl+Z</FONT>终止),<B normal"><FONT face="Times New Roman">SIGTTIN</FONT></B>(试图从<FONT face="Times New Roman">TTY</FONT>中读取信息的后台运行进程),<B normal"><FONT face="Times New Roman">SIGTTOU</FONT></B>(试图向<FONT face="Times New Roman">TTY</FONT>中写入信息的后台运行的进程);这些是所有可能中断进程的条件,也是<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>或者<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>最可能作为响应出现的情况。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28263</FONT>:在一些信号量被删除之后,调用<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>(<FONT face="Times New Roman">16654</FONT>行,将在后面讨论)来判断是否还有信号量仍然处于挂起状态以等待进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28266</FONT>:在前面的情况中,如果<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>或者<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>到达了,这四个信号量就会都被取消。但是看起来有些不太对称,如果这四个信号量有一个到达了,任何挂起等待的<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>都会被取消。然而<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>却不会被取消,这遵循<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>永远不会被锁定或者取消的规律。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28281</FONT>:如果目标进程希望忽略信号量并且允许不接收信号量,那么就跳过了信号量的接收过程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28284</FONT>:非实时信号量并不排队等待,这就意味着如果在进程处理第一个信号量实例之前,同一信号量的第二个实例就到达了,那么第二个实例就会被忽略。这一点就是在这里确保的(回想一下<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的<B normal"><FONT face="Times New Roman">singnal</FONT></B>成员中保存着一个进程的当前正在挂起等待的信号量的集合)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28304</FONT>:在限制条件的控制下,实时信号量需要排队等待。最重要的限制是可以同时排队等待的实时信号量总数的可配置限制;这一限制值为<B normal"><FONT face="Times New Roman">max_queued_signal</FONT></B>,它是在<FONT face="Times New Roman">28007</FONT>行定义的,而且可以使用<FONT face="Times New Roman">Linux</FONT>的系统控制特性加以修改。如果有空间来容纳更多的信号量,就分配<B normal"><FONT face="Times New Roman">struct signal_queue</FONT></B>结构来容纳排队等待的信息。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>为什么所要首先限制排队等待的信号量的数目呢?这是为了防止服务拒绝的攻击:如果没有这个限制,用户可以持续发送实时信号量直到内核内存溢出,这样就会阻碍内核为其它进程提供该服务及其它服务。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28310</FONT>:如果一个队列节点已经被分配,现在<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>就必须使有关这个信号量的信息进入队列。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28311</FONT>:把信息加入队列是很直接的:<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>把挂起等待的信号量数量(全局变量)增加<FONT face="Times New Roman">1</FONT>,接着把新的节点增加到目标进程的信号量队列中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28315</FONT>:根据提供给<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>的<B normal"><FONT face="Times New Roman">info</FONT></B>参数填充队列节点的<B normal"><FONT face="Times New Roman">info</FONT></B>成员。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28316</FONT>:<FONT face="Times New Roman">0</FONT>(<B normal"><FONT face="Times New Roman">NULL</FONT></B>)意味着信号量是从用户发送而来的,而且可能使用了从<FONT face="Times New Roman">28513</FONT>行到<FONT face="Times New Roman">28544</FONT>行定义的向后兼容的信号量发送函数。目标<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>使用相对比较明确的值来填写。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28323</FONT>:值<FONT face="Times New Roman">1</FONT>是指示信号量来源于内核的一个特殊值——再一次的使用了向后兼容的函数。和前面的情况一样,目标<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>使用相对比较明确的值来填写。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28331</FONT>:正常情况下,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>得到一个实际的<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>,它可以简单地将其拷贝到队列节点中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28334</FONT>:没有分配队列节点——或者因为系统内存溢出而造成<B normal"><FONT face="Times New Roman">kmem_cache_alloc</FONT></B>在<FONT face="Times New Roman">28306</FONT>行返回<B normal"><FONT face="Times New Roman">NULL</FONT></B>;或者因为已经达到了信号量队列的最大值,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>根本就没有试图分配节点。不管怎样,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>所处理的内容是相同的:除非该信号量是通过内核或者老式的信号量函数(例如<B normal"><FONT face="Times New Roman">kill</FONT></B>)发出的,否则<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>就返回<B normal"><FONT face="Times New Roman">EAGAIN</FONT></B>错误,通知调用者现在信号量不能排队等待,但是后来调用者应该可以再次使用相同的参数成功执行调用。否则,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>就传送该信号量但并不将其排入队列中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28345</FONT>:最后,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>从实际上准备好发送信号量。首先,信号量进入该进程的挂起等待的信号量的集合中。注意即使信号量被锁定了这个过程也要执行,这可能有点奇怪。但是这样处理是有原因的:内核必须提供<B normal"><FONT face="Times New Roman">sys_sigpending</FONT></B>(<FONT face="Times New Roman">28981</FONT>行,本章中后面部分将讨论),它允许进程查询在锁定时传送进来什么信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28346</FONT>:如果信号量没有被锁定,那么进程应该被通知有信号量到达了。相应的,其<B normal"><FONT face="Times New Roman">sigpendig</FONT></B>标志被置位。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28370</FONT>:如果进程正在等待信号量的到达并且有信号量也正在等待它,那么这个进程就被唤醒(使用<B normal"><FONT face="Times New Roman">wake_up_process</FONT></B>,<FONT face="Times New Roman">26356</FONT>行)来处理信号量。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">force_sig_info</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28386</FONT>:这个函数被内核用来保证不管进程是否需要,它都确实接收了一个信号量。例如,在进程释放未用指针时,可以使用这个函数来确保该进程接收了<B normal"><FONT face="Times New Roman">SIGSEGV</FONT></B>(请参看<FONT face="Times New Roman">7070</FONT>行——实际上是调用了向后兼容的函数<B normal"><FONT face="Times New Roman">force_sig</FONT></B>,但是<B normal"><FONT face="Times New Roman">force_sig</FONT></B>完全是按照<B normal"><FONT face="Times New Roman">force_sig_info</FONT></B>实现的)。<B normal"><FONT face="Times New Roman">force_sig_info</FONT></B>的参数和<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>的参数相同,两者的意义也相同。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28392</FONT>:如果目标进程是僵进程,即使是内核也不应该给它发送任何信号量;所进行的尝试将被拒绝。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28397</FONT>:如果进程将要忽略这个信号量,<B normal"><FONT face="Times New Roman">force_sig_info</FONT></B>将通过强制它执行缺省操作的方式进行纠正。实际上它并不像外表所表现出来的那样无害:在内核使用该函数的情况下,对这个信号量的缺省操作是杀死进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28399</FONT>:把信号量从<B normal"><FONT face="Times New Roman">t</FONT></B>所锁定的集合中移走。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28402</FONT>:<B normal"><FONT face="Times New Roman">force_sig_info</FONT></B>现在已经建立了一些条件使得<B normal"><FONT face="Times New Roman">t</FONT></B>必须接收信号量,因此该信号量就可以使用<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>进行发送。如果<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>的实现改变了,这仍然可能造成信号量不能发送,因此这两个函数必须保持同步。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">recalc_sigpending</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16654</FONT>:这个函数重新计算进程的<B normal"><FONT face="Times New Roman">sigpending</FONT></B>标志;当进程的<B normal"><FONT face="Times New Roman">signal</FONT></B>或<B normal"><FONT face="Times New Roman">blocked</FONT></B>集合改变时就调用该函数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16676</FONT>:在最简单的情况中,<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>将信号量和锁定集合求补的结果执行位<FONT face="Times New Roman">AND</FONT>操作。(对锁定集合求补就是允许的集合。)其它的情况仅仅是这种情况的泛化。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16679</FONT>:如果前面操作中的任何一个在<B normal"><FONT face="Times New Roman">ready</FONT></B>中遗留下了任何一位,那么挂起等待的信号量集合中最少有一个信号量现在还被锁定;因此<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>将增加<B normal"><FONT face="Times New Roman">sigpending</FONT></B>标志的值。</P><P 0cm 0cm 0pt">由于<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>所实际需要了解的全部内容只是是否至少有一个信号量在挂起等待——例如,如果不止一个,也并不需要知道有多少信号量在挂起等待——非平凡情况下的代码只要发现<B normal"><FONT face="Times New Roman">ready</FONT></B>的值被置为非<FONT face="Times New Roman">0</FONT>值就应该停止对其进行修改(例如,前面<FONT face="Times New Roman">16662</FONT>行通过中断循环)。但是,任何可能来对此优化所产生的效率增进都必须要同为此而进行的额外测试进行权衡。正是由于这个原因,又加上<B normal"><FONT face="Times New Roman">_NSIG_WORDS</FONT></B>很小(在实际中无论如何都是如此),改进的版本可能要比标准情况快一点。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">ignored_signal</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28183</FONT>:<B normal"><FONT face="Times New Roman">ignored_signal</FONT></B>有助于<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>决定是否给一个进程发送信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28189</FONT>:如果进程正被其祖先跟踪(可能是调试器),或者信号量是在进程锁定的集合中,那么它就不能被忽略。第二种情况可能是我们过去所没有考虑过的;如果信号量被锁定了,<B normal"><FONT face="Times New Roman">send_sig_info</FONT></B>(还有<B normal"><FONT face="Times New Roman">ignored_signal</FONT></B>)难道不应该将其忽略吗?如果情况的确如此,还真不应该忽略。这个函数通过信号量是否应该被忽略表明了进程的信号量的<B normal"><FONT face="Times New Roman">signal</FONT></B>集合的相应位是否应该被置位。如同前面我们已经看到的那样,对<B normal"><FONT face="Times New Roman">sigpending</FONT></B>系统调用的支持要求如果在锁定过程中有信号量到达,内核就应该设置相应的位。因此,被锁定信号量不能简单地忽略。</P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    < 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28194</FONT>:如果进程是一个僵进程,信号量就应该被忽略。这种测试是不必要的,因为这种情况甚至在<FONT face="Times New Roman">28248</FONT>行的<B normal"><FONT face="Times New Roman">ignored_signal</FONT></B>调用之前就会被发现。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28199</FONT>:在大多数情况下,<B normal"><FONT face="Times New Roman">SIG_DFL</FONT></B>(缺省的)操作是处理信号量而不是将其忽略。你所能看到的例外是<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>,<B normal"><FONT face="Times New Roman">SIGWINCH</FONT></B>,<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>,和<B normal"><FONT face="Times New Roman">SIGURG</FONT></B>。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28207</FONT>:进程允许忽略大部分信号量,但是不能忽略<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>。对于<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>,<FONT face="Times New Roman">OSIX</FONT>赋予<B normal"><FONT face="Times New Roman">SIG_IGN</FONT></B>一种特殊的意义,这一点在<FONT face="Times New Roman">28831</FONT>行将会说明。这里所提到的“<FONT face="Times New Roman">automatic child reaping</FONT>”(自动子进程空间回收)将在<FONT face="Times New Roman">3426</FONT>行执行。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28211</FONT>:在缺省的情况下,可以假定<B normal"><FONT face="Times New Roman">ignored_signal</FONT></B>有一个实际的函数指针,而不是<B normal"><FONT face="Times New Roman">SIG_DFL</FONT></B>或者<B normal"><FONT face="Times New Roman">SIG_IGN</FONT></B>两个伪值的一个。这样,信号量就和用户定义的处理句柄联系起来,这意味着进程希望处理这个信号量。它通过返回<FONT face="Times New Roman">0</FONT>来指明信号量不应该被忽略。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_signal</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3364</FONT>:<FONT face="Times New Roman"> <B normal">do_signal</B></FONT>在信号量到达进程时使用。这个函数在内核中被调用的地方不止一次——如我们在第<FONT face="Times New Roman">5</FONT>章中看到的从<FONT face="Times New Roman">203</FONT>行和<FONT face="Times New Roman">211</FONT>行,还有从<FONT face="Times New Roman">2797</FONT>行和<FONT face="Times New Roman">2827</FONT>行。通常所有这些情况都是当前进程希望处理挂起等待的信号量(如果有的话)。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3375</FONT>:<FONT face="Times New Roman"> </FONT>如果非空,<B normal"><FONT face="Times New Roman">oldset</FONT></B>用来返回当前进程锁定的信号量集合。由于<B normal"><FONT face="Times New Roman">do_signal</FONT></B>不会修改锁定的集合,它可以简单的返回一个指向现有锁定集合的指针。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3378</FONT>:<FONT face="Times New Roman"> </FONT>进入几乎扩展到该函数末尾(<FONT face="Times New Roman">3478</FONT>行)的循环。退出该循环的方法只有两种:把所有可能的信号量都处理了,或者处理唯一一个信号量。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3382</FONT>:<FONT face="Times New Roman"> </FONT>使用<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>使信号量出队列(<FONT face="Times New Roman">28060</FONT>,后面将会介绍)。<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>或者返回<FONT face="Times New Roman">0</FONT>,或者返回需要处理的信号量的编号,并且它还会填充<B normal"><FONT face="Times New Roman">info</FONT></B>中的附加信息。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3385</FONT>:<FONT face="Times New Roman"> </FONT>如果没有信号量处于等待状态,将在这里中断循环。正常情况下,它在循环第一次执行过程中是不会发生的。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3388</FONT>:<FONT face="Times New Roman"> </FONT>如果当前进程正在被其祖先跟踪(可能是调试器),而且信号量也并不是不可锁定的<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>,那么在信号量到达之前,进程的祖先就必须已经得到通知了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3391</FONT>:<FONT face="Times New Roman"> </FONT>将传递给子孙进程的信号量编号传送到祖先进程中对应子孙进程的<B normal"><FONT face="Times New Roman">exit_code</FONT></B>域;祖先使用<B normal"><FONT face="Times New Roman">sys_wait4</FONT></B>收集这些信息(<FONT face="Times New Roman">23327</FONT>行,在第<FONT face="Times New Roman">7</FONT>章中介绍)。<B normal"><FONT face="Times New Roman">do_signal</FONT></B>停止子孙进程的运行,然后使用<B normal"><FONT face="Times New Roman">notify_parent</FONT></B>(<FONT face="Times New Roman">3393</FONT>行)给祖先进程发送<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>信号量,接着调用调度函数<B normal"><FONT face="Times New Roman">schedule</FONT></B>(<FONT face="Times New Roman">26686</FONT>行,第<FONT face="Times New Roman">7</FONT>章中介绍),给其它进程——尤其是其祖先进程——运行的机会。<B normal"><FONT face="Times New Roman">schedule</FONT></B>会把<FONT face="Times New Roman">CPU</FONT>分配给其它进程,因此直到内核跳转回这个进程才会返回。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3397</FONT>:<FONT face="Times New Roman"> </FONT>如果调试器取消了信号量,<B normal"><FONT face="Times New Roman">do_signal</FONT></B>在这里就不应该处理它;循环继续进行。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3402</FONT>:<FONT face="Times New Roman"> <B normal">SIGSTOP</B></FONT>可能只是由于进程正在被跟踪而产生。这里没有必要处理它;循环继续进行。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3406</FONT>:<FONT face="Times New Roman"> </FONT>如果调试器修改了<B normal"><FONT face="Times New Roman">do_signal</FONT></B>要处理的信号量编号,<B normal"><FONT face="Times New Roman">do_signal</FONT></B>将根据新的信息填充<B normal"><FONT face="Times New Roman">info</FONT></B>。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3415</FONT>:<FONT face="Times New Roman"> </FONT>正如注释中所说明的一样,如果新的信号量被锁定了,就需要重新排队,循环继续进行。否则,控制流程将直接执行下面的代码。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3421</FONT>:<FONT face="Times New Roman"> </FONT>在这里,或者进程未被跟踪,或者进程正被跟踪但是得到了一个<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>信号量,或者控制流程直接从前面的代码块中执行下来。在任何一种情况中,<B normal"><FONT face="Times New Roman">do_signal</FONT></B>都有一个应该现在处理的信号量。它从获取<B normal"><FONT face="Times New Roman">struct k_sigaction</FONT></B>结构开始,这个结构指明了怎样处理这个信号量编号。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3423</FONT>:<FONT face="Times New Roman"> </FONT>如果进程试图忽略信号量,那么除非这个信号量是<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>,否则<B normal"><FONT face="Times New Roman">do_signal</FONT></B>就继续执行循环从而忽略该信号量。为什么这个测试不能同时保证该进程不会忽略掉<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>这个注定不可忽略也不可锁定的信号量呢?答案在于和<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>相关的操作永远不会是<B normal"><FONT face="Times New Roman">SIG_IGN</FONT></B>的,实际上也不会是除<B normal"><FONT face="Times New Roman">SIG_DFL</FONT></B>之外的任何操作——<FONT face="Times New Roman">28807</FONT>行就保证了这一点(在<B normal"><FONT face="Times New Roman">do_sigaction</FONT></B>函数中)。这样,如果操作是<B normal"><FONT face="Times New Roman">SIG_IGN</FONT></B>,那么信号量编号就不可能是<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3426</FONT>:<FONT face="Times New Roman"> </FONT>如同在从<FONT face="Times New Roman">28820</FONT>行开始的标题注释中说明的一样,<FONT face="Times New Roman">OSIX</FONT>标准说明了忽略<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>的操作实际上意味着自动回收其子孙进程。子孙进程是通过使用<B normal"><FONT face="Times New Roman">sys_wait4</FONT></B>来回收的(<FONT face="Times New Roman">23327</FONT>行,在第<FONT face="Times New Roman">7</FONT>章中介绍),此后循环继续运行。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3435</FONT>:<FONT face="Times New Roman"> </FONT>进程为这个信号量采用缺省操作。专用的初始化进程接收到全部信号量所对应的缺省操作是把信号量整个忽略掉。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3439</FONT>:<FONT face="Times New Roman"> </FONT>对信号量<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>、<B normal"><FONT face="Times New Roman">SIGCHLD</FONT></B>和<B normal"><FONT face="Times New Roman">SIGWINCH</FONT></B>所采取的缺省操作是不加处理,只是简单地继续执行循环。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3442</FONT>:<FONT face="Times New Roman"> </FONT>对于信号量<B normal"><FONT face="Times New Roman">SIGSTP</FONT></B>,<B normal"><FONT face="Times New Roman">SIGTTIN</FONT></B>和<B normal"><FONT face="Times New Roman">SIGTTOU</FONT></B>,缺省的操作各自不同。如果该进程所归属的进程组是孤立的——简单的说就是没有连接到<FONT face="Times New Roman">TTY</FONT>上——那么<FONT face="Times New Roman">OSIX</FONT>规定对于这些基于终端的信号量的缺省操作是将其忽略。如果进程的进程组不是孤立的,缺省的操作是停止进程的运行——这和<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>的情况是相同的,在这种情况下控制流程直接向下运行。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3447</FONT>:<FONT face="Times New Roman"> </FONT>在对<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>的响应中(或者是从前面情况中直接执行下来),<B normal"><FONT face="Times New Roman">do_signal</FONT></B>终止了进程。另外,除非祖先进程已经规定对其子孙进程的终止运行不加理会,否则祖先进程将会在其子孙进程退出时被通知。和<FONT face="Times New Roman">3394</FONT>行一样,调用<B normal"><FONT face="Times New Roman">schedule</FONT></B>交出<FONT face="Times New Roman">CPU</FONT>给其它某一进程。当内核把<FONT face="Times New Roman">CPU</FONT>再次分配给当前进程的时候,该循环继续运行以处理队列中的另外一个信号量。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这不是我们希望的——我认为当<B normal"><FONT face="Times New Roman">schedule</FONT></B>返回时,循环应该退出,因为信号量已经处理完了。其原理在于如果进程被终止了,唤醒进程的最可能的原因是进程又得到了信号量,可能是<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>,因此该进程现在就可以检测并处理信号量了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3456</FONT>:<FONT face="Times New Roman"> </FONT>对于其它信号量的缺省操作是退出进程。它们中的一些将使进程首先清空内核(详细的介绍请参看第<FONT face="Times New Roman">8</FONT>章),这些信号量就是<B normal"><FONT face="Times New Roman">SIGQUIT</FONT></B>,<B normal"><FONT face="Times New Roman">SIGILL</FONT></B>,<B normal"><FONT face="Times New Roman">SIGTRRAP</FONT></B>,<B normal"><FONT face="Times New Roman">SIGABRT</FONT></B>,<B normal"><FONT face="Times New Roman">SIGFPE</FONT></B>和<B normal"><FONT face="Times New Roman">SIGSEGV</FONT></B>。如果此二进制格式(详细的介绍请参看第<FONT face="Times New Roman">7</FONT>章)知道如何清空内核并且成功地清空了内核,那么在进程的返回代码中就会有一位被设置来指明进程在退出之前就已经清空了内核。接着流程按照<B normal"><FONT face="Times New Roman">default</FONT></B>的情况继续执行,终止进程的运行。注意<B normal"><FONT face="Times New Roman">do_exit</FONT></B>(<FONT face="Times New Roman">23267</FONT>行,在第<FONT face="Times New Roman">7</FONT>章中也会有介绍)是从来不会返回的——因而在<FONT face="Times New Roman">3471</FONT>行中会有“<FONT face="Times New Roman">NOTREACHED</FONT>”注释。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3476</FONT>:<FONT face="Times New Roman"> </FONT>此处,<B normal"><FONT face="Times New Roman">do_signal</FONT></B>从队列中取出一个信号量,该信号量既不和<B normal"><FONT face="Times New Roman">SIG_IGN</FONT></B>的操作有关,也不和<B normal"><FONT face="Times New Roman">SIG_DFL</FONT></B>的操作有关。唯一的另外一种可能性是这是用户定义的信号量处理函数。<B normal"><FONT face="Times New Roman">do_signal</FONT></B>调用<B normal"><FONT face="Times New Roman">handle_signal</FONT></B>(<FONT face="Times New Roman">3314</FONT>行,本章随后将会更为详细地讨论)来触发这个信号量处理函数,接着返回<FONT face="Times New Roman">1</FONT>向调用者声明这个信号量已经处理过了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3481</FONT>:<FONT face="Times New Roman"> </FONT>此处,<B normal"><FONT face="Times New Roman">do_signal</FONT></B>不能为当前进程从队列中取出信号量。(只有从<FONT face="Times New Roman">3386</FONT>行的<B normal"><FONT face="Times New Roman">break</FONT></B>退出时才能执行到本行。)如果在系统调用的处理过程中间被中断了,<B normal"><FONT face="Times New Roman">do_signal</FONT></B>就要调整寄存器,从而系统调用将可以重新执行。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3490</FONT>:返回<FONT face="Times New Roman">0</FONT>以通知调用者<B normal"><FONT face="Times New Roman">do_signal</FONT></B>没有处理任何信号量。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">dequeue_signal</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28060</FONT>:<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>将信号量从进程信号量队列中移出,同时忽略那些由掩码说明的信号量。它返回信号量的编号并使用指针参数<B normal"><FONT face="Times New Roman">info</FONT></B>返回相关的<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28071</FONT>:为了避免重复参照而建立一些别名:<B normal"><FONT face="Times New Roman">s</FONT></B>是进程的挂起等待的信号量的集合(记住其中可能包括了一些锁定的信号量),<B normal"><FONT face="Times New Roman">m</FONT></B>是掩码的集合。特别要注意的是<FONT face="Times New Roman">*s</FONT>表达式,在该函数中这个表达式出现了不止一次,但是它只是<B normal"><FONT face="Times New Roman">current-&gt;signal.sig[0]</FONT></B>的一种简单写法。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28073</FONT>:在这个<B normal"><FONT face="Times New Roman">switch</FONT></B>条件分支中,<B normal"><FONT face="Times New Roman">sig</FONT></B>被设置为第一个挂起等待的信号量。从最简单的情况入手最容易理解;其它的情况只是这种情况的泛化。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28091</FONT>:最简单的情况是:它把挂起等待的信号量和掩码求补后的结果进行位<FONT face="Times New Roman">AND</FONT>运算,结果被存储在临时变量<B normal"><FONT face="Times New Roman">x</FONT></B>中;<B normal"><FONT face="Times New Roman">x</FONT></B>现在就是掩码不能忽略的挂起等待的信号量的集合。如果<B normal"><FONT face="Times New Roman">x</FONT></B>不为<FONT face="Times New Roman">0</FONT>,那么就存在挂起等待的信号量(<B normal"><FONT face="Times New Roman">x</FONT></B>至少有一位被置位);<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>使用<B normal"><FONT face="Times New Roman">ffz</FONT></B>(本书中没有涉及)取得相应的信号量编号,并将其转化为从<FONT face="Times New Roman">1</FONT>开始计数的信号量编号,将结果存储在<B normal"><FONT face="Times New Roman">sig</FONT></B>中。正如前面所说明的一样,其它情况只是这种情况的泛化;最重要的结果是<B normal"><FONT face="Times New Roman">sig</FONT></B>被置位,如果可能的话在每种情况下都是如此——此后其它变量(<B normal"><FONT face="Times New Roman">i</FONT></B>,<B normal"><FONT face="Times New Roman">s</FONT></B>,<B normal"><FONT face="Times New Roman">m</FONT></B>或者<B normal"><FONT face="Times New Roman">x</FONT></B>)的状态就不难理解了。如果在<B normal"><FONT face="Times New Roman">switch</FONT></B>之后的<B normal"><FONT face="Times New Roman">sig</FONT></B>是<FONT face="Times New Roman">0</FONT>,掩码中就没有传递挂起等待的信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28097</FONT>:如果一个信号量正在挂起等待,那么<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>应该试图将其从队列中释放出来。<B normal"><FONT face="Times New Roman">reset</FONT></B>跟踪<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>以判定是否应该把信号量从进程的挂起等待的信号量队列中删除。将<B normal"><FONT face="Times New Roman">reset</FONT></B>初始化为<FONT face="Times New Roman">1</FONT>仅仅是由于在函数处理过程中它可能会改变的假定。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28107</FONT>:对于非实时信号量,内核不会保持原始的<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>(如果曾经有过的话),因此<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>应该尽可能的重新组织有关的信息。不幸的是,当前实现方法中并没有多少信息——只有信号量编号自身而已。<B normal"><FONT face="Times New Roman">info</FONT></B>的其它成员都简单地被设置为<FONT face="Times New Roman">0</FONT>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28118</FONT>:在另一种情况,也就是实时信号量情况下,<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>只是一种点缀。<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>会在进程的<FONT face="Times New Roman">sigqueue</FONT>中进行扫描以确定其值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28122</FONT>:如果找到了<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>,<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>现在就使其出队列,将<B normal"><FONT face="Times New Roman">siginfo_t</FONT></B>的内容拷贝到<B normal"><FONT face="Times New Roman">info</FONT></B>中,并释放为这个队列节点分配的内存。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28129</FONT>:如果队列中没有这个信号量的更多实例,那么信号量就不会在挂起等待了。但是为了弄清楚队列中是否还有信号量的实例,<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>需要遍历整个队列。因此,该函数需要扫描这个队列的其余元素来查询是否存在相同信号量的其它实例。如果发现了实例,<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>就清空<B normal"><FONT face="Times New Roman">reset</FONT></B>标志——只有在这种独特的情况下才会进行的操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28142</FONT>:正在出队的信号量是实时信号量,但是在进程的挂起等待的实时信号量队列中却没有发现它,其原因在代码中已经进行了阐述。现在,<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>和它有非实时信号量的情况相同了——它知道信号量是可以访问的,但是没有方法可以访问其原始值——并且其响应过程处理的工作也完全相同,仅仅使用信号量编号来填充<B normal"><FONT face="Times New Roman">info</FONT></B>,而没有其它属性值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28150</FONT>:除非<B normal"><FONT face="Times New Roman">reset</FONT></B>标志被清空了——也就是说除非这是一个实时信号量并且同一个信号量的其它实例仍然在挂起等待队列中——该信号量已经被处理过;它应该从进程的挂起等待集合中删除。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28152</FONT>:信号量脱离队列,因此<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>应该重新计算进程的<B normal"><FONT face="Times New Roman">sigpending</FONT></B>标志。我认为这里有一个可以进行少量优化的机会:只用当<B normal"><FONT face="Times New Roman">reset</FONT></B>为真的时候<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>才需要这样处理。<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>从进程的锁定集合和挂起等待集合中计算结果;锁定的集合没有改变,因此只有当挂起等待的集合发生改变时,<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>才需要调用<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>。如果<B normal"><FONT face="Times New Roman">reset</FONT></B>为假,挂起等待的集合就不会改变,因此对于<B normal"><FONT face="Times New Roman">recalc_sigpending</FONT></B>的调用就是不必要的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28163</FONT>:<B normal"><FONT face="Times New Roman">switch</FONT></B>没有发现信号量,因此没有信号量正在挂起等待。作为内部正确性的检测,<B normal"><FONT face="Times New Roman">dequeue_signal</FONT></B>确保内核不会认为有信号量正在为某任务挂起等待。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28174</FONT>:返回出队的信号量编号,或者如果没有信号量出队,就返回<FONT face="Times New Roman">0</FONT>。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">notify_parent</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28548</FONT>:<B normal"><FONT face="Times New Roman">notify_parent</FONT></B>寻找进程的祖先进程并通知它其子孙进程的状态发生了改变——通常情况是其子孙进程或者被终止了,或者被杀死了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28553</FONT>:使用有关信号量发生的上下文的信息填充局部变量<B normal"><FONT face="Times New Roman">info</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28564</FONT>:如果子孙进程已经退出,<B normal"><FONT face="Times New Roman">why</FONT></B>被赋以适当的值以指明其原因是因为它清空了内核,或者被某信号量将其杀死,或者因为执行了非法操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28572</FONT>:类似地,如果使用信号量终止了进程,对<B normal"><FONT face="Times New Roman">why</FONT></B>赋值以说明发生的情况。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28578</FONT>:前面的情况几乎覆盖了所有的可能性。如果没有,函数打印出警告信息并继续运行;在这种情况下,系统会在<FONT face="Times New Roman">28562</FONT>行将<B normal"><FONT face="Times New Roman">why</FONT></B>的值赋为<B normal"><FONT face="Times New Roman">SI_KERNEL</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28586</FONT>:给进程的祖先进程发送信号量。下面一行唤醒任何等待这个子孙进程的进程并为其提供<B normal"><FONT face="Times New Roman">CPU</FONT></B>。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">handle_signal</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3314</FONT>:<FONT face="Times New Roman"> <B normal">handle_signal</B></FONT>在需要调用用户定义的信号量处理程序时由<B normal"><FONT face="Times New Roman">do_signal</FONT></B>调用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3338</FONT>:<FONT face="Times New Roman"> </FONT>建立一个用户处理程序可以在其中运行的堆栈帧。如果进程已经请求了内核所拥有的有关信号量的原始值和其上下文的附加信息,那么堆栈帧就使用<B normal"><FONT face="Times New Roman">setup_rt_frame</FONT></B>(<FONT face="Times New Roman">3231</FONT>行)构建起来;否则就使用<B normal"><FONT face="Times New Roman">setup_frame</FONT></B>(<FONT face="Times New Roman">3161</FONT>行)构建。这两种方法都可以实现构建工作,这样控制流程会返回信号量处理程序。当它返回时,实际返回的是信号量到达的时候正在执行的代码。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3343</FONT>:<FONT face="Times New Roman"> </FONT>如果<B normal"><FONT face="Times New Roman">SA_ONESHOT</FONT></B>标志被设置,则信号量处理程序应该只执行一次。(注意<B normal"><FONT face="Times New Roman">sys_signal</FONT></B>是<B normal"><FONT face="Times New Roman">signal</FONT></B>系统调用的实现,它使用<B normal"><FONT face="Times New Roman">SA_ONESHOT</FONT></B>类型的信号量处理程序——请参看<FONT face="Times New Roman">29063</FONT>行。)在这种情况中,缺省的操作是立即将其恢复。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">3346</FONT>:<FONT face="Times New Roman"> <B normal">SA_NODEFER</B></FONT>意味着在执行这个信号量的处理程序时,不应该有其它信号量被锁定。如果位没有设置,其它的位现在就会被加入进程的锁定的集合中。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">其它有关信号量的函数</H2><P 0cm 0cm 0pt">其它一些有关信号量处理的函数。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_sigpending</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28981</FONT>:这个简短的系统调用允许进程询问在信号量锁定期间是否有非实时信号量到达。通过所提供的指针,该函数返回一个位集以指明它们是哪些信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28987</FONT>:这个函数的核心是进程的<B normal"><FONT face="Times New Roman">blocked</FONT></B>集合和<B normal"><FONT face="Times New Roman">signal</FONT></B>集合间的简单位<FONT face="Times New Roman">AND</FONT>操作。它只对最低<FONT face="Times New Roman">32</FONT>位感兴趣,这些都是非实时信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28992</FONT>:使用所提供的指针把挂起等待的集合拷贝回用户空间。如果失败就返回<B normal"><FONT face="Times New Roman">-EFAULT</FONT></B>,如果成功就返回<FONT face="Times New Roman">0</FONT>。注意是否有信号量正在挂起等待——也就是说,返回值是否为空——并不是成功的判据之一。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_sigaction</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28801</FONT>:<B normal"><FONT face="Times New Roman">do_sigaction</FONT></B>实现了系统调用<B normal"><FONT face="Times New Roman">sigaction</FONT></B>有意义的部分。(其余部分在<FONT face="Times New Roman">2833</FONT>行的<B normal"><FONT face="Times New Roman">sys_sigaction</FONT></B>中。)<B normal"><FONT face="Times New Roman">sigaction</FONT></B>是<FONT face="Times New Roman">POSIX</FONT>中等价于<FONT face="Times New Roman">ISO C</FONT>的函数<B normal"><FONT face="Times New Roman">signal</FONT></B>——它把信号量和操作关联起来,这样进程接收到信号量时就能够执行相应的操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28806</FONT>:健全性检测:确保<B normal"><FONT face="Times New Roman">sig</FONT></B>在范围之内并且进程没有试图把<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>或者<B normal"><FONT face="Times New Roman">SIGCONT</FONT></B>和某种操作相关联。进程被简单地剥夺了覆盖这两个信号量的缺省操作的权力。然而,和<B normal"><FONT face="Times New Roman">signal</FONT></B>实现的处理程序不同,使用<B normal"><FONT face="Times New Roman">sigaction</FONT></B>实现的处理程序不是<B normal"><FONT face="Times New Roman">SA_ONESHOT</FONT></B>类型的,因此在处理程序被调用的时候就不用每次都将其重新装载。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28811</FONT>:获取和这个信号量相关的指向<B normal"><FONT face="Times New Roman">k_sigaction</FONT></B>结构的指针。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28813</FONT>:<B normal"><FONT face="Times New Roman">sigaction</FONT></B>可以通过一个过去所提供的指针返回旧有的操作。这在以堆栈方式存在的处理程序中是很有用的,在这里处理程序被临时覆盖,以后再恢复出来。如果<B normal"><FONT face="Times New Roman">oact</FONT></B>指针非空,旧有的操作就会被拷贝到其中。(但是这并不会把信息拷贝会用户空间;调用者必须执行这样的处理。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28815</FONT>:如果<B normal"><FONT face="Times New Roman">do_sigaction</FONT></B>被赋予一个需要同信号量相关联的操作,那么二者现在就相互关联起来。<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>和<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>也必须被从操作的掩码中删除,为了确保这些信号量不会被锁定或者覆盖。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28836</FONT>:正如在<FONT face="Times New Roman">28820</FONT>行开始的标题中注释的一样,为了遵守<FONT face="Times New Roman">POSIX</FONT>标准,下面的几行代码必须要经过一定变形,并且在必要情况下还会舍弃某些信号量。对于这些细节情况,我们即使跳过也不会有什么损失。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_rt_sigtimedwait</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28694</FONT>:<B normal"><FONT face="Times New Roman">sys_rt_sigtimedwait</FONT></B>等待信号量的到达,它可能在经过一段特定的时间间隔以后超时退出。并不是所有的信号量都会接收;指针<B normal"><FONT face="Times New Roman">uthese</FONT></B>所指明的<B normal"><FONT face="Times New Roman">sigset_t</FONT></B>说明了调用者所感兴趣的信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28714</FONT>:<B normal"><FONT face="Times New Roman">uthese</FONT></B>(它已经被拷贝到局部变量<B normal"><FONT face="Times New Roman">these</FONT></B>中了)是允许的信号量的集合,于是内核元语只知道如何锁定信号量。但这样也没有关系:对允许的信号量集合进行求补运算就得到了应该锁定的信号量,所得到的结果就可以直接使用了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28717</FONT>:如果调用者提供了超时时间,该超时时间就将被拷贝到用户空间中,而且其值也必须经过健全检测。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28726</FONT>:检查是否已经有信号量正在等待了——如果有,就没有必要为其等待了。否则,调用者必须等待。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28731</FONT>:保存原来的锁定信号量集合,然后阻塞由<B normal"><FONT face="Times New Roman">these</FONT></B>定义的所有信号量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28737</FONT>:如果用户没有提供超时时间,那么超时时间会是<B normal"><FONT face="Times New Roman">MAX_SCHEDULE_TIMEOUT</FONT></B>(宏定义为<B normal"><FONT face="Times New Roman">LONG_MAX</FONT></B>,或者是<FONT face="Times New Roman">2<SUP>31</SUP>-1</FONT>,<FONT face="Times New Roman">16228</FONT>行)。但是并不永远都是如此——超时时间是以瞬间(<FONT face="Times New Roman">jiffy</FONT>)计数的,它的系统时钟以每秒<FONT face="Times New Roman">100</FONT>次的速度跳动着,因此大约有<FONT face="Times New Roman">248</FONT>天,超时时间就耗尽了。(在<FONT face="Times New Roman">64</FONT>位机器中,这大约需要三十亿年。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28739</FONT>:如果用户确实提供了超时时间,就将其转化为以瞬间计算的值。“<FONT face="Times New Roman">+</FONT>”后面的表达式是对下一个瞬间进行向上舍入的明智方法——其思想是<B normal"><FONT face="Times New Roman">timespec_to_jiffies</FONT></B>可能已经向下舍入了,但是内核必须是上舍入的,因为它必须等够用户请求的瞬间个数。它虽然可以检测<B normal"><FONT face="Times New Roman">timespec_to_jiffies</FONT></B>(<FONT face="Times New Roman">18357</FONT>行)是否是下舍入的,但是下面这种方法更为简单:如果用户提供的超时时间不是<FONT face="Times New Roman">0</FONT>就为其增加一个瞬间,并且认为是对它进行了调整。毕竟<FONT face="Times New Roman">Linux</FONT>不是一个真正的实时操作系统——当你指定了超时时间时,<FONT face="Times New Roman">Linux</FONT>只能保证至少等待如此长的时间。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28742</FONT>:设置当前用户的状态为<B normal"><FONT face="Times New Roman">TASK_INTERRUPTIBLE</FONT></B>(请参看第<FONT face="Times New Roman">7</FONT>章)。<B normal"><FONT face="Times New Roman">schedule_timeout</FONT></B>(<FONT face="Times New Roman">26577</FONT>行)用来让出<FONT face="Times New Roman">CPU</FONT>;在指定的时间用完以后或者其它事件到达并唤醒进程(比如接收了一个信号量)时,该进程才可以继续运行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28746</FONT>:进程希望被信号量唤醒。<B normal"><FONT face="Times New Roman">sys_rt_sigtimedwait</FONT></B>再次尝试从进程的等待信号量队列中取出信号量并恢复原来锁定的集合。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28752</FONT>:此处,该函数仍然不知道信号量是否已经到达了——它可能无需等待就可以得到一个信号量,或者在等待期间可能有另一个信号量到达,也或者该函数一直在等待但是没有信号量到达。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28753</FONT>:如果信号量到达,该函数就给用户进程传递信息并且返回信号量编号。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28759</FONT>:否则,虽然进行了等待,但是没有信号量到达。在这种情况下,该函数或者返回<B normal"><FONT face="Times New Roman">-EAGAIN</FONT></B>(说明用户进程可以再次使用相同的参数尝试),或者返回<B normal"><FONT face="Times New Roman">-EINTR</FONT></B>(说明其等待过程被由于某些原因而不能传递的信号量中断了)。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">内核如何区分实时信号量和非实时信号量</H2><P 0cm 0cm 0pt">简单的说,答案并不复杂。我几乎掩盖了其中的绝大部分区别,这是有一定原因的:退出语句不多。现在,为了使这一点更加清楚,让我们来看一下系统调用<B normal"><FONT face="Times New Roman">sigprocmask</FONT></B>的两个版本,它允许进程处理自己的锁定信号量的集合——增加,删除,或者简单地对信号量集合进行设置。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_sigprocmask</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28931</FONT>:<B normal"><FONT face="Times New Roman">sys_sigprocmask</FONT></B>是这个函数的原始版本,这一版本并不知道或者是不关心实时信号量。参数<B normal"><FONT face="Times New Roman">how</FONT></B>指明了要执行的操作;如果<B normal"><FONT face="Times New Roman">set</FONT></B>不为<B normal"><FONT face="Times New Roman">NULL</FONT></B>,就是这个操作的操作数;如果<B normal"><FONT face="Times New Roman">oset</FONT></B>是非空的,那么<B normal"><FONT face="Times New Roman">oset</FONT></B>返回的就是原始的锁定集合。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28937</FONT>:如果<B normal"><FONT face="Times New Roman">set</FONT></B>为空,那么<B normal"><FONT face="Times New Roman">how</FONT></B>的值就没有什么用处了:该操作就没有操作数了,因此该函数不会处理有关的内容。否则,就继续执行该操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28939</FONT>:在新的锁定集合中的拷贝,其中删除了不可锁定的<B normal"><FONT face="Times New Roman">SIGKILL</FONT></B>和<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28944</FONT>:为了处理以后将当前锁定集合拷贝回用户空间的需要,在<B normal"><FONT face="Times New Roman">old_set</FONT></B>中存储当前锁定集合的一个备份。由于当前锁定集合在以后的代码中可能会被修改,因此在它改变之前必须对其值进行存储。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28948</FONT>:当然是忽略无效的操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28951</FONT>:<B normal"><FONT face="Times New Roman">SIG_BLOCK</FONT></B>操作符指明<B normal"><FONT face="Times New Roman">new_set</FONT></B>应该解释为要锁定的附加信号量的集合。这些信号量将被加入该锁定集合中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28954</FONT>:<B normal"><FONT face="Times New Roman">SIG_UNBLOCK</FONT></B>操作符指明<B normal"><FONT face="Times New Roman">new_set</FONT></B>应该解释为要从锁定的信号量的集合移出的信号量集合。这些信号量现在被移出锁定集合。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28957</FONT>:<B normal"><FONT face="Times New Roman">SIG_SETMASK</FONT></B>操作符指明<B normal"><FONT face="Times New Roman">new_set</FONT></B>应该解释为新的锁定集合,简单覆盖该锁定集合原有的值。因此,<B normal"><FONT face="Times New Roman">sys_sigprocmask</FONT></B>正是实现这一点的。注意它只设置了<B normal"><FONT face="Times New Roman">bloched.set</FONT></B>数组的最低的元素——这个元素包含低<FONT face="Times New Roman">32</FONT>位非实时信号量,这是该函数所关心的内容。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28966</FONT>:如果调用者已经请求查询锁定的集合的原来的值,执行流程就向前跳到<B normal"><FONT face="Times New Roman">set_old</FONT></B>标号(<FONT face="Times New Roman">28970</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28968</FONT>:如果<B normal"><FONT face="Times New Roman">set</FONT></B>为空,意味着调用者没有请求对锁定集合进行修改,但是调用者可能仍然希望了解锁定集合的当前值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28972</FONT>:<B normal"><FONT face="Times New Roman">oset</FONT></B>非空(<B normal"><FONT face="Times New Roman">set</FONT></B>也可能为非空)。不管哪一种情况,<B normal"><FONT face="Times New Roman">old_set</FONT></B>都包含一个原来锁定的集合的备份,在返回之前<B normal"><FONT face="Times New Roman">sys_sigprocmask</FONT></B>会试图将其拷贝回用户空间。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">sys_rt_sigprocmask</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28612</FONT>:<B normal"><FONT face="Times New Roman">sys_rt_sigprocmask</FONT></B>和<B normal"><FONT face="Times New Roman">sys_sigprocmask</FONT></B>非常类似,但是它也能够处理新的实时信号量。由于这两者之间的相似性,在这里我仅仅介绍一下它们之间有趣的区别。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">28638</FONT>:与如下代码不相类似的是</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">    /* how sys_sigprocmask does SIG_BLOCK.*/</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">      new_set =  *set ;<B normal">      /</B>* line 28938 */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">      blocked |= new_set ;    /* line 28952 */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>(作为一个例子采用<B normal"><FONT face="Times New Roman">SIG_BLOCK</FONT></B>的情况),实际代码类似如下代码:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">    /* how sys_rt_sigprocmask does SIG_BLOCK.*/</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">      new_set =  *set ;<B normal">      /</B>* line 28625 */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">      new_set |= old_set ; <B normal">   /</B>* line 28639 */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">      blocked |= new_set ;    /* line 28648 */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>我不明白为什么<B normal"><FONT face="Times New Roman">sys_rt_sigprocmask</FONT></B>不使用和<B normal"><FONT face="Times New Roman">sys_sigprocmask</FONT></B>相同的方式实现,而且这样还可以节约一点效率。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">中断</H2><P 0cm 0cm 0pt">中断的名字十分形象,因为它们终止了系统正常的处理过程。在前面第<FONT face="Times New Roman">5</FONT>章中你就已经看到了中断的一个例子:提供系统调用基本机制的软件中断。在本章中,我们来了解一下硬件中断。</P><P 0cm 0cm 0pt">和系统调用中断一样,硬件中断也可能转化为内核模式运行然后返回。如果用户进程运行时发生了中断,系统就转化为内核模式,并且内核要对中断做出响应。接着,内核将控制返回给用户进程,用户进程能够从当时离开的位置继续运行。</P><P 0cm 0cm 0pt">同系统调用中断的另一个区别是硬件中断可能在内核已经在内核模式下运行时发生。这在系统调用中很少发生——通常内核不会麻烦地触发系统调用中断,因为它可以直接调用目标内核函数。如果中断发生时系统处于内核模式,结果就同在用户模式下的机制相一致——唯一的区别是内核自身所特有的执行过程而不是用户进程的执行过程暂时地被中断。</P><P 0cm 0cm 0pt">如果内核在一段时期内不希望被中断,那么就可以使用<B normal"><FONT face="Times New Roman">cli</FONT></B>和<B normal"><FONT face="Times New Roman">sti</FONT></B>函数(<FONT face="Times New Roman">13105</FONT>行和<FONT face="Times New Roman">13104</FONT>行是<FONT face="Times New Roman">UP</FONT>版本;<FONT face="Times New Roman">1216</FONT>行和<FONT face="Times New Roman">1229</FONT>行是<FONT face="Times New Roman">SMP</FONT>版本)屏蔽和开启中断。这些函数根据底层的<FONT face="Times New Roman">x86</FONT>指令命名:<B normal"><FONT face="Times New Roman">cli</FONT></B>代表“清除中断标志”,<B normal"><FONT face="Times New Roman">sti</FONT></B>代表“设置中断标志”。其工作方式和其名称类似:<FONT face="Times New Roman">CPU</FONT>有一个“中断允许”标志,如果对其置位就允许中断,如果将其清空就禁止中断。因此,你可以使用<B normal"><FONT face="Times New Roman">cli</FONT></B>清空这个标志从而禁止中断,也可以使用<B normal"><FONT face="Times New Roman">sti</FONT></B>设置这个标志从而允许中断。在<FONT face="Times New Roman">UP</FONT>代码中,你可以选择调用两个等价的宏<B normal"><FONT face="Times New Roman">__cli</FONT></B>和<B normal"><FONT face="Times New Roman">__sti</FONT></B>——分别见<FONT face="Times New Roman">13105</FONT>行和<FONT face="Times New Roman">13104</FONT>行。</P><P 0cm 0cm 0pt">当然,把内核移植到非<FONT face="Times New Roman">x86</FONT>平台上会使用不同的底层指令——在这些体系结构中<B normal"><FONT face="Times New Roman">cli</FONT></B>和<B normal"><FONT face="Times New Roman">sti</FONT></B>函数的实现都不相同。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">IRQs</H2><P 0cm 0cm 0pt"><FONT face="Times New Roman">IRQ</FONT>,或者中断请求,是从硬件设备发往<FONT face="Times New Roman">CPU</FONT>的中断信号。作为对<FONT face="Times New Roman">IRQ</FONT>的响应,<FONT face="Times New Roman">CPU</FONT>跳转到某个地址——中断服务例行程序(<B normal"><I normal"><FONT face="Times New Roman">ISR</FONT></I></B>),更普通的情况是调用中断处理程序——内核在前面已经对这些处理程序进行了登记。中断处理程序是内核执行的为中断服务的函数;从中断处理程序中返回就继续执行中断前所在位置的代码。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">IRQ</FONT>是有编号的,每一个硬件设备在系统中都对应一个<FONT face="Times New Roman">IRQ</FONT>号码。例如在<FONT face="Times New Roman">IBM PC</FONT>体系结构中,<FONT face="Times New Roman">IRQ 0</FONT>就关联着一个每秒产生<FONT face="Times New Roman">100</FONT>次中断的定时器。把<FONT face="Times New Roman">IRQ</FONT>号码和设备关联起来,使得<FONT face="Times New Roman">CPU</FONT>可以区分每个中断是哪个设备产生的,从而允许它跳转到正确的中断处理程序。(在某些情况中,在一个系统中一个<FONT face="Times New Roman">IRQ</FONT>号可以被多个设备所共用,当然这不是非常普遍的情况。)</P><H2 13pt 0cm; TEXT-INDENT: 0cm">Bottom Halves</H2><P 0cm 0cm 0pt">中断处理程序的下半部分(<FONT face="Times New Roman">bottom half</FONT>)是无须立即执行的部分。在某些中断之后,你甚至可能根本就不需要执行它。</P><P 0cm 0cm 0pt">给定的中断处理程序从概念上可以被分为上半部分(<FONT face="Times New Roman">top half</FONT>)和下半部分(<FONT face="Times New Roman">bottom half</FONT>);在中断发生时上半部分的处理过程立即执行,但是下半部分(如果有的话)却推迟执行。这是通过把上半部分和下半部分处理为独立的函数并对其区别对待实现的。总之,上半部分要决定其相关的下半部分是否需要执行。不能推迟的部分显然不会属于下半部分,但是可以推迟的部分只是可能属于下半部分。</P><P 0cm 0cm 0pt">你也许会很奇怪为什么<FONT face="Times New Roman">Linux</FONT>会辛苦地把它们区分开——为什么要延迟呢?一个原因是要把中断的总延迟时间最小化。<FONT face="Times New Roman">Linux</FONT>内核定义了两种类型的中断,快速的和慢速的,这两者之间的一个区别是慢速中断自身还可以被中断,而快速中断则不能。因此,当处理快速中断时,如果有其它中断到达——不管是快速中断还是慢速中断——它们都必须等待。为了尽可能快地处理这些其它的中断,内核就需要尽可能地将处理延迟到下半部分执行。</P><P 0cm 0cm 0pt">另外一个原因是,在最低层,当内核执行上半部分时,中断控制芯片将被告知禁止正在服务的这个特殊<FONT face="Times New Roman">IRQ</FONT>(这和<FONT face="Times New Roman">CPU</FONT>级别的中断禁止不同,它把快速中断和慢速中断区别开来)。我们并不希望这种状态会持续地比需要的时间还长,因此只有上半部分中时间最为关键的部分才被处理,但是下半部分中其它的工作就要延迟处理了。</P><P 0cm 0cm 0pt">区分上下部分还有一个原因是处理程序的下半部分包含有一些中断所不一定非要处理的操作,只要内核可以在一系列设备中断之后可以从某些地方得到。在这种情况下,执行对于每个中断的下半部分的处理完全是一种浪费,它可以稍稍延迟并在后来只执行一次。</P><P 0cm 0cm 0pt">最后一段的一个暗示是值得说明的:没有必要每次中断都调用下半部分。相反,是上半部分(或者也可能是其它代码)简单地标记下半部分,通过设置某一位来指明下半部分必须执行。如果下半部分已经标记过需要执行了,现在又再次标记,那么内核就简单地保持这个标记;当情况允许的时候,内核就对它进行处理。如果在内核有机会运行其下半部分之前给定的设备就已经发生了<FONT face="Times New Roman">100</FONT>次中断,那么内核的上半部分就运行<FONT face="Times New Roman">100</FONT>次,下半部分运行<FONT face="Times New Roman">1</FONT>次。</P><P 0cm 0cm 0pt">下半部分在内核中有时候被认为是“软<FONT face="Times New Roman">IRQ</FONT>”或者“软中断处理程序”,这有助于你理解今后要遇到的一些文件名和术语。</P><P 0cm 0cm 0pt">在本节的剩余内容中,我们将保持下半部分概念的抽象。下一节深入介绍定时器中断,包括其下半部分的处理,并展示了下半部分概念的一个有趣的滥用现象——我的意思是一个有趣的变种。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">数据结构</H2><P 0cm 0cm 0pt">同对信号量的处理一样,我们首先介绍一下中断和下半部分使用的重要的数据结构。图<FONT face="Times New Roman">6.1</FONT>阐述了这些数据类型之间的关系。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><wrapblock><v:shapetype><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path connecttype="rect" gradientshapeok="t" extrusionok="f"></v:path><lock aspectratio="t" v:ext="edit"></lock></v:shapetype><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">6.1 </FONT>有关中断的数据结构</P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt">我们从这里开始,体系结构无关的头文件<FONT face="Times New Roman">linux/interrupt.h</FONT>定义了<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构(<FONT face="Times New Roman">14844</FONT>行),它代表了内核接收到特定<FONT face="Times New Roman">IRQ</FONT>之后应该采取的操作(在本章后面的部分中你将看到<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构是如何与<FONT face="Times New Roman">IRQ</FONT>关联的)。其成员如下:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">handler</FONT></B>——指向某一函数的指针,该函数是作为对中断的响应所执行的操作。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">flags</FONT></B>——从与前面已经介绍过了的<B normal"><FONT face="Times New Roman">sa_flags</FONT></B>相同的集合中提取出来;这个集合从<FONT face="Times New Roman">12108</FONT>行开始。该集合中仅仅为此目的而出现的值只有<B normal"><FONT face="Times New Roman">SA_INTERRUPT</FONT></B>(使用另外一个中断来中断这个中断也是可以的),<B normal"><FONT face="Times New Roman">SA_SAMPLE_RANDOM</FONT></B>(考虑到这个中断也是源于物理随机性),和<B normal"><FONT face="Times New Roman">SA_SHIRQ</FONT></B>(这个<FONT face="Times New Roman">IRQ</FONT>和其它<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>共享)。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">mask</FONT></B>——在<FONT face="Times New Roman">x86</FONT>或者体系结构无关的代码中不会使用(除非将其设置为<FONT face="Times New Roman">0</FONT>);看起来只有在<FONT face="Times New Roman">SPARC64</FONT>的移植版本中要跟踪有关软盘的信息时才会使用它。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">name</FONT></B>——生成中断的硬件设备的名字。由于不止一个硬件可以共享一个<FONT face="Times New Roman">IRQ</FONT>,这在打印人工阅读程序时就有助于区分它们。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">dev_id</FONT></B>——标识硬件类型的一个唯一的<FONT face="Times New Roman">ID</FONT>——<FONT face="Times New Roman">Linux</FONT>支持的所有硬件设备的每一种类型都有一个由制造厂商定义的在此成员中记录的设备<FONT face="Times New Roman">ID</FONT>。其所有的可能值都是从一个巨大的集合中抽取出来的,这个集合在本书中没有介绍,因为它包含的内容是十分繁琐的,而且都是重复的——它仅仅是结构上类似于下面一小段代码的巨大宏定义块。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">#define PCI_DEVICE_ID_S3_868 0x8880</FONT></P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">#define PCI_DEVICE_ID_S3_928 0x88b0</FONT></P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">#define PCI_DEVICE_ID_S3_864_1 0x88c0</FONT></P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">#define PCI_DEVICE_ID_S3_864_2 0x88c2</FONT></P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm">在你看完这些的一部分之后,也就相当于将其完整的看了一下。可能你已经发现了,摘录的这部分内容是从包含针对基于<FONT face="Times New Roman">S3</FONT>的<FONT face="Times New Roman">PCI</FONT>显卡的设备<FONT face="Times New Roman">ID</FONT>的文件中选取的。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm">虽然<B normal"><FONT face="Times New Roman">dev_id</FONT></B>是一个指针,可它并不指向任何内容,但若将其解除参照就会引起错误。能够说明问题的是它的位结构模式。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">next</FONT></B>——如果<FONT face="Times New Roman">IRQ</FONT>是共享的,那么这就是指向队列中下一个<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构的指针。通常情况下,<FONT face="Times New Roman">IRQ</FONT>不是共享的,因此这个成员就为空。</P><P 0cm 0cm 0pt">接下来我们感兴趣的两个数据结构存在于体系结构相关的文件<FONT face="Times New Roman">arch/i386/kernel/irq.h</FONT>中。第一个是<B normal"><FONT face="Times New Roman">struct hw_interrupt_type</FONT></B>结构(<FONT face="Times New Roman">1673</FONT>行),它是一个抽象的中断控制器。这是一系列的指向函数的指针,这些函数处理控制器特有的操作:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo3; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">typename</FONT></B>——赋给控制器的人工可读的名字。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo3; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">startup</FONT></B>——允许从给定的控制器的<FONT face="Times New Roman">IRQ</FONT>所产生的事件。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo3; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">shutdown</FONT></B>——禁止从给定的控制器的<FONT face="Times New Roman">IRQ</FONT>所产生的事件。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo3; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">handle</FONT></B>——根据提供给该函数的<FONT face="Times New Roman">IRQ</FONT>处理唯一的中断。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo3; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">enable</FONT></B>和<B normal"><FONT face="Times New Roman">disable</FONT></B>——这两个函数基本上和<B normal"><FONT face="Times New Roman">startup</FONT></B>和<B normal"><FONT face="Times New Roman">shutdown</FONT></B>相同;存在的差异对于本书中涉及的代码都不很重要。(实际上,对于本书中包含的所有代码来说,<FONT face="Times New Roman"><B normal">enable</B>/<B normal">disable</B></FONT>函数对和<FONT face="Times New Roman"><B normal">startup</B>/<B normal">shutdown</B></FONT>函数对都是相同的。)</P><P 0cm 0cm 0pt">这个文件中我们感兴趣的另外一个数据结构是<B normal"><FONT face="Times New Roman">irq_desc_t</FONT></B><B normal">(</B><FONT face="Times New Roman">1698</FONT>行),它具有如下成员:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo4; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">status</FONT></B>——一个整数,它的位或者为<FONT face="Times New Roman">0</FONT>,或者对应从<FONT face="Times New Roman">1685</FONT>行到<FONT face="Times New Roman">1689</FONT>行定义的集合中抽取出的标志。这些标志的集合代表了<FONT face="Times New Roman">IRQ</FONT>的状态——<FONT face="Times New Roman">IRQ</FONT>是否被禁止了,有关<FONT face="Times New Roman">IRQ</FONT>的设备当前是否正被自动检测,等等。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo4; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">handler</FONT></B>——指向<B normal"><FONT face="Times New Roman">hw_interrupt_type</FONT></B>的指针。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo4; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">action</FONT></B>——指向<B normal"><FONT face="Times New Roman">irqaction</FONT></B>结构组成的队列的头。如同前面说明的一样,正常情况下每个<FONT face="Times New Roman">IRQ</FONT>只有一个操作,因此链接列表的正常长度是<FONT face="Times New Roman">1</FONT>(或者<FONT face="Times New Roman">0</FONT>)。但是,如果<FONT face="Times New Roman">IRQ</FONT>被两个或者多个设备所共享,那么这个队列中就有多个操作了。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo4; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">depth</FONT></B>——<B normal"><FONT face="Times New Roman">irq_desc_t</FONT></B>的当前用户的个数。主要是用来保证事件正在处理的过程中<FONT face="Times New Roman">IRQ</FONT>不会被禁止。</P><P 0cm 0cm 0pt"><B normal"><FONT face="Times New Roman">irq_desc_t</FONT></B>是在<B normal"><FONT face="Times New Roman">irq_desc</FONT></B>数组中(<FONT face="Times New Roman">733</FONT>行)积聚起来的。对于每一个<FONT face="Times New Roman">IRQ</FONT>都有一个数组入口,因此数组把每一个<FONT face="Times New Roman">IRQ</FONT>映射到和它相关的处理程序和<B normal"><FONT face="Times New Roman">irq_desc_t</FONT></B>中的其它信息上。</P><P 0cm 0cm 0pt">最后一个需要说明的数据结构集合从<FONT face="Times New Roman">29094</FONT>行开始;这些都与前面所讨论的下半部分有关:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo5; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">bh_mask_count</FONT></B>(<FONT face="Times New Roman">29094</FONT>行)——跟踪为每个下半部分提出的<FONT face="Times New Roman"><B normal">enable</B>/<B normal">disable</B></FONT>请求嵌套对的数组。这些请求通过调用<B normal"><FONT face="Times New Roman">enable_bh</FONT></B>(<FONT face="Times New Roman">12575</FONT>行)和<B normal"><FONT face="Times New Roman">disable_bh</FONT></B>(<FONT face="Times New Roman">12568</FONT>行)实现。每个禁止请求都增加计数器;每个使能请求都减小计数器。当计数器达到<FONT face="Times New Roman">0</FONT>时,所有未完成的禁止语句都已经被使能语句所匹配了,因此下半部分最终被重新使能。</P><B normal">bh_mask</B>和<B normal">bh_active</B>(14856行和14857行)——它们共同控制下半部分是否运行。它们两个都有32位,而每一个下半部分都占用一位。当一个上半部分(或者一些其它代码)决定其下半部分需要运行时,就通过设置<B normal">bh_active</B>(12498行中使用<B normal">mark_bh</B>)中的一位来标记下半部分。不管是否经过了这样的标记,下半部
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    < 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         分可能会通过清空<B normal"><FONT face="Times New Roman">bh_mask</FONT></B>中的相关位来整个跳过——通过调整<B normal"><FONT face="Times New Roman">bh_mask_count</FONT></B>入口,<B normal"><FONT face="Times New Roman">enable_bh</FONT></B>和<B normal"><FONT face="Times New Roman">disable_bh</FONT></B>完成了这个功能。</P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm">因此,对<B normal"><FONT face="Times New Roman">bh_mask</FONT></B>和<B normal"><FONT face="Times New Roman">bh_active</FONT></B>进行位<FONT face="Times New Roman">AND</FONT>运算就能够表明应该运行哪一个下半部分。特别是如果位与运算的结果是<FONT face="Times New Roman">0</FONT>,就没有下半部分需要运行。这种技术在内核中多次使用,例如在宏<B normal"><FONT face="Times New Roman">get_active_bhs</FONT></B>(<FONT face="Times New Roman">12480</FONT>行)中就使用了这种技术</P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">bh_base</FONT></B>(<FONT face="Times New Roman">14858</FONT>行)——这是一组简单的指向下半部分函数的指针。</P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.25pt">l         未命名的<B normal"><FONT face="Times New Roman">enum</FONT></B>——从<FONT face="Times New Roman">14866</FONT>行开始的未命名的<B normal"><FONT face="Times New Roman">enum</FONT></B>为内核使用的每一个下半部分指定了一个符号名称。例如,为了把计数器的下半部分标记为活动的,你可以这样的语句:</P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">mark_bh</FONT>(<FONT face="Times New Roman">TIME_BH</FONT>)<FONT face="Times New Roman">;</FONT></P>< 0cm 0cm 0pt 42.25pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">27450</FONT>行的确就是这样处理的。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">操作和IRQ</H2>< 0cm 0cm 0pt">一个经过仔细选择的小型函数集合处理了操作和<FONT face="Times New Roman">IRQ</FONT>之间的链接和解除链接。本节就是要讨论这些函数,以及那些从整体上对<FONT face="Times New Roman">IRQ</FONT>系统进行初始化的函数。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">init_ IRQ</H4>< 0cm 0cm 0pt; TEXT-INDENT: 0cm"><FONT face="Times New Roman">1597</FONT>:<FONT face="Times New Roman"> <B normal">init_ IRQ</B></FONT>初始化<FONT face="Times New Roman">IRQ</FONT>的处理。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1601</FONT>:<FONT face="Times New Roman"> </FONT>符号<B normal"><FONT face="Times New Roman">CONFIG_X86_ISWS_APIC</FONT></B>是为<FONT face="Times New Roman">SGI</FONT>虚拟工作站以及<FONT face="Times New Roman">SGI</FONT>的基于<FONT face="Times New Roman">x86</FONT>的工作站流水线而设置的。虽然同样基于<FONT face="Times New Roman">x86</FONT>的<FONT face="Times New Roman">CPU</FONT>,虚拟工作站不能和基于<FONT face="Times New Roman">IBM PC</FONT>的体系结构共享很多其它特性——特别是如同你看到的,它们的中断处理有些不同。我们以后将忽略虚拟工作站所特有的代码。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1609</FONT>:<FONT face="Times New Roman"> </FONT>建立中断描述符表,给<FONT face="Times New Roman">32</FONT>项到<FONT face="Times New Roman">95</FONT>项(十进制)赋缺省值。在这个过程中使用了<B normal"><FONT face="Times New Roman">set_nitr_gate</FONT></B>(<FONT face="Times New Roman">6647</FONT>行),该函数很快就会介绍到。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1651</FONT>:<FONT face="Times New Roman"> </FONT>建立<FONT face="Times New Roman">IRQ 2</FONT>(级联中断)和<FONT face="Times New Roman">IRQ13</FONT>(为<FONT face="Times New Roman">FPU</FONT>使用——请参看<FONT face="Times New Roman">955</FONT>行)。和这两个<FONT face="Times New Roman">IRQ</FONT>有关的<B normal"><FONT face="Times New Roman">irqaction</FONT></B>结构分别是<B normal"><FONT face="Times New Roman">irq2</FONT></B>(<FONT face="Times New Roman">979</FONT>行)和<B normal"><FONT face="Times New Roman">irq13</FONT></B>(<FONT face="Times New Roman">974</FONT>行)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">init_ISA_irqs</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1578</FONT>:<FONT face="Times New Roman"> </FONT>该函数填充<B normal"><FONT face="Times New Roman">irq_desc</FONT></B>数组,为<FONT face="Times New Roman">ISA</FONT>总线类型的机器(也就是所有标准<FONT face="Times New Roman">C</FONT>)初始化所有<FONT face="Times New Roman">IRQ</FONT>。虽然该函数没有声明为<B normal"><FONT face="Times New Roman">static</FONT></B>类型的,也没有使用<B normal"><FONT face="Times New Roman">__initfunc</FONT></B>标签标记,但是它只会被<B normal"><FONT face="Times New Roman">init_ IRQ</FONT></B>调用。因此,只有在内核初始化过程中这个函数才是必要的。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1583</FONT>:<FONT face="Times New Roman"> </FONT>对<B normal"><FONT face="Times New Roman">irq_desc</FONT></B>中的每一个元素,系统为<B normal"><FONT face="Times New Roman">status</FONT></B>,<B normal"><FONT face="Times New Roman">action</FONT></B>和<B normal"><FONT face="Times New Roman">depth</FONT></B>成员赋与了不会惹人反对的,也不会使人吃惊的的缺省值。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1589</FONT>:<FONT face="Times New Roman"> </FONT>原来的(在<FONT face="Times New Roman">CI</FONT>之前)<FONT face="Times New Roman">IRQ</FONT>使用<B normal"><FONT face="Times New Roman">i8259A_irq_type</FONT></B>(<FONT face="Times New Roman">723</FONT>行)处理。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1592</FONT>:<FONT face="Times New Roman"> </FONT>编号比较高的<FONT face="Times New Roman">IRQ</FONT>初始化为<B normal"><FONT face="Times New Roman">no_irq_type</FONT></B>(<FONT face="Times New Roman">701</FONT>行),这是一个必要的空处理程序。后来它们可能会改变——实际上,如果你使用了<FONT face="Times New Roman">CI</FONT>卡,就确实会改变,就象现在的大多数<FONT face="Times New Roman">C</FONT>一样。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">set_intr_gate</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">6647</FONT>:<FONT face="Times New Roman"> <B normal">set_intr_gate</B></FONT>在<FONT face="Times New Roman">x86CPU</FONT>的中断描述符表(<FONT face="Times New Roman">IDT</FONT>)中建立一个项。在基于<FONT face="Times New Roman">x86</FONT>的系统中发生的每一个软件中断和硬件中断都有一个编号,这个编号被<FONT face="Times New Roman">CPU</FONT>用作是对这个表的索引。(包括系统调用中断——编号为<FONT face="Times New Roman">0x80</FONT>——在第<FONT face="Times New Roman">5</FONT>章中我们已经介绍过了。)表中相关的项是中断发生时(内核)函数需要跳转到的地址。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">setup_x86_irq</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1388</FONT>:<B normal"><FONT face="Times New Roman">setup_x86_irq</FONT></B>给指定的<FONT face="Times New Roman">IRQ</FONT>增加了一个操作(一个<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构)。例如,在<FONT face="Times New Roman">6088</FONT>行使用它来记录定时器的中断。它还可以通过<B normal"><FONT face="Times New Roman">request_irq</FONT></B>(<FONT face="Times New Roman">1439</FONT>行)使用,这在下一节介绍。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1398</FONT>:<FONT face="Times New Roman"> Linux</FONT>使用了几种物理的随机源——例如中断——把一系列不可预知的值提供给设备<FONT face="Times New Roman">/dev/random</FONT>,这是一个有限却具有很高随机性的数据源,还有<FONT face="Times New Roman">/dev/urandom</FONT>,这是对应<FONT face="Times New Roman">/dev/random</FONT>的无限的但是随机性较小的对应版本。随机系统作为一个整体在本书中并没有涉及,但是如果你不知道这个概念,这一大部分代码就会显得十分神秘。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1412</FONT>:<FONT face="Times New Roman"> </FONT>如果现存的操作列表非空,<B normal"><FONT face="Times New Roman">setup_x86_irq</FONT></B>必须保证现存的操作和新的操作可以共享这个<FONT face="Times New Roman">IRQ</FONT>。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1414</FONT>:<FONT face="Times New Roman"> </FONT>验证这个<FONT face="Times New Roman">IRQ</FONT>可以和其上现存的<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构共享。这种测试是十分有效的,它部分是基于我们的一些认识:没有必要遍历执行队列中的所有操作,也没有必要检测它们可能共享的所有情况。除非这两个操作和第一个操作都允许共享<FONT face="Times New Roman">IRQ</FONT>,否则不会允许第一个操作后的所有操作都进入队列。因此,如果第一个操作可以共享<FONT face="Times New Roman">IRQ</FONT>,那么队列中的其它操作也就可以共享<FONT face="Times New Roman">IRQ</FONT>;如果第一个操作不能共享,那么队列中的其它任何操作也都不能共享<FONT face="Times New Roman">IRQ</FONT>。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1420</FONT>:<FONT face="Times New Roman"> IRQ</FONT>正在被共享。<B normal"><FONT face="Times New Roman">setup_x86_irq</FONT></B>利用<B normal"><FONT face="Times New Roman">p</FONT></B>向前执行操作队列直到末尾,离开时<B normal"><FONT face="Times New Roman">p</FONT></B>指向队列的最后一个元素的<B normal"><FONT face="Times New Roman">next</FONT></B>域。它也会增加<B normal"><FONT face="Times New Roman">shared</FONT></B>标志的值,这将会在<FONT face="Times New Roman">1429</FONT>行中被使用。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1427</FONT>:<FONT face="Times New Roman"> <B normal">p</B></FONT>现在指向队列中的最后一个元素的<B normal"><FONT face="Times New Roman">next</FONT></B>域,如果要共享<FONT face="Times New Roman">IRQ</FONT>,或者<B normal"><FONT face="Times New Roman">p</FONT></B>在不共享的情况下指向<B normal"><FONT face="Times New Roman">irq_desc[irq].action</FONT></B>——指向队列的头节点的指针。不管怎样,指针现在被设置为新的元素了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1429</FONT>:<FONT face="Times New Roman"> </FONT>如果还没有操作和这个<FONT face="Times New Roman">IRQ</FONT>关联,<B normal"><FONT face="Times New Roman">irq_desc[irq]</FONT></B>的其它部分也就还没有设置,在这里就需要对其初始化了。特别要注意<FONT face="Times New Roman">1433</FONT>行中为这个<FONT face="Times New Roman">IRQ</FONT>调用了<B normal"><FONT face="Times New Roman">startup</FONT></B>函数。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">request_irq</H4>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1439</FONT>:<FONT face="Times New Roman"> <B normal">request_irq</B></FONT>从提供的值中创建一个<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构,并将其加入对应给定的<FONT face="Times New Roman">IRQ</FONT>的<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>列表中。(如果你对<FONT face="Times New Roman">C++</FONT>和<FONT face="Times New Roman">Java</FONT>比较熟悉,可以把它当作是操作的构造函数。)它的实现非常简单明了。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1448</FONT>:<FONT face="Times New Roman"> </FONT>对一对输入值进行健全性检测。注意没有必要测试<B normal"><FONT face="Times New Roman">irq</FONT></B>是否小于<FONT face="Times New Roman">0</FONT>,因为它是一个无符号数。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1453</FONT>:<FONT face="Times New Roman"> </FONT>动态分配新的<B normal"><FONT face="Times New Roman">struct irqaction</FONT></B>结构。为此目的使用的函数<B normal"><FONT face="Times New Roman">kmalloc</FONT></B>在第<FONT face="Times New Roman">8</FONT>章中简单介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1458</FONT>:<FONT face="Times New Roman"> </FONT>填充新的操作并使用<B normal"><FONT face="Times New Roman">setup_x86_irq</FONT></B>将其加入操作列表。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">free_irq</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1472</FONT>:<FONT face="Times New Roman"> <B normal">free_irq</B></FONT>是<B normal"><FONT face="Times New Roman">request_irq</FONT></B>的补数(<FONT face="Times New Roman">inverse</FONT>)。如果<B normal"><FONT face="Times New Roman">request_irq</FONT></B>类似于操作的构造函数,那么这就是操作的析构函数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1481</FONT>:<FONT face="Times New Roman"> </FONT>在确保<B normal"><FONT face="Times New Roman">irq</FONT></B>在范围内以后,<B normal"><FONT face="Times New Roman">free_irq</FONT></B>找到有关的<B normal"><FONT face="Times New Roman">irq_desc</FONT></B>项并且开始遍历操作列表。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1483</FONT>:<FONT face="Times New Roman"> </FONT>除非它有正确的设备<FONT face="Times New Roman">ID</FONT>,否则就忽略这个队列元素。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1487</FONT>:<FONT face="Times New Roman"> </FONT>把这个元素从队列中分离出来并且释放其所占用的内存。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1489</FONT>:<FONT face="Times New Roman"> </FONT>如果现在操作队列为空——也就是如果队列中只有唯一一个元素没有被链接——设备就会被关闭。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1495</FONT>:<FONT face="Times New Roman"> </FONT>如果控制流程执行到这里,就意味着<B normal"><FONT face="Times New Roman">free_irq</FONT></B>处理了整个操作列表而没有发现匹配的<B normal"><FONT face="Times New Roman">dev_id</FONT></B>。如果发现了匹配对象,<FONT face="Times New Roman">1493</FONT>行的<B normal"><FONT face="Times New Roman">goto</FONT></B>语句就已经跳过了本行。因此,这个试图释放<FONT face="Times New Roman">IRQ</FONT>操作的努力是错误的;在这种情况下<B normal"><FONT face="Times New Roman">free_irq</FONT></B>会打印出一条警告信息对当前状况进行描述。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">prove_irq_on</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1506</FONT>:<FONT face="Times New Roman"> <B normal">probe_irq_on</B></FONT>实现了内核<FONT face="Times New Roman">IRQ</FONT>自动探测的重要的一部分。阅读<FONT face="Times New Roman">14889</FONT>行开始的标题注释就得到了对整个进程的描述。根据描述我们知道这里要作的工作(只)是执行步骤三:暂时使能所有没有定义的<FONT face="Times New Roman">IRQ</FONT>,以使得<B normal"><FONT face="Times New Roman">probe_irq_on</FONT></B>的调用者可以检测它们。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1514</FONT>:<FONT face="Times New Roman">  </FONT>对于除<FONT face="Times New Roman">IRQ 0</FONT>之外的每一个<FONT face="Times New Roman">IRQ</FONT>,如果这个<FONT face="Times New Roman">IRQ</FONT>还没有与之相关的操作,<B normal"><FONT face="Times New Roman">probe_irq_on</FONT></B>会记录下这个<FONT face="Times New Roman">IRQ</FONT>正在自动探测的事实并启动关联设备。顺便说明一下,我不认为有任何的原因使这个循环向后执行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1524</FONT>:<FONT face="Times New Roman"> </FONT>忙等待约十分之一秒时间以允许生成伪中断的设备取消自己。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1530</FONT>:<FONT face="Times New Roman"> </FONT>循环再次遍历所有的<FONT face="Times New Roman">IRQ</FONT>,这一次要过滤出所有生成伪中断的设备。这个循环每次重复执行都是从<FONT face="Times New Roman">1</FONT>开始而不是从<FONT face="Times New Roman">0</FONT>开始,这是因为不需要自动检测的<FONT face="Times New Roman">IRQ</FONT>都被忽略掉了,而<FONT face="Times New Roman">IRQ0</FONT>是从来都不会自动检测的。速度在这里也是一个问题;在十分之一秒的延时之后——这是很长的一段时间,即使是从慢速的<FONT face="Times New Roman">CPU</FONT>的观点来看也是如此——一个循环或多或少都是有些不合理的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1537</FONT>:<FONT face="Times New Roman"> </FONT>如果设备在<FONT face="Times New Roman">1524</FONT>行的等待过程中触发了中断,这个中断可能就是伪中断:在此期间系统应该还没有和设备通讯过,因此设备也应该还没有和系统通讯过。因此自动探测位将被清空,处理程序再次关闭。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1544</FONT>:<FONT face="Times New Roman"> </FONT>返回特殊数字<FONT face="Times New Roman">0x12345678</FONT>,其原因将在下面的讨论中进行说明。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">prove_irq_off</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1547</FONT>:<FONT face="Times New Roman"> <B normal">prove_irq_off</B></FONT>实现了<FONT face="Times New Roman">IRQ</FONT>自动探测的另外一部分重要的内容。这里的工作是决定对探测到的哪一个<FONT face="Times New Roman">IRQ</FONT>做出响应,并返回其中的一个<FONT face="Times New Roman">IRQ</FONT>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1551</FONT>:<FONT face="Times New Roman"> </FONT>检测名字很容易让人误解的参数<B normal"><FONT face="Times New Roman">unused</FONT></B>和<B normal"><FONT face="Times New Roman">probe_irq_on</FONT></B>返回的特殊数字是否相同。调用者假定象下面这样处理:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            magic = prove_irq_on</FONT>()</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            /* … */</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            probe_irq_off</FONT>(<FONT face="Times New Roman">magic</FONT>)<FONT face="Times New Roman">;</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>如果偶然使用了其它的方法调用了<B normal"><FONT face="Times New Roman">probe_irq_off</FONT></B>(例如,如果由于其它一些逻辑调用者偶尔跳过了对<B normal"><FONT face="Times New Roman">probe_irq_on</FONT></B>的调用),那么提供的参数可能不会包含正确的值。可能更重要的是这个参数给正在编写代码使用这个函数的程序员提供了一些信息:在研究其参数应该是什么的时候,你会发现在调用它的时候一直遵守的规则。这种规则很容易就被过度使用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>通过对紧随的错误消息的严格调整,似乎该函数的早期版本中可能已经在其参数中采用了调用者的地址。如果的确如此,这个测试就具有了第三种目的:把仍然不正确使用这个函数的调用者检测出来。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1557</FONT>:<FONT face="Times New Roman"> </FONT>循环遍历所有的<FONT face="Times New Roman">IRQ</FONT>,搜寻响应调用者探测的所有设备。这个循环也可以从<FONT face="Times New Roman">1</FONT>开始循环,这和前面讨论的<B normal"><FONT face="Times New Roman">probe_irq_on</FONT></B>的原因是相同的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1560</FONT>:<FONT face="Times New Roman"> </FONT>内核没有试图自动检测这个<FONT face="Times New Roman">IRQ</FONT>上的任何内容;它跳到了下一个<FONT face="Times New Roman">IRQ</FONT>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1563</FONT>:<FONT face="Times New Roman"> <B normal">IRQ_INPROGRESS</B></FONT>标志指明了该<FONT face="Times New Roman">IRQ</FONT>的一个中断已经到达。由于<B normal"><FONT face="Times New Roman">probe_irq_on</FONT></B>可能捕获所有的伪中断,假定这是对探测的真实响应。成功地自动检测到<FONT face="Times New Roman">IRQ</FONT>的数量因此而增一,同时保存第一次的数字。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1568</FONT>:<FONT face="Times New Roman"> </FONT>不管是否成功自动探测到<FONT face="Times New Roman">IRQ</FONT>,自动探测标志都要减少,并且再次结束处理程序。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1573</FONT>:<FONT face="Times New Roman"> </FONT>如果不止一个<FONT face="Times New Roman">IRQ</FONT>被成功地自动探测到,就通过否定的<B normal"><FONT face="Times New Roman">irq_found</FONT></B>来通知调用者。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1575</FONT>:<FONT face="Times New Roman"> </FONT>返回<B normal"><FONT face="Times New Roman">irq_found</FONT></B>——<FONT face="Times New Roman">0</FONT>,或者(可能是经过求反的)第一个成功地自动探测到的<FONT face="Times New Roman">IRQ</FONT>号。注意如果发现了设备,则返回值决不会是<FONT face="Times New Roman">0</FONT>,因为内核不会试图自动检测<FONT face="Times New Roman">IRQ 0</FONT>。因此当没有自动检测到<FONT face="Times New Roman">IRQ</FONT>时,<B normal"><FONT face="Times New Roman">probe_irq_off</FONT></B>就返回<FONT face="Times New Roman">0</FONT>。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">硬件中断处理程序和下半部分</H2><P 0cm 0cm 0pt"><FONT face="Times New Roman">x86</FONT>系列的实际中断处理程序是微不足道的;在最低的层次上,这是通过反复使用<B normal"><FONT face="Times New Roman">BUILD_IRQ</FONT></B>宏(<FONT face="Times New Roman">1886</FONT>行)建立了一系列小汇编函数而实现的。<B normal"><FONT face="Times New Roman">BUILD_ IRQ</FONT></B>自己被<B normal"><FONT face="Times New Roman">BI</FONT></B>宏调用(<FONT face="Times New Roman">866</FONT>行),这个宏又顺次被<B normal"><FONT face="Times New Roman">BUILD_16_IRQS</FONT></B>宏(<FONT face="Times New Roman">869</FONT>行)调用,该宏在<FONT face="Times New Roman">878</FONT>行到<FONT face="Times New Roman">895</FONT>行的代码中用来建立汇编程序。这一连串的宏调用的目的仅仅是试图减少必须编写的代码数量和复杂度——我们应该使用<FONT face="Times New Roman">256</FONT>次对<B normal"><FONT face="Times New Roman">BUILD_IRQ</FONT></B>的调用,而不是<FONT face="Times New Roman">16</FONT>次对<B normal"><FONT face="Times New Roman">BUILD_16_IRQS</FONT></B>的调用。</P><P 0cm 0cm 0pt">汇编程序和如下代码相类似:</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">IRQ0x00_interrupt:</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  push1 0x00-256</FONT></P><P 0cm 0cm 0pt"><FONT face="Times New Roman">  jmp common_interrupt</FONT></P><P 0cm 0cm 0pt">也就是每一次都简单地把它的<FONT face="Times New Roman">IRQ</FONT>号(减去<FONT face="Times New Roman">256</FONT>,原因在<FONT face="Times New Roman">1897</FONT>行有论述)压入堆栈并且跳转到正常的中断程序。</P><P 0cm 0cm 0pt">正常的中断处理程序是调用<B normal"><FONT face="Times New Roman">common_interrupt</FONT></B>,该函数也十分简短。它是使用<B normal"><FONT face="Times New Roman">BUILD_COMMON_IRQ</FONT></B>宏(<FONT face="Times New Roman">1871</FONT>行)建立的,在为<B normal"><FONT face="Times New Roman">do_IRQ</FONT></B>进行安排之后简单调用<B normal"><FONT face="Times New Roman">do_IRQ</FONT></B>返回给<B normal"><FONT face="Times New Roman">from_intr</FONT></B>(<FONT face="Times New Roman">233</FONT>行)——这是第<FONT face="Times New Roman">5</FONT>章中介绍的系统调用的一部分。随后将要介绍的<B normal"><FONT face="Times New Roman">do_ IRQ</FONT></B>(<FONT face="Times New Roman">1362</FONT>行)负责查看中断是否已经被处理了。</P><P 0cm 0cm 0pt">在介绍这些代码之前,从总体上观察一下在处理单个中断时这些部分如何组织在一起是很有帮助的:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">1.         CPU</FONT>跳转到<B normal"><FONT face="Times New Roman">IRQ0x<I normal">NN</I>_interrupt</FONT></B>程序(其中的<I normal"><FONT face="Times New Roman">NN</FONT></I>是中断号),它将其唯一的中断号压入堆栈并跳转到<B normal"><FONT face="Times New Roman">common_interrupt</FONT></B>。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">2.         <B normal">common_interrupt</B></FONT>调用<B normal"><FONT face="Times New Roman">do_IRQ</FONT></B>并保证当<B normal"><FONT face="Times New Roman">do_IRQ</FONT></B>返回时控制流程能够转向<B normal"><FONT face="Times New Roman">ret_from_intr</FONT></B>。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">3.         <B normal">do_IRQ</B></FONT>调用中断处理器芯片独有的代码——直接和芯片通讯的代码,如果需要就用它来处理中断。对于<FONT face="Times New Roman">PC</FONT>体系结构中流行的<FONT face="Times New Roman">8259A</FONT>控制器芯片,处理函数是<B normal"><FONT face="Times New Roman">do_8259A_IRQ</FONT></B>,在这里提到它仅仅是为了举一个例子而已。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">4.         <B normal">do_8259A_IRQ</B></FONT>暂时禁止正在处理的特殊<FONT face="Times New Roman">IRQ</FONT>,调用<B normal"><FONT face="Times New Roman">handle_IRQ_event</FONT></B>,接着重新使能这个<FONT face="Times New Roman">IRQ</FONT>。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">5.         <B normal">handle_IRQ_event</B></FONT>为慢速<FONT face="Times New Roman">IRQ</FONT>使能中断,或者为处理快速<FONT face="Times New Roman">IRQ</FONT>而将这些中断保持在禁止状态。接着遍历一个已经和这个<FONT face="Times New Roman">IRQ</FONT>建立联系的函数队列,并依次调用这些函数。由于中断为慢速<FONT face="Times New Roman">IRQ</FONT>而使能,这里就是慢速<FONT face="Times New Roman">IRQ</FONT>的处理程序可能被其它中断所中断的地方。在执行完队列中的所有函数之后,<B normal"><FONT face="Times New Roman">handle_IRQ_event</FONT></B>禁止中断并返回控制器所特有的处理函数,而该函数将返回到<B normal"><FONT face="Times New Roman">do_IRQ</FONT></B>。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo3; tab-stops: list 42.25pt"><FONT face="Times New Roman">6.         <B normal">do_IRQ</B></FONT>处理所有挂起等待的下半部分,接着返回。如同你已经知道的那样,它要返回到<B normal"><FONT face="Times New Roman">ret_from_intr</FONT></B>。第<FONT face="Times New Roman">5</FONT>章中介绍了从此之后的处理内容。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_IRQ</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1375</FONT>:<FONT face="Times New Roman"> </FONT>更新内核的一些统计数字并调用与该<FONT face="Times New Roman">IRQ</FONT>相关的处理函数。对于一些老式<FONT face="Times New Roman"> PC</FONT>上的标号较小的<FONT face="Times New Roman">IRQ</FONT>来说,其处理程序是<B normal"><FONT face="Times New Roman">handle_IRQ_event</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1383</FONT>:<FONT face="Times New Roman"> </FONT>如果下半部分是激活的,内核现在就使用<B normal"><FONT face="Times New Roman">do_bottom_half</FONT></B>(<FONT face="Times New Roman">29126</FONT>行)对它们进行处理。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_IRQ_event</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1292</FONT>:<B normal"><FONT face="Times New Roman">do_IRQ_event</FONT></B>为<B normal"><FONT face="Times New Roman">do_8259A_IRQ</FONT></B>(<FONT face="Times New Roman">821</FONT>行)负担了大半重要的工作。本书中没有涉及到的其它一些代码也会调用这个函数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1302</FONT>:与其描述(<FONT face="Times New Roman">12094</FONT>行)正好相反,<B normal"><FONT face="Times New Roman">SA_INTERRUPT</FONT></B>标志并不是一个<FONT face="Times New Roman">no_op</FONT>。如果没有设置该标志,在接下来的代码中就允许中断。这是快速中断和慢速中断之间历史上遗留下来的区别,这一点我们已经讨论过了。(处理这两种类型中断的代码通常有很多差异,但是结果是相同的——代码已经被处理得更加出色了。)恰当的说,这个标志似乎是大多数情况下都为慢速设备使用——顾名思义,就是软盘设备。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1305</FONT>:<FONT face="Times New Roman"> </FONT>通过调用每一个的处理函数来遍历执行这个<FONT face="Times New Roman">IRQ</FONT>的操作队列(队列头是由调用者提供的)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1310</FONT>:<FONT face="Times New Roman"> </FONT>这里的中断触发是用来为<FONT face="Times New Roman">/dev/random</FONT>和<FONT face="Times New Roman">/dev/urandom</FONT>增加一些随机信息——从大体上看来,大部分中断都是随机发生的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">1312</FONT>:<FONT face="Times New Roman"> </FONT>禁止中断(当条件具备时调用者可以再次允许这些中断)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_bottom_half</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29126</FONT>:<FONT face="Times New Roman">Linux</FONT>代码中有三处调用了<B normal"><FONT face="Times New Roman">do_bottom_half</FONT></B>:<FONT face="Times New Roman">26707</FONT>行,<FONT face="Times New Roman">243</FONT>行,<FONT face="Times New Roman">1384</FONT>行。(你会发现,其中的两个是在体系结构特有的文件中的;在非<FONT face="Times New Roman">x86</FONT>平台体系结构特有的文件中对应部分也会调用这个函数。)因此,下半部分是用来处理如下三种情况的:</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo4; tab-stops: list 57.25pt">l         当决定随后哪一个进程应该获得<FONT face="Times New Roman">CPU</FONT>时。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo4; tab-stops: list 57.25pt">l         当从系统调用中返回时。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo4; tab-stops: list 57.25pt">l         在从<B normal"><FONT face="Times New Roman">do_IRQ</FONT></B>中返回之前——也就是说,在每个中断之后。代码中的注释暗示了在内核的未来版本中不一定总在这里运行下半部分。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29130</FONT>:下半部分的一个理想特性是在某一时刻只能有一个下半部分处于运行状态。这种特性在这里,也就是锁定(<FONT face="Times New Roman">locking</FONT>)在<FONT face="Times New Roman">UP</FONT>代码中体现出重要意义的位置之一,得到了强化。首先调用<B normal"><FONT face="Times New Roman">soft_trylock</FONT></B>(<FONT face="Times New Roman">UP</FONT>版本在<FONT face="Times New Roman">12559</FONT>行——第<FONT face="Times New Roman">10</FONT>章中可以查看所有的<FONT face="Times New Roman">SMP</FONT>版本),只有在<B normal"><FONT face="Times New Roman">local_bh_count[cpu]</FONT></B>原来为<FONT face="Times New Roman">0</FONT>时这个函数才把<B normal"><FONT face="Times New Roman">local_bh_count [cpu]</FONT></B>设置为<FONT face="Times New Roman">1</FONT>并返回真值。根据<FONT face="Times New Roman">17479</FONT>行,对于<FONT face="Times New Roman">UP</FONT>来说<B normal"><FONT face="Times New Roman">cpu</FONT></B>总是<FONT face="Times New Roman">0</FONT>,而且你应该注意到<B normal"><FONT face="Times New Roman">softirq_trylock</FONT></B>自己是不能被中断的,因为在这里中断已经被禁止了。<B normal"><FONT face="Times New Roman">softirq_trylock</FONT></B>和对应的<B normal"><FONT face="Times New Roman">softirq_endlock</FONT></B>(<FONT face="Times New Roman">12561</FONT>行),就是仅仅为了以下目的而退出的,而不存在其它原因:协助保证这个下半部分不会被其它下半部分中断(虽然它们可以被上半部分所中断)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29131</FONT>:如果成功获得了锁,那么该函数就尝试另一个函数,<FONT face="Times New Roman">10736</FONT>行的<B normal"><FONT face="Times New Roman">hardirq_trylock</FONT></B>。它只报告当前执行进程是否位于<B normal"><FONT face="Times New Roman">hardirq_enter/hardirq_exit</FONT></B>对(<FONT face="Times New Roman">10739</FONT>行和<FONT face="Times New Roman">10740</FONT>行)之间。对于<FONT face="Times New Roman">UP</FONT>,这两者的定义是和<B normal"><FONT face="Times New Roman">irq_enter</FONT></B>与<B normal"><FONT face="Times New Roman">irq_exit</FONT></B>(<FONT face="Times New Roman">1810</FONT>行和<FONT face="Times New Roman">1811</FONT>行)两者的定义相同的;后两个函数在<B normal"><FONT face="Times New Roman">handle_IRQ_event</FONT></B>中使用,当然在其它我们所没有讨论到的地方也对它们有所引用。这些宏协同工作来保证<B normal"><FONT face="Times New Roman">__cli</FONT></B>和<B normal"><FONT face="Times New Roman">__sti</FONT></B>对能够正确的进行嵌套——由于<FONT face="Times New Roman">CPU</FONT>不会嵌套使用这些宏,我们必须保证不会使用<B normal"><FONT face="Times New Roman">__sti</FONT></B>处理其它的<B normal"><FONT face="Times New Roman">__cli</FONT></B>,而这也不是我们所希望的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29132</FONT>:没有其它下半部分在运行,而且<B normal"><FONT face="Times New Roman">do_bottom_half</FONT></B>有权使能硬件中断。因此,它就使能硬件中断,运行下半部分,接着再次禁止中断。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29135</FONT>:释放该函数已经获取的锁并返回。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">run_bottom_halves</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29110</FONT>:现在内核能够运行挂起等待的下半部分。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29115</FONT>:存储当前局部变量<B normal"><FONT face="Times New Roman">active</FONT></B>中活动的——也就是被标记过的——下半部分的集合,并使用<B normal"><FONT face="Times New Roman">clear_active_bh</FONT></B>宏(<FONT face="Times New Roman">12481</FONT>行)清空全局变量<B normal"><FONT face="Times New Roman">bh_active</FONT></B>中的设置位。对<B normal"><FONT face="Times New Roman">bh_active</FONT></B>中这些位的清除将同时取消对所有下半部分的标记。</P><P 0cm 0cm 0pt">现在你就应该可以看到下半部分有时候是批量处理的,这一点在前面已经进行了没有论证的说明。此处中断是被使能的,因此如果在<B normal"><FONT face="Times New Roman">run_bottom_halves</FONT></B>把<B normal"><FONT face="Times New Roman">bh_active</FONT></B>拷贝到<B normal"><FONT face="Times New Roman">active</FONT></B>之前就有中断触发并标记已经标记过的下半部分,那么上半部分就已经运行了两次,然而下半部分只运行了一次。还有,由于这个中断可以被自己中断,在下半部分运行一次的时候上半部分就已经运行了三次,等等。但是随着递归调用数量的增长,这很快就会变得不再可能了。</P><P 0cm 0cm 0pt">代码有可能忽略某个下半部分吗?假定中断在最坏的时刻发生:在<FONT face="Times New Roman">29115</FONT>行和<FONT face="Times New Roman">29116</FONT>行之间——也就是在拷贝<B normal"><FONT face="Times New Roman">bh_active</FONT></B>之后,但是在清空其中的设置位之前。下面是三种可能的情况:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo5; tab-stops: list 42.25pt">l         <I normal">新的中断没有标记下半部分</I>。这种情况显然不会引起什么问题——中断之后处理的下半部分集合和前面的集合相同,因此<B normal"><FONT face="Times New Roman">run_bottom_halves</FONT></B>仍将运行所有它应该运行的下半部分。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo5; tab-stops: list 42.25pt">l         <I normal">新的中断标记了一个已经标记过了的下半部分</I>。这种情况也不会引起问题——<B normal"><FONT face="Times New Roman">run_bottom_halves</FONT></B>不管怎样都要运行这个下半部分,而且在新中断返回之后就运行它。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo5; tab-stops: list 42.25pt">l         <I normal">新的中断标记了一个前面没有标记过的下半部分</I>。在这种情况下,当<B normal"><FONT face="Times New Roman">run_bottom_halves</FONT></B>遍历执行所有的下半部分时,<B normal"><FONT face="Times New Roman">active</FONT></B>就不再和<B normal"><FONT face="Times New Roman">bh_active</FONT></B>匹配了。然而,由于<B normal"><FONT face="Times New Roman">clear_active_bhs</FONT></B>只会清空<B normal"><FONT face="Times New Roman">active</FONT></B>集合中设置的位,所以<FONT face="Times New Roman">29116</FONT>行不会清空<B normal"><FONT face="Times New Roman">bh_active</FONT></B>中新近标记的位。<B normal"><FONT face="Times New Roman">clear_active_bhs</FONT></B>使用<B normal"><FONT face="Times New Roman">atomic_clear_mask</FONT></B>(<FONT face="Times New Roman">10262</FONT>行),后者简单的对<B normal"><FONT face="Times New Roman">active</FONT></B>集合中的设置位进行位<FONT face="Times New Roman">AND</FONT>运算,而并不处理其余部分。因此,当<B normal"><FONT face="Times New Roman">run_bottom_halves</FONT></B>执行循环时,就不会立刻对新近标记的下半部分进行处理;但是由于它的位仍然在<B normal"><FONT face="Times New Roman">bh_active</FONT></B>中设置,<B normal"><FONT face="Times New Roman">run_bottom_halves</FONT></B>就仍然会在最后对它进行处理。更为准确的说法是,通过这种方法跳过的下半部分会在处理随后的一个定时器中断的过程中一起处理,这只是一个瞬间的延迟而已——或者如果有其它中断首先发生了,那么这段时间延迟会更短。因此,迷途的下半部分通常的等待时间不会超过百分之一秒,而且根据定义,下半部分毕竟不是时效性要求非常高的,这种少量延时不会引起任何问题。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29118</FONT>:同时遍历执行<B normal"><FONT face="Times New Roman">bh_base</FONT></B>数组和<B normal"><FONT face="Times New Roman">active</FONT></B>中的位。如果<B normal"><FONT face="Times New Roman">active</FONT></B>中最低的位被设置了,就调用相关的下半部分;接着循环推进到下一个<B normal"><FONT face="Times New Roman">bh_base</FONT></B>项和<B normal"><FONT face="Times New Roman">active</FONT></B>中的下一位继续执行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>因为这是一个<B normal"><FONT face="Times New Roman">do/while</FONT></B>循环,所以它最少执行一次。部分原因是由于在调用<B normal"><FONT face="Times New Roman">do_bottom_half</FONT></B>之前,调用者都要检测是否有下半部分需要处理,所以这个循环最少要执行一次。在一些情况下这种检测会执行到<B normal"><FONT face="Times New Roman">do_bottom_half</FONT></B>本身中,但是如果没有下半部分需要运行,在调用之前执行测试就能够节省函数调用的开销。不管怎样,我们很容易就可以看出即使没有下半部分需要运行,这个循环也可以正确执行;虽然这会浪费一些时间,但是不这样就会引起错误。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">29123</FONT>:当<B normal"><FONT face="Times New Roman">active</FONT></B>中没有任何位被设置时,循环就终止退出。由于在循环执行的过程中<B normal"><FONT face="Times New Roman">active</FONT></B>是不断移位的,这样就同时测试了其余的位,而没有必要对它们的每一个都进行循环处理。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">时间</H2><P 0cm 0cm 0pt">本节通过观察一个中断的例子——定时器中断——的工作方式来使你能够将中断和下半部分的知识融会贯通起来。</P><P 0cm 0cm 0pt">定时器中断函数<B normal"><FONT face="Times New Roman">timer_interrupt</FONT></B>是和<FONT face="Times New Roman">6086</FONT>行的<FONT face="Times New Roman">IRQ 0</FONT>相关的。此处使用的<B normal"><FONT face="Times New Roman">irq0</FONT></B>变量是在<FONT face="Times New Roman">5937</FONT>行定义的。<FONT face="Times New Roman">27972</FONT>行通过使用<B normal"><FONT face="Times New Roman">init_bh</FONT></B>(<FONT face="Times New Roman">12484</FONT>行)把<B normal"><FONT face="Times New Roman">timer_bh</FONT></B>函数注册为定时器的下半部分。</P><P 0cm 0cm 0pt">当触发<FONT face="Times New Roman">IRQ 0</FONT>时,<B normal"><FONT face="Times New Roman">timer_interrupt</FONT></B>从<FONT face="Times New Roman">CPU</FONT>时间戳计数器中读取一些属性值,如果<FONT face="Times New Roman">CPU</FONT>中有值(这在本书中没有涉及到的一些代码中使用),就调用<B normal"><FONT face="Times New Roman">do_timer_interrupt</FONT></B>(<FONT face="Times New Roman">5758</FONT>行)。除了其它一些工作之外,它会调用<B normal"><FONT face="Times New Roman">do_timer</FONT></B>,这是定时器中断非常有趣的一部分。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_timer</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27446</FONT>:从我们的出发点来看,这是我们感兴趣的定时器的上半部分。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27448</FONT>:更新全局变量<B normal"><FONT face="Times New Roman">jiffies</FONT></B>,这个值记录了机器启动以来系统时钟跳动的次数。(显然,这实际上记录的是从定时器中断装载以来已经经过的定时器跳动的次数,定时器中断在系统启动的瞬间是不会发生的。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27449</FONT>:递增丢失的定时器跳动的数目——也就是那些没有被下半部分处理的定时器的跳动。很快你就会看到有定时器的下半部分是怎样使用这个变量的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27450</FONT>:上半部分已经运行了,因此其下半部分被标记为只要可能就运行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27451</FONT>:除了要记录从上一次定时器的下半部分运行以来定时器跳动发生的次数之外,我们还要知道有多少这种跳动发生在系统模式下。很快我们会再次看到为什么下半部分需要它;但是在目前,如果进程运行在系统(内核)模式下而不是用户模式下,那么只需要递增<B normal"><FONT face="Times New Roman">lost_ticks_system</FONT></B>的值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27453</FONT>:如果定时器队列中有任务在等待,定时器队列的下半部分被标记为准备好运行(我们很快就会对定时器队列进行讨论)。而这就是整个定时器中断。虽然这看起来十分简单,但是这很大程度上是由于主要的工作都适当的延迟到下半部分处理了。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">timer_bh</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27439</FONT>:这是定时器的下半部分。它调用函数为进程和内核本身更新有关时间的统计数字,并同时对老式内核定时器和新式内核定时器进行处理。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">update_times</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27412</FONT>:这个函数主要是更新统计数字:计算系统的平均负载,更新记录当前时间的全局变量,并更新内核的当前进程使用的<FONT face="Times New Roman">CPU</FONT>时间的估计值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27422</FONT>:取得从上次下半部分运行以来发生的定时器跳动的数目,并重置计数器。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27427</FONT>:如果数字非空——正常情况下都是这样——<B normal"><FONT face="Times New Roman">update_times</FONT></B>会找出有多少这种跳动发生在系统模式下。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27429</FONT>:调用<B normal"><FONT face="Times New Roman">calc_load</FONT></B>(<FONT face="Times New Roman">27135</FONT>行,马上就介绍)更新内核有关系统负载要素的估计值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27430</FONT>:调用<B normal"><FONT face="Times New Roman">calc_wall_time</FONT></B>(<FONT face="Times New Roman">27311</FONT>行,后面会讨论)更新<B normal"><FONT face="Times New Roman">xtime</FONT></B>,它记录了当前的<FONT face="Times New Roman">wall_clock</FONT>时间。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27433</FONT>:调用<B normal"><FONT face="Times New Roman">update_process_times</FONT></B>(<FONT face="Times New Roman">27382</FONT>行),该函数同一个辅助函数<B normal"><FONT face="Times New Roman">update_one_process</FONT></B>(<FONT face="Times New Roman">27371</FONT>行)共同作用,更新内核中有关当前进程已经运行的时间长度的统计。这些统计数字可以使用诸如<FONT face="Times New Roman">time</FONT>,<FONT face="Times New Roman">top</FONT>,和<FONT face="Times New Roman">ps</FONT>之类的普通程序得到。现在你就可以看出,这些统计资料未必一定要正确:如果一个进程能够做到在所有时钟中断触发的时候都不处于运行状态,那么它就可以偷偷的使用大部分<FONT face="Times New Roman">CPU</FONT>而且还不会被认为使用了任何<FONT face="Times New Roman">CPU</FONT>资源。然而,还是比较难以理解为什么(或者怎样)恶意的进程要试图执行它,对于意图良好的进程来说,统计资料自然要进行平均——它们要为一些自己几乎不能使用的<FONT face="Times New Roman">CPU</FONT>定时器跳动负责,但是却不用为那些自己几乎在整个定时器跳动期间都在使用<FONT face="Times New Roman">CPU</FONT>的另一种情况负责,但是这些最终都不存在了。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">update_wall_time</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27313</FONT>:为每一个必须要处理的跳动调用<B normal"><FONT face="Times New Roman">update_wall_time_one_tick</FONT></B>(<FONT face="Times New Roman">27271</FONT>行)。它更新了全局变量<B normal"><FONT face="Times New Roman">xtime</FONT></B>,如果可能,就通过遵守网络时间协议(<FONT face="Times New Roman">Network Time Protocol</FONT>)努力将其和实际时间保持同步。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27318</FONT>:将<B normal"><FONT face="Times New Roman">xtime</FONT></B>标准化,使得微秒数在<FONT face="Times New Roman">0</FONT>到<FONT face="Times New Roman">999,999</FONT>的范围内。在极端的情况下可能会丢失多于一秒的定时器跳动,那么<B normal"><FONT face="Times New Roman">xtime</FONT></B>的<B normal"><FONT face="Times New Roman">tv_usec</FONT></B>部分就可能会超出<FONT face="Times New Roman">2</FONT>百万,这段代码就可能在标准化<B normal"><FONT face="Times New Roman">xtime</FONT></B>时失败。但是随后的调用可以把<B normal"><FONT face="Times New Roman">xtime</FONT></B>完全标准化。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">calc_load</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27135</FONT>:<B normal"><FONT face="Times New Roman">calc_load</FONT></B>从定时器的下半部分中调用以更新内核对于当前系统负载的估计值。虽然对于它的介绍有点离题,但是对于每个对这个随处可见的数字是如何计算的感到疑惑的人都会对这个函数很感兴趣,因此它还是值得一看的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27138</FONT>:静态变量<B normal"><FONT face="Times New Roman">count</FONT></B>记录了从上次计算平均负载以来已经经过了多少时间。它被初始化为<B normal"><FONT face="Times New Roman">LOAD_FREQ</FONT></B>,这在<FONT face="Times New Roman">16164</FONT>行中进行宏定义以代表相当于<FONT face="Times New Roman">5</FONT>秒时间的定时器间隔。对于这个函数的每一个调用都要递减遗留到下一次计算负载的定时器跳动数量。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27142</FONT>:当剩余的跳动数小于<FONT face="Times New Roman">0</FONT>时,就应该重新计算了。(我倒是希望这个数是小于或者等于<FONT face="Times New Roman">0</FONT>,而不是仅仅小于<FONT face="Times New Roman">0</FONT>,但是实际上这两个值差不了多少。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27143</FONT>:为了能在另一<FONT face="Times New Roman">5</FONT>秒内可以再次触发,重新初始化<B normal"><FONT face="Times New Roman">count</FONT></B>,此后,<B normal"><FONT face="Times New Roman">count_active_tasks</FONT></B>(<FONT face="Times New Roman">27119</FONT>行)被用来监视系统中当前有多少任务。现在<B normal"><FONT face="Times New Roman">count_active_tasks</FONT></B>的实现可能已经不是什么奇妙的事情了,但是你所有有关它的问题在阅读完第<FONT face="Times New Roman">7</FONT>章后都应该得到解答。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27144</FONT>:在<FONT face="Times New Roman">16169</FONT>行定义的<B normal"><FONT face="Times New Roman">CALC_LOAD</FONT></B>宏用来更新<B normal"><FONT face="Times New Roman">avenrun</FONT></B>数组(<FONT face="Times New Roman">27116</FONT>行)的三个项,它的三个元素分别记录了前面<FONT face="Times New Roman">5</FONT>秒、<FONT face="Times New Roman">10</FONT>秒、<FONT face="Times New Roman">15</FONT>秒的系统负载。与前一章中说明的一样,内核中尽量避免浮点数运算,因此这些计算都是在固定点进行的。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">run_old_timers</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">27077</FONT>:内核提供了类似于下半部分的已经不再使用了的技巧,通过这种技巧内核函数可以被登记到表中,和超时时间建立联系,并在指定时间到达时调用。这个函数根据需要调用其它函数。由于现在使用这个函数的唯一目的是为了支持原来的代码,所以我就不仔细地介绍它了。它简单的遍历处理<B normal"><FONT face="Times New Roman">timer_table</FONT></B>数组(<FONT face="Times New Roman">27109</FONT>行)的列表项,如果定时器已经触发就调用相关函数。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">定时器队列</H2><P 0cm 0cm 0pt">定时器队列最初的背景思想是与上半部分相关的下半部分并不一定非要与中断处理有关。相反的,它也可能是我们所需要周期性处理的任何内容。将一个函数定义为定时器中断处理程序下半部分的一部分能够保证这个函数每秒钟大约被调用<FONT face="Times New Roman">100</FONT>次。(如果每秒运行<FONT face="Times New Roman">100</FONT>次太频繁了,那么这个函数可以保持一个计数器并每<FONT face="Times New Roman">10</FONT>次调用都简单返回<FONT face="Times New Roman">9</FONT>次,例如——我们看到<B normal"><FONT face="Times New Roman">calc_load</FONT></B>就是这样处理的。)结果就像是创建了一个进程,它的<B normal"><FONT face="Times New Roman">main</FONT></B>部分在无穷循环中调用这个函数,于是它没有占用通常的和进程相关的开销。</P><P 0cm 0cm 0pt">但是,下半部分是一种有限的资源——只存在<FONT face="Times New Roman">32</FONT>个,这是因为我们希望<B normal"><FONT face="Times New Roman">bh_mask</FONT></B>和<B normal"><FONT face="Times New Roman">bh_active</FONT></B>各自都匹配一个无符号长整型数。我们可以通过使用系统中类似于实现信号量的方法来扩展下半部分的数量,但是这样仅仅能增加静态可用的下半部分的数量——它并不能使我们能够动态地扩展下半部分列表。</P><P 0cm 0cm 0pt">从本质上说,这是定时器队列所提供的内容:一个动态的可增长的下半部分的列表,所有项都和定时器中断有关。这里有一个独立的下半部分以处理这种情况——<B normal"><FONT face="Times New Roman">TQUEUE_BH</FONT></B>——如同你看到的一样,如果定时器队列中有任务,它就和<B normal"><FONT face="Times New Roman">TIMER_BH</FONT></B>一起被标记。因此,定时器中断有两个下半部分。</P><P 0cm 0cm 0pt">定时器队列实际上只是更普通的内核特性——任务队列的一个实例。根据说明文档,任务队列在内核本身中是相当自由的——请参看文件<FONT face="Times New Roman">include/linux/tqueue.h</FONT>从<FONT face="Times New Roman">18527</FONT>行开始的部分。因此,接下来我们就不再介绍它们了。但是,它们是很重要的内核服务,而且那个简短的文件也很值得一读。</P>
    回复

    使用道具 举报

    ilikenba 实名认证       

    1万

    主题

    49

    听众

    2万

    积分

  • TA的每日心情
    奋斗
    2024-6-23 05:14
  • 签到天数: 1043 天

    [LV.10]以坛为家III

    社区QQ达人 新人进步奖 优秀斑竹奖 发帖功臣

    群组万里江山

    群组sas讨论小组

    群组长盛证券理财有限公司

    群组C 语言讨论组

    群组Matlab讨论组

    <H1 12pt 0cm; TEXT-INDENT: 0cm">第<FONT face="Times New Roman">7</FONT>章<FONT face="Times New Roman">  </FONT>进程和线程</H1>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">操作系统的存在归根结底是为了提供一个运行程序的空间。按照<FONT face="Times New Roman">Unix</FONT>的术语,将正在运行的程序为进程。<FONT face="Times New Roman">Linux</FONT>内核和其它<FONT face="Times New Roman">Unix</FONT>变种一样,都是采用了多任务技术;它可以在许多进程之间分配时间片从而使这些进程看起来似乎在同时运行一样。这里通常是内核对有关资源的访问作出仲裁;在这种情况下,资源就是<FONT face="Times New Roman">CPU</FONT>时间。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">进程传统上都有唯一的执行程序的上下文——这是说明在某个时刻它正在处理一项内容的流行的方法。在给定的时刻,我们可以精确地知道代码的哪一部分正在执行。但是有时我们希望一个进程同时处理多件事情。例如,我们可能希望<FONT face="Times New Roman">Web</FONT>浏览器获取并显示<FONT face="Times New Roman">Web</FONT>页,同时也要监视用户是否点击停止按钮。只为监视停止按钮而运行一个全新的程序显然是不必要的,但是对于<FONT face="Times New Roman">Web</FONT>浏览器来说要对其时间进行分隔也并不总是非常方便——获取一些<FONT face="Times New Roman">Web</FONT>页信息,检测停止按钮,再获取一些<FONT face="Times New Roman">Web</FONT>页信息,再重新检测停止按钮,等等。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">对于这个问题的比较流行的解决方法是线程。从概念上来说,线程是同一个进程中独立的执行上下文——更简单一点地说,它们为单一进程提供了一种同时处理多件事情的方法,就像是进程是一个自行控制的微缩化了的多任务操作系统。同一线程组中的线程共享它们的全局变量并有相同的堆(<FONT face="Times New Roman">heap</FONT>),因此使用<B normal"><FONT face="Times New Roman">malloc</FONT></B>给线程组中的一个线程分配的内存可以被该线程组中的其它线程读写。但是它们拥有不同的堆栈(它们的局部变量是不共享的)并可以同时在进程代码不同的地方运行。这样,你的<FONT face="Times New Roman">Web</FONT>浏览器可以让一个线程来获取并显示<FONT face="Times New Roman">Web</FONT>页,同时另外一个线程观测停止按钮是否被点击,并且在停止按钮被点击时停止第一个线程。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">和线程等价的一种观点——这是<FONT face="Times New Roman">Linux</FONT>内核使用的观点——线程只是偶然的共享相同的全局内存空间的进程。这意味着内核无需为线程创建一种全新的机制,否则必然会和现在已经编写完成的进程处理代码造成重复,而且有关进程的讨论绝大多数也都可以应用到线程上。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">当然,以上的说明仅仅适用于内核空间的线程。实际中也有用户空间的线程,它执行相同的功能,但是却是在应用层实现的。用户空间的线程和内核空间的线程相比有很多优点,也有很多缺点,但是有关这些问题的讨论超出了本书的范围。而使人更加容易造成混淆是一个名为<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>(<FONT face="Times New Roman">2426</FONT>行)的函数,尽管该函数被赋予了这样一个名字,但是它实际和内核空间的线程没有任何关系。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">部分是由于历史的原因,部分是由于<FONT face="Times New Roman">Linux</FONT>内核并没有真正区分进程和线程这两者在概念上的不同,在内核代码中进程和线程都使用更通用的名字“任务”来引用。根据同样的思路,本书中所出现的<FONT face="Times New Roman"> </FONT>“任务”和“进程”具有相同的意义。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">调度和时间片</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">对<FONT face="Times New Roman">CPU</FONT>访问的裁决过程被称为调度(<FONT face="Times New Roman">Scheduling</FONT>)。良好的调度决策要尊重用户赋予的优先级,这可以建立一种所有进程都在同时运行的十分逼真的假象。糟糕的调度决策会使操作系统变得沉闷缓慢。这是<FONT face="Times New Roman">Linux</FONT>调度程序必须经过高度优化的一个原因。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">从概念上来说,调度程序把时间分为小片断,并根据一定的原则把这些片断分配给进程。你可能已经猜到,时间的这些小片断称为时间片。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">实时进程</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">Linux</FONT>提供了三种调度算法:一种传统的<FONT face="Times New Roman">Unix</FONT>调度程序和两个由<FONT face="Times New Roman">OSIX.1b</FONT>(原名为<FONT face="Times New Roman">OSIX.4</FONT>)操作系统标准所规定的“实时”调度程序。因此,本书中有时会使用实时进程(从技术上考虑,系统使用术语“非实时进程(<FONT face="Times New Roman">nonrealtime process</FONT>)”来作为实时进程的对应,虽然我更倾向于使用另外一个术语<FONT face="Times New Roman">unrealtime process</FONT>)。不要过分计较“实时”这个术语,虽然——如果从硬件的角度来看待这个问题,实时意味着你可以得到有关操作系统的某种性能保证,例如有关中断等待时间的承诺,但是这一点在<FONT face="Times New Roman">Linux</FONT>实时调度规则中并没有提供。相反的,<FONT face="Times New Roman">Linux</FONT>的调度规则是“软件实时”,也就是说如果实时进程需要,它们就只把<FONT face="Times New Roman">CPU</FONT>分配给实时进程;否则就把<FONT face="Times New Roman">CPU</FONT>时间让出给非实时进程。</P>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">但是如果你真正需要,一些<FONT face="Times New Roman">Linux</FONT>的变种也承诺提供一种“硬实时”。但是,在当前的<FONT face="Times New Roman">Linux</FONT>内核中——因此也就是在本章中——“实时”仅指“软件实时”。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">优先级</H2>< 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级是一些简单的整数,它代表了为决定应该允许哪一个进程使用<FONT face="Times New Roman">CPU</FONT>的资源时判断方便而赋予进程的权值——优先级越高,它得到<FONT face="Times New Roman">CPU</FONT>时间的机会也就越大:</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          静态优先级——被称为“静态”是因为它不随时间而改变,只能由用户进行修改。它指明了在被迫和其它进程竞争<FONT face="Times New Roman">CPU</FONT>之前该进程所应该被允许的时间片的最大值。(但是也可能由于其它原因,在该时间片耗尽之前进程就被迫交出了<FONT face="Times New Roman">CPU</FONT>。)</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          动态优先级——只要进程拥有<FONT face="Times New Roman">CPU</FONT>,它就随着时间不断减小;当它小于<FONT face="Times New Roman">0</FONT>时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。</P>< 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 42.5pt">l          实时优先级——指明这个进程自动把<FONT face="Times New Roman">CPU</FONT>交给哪一个其它进程:较高权值的进程总是优先于较低权值的进程。因为如果一个进程不是实时进程,其优先级就是<FONT face="Times New Roman">0</FONT>,所以实时进程总是优先于非实时进程的。(这并不完全正确;如同后面论述的一样,实时进程也会明确地交出<FONT face="Times New Roman">CPU</FONT>,而在等待<FONT face="Times New Roman">I/O</FONT>时也会被迫交出<FONT face="Times New Roman">CPU</FONT>。前面的描述仅限于能够交付<FONT face="Times New Roman">CPU</FONT>运行的进程)</P><H2 13pt 0cm; TEXT-INDENT: 0cm">进程ID(PIDs)</H2>< 0cm 0cm 0pt">传统上每个<FONT face="Times New Roman">Unix</FONT>进程都有一个唯一的标志符,它是一个被称为进程标志符(<FONT face="Times New Roman">ID</FONT>)的,范围在<FONT face="Times New Roman">0</FONT>到<FONT face="Times New Roman">32,767</FONT>之间的整数。<FONT face="Times New Roman">ID 0</FONT>和<FONT face="Times New Roman">ID 1</FONT>对于系统有特定的意义;其它的进程标识符都被认为是普通进程。在本章后面对<B normal"><FONT face="Times New Roman">get_pid</FONT></B>的讨论中,你会看到<FONT face="Times New Roman">ID</FONT>是如何生成和赋值的。</P>< 0cm 0cm 0pt">在<FONT face="Times New Roman">Linux</FONT>中,<FONT face="Times New Roman">ID</FONT>不一定非要唯一——虽然通常都是唯一的,但是两个任务也可以共享一个<FONT face="Times New Roman">ID</FONT>。这是<FONT face="Times New Roman">Linux</FONT>对线程支持的一个副作用,这些线程从概念上讲应该共享一个<FONT face="Times New Roman">ID</FONT>,因为它们是同一个进程的一部分。在<FONT face="Times New Roman">Linux</FONT>中,你可以创建两个任务,并且共享且仅共享它们的<FONT face="Times New Roman">ID</FONT>——从实际使用角度讲它们不会是线程,但是它们可以使用同一个<FONT face="Times New Roman">ID</FONT>。这并没有多大的意义,但是如果你希望这样处理,<FONT face="Times New Roman">Linux</FONT>是支持的。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">引用计数</H2>< 0cm 0cm 0pt">引用计数是多个对象之间为共享普通信息而广泛使用的技术。使用更通用的术语来说,一个或多个“容器对象”携带指向共享数据对象的指针,其中包含了一个称为“引用计数(<FONT face="Times New Roman">Reference Count</FONT>)”的整数;这个引用计数的值和共享数据的容器对象的个数相同。希望共享数据的新容器对象将被赋予一个指向同一结构的指针,并且递增该共享数据对象的引用计数。</P>< 0cm 0cm 0pt">当容器对象离开时,就递减共享数据的引用计数,并做到“人走灯熄”——也就是当引用计数减小到<FONT face="Times New Roman">0</FONT>时,容器对象回收共享对象。图<FONT face="Times New Roman">7.1</FONT>阐述了这种技术。</P>< 0cm 0cm 0pt"><wrapblock><v:shapetype><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path connecttype="rect" gradientshapeok="t" extrusionok="f"></v:path><lock aspectratio="t" v:ext="edit"></lock></v:shapetype><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-ALIGN: center; mso-outline-level: 1" align=center>图<FONT face="Times New Roman">7.1 </FONT>引用计数</P><P 0cm 0cm 0pt; TEXT-ALIGN: center; mso-outline-level: 1" align=center><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; mso-outline-level: 1">就象你随后会看到的那样,<FONT face="Times New Roman">Linux</FONT>通过使用引用计数技术来实现线程间的数据共享。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">权能</H2><P 0cm 0cm 0pt">在早期的<FONT face="Times New Roman">Unix</FONT>中,你或者是<FONT face="Times New Roman">root</FONT>用户,或者不是。如果你是<FONT face="Times New Roman">root</FONT>,你几乎可以进行任何希望进行的操作,即使你的想法实际上十分糟糕,例如删除系统引导盘上的所有文件。如果你不是<FONT face="Times New Roman">root</FONT>,那么你就不可能对系统造成太大的损害,但是你也不能执行任何重要的系统管理任务。</P><P 0cm 0cm 0pt">不幸的是,很多应用程序的需要都介于这两个安全性极端之间。例如,修改系统时间是只有<FONT face="Times New Roman">root</FONT>才能执行的操作,因此实现它的程序必须作为<FONT face="Times New Roman">root</FONT>运行。但是因为是作为<FONT face="Times New Roman">root</FONT>运行的,修改系统时间的进程也就能处理<FONT face="Times New Roman">root</FONT>可以完成的任何事情。对于编写良好的程序来说并不会造成问题,但是程序仍然会有意无意地把系统搞得一团糟。(数不清的计算机攻击事件都是欺骗<FONT face="Times New Roman">root</FONT>去运行一些看似值得信任的可执行代码,造成了一些恶作剧。)</P><P 0cm 0cm 0pt">这些问题中有一些可以通过正确使用组和诸如<FONT face="Times New Roman">sudo</FONT>之类的程序而避免,但是有一些则不行。对于某些重要的操作,虽然你可能只想允许它们执行一两种权限操作,你也只能给予这些进程普通<FONT face="Times New Roman">root</FONT>访问许可。<FONT face="Times New Roman">Linux</FONT>对于这个问题的解决方法是使用从现在已经舍弃了的<FONT face="Times New Roman">POSIX</FONT>草案标准中抽取出来的思想:权能。</P><P 0cm 0cm 0pt">权能使你可以更精确的定义经授权的进程所允许处理的事情。例如,你可以给一个进程授予修改系统时间的权力,而没有授予它可以杀掉系统中的其它进程、毁坏你的文件、并胡乱运行的权力。而且,为了帮助防止意外地滥用其优先级,长时间运行的进程可以暂时获得权能(如果允许),只要时间足够处理特殊的零碎工作就可以了,在处理完这个零碎的工作以后再收回权能。</P><P 0cm 0cm 0pt">在本书的编写期间,权能仍然处于开发状态。为了完全实现权能的预期功能,开发者们还必须要实现一些新的特性——例如,目前还没有内核支持将程序的权能附加到文件本身中。这样所造成的一个后果是<FONT face="Times New Roman">Linux</FONT>有时仍要检测进程是否作为<FONT face="Times New Roman">root</FONT>运行,而不是检测所进程需要的特殊权能。但是迄今为止已经实现了的内容仍然是十分有用的。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">进程在内核中是如何表示的</H2><P 0cm 0cm 0pt">内核使用几个数据结构来跟踪进程;其中有一些和进程自身的表示方法是密切相关的,另外一些则是独立的。图<FONT face="Times New Roman">7.2</FONT>阐述了这些数据结构,随后就会对它们进行详细介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt; TEXT-ALIGN: center" align=center><wrapblock><v:shape><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></v:shape></wrapblock><BR vglayout" clear=all>图<FONT face="Times New Roman">7.2 </FONT>管理任务使用的内核数据结构<p></p></P><P 0cm 0cm 0pt"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16325</FONT>:表示进程的内核数据结构是<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>。我们暂时向前跳过这个结构的定义,继续往下看。它相当大,但是可以从逻辑上划分为很多部分。随着本章讨论的展开,你将会逐渐清楚它们每一部分的意义。在阅读的过程中,要注意这个结构的很多部分都是指向其它结构的指针;这在子孙进程和祖先进程希望共享指针所指向的信息时可以灵活运用——很多指针都指向正在被引用计数的信息。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16350</FONT>:任务本身使用<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的<B normal"><FONT face="Times New Roman">next_task</FONT></B>和<B normal"><FONT face="Times New Roman">prev_task</FONT></B>成员组成一个循环的双向链接列表,它被称为任务队列。的确,这忽略了一个事实,它们在中心数组<B normal"><FONT face="Times New Roman">task</FONT></B>(很快就会讨论)中早已存在了。最初这看起来可能有些奇怪,但实际上这是十分有用的,因为这样允许内核代码可以遍历执行所有现存的任务——也就是<B normal"><FONT face="Times New Roman">task</FONT></B>中所有经过填充的时间片——而无须浪费时间跳过空时间片。实际上对这个循环的访问是如此频繁,以至于在<FONT face="Times New Roman">16898</FONT>行单独为它定义了一个宏<B normal"><FONT face="Times New Roman">for_each_task</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>虽然<B normal"><FONT face="Times New Roman">for_each_task</FONT></B>是单向的,但是它有一些值得注意的特性。首先,注意到循环的开始和末尾都是<B normal"><FONT face="Times New Roman">init_task</FONT></B>。这是很安全的,因为<B normal"><FONT face="Times New Roman">init_task</FONT></B>从来不会退出;因此,作为标记它一直都是可用的。但是,注意到<B normal"><FONT face="Times New Roman">ini_task</FONT></B>本身不是作为循环的一部分而访问的——这恰好就是你使用这个宏时所需要的东西。还有,作为我们关心的一小部分,你总是使用<B normal"><FONT face="Times New Roman">next_task</FONT></B>成员直接向前遍历执行列表的;不存在相关的向后执行的宏。也没有必要需要这样一个宏——只有在需要及时把任务从列表中处理清除时才需要使用<B normal"><FONT face="Times New Roman">prev_task</FONT></B>成员。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16351</FONT>:<FONT face="Times New Roman">Linux</FONT>还保持一个和这个任务列表类似的循环的双向任务列表。这个列表使用<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的<B normal"><FONT face="Times New Roman">prev_run</FONT></B>成员和<B normal"><FONT face="Times New Roman">next_tun</FONT></B>成员进行链接,基本上是作为队列来处理的(这真值得让人举杯庆祝);出于这个原因,这个列表通常被称为运行队列(<FONT face="Times New Roman">run queue</FONT>)。对于<B normal"><FONT face="Times New Roman">next_task</FONT></B>来说,只是因为需要高效地将一个项移出队列才会使用到<B normal"><FONT face="Times New Roman">prev_run</FONT></B>成员;对于这个列表的遍历循环执行通常都是使用<B normal"><FONT face="Times New Roman">next_run</FONT></B>向前的。同样,在这个任务队列中也使用<B normal"><FONT face="Times New Roman">init_task</FONT></B>来标记队列的开始和末尾。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>通过使用<B normal"><FONT face="Times New Roman">add_to_runqueue</FONT></B>(<FONT face="Times New Roman">26276</FONT>行)能够将任务加入队列,而使用<B normal"><FONT face="Times New Roman">del_from_runqueue</FONT></B>(<FONT face="Times New Roman">26287</FONT>行)则把任务移出队列。有时候分别使用<B normal"><FONT face="Times New Roman">move_first_runqueue</FONT></B>(<FONT face="Times New Roman">26318</FONT>行)和<B normal"><FONT face="Times New Roman">move_last_runqueue</FONT></B>(<FONT face="Times New Roman">26300</FONT>行)把它们强制移动到队列的开头和末尾。注意这些函数都是局限于<FONT face="Times New Roman">kernel/sched.c</FONT>的,在别的文件中不会使用<B normal"><FONT face="Times New Roman">prev_run</FONT></B>和<B normal"><FONT face="Times New Roman">next_run</FONT></B>域(特别是在<FONT face="Times New Roman">kernel/fork.c</FONT>文件中的进程创建期间);这是十分恰当的,因为只有在调度时才需要运行队列。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16370</FONT>:首先,任务能够组成一个图,该图的结构表达了任务之间的家族关系;由于我不清楚这个图所使用的通用术语,我就称它为进程图(<FONT face="Times New Roman">process graph</FONT>)。这和<FONT face="Times New Roman"><B normal">next_task</B>/<B normal">prev_task</B></FONT>之间的连接根本没有关系,在那里任务的位置是毫无意义的——只是一个偶然的历史事件而已。每一个<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>中有五个指向进程图表中自己位置的指针。这五个指针在从<FONT face="Times New Roman">16370</FONT>行到<FONT face="Times New Roman">16371</FONT>行的代码中被定义。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo3; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">p_opptr</FONT></B>指向进程的原始祖先;通常和<B normal"><FONT face="Times New Roman">p_pptr</FONT></B>类似。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo3; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">p_pptr</FONT></B>指向进程的当前祖先。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo3; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">p_cptr</FONT></B>指向进程的最年青(最近)子孙。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo3; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">p_ysptr</FONT></B>指向进程的下一个最年青(下一个最近)兄弟。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l3 level1 lfo3; tab-stops: list 57.25pt">l         <B normal"><FONT face="Times New Roman">p_osptr</FONT></B>指向进程的下一个最古老(下一个最远)兄弟。</P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt; TEXT-INDENT: 0cm"><wrapblock><v:shape><FONT face="Times New Roman"><v:imagedata></v:imagedata><w:wrap type="topAndBottom"></w:wrap></FONT></v:shape></wrapblock><BR vglayout" clear=all></P><P 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">7.3  </FONT>进程图</P><P 0cm 0cm 0pt 21pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center><p><FONT face="Times New Roman"> </FONT></p></P><P 0cm 0cm 0pt">图<FONT face="Times New Roman">7.3</FONT>说明了它们之间的关系(整个链接集合都以标号为“<FONT face="Times New Roman">Me</FONT>”的节点为核心)。这个指针的集合还提供了浏览系统中进程集合的另外一种方法;显然,在处理诸如查找进程祖先或者查找列表中进程子孙时这个指针特别有用。这个指针是由两个宏维护的:</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo4; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">REMOVE_LINKS</FONT></B>(<FONT face="Times New Roman">16876</FONT>行)从图中移出指针。</P><P 0cm 0cm 0pt 42.25pt; TEXT-INDENT: -21.25pt; mso-list: l4 level1 lfo4; tab-stops: list 42.25pt">l         <B normal"><FONT face="Times New Roman">SET_LINKS</FONT></B>(<FONT face="Times New Roman">16887</FONT>行)向图中插入指针。</P><P 0cm 0cm 0pt">这两个宏都可以调整<FONT face="Times New Roman"><B normal">next_task</B>/<B normal">prev_task</B></FONT>的链接。如果你仔细研究一下这两个宏,你就会发现它们只是增加或者删除叶子进程——而从不对拥有子孙进程的进程进行处理。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16517</FONT>:<B normal"><FONT face="Times New Roman">task</FONT></B>定义为由指向<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的指针组成的数组。这个数组中的每一个项代表系统中的一个任务。数组的大小是<B normal"><FONT face="Times New Roman">NR_TASKS</FONT></B>(在<FONT face="Times New Roman">18320</FONT>行设置为<FONT face="Times New Roman">512</FONT>),它规定了系统中可以同时运行的任务数量的上限。由于一共有<FONT face="Times New Roman">32,768</FONT>个可能的<FONT face="Times New Roman">PID</FONT>,由于数组不够大,要通过它们的<FONT face="Times New Roman">PID</FONT>直接索引系统中所有任务显然是不可能的。(也就是<B normal"><FONT face="Times New Roman">task</FONT></B>未必是由<FONT face="Times New Roman">PID <B normal">i</B></FONT>指明的任务。)相反,<FONT face="Times New Roman">Linux</FONT>使用其它的数据结构来帮助系统管理这种有限的资源。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16519</FONT>:自由时间片列表<B normal"><FONT face="Times New Roman">tarray_freelist</FONT></B>拥有一个说明<B normal"><FONT face="Times New Roman">task</FONT></B>数组中自由位置的列表(实际上是一个堆栈)。它在<FONT face="Times New Roman">27966</FONT>行和<FONT face="Times New Roman">27967</FONT>行初始化,接着被两个在<FONT face="Times New Roman">16522</FONT>行到<FONT face="Times New Roman">16542</FONT>行定义的内联函数所使用。在<FONT face="Times New Roman">SMP</FONT>平台上,对于<B normal"><FONT face="Times New Roman">tarray_freelist</FONT></B>的访问必须受自旋锁<B normal"><FONT face="Times New Roman">taskslot_lock</FONT></B>(<FONT face="Times New Roman">23475</FONT>行)的限制。(自旋锁在第<FONT face="Times New Roman">10</FONT>章中详细讨论。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16546</FONT>:<B normal"><FONT face="Times New Roman">pidhash</FONT></B>数组有助于把<FONT face="Times New Roman">PID</FONT>映象到指向<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>的指针。<B normal"><FONT face="Times New Roman">pidhash</FONT></B>在<FONT face="Times New Roman">27969</FONT>行和<FONT face="Times New Roman">27970</FONT>行初始化,此后它被一系列在<FONT face="Times New Roman">16548</FONT>行到<FONT face="Times New Roman">16580</FONT>行定义的宏和内联函数所操纵。这些最终实现了一个普通的哈希表。注意,为了处理<FONT face="Times New Roman">hush</FONT>记录,维护<B normal"><FONT face="Times New Roman">pidhash</FONT></B>的函数使用了<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构中的两个成员——<B normal"><FONT face="Times New Roman">pidhash_next</FONT></B>(<FONT face="Times New Roman">16374</FONT>行)和<B normal"><FONT face="Times New Roman">pidhash_pprev</FONT></B>(<FONT face="Times New Roman">16375</FONT>行)。通过使用<B normal"><FONT face="Times New Roman">pidhash</FONT></B>,内核可以通过其<FONT face="Times New Roman">PID</FONT>有效地发现任务——虽然这种方式仍然比直接查找要慢。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>仅仅是为了好玩,你可以自己证明这个哈希函数——<B normal"><FONT face="Times New Roman">pid_hashfn</FONT></B>,<FONT face="Times New Roman">16548</FONT>行——提供了一个均匀覆盖其域<FONT face="Times New Roman">0</FONT>到<FONT face="Times New Roman">32,767</FONT>(所有有效的<FONT face="Times New Roman">PID</FONT>)的发行版本。除非你所谓的“好玩”的概念和我不一样,否则你会同我一样感到有趣。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">            </FONT>这些数据结构提供了有关当前运行系统的很多信息,但是这也需要付出代价:每当增加或删除进程时这些信息必须能够得到正确维护,否则系统就会变得混乱不堪。部分出于实现这种正确的维护非常困难的考虑,进程只在一个地方创建(使用<B normal"><FONT face="Times New Roman">do_fork</FONT></B>,后面会讨论),也只在一个地方删除(使用<B normal"><FONT face="Times New Roman">release</FONT></B>,也在后面中讨论)。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">如果我们能把<B normal"><FONT face="Times New Roman">task</FONT></B>处理为<FONT face="Times New Roman">32,768</FONT>个<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构组成的数组,其中的每一项代表一个可能的<FONT face="Times New Roman">PID</FONT>,那么至少可以消除一部分这种类型的复杂性。但是这样处理会大量增加内核对于内存的需求。每一个<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构在<FONT face="Times New Roman">UP</FONT>平台上占用<FONT face="Times New Roman">964</FONT>字节,在<FONT face="Times New Roman">SMP</FONT>平台上占用<FONT face="Times New Roman">1,212</FONT>字节——取整以后,近似的数字是<FONT face="Times New Roman">1K</FONT>。为了容纳所有这些结构,<B normal"><FONT face="Times New Roman">task</FONT></B>会像气球一样迅速膨胀到<FONT face="Times New Roman">32,768K</FONT>,也就是<FONT face="Times New Roman">32M</FONT>!(实际情况会更糟糕:我们尚未提到的有关任务的额外内存开销会把这个数字增长<FONT face="Times New Roman">8</FONT>倍——也就是<FONT face="Times New Roman">256M</FONT>——而且不要忘记了,这些开销实际上都还没有运行一个任务。)此外,<FONT face="Times New Roman">x86</FONT>的内存管理硬件把活动任务的数量限制在<FONT face="Times New Roman">4,000</FONT>左右;这一主题在下一章介绍。因此,数组中大多数的空间都会不可避免地被浪费了。</P><P 0cm 0cm 0pt">在目前的实现中,如果没有进程在运行,<B normal"><FONT face="Times New Roman">task</FONT></B>仅仅是<FONT face="Times New Roman">512</FONT>个<FONT face="Times New Roman">4</FONT>字节的指针,总共才<FONT face="Times New Roman">2K</FONT>。如果我们考虑到那些附加的数据结构会占用一些额外开销,可能有一些超过这个数字,但是比起<FONT face="Times New Roman">32M</FONT>来还差得远呢。即使是<B normal"><FONT face="Times New Roman">task</FONT></B>中的每一项都使用了,而且每个<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构也都分配了,总共使用的内存也才不过大约<FONT face="Times New Roman">512K</FONT>。应用程序能够忽略这种微小的区别。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">进程状态</H2><P 0cm 0cm 0pt">在一个给定的时间,进程处于下面注释中描述的六种状态中的一种。进程的当前状态被记录在<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构的<B normal"><FONT face="Times New Roman">state</FONT></B>成员中(<FONT face="Times New Roman">16328</FONT>行)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16188</FONT>:<B normal"><FONT face="Times New Roman">TASK_RUNNING</FONT></B>意味着进程准备好运行了。即使是在<FONT face="Times New Roman">UP</FONT>系统中,也有不止一个任务同时处于<B normal"><FONT face="Times New Roman">TASK_RUNNING</FONT></B>状态——<B normal"><FONT face="Times New Roman">TASK_RUNNING</FONT></B>并不意味着该进程可以立即获得<FONT face="Times New Roman">CPU</FONT>(虽然有时候是这样),而是仅仅说明只要<FONT face="Times New Roman">CPU</FONT>一旦可用,进程就可以立即准备好执行了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16189</FONT>:<B normal"><FONT face="Times New Roman">TASK_INTERRUPTIBLE</FONT></B>是两种等待状态的一种——这种状态意味着进程在等待特定事件,但是也可以被信号量中断。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16190</FONT>:<B normal"><FONT face="Times New Roman">TASK_UNINTERRUPTIBLE</FONT></B>是另外一种等待状态。这种状态意味着进程在等待硬件条件而且不能被信号量中断。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16191</FONT>:<B normal"><FONT face="Times New Roman">TASK_ZOMBIE</FONT></B>意味着进程已经退出了(或者已经被杀掉了),但是其相关的<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构并没有被删除。这样即使子孙进程已经退出,也允许祖先进程对已经死去的子孙进程的状态进行查询。在本章后面我们会详细介绍这一点。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16192</FONT>:<B normal"><FONT face="Times New Roman">TASK_STOPPED</FONT></B>意味着进程已经停止运行了。一般情况下,这意味着进程已经接收到了<B normal"><FONT face="Times New Roman">SIGSTOP</FONT></B>,<B normal"><FONT face="Times New Roman">SIGSTP</FONT></B>,<B normal"><FONT face="Times New Roman">SITTIN</FONT></B>或者<B normal"><FONT face="Times New Roman">SIGTTOU</FONT></B>信号量中的一个,但是它也可能意味着当前进程正在被跟踪(例如,进程正在调试器下运行,用户正在单步执行代码)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">16193</FONT>:<B normal"><FONT face="Times New Roman">TASK_SWAPPING</FONT></B>主要用于表明进程正在执行磁盘交换工作。然而,这种状态似乎是没有什么用处的——虽然该标志符在整个内核中出现了好几次,但是其值从来没有被赋给进程的<B normal"><FONT face="Times New Roman">state</FONT></B>成员。这种状态正在被逐渐淘汰。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">进程来源:fork和_ _clone</H2><P 0cm 0cm 0pt">传统的<FONT face="Times New Roman">Unix</FONT>实现方法在系统运行以后只给出了一种创建新进程的方法:系统调用<B normal"><FONT face="Times New Roman">fork</FONT></B>。(如果你奇怪第一个进程是哪里来的,实际上该进程是<FONT face="Times New Roman">init</FONT>,在第<FONT face="Times New Roman">4</FONT>章中已经讨论过。)当进程调用<B normal"><FONT face="Times New Roman">fork</FONT></B>时,该进程从概念上被分成了两部分——这就像是道路中的分支——祖先和子孙可以自由选择不同的路径。在<B normal"><FONT face="Times New Roman">fork</FONT></B>之后,祖先进程和其子进程几乎是等同的——它们所有的变量都有相同的值,它们打开的文件都相同,等等。但是,如果祖先进程改变了一个变量的值,子进程将不会看到这个变化,反之亦然。子进程是祖先进程的一个拷贝(至少最初是这样),但是它们并不共享内容。</P><P 0cm 0cm 0pt"><FONT face="Times New Roman">Linux</FONT>保留了传统的<B normal"><FONT face="Times New Roman">fork</FONT></B>并增加了一个更通用的函数<B normal"><FONT face="Times New Roman">_ _clone</FONT></B>。(前面的两个下划线有助于强调普通应用程序代码不应该直接调用<B normal"><FONT face="Times New Roman">_ _clone</FONT></B>,应该从在<B normal"><FONT face="Times New Roman">_ _clone</FONT></B>之上建立的线程库中调用这个函数。)鉴于<B normal"><FONT face="Times New Roman">fork</FONT></B>创建一个新的子孙进程后,子孙进程虽然是其祖先进程的拷贝,但是它们并不共享任何内容,<B normal"><FONT face="Times New Roman">_ _clone</FONT></B>允许你定义祖先进程和子孙进程所应该共享的内容。如果你没有给<B normal"><FONT face="Times New Roman">_ _clone</FONT></B>提供它所能够识别的五个标志,子孙进程和祖先进程之间就不会共享任何内容,这样它就和<B normal"><FONT face="Times New Roman">fork</FONT></B>类似。如果你提供了全部的五个标志,子孙进程就可以和祖先进程共享任何内容,这就和传统线程类似。其它标记的不同组合可以使你完成介于两者之间的功能。</P><P 0cm 0cm 0pt">顺便提一下,内核使用<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>函数(<FONT face="Times New Roman">2426</FONT>行)为了自己的使用创建了几个任务。用户从来不会调用这个函数——实际上,用户也不能调用这个函数;它只在创建例如<FONT face="Times New Roman">kswapd</FONT>(在第<FONT face="Times New Roman">8</FONT>章中介绍)之类的特殊进程时才会使用,这些特殊进程有效地把内核分为很多部分,为了简单起见也把它们当作任务处理。使用<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>创建的任务具有一些特殊的性质,这些性质我们在此不再深入介绍(例如,它们不能被抢占);但是现在主要需要引起注意的是<B normal"><FONT face="Times New Roman">kernel_thread</FONT></B>使用<B normal"><FONT face="Times New Roman">do_fork</FONT></B>处理其垃圾工作。因此,即使是这些特殊进程,它们最终也要使用你我所使用的普通进程的创建方法来创建。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_fork</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23953</FONT>:<B normal"><FONT face="Times New Roman">do_fork</FONT></B>是实现<B normal"><FONT face="Times New Roman">fork</FONT></B>和<B normal"><FONT face="Times New Roman">_ _clone</FONT></B>的内核程序。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23963</FONT>:分配<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构以代表一个新的进程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23967</FONT>:给新的<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构赋予初始值,该值直接从当前进程中拷贝而来。<B normal"><FONT face="Times New Roman">do_fork</FONT></B>的剩余工作主要包含为祖先进程和子孙进程不会共享的信息建立新的拷贝。(在本行和整个内核中你可以看到的<B normal"><FONT face="Times New Roman">current</FONT></B>是一个宏,它把一个指针指向代表当前正在执行的进程的<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构。这在<FONT face="Times New Roman">10285</FONT>行定义,但实际上只是对<B normal"><FONT face="Times New Roman">get_current</FONT></B>函数的一个调用,而后者的定义在<FONT face="Times New Roman">10277</FONT>行。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23981</FONT>:新到达者需要<B normal"><FONT face="Times New Roman">task</FONT></B>数组中的一个项;这个项是使用<B normal"><FONT face="Times New Roman">find_empty_process</FONT></B>(<FONT face="Times New Roman">23598</FONT>行——它严格依赖于<FONT face="Times New Roman">16532</FONT>行的<B normal"><FONT face="Times New Roman">get_free_taskslot</FONT></B>)找到的。然而,它工作的方式有点不明显:<B normal"><FONT face="Times New Roman">task</FONT></B>数组没有使用的成员不是设置为空,而是设置为自由列表的下一个元素(使用<B normal"><FONT face="Times New Roman">add_free_taskslot</FONT></B>,<FONT face="Times New Roman">16523</FONT>行)。因此,<B normal"><FONT face="Times New Roman">task</FONT></B>中没有使用的项指向链接列表中另外一个<B normal"><FONT face="Times New Roman">task</FONT></B>没有使用的项,而<B normal"><FONT face="Times New Roman">tarray_freelist</FONT></B>仅仅指向这个列表的表头。那么,返回一个自由位置就简单地变成了返回列表头的问题了(当然要把这个头指针指向下一个元素)。更传统的方法是使用一个独立的数据结构来管理这些信息,但是在内核中,空间总会显得有些不足。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23999</FONT>:给新的任务赋<FONT face="Times New Roman">PID</FONT>(其中的细节很快就会介绍)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24045</FONT>:本行和下面几行,使用该文件中别处定义的辅助函数,根据所提供的<B normal"><FONT face="Times New Roman">clone_flags</FONT></B>参数的值为子孙进程建立祖先进程的数据结构中子孙进程所选择部分的拷贝。如果<B normal"><FONT face="Times New Roman">clone_flags</FONT></B>指明相关的部分应该共享而不是拷贝,这时辅助函数(<FONT face="Times New Roman">help function</FONT>)就简单地增加引用计数接着返回;否则,它就创建新进程所独有的新的拷贝。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24078</FONT>:到现在为止,所有进程所有的数据结构都已经设置过了,但是大部分跟踪进程的数据结构还没有被设置。系统将通过把进程增加到进程图表中开始设置它们。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24079</FONT>:通过调用<B normal"><FONT face="Times New Roman">hash_pid</FONT></B>把新的进程置入<B normal"><FONT face="Times New Roman">pidhash</FONT></B>表中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">24088</FONT>:通过调用<B normal"><FONT face="Times New Roman">wake_up_process</FONT></B>(<FONT face="Times New Roman">26356</FONT>行)把新的进程设置为<B normal"><FONT face="Times New Roman">TASK_RUNNING</FONT></B>状态并将其置入运行队列。</P><P 0cm 0cm 0pt">注意到现在不止是<B normal"><FONT face="Times New Roman">struct task_struct</FONT></B>结构被设置了,而且所有相关的数据结构——自由时间片列表,任务列表、进程图、运行队列和<FONT face="Times New Roman">PID hash</FONT>表——这些都已经为新的到达者正确地进行了修改。恭喜你,你现在已经得到了一个健康的子孙任务。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">PID的分配</H2><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">PID</FONT>是使用<B normal"><FONT face="Times New Roman">get_pid</FONT></B>函数(<FONT face="Times New Roman">23611</FONT>行)生成的,该函数能够返回一个没有使用的<FONT face="Times New Roman">PID</FONT>。它从<B normal"><FONT face="Times New Roman">last_pid</FONT></B>(<FONT face="Times New Roman">23464</FONT>行)开始——这是最近分配的<FONT face="Times New Roman">PID</FONT>。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">内核中使用的<B normal"><FONT face="Times New Roman">get_pid</FONT></B>的版本是内核复杂性和速度频繁折中的一个例子;这里速度更为重要一些。<B normal"><FONT face="Times New Roman">get_pid</FONT></B>经过了高度优化——它比直接向前的实现方法要复杂的多,但是速度也要快的多。最直接的实现方法将遍历执行整个任务列表——典型的情况可能有几十项,有时候也可能成百上千项——对每一个可能的<FONT face="Times New Roman">PID</FONT>进程检测并找出适当的值。我们见到的版本有时是必须执行这些步骤的,但是在大多数情况下都可以跳过。这一结果被用来帮助加速进程创建的操作,它在<FONT face="Times New Roman">Unix</FONT>上慢得臭名卓著。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">如果我们所需要的只是要为每一个运行进程都快速计算一个各不相同的整数,那么这里已经有现实可用的方法:只要取在<B normal"><FONT face="Times New Roman">task</FONT></B>数组中进程的索引就可以了。平均说来,这肯定要比现在的<B normal"><FONT face="Times New Roman">get_pid</FONT></B>速度要快——毕竟,这无须遍历任务列表。不幸的是,很多现存的应用程序都假定在一个<FONT face="Times New Roman">PID</FONT>可以再重用之前都需要等待一段时间。这种假定在任何情况下都是不安全的,但是在如果为了这些程序的问题而将内核牵涉进去可能仍然是一个很糟糕的思想。现存的<FONT face="Times New Roman">PID</FONT>分配策略速度仍然很快,并且它偶尔还有可以暴露这些应用程序中的潜在缺陷的优点,如果有的话(如果你认为这是一种优点)。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">get_pid</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23613</FONT>:<B normal"><FONT face="Times New Roman">next_safe</FONT></B>变量是一个为加快系统运行速度而设定的变量;它保持记录了可能保留的次最低的的候选<FONT face="Times New Roman">PID</FONT>。(更正确的应该把它命名为<B normal"><FONT face="Times New Roman">next_unsafe</FONT></B>。)当<B normal"><FONT face="Times New Roman">last_pid</FONT></B>递增并超过这个范围时,系统应该检测整个任务列表来保证这个候选<FONT face="Times New Roman">PID</FONT>是否仍在被保留着(原来保留这个<FONT face="Times New Roman">PID</FONT>的进程现在可能已经运行完了)。由于遍历这个任务列表可能会很慢,所以只要可能就应该避免执行这样的操作。因此,在执行这个遍历的过程中,<B normal"><FONT face="Times New Roman">get_pid</FONT></B>要重新计算<B normal"><FONT face="Times New Roman">next_safe</FONT></B>——如果有些进程已经死掉了,这个数字可能现在更大了,因此<B normal"><FONT face="Times New Roman">get_pid</FONT></B>可以避免一些将来对任务列表的遍历。(<B normal"><FONT face="Times New Roman">next_safe</FONT></B>是静态的,因此其值在下次<B normal"><FONT face="Times New Roman">get_pid</FONT></B>需要分配<FONT face="Times New Roman">PID</FONT>时就会保留下来。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23616</FONT>:如果新的进程要和其祖先共享<FONT face="Times New Roman">PID</FONT>,就返回祖先进程的<FONT face="Times New Roman">PID</FONT>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23620</FONT>:开始搜寻候选<FONT face="Times New Roman">PID</FONT>寻找未使用的值。位与运算只是通过测试低<FONT face="Times New Roman">15</FONT>位是否置位来简单测试<B normal"><FONT face="Times New Roman">last_pid</FONT></B>的新值是否超过了<FONT face="Times New Roman">32,767</FONT>(最大允许的<FONT face="Times New Roman">PID</FONT>)。我怀疑这些内核开发者真正需要通过这样做来获得微小的速度优势,但是你永远也不会知道;至少在这段代码编写期间,<FONT face="Times New Roman">gcc</FONT>还不够敏锐到足以注意到它们的等价性并在生成的代码中选择稍微快速的形式。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23621</FONT>:如果<B normal"><FONT face="Times New Roman">last_pid</FONT></B>已经超出了允许的最大值,它就会滚动到<FONT face="Times New Roman">300</FONT>。<FONT face="Times New Roman">300</FONT>这个数字并没有什么魔力——它对于内核并没有特别的意义——这是另外一个加速变量。其思想是数字比较小的<FONT face="Times New Roman">PID</FONT>通常都属于系统开始运行时就已经创建的,从不会退出的长时间运行的后台监控程序。由于它们总是占据着数字比较小的<FONT face="Times New Roman">PID</FONT>,所以如果不考虑对前面几百个值的重用问题,我们将会发现寻找可以使用的<FONT face="Times New Roman">PID</FONT>的过程会快许多。而且,由于<FONT face="Times New Roman">PID</FONT>的空间是同时允许的任务数(<FONT face="Times New Roman">512</FONT>)的<FONT face="Times New Roman">64</FONT>倍,为了追求速度而损失一些空间是一种非常值得的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23622</FONT>:由于<B normal"><FONT face="Times New Roman">last_pid</FONT></B>超出了最大允许的<FONT face="Times New Roman">PID</FONT>,它必然也就超出了<B normal"><FONT face="Times New Roman">next_safe</FONT></B>;因此,后面的<B normal"><FONT face="Times New Roman">if</FONT></B>测试也可以跳过。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23624</FONT>:如果<B normal"><FONT face="Times New Roman">last_pid</FONT></B>仍然小于<B normal"><FONT face="Times New Roman">next_safe</FONT></B>,其值就可以再用。否则,必须检查任务列表。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23633</FONT>:如果取得了<B normal"><FONT face="Times New Roman">last_pid</FONT></B>的当前值,它就简单的递增,如果需要就跳转到<FONT face="Times New Roman">300</FONT>,重新开始循环。初次看的时候,仿佛这个循环会一直运行下去——如果所有的<FONT face="Times New Roman">PID</FONT>都已经被使用了会出现什么情况呢?但是稍微考虑一下,我们就可以排除这种可能性:任务列表的最大值和同时并发的任务的最大数是相同的,有效的<FONT face="Times New Roman">PID</FONT>数目要比这两个数都大得多。因此,循环最终会找到有效的<FONT face="Times New Roman">PID</FONT>;这仅仅是个时间的问题。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">23651</FONT>:<B normal"><FONT face="Times New Roman">get_pid</FONT></B>已经发现了一个没有被使用的<FONT face="Times New Roman">PID</FONT>,随后返回该<FONT face="Times New Roman">PID</FONT>。</P><H2 13pt 0cm; TEXT-INDENT: 0cm">运行新程序</H2><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">如果我们能够进行的所有工作只是<B normal"><FONT face="Times New Roman">fork</FONT></B>(或者<B normal"><FONT face="Times New Roman">__clone</FONT></B>),那么我们就只能一次次建立同一个进程的拷贝就可以了——这样我们的<FONT face="Times New Roman">Linux</FONT>系统就只能运行在系统中第一个创建的用户进程<FONT face="Times New Roman">init</FONT>了。<FONT face="Times New Roman">Init</FONT>是很有用的,但是还没有功能如此强大;我们也还需要处理其它事情。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">在我们创建新的进程以后,它通过调用<B normal"><FONT face="Times New Roman">exec</FONT></B>就能够变成独立于其它进程的进程了。(这实际上不止是一个名为<B normal"><FONT face="Times New Roman">exec</FONT></B>的函数;而是<B normal"><FONT face="Times New Roman">exec</FONT></B>通常用作一个引用一系列函数的通用术语,所有这些函数基本上都处理相同的事情,但是使用的参数稍微有些不同。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">因此,创建一个“真正”的新进程——与其祖先不同的程序运行镜像——任务分为两步,一步是<B normal"><FONT face="Times New Roman">fork</FONT></B>,另一步是<B normal"><FONT face="Times New Roman">exec</FONT></B>,最后能够得出下面的风格非常熟悉的<FONT face="Times New Roman">C</FONT>代码:</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1"><FONT face="Times New Roman">P485 1<p></p></FONT></P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt; mso-outline-level: 1">(<B normal"><FONT face="Times New Roman">execl</FONT></B>是<B normal"><FONT face="Times New Roman">exec</FONT></B>家族若干函数中的一个。)</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">实现所有<B normal"><FONT face="Times New Roman">exec</FONT></B>家族函数的底层内核函数是<FONT face="Times New Roman">10079</FONT>行到<FONT face="Times New Roman">10141</FONT>行的<B normal"><FONT face="Times New Roman">do_execve</FONT></B>。<B normal"><FONT face="Times New Roman">do_execve</FONT></B>处理三种工作:</P><P 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo5; tab-stops: list 42.5pt">l          把一些定义信息从文件读入内存。(<B normal"><FONT face="Times New Roman">do_execve</FONT></B>把这个工作交给<B normal"><FONT face="Times New Roman">prepare_binprm</FONT></B>处理。)</P><P 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo5; tab-stops: list 42.5pt">l          准备新的参数和环境——这是<FONT face="Times New Roman">C</FONT>应用程序将它作为<B normal"><FONT face="Times New Roman">argc</FONT></B>,<B normal"><FONT face="Times New Roman">argv</FONT></B>和<B normal"><FONT face="Times New Roman">envp</FONT></B>使用的内容。</P><P 0cm 0cm 0pt 42.5pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo5; tab-stops: list 42.5pt">l          装载可以解析可执行文件的二进制处理程序,并让它处理剩余的修改内核数据结构的工作。</P><P 0cm 0cm 0pt; TEXT-INDENT: 21.25pt">记住这些任务,现在让我们开始仔细研究一下<B normal"><FONT face="Times New Roman">do_execve</FONT></B>。</P><H4 6pt 0cm; TEXT-INDENT: 0cm">do_execve</H4><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">10082</FONT>:代表在使用<B normal"><FONT face="Times New Roman">exec</FONT></B>处理进程时所需要记录的全部信息的数据类型是<B normal"><FONT face="Times New Roman">struct linux_binprm</FONT></B>结构(请参看<FONT face="Times New Roman">13786</FONT>行)——我确信<B normal"><FONT face="Times New Roman">binprm</FONT></B>是“<FONT face="Times New Roman">binary parameters</FONT>(二进制参数)”的缩写。<B normal"><FONT face="Times New Roman">do_execve</FONT></B>处理自己的工作,并使用这种类型的变量<B normal"><FONT face="Times New Roman">bprm</FONT></B>同那些负责处理其部分工作的函数进行通信。注意到当<B normal"><FONT face="Times New Roman">do_execve</FONT></B>返回时<B normal"><FONT face="Times New Roman">bprm</FONT></B>就会被废弃——只有在执行<B normal"><FONT face="Times New Roman">exec</FONT></B>时才需要<B normal"><FONT face="Times New Roman">bprm</FONT></B>,它并不在该进程的整个生命期中存在。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">10087</FONT>:<B normal"><FONT face="Times New Roman">do_execve</FONT></B>通过初始化一个记录新进程参数和环境分配的内存页的微型页表开始执行。它为这个目的总共需要申请<B normal"><FONT face="Times New Roman">MAX_ARG_PAGES</FONT></B>(在<FONT face="Times New Roman">13780</FONT>行宏定义为<FONT face="Times New Roman">32</FONT>)个页,在<FONT face="Times New Roman">x86</FONT>平台上每一页是<FONT face="Times New Roman">4K</FONT>,因此参数总共可以使用的空间加起来就是<FONT face="Times New Roman">32*4K=128K</FONT>。作为我个人而言,我很高兴了解到这个内容,因为我偶而会超过这个限定,通常是在一个具有成百个文件的目录下运行<FONT face="Times New Roman"><B normal">cat</B>*<B normal">&gt;/tmp/joined</B></FONT>之类的东西的时候——所有这些文件名连接起来可能就超过了<FONT face="Times New Roman">128K</FONT>。我通常是使用<FONT face="Times New Roman">xargs</FONT>程序解决这个问题,但是我现在也可以通过为<B normal"><FONT face="Times New Roman">MAX_ARG_PAGES</FONT></B>重新定义一个比较大的值并重新编译内核来解决这个问题。至少现在如果这个问题再困扰我,我也知道该如何增加这一限制了。(可能一些热心的读者会重新编写程序来去掉这段糟糕的限制。)所以我非常喜欢拥有内核的源代码。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">10091</FONT>:下一步是要打开可执行文件。这不是简单的从文件中读出数据——现在的焦点是要确保文件存在,这样<B normal"><FONT face="Times New Roman">do_execve</FONT></B>就可以清楚是否有必要继续进行处理。如果这是第一步,而不是首先填充<B normal"><FONT face="Times New Roman">bprm</FONT></B>的页表的话,<B normal"><FONT face="Times New Roman">do_execve</FONT></B>在执行时有时能够获得很高的边际效应——如果这样失败了,用来初始化页表的时间就浪费了。然而,这只在文件不存在时才有用——这不是普通的情况,不值得优化。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">10096</FONT>:继续填充<B normal"><FONT face="Times New Roman">bprm</FONT></B>,特别是其<B normal"><FONT face="Times New Roman">argc</FONT></B>和<B normal"><FONT face="Times New Roman">envc</FONT></B>成员。为了填充这些成员,<B normal"><FONT face="Times New Roman">do_execve</FONT></B>使用<B normal"><FONT face="Times New Roman">count</FONT></B>函数(<FONT face="Times New Roman">9480</FONT>行),它通过使用被传递进来的<FONT face="Times New Roman"> <B normal">argv</B></FONT>和<B normal"><FONT face="Times New Roman">envp</FONT></B>数组计算非空指针的个数。第一个空指针标志着列表结束,因此在到达空指针时就可以得到非空指针的个数并将其返回。这开始看起来似乎很可能因此而造成一些效率的损失:调用<B normal"><FONT face="Times New Roman">do_execve</FONT></B>的函数有时早就知道了<B normal"><FONT face="Times New Roman">argv</FONT></B>和<B normal"><FONT face="Times New Roman">envp</FONT></B>数组的长度。因此可以再给<B normal"><FONT face="Times New Roman">do_execve</FONT></B>增加两个整型参数<B normal"><FONT face="Times New Roman">argc</FONT></B>和<B normal"><FONT face="Times New Roman">envc</FONT></B>。如果这两个参数都是非负的,那么它们就可以分别代表两个数组的长度。但是事情并没有这么简单:<B normal"><FONT face="Times New Roman">count</FONT></B>同时要检测它扫描的数组中是否有访问内存的错误发生。强迫(更多的情况是完全信任)<B normal"><FONT face="Times New Roman">do_execve</FONT></B>的调用者来对这些内容进行检测是不正确的。所以目前这样的处理方式要更好一些。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">10115</FONT>:主要使用<B normal"><FONT face="Times New Roman">copy_strings</FONT></B>(<FONT face="Times New Roman">9519</FONT>行)把参数和环境变量拷贝到新进程中。<B normal"><FONT face="Times New Roman">copy_strings</FONT></B>看起来很复杂,但是它要处理的工作十分简单:把字符串拷贝到新进程的内存空间中,如果需要就给它们分配页。这种复杂性的增长主要出现在对页表的管理需要和跨越内核<FONT face="Times New Roman">/</FONT>用户空间限制的需要,这一点将在第<FONT face="Times New Roman">8</FONT>章中更详细地介绍。</P>
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2025-6-28 12:38 , Processed in 1.675584 second(s), 95 queries .

    回顶部