- 在线时间
- 1957 小时
- 最后登录
- 2024-6-29
- 注册时间
- 2004-4-26
- 听众数
- 49
- 收听数
- 0
- 能力
- 60 分
- 体力
- 40950 点
- 威望
- 6 点
- 阅读权限
- 255
- 积分
- 23860
- 相册
- 0
- 日志
- 0
- 记录
- 0
- 帖子
- 20501
- 主题
- 18182
- 精华
- 5
- 分享
- 0
- 好友
- 140
TA的每日心情 | 奋斗 2024-6-23 05:14 |
---|
签到天数: 1043 天 [LV.10]以坛为家III
群组: 万里江山 群组: sas讨论小组 群组: 长盛证券理财有限公司 群组: C 语言讨论组 群组: Matlab讨论组 |
<H1 17pt 0cm 16.5pt; TEXT-ALIGN: center" align=center>第<FONT face="Times New Roman">11</FONT>章<FONT face="Times New Roman"> </FONT>可调内核参数</H1>< 0cm 0cm 0pt">遵循<FONT face="Times New Roman">Unix</FONT>的<FONT face="Times New Roman">BSD 4.4</FONT>版本所倡导的风格,<FONT face="Times New Roman">Linux</FONT>提供<B normal"><FONT face="Times New Roman">sysctl</FONT></B>系统调用以便在系统运行过程中对它所拥有的某些特性进行检查和重新配置,它并不需要你编辑内核的源代码、重新编译,然后重启机器。这是对早期<FONT face="Times New Roman">Unix</FONT>版本的一个十分重要的改进,在早期版本里调整系统经常是令人头痛的琐碎事务。<FONT face="Times New Roman">Linux</FONT>把可以被检查和重新配置的系统特性有机地组织成了几个种类:常规内核参数、虚拟内存参数、网络参数,等等。</P>< 0cm 0cm 0pt">同样的特性也可以从一个不同的接口进行访问:<FONT face="Times New Roman">/proc</FONT>文件系统。(因为它真正的是系统的一个透视区(<FONT face="Times New Roman">window</FONT>)而不只是真实文件的一个容器,所以<FONT face="Times New Roman">/proc</FONT>是一个“伪的文件系统”,不过那是一个蹩脚的词汇,而且无论如何这个区别在此并不重要。)每种可调内核参数在<FONT face="Times New Roman">/proc/sys</FONT>下都表现为一个子目录,而每个单独的可调系统参数由某个子目录下的一个文件来代表。这些子目录可能又包含一级子目录,它们仍然含有更多的代表可调系统参数的文件和子目录,等等,但是这种嵌套级数从来都不会很深。<FONT face="Times New Roman"> </FONT></P>< 0cm 0cm 0pt"><FONT face="Times New Roman">/proc/sys</FONT>绕过了通常的<B normal"><FONT face="Times New Roman">sysctl</FONT></B>接口:一个可调内核参数的值可以简单的通过读取相应的文件来得到,通过写入该文件可以设置它的值。普通<FONT face="Times New Roman">Unix</FONT>文件系统的许可被应用于这些文件,以便对能够对它们进行读写的用户进行控制。大多数文件对所有用户是可读的但是只对<FONT face="Times New Roman">root</FONT>(根用户)可写,不过也有例外:比如,<FONT face="Times New Roman">/proc/sys/vm</FONT>下的文件(虚拟内存参数)只能被<FONT face="Times New Roman">root</FONT>来读写。如果不使用<FONT face="Times New Roman">/proc/sys</FONT>,检查和调整系统将需要编写程序并使用必须的参数调用<B normal"><FONT face="Times New Roman">sysctl</FONT></B>——虽然不是任务艰巨的劳动,可是也比不上使用<FONT face="Times New Roman">/proc/sys</FONT>来得方便。</P>< 12pt 0cm 3.2pt"><b>struct ctl_table</b></P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">18274</FONT>:这是本章涉及的代码中所使用的一个主要数据结构。<FONT face="Times New Roman"><B normal">struct ctl_table</B>s</FONT>通常是由数组聚合起来的,每个这样的数组对应于<FONT face="Times New Roman">/proc/sys</FONT>下某处一个单独目录里的条目。(依我之见,称它为<B normal"><FONT face="Times New Roman">struct ctl_table_entry</FONT></B>可能更好。)<B normal"><FONT face="Times New Roman">root_table</FONT></B>(<FONT face="Times New Roman">30328</FONT>行)以及在它之后的数组通过<B normal"><FONT face="Times New Roman">struct ctl_table</FONT></B>的<B normal"><FONT face="Times New Roman">child</FONT></B>指针连结节点而形成了一个数组树(<B normal"><FONT face="Times New Roman">child</FONT></B>将在下边的列表中介绍)。注意所有这些都是<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>的数组,它只是为<B normal"><FONT face="Times New Roman">struct ctl_table</FONT></B>进行<B normal"><FONT face="Times New Roman">typedef</FONT></B>;<FONT face="Times New Roman">18184</FONT>行完成这项工作。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>图<FONT face="Times New Roman">11.1</FONT>示意出了数组树间的关系。这幅图显示了由<B normal"><FONT face="Times New Roman">root_table</FONT></B>形成的树的一小部分以及它所指向的树。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> <B normal">struct ctl_table</B></FONT>具有如下成员:</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">ctl_name</FONT></B>——是唯一标识表项的一个整数——在它所在的数组中是唯一的;这个数字在不同的数组中是可以重用的。数组的任何一项都已经存在这样一个唯一的数字了——就是它的数组下标——可是这个数字不能被用于该目的,因为我们想要维护不同内核发布版本中的二进制兼容性。与某内核版本里一个数组项相关联的可调内核参数可能不会出现在将来的内核版本里,所以假如参数是被它们的数组下标定义的,对数组里废弃项目位置的重新使用将使还没有在新内核版本下编译过的程序变得混乱。随着时间的推移,为了向后兼容而带上的只浪费空间但没有作用的元素项将会使数组变得乱七八糟。相反的,这种方法只会“浪费”整数,而整数资源却无疑是非常丰富的。(另一方面,查找也会更慢,因为一个简单的数组下标还不足以满足这种方法。)<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: 0cm">要注意的是这与有系统调用的情形相当类似:每个系统调用都与一个在系统调用表里唯一标识它位置的数字相关联。但是在这种情况里使用了一个不同的解决办法,可能由于速度在此并不重要的缘故。</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><p><FONT face="Times New Roman"> </FONT></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>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">11.1 <B normal">ctl_table</B> </FONT>树的一部分</P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><p><FONT face="Times New Roman"> </FONT></p></P>< 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> <B normal">struct ctl_table</B></FONT>具有如下成员:</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">ctl_name</FONT></B>——是唯一标识表项的一个整数——在它所在的数组中是唯一的;这个数字在不同的数组中是可以重用的。数组的任何一项都已经存在这样一个唯一的数字了——就是它的数组下标——可是这个数字不能被用于该目的,因为我们想要维护不同内核发布版本中的二进制兼容性。与某内核版本里一个数组项相关联的可调内核参数可能不会出现在将来的内核版本里,所以假如参数是被它们的数组下标定义的,对数组里废弃项目位置的重新使用将使还没有在新内核版本下编译过的程序变得混乱。随着时间的推移,为了向后兼容而带上的只浪费空间但没有作用的元素项将会使数组变得乱七八糟。相反的,这种方法只会“浪费”整数,而整数资源却无疑是非常丰富的。(另一方面,查找也会更慢,因为一个简单的数组下标还不足以满足这种方法。)<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: 0cm">要注意的是这与有系统调用的情形相当类似:每个系统调用都与一个在系统调用表里唯一标识它位置的数字相关联。但是在这种情况里使用了一个不同的解决办法,可能由于速度在此并不重要的缘故。</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">procname</FONT></B>——是用于<FONT face="Times New Roman">/proc/sys</FONT>下的相应项的一个可供我们阅读的简短文件名。<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">data</FONT></B>——一个指向与此表项关联的数据的指针。它通常指向一个<B normal"><FONT face="Times New Roman">int</FONT></B>或者一个<B normal"><FONT face="Times New Roman">char</FONT></B>(当然,指向<B normal"><FONT face="Times New Roman">char</FONT></B>的指针是字符串)。<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">maxlen</FONT></B>——可以读取或者写入<B normal"><FONT face="Times New Roman">data</FONT></B>的最大字节数。如果<B normal"><FONT face="Times New Roman">data</FONT></B>指向一个单精度型的<B normal"><FONT face="Times New Roman">int</FONT></B>,举例来说,<B normal"><FONT face="Times New Roman">maxlen</FONT></B>就应该是<B normal"><FONT face="Times New Roman">sizeof</FONT></B><B normal">(<FONT face="Times New Roman">int</FONT></B><B normal">)</B>。<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">mode</FONT></B>——<FONT face="Times New Roman">Unix</FONT>类型的文件许可位,它对应于这一项的<FONT face="Times New Roman">/proc</FONT>文件(或目录)。对此的解释需要少量文件系统的内容。就像其它<FONT face="Times New Roman">Unix</FONT>的实现一样,<FONT face="Times New Roman">Linux</FONT>使用三个三元组,其中每一位都记录一个文件许可(在<B normal"><FONT face="Times New Roman">ls -l</FONT></B>命令产生的列表里它们表现为<FONT face="Times New Roman">r</FONT>、<FONT face="Times New Roman">w</FONT>,和<FONT face="Times New Roman">x</FONT>的三组字母)——参见图<FONT face="Times New Roman">11.2</FONT>。它们占据<B normal"><FONT face="Times New Roman">mode</FONT></B>的低端<FONT face="Times New Roman">9</FONT>位。文件系统把文件的<B normal"><FONT face="Times New Roman">mode</FONT></B>里剩余的位留作它用,比如用来跟踪是否文件是常规文件(第<FONT face="Times New Roman">16</FONT>位,当它如此时)、目录(第<FONT face="Times New Roman">15</FONT>位)、<FONT face="Times New Roman">setuid</FONT>或<FONT face="Times New Roman">setgid</FONT>执行程序(第<FONT face="Times New Roman">12</FONT>和<FONT face="Times New Roman">11</FONT>位),等等。不过就本章的目的来说,那些其它位都不是我们所关心的内容。<B normal"><p></p></B></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>< 0cm 0cm 0pt; TEXT-INDENT: 0cm"><B normal"><p><FONT face="Times New Roman"> </FONT></p></B></P>< 0cm 0cm 0pt; TEXT-INDENT: 0cm; TEXT-ALIGN: center" align=center>图<FONT face="Times New Roman">11.2 </FONT>文件的<B normal"><FONT face="Times New Roman">mode</FONT></B>位<B normal"><p></p></B></P>< 0cm 0cm 0pt; TEXT-INDENT: 0cm"><B normal"><p><FONT face="Times New Roman"> </FONT></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: 0cm">这种方式的结果是,读者将经常见到八进制的常数<FONT face="Times New Roman">004</FONT>、<FONT face="Times New Roman">002</FONT>,和<FONT face="Times New Roman">001</FONT>与<B normal"><FONT face="Times New Roman">mode</FONT></B>一起使用——它们分别是在移位<B normal"><FONT face="Times New Roman">mode</FONT></B>后可能得到的适当的三位组中检测读(<FONT face="Times New Roman">r</FONT>)、写(<FONT face="Times New Roman">w</FONT>),和执行(<FONT face="Times New Roman">x</FONT>)位。这种移位和检查工作基本上是在<FONT face="Times New Roman">30544</FONT>行的<B normal"><FONT face="Times New Roman">test_perm</FONT></B>里完成的。</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: 0cm">注意如果一个表项的<B normal"><FONT face="Times New Roman">maxlen</FONT></B>是<FONT face="Times New Roman">0</FONT>,那么不管它的<B normal"><FONT face="Times New Roman">mode</FONT></B>是什么,从最终效果上看它都是既不可读也不可写的。</P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">child</FONT></B>——如果这是一个目录类型的条目,那么它就是指向子表(<FONT face="Times New Roman">child table</FONT>)的一个指针。在这样的情况下,因为没有数据与此条目相关联,<B normal"><FONT face="Times New Roman">data</FONT></B>将是<B normal"><FONT face="Times New Roman">NULL</FONT></B>,而<B normal"><FONT face="Times New Roman">maxlen</FONT></B>则将是<FONT face="Times New Roman">0</FONT>。<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">proc_handler</FONT></B>——指针,指向对<B normal"><FONT face="Times New Roman">data</FONT></B>成员实际进行读取和写入操作的一个函数;它在通过<FONT face="Times New Roman">/proc</FONT>文件系统读写数据时被使用。以这种方法,任何类型的数据都可以通过<B normal"><FONT face="Times New Roman">data</FONT></B>来进行指向,而且<B normal"><FONT face="Times New Roman">proc_handler</FONT></B>函数会正确的处理对它的工作。<B normal"><FONT face="Times New Roman"> roc_handler</FONT></B>通常指向<B normal"><FONT face="Times New Roman">proc_dostring</FONT></B>函数(<FONT face="Times New Roman">30820</FONT>行)或<B normal"><FONT face="Times New Roman">proc_dointvec</FONT></B>函数(<FONT face="Times New Roman">30881</FONT>行);这两个以及其它被普遍适用的函数将在本章后面被讨论。(当然,任何具有适当原型(<FONT face="Times New Roman">prototype</FONT>)的函数都可以使用。)对于目录类型的条目,<B normal"><FONT face="Times New Roman">proc_ handler</FONT></B>是<B normal"><FONT face="Times New Roman">NULL</FONT></B>。<B normal"><p></p></B></P>< 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">strategy</FONT></B>——指针,指向对<B normal"><FONT face="Times New Roman">data</FONT></B>成员实际进行读取和写入操作的另一个函数;它使用在通过<B normal"><FONT face="Times New Roman">sysctl</FONT></B>系统调用进行读写的时候。它通常是<B normal"><FONT face="Times New Roman">sysctl_string</FONT></B>(<FONT face="Times New Roman">31121</FONT>行),不过也可以是<B normal"><FONT face="Times New Roman">stringctl_intvec</FONT></B>(<FONT face="Times New Roman">31163</FONT>行);这两个函数在本章后面进行讨论。出于种种原因,大多数可调内核参数是通过<FONT face="Times New Roman">/proc</FONT>接口而不是<B normal"><FONT face="Times New Roman">sysctl</FONT></B>系统调用进行调整的,所以这个指针是<B normal"><FONT face="Times New Roman">NULL</FONT></B>会比非空更为常见。<B normal"><p></p></B></P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">de</FONT></B>——指向<B normal"><FONT face="Times New Roman">struct proc_dir_entry</FONT></B>的一个指针,它在<FONT face="Times New Roman">/proc</FONT>文件系统代码中使用以追踪文件系统里的文件或目录。如果它非空,<B normal"><FONT face="Times New Roman">struct ctl_table</FONT></B>就在<FONT face="Times New Roman">/proc</FONT>下的某处注册过了。<B normal"><p></p></B></P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l0 level1 lfo1; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">extra</FONT></B>和<B normal"><FONT face="Times New Roman">extra2</FONT></B>——指向在处理这个表元素时所需的任何补充数据。它们当前只用于指定某些整数参数的最小和最大值。</P><H2 13pt 0cm">/proc/sys 支持</H2><P 0cm 0cm 0pt">不是所有实现用于可调内核参数<FONT face="Times New Roman">/proc/sys</FONT>接口的代码都包括在这本书中——的确,大部分代码并没有包括在内,因为它们基本上属于<FONT face="Times New Roman">/proc</FONT>文件系统本身。尽管如此,只要你不关心<FONT face="Times New Roman">/proc</FONT>剩下的部分是如何工作的,就不难理解在<FONT face="Times New Roman">kernel/sysctl.c</FONT>里的代码,它们与<FONT face="Times New Roman">/proc</FONT>文件系统一起工作用来使<FONT face="Times New Roman">/proc</FONT>下的可调内核参数是可见的。</P><P 12pt 0cm 3.2pt"><b>register_proc_table</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30689</FONT>:<B normal"><FONT face="Times New Roman">register_proc_table</FONT></B>函数在<FONT face="Times New Roman">/proc/sys</FONT>下注册一个<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>。注意这里并不要求所提供的表是根一级的节点(即<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>没有双亲)——它本应该是,不过这取决于调用者是否能够进行保证。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>这个表被直接建立在<B normal"><FONT face="Times New Roman">root</FONT></B>之下,它应该对应于<FONT face="Times New Roman">/proc/sys</FONT>或者其下的一个子目录。(在初次调用时,<B normal"><FONT face="Times New Roman">root</FONT></B>总是指向<B normal"><FONT face="Times New Roman">proc_sys_root</FONT></B>的,但是在递归调用时它的值改变了。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30696</FONT>:开始在<B normal"><FONT face="Times New Roman">table</FONT></B>数组的所有元素中进行循环;在当前元素的<B normal"><FONT face="Times New Roman">clt_name</FONT></B>成员为<FONT face="Times New Roman">0</FONT>时循环结束,表示这是数组的末尾。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30698</FONT>:如果<B normal"><FONT face="Times New Roman">ctl_table</FONT></B><B normal">的<FONT face="Times New Roman">procname</FONT></B>元素是<B normal"><FONT face="Times New Roman">NULL</FONT></B>,那么即使同一数组的其它元素都可以为用户所见,它也不可以在<FONT face="Times New Roman">/proc/sys</FONT>下被用户所见。这样的数组元素会被跳过。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30701</FONT>:如果表项有<B normal"><FONT face="Times New Roman">procname</FONT></B>,表明它应该在<FONT face="Times New Roman">/proc/sys</FONT>下被注册,那么它一定还有一个<B normal"><FONT face="Times New Roman">proc _handler</FONT></B>(如果是一个叶子,或文件类型的节点)或者一个<B normal"><FONT face="Times New Roman">child</FONT></B>(如果是一个目录类型的节点)。如果它同时缺少这两者,那么系统将显示一条警告,而后循环继续进行。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30711</FONT>:若表项有一个<B normal"><FONT face="Times New Roman">proc_handler</FONT></B>,它被标记成常规文件。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30713</FONT>:否则,正如可从第<FONT face="Times New Roman">30701</FONT>行推断的那样,它一定有一个非空的<B normal"><FONT face="Times New Roman">child</FONT></B>,这样该条目将被看作是一个目录。注意并没有禁止<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>同时拥有非空<B normal"><FONT face="Times New Roman">proc_handler</FONT></B>和<B normal"><FONT face="Times New Roman">child</FONT></B>这两者——在这种情形下,所有代码将对其一视同仁。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30715</FONT>:用给定的名字搜索一个存在的子目录,如果找到就让<B normal"><FONT face="Times New Roman">de</FONT></B>指向它,如果没找到则<B normal"><FONT face="Times New Roman">de</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">30723</FONT>:如果指定的子目录已经不存在了,或者假如<B normal"><FONT face="Times New Roman">table</FONT></B>对应于一个文件而不是一个目录,新的文件或者目录就会通过调用<B normal"><FONT face="Times New Roman">create_proc_entry</FONT></B>(未包含在本书中)来创建。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30728</FONT>:如果表项是一个叶子节点,<B normal"><FONT face="Times New Roman">register_proc_table</FONT></B>会告诉文件系统代码使用由<B normal"><FONT face="Times New Roman">proc_sys_ inode_operations</FONT></B>(<FONT face="Times New Roman">30295</FONT>行)定义的文件操作。<B normal"><FONT face="Times New Roman">proc_sys_inode_operations</FONT></B>只定义了两个操作,读和写(不是搜索、内存映射,或者其它)。这些操作是用<B normal"><FONT face="Times New Roman">proc_readsys</FONT></B>和<B normal"><FONT face="Times New Roman">proc_writesys</FONT></B>函数(<FONT face="Times New Roman">30802</FONT>和<FONT face="Times New Roman">30808</FONT>行)来执行的,在本章的后面章节中将对它们进行介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30731</FONT>:到了这一行,<B normal"><FONT face="Times New Roman">de</FONT></B>就不可能是<B normal"><FONT face="Times New Roman">NULL</FONT></B>了——它或者已经非空或者在第<FONT face="Times New Roman">30723</FONT>行被初始化了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30733</FONT>:如果增加的条目是目录类型,<B normal"><FONT face="Times New Roman">register_proc_table</FONT></B>会被递归调用来增加这一项的所有子孙。这是内核里不多见的一次递归调用。</P><P 12pt 0cm 3.2pt"><b>unregister_proc_table</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30739</FONT>:<B normal"><FONT face="Times New Roman">unregister_proc_table</FONT></B>函数删除<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>数组树和<FONT face="Times New Roman">/proc</FONT>文件系统之间的关联。<B normal"><FONT face="Times New Roman">ctl_ table</FONT></B>里的条目以及它们下面所有的“子目录”里的条目也将会从<FONT face="Times New Roman">/proc/sys</FONT>消失。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30743</FONT>:同第<FONT face="Times New Roman">30396</FONT>行一样,这一行开始在给定的表项数组上进行循环。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30744</FONT>:与<FONT face="Times New Roman">/proc/sys</FONT>下任意条目都不关联的表项具有一个为<B normal"><FONT face="Times New Roman">NULL</FONT></B>的<B normal"><FONT face="Times New Roman">de</FONT></B>成员;显然这些表项可被忽略。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30748</FONT>:如果<FONT face="Times New Roman">/proc</FONT>文件系统认为这是一个目录,但表项是一个叶子(非目录),这两个结构就是不一致的。<B normal"><FONT face="Times New Roman">unregister_proc_table</FONT></B>就会显示一条警告并继续循环,而不会移去这一项。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30752</FONT>:目录被逐层的进行释放——内核中另一次并不多见的递归过程。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30756</FONT>:在递归调用结束之后,<B normal"><FONT face="Times New Roman">unregister_proc_table</FONT></B>检查是否所有子目录和文件都被逐层删除了——如果不是,当前元素就不能被安全的移去,接着要继续循环。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30762</FONT>:这里就是为什么子目录(以及其中的文件)可能还没有被移去的原因:它们可能当前还正被使用着。如果这个元素正在被使用,循环将继续,这样该元素就不会被移走。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30765</FONT>:节点通过<B normal"><FONT face="Times New Roman">proc_unregister</FONT></B>(本书不进行介绍)从文件系统里被删除,接着用于追踪该节点而分配的内存被释放。</P><P 12pt 0cm 3.2pt"><b>do_rw_proc</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30771</FONT>:<B normal"><FONT face="Times New Roman">do_rw_proc</FONT></B>实现<B normal"><FONT face="Times New Roman">proc_readsys</FONT></B>(<FONT face="Times New Roman">30802</FONT>行)和<B normal"><FONT face="Times New Roman">proc_writesys</FONT></B>(<FONT face="Times New Roman">30806</FONT>行)函数的核心部分,这两个函数被<FONT face="Times New Roman">/proc</FONT>文件系统代码用于对<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>执行读取和写入操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30782</FONT>:确保一个表与<FONT face="Times New Roman">/proc/sys</FONT>下的这一条目相关联。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30785</FONT>:注意这一行的第一个测试与第<FONT face="Times New Roman">30782</FONT>行的第二个测试是相重复的,这是因为<B normal"><FONT face="Times New Roman">table</FONT></B>是从<B normal"><FONT face="Times New Roman">de->data</FONT></B>初始而来。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30788</FONT>:确保调用进程有适当的读或写权限。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30795</FONT>:调用该表项的<B normal"><FONT face="Times New Roman">proc_handler</FONT></B>来完成真正的读操作或写操作。(要注意第<FONT face="Times New Roman">30785</FONT>行证实了<B normal"><FONT face="Times New Roman">proc_ handler</FONT></B>成员是非空的。)如前所述,<B normal"><FONT face="Times New Roman">proc_handler</FONT></B>成员通常是<B normal"><FONT face="Times New Roman">proc_dostring</FONT></B>或<B normal"><FONT face="Times New Roman">proc_ dointvec</FONT></B>(<FONT face="Times New Roman">30820</FONT>行和<FONT face="Times New Roman">30792</FONT>行),在随后的几段中我们将对它们进行讨论。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30799</FONT>:<B normal"><FONT face="Times New Roman">do_rw_proc</FONT></B>返回实际读取或写入的字节数。注意到本地变量<B normal"><FONT face="Times New Roman">res</FONT></B>完全是多余的;它可以被参数<B normal"><FONT face="Times New Roman">count</FONT></B>所替代。</P><P 12pt 0cm 3.2pt"><b>proc_dostring</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30820</FONT>:<B normal"><FONT face="Times New Roman">proc_dostring</FONT></B>是供文件系统代码调用以对<FONT face="Times New Roman">C</FONT>语言字符串型的内核参数进行读取或写入操作的函数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>注意<B normal"><FONT face="Times New Roman">write</FONT></B>标志表示调用者正在写表元素,不过这主要是涉及从输入缓冲区里进行读取——因此,用来写入的代码是受读控制的。类似的,如果<B normal"><FONT face="Times New Roman">write</FONT></B>未被设置,调用者正从该表项读取,这里主要涉及的是写入给定的缓冲区。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>这个函数在第<FONT face="Times New Roman">31085</FONT>行还实现了一个存根程序(<FONT face="Times New Roman">stub</FONT>);这个存根程序在<FONT face="Times New Roman">/proc</FONT>文件系统被编译出内核时使用。大多数其它函数中的类似存根程序将在这个存根程序之后被介绍。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30835</FONT>:从输入缓冲区内读取字符直到一个表示结束的<FONT face="Times New Roman">ASCII NUL</FONT>(<FONT face="Times New Roman">0</FONT>)字节或者发现新的一行,再或者到达了被允许从该输入缓冲区内读出数据的最大值(被<B normal"><FONT face="Times New Roman">lenp</FONT></B>所指定)为止。(为了不引起混淆,牢记<B normal"><FONT face="Times New Roman">NULL</FONT></B>是一个<FONT face="Times New Roman">C</FONT>指针常量,而<FONT face="Times New Roman">NUL</FONT>——只有一个<FONT face="Times New Roman">L</FONT>——是<FONT face="Times New Roman">ASCII</FONT>用于字符数字<FONT face="Times New Roman">0</FONT>的术语。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30842</FONT>:如果从缓冲区读出的字符数超出了可在表项里存储的限度,该数目会被降低。在循环之前就限制最大输入长度(<B normal"><FONT face="Times New Roman">lenp</FONT></B>)可能会更高效,因为不管怎样从<B normal"><FONT face="Times New Roman">buffer</FONT></B>里读取大于<B normal"><FONT face="Times New Roman">table->maxlen</FONT></B>字节的数据是无意义的。实际上,循环可能读出,假设是<FONT face="Times New Roman">1024</FONT>字节,然后降低计数到<FONT face="Times New Roman">64</FONT>,因为表项里只能存储这么多。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30844</FONT>:该字符串从输入缓冲区里被读出,然后以<FONT face="Times New Roman">NUL</FONT>结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30847</FONT>:内核为每个进程所拥有的每个文件维护一个“当前位置”变量;这就是<B normal"><FONT face="Times New Roman">struct file</FONT></B>的<B normal"><FONT face="Times New Roman">f_pos</FONT></B>成员。它是<B normal"><FONT face="Times New Roman">tell</FONT></B>系统调用返回的值并由<B normal"><FONT face="Times New Roman">seek</FONT></B>系统调用进行设置。因此,文件的当前位置是由写入的字节数所推进的。</P><P 12pt 0cm 3.2pt"><b>proc_doutsstring</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30871</FONT>:在获得<B normal"><FONT face="Times New Roman">uts_sem</FONT></B>信号量后(<FONT face="Times New Roman">29975</FONT>行),<B normal"><FONT face="Times New Roman">proc_doutsstring</FONT></B>仅是调用<B normal"><FONT face="Times New Roman">proc_dostring</FONT></B>。这个函数被<B normal"><FONT face="Times New Roman">kern_table</FONT></B>(<FONT face="Times New Roman">30341</FONT>行)里的一些条目用来设置<B normal"><FONT face="Times New Roman">system_utsname</FONT></B>结构体的不同部分(<FONT face="Times New Roman">20094</FONT>行)。</P><P 12pt 0cm 3.2pt"><b>do_proc_dointvec</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30881</FONT>:<B normal"><FONT face="Times New Roman">proc_dointvec</FONT></B>(<FONT face="Times New Roman">30972</FONT>行)把它的工作委托给了该函数。<B normal"><FONT face="Times New Roman">do_proc_dointvec</FONT></B>读或写一个被<B normal"><FONT face="Times New Roman">table</FONT></B>的<B normal"><FONT face="Times New Roman">data</FONT></B>成员所指向的<B normal"><FONT face="Times New Roman">int</FONT></B>类型数组。要读写的<B normal"><FONT face="Times New Roman">int</FONT></B>类型数目通过<B normal"><FONT face="Times New Roman">lenp</FONT></B>传递;它通常是<FONT face="Times New Roman">1</FONT>,所以本函数通常只被用于读写单独一个<B normal"><FONT face="Times New Roman">int</FONT></B>。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>用于<B normal"><FONT face="Times New Roman">int</FONT></B>的值是被<B normal"><FONT face="Times New Roman">buffer</FONT></B>指定的。这些<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">ASCII</FONT>文本给出,而这正是用户写入相关<FONT face="Times New Roman">/proc</FONT>文件的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30898</FONT>:在所有要读写的<B normal"><FONT face="Times New Roman">int</FONT></B>中循环。<B normal"><FONT face="Times New Roman">left</FONT></B>追踪调用者想要读写<B normal"><FONT face="Times New Roman">int</FONT></B>的剩余数目,而<B normal"><FONT face="Times New Roman">vleft</FONT></B>追踪<B normal"><FONT face="Times New Roman">table->data</FONT></B>里剩余的有效元素数目。在这二者中任何一个到达<FONT face="Times New Roman">0</FONT>,或它从半途退出时,该循环结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>注意如果从循环中去掉第<FONT face="Times New Roman">30899</FONT>行的<B normal"><FONT face="Times New Roman">if</FONT></B>语句,可以使整个循环的效率稍微提高一些,尽管这样做的结果较难维护。取代的代码如下:</P><P 0cm 0cm 0pt"><FONT face="Times New Roman"> </FONT></P><P 0cm 0cm 0pt 14.75pt; TEXT-INDENT: 21.25pt"><FONT face="Times New Roman">P556—1<p></p></FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> <p></p></FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: 0cm">这种方式使得并不在循环内改变的<B normal"><FONT face="Times New Roman">write</FONT></B>的值将只需被检查一次,而不必在每次循环重复检查。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30900</FONT>:向前搜索一个不是空格的字符,它是输入(缓冲区)里下一个数字的开头。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30913</FONT>:从用户空间把一大块数据复制到本地缓冲区<B normal"><FONT face="Times New Roman">buf</FONT></B>,然后以<FONT face="Times New Roman">NUL</FONT>结束<B normal"><FONT face="Times New Roman">buf</FONT></B>。现在<B normal"><FONT face="Times New Roman">buf</FONT></B>里包含了所有输入缓冲里剩余的<FONT face="Times New Roman">ASCII</FONT>文本——或者是它所能容纳的那些文本。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>这种方法看起来不很有效率,原因在于它可能读取的超出了它所需要的。然而,因为<B normal"><FONT face="Times New Roman">buf</FONT></B>的容量仅为<FONT face="Times New Roman">20</FONT>(<B normal"><FONT face="Times New Roman">TMPBUFLEN</FONT></B>,<FONT face="Times New Roman">30885</FONT>行),它就不可能读取比它所需多出许多的数据。这里的思想可能是读入稍多一些数据要比检查每个字节以确定是否应该停止读操作所付出的代价要少些。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>计划使<B normal"><FONT face="Times New Roman">buf</FONT></B>足够大来包括任何<FONT face="Times New Roman">64</FONT>位整数的<FONT face="Times New Roman">ASCII</FONT>表示,以便这个函数不仅可以支持<FONT face="Times New Roman">32</FONT>位平台还可以支持<FONT face="Times New Roman">64</FONT>位平台。的确,它只能满足最大的正<FONT face="Times New Roman">64</FONT>位整数,它有<FONT face="Times New Roman">19</FONT>个数位(使终结的<FONT face="Times New Roman">NUL</FONT>字节是第<FONT face="Times New Roman">20</FONT>个字节)。可是要记住这些是有符号的整数,所以最小的<FONT face="Times New Roman">64</FONT>位有符号整数,即<FONT face="Times New Roman">-9,223,372,032,854,775,808</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">30919</FONT>:处理打头的减号(<FONT face="Times New Roman">-</FONT>),如果发现一个减号就跳过它并设置一个标志。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30923</FONT>:确保从<B normal"><FONT face="Times New Roman">buffer</FONT></B>读取的文本(可能是打头的减号之后的部分)至少是以一个数字开始的,这样它才能顺利的转换为一个整数。若没有这次检查,就不可能分辨出第<FONT face="Times New Roman">30925</FONT>行调用<B normal"><FONT face="Times New Roman">simple_strtoul</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">30925</FONT>:把文本转换为一个整数,用<B normal"><FONT face="Times New Roman">conv</FONT></B>参数换算结果。这个换算步骤对于<B normal"><FONT face="Times New Roman">proc_dointvec _jiffies</FONT></B>这样的函数(<FONT face="Times New Roman">31077</FONT>行)比较有用,它用乘以常数<B normal"><FONT face="Times New Roman">HZ</FONT></B>的简单手段把它的输入从秒转换为一段时间值(<FONT face="Times New Roman">jiffies</FONT>)。然而一般情况里,这个比例因子是<FONT face="Times New Roman">1</FONT>——即没有换算。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30927</FONT>:如果还要从缓冲区读取更多的文本,而且下一个要读的字符不是分割参数的(<FONT face="Times New Roman">argument-separating</FONT>)空格,那么整个参数(<FONT face="Times New Roman">argument</FONT>)就无法装进<B normal"><FONT face="Times New Roman">buf</FONT></B>。这样的输入是无效的,所以循环提早结束。(一种可以导致函数处于这种状态的方式就是前边所描述的,输入表示的是最小的有符号<FONT face="Times New Roman">64</FONT>位整数。)不过,没有错误代码会被返回,因此调用者可能会错误地认为一切正常。当然这也不完全正确:一个错误代码将在第<FONT face="Times New Roman">31070</FONT>行被返回,不过这仅当无效参数是在第一次循环重复中被检测到的时候;如果它在后续的循环里被检测到,错误就不会被注意到。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30929</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">30936</FONT>:调用者从表项里读取值——由于无需对<FONT face="Times New Roman">ASCII</FONT>文本进行语法分析,这就是一种更为简单的情形。输出是由<FONT face="Times New Roman">tab</FONT>(制表符)分隔的,所以在除了第一次之外的任何一次循环里都把一个<FONT face="Times New Roman">tab</FONT>写入临时缓冲区里(在最后一个参数之后也不用写,只需在参数之间即可)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30938</FONT>:接着,当前的整数被<B normal"><FONT face="Times New Roman">conv</FONT></B>因子按比例缩减并打印到临时缓冲区里。这段代码同样会受读者前边已经见到的问题的损害:临时缓冲区<B normal"><FONT face="Times New Roman">buf</FONT></B>的大小可能不足以容纳打印到它里边的全部整数值。在这种情况下,实际问题还会因缓冲区的第一个位置可以是一个<FONT face="Times New Roman">tab</FONT>制表符而被恶化。这会使<B normal"><FONT face="Times New Roman">buf</FONT></B>的可用部分减少一个字符,进一步还会降低可被正确处理的输入范围。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>在这里过大或过小的整数所造成的结果要比在写入情形里严重的多。在那种情形中,代码只要抛弃一些本应接受的输入即可。而在这儿,<B normal"><FONT face="Times New Roman">sprintf</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"> </FONT>然而令人惊讶的是,这正是实际工作中可能发生的。在一次典型的执行过程中将有可能发生如下执行过程:从总体上来说,超过<B normal"><FONT face="Times New Roman">buf</FONT></B>的末尾之后还要写入两个额外的字节(一个是因为它可以写入比预期更长的数字,另一个是<FONT face="Times New Roman">tab</FONT>制表符)。在栈里<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">buf</FONT></B>末尾写入的部分将会覆盖<B normal"><FONT face="Times New Roman">p</FONT></B>。可是由于<B normal"><FONT face="Times New Roman">p</FONT></B>没有先被重新初始化时它是不会再被使用的,因此暂时覆盖它的值并没有危害。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>这是一个看似有趣的事故,但是仅仅通过使<B normal"><FONT face="Times New Roman">buf</FONT></B>稍微大一些就能够成为一个更好的解决方式,这样便于代码为正确的而不是错误的前提(<FONT face="Times New Roman">reason</FONT>)而工作。依照原样,对于<FONT face="Times New Roman">gcc</FONT>的代码生成器进行完全合法的很小的修改就能够揭示出潜在的缺陷。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30939</FONT>:把当前<B normal"><FONT face="Times New Roman">int</FONT></B>的文本型表示复制进输出缓冲区里——或者和它所能容纳的相等的文本——接着更新本地变量使其转移到表项的下一个数组元素。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30949</FONT>:如果调用者刚才在读取,输出就被新的一行结束。<B normal"><FONT face="Times New Roman">if</FONT></B>条件语句也保证循环不会在其第一遍执行而且还有空间来写入新行时就结束。注意输出缓冲区不是用<FONT face="Times New Roman">ASCII NUL</FONT>字节(读者可能会这样猜测)来结束的,因为它无需如此:调用者能够利用<B normal"><FONT face="Times New Roman">lenp</FONT></B>被写入新值来减少返回字符串的长度。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30954</FONT>:如果调用者正向表项里写入数值,则略过从输入缓冲区读取的最后参数之后所有的空格。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30967</FONT>:更新文件的当前位置和<B normal"><FONT face="Times New Roman">lenp</FONT></B>,然后返回<FONT face="Times New Roman">0</FONT>表示成功。</P><P 12pt 0cm 3.2pt"><b>proc_dointvec_minmax</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30978</FONT>:<B normal"><FONT face="Times New Roman">proc_dointvec_minmax</FONT></B>函数类似于<B normal"><FONT face="Times New Roman">do_proc_dointvec</FONT></B>,区别是这个函数还把表项的<B normal"><FONT face="Times New Roman">extra1</FONT></B>和<B normal"><FONT face="Times New Roman">extra2</FONT></B>成员作为可以写入该表项的限制值数组来处理。<B normal"><FONT face="Times New Roman">extra1</FONT></B>里的值是最小限度,而<B normal"><FONT face="Times New Roman">extra2</FONT></B>里的值则是最大限度。另一点区别是<B normal"><FONT face="Times New Roman">proc_dointvec_minmax</FONT></B>不使用<B normal"><FONT face="Times New Roman">conv</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">31033</FONT>:最大的区别在于:当写入时,超过被<B normal"><FONT face="Times New Roman">min</FONT></B>和<B normal"><FONT face="Times New Roman">max</FONT></B>(在<B normal"><FONT face="Times New Roman">extra1</FONT></B>和<B normal"><FONT face="Times New Roman">extra2</FONT></B>数组上循环得到)所定义的范围之外的值将悄无声息的被略过。这段代码的目的明显是要使<B normal"><FONT face="Times New Roman">min</FONT></B>和<B normal"><FONT face="Times New Roman">max</FONT></B>伴随着<B normal"><FONT face="Times New Roman">val</FONT></B>一起继续。当一个数值从输入缓冲里被读取时,它应该被下一个<B normal"><FONT face="Times New Roman">min</FONT></B>和<B normal"><FONT face="Times New Roman">max</FONT></B>来检查,然后才能决定被接受或被忽略。可是,这并非是实际所发生的那样。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>假设从<B normal"><FONT face="Times New Roman">buffer</FONT></B>而来的当前值已经进行了语法分析并存入里<B normal"><FONT face="Times New Roman">val</FONT></B>,它小于最小值;为了更具体一些,再假设已是第三遍循环,以便<B normal"><FONT face="Times New Roman">min</FONT></B>和<B normal"><FONT face="Times New Roman">max</FONT></B>分别指向对应数组中的第三个元素。然后<B normal"><FONT face="Times New Roman">val</FONT></B>将用<B normal"><FONT face="Times New Roman">min</FONT></B>来检查并发现它超出了范围(太小),接着循环还要继续。可是<B normal"><FONT face="Times New Roman">min</FONT></B>会作为检查的副作用被更新,而<B normal"><FONT face="Times New Roman">max</FONT></B>则没有。现在,<B normal"><FONT face="Times New Roman">min</FONT></B>指向它对应数组的第四个元素了,可是<B normal"><FONT face="Times New Roman">max</FONT></B>仍然指向它的数组的第三个元素。这两者不再同步,而且它们还将保存这种状态,这样在下一个从<B normal"><FONT face="Times New Roman">buffer</FONT></B>里读取的值被检验时采用的就是错误的界限。下列代码是最简单的一种修补程序:</P><P 0cm 0cm 0pt"><FONT face="Times New Roman"> P558—1<p></p></FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>正如读者将要在本章后边看到的,现在的<FONT face="Times New Roman">Linux</FONT>源代码永远不会暴露出这个缺陷。(未来发行的版本情况将有所不同,尽管还未曾明确写出。)</P><H2 13pt 0cm">sysctl系统调用</H2><P 0cm 0cm 0pt">用于可调内核参数的另一个接口是<B normal"><FONT face="Times New Roman">sysctl</FONT></B>系统调用,以及相关函数。我不很喜欢这个接口。为什么不呢?对于大部分实际工作目的来说,使用<B normal"><FONT face="Times New Roman">sysctl</FONT></B>——不过这种方法比修改源代码的旧方法来调整内核能够获得更大的性能提高——只会比访问<FONT face="Times New Roman">/proc</FONT>文件更为笨拙。通过<B normal"><FONT face="Times New Roman">sysctl</FONT></B>来进行读写需要<FONT face="Times New Roman">C</FONT>程序(或相似的东西),而<FONT face="Times New Roman">/proc</FONT>却很容易通过外壳(<FONT face="Times New Roman">shell</FONT>)命令(或等价的通过命令解释程序脚本)来进行访问。</P><P 0cm 0cm 0pt">另一方面,如果你正在<FONT face="Times New Roman">C</FONT>环境下工作,调用<B normal"><FONT face="Times New Roman">sysctl</FONT></B>就比打开文件、读取并<FONT face="Times New Roman">/</FONT>或写入,以及再关闭它要方便的多,所以<B normal"><FONT face="Times New Roman">sysctl</FONT></B>在今后也有它的用武之地。与此同时,还是让我们来看一看它的实现吧。</P><P 12pt 0cm 3.2pt"><b>do_sysctl</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30471</FONT>:<B normal"><FONT face="Times New Roman">do_sysctl</FONT></B>实现<B normal"><FONT face="Times New Roman">sys_sysctl</FONT></B>(<FONT face="Times New Roman">30504</FONT>行),即<B normal"><FONT face="Times New Roman">sysctl</FONT></B>系统调用的主要内容。注意<B normal"><FONT face="Times New Roman">sys_sysctl</FONT></B>还在第<FONT face="Times New Roman">31275</FONT>行出现过——那个版本只是在<B normal"><FONT face="Times New Roman">sysctl</FONT></B>系统调用被编译出内核时所使用的一个简单的存根程序(<FONT face="Times New Roman">stub</FONT>)函数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>如果<B normal"><FONT face="Times New Roman">oldval</FONT></B>非空就用<B normal"><FONT face="Times New Roman">oldval</FONT></B>返回内核参数原有的值,而它的新值在<B normal"><FONT face="Times New Roman">newval</FONT></B>非空时从<B normal"><FONT face="Times New Roman">newval</FONT></B>来进行设置。<B normal"><FONT face="Times New Roman">oldlenp</FONT></B>和<B normal"><FONT face="Times New Roman">newlen</FONT></B>分别标识出有多少字节应被写入<B normal"><FONT face="Times New Roman">oldval</FONT></B>和从<B normal"><FONT face="Times New Roman">newval</FONT></B>读出,这是在相应的指针不是<B normal"><FONT face="Times New Roman">NULL</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"> </FONT>要注意这里的不对称性:函数对旧值的长度使用指针,而对新的长度不使用指针。这是因为旧的长度既是输入参数也是输出参数;它的输入值是可以通过<B normal"><FONT face="Times New Roman">oldval</FONT></B>返回的最大字节数,而它的输出值是实际返回的字节数。与之相反,新的长度只是一个输入参数。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30482</FONT>:如果调用者需要旧的内核参数值,从<B normal"><FONT face="Times New Roman">oldlenp</FONT></B>来对<B normal"><FONT face="Times New Roman">old_len</FONT></B>进行设置。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30490</FONT>:开始遍历表树的循环列表。(参见本章随后对<B normal"><FONT face="Times New Roman">register_sysctl_table</FONT></B>的讨论。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30493</FONT>:使用<B normal"><FONT face="Times New Roman">parse_table</FONT></B>(<FONT face="Times New Roman">30560</FONT>行,在下一段里讨论)来定位可调内核参数,然后读和<FONT face="Times New Roman">/</FONT>或写它的值。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30495</FONT>:如果<B normal"><FONT face="Times New Roman">parse_table</FONT></B>分配了所有环境信息,它就被释放。很难准确地说出这个环境信息表示着什么。它不被本书所讨论的任何代码使用——据我所知,它目前甚至没有被内核里的任何代码所使用。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30497</FONT>:<B normal"><FONT face="Times New Roman">ENOTDIR</FONT></B>错误表示没有在这一棵表树中找到指定的内核参数——它可能在另一棵还没有查找过的表树中。否则,<B normal"><FONT face="Times New Roman">error</FONT></B>将为某个其它的错误代码,或者是代表成功的<FONT face="Times New Roman">0</FONT>;无论如何,函数应该返回了。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30499</FONT>:用<B normal"><FONT face="Times New Roman">DLIST_NEXT</FONT></B>宏(本书对此不做介绍)来增加循环控制变量的值(<FONT face="Times New Roman">loop iterator</FONT>)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30501</FONT>:返回<B normal"><FONT face="Times New Roman">ENOTDIR</FONT></B>错误,报告出指定的内核参数在任何一个表里都未找到。</P><P 12pt 0cm 3.2pt"><b>parse_table</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30560</FONT>:<B normal"><FONT face="Times New Roman">parse_table</FONT></B>用于在表树里查找一个条目,其方法类同于在一个目录树里解析出一个完全合格的文件名的方法。其思想如下:沿着一个<B normal"><FONT face="Times New Roman">int</FONT></B>数组(数组<B normal"><FONT face="Times New Roman">name</FONT></B>)进行查找,并在一个<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>数组里搜索每个<B normal"><FONT face="Times New Roman">int</FONT></B>。当找到一个匹配项时,它对应的子孙表就被递归查阅(如果匹配项是目录类型的条目),或者该条目被读和<FONT face="Times New Roman">/</FONT>或写(如果它是文件类型的条目)。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30566</FONT>:多少有些令人惊讶的是,这一行就开始了对整型数组<B normal"><FONT face="Times New Roman">name</FONT></B>内所有元素的循环。习惯上的方法原本是把从这一行到第<FONT face="Times New Roman">30597</FONT>行所有代码用一个<B normal"><FONT face="Times New Roman">for</FONT></B>循环包括起来,它的开始是这样的:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> for ( ; nlen ; ++name , --nlen , table = table -> child )</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>(这个循环还需要删除第<FONT face="Times New Roman">30567</FONT>和<FONT face="Times New Roman">30568</FONT>行代码,并用一个语句来替代从<FONT face="Times New Roman">30587</FONT>直到<FONT face="Times New Roman">30590</FONT>行的代码。)推测起来,可能是实际使用的版本可以生成更好的目标代码吧。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30570</FONT>:开始循环所有的表项,查找与当前<B normal"><FONT face="Times New Roman">name</FONT></B>匹配的一项;当表已被遍历结束(<B normal"><FONT face="Times New Roman">table->ctl_ name</FONT></B>为<FONT face="Times New Roman">0</FONT>了)或者指定的表项已被找到并处理时本循环结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30572</FONT>:把<B normal"><FONT face="Times New Roman">name</FONT></B>数组的当前项读入<B normal"><FONT face="Times New Roman">n</FONT></B>里,以便它可以与当前表项的<B normal"><FONT face="Times New Roman">ctl_name</FONT></B>进行检查。因为<B normal"><FONT face="Times New Roman">name</FONT></B>在内层循环中没有变化,这个读取操作可以放在循环外边(也就是移至<FONT face="Times New Roman">30569</FONT>行)以提高一点速度。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30574</FONT>:核查是否当前<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>的名字与被找到的名字相匹配,或是否它有特殊的“通配符(<FONT face="Times New Roman">wildcard</FONT>)”值,即<B normal"><FONT face="Times New Roman">CTL_ANY</FONT></B>(<FONT face="Times New Roman">17761</FONT>行)。后者的使用目的还不清楚,因为现在并没有内核源代码的任何一处使用过<B normal"><FONT face="Times New Roman">CTL_ANY</FONT></B>。它可能用于将来的方案中——我也不认为它是过去版本的一个遗留物,因为<B normal"><FONT face="Times New Roman">CTL_ANY</FONT></B>在<FONT face="Times New Roman">2.0</FONT>内核里也没有被用到,而且整个<B normal"><FONT face="Times New Roman">sysctl</FONT></B>接口也只向后兼容到<FONT face="Times New Roman">2.0</FONT>以前的开发树版本。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30576</FONT>:如果这个表元素有一个孩子,它就是一个“目录”。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30577</FONT>:遵循标准<FONT face="Times New Roman">Unix</FONT>行为,检查目录的<FONT face="Times New Roman">x</FONT>(可执行)位来判断是否当前进程可被允许对它进行访问。注意到这与文件系统所实现的工作非常类似,虽然这并不是(<FONT face="Times New Roman">/proc</FONT>)文件系统接口。这样可以使这两种接口在施用于可调内核参数时能够得到一致的结果——如果一个用户有通过一种接口来修改某个内核参数的权限而通过另一种却没有该权限,那么将是非常不可思议的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30579</FONT>:如果表项有一个策略(<FONT face="Times New Roman">strategy</FONT>)函数,它可能需要覆盖允许该进程进入目录的授权。这个策略函数将被访问,如果它返回一个非零值,整个查找就被中止。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30587</FONT>:进入目录。本行有效的继续外层循环,并转移到该名字的下一部分。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30592</FONT>:这个表节点是一个叶子节点,因此内核参数就被找到了。注意这并不打扰对<B normal"><FONT face="Times New Roman">name</FONT></B>数组是否已到其最后元素的检查(也就是现在<B normal"><FONT face="Times New Roman">nlen</FONT></B>是否为<FONT face="Times New Roman">1</FONT>),虽然可以证明如果不是那样就会有某类型错误产生。不管哪一种情况,<B normal"><FONT face="Times New Roman">do_sysctl_strategy</FONT></B>(<FONT face="Times New Roman">30603</FONT>行)都要负责对当前表元素进行读和<FONT face="Times New Roman">/</FONT>或写操作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30598</FONT>:<B normal"><FONT face="Times New Roman">name</FONT></B>数组非空,可是它的元素在叶子节点被找到之前均已用完。<B normal"><FONT face="Times New Roman">parse_table</FONT></B>就返回<B normal"><FONT face="Times New Roman">ENOTDIR</FONT></B>错误,来表示查找指定节点失败。顺便提及一点,注意前一行里的分号是多余的。</P><P 12pt 0cm 3.2pt"><b>do_sysctl_strategy</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30603</FONT>:<B normal"><FONT face="Times New Roman">do_sysctl_strategy</FONT></B>在单独一个<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>里读和<FONT face="Times New Roman">/</FONT>或写数据。计划使用该表元素里的<B normal"><FONT face="Times New Roman">strategy</FONT></B>成员,如果存在的话,来完成读<FONT face="Times New Roman">/</FONT>写工作。如果表元素没有它自己的<B normal"><FONT face="Times New Roman">strategy</FONT></B>例程,某些通用的读<FONT face="Times New Roman">/</FONT>写代码将被替代使用。不过读者将要看到,它并不完全按照计划工作。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30610</FONT>:如果<B normal"><FONT face="Times New Roman">oldval</FONT></B>非空,调用者将读取旧值,这样<FONT face="Times New Roman">r</FONT>位就会在<B normal"><FONT face="Times New Roman">op</FONT></B>里被设置。类似的,如果<B normal"><FONT face="Times New Roman">newval</FONT></B>非空则<FONT face="Times New Roman">w</FONT>位被设置。接着,第<FONT face="Times New Roman">30614</FONT>行核查许可,如果当前进程缺少所需的授权就返回<B normal"><FONT face="Times New Roman">EPERM</FONT></B>错误。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30617</FONT>:如果表项有它自己的<B normal"><FONT face="Times New Roman">strategy</FONT></B>例程,这个例程就要处理读<FONT face="Times New Roman">/</FONT>写请求。如果它返回负数——一个错误——这个错误就被传送给调用者。如果返回的是正数,<FONT face="Times New Roman">0</FONT>(成功)就会被传送给调用者。如果是<FONT face="Times New Roman">0</FONT>,<B normal"><FONT face="Times New Roman">strategy</FONT></B>例程就拒绝由它自己来处理请求,取而代之的将是缺省行为。(读者可以设想只返回<FONT face="Times New Roman">0</FONT>的<B normal"><FONT face="Times New Roman">strategy</FONT></B>例程,如果它完成一些其它诸如收集被调用次数的统计数据这样的工作,它仍然是有用处的。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30630</FONT>:这里是通用读取代码开始的地方。注意<B normal"><FONT face="Times New Roman">get_user</FONT></B>(<FONT face="Times New Roman">13254</FONT>行)的返回值不被检查。(类似的缺陷发生在第<FONT face="Times New Roman">9537</FONT>和<FONT face="Times New Roman">31186</FONT>行。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30632</FONT>:确保不会有多于与该表项的<B normal"><FONT face="Times New Roman">maxlen</FONT></B>成员所指定的数值相等的数据被返回。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30634</FONT>:通过<B normal"><FONT face="Times New Roman">oldval</FONT></B>从表里复制所要求的数据,再将真正被写的数据总量存储在<B normal"><FONT face="Times New Roman">oldlenp</FONT></B>中。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30642</FONT>:类似于<B normal"><FONT face="Times New Roman">oldlenp</FONT></B>,要确保写入表项的数据不能多于它的<B normal"><FONT face="Times New Roman">maxlen</FONT></B>成员所允许的值。注意如果<B normal"><FONT face="Times New Roman">copy_from_user</FONT></B>在中途的第<FONT face="Times New Roman">30644</FONT>行检测到一个错误,<B normal"><FONT face="Times New Roman">tabel->data</FONT></B>可能会在仅仅被部分更新的情况下就结束。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30648</FONT>:返回<FONT face="Times New Roman">0</FONT>表示成功。以下三种情况都可以达到这一点:</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 57.25pt">l 调用者对这个表项既不读也不写。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l2 level1 lfo2; tab-stops: list 57.25pt">l 调用者尝试读和<FONT face="Times New Roman">/</FONT>或写这个表项,而且所有步骤都被成功执行。</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">maxlen</FONT></B>是<FONT face="Times New Roman">0</FONT>,所以它是只读的。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: 0cm">三种情形中的第一种有点儿奇怪,而最后一种则更令人奇怪。第一种情况有些奇特是因为调用<B normal"><FONT face="Times New Roman">sysctl</FONT></B>却要求它对指定的表项既不读也不写,这并没有多少意义,所以可以正当的把它当作一个错误来处理。尽管如此,它要与其它系统调用的内核实现保持基本一致,那就是把一个无操作请求不看作是一个错误。比如说,在第<FONT face="Times New Roman">8</FONT>章中介绍的<B normal"><FONT face="Times New Roman">sys_brk</FONT></B>(<FONT face="Times New Roman">33155</FONT>行)在由调用者指定的新<B normal"><FONT face="Times New Roman">brk</FONT></B>值与旧值相同时并不产生一个错误信号。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: 0cm">第三种情况要比第一种奇怪一些,因为它可能真的反映着一个错误。例如,调用代码尝试写入一个<B normal"><FONT face="Times New Roman">maxlen</FONT></B>是<FONT face="Times New Roman">0</FONT>的参数,而且由于系统调用返回成功值而认为该尝试已被完成。看起来事情好像不是这样,因为不管怎样为<FONT face="Times New Roman">0</FONT>的<B normal"><FONT face="Times New Roman">maxlen</FONT></B>都会使该条目失效,不过还真的存在一个<B normal"><FONT face="Times New Roman">maxlen</FONT></B>为<FONT face="Times New Roman">0</FONT>的表项——参见第<FONT face="Times New Roman">30380</FONT>行。最终,一切都归结为<B normal"><FONT face="Times New Roman">sysctl</FONT></B>是怎样在文档中描述的,但是<FONT face="Times New Roman">man</FONT>帮助程序中却对此没有任何记载。我仍然认为<B normal"><FONT face="Times New Roman">do_sysctl_strategy</FONT></B>在这种情况下应该返回一个<B normal"><FONT face="Times New Roman">EPERM</FONT></B>错误。</P><P 12pt 0cm 3.2pt"><b>register_sysctl_table</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30651</FONT>:把一个新的根已经被给出的<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>树插入到其它树所形成的循环链表里。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30655</FONT>:分配一个<B normal"><FONT face="Times New Roman">struct ctl_table_header</FONT></B>用来管理新树的信息。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30659</FONT>:把新的首部(跟踪<FONT face="Times New Roman"><B normal">ctl_table</B>s</FONT>数组形成的新树)插入到首部组成的链表里。<FONT face="Times New Roman"> </FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30666</FONT>:调用<B normal"><FONT face="Times New Roman">register_proc_table</FONT></B>(<FONT face="Times New Roman">30689</FONT>行,本章前边讨论过)把新的表树注册在<FONT face="Times New Roman">/proc/sys</FONT>目录下。如果没有内核在没有<FONT face="Times New Roman">/proc</FONT>文件系统支持的情况下进行编译时,则这一行将被编译到内核以外。<p></p></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30688</FONT>:新分配的首部被返回给调用程序,以便调用程序能够在以后通过把该首部转递给<B normal"><FONT face="Times New Roman">unregister_sysctl_table</FONT></B>(<FONT face="Times New Roman">30672</FONT>行)来删除相应的树。</P><P 12pt 0cm 3.2pt"><b>unregister_sysctl_table</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">30672</FONT>:如前所述,这个简单函数只是把一个<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>的树从内核里这样的树所组成的循环链表里删除。如果内核是在支持<FONT face="Times New Roman">/proc</FONT>的情况下编译的,它也用于从<FONT face="Times New Roman">/proc</FONT>文件系统里删除相应的数据。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>回顾一下第<FONT face="Times New Roman">30490</FONT>和<FONT face="Times New Roman">30500</FONT>行,读者不难发现<B normal"><FONT face="Times New Roman">root_table_header</FONT></B>(<FONT face="Times New Roman">30256</FONT>行)——对应于<B normal"><FONT face="Times New Roman">root_table</FONT></B>的列表节点——是在遍历树的循环链表时被用作头和尾节点的。读者现在能够明白实际上在<B normal"><FONT face="Times New Roman">unregister_sysctl_table</FONT></B>函数里没有什么可以避免<B normal"><FONT face="Times New Roman">root_table_header</FONT></B>被从表头列表里删除——它只是还没有这样做而已。</P><P 12pt 0cm 3.2pt"><b>sysctl_string</b></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31121</FONT>:<B normal"><FONT face="Times New Roman">sysctl_string</FONT></B>是<B normal"><FONT face="Times New Roman">ctl_table</FONT></B>的策略例程之一。回忆一下,策略例程可以从第<FONT face="Times New Roman">30618</FONT>行(在<B normal"><FONT face="Times New Roman">do_sysctl_strategy</FONT></B>里)被调用来有选择的覆盖一个表项的缺省读<FONT face="Times New Roman">/</FONT>写代码。(策略例程也可以从第<FONT face="Times New Roman">30580</FONT>行被调用,不过该例程却从不会被调用。)</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31127</FONT>:如果该表没有相关数据,或者如果可访问部分的长度是<FONT face="Times New Roman">0</FONT>,则返回<B normal"><FONT face="Times New Roman">ENOTDIR</FONT></B>错误。这与<B normal"><FONT face="Times New Roman">do_sysctl_strategy</FONT></B>的做法是不一致的,在同样的情况里它返回的是成功。</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman">31138</FONT>:当前字符串的值被复制到用户空间,然后结果以<FONT face="Times New Roman">NUL</FONT>来结束(这意味着比由<B normal"><FONT face="Times New Roman">lenp</FONT></B>指定值多一个字节的数据可能会被复制——依据文档记录,这可能是一个缺陷)。因为当前值已经是<FONT face="Times New Roman">NUL</FONT>结束的,这四行代码可以被简化为两行:</P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> if ( copy_to_user ( oldval , table -> data , len + 1 ) )</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> return –EFAULT ;</FONT></P><P 0cm 0cm 0pt 36pt; TEXT-INDENT: -36pt"><FONT face="Times New Roman"> </FONT>这种改变的正确性部分上依赖于当写入<B normal"><FONT face="Times New Roman">table->data</FONT></B>时代码剩余部分所遵循的三个特征:</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 57.25pt">l 代码剩余部分不能把多于<B normal"><FONT face="Times New Roman">table->maxlen</FONT></B>的<B normal"><FONT face="Times New Roman">char</FONT></B>数据复制进<B normal"><FONT face="Times New Roman">table->data</FONT></B>里。(这也使得第<FONT face="Times New Roman">31136</FONT>行的测试变得没有必要。即使还需要该测试,那也只用检查<FONT face="Times New Roman">></FONT>,而不用检查<FONT face="Times New Roman">>=</FONT>了。)</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 57.25pt">l 然后<B normal"><FONT face="Times New Roman">table->data</FONT></B>以<FONT face="Times New Roman">NUL</FONT>来结束,如果必要就复写最后一个拷贝进来的字节,以便包括<FONT face="Times New Roman">NUL</FONT>在内的总长度不大于<B normal"><FONT face="Times New Roman">table->maxlen</FONT></B>。</P><P 0cm 0cm 0pt 57.25pt; TEXT-INDENT: -21.25pt; mso-list: l1 level1 lfo3; tab-stops: list 57.25pt">l <B normal"><FONT face="Times New Roman">table->maxlen</FONT></B>永不发生变化。</P><B normal"> </B>因为所有三个特征都有效,所以在第31138行<B normal">len</B>将总是严格小于<B normal">table->maxlen</B>,而且结束NUL字节一定会在<B normal">table->data[len+1]</B>或之前的位置出现。 |
|