diff options
author | Xin LI <delphij@FreeBSD.org> | 2013-11-14 00:20:51 +0000 |
---|---|---|
committer | Xin LI <delphij@FreeBSD.org> | 2013-11-14 00:20:51 +0000 |
commit | 797a990cf709e43fdcfa0f1b0b8b8e0127076002 (patch) | |
tree | 38ebab2823385023d1fa54018fceb14262c4cf93 /zh_CN.UTF-8 | |
parent | 8ff93b9827e6bfbbae625ff0d45eb2ce00ebe029 (diff) | |
download | doc-797a990cf709e43fdcfa0f1b0b8b8e0127076002.tar.gz doc-797a990cf709e43fdcfa0f1b0b8b8e0127076002.zip |
Convert zh_CN from GB2312 to UTF-8. While I'm there, also
fix a bunch of font issues when rendering PDF.
Notes
Notes:
svn path=/head/; revision=43188
Diffstat (limited to 'zh_CN.UTF-8')
342 files changed, 319568 insertions, 0 deletions
diff --git a/zh_CN.UTF-8/Makefile b/zh_CN.UTF-8/Makefile new file mode 100644 index 0000000000..bc47a5ed74 --- /dev/null +++ b/zh_CN.UTF-8/Makefile @@ -0,0 +1,10 @@ +# Original Revision: 1.7 +# $FreeBSD$ + +SUBDIR = articles +SUBDIR+= books + +COMPAT_SYMLINK= zh_CN + +DOC_PREFIX?= ${.CURDIR}/.. +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/Makefile.inc b/zh_CN.UTF-8/Makefile.inc new file mode 100644 index 0000000000..453700a49b --- /dev/null +++ b/zh_CN.UTF-8/Makefile.inc @@ -0,0 +1,3 @@ +# $FreeBSD$ + +DOC_PREFIX?= ${.CURDIR}/../.. diff --git a/zh_CN.UTF-8/articles/Makefile b/zh_CN.UTF-8/articles/Makefile new file mode 100644 index 0000000000..a0821cb7e0 --- /dev/null +++ b/zh_CN.UTF-8/articles/Makefile @@ -0,0 +1,16 @@ +# +# The FreeBSD Simplified Chinese Project +# Original Revision: 1.52 +# +# $FreeBSD$ + +SUBDIR = +SUBDIR+= contributing +SUBDIR+= cvs-freebsd +SUBDIR+= linux-users +SUBDIR+= nanobsd +SUBDIR+= rc-scripting +SUBDIR+= remote-install + +DOC_PREFIX?= ${.CURDIR}/../.. +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/Makefile.inc b/zh_CN.UTF-8/articles/Makefile.inc new file mode 100644 index 0000000000..6f1cac5e29 --- /dev/null +++ b/zh_CN.UTF-8/articles/Makefile.inc @@ -0,0 +1,8 @@ +# +# The FreeBSD Simplified Chinese Project +# Original Revision: 1.4 +# +# $FreeBSD$ +# + +DESTDIR?= ${DOCDIR}/zh_CN.UTF-8/articles/${.CURDIR:T} diff --git a/zh_CN.UTF-8/articles/contributing/Makefile b/zh_CN.UTF-8/articles/contributing/Makefile new file mode 100644 index 0000000000..917c12fe11 --- /dev/null +++ b/zh_CN.UTF-8/articles/contributing/Makefile @@ -0,0 +1,22 @@ +# +# The FreeBSD Simplified Chinese Project +# +# Original Revision: 1.6 +# $FreeBSD$ +# +# Article: Contributing to FreeBSD + +DOC?= article + +FORMATS?= html +WITH_ARTICLE_TOC?= YES + +INSTALL_COMPRESSED?=gz +INSTALL_ONLY_COMPRESSED?= + +SRCS= article.xml + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. + +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/contributing/article.xml b/zh_CN.UTF-8/articles/contributing/article.xml new file mode 100644 index 0000000000..7a0437e965 --- /dev/null +++ b/zh_CN.UTF-8/articles/contributing/article.xml @@ -0,0 +1,488 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd"> +<!-- + The FreeBSD Documentation Project + The FreeBSD Chinese (Simplified) Documentation Project + + Original Revision: 1.511 +--> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>为 FreeBSD 提供帮助</title> + + + <abstract> + <para>无论是作为个人还是组织机构,如果您希望为FreeBSD项目提供帮助, + 都可以在本文中找到合适的方法。</para> + </abstract> + + <authorgroup> + <author><personname><firstname>Jordan</firstname><surname>Hubbard</surname></personname><contrib>原著</contrib></author> + </authorgroup> + + <legalnotice xml:id="trademarks" role="trademarks"> + &tm-attrib.freebsd; + &tm-attrib.ieee; + &tm-attrib.general; + </legalnotice> + + <pubdate>$FreeBSD$</pubdate> + + <releaseinfo>$FreeBSD$</releaseinfo> + + </info> + + <indexterm><primary>贡献 </primary></indexterm> + + <para>您希望给 FreeBSD 项目做点什么吗? 太好了, 我们欢迎您。 FreeBSD 正是 + <emphasis>依靠</emphasis> 广大用户的贡献才得以发展壮大的。 + 我们不仅非常感谢您所做的贡献,而且,这些工作对于 FreeBSD 的持续发展也至关重要。</para> + + <para>也许与您想象的不同, 您既不必是一名出色的程序员, 也无须和 + FreeBSD 核心团队成员有很好的私交, 我们会一视同仁的对待您的工作。 + FreeBSD 的开发人员遍布全球, 大家技术专长各异, 年龄分布也非常广泛。 + 每天, 我们都在面对持续增长的工作而苦于没有足够的人手, + 因此我们随时欢迎您的帮助。</para> + + <para>FreeBSD 项目处理的是一个完整的操作系统环境, + 而不只是一个内核或是一些零散的工具包。 因此, 我们的 + <filename>TODO</filename> 任务列表里包含各种各样的工作, + 从文档、用户测试、演示, 到系统安装程序和高度专业的内核开发。 + 因此无论您的技术水平如何, 从事何种领域, 都可以帮助这个项目。</para> + + <para>我们鼓励从事和 FreeBSD 相关工作的企业和我们联系。 + 您需要一些特殊的扩展来使您的产品运转起来么? + 您会发现我们很乐意答应您的请求, 除非是特别稀奇古怪的。 + 您是否正从事相关的增值业务? 让我们来帮助您吧, + 我们也许可以在其中的某些方面相互协作。 + 自由软件世界正在努力打破旧有的关于软件开发、 销售和维护的框框, + 我们希望恳请您至少能给它一次机会。</para> + + <sect1 xml:id="contrib-what"> + <title>我们的需求</title> + + <para>下面列出了一些需要完成的任务和子项目, + 它们基本上可以被等同于 <filename>TODO</filename>(任务列表) + 列表, 以及用户的要求。</para> + + <sect2 xml:id="non-programmer-tasks"> + <title>正在进行中的非开发任务</title> + + <para>很多参加FreeBSD项目的人不是程序员。 + 这个项目里有文档撰写者、 网页设计师、 以及技术支持人员。 + 对于这些志愿者来说, 他们只需要贡献一些时间, + 并且具有学习的意愿。</para> + + <orderedlist> + <listitem> + <para>您可以经常通读FAQ和手册, 如果您发现了繁琐的解释, + 或者是过时的知识, 甚至完全不正确的地方, 都请告诉我们。 + 如果您能顺手把他们改过来那就更好了 + (SGML其实并不难学, 但我们也不反对您直接提交 + ASCII 的版本)。</para> + </listitem> + + <listitem> + <para>帮助我们把 FreeBSD 文档翻译成您的母语。 + 如果您的母语版本已经存在了, + 您也可以翻译一些其他的文档或者检查那些已有的文档是否是最新更新过的。 + 您可以先简单看看 FreeBSD 文档计划中有关 <link xlink:href="&url.books.fdp-primer;/translations.html">翻译的常见问题</link>。 + 参加翻译工作并不是说您要孤军奋战翻译所有的 FreeBSD 文档。 + 作为一个志愿者, 做多少工作完全取决于您的意愿。 一旦某个人开始翻译了, + 其他人几乎一定会参与到这些工作中来。 + 如果您只有有限的时间或者精力去翻译部分文档, + 您可以首先去翻译安装指南。</para> + </listitem> + + <listitem> + <para>阅读 &a.questions; 并偶尔看一看 &ng.misc; + (甚至有规律地这样做)。 与别人分享您的专业知识, + 并帮助他们解决问题是一件令人愉悦的事情; + 有些时候您甚至可以在这个过程中学到一些新东西! + 这些论坛有时也会为您提供一些有价值的主意。</para> + </listitem> + </orderedlist> + </sect2> + + <sect2 xml:id="ongoing-programmer-tasks"> + <title>正在进行的开发任务</title> + <para>列在这里的大部分任务都需要您投入可观的时间, + 或者需要您在 FreeBSD 内核方面有丰富的知识, 或者两者都要。 + 当然这里也有很多重要的任务也许您一个 + <quote>周末开发人员</quote> 就可以干完。</para> + + <orderedlist> + <listitem> + <para>如果您正在运行 FreeBSD-CURRENT 版本并且有一条高速的 + Internet接入线路, 您可以访问 <systemitem class="fqdomainname">current.FreeBSD.org</systemitem>, + 这里每天会有一个新版本 — 如果您有空, + 您可以隔三岔五地下载一份并且安装它, + 其间如果出了什么问题,请告诉我们。</para> + </listitem> + + <listitem> + <para>阅读 &a.bugs;。 您可能会为这些问题提供具有建设性意义的评论, + 或者帮忙测试一些补丁。 此外, + 您甚至可以尝试修正其中的一些问题。</para> + </listitem> + + <listitem> + <para>如果您知道有一些修正已经在 -CURRENT 上成功地进行, + 但在经过一段时间之后仍然没有合并到 -STABLE + (通常是 2周左右), 给相关的 committer 发一封礼貌的提示信。</para> + </listitem> + + <listitem> + <para>将第三方软件加入到源代码中的 + <filename>src/contrib</filename> 目录。</para> + </listitem> + + <listitem> + <para>确保 <filename>src/contrib</filename> + 中的代码是最新的</para> + </listitem> + + <listitem> + <para>以更高的警告级别构建源代码 (或一部分源代码) + 并清理这些警告。</para> + </listitem> + + <listitem> + <para>更新那些在 ports 中使用过时的东西, + 例如 <function>gets()</function> 或包含 + <filename>malloc.h</filename> 所产生的警告。</para> + </listitem> + + <listitem> + <para>如果您制作了 ports, 并进行了一些针对 + &os; 的改动, 将您的补丁发回给原作者 + (这样下次升级时您的工作会变得轻松一些)。</para> + </listitem> + + <listitem> + <para>获取一份正式的标准, 如 &posix; 的副本。 + 您可以在 <link xlink:href="&url.base;/projects/c99/index.html">FreeBSD + C99 & POSIX 标准顺应项目</link> 网站上得到相关的链接。 + 将 FreeBSD 的行为同标准进行比较。 如果与标准不同, + 特别是那些细节地方的微小差异, 请发送一个关于它的 PR (问题报告)。 + 如果可能, 请指出如何修正它, 并随 PR 提交补丁。 + 如果您认为标准有问题, + 请向标准化团体要求对其进行重新的考虑。</para> + </listitem> + + <listitem> + <para>为这份列表建议更多的内容!</para> + </listitem> + </orderedlist> + </sect2> + + <sect2> + <title>查看整个 PR 数据库</title> + + <indexterm><primary>问题报告数据库</primary></indexterm> + + <para><link xlink:href="http://www.FreeBSD.org/cgi/query-pr-summary.cgi">FreeBSD + PR 列表</link> 展示了所有当前处于活跃状态的问题报告, 以及由 + FreeBSD 用户提交的改进建议。 PR + 数据库同时包括了开发人员和非开发人员的任务。 + 查看那些尚未解决的 PR, 并看看是否有您感兴趣的任务。 + 这其中可能有一些是非常简单的问题, + 只需要看一看并确认 PR 是正确的。 另外一些可能会非常复杂, + 或者完全没有包括任何修正。</para> + + <para>首先看一看那些还没有人接手的 PR。 + 如果 PR 已经分配给了其它人, 但看起来是您能够处理的, + 您可以给那个人发信, 并询问您是否可以提供帮助 — + 他们可能已经有了可供测试的补丁, 或有一些可供讨论的意见。</para> + </sect2> + + <sect2> + <title>从 <quote>点子</quote> 网页上认领项目</title> + + <para><link xlink:href="&url.base;/projects/ideas/">&os; + 志愿者项目和点子清单</link> 也是提供给愿意为 + &os; 项目做出贡献的人们的。 + 这张清单一直在被定期更新着, + 包含了对程序员和非程序员有用的每个项目的信息。</para> + </sect2> + </sect1> + + <sect1 xml:id="contrib-how"> + <title>如何提供帮助</title> + + <para>帮助改进系统基本上可以分为 5 类:</para> + + <sect2 xml:id="contrib-general"> + <title>错误报告和一般的注解</title> + + <para>通常, <emphasis>一般意义上的</emphasis> + 技术想法和建议应该发到 &a.hackers;。 + 同样地, 对于这些东西有兴趣的人 (当然, + 他们同时还要能够容忍 <emphasis>大量的</emphasis> 邮件!) + 可以考虑订阅 &a.hackers;。 + 参见 <link xlink:href="&url.books.handbook;/eresources.html#ERESOURCES-MAIL">FreeBSD + 使用手册</link> 以了解关于这个邮件列表, + 以及其它邮件列表的详细情况。</para> + + <para>如果您发现了 bug 或者想要提交某些修改, + 请通过 &man.send-pr.1; 程序或使用 + <link xlink:href="&url.base;/send-pr.html">基于 WEB + 的提交页面</link> 来提交。 请试着填写 bug 报告的每一项。 + 一般来说, 我们建议在 bug 报告中直接附上补丁, 除非它超过了 65KB。 + 如果补丁可以直接应用到源代码上, 则建议您在报告的 + synopsis 一栏写上 <literal>[PATCH]</literal>。 + 在附带补丁时, 请 <emphasis>不要</emphasis> + 通过复制和粘贴来进行, 因为这样做会把 tab 变成空格, + 结果补丁很可能就不能用了。 如果补丁超过 20KB 很多, + 应考虑将其压缩 (例如使用 &man.gzip.1; 或 &man.bzip2.1;) + 之后用 &man.uuencode.1; 进行编码之后再放进您的问题报告中。</para> + + <para>一旦报告被存档, 您会收到一封确认邮件以及一个事件追踪编号。 + 请保留这个编号, 因为您可以在之后使用这个编号, + 发邮件到 &a.bugfollowup; + 来提供关于该事件的进一步信息。 您需要做的是将编号放到邮件的标题中, + 例如 <literal>"Re: + kern/3377"</literal>。 + 关于同一问题更进一步的情况应该通过这种方式来提交。</para> + + <para>如果您在一段时间之后仍然没有收到确认信 (超过 3 + 天甚至 1 周, 这取决于您的邮件服务) + 或者由于某种原因无法使用 &man.send-pr.1; 命令, + 则可以发信给 &a.bugs; 要求别人代您发送它。</para> + + <para>请参见 <link xlink:href="&url.articles.problem-reports;/article.html">这篇文章</link> + 了解如何撰写好的问题报告。</para> + </sect2> + + <sect2> + <title>对于文档的修订</title> + + <indexterm><primary>提交文档</primary></indexterm> + + <para>对于文档的修改由 &a.doc; 来审查。 + 请参见 <link xlink:href="&url.books.fdp-primer;/index.html">FreeBSD + 文档计划初级读本</link> 来获得完整的指导。 + 请按照 <xref linkend="contrib-general"/> 中介绍的方法使用 &man.send-pr.1; + 来发送新的文档或者对于现有文档的完善 (哪怕是很小的改进也是欢迎的!)。</para> + </sect2> + + <sect2> + <title>对于现有源代码的修改</title> + + <indexterm><primary>FreeBSD-CURRENT</primary></indexterm> + + <para>在现有代码上进行修改或增加功能在某种程度上是需要更多技巧的事情, + 并且还和您对于目前 FreeBSD 的开发现状的了解有关。 + 有多种方式可以得到被称作 <quote>FreeBSD-CURRENT</quote> + 的 FreeBSD 开发版本, 您可以通过它来了解最近的开发情况。 + 请参见 <link xlink:href=" &url.books.handbook;/current-stable.html">FreeBSD + 使用手册</link> 来了解使用 FreeBSD-CURRENT 的进一步详情。</para> + + <para>在旧的代码上进行修改, 则通常可能由于代码已经过时, + 或与新的开发版本差异太大而无法被重新集成到 FreeBSD 中。 + 如果您订阅了 &a.announce; 以及 &a.current; 邮件列表, + 则可以通过它们来大体了解目前的开发状态。</para> + + <para>假如说您能够基于尽可能新的代码来完成您的修改, + 则下一步要做的事情就是生成您所进行的修改的差异文件, + 并将它发给 FreeBSD 的维护人员。 这项工作可以通过 &man.diff.1; + 命令来完成。</para> + + <para>提交补丁时推荐的 &man.diff.1; 格式是一致差异 (unified diff), + 它可以通过 <command>diff + -u</command> 来生成。 不过, 如果您修改了大量的代码, + 则使用 <command>diff -c</command> 来生成的上下文格式 (context diff) + 的差异可能更容易阅读, 因而推荐使用。</para> + + <indexterm> + <primary><command>diff</command></primary> + </indexterm> + + <para>例如:</para> + + <screen>&prompt.user; <userinput>diff -c oldfile newfile</userinput></screen> + + <para>或者</para> + + <screen>&prompt.user; <userinput>diff -c -r olddir newdir</userinput></screen> + + <para>将分别生成给定文件或目录结构的 context diff。</para> + + <para>类似地,</para> + + <screen>&prompt.user; <userinput>diff -u oldfile newfile</userinput></screen> + + <para>或</para> + + <screen>&prompt.user; <userinput>diff -u -r olddir newdir</userinput></screen> + + <para>的作用与前面的类似, 但采用的格式是 unified diff。</para> + + <para>请参见 &man.diff.1; 联机手册了解更多细节。</para> + + <para>一旦您使用 &man.diff.1; 生成了差异集 (可以使用 + &man.patch.1; 命令来测试一下), 就可以提交它们, + 以便被 FreeBSD 收录。 通过使用 <xref linkend="contrib-general"/> + 中所介绍的 &man.send-pr.1; 程序就可以完成这项工作。 + <emphasis>不要</emphasis> 只是把差异集发到 &a.hackers;, + 否则它们可能会被丢掉! 我们会非常感激您提交的修改 + (这是一个志愿者项目!); 因为我们都很忙, + 因此有时不一定能够立即修正问题, 但 PR + 数据库将一直保持着这些记录, + 因此只要有人有了时间它们就能被改正了。 + 如果您的问题报告中包括补丁, 一定不要忘了在标题上用 + <literal>[PATCH]</literal> 来强调一下。</para> + + <indexterm> + <primary><command>uuencode</command></primary> + </indexterm> + + <para>如果您认为合适 (例如您添加、 删除或重命名了文件), + 还可以考虑使用 + <command>tar</command> 来将文件打包, 然后用 &man.uuencode.1; + 来编码。 我们也欢迎用 &man.shar.1; 创建的包。</para> + + <para>如果您的修改可能存在潜在的争议, 例如, + 您不确定与之相关的版权问题, 或者感觉需要经过更严格的复审才可以发布它们, + 则应直接发给 &a.core;, 而不是通过 &man.send-pr.1; 来发送。 + &a.core; 是一个小组, 其成员更多的从事 FreeBSD 的日常工作。 + 需要注意的是, 这个小组也因此 <emphasis>很忙</emphasis>, + 因此只有在非常必要的时候才应给他们写信。</para> + + <para>请参考 &man.intro.9; 和 &man.style.9; 以了解关于编码习惯和约定的详情。 + 如果您了解这些约定, 则对我们来说将是极大的帮助。</para> + </sect2> + + <sect2> + <title>新代码或重要的增值软件包</title> + + <para>如果您打算提供规模较大的代码, 或者为 FreeBSD 增加重要的新功能, + 则可能必须将它们通过 uuencode 进行编码, 或传到某个 Web 或 + FTP 站点, 以便更多的人能够得到它。 如果您没有这样的服务器, + 请到相关的 FreeBSD 邮件列表提出, 看看是否有人愿意帮您放置它们。</para> + + <para>对于大量的代码而言, 关于版权的问题肯定会被提出。 + FreeBSD 基本系统中能够使用的版权声明包括:</para> + + <orderedlist> + <listitem> + <para>BSD<indexterm><primary>BSD 版权声明</primary></indexterm> 版权。 我们倾向于使用这类授权的代码, + 因为它 <quote>不附加多余的条件</quote>, 因而更能够吸引商业企业使用。 + FreeBSD 并不反对商业公司使用它的代码, 相反, + 我们积极地鼓励商业公司使用我们的代码, + 当然, 如果它们最终把一部分代码重新捐赠给 FreeBSD + 就更好了。</para> + </listitem> + + <listitem> + <para>GNU General Public License,<indexterm><primary>GPL</primary><see>GNU General Public License</see></indexterm><indexterm><primary>GNU General Public License</primary></indexterm> 或简称 <quote>GPL</quote>。 + 我们并不很欢迎使用这样授权的代码, + 因为商业公司使用它需要做更多的工作。 不过, 由于很多使用 + GPL 授权的代码目前是无法避免的 (编译器、 汇编器, + 文本排版程序等等), 拒绝使用所有采用这样授权的软件是很不明智的。 + 采用 GPL 授权的代码会被放到源代码的一些专门的位置, 例如 + <filename>/sys/gnu</filename> 或 + <filename>/usr/src/gnu</filename>, + 以方便那些使用 GPL 代码可能会给他们带来问题的人识别。</para> + </listitem> + </orderedlist> + + <para>使用其它授权的代码在进入 FreeBSD 之前必须经过慎重的复审和考虑。 + 采用包含严厉限制的商业授权的代码, 一般来说会被拒绝, + 但我们鼓励这些代码的作者通过自己的渠道来发布它们。</para> + + <para>要在您的成果上加入 <quote>BSD式</quote> 的版权, + 请把下列文本放到每一个源文件的最开始部分, + 并用适当的文字替换 <literal>%%</literal> 之间的文字。</para> + + <programlisting>Copyright (c) %%proper_years_here%% + %%your_name_here%%, %%your_state%% %%your_zip%%. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer as + the first lines of this file unmodified. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY %%your_name_here%% ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL %%your_name_here%% BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + $Id$</programlisting> + + <para>为了方便您的使用, 在 + <filename>/usr/share/examples/etc/bsd-style-copyright</filename> + 也可以找到此授权的副本。</para> + </sect2> + + <sect2> + <title>资金、 硬件或 Internet 接入</title> + + <para>我们非常愿意接受各种形式的捐赠, 以进一步拓展 FreeBSD + Project 的事业, 因为有您的支持, + 像我们这样的志愿者努力才能够有更大的成就! + 捐赠硬件也非常重要, 因为这样能够帮助我们增加可以支持的硬件种类, + 而我们中的很多人并没有足够的资金来购置这些硬件。</para> + + <sect3 xml:id="donations"> + <title>捐款</title> + + <para>FreeBSD 基金会是一个非营利的、 有课税豁免权的基金会, + 建立这个基金会的目标是为了让 FreeBSD Project 能够达成更加长远的目标。 + 作为 501(c)3 实体, 一般而言基金会可以免予上缴美国联邦收入税, + 以及科罗拉多州收入税。 通常对于课税豁免的实体进行捐赠, + 可以折抵联邦收入中应课税部分的金额。</para> + + <para>您可以把支票寄往: + <address> + The FreeBSD Foundation + <street>7321 Brockway Dr.</street> + <city>Boulder</city>, <state>CO</state> <postcode>80303</postcode> + <country>USA</country> + </address> + </para> + + <para>FreeBSD 基金会现在可以通过 PayPal 从网上接受捐款。 + 如果您想向基金会捐款, 请访问它的 <link xlink:href="http://www.freebsdfoundation.org">web + 站点</link>。</para> + + <para>关于 FreeBSD 基金会的更多详情, 可以在 <link xlink:href="http://people.FreeBSD.org/~jdp/foundation/announcement.html">FreeBSD + 基金会 -- 介绍</link> 找到。 要联络基金会, + 请发送电子邮件到 + <email>bod@FreeBSDFoundation.org</email>。</para> + </sect3> + + <sect3> + <title>捐赠硬件</title> + <indexterm><primary>捐赠</primary></indexterm> + + <para>FreeBSD 计划欢迎任何人捐赠可以使用的硬件。 + 如果您有兴趣捐赠硬件, 请联系 <link xlink:href="&url.base;/donations/">捐赠联络人办公室</link>。</para> + </sect3> + + <sect3> + <title>捐赠 Internet 接入</title> + + <para>我们欢迎新的 FTP、 WWW 或 + <command>cvsup</command> 镜像。 如果您希望成为这样的镜像, + 请参见 <link xlink:href="&url.articles.hubs;/index.html">如何架设 FreeBSD 镜像</link> + 一文, 以了解进一步的情况。</para> + </sect3> + </sect2> + </sect1> + + <index/> + +</article> diff --git a/zh_CN.UTF-8/articles/cvs-freebsd/Makefile b/zh_CN.UTF-8/articles/cvs-freebsd/Makefile new file mode 100644 index 0000000000..ce7b36b13f --- /dev/null +++ b/zh_CN.UTF-8/articles/cvs-freebsd/Makefile @@ -0,0 +1,22 @@ +# The FreeBSD Simplified Chinese Project +# +# Original Revision: 1.5 +# $FreeBSD$ +# +# Article: Setting up a CVS repository - the FreeBSD way + +MAINTAINER= stijn@win.tue.nl + +DOC?= article + +FORMATS?= html + +INSTALL_COMPRESSED?= gz +INSTALL_ONLY_COMPRESSED?= + +SRCS= article.xml + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. + +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/cvs-freebsd/article.xml b/zh_CN.UTF-8/articles/cvs-freebsd/article.xml new file mode 100644 index 0000000000..70d53be1aa --- /dev/null +++ b/zh_CN.UTF-8/articles/cvs-freebsd/article.xml @@ -0,0 +1,588 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd"> +<!-- + The FreeBSD Documentation Project + The FreeBSD Chinese (Simplified) Documentation Project + + From FreeBSD: doc/zh_TW.Big5/articles/cvs-freebsd/article.xml,v 1.2 2006/01/31 01:30:59 vanilla + Original Revision: 1.17 +--> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>建立 CVS 储存库 (repository) - 以 FreeBSD 的方式</title> + + + <author><personname><firstname>Stijn</firstname><surname>Hoop</surname></personname><affiliation> + <address><email>stijn@win.tue.nl</email></address> + </affiliation></author> + + <pubdate>$FreeBSD$</pubdate> + + <copyright> + <year>2001</year> + <year>2002</year> + <year>2003</year> + <holder role="mailto:stijn@win.tue.nl">Stijn Hoop</holder> + </copyright> + + <releaseinfo>$FreeBSD$</releaseinfo> + + <legalnotice xml:id="trademarks" role="trademarks"> + &tm-attrib.freebsd; + &tm-attrib.general; + </legalnotice> + + <abstract> + <para>这份文件描述了使用和 FreeBSD 项目相同的命令脚本来建立 CVS 储存库的步骤。 + 这和标准 CVS 建立的储存库相较之下有许多优点, + 它提供了更多对于源代码树的细粒度访问控制, + 并能够为每一次的提交生成和发出易读的电子邮件。</para> + </abstract> + </info> + + <sect1 xml:id="introduction"> + <title>简介</title> + + <para>大多数的开放源代码软件项目都使用 <application>CVS</application> + 作为它们的源代码控制系统。 尽管 <application>CVS</application> 有许多的优点, + 但它也有部份的瑕疵和缺点。 + 其中之一的原因是和其它的开发者分享源代码树可能会迅速成为系统管理的恶梦, + 特别是当有人希望保护部份的源代码树免受于一般的存取时。</para> + + <para>FreeBSD 是众多使用 <application>CVS</application> 的项目之一, + 因为基于它进行开发的开发人员遍布于全世界。 + 他们撰写了一些命令脚本使得管理储存库变得更加容易。 + 最近这些命令脚本由 &a.joe; 重新整理过且更标准化, + 使得在其它的项目上再次使用这些命令脚本会更加容易。 + 本文件将描述使用这些新的命令脚本的方法。</para> + + <para>为了使本文件中的信息对您有用, 您需要首先熟悉 <application>CVS</application> + 的基本操作方式。</para> + </sect1> + + <sect1 xml:id="first-setup"> + <title>基本配置</title> + + <warning> + <para>最好的方式是在一个全新的储存库中执行这些步骤, + 并确定你了解所有的后果。 同时, 请确定你有最新且可读的资料备份!</para> + </warning> + + <sect2> + <title>初始化储存库</title> + + <para>首先要做的是建立一个新的储存库, 执行下列命令告诉 + <application>CVS</application> 建立并初始化:</para> + + <screen>&prompt.user; <userinput>cvs -d path-to-repository init</userinput></screen> + + <para>这命令告诉 <application>CVS</application> 建立 + <filename>CVSROOT</filename> 的目录, + 这个目录里放置了所有的配置文件。</para> + </sect2> + + <sect2> + <title>配置储存库的用户组</title> + + <para>现在我们将建立一个拥有该储存库的用户组, + 所有的开发者必须加入这个用户组, 这样他们才能够存取该储存库。 + 我们假设用户组名称是以 FreeBSD 内部所采用的 + <literal>ncvs</literal>。</para> + + <screen>&prompt.root; <userinput>pw groupadd ncvs</userinput></screen> + + <para>接着你需要使用 &man.chown.8; 将目录所有者指定给刚刚新增的用户组:</para> + + <screen>&prompt.root; <userinput>chown -R :ncvs path-to-your-repository</userinput></screen> + + <para>如此一来, 没有适当的用户组许可的用户, 就不再能够写入该储存库。</para> + </sect2> + + <sect2> + <title>取回源文件</title> + + <para>现在你需要从 FreeBSD 储存库中取回 <filename>CVSROOT</filename> 目录, + 从 FreeBSD 匿名的 CVS 镜像站来取回会是最简单的方法。请查阅 <link xlink:href="&url.books.handbook;/anoncvs.html">在 使用手册 中的相关章节</link> + 来获得更多信息。 我们假设取回的文件存放在相同目录下的 + <filename>CVSROOT-freebsd</filename> 目录中。</para> + </sect2> + + <sect2> + <title>复制 FreeBSD 的命令脚本</title> + + <para>接下来我们要复制 FreeBSD <filename>CVSROOT</filename> + 里的文件到你的储存库中。 如果你熟悉 <application>CVS</application>, + 你也许会想你可以直接汇入 (import) 这些命令脚本, + 从而更容易地在未来有新版时进行版本同步; 不过,事实是 + <application>CVS</application> 在这个部份有缺点: 当汇入文件到 + <filename>CVSROOT</filename> 时, 它并不会更新配置文件。 + 为了要认出这些文件, 你还需要在汇入它们后一一重新提交, 这就失去了 + <literal>cvs import</literal> 的价值。 因此, 推荐的方法是直接将这些命令脚本复制过去。</para> + + <para>如果您不了解这些操作也没有关系 — 因为最后的结果都是一样的。 + 首先汇出 (checkout) 你的 <filename>CVSROOT</filename>, + 然后复制刚刚取回的 FreeBSD 文件到本地的目录中(尚未变动过):</para> + + <screen>&prompt.user; <userinput>cvs -d path-to-your-repository checkout CVSROOT</userinput> +&prompt.user; <userinput>cd CVSROOT</userinput> +&prompt.user; <userinput>cp ../CVSROOT-freebsd/* .</userinput> +&prompt.user; <userinput>cvs add *</userinput></screen> + + <para>注意: 你很可能会得到一段关于某些目录没有被复制的警告,这是正常的, + 你并不需要用到这些目录。</para> + </sect2> + + <sect2> + <title>命令脚本说明</title> + + <para>现在你的工作目录中有了完整 FreeBSD 项目在他们的储存库中使用的命令脚本的副本, + 以下是每个文件简单的介绍。</para> + + <itemizedlist> + <listitem> + <para><filename>access</filename> - 此文件在预设的安装中没有被用到。 + 它是在 <link linkend="freebsdspecific">FreeBSD 的专用配置</link> + 中用来控制储存库的存取的。 如果你不希望使用这个配置的话, + 则可以删除这个文件。</para> + </listitem> + + <listitem> + <para><filename>avail</filename> - 此文件控制储存库的存取。 + 在此文件中你可以指定允许存取储存库的用户组, + 也可以针对目录或文件来拒绝提交。 + 你应该调整为在你的储存库中将包含的用户组和目录。</para> + </listitem> + + <listitem> + <para><filename>cfg.pm</filename> - 此文件说明了配置内容,并提供预设的配置。 + 你 <emphasis>不</emphasis> 应修改此文件, 而应将修改的配置放到 + <filename>cfg_local.pm</filename>。</para> + </listitem> + + <listitem> + <para><filename>cfg_local.pm</filename> - 此文件包含所有的系统配置值。 + 你应该配置所有列在此的配置, 例如提交的邮件要寄到哪、 + 在哪些主机上的使用者可以提交等等。 更多的相关信息在稍后会提到。</para> + </listitem> + + <listitem> + <para><filename>checkoutlist</filename> - 此文件列出所有在 + <application>CVS</application> 控制下此目录中的文件, 除了标准在 + <literal>cvs init</literal> 建立出的文件。 你可以删除某些不需要的 + FreeBSD 专用的文件。</para> + </listitem> + + <listitem> + <para><filename>commit_prep.pl</filename> - 此命令脚本执行各种提交前的检查, + 这些检查是否启用, 取决于您在 <filename>cfg_local.pm</filename> + 中所进行的配置。 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>commitcheck</filename> - 此命令脚本会直接影响 + <application>CVS</application>。 首先它会使用 + <filename>cvs_acls.pl</filename> 来检查提交者是否可以存取指定的源代码树, + 然后执行 <filename>commit_prep.pl</filename> 来确认各种提交前的检查。 + 如果一切正常,<application>CVS</application> 将允许此次提交继续执行。 + 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>commitinfo</filename> - 此文件是 + <application>CVS</application> 用来定义在提交前所要执行的程序 — + 在此例中是 <filename>commitcheck</filename>。你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>config</filename> - 储存库的配置选项。 + 你可以修改为你想要的, 但大多数的管理者可能会保留默认值。 + 更多关于可以在此配置的选项信息可以查阅 <application>CVS</application> 手册。</para> + </listitem> + + <listitem> + <para><filename>cvs_acls.pl</filename> - 此命令脚本定义提交者的身分, + 以及他/她是否允许存取源代码树, 这些判断基于 <filename>avail</filename> + 中的配置。 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>cvsignore</filename> - 此文件列出哪些文件 + <application>CVS</application> 不用处理到储存库中, + 你可以修改成你想要的。 更多关于可以此文件的说明可以查阅 <application>CVS</application> + 手册。</para> + </listitem> + + <listitem> + <para><filename>cvswrappers</filename> - 此文件是 + <application>CVS</application> 用来启用或停用关键词展开, + 或者是否应将文件视为二进制文件。 你可以修改成你想要的。 + 更多关于可以此文件的说明可以查阅 <application>CVS</application> 手册。 + 注意 <literal>-t</literal> 和 <literal>-f</literal> 选项在 + <application>CVS</application> 客户端/服务器 并不能够正确地运作。</para> + </listitem> + + <listitem> + <para><filename>edithook</filename> - 此文件已经不再使用了, + 仅为历史原因保留。 你可以安全地删除此文件。</para> + </listitem> + + <listitem> + <para><filename>editinfo</filename> - <application>CVS</application> + 使用这个文件来强迫你使用特定的编辑器。 FreeBSD 没有使用这个功能, + 而对输入的日志信息的检查, 则由 <filename>verifymsg</filename> 和 + <filename>logcheck</filename> 来完成。 这是因为 + <filename>editinfo</filename> 功能在从远程提交或是使用 + <literal>-m</literal> 或 <literal>-F</literal> 选项时不会执行。 + 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>exclude</filename> - 此文件列出被 + <filename>commit_prep.pl</filename> 定义不能包含修正版标头的文件。 + 在 FreeBSD 版本的配置中, 所有在修正版控制下的文件需有一个修正版标头, + (类似 $FreeBSD$ 这样)。 + 此文件逐行列出不应进行检查的文件名字。 + 你可以在此文件中为不需要修正版标头的文件新增一个正则表达式。 + 为了安装这些命令脚本, 最好的方法是将 + <filename>CVSROOT/</filename> 从标头检查中排除。</para> + </listitem> + + <listitem> + <para><filename>log_accum.pl</filename> - 此命令脚本会处理由 + <filename>logcheck</filename> 所提供的日志信息, + 并且将之为备份目的附加于储存库中的记录文件。 + 同时也执行要将邮件寄到你提供的信箱中的程序 (在 <filename>cfg_local.pm</filename> 中)。 + 它和 <application>CVS</application> 之间是由 <filename>loginfo</filename> + 负责沟通。 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>logcheck</filename> - 此文件分析提交者提供的日志信息, + 并试图对其作清理动作。 它和 <application>CVS</application> + 之间是由 <filename>verifymsg</filename> 负责沟通。 你不应更动此文件。</para> + + <note><para>此命令脚本依赖于经过 FreeBSD 修改的 <application>CVS</application>: + FreeBSD 版本在此命令脚本修改过后才读取日志信息; 标准的 + <application>CVS</application> 版本虽然能够检查语法上是否正确, + 但并不会清理日志信息。<application>CVS</application> 1.11.2 可以通过在 + <filename>config</filename> 配置 <literal>RereadLogAfterVerify=always</literal> + 来和 FreeBSD 版本有相同的作用。</para></note> + </listitem> + + <listitem> + <para><filename>loginfo</filename> - 此文件是 + <application>CVS</application> 用来控制日志信息要寄到哪里,而 + <filename>log_accum.pl</filename> 负责处理。你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>modules</filename> - 此文件保留了 + <application>CVS</application> 原始的意义。 你应该删除新增的 FreeBSD + 模块, 并修改为你想要的内容。 更多关于可以此文件的说明可以查阅 + <application>CVS</application> 手册。</para> + </listitem> + + <listitem> + <para><filename>notify</filename> - 此文件为 + <application>CVS</application> 用来控制监看某个文件。 + 在 FreeBSD 的储存库中没有用到此文件, + 你可以修改成你想要的。 更多关于可以此文件的说明, + 可以查阅 <application>CVS</application> 手册。</para> + </listitem> + + <listitem> + <para><filename>options</filename> - 此文件仅限使用于 FreeBSD 和 Debian + 的 <application>CVS</application> 版本。 + 它包含了需要在修正版标头中展开的关键词。 + 你可以修改为符合你在 <filename>cfg_local.pm</filename> 中指定的关键词。</para> + </listitem> + + <listitem> + <para><filename>rcsinfo</filename> - 此文件定义提交时, + 储存库所要使用的日志信息样式模板, 如 <filename>rcstemplate</filename>。 + FreeBSD 预设为所有的储存库使用同一个样式模板, + 你可以加入其它你想要的。</para> + </listitem> + + <listitem> + <para><filename>rcstemplate</filename> - 此文件是提交者在提交时会看到的日志信息样式模板, + 你应该修改为你在 <filename>cfg_local.pm</filename> + 中定义的各种参数。</para> + </listitem> + + <listitem> + <para><filename>tagcheck</filename> - 此文件控制在储存库中贴上标签的存取。 + 标准的 FreeBSD 版本拒绝名为 RELENG* 的标签,因为这是 交付工程组 的工作。 + 你可以根据需要来修改此文件。</para> + </listitem> + + <listitem> + <para><filename>taginfo</filename> - 此文件控制执行在储存库中贴上标签的存取的命令脚本, + 如 <filename>tagcheck</filename>。你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>unwrap</filename> - 此命令脚本可以用来在汇出时自动 <quote>解开</quote> + 二进制文件 (请见 <filename>cvswrappers</filename>)。 目前 FreeBSD + 并没有使用此配置, 因为此功能在远程提交时执行的并不十分完善。 + 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>verifymsg</filename> - 此文件用来执行和日志信息相关的命令脚本, + 如 <filename>logcheck</filename>。你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>wrap</filename> - 此命令脚本可以用来在提交时自动 <quote>包裹</quote> + 二进制文件 (请见 <filename>cvswrappers</filename>)。 目前 FreeBSD + 并没有使用此配置, 因为此功能在远程提交时执行的并不十分完善。 + 你不应更动此文件。</para> + </listitem> + </itemizedlist> + </sect2> + + <sect2> + <title>定制命令脚本</title> + + <para>接下来的步骤要配置这些命令脚本使得它们可以在你的环境中运作。 + 你应该检查所有在目录中的文件, 并修改为符合你的配置。 + 尤其, 你会想要修改下列的文件:</para> + + <procedure> + <step> + <para>如果你不希望使用 <link linkend="freebsdspecific">FreeBSD 的专用配置</link>, + 你可以安全地删除 <filename>access</filename>:</para> + + <screen>&prompt.user; <userinput>cvs rm -f access</userinput></screen> + </step> + + <step> + <para>编辑 <filename>avail</filename> 来包含你想控制存取的各种储存库目录, + 请确定你有保留 <literal>avail||CVSROOT</literal> 这一行, + 否则你将会在下一步把你自己锁在外面。</para> + + <para>另外你可以在此文件中新增开发者的用户组,FreeBSD 预设使用 + <filename>access</filename> 来列出所有的开发者, + 但你可以使用任何你想要用的文件。 如果你想的话也可以新增用户组 (请使用指定在 + <filename>cvs_acls.pl</filename> 前面所介绍的语法)。</para> + </step> + + <step> + <para>编辑 <filename>cfg_local.pm</filename> 来包含你需要的选项。 + 你应该特别检视一下下列的配置项目:</para> + + <itemizedlist> + <listitem> + <para><literal>%TEMPLATE_HEADERS</literal> - 这是用来取得日志信息内容的程序, + 并加入将呈现的邮件项目和提供非空值的信息。 + 你可以删除 <literal>PR</literal> 和 <literal>MFC after</literal> + 叙述,当然也可以加入你想要的。</para> + </listitem> + + <listitem> + <para><literal>$MAIL_BRANCH_HDR</literal> - 如果你想要在每一封提交的邮件中加入描述是在哪一个分支中提交的标头, + 那么请定义为符合你的配置。 如果你不想使用这样的标头, 那么请配置为空值。</para> + </listitem> + + <listitem> + <para><literal>@COMMIT_HOSTS</literal> - 定义使用者能够提交的主机。</para> + </listitem> + + <listitem> + <para><literal>$MAILADDRS</literal> - 配置应该收到提交邮件的邮件地址。</para> + </listitem> + + <listitem> + <para><literal>@LOG_FILE_MAP</literal> - 以你所需要的来修改这个数组, + 每个配置值应该符合被提交的目录,而提交的日志信息会以 + <filename>commitlogs</filename> 的名称储存在每个被配置的目录下。</para> + </listitem> + + <listitem> + <para><literal>$COMMITCHECK_EXTRA</literal> - 如果你不想使用 + <link linkend="freebsdspecific">FreeBSD 专用的存取控制</link> + 功能, 你可以在此文件中删除对 <literal>$COMMITCHECK_EXTRA</literal> + 的定义。</para> + </listitem> + </itemizedlist> + + <note><para>修改 <literal>$IDHEADER</literal> 的功能只有在 FreeBSD + 平台上可以运作, 它依赖于 FreeBSD 专用的 <application>CVS</application> + 配置。</para></note> + + <para>你可以检查 <filename>cfg.pm</filename> 是否有其它的参数可以修改, + 但是修改最好是有原因的。</para> + </step> + + <step> + <para>删除 <filename>exclude</filename> 中关于FreeBSD 的专用配置的叙述 + (如以 <literal>^ports/</literal> 为开头的每一行等)。 此外, + 注释掉以 <literal>^CVSROOT/</literal> 为开头的行列, 然后新增一行只有 + <literal>^CVSROOT/</literal>。 等到关键词展开的命令脚本安装好后, + 你可以在 <filename>CVSROOT</filename> 目录中的文件里加上标头, + 然后再恢复刚刚注释的行列, 但在你还没有提交前则只保持这样。</para> + </step> + + <step> + <para>编辑 <filename>modules</filename>, 并删除所有 FreeBSD 的模块。 + 加入你需要的模块。</para> + </step> + + <step> + <note><para>此步骤只有在你于 <filename>cfg_local.pm</filename> 中指定了 + <literal>$IDHEADER</literal> 才有必要配置 (只有在 FreeBSD 专用的 + <application>CVS</application> 配置上才能够执行)。</para></note> + + <para>编辑 <filename>options</filename> 以符合你在 + <filename>cfg_local.pm</filename> 中配置的标签名称。 + 在所有的文件中搜寻 <literal>FreeBSD</literal> + 并替换为你配置的标签名称。</para> + </step> + + <step> + <para>修改 <filename>rcstemplate</filename> 为和在 + <filename>cfg_local.pm</filename> 中相同的配置。</para> + </step> + + <step> + <para>选择性的删除在 <filename>tagcheck</filename> 中针对 FreeBSD + 检查的配置。你可以仅仅在文件的最上层加上 <literal>exit 0</literal> + 来取消所有标签的检查。</para> + </step> + + <step> + <para>在你完成前的最后一件事是确认 commitlogs 可以正确储存。 + 预设会储存在储存库中 <filename>CVSROOT</filename> 里的 + <filename>commitlogs</filename> 子目录中, + 而这个目录需要事先建立:</para> + + <screen>&prompt.user; <userinput>mkdir commitlogs</userinput> +&prompt.user; <userinput>cvs add commitlogs</userinput></screen> + </step> + </procedure> + + <para>现在, 在细心的检视过后, 你可以提交你的修改了。 确定你先前有在 + <filename>avail</filename> 中允许你自己存取 <filename>CVSROOT</filename> + 目录, 因为如果没有这样做的话你会把你自己锁在外面。 + 完整确认过后请执行下列命令:</para> + + <screen>&prompt.user; <userinput>cvs commit -m '- Initial FreeBSD scripts commit'</userinput></screen> + </sect2> + + <sect2> + <title>测试配置</title> + + <para>你已经准备好做基本的测试了: 强制提交 <filename>avail</filename> + 以确认每件事都如预期的运作。</para> + + <screen>&prompt.user; <userinput>cvs commit -f -m 'Forced commit to test the new CVSROOT scripts' avail</userinput></screen> + + <para>如果一切正常, 那么恭喜了! 你现在已经为你的储存库建立好 FreeBSD + 的命令脚本了。 如果 <application>CVS</application> 仍然有警告什么, + 回头检视上述的步骤是否有正确的执行。</para> + </sect2> + </sect1> + + <sect1 xml:id="freebsdspecific"> + <title>FreeBSD 的专用配置</title> + + <para>FreeBSD 项目自己使用一个有点不同的配置,那就是同时也使用 FreeBSD + <filename>CVSROOT</filename> 中的 <filename>freebsd</filename> 子目录。 + 因为大量的 committer 必须在相同的用户组中, 因此项目写了一个简单的 wrapper + 来确保 committer 可以正确的提交, 并配置储存库的用户组名称。</para> + + <para>如果你的储存库也需要这样的功能,那么下面就会介绍如何建立, + 不过首先要先来看一段复杂的概述。</para> + + <sect2> + <title>FreeBSD 配置中使用的文件</title> + + <itemizedlist> + <listitem> + <para><filename>access</filename> - 此文件用来控制储存库的存取。 + 你应该编辑并加入所有在项目中的成员。</para> + </listitem> + + <listitem> + <para><filename>freebsd/commitmail.pl</filename> - + 此文件已经不再使用了, + 只是因为历史原因而保留。 你不应更动此文件。</para> + </listitem> + + <listitem> + <para><filename>freebsd/cvswrap.c</filename> - 此 CVS wrapper + 源代码是用来建立检查所有存取的工作。更多的信息在稍后会提出。 + 你应该编辑 <literal>ACCESS</literal> 和 <literal>REALCVS</literal> + 的路径以符合你的配置。</para> + </listitem> + + <listitem> + <para><filename>freebsd/mailsend.c</filename> - 此文件是 FreeBSD 设定 + mailing lists 需要的, 你不应更动此文件。</para> + </listitem> + </itemizedlist> + </sect2> + + <sect2> + <title>步骤</title> + + <procedure> + <step> + <para>只把你的用户名加到 <filename>access</filename> 中。</para> + </step> + + <step> + <para>编辑 <filename>cvswrap.c</filename> 的路径以符合你的配置, + 定义在大写的 <literal>ACCESS</literal> 中。 + 同时如果默认值不符合你的情况的话也应该修改本地实际的 <command>cvs</command> + 程序所在位置。 原始的 <filename>cvswrap.c</filename> + 希望替代服务器端的 CVS 程序, + 例如将其改名为 <filename>/usr/bin/ncvs</filename>。</para> + + <para>我的 <filename>cvswrap.c</filename> 是这样:</para> + + <programlisting>#define ACCESS "/local/cvsroot/CVSROOT/access" +#define REALCVS "/usr/bin/ncvs"</programlisting> + </step> + + <step> + <para>接下来是建立 wrapper 来确认你在提交时是在正确的用户组中。 + 在你的 <filename>CVSROOT</filename> 中的 + <filename>cvswrap.c</filename> 要能够使用。</para> + + <para>在你完成编辑并加入正确的路径后我们要来编译源代码:</para> + + <screen>&prompt.user; <userinput>cc -o cvs cvswrap.c</userinput></screen> + + <para>然后进行需要配置(此步骤需要 root 权限):</para> + + <screen>&prompt.root; <userinput>mv /usr/bin/cvs /usr/bin/ncvs</userinput> +&prompt.root; <userinput>mv cvs /usr/bin/cvs</userinput> +&prompt.root; <userinput>chown root:ncvs /usr/bin/cvs /usr/bin/ncvs</userinput> +&prompt.root; <userinput>chmod o-rx /usr/bin/ncvs</userinput> +&prompt.root; <userinput>chmod u-w,g+s /usr/bin/cvs</userinput></screen> + + <para>这会将 wrapper 安装成预设的 <command>cvs</command> 程序, + 请确定任何要使用储存库的人应该有正确的存取权限。</para> + </step> + + <step> + <para>现在你可以删除所有在储存库用户组中的使用者,所有的存取控制会经由 + wrapper 完成,同时 wrapper 会配置存取的正确用户组。</para> + </step> + </procedure> + </sect2> + + <sect2> + <title>测试配置</title> + + <para>你的 wrapper 现在应该已经安装好了,你当然也可以强制提交 + <filename>access</filename> 来测试是否正常:</para> + + <screen>&prompt.user; <userinput>cvs commit -f -m 'Forced commit to test the new CVSROOT scripts' access</userinput></screen> + + <para>同样地,如果有错误,检查是否上述所有步骤都有正确的执行。</para> + </sect2> + </sect1> +</article> diff --git a/zh_CN.UTF-8/articles/linux-users/Makefile b/zh_CN.UTF-8/articles/linux-users/Makefile new file mode 100644 index 0000000000..7655a1e45c --- /dev/null +++ b/zh_CN.UTF-8/articles/linux-users/Makefile @@ -0,0 +1,19 @@ +# +# $FreeBSD$ +# Original Revision: 1.1 +# Article: FreeBSD Quickstart for Linux Users + +DOC?= article + +FORMATS?= html +WITH_ARTICLE_TOC?= YES + +INSTALL_COMPRESSED?= gz +INSTALL_ONLY_COMPRESSED?= + +SRCS= article.xml + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. + +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/linux-users/article.xml b/zh_CN.UTF-8/articles/linux-users/article.xml new file mode 100644 index 0000000000..01f4c45b5b --- /dev/null +++ b/zh_CN.UTF-8/articles/linux-users/article.xml @@ -0,0 +1,572 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd"> +<!-- + The FreeBSD Documentation Project + The FreeBSD Chinese (Simplified) Documentation Project + + Original Revision: r39170 +--> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>&linux; 用户的 FreeBSD 快速入门向导</title> + + + <authorgroup> + <author><personname><firstname>John</firstname><surname>Ferrell</surname></personname></author> + </authorgroup> + + <pubdate>$FreeBSD$</pubdate> + + <copyright> + <year>2008</year> + <holder>The FreeBSD Documentation Project</holder> + </copyright> + + <releaseinfo>$FreeBSD$</releaseinfo> + + <legalnotice xml:id="trademarks" role="trademarks"> + &tm-attrib.freebsd; + &tm-attrib.linux; + &tm-attrib.intel; + &tm-attrib.redhat; + &tm-attrib.unix; + &tm-attrib.general; + </legalnotice> + + <abstract> + <para>本文档旨在快速使那些高级 &linux; + 用户熟悉FreeBSD的一些基础知识。</para> + </abstract> + </info> + + <sect1 xml:id="intro"> + <title>简介</title> + + <para>本文档将突出介绍 &os; 与 &linux; 的差别, + 以使得那些 &linux; 高级用户能自己快速熟悉 &os; + 的基础内容。这只是份技术上的快速入门, + 并非是试图描绘这两种操作系统之间的"哲学"上的差异。</para> + + <para>此文档假定认为你已经安装好了 &os;。 + 如果你还没有安装 &os; 或者对 &os; + 的安装过程方面需要帮助,请参考 &os; 手册的 + <link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/install.html"> + 安装 FreeBSD</link>一章。</para> + </sect1> + + <sect1 xml:id="shells"> + <title>Shell程序:没有Bash吗?</title> + + <para>那些从 &linux; 转过来的用户经常会惊讶于 + <application>Bash</application> 不是 &os; 的默认 Shell。 + 事实上,<application>Bash</application> 甚至没有包括在 + &os; 的默认安装中。代替的是,&os; 使用 &man.tcsh.1; + 作为自己的默认 Shell,尽管如此,<application>Bash</application> + 和其他你喜爱的 Shell 程序在 &os; 的 + <link xlink:href="article.html#SOFTWARE">Packages 和 Ports 套件</link> + 里都可以找到。</para> + + <para>如果你安装了其他的 Shell 你可以使用 &man.chsh.1; + 来设置一个用户的默认 Shell。 通常情况下, + 强烈建议不要去更改 <systemitem class="username">root</systemitem> + 用户的默认 Shell。原因是这些 Shell + 没有包括在基本系统中,正常情况下它们会被安装到 + <filename>/usr/local/bin</filename> 和 + <filename>/usr/bin</filename> 目录下。万一某天 + <filename>/usr/local/bin</filename> 和 + <filename>/usr/bin</filename> 的文件系统不能被挂载, + 这样情况下 <systemitem class="username">root</systemitem> + 将不能进入自己默认的 Shell,从而 + <systemitem class="username">root</systemitem> 将不能够登录进去。 + 鉴于这个原因,第二个系统管理员帐户 + <systemitem class="username">toor</systemitem> 创建时使用的是非默认的 + Shell。在安全 FAQ 可以查阅到关于 <link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/faq/security.html#TOOR-ACCOUNT">toor 帐户</link> + 的信息。</para> + </sect1> + + <sect1 xml:id="software"> + <title>Packages和Ports:在 &os; 中添加软件</title> + + <para>除了经典的 &unix; 安装软件的方法 + (下载源码包,解压,编辑源码,编译)外,&os; + 还提供了另外两种方法来安装应用程序:packages 和 ports。 + 你可以在 <link xlink:href="http://www.freebsd.org/ports/master-index.html">这里</link> + 到一份完整可用的 ports 和 packages 的软件清单。</para> + + <sect2 xml:id="packages"> + <title>Packages</title> + + <para>Packages 是预编译好的应用程序,在 &os; + 中等价于基于 Debian/Ubuntu 的系统中的 + <filename>.deb</filename> 软件包以及基于 + Red Hat/Fedora 的系统中的 + <filename>.rpm</filename> 软件包。 + Packages使用 &man.pkg.add.1; 来进行安装。 + 例如,下面的命令将用来安装 + <application>Apache 2.2</application>:</para> + + <screen>&prompt.root; <userinput>pkg_add /tmp/apache-2.2.6_2.tbz</userinput></screen> + + <para>使用 <option>-r</option> 操作将告诉 &man.pkg.add.1; + 来自动获取并安装一个软件包,以及解决所有的依赖关系:</para> + + <screen>&prompt.root; <userinput>pkg_add -r apache22</userinput> +Fetching ftp://ftp.freebsd.org/pub/FreeBSD/ports/i386/packages-6.2-release/Latest/apache22.tbz... Done. +Fetching ftp://ftp.freebsd.org/pub/FreeBSD/ports/i386/packages-6.2-release/All/expat-2.0.0_1.tbz... Done. +Fetching ftp://ftp.freebsd.org/pub/FreeBSD/ports/i386/packages-6.2-release/All/perl-5.8.8_1.tbz... Done. +[snip] + +To run apache www server from startup, add apache22_enable="YES" +in your /etc/rc.conf. Extra options can be found in startup script.</screen> + + <note> + <para>如果你正运行着 release 版本的 &os; (6.2,6.3,7.0等, + 通常从 CD-ROM 被安装的)<command>pkg_add -r</command> + 会为其下载专门为这些特定版本构建好的软件包。 + 这些软件包 <emphasis>可能</emphasis> 不是当前最新的程序。 + 你可以使用 <envar>PACKAGESITE</envar> 变量来覆盖默认的动作。 + 例如,把 <envar>PACHAGESITE</envar> 设置成 + <uri xlink:href="ftp://ftp.freebsd.org/pub/FreeBSD/ports/i386/packages-6-stable/Latest/">ftp://ftp.freebsd.org/pub/FreeBSD/ports/i386/packages-6-stable/Latest/</uri> + 来下载 6.X 系列最新的包。</para> + </note> + + <para>想了解更多的 packages 信息请查阅 &os; + 手册的 4.4 小节:<link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/packages-using.html">使用 Packages 系统</link>。</para> + </sect2> + + <sect2 xml:id="ports"> + <title>Ports</title> + + <para>&os; 的第二种安装应用程序的方法就是使用 Ports 套件了。 + Ports 套件是 &os; 上的一个利用 <filename>Makefile</filename> + 和一些补丁文件来特定从源码定制安装各种软件程序的框架。 + 当安装一个 port 时系统会获取程序源码, + 应用任何所需要的补丁,编译源码, + 并安装应用程序(并针对依赖关系以同样的方式安装解决)。</para> + + <para>Ports 套件,常被称作 ports 树,可以在 + <filename>/usr/ports</filename> 下找到。 + 假设Ports套件已经在安装 &os; 时安装过了。 + 如果 Ports 套件还没有被安装可以通过 &man.sysinstall.8; + 来进行安装,或者使用 &man.csup.1; 或 &man.portsnap.8; + 来从 &os; 的服务器上面拉下来。在手册的 <link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/ports-using.html">4.5.1 小节</link> + 可以找到安装 Ports 套件的详细介绍。</para> + + <para>安装一个 port 就像进入 port + 的目录并开始构建过程一样简单(通常情况下), + 下面是从 Ports 套件安装 + <application>Apache 2.2</application> 的例子:</para> + + <screen>&prompt.root; <userinput>cd /usr/ports/www/apache22</userinput> +&prompt.root; <userinput>make install clean</userinput></screen> + + <para>使用 ports 安装软件的最大好处就是能够自定义安装选项。 + 例如,从 ports 安装 <application>Apache 2.2</application> + 时你可以通过设置 <varname>WITH_LDAP</varname> &man.make.1; + 变量来启用 <application>mod_ldap</application>:</para> + + <screen>&prompt.root; <userinput>cd /usr/ports/www/apache22</userinput> +&prompt.root; <userinput>make WITH_LDAP="YES" install clean</userinput></screen> + + <para>请查看 &os; 手册的 4.5 小节,<link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/ports-using.html"> + 使用 Ports Collection</link>, + 以获取更多关于Ports Collection 的信息。</para> + </sect2> + + <sect2 xml:id="which"> + <title>Ports还是packages,我应该使用哪个?</title> + + <para>Packages 就是预编译好的 ports, + 所以从源码(ports)安装与从二进制 packages + 安装这两者间确实有很大关联。每种方法各有自己的优点:</para> + + <itemizedlist> + <title>Packages(二进制)</title> + + <listitem><simpara>更快速的安装 + (比较大的应用程序编译起来会花很长时间)。</simpara></listitem> + + <listitem><simpara>你不需要知道如何编译软件。</simpara></listitem> + + <listitem><simpara>不需要在操作系统上安装编译器。</simpara></listitem> + </itemizedlist> + + <itemizedlist> + <title>Ports(源码)</title> + + <listitem><simpara>能够定制安装选项。 + (Packages通常都是使用标准选项构建的。使用 ports + 你能够定义各种各样的选项, + 比如类似构建附加的模块或是更改安装路径之类的。)</simpara></listitem> + + <listitem><simpara>如果你喜欢的话还可以使用自己的补丁。</simpara></listitem> + </itemizedlist> + + <para>如果你没有一些特别的需求, packages + 可能刚好最适合你的情况。如果你需要进一步定制, + ports 是最适合的方法了。(请记得, + 如果你需要定制而自己又更倾向于使用 packages, + 你可以使用 <command>make</command> + <buildtarget>package</buildtarget> 从 ports + 构建一个定制的 package,然后复制到其他的服务器。)</para> + </sect2> + </sect1> + + <sect1 xml:id="startup"> + <title>系统启动:运行级别在哪里?</title> + + <para>&linux; 使用 Sysv init 初始化系统,而 &os; + 使用的是传统的 BSD 风格的 &man.init.8;。在 BSD 风格的 + &man.init.8; 中没有运行级别和 <filename>/etc/inittab</filename>, + 代替控制启动的是 &man.rc.8; 实用程序。 + <filename>/etc/rc</filename> 脚本读取 + <filename>/etc/defaults/rc.conf</filename> 和 + <filename>/etc/rc.conf</filename> 文件来决定哪个服务将被启动。 + 特殊服务在此后由处于 <filename>/etc/rc.d/</filename> 和 + <filename>/usr/local/etc/rc.d/</filename> + 下的相应服务初始化脚本文件所启动。 + 这些脚本类似于位于 &linux; 系统中的 + <filename>/etc/init.d/</filename> 目录下的脚本。</para> + + <sidebar> + <para><emphasis>为何会有两个服务初始化脚本的目录呢?</emphasis> + <filename>/etc/rc.d/</filename> 下的脚本是属于 + <quote>基本</quote> 系统一部分的应用程序所使用的。 + (&man.cron.8;,&man.sshd.8;,&man.syslog.3;,以及其他。) + <filename>/usr/local/etc/rc.d/</filename> + 下的脚本是用户安装的应用程序如 <application>Apache</application>, + <application>Squid</application> 等使用的。</para> + + <para><emphasis><quote>基本</quote> + 系统和用户安装的应用程序之间的区别是什么?</emphasis> FreeBSD + 是一套开发出来的完整的操作系统,也就是说,内核,系统类库, + 还有实用应用程序(如 &man.ls.1;,&man.cat.1;,&man.cp.1; 等) + 全部被做为一个整体一起开发并释出。这就是被认为归属于 + <quote>基本</quote>系统的程序。用户安装的程序并不是 + <quote>基本</quote>系统的一部分,如 + <application>Apache</application>,<application>X11</application>, + <application>Moazilla Firefox</application>,等等。这 + 些用户安装的应用程序通常是使用 &os; 的 Packages 和 Ports + 套件安装上去的。为了将这些程序和 <quote>基本</quote> + 系统区分开来,用户安装的应用程序通常被安装到 + <filename>/usr/local/</filename>下。 + 因此用户安装的二进制执行文件存在于 + <filename>/usr/local/bin</filename>下,配置文件在 + <filename>/usr/local/etc</filename>下,以此类推。</para> + </sidebar> + + <para>您可以通过在 + <filename>/etc/rc.conf</filename>(&man.rc.conf.5;) + 文件中增加与之对应的 + <literal>ServiceName_enable="YES"</literal> + 配置来启用服务。 看一下系统默认的 + <filename>/etc/defaults/rc.conf</filename> 文件, 这些默认配置可以使用 + <filename>/etc/rc.conf</filename> 文件来改变。 因此, + 当安装附加应用程序时最好回顾下文档来决定到底该如何启用任何相关的服务。</para> + + <para>下面的一小段内容用来在 <filename>/etc/rc.conf</filename> + 中启用 &man.sshd.8; 和 <application>Apache 2.2</application>。 + 还指定了 <application>Apache</application> 应该通过 + SSL 方式启动。</para> + + <programlisting># enable SSHD +sshd_enable="YES" +# enable Apache with SSL +apache22_enable="YES" +apache22_flags="-DSSL"</programlisting> + + <para>一旦服务已经在 <filename>/etc/rc.conf</filename> + 中启用,服务将能够从命令行启动(不需要重新启动系统):</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/sshd start</userinput></screen> + + <para>如果服务还没有被启用,可以使用 + <option>forcestart</option> 来从命令行启动:</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/sshd forcestart</userinput></screen> + </sect1> + + <sect1 xml:id="network"> + <title>网络配置</title> + + <sect2 xml:id="interfaces"> + <title>网络接口</title> + + <para>代替 &linux; 中所使用的标识网络接口所常用的 + <emphasis>ethX</emphasis> 格式的是,&os; + 使用驱动名字后跟一个数字来标识。下面 + &man.ifconfig.8; 的输出显示了两个 &intel; Pro 1000 + 的网络接口(<filename>em0</filename> 和 <filename>em1</filename>):</para> + + <screen>&prompt.user; <userinput>ifconfig</userinput> +em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500 + options=b<RXCSUM,TXCSUM,VLAN_MTU> + inet 10.10.10.100 netmask 0xffffff00 broadcast 10.10.10.255 + ether 00:50:56:a7:70:b2 + media: Ethernet autoselect (1000baseTX <full-duplex>) + status: active +em1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500 + options=b<RXCSUM,TXCSUM,VLAN_MTU> + inet 192.168.10.222 netmask 0xffffff00 broadcast 192.168.10.255 + ether 00:50:56:a7:03:2b + media: Ethernet autoselect (1000baseTX <full-duplex>) + status: active</screen> + </sect2> + + <sect2 xml:id="ipaddress"> + <title>IP配置</title> + + <para>一个 IP 地址可以使用 &man.ifconfig.8; + 来指定到一个网络接口。通常,要保持重启后依然能够使用的 + IP 配置信息需要包含在 <filename>/etc/rc.conf</filename> + 中。下列例子指定了主机名,IP 地址,以及默认网关:</para> + + <programlisting>hostname="server1.example.com" +ifconfig_em0="inet 10.10.10.100 netmask 255.255.255.0" +defaultrouter="10.10.10.1"</programlisting> + + <para>使用下面内容来为网络接口配置DHCP:</para> + + <programlisting>hostname="server1.example.com" +ifconfig_em0="DHCP"</programlisting> + + </sect2> + </sect1> + + <sect1 xml:id="firewall"> + <title>防火墙</title> + + <para>像 &linux; 中的 <application>IPTABLES</application> + 一样, &os; 也提供了一个内核级的防火墙; + 实际上 &os; 提供了三个防火墙:</para> + + <itemizedlist> + <listitem><simpara><link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/firewalls-ipfw.html">IPFIREWALL</link></simpara></listitem> + <listitem><simpara><link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/firewalls-ipf.html">IPFILTER</link></simpara></listitem> + <listitem><simpara><link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/firewalls-pf.html">PF</link></simpara></listitem> + </itemizedlist> + + <para><application>IPFIREWALL</application> 或是 + <application>IPFW</application>(管理 + <application>IPFW</application> 规则的 &man.ipfw.8; 命令) + 是 &os; 开发者开发并维持的。 + <application>IPFW</application> 能够与 &man.dummynet.4; + 配合使用来提供流量图形功能以及模拟不同网络连接类型的功能。</para> + + <para>允许 <application>SSH</application> + 进入的 <application>IPFW</application> 规则样例如下:</para> + + <programlisting>ipfw add allow tcp from any to me 22 in via $ext_if</programlisting> + + <para><application>IPFILTER</application> 是 + Darren Reed 所开发的防火墙程序。不是专门针对 + &os; 的,它已经被移植到 NetBSD,OpenBSD,SunOS,HP/UX, + 还有Solaris等一些操作系统之上。</para> + + <para>允许 <application>SSH</application> + 进入的 <application>IPFILTER</application> + 命令样例如下:</para> + + <programlisting>pass in on $ext_if proto tcp from any to any port = 22</programlisting> + + <para>最后一种防火墙程序,<application>PF</application>, + 是 OpenBSD 项目所开发的。<application>PF</application> + 是被作为 <application>IPFILTER</application> + 的一个替代品而被创建出的。就这点而言, + <application>PF</application> 的语法与 + <application>IPFILTER</application> 的非常相似。 + <application>PF</application> 可以与 &man.altq.4; + 配合来提供 QoS 的特性。</para> + + <para>允许 <application>SSH</application> + 进入的 <application>PF</application> + 命令样例如下:</para> + + <programlisting>pass in on $ext_if inet proto tcp from any to ($ext_if) port 22</programlisting> + </sect1> + + <sect1 xml:id="updates"> + <title>升级 &os;</title> + + <para>共有三种方法来升级 &os; 系统: + 源码,二进制更新,还有安装光盘。</para> + + <para>从源码升级是最复杂的升级方法,但是提供了最棒的总体灵活性。 + 这个过程包含了使用 &os; <application>CVS</application> + (并行版本系统)来同步一个本地的 &os; 源代码。 + 一旦本地源码已经更新到当前最新你便可以构建新版本的内核以及应用程序。 + 关于源码更新的更多信息可见于 &os; 手册 + <link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/cutting-edge.html"> + 关于如何更新操作系统的章节</link>。</para> + + <para>二进制更新类似于使用 <command>yum</command> 或 + <command>apt-get</command> 更新 &linux; 系统。 + &man.freebsd-update.8; 命令会获取新的更新并安装它们。 + 这些更新可以通过 &man.cron.8; 使用程序来调度。</para> + + <note> + <para>如果你使用 &man.cron.8; 来预定更新, + 请确信在你的 &man.crontab.1; 中使用了 + <command>freebsd-update cron</command> + 来控制大数目的机器同时获取更新。</para> + + <programlisting>0 3 * * * root /usr/sbin/freebsd-update cron</programlisting> + </note> + + <para>最后一种更新的方法,从安装光盘来升级,是个直接的过程。 + 从安装光盘启动并选择该选项来更新。</para> + </sect1> + + <sect1 xml:id="procfs"> + <title>procfs:已是过去式但仍未被遗忘</title> + + <para>&linux; 中,你可能会通过看一看 + <filename>/proc/sys/net/ipv4/ip_forward</filename> + 来确定 IP 转发是否被启用。在 &os; 中你应该使用 &man.sysctl.8; + 来查看这和其他方面的系统设置,在当前的 &os; 版本中 + &man.procfs.5; 已经不赞成使用了。(虽然 + <command>sysctl</command>在 &os; 也同样可用。)</para> + + <para>在 IP 转发样例中,你应该使用下列内容来确定 &os; + 系统中是否已经开启了 IP 转发:</para> + + <screen>&prompt.user; <userinput>sysctl net.inet.ip.forwarding</userinput> +net.inet.ip.forwarding: 0</screen> + + <para><option>-a</option> 标志用来列出所有的系统设置:</para> + + <screen>&prompt.user; <userinput>sysctl -a</userinput> +kern.ostype: FreeBSD +kern.osrelease: 6.2-RELEASE-p9 +kern.osrevision: 199506 +kern.version: FreeBSD 6.2-RELEASE-p9 #0: Thu Nov 29 04:07:33 UTC 2007 + root@i386-builder.daemonology.net:/usr/obj/usr/src/sys/GENERIC + +kern.maxvnodes: 17517 +kern.maxproc: 1988 +kern.maxfiles: 3976 +kern.argmax: 262144 +kern.securelevel: -1 +kern.hostname: server1 +kern.hostid: 0 +kern.clockrate: { hz = 1000, tick = 1000, profhz = 666, stathz = 133 } +kern.posix1version: 200112 +...</screen> + + <note> + <para>某些 <command>sysctl</command> 的参数是只读的。</para> + </note> + + <para>需要 procfs 的情况是,运行一些较老的软件,使用 + &man.truss.1; 来跟踪系统信号,以及 + <link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/linuxemu.html">&linux; 二进制兼容</link>. + (尽管,&linux; 二进制兼容性使用其本身的 procfs,&man.linprocfs.5;。) + 如果你需要挂载 procfs 你可以在 + <filename>/etc/fstab</filename> 中加入如下内容:</para> + + <screen>proc /proc procfs rw,noauto 0 0</screen> + + <note> + <para><option>noauto</option> 会防止 + <filename>/proc</filename> 在启动时被自动挂载。</para> + </note> + + <para>然后使用如下命令挂载 procfs:</para> + + <screen>&prompt.root; <userinput>mount /proc</userinput></screen> + </sect1> + + <sect1 xml:id="commands"> + <title>常用命令</title> + + <sect2 xml:id="packageCommands"> + <title>软件包管理</title> + + <para> + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + <thead> + <row> + <entry>&linux; 命令 (Red Hat/Debian)</entry> + <entry>&os; 等价命令</entry> + <entry>目的</entry> + </row> + </thead> + + <tbody> + <row> + <entry><command>yum install package</command> / <command>apt-get install package</command></entry> + <entry><command>pkg_add -r package</command></entry> + <entry>从远程仓库安装 <replaceable>package</replaceable></entry> + </row> + + <row> + <entry><command>rpm -ivh package</command> / <command>dpkg -i package</command></entry> + <entry><command>pkg_add -v package</command></entry> + <entry>安装 package</entry> + </row> + + <row> + <entry><command>rpm -qa</command> / <command>dpkg -l</command></entry> + <entry><command>pkg_info</command></entry> + <entry>列出已安装的软件包</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </para> + </sect2> + + <sect2 xml:id="systemCommands"> + <title>系统管理</title> + + <para> + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + <thead> + <row> + <entry>&linux; 命令</entry> + <entry>&os; 等价命令</entry> + <entry>目的</entry> + </row> + </thead> + + <tbody> + <row> + <entry><command>lspci</command></entry> + <entry><command>pciconf</command></entry> + <entry>列出 PCI 设备</entry> + </row> + + <row> + <entry><command>lsmod</command></entry> + <entry><command>kldstat</command></entry> + <entry>列出已载入的内核模块</entry> + </row> + + <row> + <entry><command>modprobe</command></entry> + <entry><command>kldload</command> / <command>kldunload</command></entry> + <entry>载入/卸载内核模块</entry> + </row> + + <row> + <entry><command>strace</command></entry> + <entry><command>truss</command></entry> + <entry>跟踪系统调用</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </para> + </sect2> + </sect1> + + <sect1 xml:id="conclusion"> + <title>总结</title> + + <para>非常希望这篇文档能够给予你足够的帮助来开始你的 + &os; 之路。务必要再去看一下 <link xlink:href="&url.base;/doc/en_US.ISO8859-1/books/handbook/index.html">&os; 手册</link> + 所提到的但并没有被包含在本文档中的那些更深入广泛的主题。</para> + </sect1> +</article> diff --git a/zh_CN.UTF-8/articles/nanobsd/Makefile b/zh_CN.UTF-8/articles/nanobsd/Makefile new file mode 100644 index 0000000000..dcb5195f26 --- /dev/null +++ b/zh_CN.UTF-8/articles/nanobsd/Makefile @@ -0,0 +1,28 @@ +# +# The FreeBSD Simplified Chinese Project +# +# Original Revision: 1.2 +# $FreeBSD$ +# +# Article: Introduction to NanoBSD + +DOC?= article + +FORMATS?= html +WITH_ARTICLE_TOC?= YES + +INSTALL_COMPRESSED?= gz +INSTALL_ONLY_COMPRESSED?= + +# Images from the cross-document image library +IMAGES_LIB= callouts/1.png +IMAGES_LIB+= callouts/2.png +IMAGES_LIB+= callouts/3.png +IMAGES_LIB+= callouts/4.png + +SRCS= article.xml + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. + +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/nanobsd/article.xml b/zh_CN.UTF-8/articles/nanobsd/article.xml new file mode 100644 index 0000000000..c2f7554c5e --- /dev/null +++ b/zh_CN.UTF-8/articles/nanobsd/article.xml @@ -0,0 +1,468 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd"> +<!-- + The FreeBSD Documentation Project + The FreeBSD Chinese (Simplified) Documentation Project + + Original Revision: 1.7 +--> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>NanoBSD 简介</title> + + + <authorgroup> + <author><personname><firstname>Daniel</firstname><surname>Gerzo</surname></personname></author> + </authorgroup> + + <copyright> + <year>2006</year> + <holder>The FreeBSD Documentation Project</holder> + </copyright> + + <pubdate>$FreeBSD$</pubdate> + + <releaseinfo>$FreeBSD$</releaseinfo> + + <legalnotice xml:id="trademarks" role="trademarks"> + &tm-attrib.freebsd; + &tm-attrib.general; + </legalnotice> + + <abstract> + <para>这篇文档提供了关于 <application>NanoBSD</application> 工具的介绍信息, + 这一工具可以用来创建用于嵌入式应用的 &os; 系统映像, + 以适应存放到袖珍闪存 (Compact Flash) 卡 (或其它大容量存储介质) 上的需要。</para> + </abstract> + </info> + + <sect1 xml:id="intro"> + <title>NanoBSD 简介</title> + + <indexterm><primary>NanoBSD</primary></indexterm> + + <para><application>NanoBSD</application> 是 &a.phk; 目前正在开发的一项工具。 + 它可以用来创建用于嵌入式应用的 &os; 系统映像, + 以便配合袖珍闪存 (Compact Flash) 卡 (或其他大容量存储介质) 使用。</para> + + <para>这一工具也可以用来构建定制的安装映像, + 以简化通常称为 <quote>计算设备 (computer appliances)</quote> + 的系统的安装和维护工作。 计算设备通常在产品中将捆绑硬件和软件, + 或者换言之, 所有的应用程序都是预先装好的。 + 这些设备可以直接插到暨存的网络中, + 并 (几乎是) 立即投入使用。</para> + + <para><application>NanoBSD</application> 提供的功能包括:</para> + + <itemizedlist> + <listitem> + <para>可以和 &os; 一样使用 Ports 和预编译包— + 所有的应用程序都可以在 <application>NanoBSD</application> 映像中直接使用, + 而方式与 &os; 完全一样。</para> + </listitem> + + <listitem> + <para>不减少功能 — 能够使用 &os; 做的任何工作, 都可以在 + <application>NanoBSD</application> 中使用, + 除非您在创建 <application>NanoBSD</application> 映像时, + 明确地删去它们。</para> + </listitem> + + <listitem> + <para>所有对象在运行时均是只读的 — 可以安全地拔掉电源插销。 + 在系统非正常关闭之后, 无需运行 + &man.fsck.8;。</para> + </listitem> + + <listitem> + <para>便于联编和定制 — 只需使用一个 shell 脚本和一个配置文件, + 您可以很容易地裁减和定制适于任意需求的映像。</para> + </listitem> + </itemizedlist> + </sect1> + + <sect1 xml:id="howto"> + <title>如何使用 NanoBSD</title> + + <sect2 xml:id="design"> + <title>NanoBSD 的设计</title> + + <para>一旦将映像存入介质, 就可以用它来引导 <application>NanoBSD</application> + 了。 默认情况下, 大容量存储器会划分为三个区:</para> + + <itemizedlist> + <listitem> + <para>两个映像区: <literal>code#1</literal> + 和 <literal>code#2</literal>。</para> + </listitem> + + <listitem> + <para>一个配置文件区, 运行环境中, + 可以将其挂接到 <filename>/cfg</filename> 目录下。</para> + </listitem> + </itemizedlist> + + <para>这些分区默认情况下以只读方式挂接。</para> + + <para><filename>/etc</filename> 和 + <filename>/var</filename> 目录均为 + &man.md.4; (malloc) 盘。</para> + + <para>配置文件分区保存在 + <filename>/cfg</filename> 目录。 + 它包含了用于 <filename>/etc</filename> + 目录的文件, 在启动之后暂时以只读方式挂接。 因此, + 在需要从 <filename>/etc</filename> 向 + <filename>/cfg</filename> 目录复制所进行的、 + 希望在重启时保持不变的配置时, 需要进行一些额外的操作。</para> + + <example> + <title>在 <filename>/etc/resolv.conf</filename> 中进行需要保持的修改</title> + + <screen>&prompt.root; <userinput>vi /etc/resolv.conf</userinput> +[...] +&prompt.root; <userinput>mount /cfg</userinput> +&prompt.root; <userinput>cp /etc/resolv.conf /cfg</userinput> +&prompt.root; <userinput>umount /cfg</userinput></screen> + </example> + + <note> + <para>只有在系统启动过程中, 以及需要修改配置文件的场合, 才需要挂接包含 + <filename>/cfg</filename> 的那个分区。</para> + + <para>在任何时候都保持挂接 <filename>/cfg</filename> + 不是一个好主意, 特别是当您把 <application>NanoBSD</application> + 放在不适合进行大量写操作的分区时 + (由于文件系统的同步进程会定期向系统盘写一些数据)。</para> + </note> + </sect2> + + <sect2> + <title>构建 NanoBSD 映像</title> + + <para><application>NanoBSD</application> 映像是通过使用非常简单的 + <filename>nanobsd.sh</filename> shell 脚本来构建的, 这个脚本可以在 + <filename>/usr/src/tools/tools/nanobsd</filename> + 目录中找到。 这个脚本建立的映像文件, 可以用 &man.dd.1; 工具复制到存储介质上。</para> + + <para>构建 + <application>NanoBSD</application> 映像所需的命令是:</para> + + <screen>&prompt.root; <userinput>cd /usr/src/tools/tools/nanobsd</userinput> <co xml:id="nbsd-cd"/> +&prompt.root; <userinput>sh nanobsd.sh</userinput> <co xml:id="nbsd-sh"/> +&prompt.root; <userinput>cd /usr/obj/nanobsd.full</userinput> <co xml:id="nbsd-cd2"/> +&prompt.root; <userinput>dd if=_.disk.full of=/dev/da0 bs=64k</userinput> <co xml:id="nbsd-dd"/></screen> + + <calloutlist> + <callout arearefs="nbsd-cd"> + <para>进入 <application>NanoBSD</application> 构建脚本的主目录。</para> + </callout> + + <callout arearefs="nbsd-sh"> + <para>开始构建过程。</para> + </callout> + + <callout arearefs="nbsd-cd2"> + <para>进入构建好的映像文件所在的目录。</para> + </callout> + + <callout arearefs="nbsd-dd"> + <para>在存储介质上安装 <application>NanoBSD</application>。</para> + </callout> + </calloutlist> + </sect2> + + <sect2> + <title>定制 NanoBSD 映像</title> + + <para>这可能是 <application>NanoBSD</application> 最为重要, + 同时也是您最感兴趣的功能。 同时, 您在开发 + <application>NanoBSD</application> 应用时, + 这也是相当耗时的过程。</para> + + <para>执行下面的命令将使 + <filename>nanobsd.sh</filename> 从当前目录中的 + <filename>myconf.nano</filename> 文件读取配置:</para> + + <screen>&prompt.root; <userinput>sh nanobsd.sh -c myconf.nano</userinput></screen> + + <para>定制过程包含两步:</para> + + <itemizedlist> + <listitem> + <para>配置选项</para> + </listitem> + + <listitem> + <para>定制函数</para> + </listitem> + </itemizedlist> + + <sect3> + <title>配置选项</title> + + <para>通过对配置进行设置, 可以配置用以传递给 + <application>NanoBSD</application> 构建过程中 + <buildtarget>buildworld</buildtarget> + 和 <buildtarget>installworld</buildtarget> 阶段的联编和安装选项, 以及 + <application>NanoBSD</application> 的主构建过程中的选项。 + 通过使用这些选项可以削减系统的尺寸, 使之能够放入 + 64MB 的存储。 您还可以进一步通过这些选项来削减 &os;, + 直到它只包含内核以及两三个用户环境文件为止。</para> + + <para>配置文件中包含用以代替默认值的配置选项。 + 最重要的语句包括:</para> + + <itemizedlist> + <listitem> + <para><literal>NANO_NAME</literal> — 本次构建的名称 + (用于创建工作目录的名字)。</para> + </listitem> + + <listitem> + <para><literal>NANO_SRC</literal> — 用以联编和构建映像的源码树的位置。</para> + </listitem> + + <listitem> + <para><literal>NANO_KERNEL</literal> — 用以联编内核的配置文件的名字。</para> + </listitem> + + <listitem> + <para><literal>CONF_BUILD</literal> — 用于传递给 + <buildtarget>buildworld</buildtarget> 构建阶段的选项。</para> + </listitem> + + <listitem> + <para><literal>CONF_INSTALL</literal> — 用于传递给 + <buildtarget>installworld</buildtarget> 构建阶段的选项。</para> + </listitem> + + <listitem> + <para><literal>CONF_WORLD</literal> — 用以传递给 + <buildtarget>buildworld</buildtarget> 和 + <buildtarget>installworld</buildtarget> 这两个构建阶段的选项。</para> + </listitem> + + <listitem> + <para><literal>FlashDevice</literal> — 定义所用的介质类型。 + 要了解进一步的细节, 请参考 <filename>FlashDevice.sub</filename> + 文件。</para> + </listitem> + </itemizedlist> + </sect3> + + <sect3> + <title>定制函数</title> + + <para>通过在配置文件中使用 shell 函数可以进一步微调 + <application>NanoBSD</application>。 下面的例子展示了定制函数的基本模式:</para> + + <programlisting>cust_foo () ( + echo "bar=baz" > \ + ${NANO_WORLDDIR}/etc/foo +) +customize_cmd cust_foo</programlisting> + + <para>下面是一个更贴近实际的例子, 它将默认的 + <filename>/etc</filename> 目录尺寸, + 从 5MB 调整为 30MB:</para> + + <programlisting>cust_etc_size () ( + cd ${NANO_WORLDDIR}/conf + echo 30000 > default/etc/md_size +) +customize_cmd cust_etc_size</programlisting> + + <para>除此之外, 还有几个默认的预定义定制函数:</para> + + <itemizedlist> + <listitem> + <para><literal>cust_comconsole</literal> — 在 VGA 设备上禁止 + &man.getty.8; + (<filename>/dev/ttyv*</filename> 设备节点) 并启用串口 COM1 + 作为系统控制台。</para> + </listitem> + + <listitem> + <para><literal>cust_allow_ssh_root</literal> — 允许 + <systemitem class="username">root</systemitem> 通过 &man.sshd.8; 登录。</para> + </listitem> + + <listitem> + <para><literal>cust_install_files</literal> — + 从 <filename>nanobsd/Files</filename> + 目录中安装文件, 这包含一些实用的系统管理脚本。</para> + </listitem> + </itemizedlist> + </sect3> + + <sect3> + <title>安装预编译软件包</title> + + <para>通过增加自定义的函数, 可以在 <application>NanoBSD</application> + 增加预编译的软件包。 下面的函数会添加位于 + <filename>/usr/src/tools/tools/nanobsd/packages</filename> + 的全部预编译软件包:</para> + + <programlisting>install_packages () ( +mkdir -p ${NANO_WORLDDIR}/packages +cp /usr/src/tools/tools/nanobsd/packages/* ${NANO_WORLDDIR}/packages +chroot ${NANO_WORLDDIR} sh -c 'cd packages; pkg_add -v *;cd ..;' +rm -rf ${NANO_WORLDDIR}/packages +) +customize_cmd install_packages</programlisting> + </sect3> + + <sect3> + <title>配置文件举例</title> + + <para>下面是一个用于构建定制的 <application>NanoBSD</application> 映像的完整例子:</para> + + <programlisting>NANO_NAME=custom +NANO_SRC=/usr/src +NANO_KERNEL=MYKERNEL +NANO_IMAGES=2 + +CONF_BUILD=' +NO_KLDLOAD=YES +NO_NETGRAPH=YES +NO_PAM=YES +' + +CONF_INSTALL=' +NO_ACPI=YES +NO_BLUETOOTH=YES +NO_CVS=YES +NO_FORTRAN=YES +NO_HTML=YES +NO_LPR=YES +NO_MAN=YES +NO_SENDMAIL=YES +NO_SHAREDOCS=YES +NO_EXAMPLES=YES +NO_INSTALLLIB=YES +NO_CALENDAR=YES +NO_MISC=YES +NO_SHARE=YES +' + +CONF_WORLD=' +NO_BIND=YES +NO_MODULES=YES +NO_KERBEROS=YES +NO_GAMES=YES +NO_RESCUE=YES +NO_LOCALES=YES +NO_SYSCONS=YES +NO_INFO=YES +' + +FlashDevice SanDisk 1G + +cust_nobeastie() ( + touch ${NANO_WORLDDIR}/boot/loader.conf + echo "beastie_disable=\"YES\"" >> ${NANO_WORLDDIR}/boot/loader.conf +) + +customize_cmd cust_comconsole +customize_cmd cust_install_files +customize_cmd cust_allow_ssh_root +customize_cmd cust_nobeastie</programlisting> + </sect3> + </sect2> + + <sect2> + <title>更新 NanoBSD</title> + + <para>更新 <application>NanoBSD</application> 相对而言较为简单:</para> + + <procedure> + <step> + <para>和之前一样构建新的 <application>NanoBSD</application> 映像文件。</para> + </step> + + <step> + <para>将新的映像放入正运行的 + <application>NanoBSD</application> 设备中的一个未用的分区。</para> + + <para>与之前最初安装 <application>NanoBSD</application> 的步骤相比, + 这一步骤最重要的区别在于这次不应使用 <filename>_.disk.full</filename> 文件 + (它包含整个盘的映像), + 而应安装 <filename>_.disk.image</filename> 映像 (这个文件中, + 只包含一个系统分区)。</para> + </step> + + <step> + <para>重新启动, 并从新安装的分区中启动系统。</para> + </step> + + <step> + <para>如果一切顺利的话, 升级工作就完成了。</para> + </step> + + <step> + <para>如果发生了任何问题, 则可以从先前的分区启动 + (其中包含了旧的、 可用的映像), 来尽可能快地恢复系统功能。 + 接下来可以修正新联编的版本中存在的问题, 并重复前述步骤。</para> + </step> + </procedure> + + <para>要在正在运行的 + <application>NanoBSD</application> 系统中安装新的映像, 可以使用位于 + <filename>/root</filename> 目录的 + <filename>updatep1</filename> 或 + <filename>updatep2</filename> 脚本, + 具体使用哪一个脚本, 取决于正在运行的系统位于那个分区。</para> + + <para>随时提供新 <application>NanoBSD</application> 映像所提供的服务, + 以及采用的传输方法的不同, 您可以参考并使用下列三种方式之一:</para> + + <sect3> + <title>使用 &man.ftp.1;</title> + + <para>如果传输速度是第一要务, + 采用下面的例子:</para> + + <screen>&prompt.root; <userinput>ftp myhost +get _.disk.image "| sh updatep1"</userinput></screen> + </sect3> + + <sect3> + <title>使用 &man.ssh.1;</title> + + <para>如果更倾向于安全传输, 应参考下面的例子:</para> + + <screen>&prompt.root; <userinput>ssh myhost cat _.disk.image.gz | zcat | sh updatep1</userinput></screen> + </sect3> + + <sect3> + <title>使用 &man.nc.1;</title> + + <para>如果远程主机既不提供 + &man.ftp.1; 服务, 也不提供 &man.sshd.8; 服务:</para> + + <procedure> + <step> + <para>开始时, 在提供映像的主机上开启 TCP 监听, + 并令其将映像文件发给客户机:</para> + + <screen>myhost&prompt.root; <userinput>nc -l 2222 < _.disk.image</userinput></screen> + + <note> + <para>请确认您所使用的端口没有通过防火墙阻止来自 + <application>NanoBSD</application> 客户机的联接请求。</para> + </note> + </step> + <step> + <para>连接到提供新映像服务的主机, 并执行 + <filename>updatep1</filename> 脚本:</para> + + <screen>&prompt.root; <userinput>nc myhost 2222 | sh updatep1</userinput></screen> + </step> + </procedure> + </sect3> + </sect2> + </sect1> + + <index/> +</article> diff --git a/zh_CN.UTF-8/articles/rc-scripting/Makefile b/zh_CN.UTF-8/articles/rc-scripting/Makefile new file mode 100644 index 0000000000..6ac2a1559d --- /dev/null +++ b/zh_CN.UTF-8/articles/rc-scripting/Makefile @@ -0,0 +1,33 @@ +# +# $FreeBSD$ +# Original Revision: 1.2 +# Article: Practical rc.d scripting in BSD + +DOC?= article + +FORMATS?= html +WITH_ARTICLE_TOC?= YES + +INSTALL_COMPRESSED?= gz +INSTALL_ONLY_COMPRESSED?= + +SRCS= article.xml + +IMAGES_LIB+= callouts/1.png +IMAGES_LIB+= callouts/2.png +IMAGES_LIB+= callouts/3.png +IMAGES_LIB+= callouts/4.png +IMAGES_LIB+= callouts/5.png +IMAGES_LIB+= callouts/6.png +IMAGES_LIB+= callouts/7.png +IMAGES_LIB+= callouts/8.png +IMAGES_LIB+= callouts/9.png +IMAGES_LIB+= callouts/10.png +IMAGES_LIB+= callouts/11.png +IMAGES_LIB+= callouts/12.png +IMAGES_LIB+= callouts/13.png +IMAGES_LIB+= callouts/14.png + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/rc-scripting/article.xml b/zh_CN.UTF-8/articles/rc-scripting/article.xml new file mode 100644 index 0000000000..8714b75920 --- /dev/null +++ b/zh_CN.UTF-8/articles/rc-scripting/article.xml @@ -0,0 +1,1219 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd"> +<!-- + The FreeBSD Documentation Project + The FreeBSD Chinese (Simplified) Documentation Project + + Original Revision: 1.14 +--> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>BSD rc.d脚本编程实战</title> + + + <author><personname><firstname>Yar</firstname><surname>Tikhiy</surname></personname><affiliation> + <address><email>yar@FreeBSD.org</email></address> + </affiliation></author> + + <copyright> + <year>2005</year> + <year>2006</year> + <year>2012</year> + + <holder>The FreeBSD Project</holder> + </copyright> + + <pubdate>$FreeBSD$</pubdate> + + <releaseinfo>$FreeBSD$</releaseinfo> + + <legalnotice xml:id="trademarks" role="trademarks"> + &tm-attrib.freebsd; + &tm-attrib.netbsd; + &tm-attrib.general; + </legalnotice> + + <abstract> + <para>初学者可能会发现,难以通过正式的文档, + 基于 BSD 的 <filename>rc.d</filename> + 框架,编写一些实际任务的 <filename>rc.d</filename> 脚本。 + 本文中,我们采用了一些复杂性不断增加的典型案例, + 来展示适合每个案例的 <filename>rc.d</filename> 特性, + 并探讨其中的工作原理。 + 这样的实验为大家进一步研究设计有效的 + <filename>rc.d</filename> 应用程序提供了一些参考点。</para> + </abstract> + </info> + + <sect1 xml:id="rcng-intro"> + <title>简介</title> + + <para>历史上 BSD 曾有过一个单一的启动脚本, + <filename>/etc/rc</filename>。 该脚本在系统启动的时候被 + &man.init.8; 程序所引导,并执行所有多用户操作所需求的用户级任务: + 检查并挂载文件系统,设置网络,启动守护进程,等等。 + 在每个系统中实际的任务清单也并不相同; + 管理员需要根据需求自定义这样的任务清单。在一些特殊的情况中, + 还不得不去修改 <filename>/etc/rc</filename> 文件, + 一些真正的黑客乐此不疲。</para> + + <para>单一脚本启动方法的真正问题是它没有提供对从 + <filename>/etc/rc</filename> 启动的单个组件的控制。 + 拿一个例子来说吧,<filename>/etc/rc</filename> + 不能够重新启动某个单独的守护进程。 + 系统管理员不得不手动找出守护进程,并杀掉它, + 等待它真正退出后,再通过浏览 <filename>/etc/rc</filename> + 得到该守护进程的标识,最终输入全部命令来再次启动守护进程。 + 如果重新启动的服务包括不止一个守护进程或需要更多动作的话, + 该任务将变得更加困难以及容易出错。简而言之, + 单一脚本在实现我们这样的目的上是不成功的: + 让系统管理员的生活更轻松。</para> + + <para>再后来,为了将最重要的一些子系统独立出来, + 便尝试将部分的内容从 <filename>/etc/rc</filename> 分离出来了。 + 最广为人知的例子就是用来启动联网的 <filename>/etc/netstart</filename> + 文件。它容许从单用户模式访问网络, + 但由于它的部分代码需要和一些与联网完全无关的动作交互, + 所以它并没有完美地结合到自启动的进程中。那便是为何 + <filename>/etc/netstart</filename> 被演变成 + <filename>/etc/rc.network</filename> 的原因了。 + 后者不再是一个普通的脚本;它包括了庞大的,由 + <filename>/etc/rc</filename> 在不同的系统启动级别中调用的凌乱的 + &man.sh.1; 函数。然而,当启动任务变得多样化以及久经更改, + <quote>类模块化</quote> 方法变得比曾经的整体 + <filename>/etc/rc</filename> 更缓慢费事。</para> + + <para>由于没有一个干净和易于设计的框架, + 启动脚本不得不全力更改以满足飞速开发中基于 BSD 的操作系统的需求。 + 它逐渐变得明朗并经过许多必要的步骤最终变成一个具有细密性和扩展性的 + <filename>rc</filename> 系统。BSD <filename>rc.d</filename> + 就这样诞生了。Luke Mewburn 和 NetBSD 社区是公认的 + <filename>rc.d</filename> 之父。再之后它被引入到了 &os; 中。 + 它的名字引用为系统单独的服务脚本的位置,也就是 + <filename>/etc/rc.d</filename>下面的那些脚本。 + 之后我们将学习到更多的 <filename>rc.d</filename> + 系统的组件并看看单个脚本是如何被调用的。</para> + + <para>BSD <filename>rc.d</filename> + 背后的基本理念是 <emphasis>良好</emphasis> 的模块化和代码重用性。 + <emphasis>良好</emphasis> 的模块化意味着每个基本 + <quote>服务</quote> 就象系统守护进程或原始启动任务那样, + 通过属于它们的可启动该服务的 &man.sh.1; 脚本,来停止服务, + 重载服务,检查服务的状态。具体动作由脚本的命令行参数所决定。 + <filename>/etc/rc</filename> 脚本仍然掌管着系统的启动, + 但现在它仅仅是使用 <option>start</option> 参数来一个个调用那些小的脚本。 + 这便于用 <option>stop</option> 来对运行中的同样的脚本很好地执行停止任务, + 这是被 <filename>/etc/rc.shutdown</filename> + 脚本所完成的。看,这是多么好地体现了 Unix 的哲学: + 拥有一组小的专用的工具,每个工具尽可能好地完成自己的任务。 + <emphasis>代码重用</emphasis> 意味着所有的通用操作由 + <filename>/etc/rc.subr</filename> 中的一些 &man.sh.1; 函数所实现。 + 现在一个典型的脚本只需要寥寥几行的 &man.sh.1; 代码。最终, + &man.rcorder.8; 成为了 <filename>rc.d</filename> 框架中重要的一部分, + 它用来帮助 <filename>/etc/rc</filename> + 处理小脚本之间的依赖关系并有次序地运行它们。它同样帮助 + <filename>/etc/rc.shutdown</filename> 做类似的事情, + 因为正确的关闭次序是相对于启动的次序的。</para> + + <para>BSD <filename>rc.d</filename> 的设计在 + <link linkend="lukem"> Luke Mewburn 的原文 </link> 中有记录, + 以及 <filename>rc.d</filename> 组件也被充分详细地记录在各自的 + <link linkend="manpages">联机手册</link> 中。然而, + 它可能没能清晰展现给一个 <filename>rc.d</filename> + 新手,如何将无数的块和片进行关联来为具体的任务创建一个好风格的脚本。 + 因此本文将试着以不同的方式来讲述 <filename>rc.d</filename>。 + 它将展示在某些典型情况中应该使用哪些特性,并阐述了为何如此。 + 注意这并不是一篇 how-to 文档,我们的目的不是给出现成的配方, + 而是在展示一些简单的进入 <filename>rc.d</filename> 的范围的门路。 + 本文也不是相关联机手册的替代品。 + 阅读本文时记得同时参考联机手册以获取更完整正规的文档。</para> + + <para>理解本文需要一些先决条件。首先,你需要熟悉 + &man.sh.1; 脚本编程语言以掌握 <filename>rc.d</filename>, + 还有,你需要知道系统是如何执行用户级的启动和停止任务,这些在 + &man.rc.8; 中都有说明。</para> + + <para>本文关注的是 <filename>rc.d</filename> 的 &os; 分支。 + 不过,它可能对 NetBSD 的开发者也同样有用,因为 BSD + <filename>rc.d</filename> 的两个分支不只是共享了同样的设计, + 还保留了对脚本编写者都可见的类似观点。</para> + </sect1> + + <sect1 xml:id="rcng-task"> + <title>任务描述</title> + + <para>在开始打开 <envar>$EDITOR</envar>(编辑器) + 之前进行小小的思考不是坏事。为了给一个系统服务写一个 + <quote>听话的</quote> <filename>rc.d</filename> 脚本, + 我们首先应该能回答以下问题:</para> + + <itemizedlist> + <listitem> + <para>该服务是必须性的还是可选性的?</para> + </listitem> + + <listitem> + <para>脚本将为单个程序服务,如一个守护进程,还是执行更复杂的动作?</para> + </listitem> + + <listitem> + <para>我们的服务依赖哪些服务?反过来哪些服务依赖我们的服务?</para> + </listitem> + </itemizedlist> + + <para>从下面的例子中我们将看到,为什么说知道这些问题的答案是很重要的。</para> + </sect1> + + <sect1 xml:id="rcng-dummy"> + <title>虚拟的脚本</title> + + <para>下面的脚本是用来在每次系统启动时发出一个信息:</para> + + <informalexample> + <programlisting>#!/bin/sh<co xml:id="rcng-dummy-shebang"/> + +. /etc/rc.subr<co xml:id="rcng-dummy-include"/> + +name="dummy"<co xml:id="rcng-dummy-name"/> +start_cmd="${name}_start"<co xml:id="rcng-dummy-startcmd"/> +stop_cmd=":"<co xml:id="rcng-dummy-stopcmd"/> + +dummy_start()<co xml:id="rcng-dummy-startfn"/> +{ + echo "Nothing started." +} + +load_rc_config $name<co xml:id="rcng-dummy-loadconfig"/> +run_rc_command "$1"<co xml:id="rcng-dummy-runcommand"/></programlisting> + </informalexample> + + <para>需要注意的是:</para> + + <calloutlist> + <callout arearefs="rcng-dummy-shebang"> + <para>一个解释性的脚本应该以一行魔幻的 + <quote>shebang</quote> 行开头。 + 该行指定了脚本的解析程序。由于 shebang 行的作用, + 假如再有可执行位的设置, + 脚本就能象一个二进制程序一样被精确地调用执行。 + (请参考 &man.chmod.1;。) 例如, + 一个系统管理员可以从命令行手动运行我们的脚本:</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/dummy start</userinput></screen> + + <note> + <para>为了使 <filename>rc.d</filename> 框架正确地管理脚本, + 它的脚本需要用 &man.sh.1; 语言编写。 + 如果你的某个服务或 port 套件使用了二进制控制程序或是用其它语言编写的例程, + 请将其组件安装到 <filename>/usr/sbin</filename>(相对于系统) + 或 <filename>/usr/local/sbin</filename>(相对于ports), + 然后从合适的 <filename>rc.d</filename> 目录的某个 + &man.sh.1; 脚本调用它。</para> + </note> + + <tip> + <para>如果你想知道为什么 <filename>rc.d</filename> + 脚本必须用 &man.sh.1; 语言编写的细节,先看下 + <filename>/etc/rc</filename> 是如何依靠 + <function>run_rc_script</function> 调用它们, + 然后再去学习 <filename>/etc/rc.subr</filename> + 下 <function>run_rc_script</function> + 的相关实现。</para> + </tip> + </callout> + + <callout arearefs="rcng-dummy-include"> + <para>在 <filename>/etc/rc.subr</filename> 下, + 有许多定义过的 &man.sh.1; 函数可供每个 + <filename>rc.d</filename> 脚本使用。这些函数在 + &man.rc.subr.8; 中都有说明。尽管理论上可以完全不使用 + &man.rc.subr.8; 来编写一个 <filename>rc.d</filename> + 脚本,但它的函数已经证明了它真的很方便, + 并且能使任务更加的简单。所以所有人在编写 + <filename>rc.d</filename> 脚本时都会求助于 + &man.rc.subr.8; 也不足为奇了。当然我们也不例外。</para> + + <para>一个 <filename>rc.d</filename> 脚本在其调用 + &man.rc.subr.8; 函数之前必须先 <quote>source</quote> + <filename>/etc/rc.subr</filename>(用 + <quote><command>.</command></quote>将其包含进去), + 而使 &man.sh.1; 程序有机会来获悉那些函数。 + 首选风格是在脚本的最开始 source + <filename>/etc/rc.subr</filename> 文件。</para> + + <note> + <para>某些有用的与联网有关的函数由另一个被包含进来的文件提供, + <filename>/etc/network.subr</filename> 文件。</para> + </note> + </callout> + + <callout arearefs="rcng-dummy-name"> + <para><anchor xml:id="name-var"/>强制的变量 + <envar>name</envar> 指定我们脚本的名字。 + 这是 &man.rc.subr.8; 所强调的。也就是, + 每个 <filename>rc.d</filename> 脚本在调用 + &man.rc.subr.8; 的函数之前必须设置 + <envar>name</envar> 变量。</para> + + <para>现在是时候来为我们的脚本一次性选择一个独一无二的名字了。 + 在编写这个脚本的时我们将在许多地方用到它。在开始之前, + 我们来给脚本文件也取个相同的名字。</para> + + <note> + <para>当前的 <filename>rc.d</filename> + 脚本风格是把值放在双引号中来给变量赋值。 + 请记住这只是个风格问题,可能并不总是这样。 + 你可以在只是简单的并不包括 &man.sh.1; + 元字符的词句中放心地省略掉引号, + 而在某些情况下你将需要使用单引号以防止 + &man.sh.1; 对任何的变量的解释。 + 程序员是可以灵巧地由风格惯例获悉其语法以及使用的。 + </para> + </note> + </callout> + + <callout arearefs="rcng-dummy-startcmd"> + <para>&man.rc.subr.8; 背后主要的构思是 + <filename>rc.d</filename> 脚本提供处理程序,或者方法,来让 + &man.rc.subr.8; 调用。特别是,<option>start</option>, + <option>stop</option>,以及其它的 <filename>rc.d</filename> + 脚本参数都是这样被处理的。方法是存储在一个以 + <envar><replaceable>argument_cmd</replaceable></envar> + 形式命名的变量中的 &man.sh.1; 表达式,该 + <replaceable>argument</replaceable> + 对应着脚本命令行中所特别指定的参数。我们稍后将看到 + &man.rc.subr.8; 是如何为标准参数提供默认方法的。</para> + + <note> + <para>为了让 <filename>rc.d</filename> 中的代码更加统一, + 常见的是在任何适合的地方都使用 <envar>${name}</envar> 形式。 + 这样一来,可以轻松地将一些代码从一个脚本拷贝到另一个中使用。</para> + </note> + </callout> + + <callout arearefs="rcng-dummy-stopcmd"> + <para>我们应谨记 &man.rc.subr.8; 为标准参数提供了默认的方法。 + 因此,如果希望它什么都不做的话,我们必须使用无操作的 + &man.sh.1; 表达式来改写标准的方法。</para> + </callout> + + <callout arearefs="rcng-dummy-startfn"> + <para>比较复杂的方法主体可以用函数来实现。 + 在能够保证函数名有意义的情况下,这是个很不错的想法。</para> + + <important> + <para>强烈推荐给我们脚本中所定义的所有函数名都添加类似 + <envar>${name}</envar> 这样的前缀,以使它们永远不会和 + &man.rc.subr.8; 或其它公用包含文件中的函数冲突。</para> + </important> + </callout> + + <callout arearefs="rcng-dummy-loadconfig"> + <para>这是在请求 &man.rc.subr.8; 载入 &man.rc.conf.5; 变量。 + 尽管我们这个脚本中使用的变量并没有被其它地方使用,但由于 + &man.rc.subr.8; 自身所控制着的 &man.rc.conf.5; + 变量存在的原因,仍然推荐脚本去装载 &man.rc.conf.5;。</para> + </callout> + + <callout arearefs="rcng-dummy-runcommand"> + <para>通常这是 <filename>rc.d</filename> 脚本的最后一个命令。 + 它调用 &man.rc.subr.8; + 体系使用我们脚本所提供的变量和方法来执行相应的请求动作。</para> + </callout> + </calloutlist> + </sect1> + + <sect1 xml:id="rcng-confdummy"> + <title>可配置的虚拟脚本</title> + + <para>现在我们来给我们的虚拟脚本增加一些控制参数吧。正如你所知, + <filename>rc.d</filename> 脚本是由 &man.rc.conf.5; 所控制的。 + 幸运的是,&man.rc.subr.8; 隐藏了所有复杂化的东西。 + 下面这个脚本使用 &man.rc.conf.5; 通过 &man.rc.subr.8; + 来查看它是否在第一个地方被启用,并获取一条信息在启动时显示。 + 事实上这两个任务是相互独立的。一方面,<filename>rc.d</filename> + 脚本要能够支持启动和禁用它的服务。另一方面, + <filename>rc.d</filename> 脚本必须能具备配置信息变量。 + 我们将通过下面同一脚本来演示这两方面的内容:</para> + + <informalexample> + <programlisting>#!/bin/sh + +. /etc/rc.subr + +name=dummy +rcvar=dummy_enable<co xml:id="rcng-confdummy-rcvar"/> + +start_cmd="${name}_start" +stop_cmd=":" + +load_rc_config $name<co xml:id="rcng-confdummy-loadconfig"/> +eval "${rcvar}=\${${rcvar}:-'NO'}"<co xml:id="rcng-confdummy-enable"/> +dummy_msg=${dummy_msg:-"Nothing started."}<co xml:id="rcng-confdummy-opt"/> + +dummy_start() +{ + echo "$dummy_msg"<co xml:id="rcng-confdummy-msg"/> +} + +run_rc_command "$1"</programlisting> + </informalexample> + + <para>在这个样例中改变了什么?</para> + + <calloutlist> + <callout arearefs="rcng-confdummy-rcvar"> + <para>变量 <envar>rcvar</envar> 指定了 ON/OFF + 开关变量的名字。</para> + </callout> + + <callout arearefs="rcng-confdummy-loadconfig"> + <para>现在 <function>load_rc_config</function> 在任何 + &man.rc.conf.5; 变量被访问之前就在脚本中被预先调用。</para> + + <note> + <para>检查 <filename>rc.d</filename> 脚本时,切记 &man.sh.1; + 会把函数延迟到其被调用时才对其中的表达式进行求值运算。 + 因此尽可能晚地在 <function>run_rc_command</function> + 之前调用 <function>load_rc_config</function>, + 以及仍然访问从方法函数输出到 + <function>run_rc_command</function> 的 &man.rc.conf.5; + 变量并不是一个错误。这是因为方法函数将在 + <function>load_rc_config</function> <emphasis>之后</emphasis>, + 被调用的 <function>run_rc_command</function> 调用。</para> + </note> + </callout> + + <callout arearefs="rcng-confdummy-enable"> + <para>如果自身设置了 <envar>rcvar</envar>, + 但指示开关变量却未被设置,那么 <function>run_rc_command</function> + 将发出一个警告。如果你的 <filename>rc.d</filename> + 脚本是为基本系统所用的,你应当在 + <filename>/etc/defaults/rc.conf</filename> + 中给开关变量添加一个默认的设置并将其标注在 &man.rc.conf.5; 中。 + 否则的话你的脚本应该给开关变量提供一个默认设置。 + 范例中演示了一个可移植接近于后者情况的案例。</para> + + <note> + <para>你可以通过将开关变量设置为 ON 来使 &man.rc.subr.8; 有效, + 使用 <literal>one</literal> 或 <literal>force</literal> + 为脚本的参数加前缀,如 <literal>onestart</literal> 或 + <literal>forcestop</literal> 这样,会忽略其当前的设置。 + 切记 <literal>force</literal> + 在我们下面要提到的情况下有额外的危险后果,那就是当用 + <literal>one</literal> 改写了 ON/OFF 开关变量。例如, + 假定 <envar>dummy_enable</envar> 是 <literal>OFF</literal> + 的,而下面的命令将忽略系统设置而强行运行 + <option>start</option>方法:</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/dummy onestart</userinput></screen> + </note> + </callout> + + <callout arearefs="rcng-confdummy-opt"> + <para>现在启动时显示的信息不再是硬编码在脚本中的了。 + 它是由一个命名为 <envar>dummy_msg</envar> 的 + &man.rc.conf.5; 变量所指定的。这就是 &man.rc.conf.5; + 变量如何来控制 <filename>rc.d</filename> + 脚本的一个小例子。</para> + + <important> + <para>我们的脚本所独占使用的所有 &man.rc.conf.5; 变量名, + 都必须具有同样的前缀:<envar>${name}</envar>。 + 例如:<envar>dummy_mode</envar>, + <envar>dummy_state_file</envar>,等等。</para> + </important> + + <note> + <para>当可以内部使用一个简短的名字时,如 <envar>msg</envar> + 这样,为我们的脚本所引进的全部的共用名添加唯一的前缀 + <envar>${name}</envar>,能够避免我们与 &man.rc.subr.8; + 命名空间冲突的可能。</para> + + <para>只要一个 &man.rc.conf.5; 变量与其内部等同值是相同的, + 我们就能够使用一个更加兼容的表达式来设置默认值:</para> + + <programlisting>: ${dummy_msg:="Nothing started."}</programlisting> + + <para>尽管目前的风格是使用了更详细的形式。</para> + + <para>通常,基本系统的 <filename>rc.d</filename> + 脚本不需要为它们的 &man.rc.conf.5; 变量提供默认值, + 因为默认值应该是在 <filename>/etc/defaults/rc.conf</filename> + 设置过了。但另一方面,为 ports 所用的 <filename>rc.d</filename> + 脚本应提供如范例所示的默认设置。</para> + </note> + </callout> + + <callout arearefs="rcng-confdummy-msg"> + <para>这里我们使用 <envar>dummy_msg</envar> + 来实际地控制我们的脚本,即,发一个变量信息。</para> + </callout> + </calloutlist> + </sect1> + + <sect1 xml:id="rcng-daemon"> + <title>启动并停止简单守护进程</title> + + <para>我们早先说过 &man.rc.subr.8; 是能够提供默认方法的。 + 显然,这些默认方法并不是太通用的。 + 它们都是适用于大多数情况下来启动和停止一个简单的守护进程况。 + 我们来假设现在需要为一个叫做 <command>mumbled</command> + 的守护进程编写一个 <filename>rc.d</filename>脚本, + 在这里:</para> + + <informalexample> + <programlisting>#!/bin/sh + +. /etc/rc.subr + +name=mumbled +rcvar=mumbled_enable + +command="/usr/sbin/${name}"<co xml:id="rcng-daemon-basic-cmd"/> + +load_rc_config $name +run_rc_command "$1"</programlisting> + </informalexample> + + <para>感到很简单吧,不是么?我们来检查下我们这个小脚本。 + 只需要注意下面的这些新知识点:</para> + + <calloutlist> + <callout arearefs="rcng-daemon-basic-cmd"> + <para>这个 <envar>command</envar> 变量对于 + &man.rc.subr.8; 来说是有意义的。当它被设置时, + &man.rc.subr.8; 将根据提供传统守护进程的情形而生效。 + 特别是,将为这些参数提供默认的方法: + <option>start</option>,<option>stop</option>, + <option>restart</option>,<option>poll</option>, + 以及 <option>status</option>。</para> + + <para>该守护进程将会由运行中的 <envar>$command</envar> + 配合由 <envar>$mumbled_flags</envar> 所指定的命令行标帜来启动。 + 因此,对默认的 <option>start</option> 方法来说, + 所有的输入数据在我们脚本变量集合中都可用。与 + <option>start</option> 不同的是, + 其他方法可能需要与进程启动相关的额外信息。举个例子, + <option>stop</option> 必须知道进程的 PID 号来终结进程。 + 在目前的情况中,&man.rc.subr.8; 将扫描全部进程的清单, + 查找一个名字等同于 <envar>$procname</envar> 的进程。 + 后者是另一个对 &man.rc.subr.8; 有意义的变量, + 并且默认它的值跟 <envar>command</envar> 一样。 + 换而言之,当我们给 <envar>command</envar> 设置值后, + <envar>procname</envar> 实际上也设置了同样的值。 + 这启动我们的脚本来杀死守护进程并检查它是否正在第一个位置运行。</para> + + <note> + <para>某些程序实际上是可执行的脚本。 + 系统启动脚本的解释器以传递脚本名为命令行参数的形式来运行脚本。 + 然后被映射到进程列表中,这会使 &man.rc.subr.8; 迷惑。因此,当 + <envar>$command</envar> 是一个脚本的时,你应该额外地设置 + <envar>command_interpreter</envar> 来让 &man.rc.subr.8; + 知晓进程的实际名字。</para> + + <para>对每个 <filename>rc.d</filename> 脚本而言, + 有一个可选的 &man.rc.conf.5; 变量给 + <envar>command</envar> 指示其优先级。 + 它的名字是下面这样的形式:<envar>${name}_program</envar>, + <envar>name</envar> 是我们 <link linkend="name-var">之前</link> + 讨论过的必须性变量。如,在这个案例中它应该命名为 + <envar>emumbled_program</envar>。这其实是 &man.rc.subr.8; + 分配 <envar>${name}_program</envar> 来改写 + <envar>command</envar> 的。</para> + + <para>当然,即使 <envar>command</envar> 未被设置, + &man.sh.1; 也将允许你从 &man.rc.conf.5; 或自身来设置 + <envar>${name}_program</envar>。在那种情况下, + <envar>${name}_program</envar> 的特定属性丢失了, + 并且它成为了一个能供你的脚本用于其自身目的的普通变量。 + 然而,单独使用 <envar>${name}_program</envar> + 是并不是我们所寄望的,因为同时使用它和 <envar>command</envar> + 已成为了 <filename>rc.d</filename> 脚本编程的一个惯用的约定。</para> + </note> + + <para>关于默认方法的更详细的信息,请参考 + &man.rc.subr.8;。</para> + </callout> + </calloutlist> + </sect1> + + <sect1 xml:id="rcng-daemon-adv"> + <title>启动并停止高级守护进程</title> + + <para>我们来给之前的 <quote>骨架</quote> + 脚本加点 <quote>血肉</quote>,并让它更复杂更富有特性吧。 + 默认的方法已能够为我们做很好的工作了, + 但是我们可能会需要它们一些方面的调整。 + 现在我们将学习如何调整默认方法来符合我们的需要。</para> + + <informalexample> + <programlisting>#!/bin/sh + +. /etc/rc.subr + +name=mumbled +rcvar=mumbled_enable + +command="/usr/sbin/${name}" +command_args="mock arguments > /dev/null 2>&1"<co xml:id="rcng-daemon-adv-args"/> + +pidfile="/var/run/${name}.pid"<co xml:id="rcng-daemon-adv-pid"/> + +required_files="/etc/${name}.conf /usr/share/misc/${name}.rules"<co xml:id="rcng-daemon-adv-reqfiles"/> + +sig_reload="USR1"<co xml:id="rcng-daemon-adv-sig"/> + +start_precmd="${name}_prestart"<co xml:id="rcng-daemon-adv-precmd"/> +stop_postcmd="echo Bye-bye"<co xml:id="rcng-daemon-adv-postcmd"/> + +extra_commands="reload plugh xyzzy"<co xml:id="rcng-daemon-adv-extra"/> + +plugh_cmd="mumbled_plugh"<co xml:id="rcng-daemon-adv-methods"/> +xyzzy_cmd="echo 'Nothing happens.'" + +mumbled_prestart() +{ + if checkyesno mumbled_smart; then<co xml:id="rcng-daemon-adv-yn"/> + rc_flags="-o smart ${rc_flags}"<co xml:id="rcng-daemon-adv-rcflags"/> + fi + case "$mumbled_mode" in + foo) + rc_flags="-frotz ${rc_flags}" + ;; + bar) + rc_flags="-baz ${rc_flags}" + ;; + *) + warn "Invalid value for mumbled_mode"<co xml:id="rcng-daemon-adv-warn"/> + return 1<co xml:id="rcng-daemon-adv-preret"/> + ;; + esac + run_rc_command xyzzy<co xml:id="rcng-daemon-adv-run"/> + return 0 +} + +mumbled_plugh()<co xml:id="rcng-daemon-adv-plugh"/> +{ + echo 'A hollow voice says "plugh".' +} + +load_rc_config $name +run_rc_command "$1"</programlisting> + </informalexample> + + <calloutlist> + <callout arearefs="rcng-daemon-adv-args"> + <para>附加给 <envar>$command</envar> 的参数在 + <envar>command_args</envar> 中进行传递。它们在 + <envar>$mumbled_flags</envar> 之后将被添加到命令行。 + 其实际的执行便是此后最终的命令行传递给 + <command>eval</command> 运算,输入和输出以及重定向都可以在 + <envar>command_args</envar> 中指定。</para> + + <note> + <para><emphasis>永远不要</emphasis> 在 + <envar>command_args</envar> 包含破折号选项, + 类似 <option>-X</option> 或 <option>--foo</option> + 这样的。<envar>command_args</envar> + 的内容将出现在最终命令行的末尾,因此它们可能是紧接在 + <envar>${name}_flags</envar> 中所列出的参数后面; + 但大多的命令将不能识别出普通参数后的破折号选项。 + 更好的传递附加给 <envar>$command</envar> + 的选项的方式是添加它们到 <envar>${name}_flags</envar> + 的起始处。另一种方法是像后文所示的那样来修改 + <envar>rc_flags</envar>。</para> + </note> + </callout> + + <callout arearefs="rcng-daemon-adv-pid"> + <para>一个得体的守护进程会创建一个 + <emphasis>pidfile</emphasis> 进程文件, + 以使其进程能够更容易更可靠地被找到。如果设置了 + <envar>pidfile</envar> 变量,告诉 &man.rc.subr.8; + 哪里能找到供其默认方法所使用的 + <envar>pidfile</envar> 进程文件。</para> + + <note> + <para>事实上,&man.rc.subr.8; + 在启动一个守护进程前还会使用 pidfile + 进程文件来查看它是否已经在运行。使用了 + <option>faststart</option> 参数可以跳过这个检查步骤。</para> + </note> + </callout> + + <callout arearefs="rcng-daemon-adv-reqfiles"> + <para>如果守护进程只有在确定的文件存在的情况下才可以运行, + 那就将它们列到 <envar>required_files</envar> 中,而 + &man.rc.subr.8; 将在启动守护进程之前检查那些文件是否存在。 + 还有相关的分别用来检查目录和环境变量的 + <envar>required_dirs</envar> 和 <envar>required_vars</envar> + 可供使用。它们都在 &man.rc.subr.8; 中有详细的说明。</para> + + <note> + <para>来自 &man.rc.subr.8; 的默认方法,通过使用 + <option>forcestart</option> 作为脚本的参数, + 可以强制性地跳过预先需要的检查。</para> + </note> + </callout> + + <callout arearefs="rcng-daemon-adv-sig"> + <para>我们可以在守护进程有异常的时候,自定义发送给守护进程的信号。 + 特别是,<envar>sig_reload</envar> + 指定了使守护进程重新装载其配置的信号;默认情况也就是 + <symbol>SIGHUP</symbol> 信号。 + 另一个信号是发送给守护进程以停止该进程;默认情况下是 + <symbol>SIGTERM</symbol> 信号,但这是可以通过设置 + <envar>sig_stop</envar> 来进行适当更改的。</para> + + <note> + <para>信号名称应当以不包含 <literal>SIG</literal> + 前缀的形式指定给 &man.rc.subr.8;,就如范例中所示的那样。 + &os; 版本的 &man.kill.1; 程序能够识别出 + <literal>SIG</literal> 前缀,不过其它系统版本的就不一定了。</para> + </note> + </callout> + + <callout arearefs="rcng-daemon-adv-precmd rcng-daemon-adv-postcmd"> + <para>在默认的方法前面或后面执行附加任务是很容易的。 + 对于我们脚本所支持的每条命令参数而言,我们可以定义 + <envar><replaceable>argument</replaceable>_precmd</envar> 和 + <envar><replaceable>argument</replaceable>_postcmd</envar> + 来完成。这些 &man.sh.1; 命令分别在它们各自的方法前后被调用, + 显然,从它们各自的名字就能看出来。</para> + + <note> + <para>如果我们需要的话,用自定义的 + <envar><replaceable>argument</replaceable>_cmd</envar> + 改写默认的方法,并不妨碍我们仍然使用 + <envar><replaceable>argument</replaceable>_precmd</envar> 和 + <envar><replaceable>argument</replaceable>_postcmd</envar>。 + 特别是,前者便于检查自定义的方法, + 以及执行自身命令之前所遇到更严密的条件。于是,将 + <envar><replaceable>argument</replaceable>_precmd</envar> 和 + <envar><replaceable>argument</replaceable>_cmd</envar> + 一起使用,使我们合理地将检查从动作中独立了出来。</para> + + <para>别忘了你可以将任意的有效的 &man.sh.1; + 表达式插入到方法和你定义的 pre- 与 post-commands 命令中。 + 在大部分情况下,调用函数使实际任务有好的风格, + 但千万不要让风格限制了你对其幕后到底是怎么回事的思考。</para> + </note> + </callout> + + <callout arearefs="rcng-daemon-adv-extra"> + <para>如果我们愿意实现一些自定义参数, + 这些参数也可被认作为我们脚本的 <emphasis>命令</emphasis>,我们需要在 + <envar>extra_commands</envar> 中将它们列出并提供方法以处理它们。</para> + + <note> + <para><option>reload</option> 是个特别的命令。一方面, + 它有一个在 &man.rc.subr.8; 中预置的方法。另一方面, + <option>reload</option> 命令默认是不被提供的。 + 理由是并非所有的守护进程都使用同样的重载方法, + 并且有些守护进程根本没有任何东西可重载的。所以显而易见, + 我们需要去询问都提供了哪些的内建功能。我们可以通过 + <envar>extra_commands</envar> 来这样做。</para> + + <para>我们从 <option>reload</option> 的默认方法得到了什么呢? + 守护进程常常在收到一个信号后重新载入它们的配置 — + 一般来说,也就是 <symbol>SIGHUP</symbol> 信号。因此 + &man.rc.subr.8; 尝试发送一个信号给守护进程来重载它。 + 该信号一般预设为 <symbol>SIGHUP</symbol>, + 但是如果必要的话可以通过 <envar>sig_reload</envar> + 变量来自定义它。</para> + </note> + </callout> + + <callout arearefs="rcng-daemon-adv-methods rcng-daemon-adv-plugh"> + <para>我们的脚本提供了两个非标准的命令, + <option>plugh</option> 和 <option>xyzzy</option>。 + 我们看到它们在 <envar>extra_commands</envar> 中被列出来了, + 并且现在是时候给它们提供方法了。<option>xyzzy</option> + 的方法是内联的而 <option>plugh</option> 的是以 + <function>mumbled_plugh</function> 形式完成的函数。</para> + + <para>非标准命令在启动或停止的时候不被调用。 + 通常它们是为了系统管理员的方便。它们还能被其它的子系统所使用, + 例如,&man.devd.8;,前提是 &man.devd.conf.5; 中已经指定了。</para> + + <para>全部可用命令的列表,当脚本不加参数地调用时,在 + &man.rc.subr.8; 打印出的使用方法中能够找到。例如, + 这就是供学习的脚本用法的内容:</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/mumbled</userinput> +Usage: /etc/rc.d/mumbled [fast|force|one](start|stop|restart|rcvar|reload|plugh|xyzzy|status|poll)</screen> + </callout> + + <callout arearefs="rcng-daemon-adv-run"> + <para>如果脚本需要的话,它可以调用自己的标准或非标准的命令。 + 这可能看起来有点像函数的调用,但我们知道,命令和 shell + 函数并非一直都是同样的东西。举个例子,<command>xyzzy</command> + 在这里不是以函数来实现的。另外,还有应该被有序调用的 + pre-command 预置命令和 post-command 后置命令。 + 所以脚本运行自己命令的合适方式就是利用 &man.rc.subr.8;, + 就像范例中展示的那样。</para> + </callout> + + <callout arearefs="rcng-daemon-adv-yn"> + <para>&man.rc.subr.8; 提供了一个方便的函数叫做 + <function>checkyesno</function>。 + 它以一个变量名作为参数并返回一个为零的退出值, + 当且仅当该变量设置为 <literal>YES</literal>,或 + <literal>TRUE</literal>,或 <literal>ON</literal>,或 + <literal>1</literal>,区分大小写;否则返回一个非零的退出值。 + 在第二种情况中,函数测试变量的设置为 <literal>NO</literal>, + <literal>FALSE</literal>,<literal>OFF</literal>,或 + <literal>0</literal>,区分大小写; + 如果变量包含别的内容的话它打印一条警告信息,例如,垃圾。</para> + + <para>切记对 &man.sh.1; 而言零值意味着真而非零值意味着假。</para> + + <important> + <para><function>checkyesno</function> 函数使用一个 + <emphasis>变量名</emphasis>。不要扩大含义将变量的 + <emphasis>值</emphasis> 传递给它; + 否则它不会如你预期那样的工作。</para> + + <para>下面是 <function>checkyesno</function> + 的合理使用范围:</para> + + <programlisting>if checkyesno mumbled_enable; then + foo +fi</programlisting> + + <para>相反地,以下面的方式调用 <function>checkyesno</function> + 是不会工作的 -- 至少是不会如你预期的那样:</para> + + <programlisting>if checkyesno "${mumbled_enable}"; then + foo +fi</programlisting> + </important> + </callout> + + <callout arearefs="rcng-daemon-adv-rcflags"> + <para>我们可以通过修改 <envar>$start_precmd</envar> 中的 + <envar>rc_flags</envar> 来影响传递到 + <envar>$command</envar> 的标帜。</para> + </callout> + + <callout arearefs="rcng-daemon-adv-warn"> + <para>某种情况下我们可能需要发出一条重要的信息,那样的话 + <application>syslog</application> 可以很好地记录日志。 + 这可以使用下列 &man.rc.subr.8; 函数来轻松完成: + <function>debug</function>,<function>info</function>, + <function>warn</function>,以及 <function>err</function>。 + 后者以指定的代码值退出脚本。</para> + </callout> + + <callout arearefs="rcng-daemon-adv-preret"> + <para>方法的退出值和它们的 pre-commands 预命令不只是默认被忽略掉。如果 + <envar><replaceable>argument</replaceable>_precmd</envar> + 返回了一个非零退出值,主方法将不会被执行。依次地是, + <envar><replaceable>argument</replaceable>_postcmd</envar> + 将不会被调用,除非主方法返回的是一个为零的退出值。</para> + + <note> + <para>然而,当给一个参数使用 <literal>force</literal> + 前缀的时候,如 <option>forcestart</option>,&man.rc.subr.8; + 会听从命令行指示而忽略那些退出值最后仍然调用所有的命令。</para> + </note> + </callout> + </calloutlist> + </sect1> + + <sect1 xml:id="rcng-hookup"> + <title>链接脚本到 rc.d 框架</title> + + <para>当编写好了一个脚本,它需要被整合到 <filename>rc.d</filename> 中去。 + 一个重要的步骤就是安装脚本到 <filename>/etc/rc.d</filename> + (对基本系统而言)或 <filename>/usr/local/etc/rc.d</filename> + (对ports而言)中去。在 <<filename>bsd.prog.mk</filename>> 和 + <<filename>bsd.port.mk</filename>> 中都为此提供了方便的接口, + 通常你不必担心适当的所有权限和模式。系统脚本应当是通过可以在 + <filename>src/etc/rc.d</filename> 找到的 <filename>Makefile</filename> + 安装的。Port 脚本可以像 + <link xlink:href="&url.books.porters-handbook;/rc-scripts.html">Porter's Handbook</link> + 中描述那样通过使用 <varname>USE_RC_SUBR</varname> 来被安装。</para> + + <para>不过,我们应该预先考虑到我们脚本在系统启动顺序中的位置。 + 我们的脚本所处理的服务可能依赖于其它的服务。举个例子, + 没有网络接口和路由选择的启用运行的话,一个网络守护进程是不起作用的。 + 即使一个服务看似什么都不需要,在基本文件系统检查挂载完毕之前也很难启动。</para> + + <para>之前我们曾提到过 &man.rcorder.8;。现在是时候来密切地关注下它了。 + 笼统地说,&man.rcorder.8; 处理一组文件,检验它们的内容, + 并从文件集合打印一个文件列表的依赖顺序到 <varname>stdout</varname> + 标准输出。这点是用于保持文件内部的依赖信息, + 而每个文件只能说明自己的依赖。一个文件可以指定如下信息:</para> + + <itemizedlist> + <listitem> + <para>它 <emphasis>提供</emphasis> 的 <quote>条件</quote> + 的名字(意味着我们服务的名字);</para> + </listitem> + + <listitem> + <para>它 <emphasis>需求</emphasis> 的 + <quote>条件</quote> 的名字;</para> + </listitem> + + <listitem> + <para>应该 <emphasis>先</emphasis> 运行的文件的 + <quote>条件</quote>的名字;</para> + </listitem> + + <listitem> + <para>能用于从全部文件集合中选择一个子集的额外 + <emphasis>关键字</emphasis>( &man.rcorder.8; + 可通过选项而被指定来包括或省去由特殊关键字所列出的文件。)</para> + </listitem> + </itemizedlist> + + <para>并不奇怪的是,&man.rcorder.8; 只能处理接近 &man.sh.1; + 语法的文本文件。&man.rcorder.8; 所解读的特殊行看起来类似 + &man.sh.1; 的注释。这种特殊文本行的语法相当严格地简化了其处理。 + 请查阅 &man.rcorder.8; 以获取更详细的信息。</para> + + <para>除使用 &man.rcorder.8; 的特殊行以外, + 脚本可以坚持将其依赖的其它服务强制性启动。当其它服务是可选的, + 并因系统管理员错误地在 &man.rc.conf.5; + 中禁用掉该服务而使其不能自行启动时,会需要这一点。</para> + + <para>将这些谨记在心,我们来考虑下简单结合了依赖信息增强的守护进程脚本:</para> + + <informalexample> + <programlisting>#!/bin/sh + +# PROVIDE: mumbled oldmumble <co xml:id="rcng-hookup-provide"/> +# REQUIRE: DAEMON cleanvar frotz<co xml:id="rcng-hookup-require"/> +# BEFORE: LOGIN<co xml:id="rcng-hookup-before"/> +# KEYWORD: nojail shutdown<co xml:id="rcng-hookup-keyword"/> + +. /etc/rc.subr + +name=mumbled +rcvar=mumbled_enable + +command="/usr/sbin/${name}" +start_precmd="${name}_prestart" + +mumbled_prestart() +{ + if ! checkyesno frotz_enable && \ + ! /etc/rc.d/frotz forcestatus 1>/dev/null 2>&1; then + force_depend frotz || return 1<co xml:id="rcng-hookup-force"/> + fi + return 0 +} + +load_rc_config $name +run_rc_command "$1"</programlisting> + </informalexample> + + <para>跟前面一样,做如下详细分析:</para> + + <calloutlist> + <callout arearefs="rcng-hookup-provide"> + <para>该行声明了我们脚本所提供的 <quote>条件</quote> 的名字。 + 现在其它脚本可以用那些名字来标明我们脚本的依赖。</para> + + <note> + <para>通常脚本指定一个单独的已提供的条件。然而, + 并没有什么妨碍我们从列出的那些条件中指定,例如, + 为了兼容性的目的。</para> + + <para>在其它情况,主要的名称,或者说唯一的, + <literal>PROVIDE:</literal> 条件应该与 + <envar>${name}</envar> 相同。</para> + </note> + </callout> + + <callout arearefs="rcng-hookup-require rcng-hookup-before"> + <para>因此我们的脚本指示了其依赖于别的脚本所提供的 + <quote>条件</quote>。根据这些行的信息,脚本请示 + &man.rcorder.8; 以将其放在一个或多个提供 + <filename>DAEMON</filename> 和 <filename>cleanvar</filename> + 的脚本后面,但在提供 <filename>LOGIN</filename> 的脚本前面。</para> + + <note> + <para><literal>BEFORE:</literal> + 这一行不可以在其它脚本不完整的依赖关系列表中滥用。 + 适合使用 <literal>BEFORE:</literal> + 的情况是当其它脚本不关心我们的脚本, + 但是我们的脚本如果在另一个之前运行的话能够更好地执行任务。 + 一个典型的实例是网络接口和防火墙: + 虽然接口不依赖防火墙来完成自己的工作, + 但是系统安全将因一切网络流量之前启动的防火墙而受益。</para> + + <para>除了条件相对应的每个单独服务,脚本使用元条件和它们的 + <quote>占位符</quote> 来保证某个操作组在其它之前被执行。 + 这些是由 <filename>UPPERCASE</filename> + 大写名字所表示的。它们的列表和用法可以在 &man.rc.8; 中找到。</para> + + <para>切记将一个服务名称放进 <literal>REQUIRE:</literal> + 行不能保证实际的服务会在我们的脚本启动的时候运行。 + 所需求的服务可能会启动失败或在 &man.rc.conf.5; 中被禁掉了。 + 显然,&man.rcorder.8; 是无法追踪这些细节的,并且 + &man.rc.8; 也不会去追踪。所以, + 脚本启动的应用程序应当能够应付任何所需求的服务的不可用情况。 + 某些情况下,我们可以用 <link linkend="forcedep">下面</link> + 所讨论的方式来协助脚本。</para> + </note> + </callout> + + <callout arearefs="rcng-hookup-keyword"> + <para><anchor xml:id="keywords"/>如我们从上述文字所记起的,&man.rcorder.8; + 关键字可以用来选择或省略某些脚本。即任何 &man.rcorder.8; + 用户可以通过指定 <option>-k</option> 和 <option>-s</option> + 选项来分别指定 <quote>保留清单(keep list)</quote> 和 + <quote>跳过清单(skip list)</quote>。 + 从全部文件到按依赖关系排列的清单,&man.rcorder.8; + 将只是挑出保留清单(除非是空的) + 中那些带关键字的以及从跳过清单中挑出不带关键字的文件。</para> + + <para>在 &os; 中,&man.rcorder.8; 被 + <filename>/etc/rc</filename> 和 + <filename>/etc/rc.shutdown</filename> 所使用。 + 这两个脚本定义了 &os; 中 <filename>rc.d</filename> + 关键字以及它们的意义的标准列表如下:</para> + + <variablelist> + <varlistentry> + <term><literal>nojail</literal></term> + + <listitem> + <para>该服务不适用于 &man.jail.8; 环境。 + 如果是在 jail 的内部的话,自动启动和关闭程序将忽略该脚本。</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>nostart</literal></term> + + <listitem> + <para>该服务只能手动启动否则将不会启动。 + 自动启动程序将忽略此脚本。结合 <literal>shutdown</literal> + 关键字的话,这可以用来编写只在系统关闭时执行一些任务的脚本。</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>shutdown</literal></term> + + <listitem> + <para>这个关键字 <emphasis>明确</emphasis> + 地列出了需要在系统关闭前停止的服务。</para> + + <note> + <para>当系统即将关闭的时候, + <filename>/etc/rc.shutdown</filename> 在运行。 + 它假定认为大部分的 <filename>rc.d</filename> + 脚本在那刻什么都不做。因此, + <filename>/etc/rc.shutdown</filename> + 选择性地调用带有 <literal>shutdown</literal> + 关键字的 <filename>rc.d</filename> 脚本, + 有效地忽略其余的脚本。为了更快的关闭, + <filename>/etc/rc.shutdown</filename> 传递 + <option>faststop</option> 命令给其运行的脚本, + 以跳过预置的检查,例如,进程文件 pidfile 的检查。 + 正如依赖性服务应该在其所依赖的服务之前停止, + <filename>/etc/rc.shutdown</filename> + 以相反的依赖次序来运行这些脚本。</para> + + <para>如果写一个真正的 <filename>rc.d</filename> 脚本的话, + 你应当考虑到其是否与系统关闭时有关系。例如, + 如果你的脚本只通过响应 <option>start</option> + 命令来运行任务,那么你不需要包含这个关键字。然而, + 如果你的脚本管理着一个服务,那么,在系统进入 &man.halt.8; + 中所描述的其本身关闭顺序的最终阶段之前停止该脚本, + 可能是个不错的主意。特别是, + 你显然是应该关闭一个需要相当长时间, + 或需要特定的动作才能干净地关闭的服务。 + 数据库引擎就是这样一个典型的例子。</para> + </note> + </listitem> + </varlistentry> + </variablelist> + </callout> + + <callout arearefs="rcng-hookup-force"> + <para><anchor xml:id="forcedep"/>以 + <function>force_depend</function> + 起始的行应被用于更谨慎的情况。通常,用于修正相互关联的 + <filename>rc.d</filename> + 脚本分层结构的配置文件时会更加稳妥。</para> + + <para>如果你仍不能完成不含 <function>force_depend</function> 的脚本, + 范例提供了一个如何有条件地调用它的习惯用法。在范例中,我们的 + <command>mumbled</command> 守护进程需求另一个以高级方式启动的进程, + <command>frotz</command>。但 <command>frotz</command> 也是可选的; + 而且 &man.rcorder.8; 对这些信息是一无所知的。幸运的是, + 我们的脚本已访问到全部的 &man.rc.conf.5; 变量。如果 + <envar>frotz_enable</envar> 为真,我们希望的最好结果是依靠 + <filename>rc.d</filename> 已经启动了 <command>frotz</command>。 + 否则我们强制检查 <command>frotz</command> 的状态。最终, + 如果 <command>frotz</command> 依赖的服务没有找到或运行的话, + 我们将强制其运行。这时 <function>force_depend</function> + 将发出一条警告信息,因为它只应该在检查到配置信息丢失的情况下被调用。</para> + </callout> + </calloutlist> + </sect1> + + <sect1 xml:id="rcng-args"> + <title>给予 rc.d 脚本更多的灵活性</title> + + <para>当进行启动或停止的调用时,<filename>rc.d</filename> + 脚本应该作用于其所负责的整个子系统。例如, + <filename>/etc/rc.d/netif</filename> 应该启动或停止 + &man.rc.conf.5; 中所描述的全部网络接口。每个任务都唯一地听从一个如 + <option>start</option> 或 <option>stop</option> + 这样的单独命令参数的指示。在启动和停止之间的时间, + <filename>rc.d</filename> 脚本帮助管理员控制运行中的系统, + 并其在需要的时候它将产生更多的灵活性和精确性。举个例子, + 管理员可能想在 &man.rc.conf.5; 中添加一个新网络接口的配置信息, + 然后在不妨碍其它已存在接口的情况下将其启动。 + 在下次管理员可能需要关闭一个单独的网络接口。在魔幻的命令行中, + 对应的 <filename>rc.d</filename> 脚本调用一个额外的参数, + 网络接口名即可。</para> + + <para>幸运的是,&man.rc.subr.8; 允许传递任意多(取决于系统限制)的参数给脚本的方法。 + 由于这个原因,脚本自身的改变可以说是微乎其微。</para> + + <para>&man.rc.subr.8; 如何访问到附加的命令行参数呢?直接获取么? + 并非是无所不用其极的。首先,&man.sh.1; + 函数没有访问到调用者的定位参数,而 &man.rc.subr.8; + 只是这些函数的容器。其次,<filename>rc.d</filename> + 指令的一个好的风格是由主函数来决定将哪些参数传递给它的方法。</para> + + <para>所以 &man.rc.subr.8; 提供了如下的方法: + <function>run_rc_command</function> + 传递其所有参数但将第一个参数逐字传递到各自的方法。首先, + 发出以方法自身为名字的参数:<option>start</option>, + <option>stop</option>,等等。这会被 + <function>run_rc_command</function> 移出, + 这样命令行中原本 <envar>$2</envar> 的内容将作为 + <envar>$1</envar> 来提供给方法,等等。</para> + + <para>为了说明这点,我们来修改原来的虚拟脚本, + 这样它的信息将取决于所提供的附加参数。从这里出发:</para> + + <informalexample> + <programlisting>#!/bin/sh + +. /etc/rc.subr + +name="dummy" +start_cmd="${name}_start" +stop_cmd=":" +kiss_cmd="${name}_kiss" +extra_commands="kiss" + +dummy_start() +{ + if [ $# -gt 0 ]; then<co xml:id="rcng-args-start"/> + echo "Greeting message: $*" + else + echo "Nothing started." + fi +} + +dummy_kiss() +{ + echo -n "A ghost gives you a kiss" + if [ $# -gt 0 ]; then<co xml:id="rcng-args-kiss"/> + echo -n " and whispers: $*" + fi + case "$*" in + *[.!?]) + echo + ;; + *) + echo . + ;; + esac +} + +load_rc_config $name +run_rc_command "$@"<co xml:id="rcng-args-all"/></programlisting> + </informalexample> + + <para>能注意到脚本里发生了那些实质性改变么?</para> + + <calloutlist> + <callout arearefs="rcng-args-start"> + <para>你输入的所有在 <option>start</option> + 之后的参数可以被当作各自方法的定位参数一样被终结。 + 我们可以根据我们的任务、技巧和想法来以任何方式使用他们。 + 在当前的例子中,我们只是以下行中字符串的形式传递参数给 + &man.echo.1; 程序 — 注意 <envar>$*</envar> + 是有双引号的。这里是脚本如何被调用的:</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/dummy start</userinput> +Nothing started. +&prompt.root; <userinput>/etc/rc.d/dummy start Hello world!</userinput> +Greeting message: Hello world!</screen> + </callout> + + <callout arearefs="rcng-args-kiss"> + <para>同样用于我们脚本提供的任何方法,并不仅限于标准的方法。 + 我们已经添加了一个自定义的叫做 <option>kiss</option> 的方法, + 并且它给附加参数带来的戏耍决不亚于 <option>start</option>。 + 例如:</para> + + <screen>&prompt.root; <userinput>/etc/rc.d/dummy kiss</userinput> +A ghost gives you a kiss. +&prompt.root; <userinput>/etc/rc.d/dummy kiss Once I was Etaoin Shrdlu...</userinput> +A ghost gives you a kiss and whispers: Once I was Etaoin Shrdlu...</screen> + </callout> + + <callout arearefs="rcng-args-all"> + <para>如果我们只是传递所有附加参数给任意的方法, + 我们只需要在脚本的最后一行我们调用 + <function>run_rc_command</function> 的地方, + 用 <literal>"$@</literal> 代替 <literal>"$1"</literal> 即可。</para> + + <important> + <para>一个 &man.sh.1; 程序员应该是可以理解 + <envar>$*</envar> 和 <envar>$@</envar> + 的微妙区别只是指定全部定位参数的不同方法。 + 关于此更深入的探讨,可以参考这个很好的 &man.sh.1; + 脚本编程手册。在你完全理解这些表达式的意义之前请不要使用它们, + 因为误用它们将给脚本引入缺陷和不安全的弊端。</para> + </important> + + <note> + <para>现在 <function>run_rc_command</function> 可能有个缺陷, + 它将影响保持参数之间的原本边界。也就是, + 带有嵌入空白的参数可能不会被正确处理。该缺陷是由于对 + <envar>$*</envar> 的误用。</para> + </note> + </callout> + </calloutlist> + </sect1> + + <sect1 xml:id="rcng-furthur"> + <title>进一步阅读</title> + + <para><anchor xml:id="lukem"/><link xlink:href="http://www.mewburn.net/luke/papers/rc.d.pdf"> + Luke Mewburn 的原始文章</link> 中讲述了 + <filename>rc.d</filename> 的基本概要, + 并详细阐述了其设计方案的原理。该文章提供了深入了解整个 + <filename>rc.d</filename> 框架以及其所在的现代 BSD + 操作系统的内容。</para> + + <para><anchor xml:id="manpages"/>在 &man.rc.8;,&man.rc.subr.8;, + 还有 &man.rcorder.8; 的联机手册中,对 + <filename>rc.d</filename> 组件做了非常详细的记载。 + 在你写脚本时,如果不去学习和参考这些联机手册的话, + 你是无法完全发挥出 <filename>rc.d</filename> 的能量的。</para> + + <para>工作中实际范例的主要来源就是运行的系统中的 + <filename>/etc/rc.d</filename> 目录。 + 它的内容可读性非常好,因为大部分的枯燥的内容都深藏在 + &man.rc.subr.8; 中了。切记 <filename>/etc/rc.d</filename> + 的脚本也不是神仙写出来的, + 所以它们可能也存在着代码缺陷以及低级的设计方案。 + 但现在你可以来改进它们了!</para> + </sect1> +</article> diff --git a/zh_CN.UTF-8/articles/remote-install/Makefile b/zh_CN.UTF-8/articles/remote-install/Makefile new file mode 100644 index 0000000000..5fc385e414 --- /dev/null +++ b/zh_CN.UTF-8/articles/remote-install/Makefile @@ -0,0 +1,28 @@ +# +# $FreeBSD$ +# Original Revision: 1.1 +# + +DOC?= article + +FORMATS?= html +WITH_ARTICLE_TOC?= YES + +INSTALL_COMPRESSED?= gz +INSTALL_ONLY_COMPRESSED?= + +SRCS= article.xml + +# Images from the cross-document image library +IMAGES_LIB= callouts/1.png +IMAGES_LIB+= callouts/2.png +IMAGES_LIB+= callouts/3.png +IMAGES_LIB+= callouts/4.png +IMAGES_LIB+= callouts/5.png +IMAGES_LIB+= callouts/6.png +IMAGES_LIB+= callouts/7.png + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. + +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/articles/remote-install/article.xml b/zh_CN.UTF-8/articles/remote-install/article.xml new file mode 100644 index 0000000000..fa95df333a --- /dev/null +++ b/zh_CN.UTF-8/articles/remote-install/article.xml @@ -0,0 +1,512 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd"> +<!-- + The FreeBSD Documentation Project + The FreeBSD Chinese (Simplified) Documentation Project + + Original Revision: 1.6 +--> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>&os; 操作系统在无远程控制台下的远程安装</title> + + + <author><personname><firstname>Daniel</firstname><surname>Gerzo</surname></personname><affiliation> + <address><email>danger@FreeBSD.org</email></address> + </affiliation></author> + + <legalnotice xml:id="trademarks" role="trademarks"> + &tm-attrib.freebsd; + &tm-attrib.general; + </legalnotice> + + <copyright> + <year>2008</year> + <holder>The &os; Documentation Project</holder> + </copyright> + + <pubdate>$FreeBSD$</pubdate> + + <releaseinfo>$FreeBSD$</releaseinfo> + + <abstract> + <para>本文归档了当远程控制台不可用的情况下 &os; 操作系统的远程安装。 + 文章背后的主要灵感归功于和 &a.mm; 还有由 &a.pjd; + 提供的宝贵输入合作的结果。</para> + </abstract> + </info> + + <sect1 xml:id="background"> + <title>背景</title> + + <para>世界上有很多的服务器主机供应商, + 但是他们中只有很少的一部分正式支持 &os;, + 他们通常为他们提供的服务器上安装 &linux; + 发行版提供支持。</para> + + <para>在某些情况下,如果你请求这些公司他们会安装一个你首选的 &linux; + 发行版。有了这个选择,我们将试图安装 &os;。 + 在其他情况下,他们可能提供一个急救系统用于紧急情况。 + 使用这个可能将有利于我们的目的更好的实现。</para> + + <para>本文涵盖了引导一个包含 RAID-1 及 <application>ZFS</application> + 性能的 &os; 系统的远程安装的基本安装配置所必须的步骤。</para> + </sect1> + + <sect1 xml:id="intro"> + <title>简介</title> + + <para>这一节会摘要本文的目的以及更好阐述这里所概括的东西。 + 本文中的这些指令将有益于那些使用不支持 &os; + 的托管设施提供的服务的人。</para> + + <procedure> + <step> + <para>如我们提到过的 <link linkend="background">背景</link> + 的那一节,许多的有声望的服务器主机托管公司提供了各种的急救系统。 + 可以从他们自己的 <acronym>局域网</acronym> 启动并可以通过 + <application>SSH</application> 访问。 + 他们通常提供这种支持目的用于帮助他们的顾客修正损坏的操作系统。 + 如文章将说明的,我们将能够通过这些急救系统的帮助来安装 &os;。</para> + <!-- XXXTR: Solaris has a restore command, something like + sysrestore, FreeBSD Should have one too. --> + </step> + + <step> + <para>文章的下一小节会描述如何配置,并在本地机器上构建最小限度的 + &os;。该版本最终会从随机存储盘运行到远程机器上面去。 + 这将允许我们使用 <application>sysinstall</application> + 实用程序从一个 <acronym>FTP</acronym> + 镜像安装一套完整的 &os; 操作系统。</para> + </step> + + <step> + <para>文章的剩余内容除了描述 <application>ZFS</application> + 文件系统的配置还将描述系统本身的安装步骤。</para> + </step> + </procedure> + + <sect2 xml:id="requirements"> + <title>需求</title> + + <para>想要成功地做下去,你必须:</para> + + <itemizedlist> + <listitem> + <para>拥有一个可通过 <application>SSH</application> + 网络访问的操作系统。</para> + </listitem> + + <listitem> + <para>理解 &os; 的安装过程</para> + </listitem> + + <listitem> + <para>熟悉 &man.sysinstall.8; 实用程序</para> + </listitem> + + <listitem> + <para>拥有 &os; 安张的 <acronym>ISO</acronym> + 镜像文件或者易于使用的 <acronym>CD</acronym></para> + </listitem> + </itemizedlist> + </sect2> + </sect1> + + <sect1 xml:id="preparation"> + <title>准备工作 - <application>mfsBSD</application></title> + + <para>在 &os; 可能安装到目标系统上之前, + 需要先构建一个最小化的从磁盘启动的 &os; 操作系统映像文件。 + 此方法中新系统必须能够从网络访问, + 并且安装的其他过程能够在没有远程访问到系统控制台的情况下完成。</para> + + <para><application>mfsBSD</application> 设置工具能够被用来构建一个微小的 + &os; 映像。如 <application>mfsBSD</application> 名字的含义 + (<quote>mfs</quote> 的意思是 <quote>memory file system</quote> 内存文件系统), + 最后的映像全部从随机存储器运行。多亏了这个特性, + 磁盘的操作将不会有任何限制,因此它能够被用来安装一个完整的 &os; 操作系统。 + <application>mfsBSD</application> 的主页在 + <uri xlink:href="http://people.freebsd.org/~mm/mfsbsd/">http://people.freebsd.org/~mm/mfsbsd/</uri>, + 包含了指向最新释出的设置工具。</para> + + <para>请注意关于 <application>mfsBSD</application> + 内幕以及它所有的适用都超出了本文的内容, + 感兴趣的读者应该去查阅 <application>mfs</application> + 的原始文档得到更多详细内容。</para> + + <para>下载并解压出最新的 <application>mfsBSD</application> + 版本,并改变自己的当前工作目录到存在 <application>mfsBSD</application> + 脚本文件的目录:</para> + + <screen>&prompt.root; <userinput>fetch http://people.freebsd.org/~mm/mfsbsd/mfsbsd-latest.tar.gz</userinput> +&prompt.root; <userinput>tar xvzf mfsbsd-1.0-beta1.tar.gz</userinput> +&prompt.root; <userinput>cd mfsbsd-1.0-beta1/</userinput></screen> + + <sect2 xml:id="mfsbsd-config"> + <title><application>mfsBSD</application> 的配置</title> + + <para>引导 <application>mfsBSD</application> 之前, + 必须设置一些重要的配置选项。 + 最重要的是我们必须有正确地,自然地,网络配置。 + 最适合的方法配置网络选项取决于我们是否事先知道我们会用到的网络接口, + 而且网络接口驱动程序应被系统为我们的硬件载入。 + 我们将看到 <application>mfsBSD</application> + 如何能够在任一种情况下被配置。</para> + + <para>另外一件重要的事情是设置 <systemitem class="username">root</systemitem> 的密码。 + 这将通过编辑 <filename>conf/rootpw.conf</filename> 文件来完成。 + 请记住该文件将把你的密码保存在简单的文本中, + 所以在此我们不推荐你使用真实的密码。然而, + 这只是一个临时使用一次的密码,你可以在随后安装好的系统中更改它。</para> + + <sect3> + <title>编辑 <filename>conf/interfaces.conf</filename> 的方法</title> + + <para>如果我们安装好的网卡是未知类型的, + 我们可以使用 <application>mfsBSD</application> 的自动探测功能。 + <application>mfsBSD</application> 启动脚本能够探测到正确的驱动来使用, + 基于网络接口的 MAC 地址,我们假设在 + <filename>conf/interfaces.conf</filename> 文件中设置如下选项:</para> + + <programlisting>initconf_interfaces="ext1" +initconf_mac_ext1="00:00:00:00:00:00" +initconf_ip_ext1="192.168.0.2" +initconf_netmask_ext1="255.255.255.0"</programlisting> + + <para>别忘了添加 <literal>defaultrouter</literal> + 信息到 <filename>conf/rc.conf</filename> 文件中:</para> + + <programlisting>defaultrouter="192.168.0.1"</programlisting> + </sect3> + + <sect3> + <title>编辑 <filename>conf/rc.conf</filename> 的方法</title> + + <para>当网络接口的驱动是已知类型的,使用 + <filename>conf/rc.conf</filename> 文件添加联网选项会更加方便。 + 该文件的语法跟 &os; 中标准的 &man.rc.conf.5; 文件的语法相同。</para> + + <para>例如,当你知道被使用的将是一个 &man.re.4; 网络接口设备, + 你可以在 <filename>conf/rc.conf</filename> 文件中设置如下选项:</para> + + <programlisting>defaultrouter="192.168.0.1" +ifconfig_re0="inet 192.168.0.2 netmask 255.255.255.0"</programlisting> + </sect3> + </sect2> + + <sect2 xml:id="mfsbsd-build"> + <title>构建一个 <application>mfsBSD</application> 映像</title> + + <para>构建一个 <application>mfsBSD</application> + 映像文件的过程是非常简单明了的。</para> + + <para>第一步是挂载 &os; 的安装 <acronym>CD</acronym>, + 或者挂载安装 <acronym>ISO</acronym> 文件到 + <filename>/cdrom</filename>。 + 因为例子的缘故,在文章中我们将假定你下载的是 &os; 7.0-RELEASE + <acronym>ISO</acronym> 文件。使用 &man.mdconfig.8; + 实用程序挂载 <acronym>ISO</acronym> 映像文件到 + <filename>/cdrom</filename> 目录非常简单:</para> + + <screen>&prompt.root; <userinput>mdconfig -a -t vnode -u 10 -f 7.0-RELEASE-amd64-disc1.iso</userinput> +&prompt.root; <userinput>mount_cd9660 /dev/md10 /cdrom</userinput></screen> + + <para>紧接着,构建可启动的 <application>mfsBSD</application> + 映像:</para> + + <screen>&prompt.root; <userinput>make BASE=/cdrom/7.0-RELEASE</userinput></screen> + + <note> + <para>上面的 <command>make</command> 命令必须在 + <application>mfsBSD</application> 目录树的最高一层运行,也就是: + <filename>~/mfsbsd-1.0-beta1/</filename>。</para> + </note> + </sect2> + + <sect2> + <title>启动 <application>mfsBSD </application></title> + + <para>现在 <application>mfsBSD</application> 映像已经准备好了, + 必须把它上传到远程的一个正在运行的急救系统上或者一个预安装了 + &linux; 发行版的系统上。最适合做这个工作的工具是 + <application>scp</application>:</para> + + <screen>&prompt.root; <userinput>scp disk.img root@192.168.0.2:.</userinput></screen> + + <para>想要正确的引导 <application>mfsBSD</application> 映像, + 必须把它安放在机器的第一块(可启动)设备上。 + 这可能会和使用的例子我们假定的一样,第一块可启动磁盘设备是 + <filename>sda</filename>:</para> + + <screen>&prompt.root; <userinput>dd if=/root/disk.img of=/dev/sda bs=1m</userinput></screen> + + <para>如果一切正常,该映像现在应该存在于第一块设备的 + <acronym>MBR</acronym>(主引导区)而机器也应该能够被启动了。 + 使用工具 &man.ping.8; 来查看机器是否被正确启动。 + 一旦它回复在线状态,就应该能够使用 <systemitem class="username">root</systemitem> + 用户和配置好的密码通过 &man.ssh.1; 来访问它了。</para> + </sect2> + </sect1> + + <sect1 xml:id="installation"> + <title>&os; 操作系统的安装</title> + + <para><application>mfsBSD</application> 成功被引导后它就应该能够通过 + &man.ssh.1; 登入了。这一节会描述如何创建 slices 并标记 slices 的 label, + 为 RAID-1 配置 <application>gmirror</application>, + 还有如何使用 <application>sysinstall</application> + 来安装一个最小的FreeBSD操作系统版本。</para> + + <sect2> + <title>准备磁盘</title> + + <para>首要的任务是为 &os; 分配磁盘空间,也就是, + 创建 slices 和 partitions。很显然, + 当前运行的系统是全部被载入到系统内存中的因此操作磁盘将没有任何问题。 + 要完成这个任务,可以是使用 <application>sysinstall</application> + 或者 &man.fdisk.8; 中的二者任一并结合工具 &man.bsdlabel.8;。</para> + + <para>在开始时,将所有磁盘都标记成空的, + 在每个磁盘上重复如下命令:</para> + + <screen>&prompt.root; <userinput>dd if=/dev/zero of=/dev/ad0 count=2</userinput></screen> + + <para>下面,使用你喜欢的工具创建 slices 并标记磁盘 label。 + 比较简单的方法是使用 <application>sysinstall</application>, + 强大也可能几乎没有漏洞方法是使用标准的基于文本的 &unix; 工具, + 类似于 &man.fdisk.8; 和 &man.bsdlabel.8; + 这些工具的使用也会在这一节中包括。前者已经被包括在 &os; 手册的 + <link xlink:href="&url.books.handbook;/install-steps.html">安装FreeBSD</link> + 一章中了。如本节中刚提到的,这篇文章会展示如何设置一个带有 RAID-1 + 和 <application>ZFS</application> 性能的系统。我们的设置由一个小工具 + &man.gmirror.8; 镜像为 <filename>/</filename> (root), + <filename>/usr</filename> 和 + <filename>/var</filename> 文件系统, + 并把剩余的磁盘空间被分配为 &man.zpool.8; 镜像出的 + <application>ZFS</application> 文件系统。请注意, + <application>ZFS</application> 文件系统将在 &os; + 操作系统成功安装并启动后才会被配置。</para> + + <para>下面的例子会描述如何去创建 slices 和 labels, + 在每个 partition 上初始化 &man.gmirror.8; + 并如何在每个被镜像过的 partition 上创建 + <application>UFS2</application> 文件系统:</para> + + <screen>&prompt.root; <userinput>fdisk -BI /dev/ad0</userinput> <co xml:id="fdisk"/> +&prompt.root; <userinput>fdisk -BI /dev/ad1</userinput> +&prompt.root; <userinput>bsdlabel -wB /dev/ad0s1</userinput> <co xml:id="bsdlabel-writing"/> +&prompt.root; <userinput>bsdlabel -wB /dev/ad1s1</userinput> +&prompt.root; <userinput>bsdlabel -e /dev/ad0s1</userinput> <co xml:id="bsdlabel-editing"/> +&prompt.root; <userinput>bsdlabel /dev/ad0s1 > /tmp/bsdlabel.txt && bsdlabel -R /dev/ad1s1 /tmp/bsdlabel.txt</userinput> <co xml:id="bsdlabel-restore"/> +&prompt.root; <userinput>gmirror label root /dev/ad[01]s1a</userinput> <co xml:id="gmirror1"/> +&prompt.root; <userinput>gmirror label var /dev/ad[01]s1d</userinput> +&prompt.root; <userinput>gmirror label usr /dev/ad[01]s1e</userinput> +&prompt.root; <userinput>gmirror label -F swap /dev/ad[01]s1b</userinput> <co xml:id="gmirror2"/> +&prompt.root; <userinput>newfs /dev/mirror/root</userinput> <co xml:id="newfs"/> +&prompt.root; <userinput>newfs /dev/mirror/var</userinput> +&prompt.root; <userinput>newfs /dev/mirror/usr</userinput></screen> + + <calloutlist> + <callout arearefs="fdisk"> + <para>在整个磁盘上创建一个 slice + 并初始化包含在磁盘第一个扇区启动代码。 + 重复在系统上全部的磁盘上执行此命令。</para> + </callout> + + <callout arearefs="bsdlabel-writing"> + <para>为每块磁盘写入一个包括启动代码的内容的标准 + label。</para> + </callout> + + <callout arearefs="bsdlabel-editing"> + <para>现在,手动去编辑磁盘的 label。可以查阅 &man.bsdlabel.8; + 的联机手册来找到如何建立 partitions 的方法。创建如下 + partions,<literal>a</literal> 为 + <filename>/</filename> (root) 文件系统, + <literal>b</literal> 为 swap 交换空间, + <literal>d</literal> 为 + <filename>/usr</filename> + 还有最后 <literal>f</literal> 被用于 + <application>ZFS</application>。</para> + </callout> + + <callout arearefs="bsdlabel-restore"> + <para>引入你刚才创建的 label 到第二块磁盘, + 所以两块磁盘会使用同样的 label。</para> + </callout> + + <callout arearefs="gmirror1"> + <para>在每个 partition 上初始化 &man.gmirror.8;。</para> + </callout> + + <callout arearefs="gmirror2"> + <para>注意 <option>-F</option> 选项被用在 swap + 交换分区的 partition。 &man.gmirror.8; + 这个指令认为设备处于可靠的状态除非电源系统故障。</para> + </callout> + + <callout arearefs="newfs"> + <para>在每个被镜像的分区上创建一个 + <application>UFS2</application> 的文件系统。</para> + </callout> + </calloutlist> + </sect2> + + <sect2> + <title>系统安装</title> + + <para>这是最重要的一部分。 + 此节将描述如何在我们上一小节已经准备好的磁盘上安装一个最小的 + &os; 版本。要达成这个目的,所有的文件安系统需要被挂载乃至于 + <application>sysinstall</application> 可以把 &os; + 系统的内容写到磁盘上:</para> + + <screen>&prompt.root; <userinput>mount /dev/mirror/root /mnt</userinput> +&prompt.root; <userinput>mkdir /mnt/var /mnt/usr</userinput> +&prompt.root; <userinput>mount /dev/mirror/var /mnt/var</userinput> +&prompt.root; <userinput>mount /dev/mirror/usr /mnt/usr</userinput></screen> + + <para>当你做完这些时,打开 &man.sysinstall.8;。 + 从主菜单选择自定义 <guimenuitem>Custom</guimenuitem> 安装。 + 选中 <guimenuitem>Options</guimenuitem> 选项然后按回车确认。 + 使用方向键获取帮助,移动鼠标指针到 <literal>Install Root</literal> + 选项,按 <keycap>空格</keycap> 更改为 + <filename>/mnt</filename>。 + 按 <keycap>回车</keycap> 提交你的更改并使用 + <keycap>q</keycap> 退出 <guimenuitem>Options</guimenuitem> + (选项)菜单。</para> + + <warning> + <para>注意这一步骤非常重要,如果被跳过了, + <application>sysinstall</application> + 将不能安装 &os;。</para> + </warning> + + <para>到 <guimenuitem>Distributions</guimenuitem>(发行版)菜单选项, + 使用方向键移动鼠标指针到 <option>Minimal</option>(最小化)选项, + 并使用 <keycap>空格键</keycap> 选中该选项。 + 本文使用了最小版本来保存网络联通信息,因为系统本身会通过 + <application>ftp</application> 来安装。使用 + <option>Exit</option>(退出)选项退出这个菜单。</para> + + <note> + <para><guimenuitem>Partition</guimenuitem> 和 + <guimenuitem>Label</guimenuitem> 菜单将被跳过, + 这些没有多少价值了。</para> + </note> + + <para><guimenuitem>Media</guimenuitem>(媒介)菜单, + 选择 <option>FTP</option> 选项。 + 选择一个距离你最近的镜像站点并交给 + <application>sysinstall</application> + 假定网络已经配置完好。你将再回到 + <guimenuitem>Custom</guimenuitem> + (自定义)菜单。</para> + + <para>最后,选择最后的选项来执行系统的安装过程, + <guimenuitem>Commit</guimenuitem>, + 当安装完成后退出 <application>sysinstall</application> + 即可。</para> + </sect2> + + <sect2> + <title>后期安装步骤</title> + + <para>&os; 操作系统现在应该安装完毕了;通常情况下, + 安装过程还没有结束。还需要进行一些安装后期的步骤使得容许 + &os; 在将来启动并能够登入系统。</para> + + <para>你现在必须 &man.chroot.8; + 到刚安装的全新的系统中来完成安装。 + 使用如下命令: </para> + + <screen>&prompt.root; <userinput>chroot /mnt</userinput></screen> + + <para>要达到我们的目的,进行如下步骤:</para> + + <itemizedlist> + <listitem> + <para>拷贝 <literal>GENERIC</literal>(通用)内核到 + <filename>/boot/kernel</filename> + 目录:</para> + + <screen>&prompt.root; <userinput>cp -Rp /boot/GENERIC/* /boot/kernel</userinput></screen> + </listitem> + + <listitem> + <para>创建 <filename>/etc/rc.conf</filename>, + <filename>/etc/resolv.conf</filename> 还有 + <filename>/etc/fstab</filename> 文件。 + 不要忘记正确地设置网络信息并在 + <filename>/etc/rc.conf</filename> + 文件中启用 <application>sshd</application>。 + <filename>/etc/fstab</filename> + 文件内容类似于下面的内容:</para> + + <programlisting># Device Mountpoint FStype Options Dump Pass# +/dev/mirror/swap none swap sw 0 0 +/dev/mirror/root / ufs rw 1 1 +/dev/mirror/usr /usr ufs rw 2 2 +/dev/mirror/var /var ufs rw 2 2 +/dev/cd0 /cdrom cd9660 ro,noauto 0 0</programlisting> + </listitem> + + <listitem> + <para>创建 <filename>/boot/loader.conf</filename> + 文件,并写入如下内容:</para> + + <programlisting>geom_mirror_load="YES" +zfs_load="YES"</programlisting> + </listitem> + + <listitem> + <para>执行下面的命令,使得 + <application>ZFS</application> 在下次启动后可用:</para> + + <screen>&prompt.root; <userinput>echo 'zfs_enable="YES"' >> /etc/rc.conf </userinput></screen> + </listitem> + + <listitem> + <para>可以用 &man.adduser.8; 工具来添加额外的用户。 + 不要忘记添加一个用户到 <systemitem class="groupname">wheel</systemitem> + 组,这样你可以在重新启动后获得 root 权限。</para> + </listitem> + + <listitem> + <para>反复检验你的设置是否正确。</para> + </listitem> + </itemizedlist> + + <para>现在你的系统在下次启动后应该可用了。使用 + &man.reboot.8; 命令重新启动你的系统。</para> + </sect2> + </sect1> + + <sect1 xml:id="zfs"> + <title>ZFS</title> + + <para>如果你的系统重新启动后还完好,现在应该能够登入了。 + 欢迎来到崭新的 &os; 安装,进行远程的不使用远程控制台的安装。</para> + + <para>最后还剩下的步骤是配置 &man.zpool.8; 并创建一些 + &man.zfs.8; 文件系统。建立并管理 + <application>ZFS</application> 非常简单。 + 首先,创建一个镜像的pool:</para> + + <screen>&prompt.root; <userinput>zpool create tank mirror /dev/ad[01]s1f</userinput></screen> + + <para>再接着,创建一些文件系统:</para> + + <screen>&prompt.root; <userinput>zfs create tank/ports</userinput> +&prompt.root; <userinput>zfs create tank/src</userinput> +&prompt.root; <userinput>zfs set compression=gzip tank/ports</userinput> +&prompt.root; <userinput>zfs set compression=on tank/src</userinput> +&prompt.root; <userinput>zfs set mountpoint=/usr/ports tank/ports</userinput> +&prompt.root; <userinput>zfs set mountpoint=/usr/src tank/src</userinput></screen> + + <para>这就是全部步骤了。如果你对 &os; 上的 + <application>ZFS</application> 感兴趣,请查阅 &os; WIKI 中的 + <link xlink:href="http://wiki.freebsd.org/ZFS">ZFS</link> 一节。</para> + </sect1> +</article> diff --git a/zh_CN.UTF-8/books/Makefile b/zh_CN.UTF-8/books/Makefile new file mode 100644 index 0000000000..4297aff018 --- /dev/null +++ b/zh_CN.UTF-8/books/Makefile @@ -0,0 +1,14 @@ +# +# The FreeBSD Simplified Chinese Project +# +# Original Revision: 1.14 +# $FreeBSD$ + +SUBDIR = arch-handbook +SUBDIR+= handbook +SUBDIR+= porters-handbook + +ROOT_SYMLINKS= handbook + +DOC_PREFIX?= ${.CURDIR}/../.. +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/books/Makefile.inc b/zh_CN.UTF-8/books/Makefile.inc new file mode 100644 index 0000000000..e7b5457951 --- /dev/null +++ b/zh_CN.UTF-8/books/Makefile.inc @@ -0,0 +1,6 @@ +# +# Original Revision: 1.4 +# $FreeBSD$ +# + +DESTDIR?= ${DOCDIR}/zh_CN.UTF-8/books/${.CURDIR:T} diff --git a/zh_CN.UTF-8/books/arch-handbook/Makefile b/zh_CN.UTF-8/books/arch-handbook/Makefile new file mode 100644 index 0000000000..d9a4337a92 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/Makefile @@ -0,0 +1,58 @@ +# +# Original Revision: 1.25 +# $FreeBSD$ +# +# Build the FreeBSD Architecture Handbook. +# + +MAINTAINER=doc@FreeBSD.org + +DOC?= book + +FORMATS?= html-split + +INSTALL_COMPRESSED?= gz +INSTALL_ONLY_COMPRESSED?= + +# +# SRCS lists the individual XML files that make up the document. Changes +# to any of these files will force a rebuild +# + +# XML content +SRCS= book.xml +SRCS+= boot/chapter.xml +SRCS+= driverbasics/chapter.xml +SRCS+= isa/chapter.xml +SRCS+= jail/chapter.xml +SRCS+= kobj/chapter.xml +SRCS+= locking/chapter.xml +SRCS+= mac/chapter.xml +SRCS+= newbus/chapter.xml +SRCS+= pci/chapter.xml +SRCS+= scsi/chapter.xml +SRCS+= smp/chapter.xml +SRCS+= sound/chapter.xml +SRCS+= pccard/chapter.xml +SRCS+= sysinit/chapter.xml +SRCS+= usb/chapter.xml +SRCS+= vm/chapter.xml + +# Images from the cross-document image library +IMAGES_LIB= callouts/1.png +IMAGES_LIB+= callouts/2.png +IMAGES_LIB+= callouts/3.png +IMAGES_LIB+= callouts/4.png +IMAGES_LIB+= callouts/5.png +IMAGES_LIB+= callouts/6.png +IMAGES_LIB+= callouts/7.png +IMAGES_LIB+= callouts/8.png +IMAGES_LIB+= callouts/9.png +IMAGES_LIB+= callouts/10.png + +# Entities + +URL_RELPREFIX?= ../../../.. +DOC_PREFIX?= ${.CURDIR}/../../.. + +.include "${DOC_PREFIX}/share/mk/doc.project.mk" diff --git a/zh_CN.UTF-8/books/arch-handbook/book.xml b/zh_CN.UTF-8/books/arch-handbook/book.xml new file mode 100644 index 0000000000..685341953b --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/book.xml @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE book PUBLIC "-//FreeBSD//DTD DocBook XML V5.0-Based Extension//EN" + "http://www.FreeBSD.org/XML/share/xml/freebsd50.dtd" [ +<!ENTITY % chapters SYSTEM "chapters.ent"> +%chapters; +<!ENTITY % mac-entities SYSTEM "mac.ent"> +%mac-entities; +]> +<!-- + The FreeBSD Documentation Project + The FreeBSD Simplified Chinese Project + + Original Revision: 1.51 + $FreeBSD$ +--> +<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:lang="zh_cn"> + <info><title>&os; 系统结构手册</title> + + + <author><orgname>The FreeBSD Documentation Project</orgname></author> + + <pubdate>2000 年 8 月</pubdate> + + <copyright> + <year>2000</year> + <year>2001</year> + <year>2002</year> + <year>2003</year> + <year>2004</year> + <year>2005</year> + <year>2006</year> + <holder>The FreeBSD Documentation Project</holder> + </copyright> + + <author><orgname>&cnproj.freebsd.org;</orgname></author> + + <pubdate>2005 年 12 月</pubdate> + + <copyright> + <year>2004</year> + <year>2005</year> + <year>2006</year> + <holder>&cnproj.freebsd.org;</holder> + </copyright> + + &trademarks; + &legalnotice; + + <releaseinfo>$FreeBSD$</releaseinfo> + + <abstract> + + <!-- + The following two entities "contributing.to.freebsd.doc" and + "getting.freebsd.doc" are defined in zh_CN.UTF-8/share/xml/l10n.ent + --> + + &cnproj.contributing.to.freebsd.doc; + + &cnproj.getting.freebsd.doc; + </abstract> + </info> + + <part xml:id="kernel"> + <title>内核</title> + + &chap.boot; + &chap.locking; + &chap.kobj; + &chap.jail; + &chap.sysinit; + &chap.mac; + &chap.vm; + &chap.smp; + + </part> + + <part xml:id="devicedrivers"> + <title>设备驱动程序</title> + + &chap.driverbasics; + &chap.isa; + &chap.pci; + &chap.scsi; + &chap.usb; + &chap.newbus; + + &chap.snd; + &chap.pccard; + + </part> + +<!-- XXX - finish me + <part id="architectures"> + <title>Architectures</title> + + <chapter id="i386"> + <title>* I386</title> + + <para>Talk about <literal>i386</literal> specific &os; + architecture.</para> + </chapter> + + <chapter id="alpha"> + <title>* Alpha</title> + + <para>Talk about the architectural specifics of + FreeBSD/alpha.</para> + </chapter> + + <chapter id="ia64"> + <title>* IA-64</title> + + <para>Talk about the architectural specifics of + FreeBSD/ia64.</para> + + </chapter> + + <chapter id="sparc64"> + <title>* SPARC64</title> + + <para>Talk about <literal>SPARC64</literal> specific &os; + architecture.</para> + </chapter> + + <chapter id="amd64"> + <title>* AMD64</title> + + <para>Talk about <literal>AMD64</literal> specific &os; + architecture.</para> + </chapter> + + <chapter id="powerpc"> + <title>* PowerPC</title> + + <para>Talk about <literal>PowerPC</literal> specific &os; + architecture.</para> + </chapter> + </part> +--> + + <part xml:id="appendices"> + <title>附录</title> + + <bibliography> + + <biblioentry xreflabel="1"> + <authorgroup> + <author><personname><firstname>Marshall</firstname><othername role="Middle">Kirk</othername><surname>McKusick</surname></personname></author> + <author><personname><firstname>Keith</firstname><surname>Bostic</surname></personname></author> + <author><personname><firstname>Michael</firstname><othername role="MI">J</othername><surname>Karels</surname></personname></author> + <author><personname><firstname>John</firstname><othername role="MI">S</othername><surname>Quarterman</surname></personname></author> + </authorgroup> + <copyright><year>1996</year><holder>Addison-Wesley Publishing Company, + Inc.</holder></copyright> + <biblioid class="isbn">0-201-54979-4</biblioid> + <publisher> + <publishername>Addison-Wesley Publishing Company, Inc.</publishername> + </publisher> + <citetitle>The Design and Implementation of the 4.4 BSD Operating System</citetitle> + <pagenums>1-2</pagenums> + </biblioentry> + + </bibliography> + </part> + + &chap.index; + +</book> diff --git a/zh_CN.UTF-8/books/arch-handbook/boot/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/boot/chapter.xml new file mode 100644 index 0000000000..74cf854717 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/boot/chapter.xml @@ -0,0 +1,915 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +The FreeBSD Documentation Project +The FreeBSD Simplified Chinese Project + +Original Revision: 1.28 + +Copyright (c) 2002 Sergey Lyubka <devnull@uptsoft.com> +All rights reserved +$FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="boot"> + <info><title>引导过程与内核初始化</title> + <authorgroup> + <author><personname><firstname>Sergey</firstname><surname>Lyubka</surname></personname><contrib>&cnproj.contributed.by;</contrib></author> <!-- devnull@uptsoft.com 12 Jun 2002 --> + </authorgroup> + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + </info> + + + <sect1 xml:id="boot-synopsis"> + <title>概述</title> + + <indexterm><primary>BIOS(基本输入输出系统, Basic Input Output System)</primary></indexterm> + <indexterm><primary>fireware(固件)</primary></indexterm> + <indexterm><primary>POST(加电自检, Power On Self Test)</primary></indexterm> + <indexterm><primary>IA-32</primary></indexterm> + <indexterm><primary>booting(引导)</primary></indexterm> + <indexterm><primary>system initialization(系统初始化)</primary></indexterm> + <para>这一章是对引导过程和系统初始化过程的总览。这些过程始于BIOS(固件)POST, + 直到第一个用户进程建立。由于系统启动的最初步骤是与硬件结构相关的、是紧配合的, + 这里用IA-32(Intel Architecture 32bit)结构作为例子。</para> + </sect1> + + <sect1 xml:id="boot-overview"> + <title>总览</title> + + <para>一台运行FreeBSD的计算机有多种引导方法。这里讨论其中最通常的方法, + 也就是从安装了操作系统的硬盘上引导。引导过程分几步完成:</para> + + <itemizedlist> + <listitem><para>BIOS POST</para></listitem> + <listitem><para><literal>boot0</literal>阶段</para></listitem> + <listitem><para><literal>boot2</literal>阶段</para></listitem> + <listitem><para>loader阶段</para></listitem> + <listitem><para>内核初始化</para></listitem> + </itemizedlist> + + <indexterm><primary>BIOS POST</primary></indexterm> + <indexterm><primary>boot0</primary></indexterm> + <indexterm><primary>boot2</primary></indexterm> + <indexterm><primary>loader</primary></indexterm> + <para><literal>boot0</literal>和<literal>boot2</literal>阶段在手册 + &man.boot.8;中被称为<emphasis>bootstrap stages 1 and 2</emphasis>, + 是FreeBSD的3阶段引导过程的开始。在每一阶段都有各种各样的信息显示在屏幕上, + 你可以参考下表识别出这些步骤。请注意实际的显示内容可能随机器的不同而有一些区别: + </para> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="2"> + <tbody> + <row> + <entry><para>视不同机器而定</para></entry> + + <entry><para>BIOS(固件)消息</para></entry> + </row> + + <row> + <entry><para> +<screen>F1 FreeBSD +F2 BSD +F5 Disk 2</screen> + </para></entry> + + <entry><para><literal>boot0</literal></para></entry> + </row> + + <row> + <entry><para> +<screen>>>FreeBSD/i386 BOOT +Default: 1:ad(1,a)/boot/loader +boot:</screen> + </para></entry> + + <entry><para><literal>boot2</literal><footnote><para> + 这种提示仅在<literal>boot0</literal>阶段用户选择操作系统后 + 仍按住键盘上某一键时才出现。</para></footnote></para></entry> + </row> + + <row> + <entry><para> +<screen>BTX loader 1.0 BTX version is 1.01 +BIOS drive A: is disk0 +BIOS drive C: is disk1 +BIOS 639kB/64512kB available memory +FreeBSD/i386 bootstrap loader, Revision 0.8 +Console internal video/keyboard +(jkh@bento.freebsd.org, Mon Nov 20 11:41:23 GMT 2000) +/kernel text=0x1234 data=0x2345 syms=[0x4+0x3456] +Hit [Enter] to boot immediately, or any other key for command prompt +Booting [kernel] in 9 seconds..._</screen> + </para></entry> + + <entry><para>loader</para></entry> + </row> + + <row> + <entry><para> + <screen>Copyright (c) 1992-2002 The FreeBSD Project. +Copyright (c) 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994 + The Regents of the University of California. All rights reserved. +FreeBSD 4.6-RC #0: Sat May 4 22:49:02 GMT 2002 + devnull@kukas:/usr/obj/usr/src/sys/DEVNULL +Timecounter "i8254" frequency 1193182 Hz</screen></para></entry> + + <entry><para>内核</para></entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect1> + + <sect1 xml:id="boot-bios"> + <title>BIOS POST</title> + + <para>当PC加电后,处理器的寄存器被设为某些特定值。在这些寄存器中, + <emphasis>指令指针</emphasis>寄存器被设为32位值0xfffffff0。 + 指令指针寄存器指向处理器将要执行的指令代码。<literal>cr1</literal>, + 一个32位控制寄存器,在刚启动时值被设为0。cr1的PE(Protected Enabled, + 保护模式使能)位用来指示处理器是处于保护模式还是实地址模式。 + 由于启动时该位被清位,处理器在实地址模式中引导。在实地址模式中, + 线性地址与物理地址是等同的。</para> + + <para>值0xfffffff0略小于4G,因此计算机没有4G字节物理内存, + 这就不会是一个有效的内存地址。计算机硬件将这个地址转指向BIOS存储块。 + </para> + + <para>BIOS表示<emphasis>Basic Input Output System</emphasis> + (基本输入输出系统)。在主板上,它被固化在一个相对容量较小的 + 只读存储器(Read-Only Memory, ROM)。BIOS包含各种各样为主板硬件 + 定制的底层例程。就这样,处理器首先指向常驻BIOS存储器的地址 + 0xfffffff0。通常这个位置包含一条跳转指令,指向BIOS的POST例程。</para> + + <para>POST表示<emphasis>Power On Self Test</emphasis>(加电自检)。 + 这套程序包括内存检查,系统总线检查和其它底层工具, + 从而使得CPU能够初始化整台计算机。这一阶段中有一个重要步骤, + 就是确定引导设备。现在所有的BIOS都允许手工选择引导设备。 + 你可以从软盘、光盘驱动器、硬盘等设备引导。</para> + + <para>POST的最后一步是执行<literal>INT 0x19</literal>指令。 + 这个指令从引导设备第一个扇区读取512字节,装入地址0x7c00。 + <emphasis>第一个扇区</emphasis>的说法最早起源于硬盘的结构, + 硬盘面被分为若干圆柱形轨道。给轨道编号,同时又将轨道分为 + 一定数目(通常是64)的扇形。0号轨道是硬盘的最外圈,1号扇区, + 第一个扇区(轨道、柱面都从0开始编号,而扇区从1开始编号) + 有着特殊的作用,它又被称为主引导记录(Master Boot Record, MBR)。 + 第一轨剩余的扇区常常不使用<footnote><para>有些工具如&man.disklabel.8; + 会使用这一区域存储信息,主要是在第二扇区里。</para></footnote>。</para> + </sect1> + + <sect1 xml:id="boot-boot0"> + <title><literal>boot0</literal>阶段</title> + + <indexterm><primary>MBR (主引导记录)</primary></indexterm> + <para>让我们看一下文件<filename>/boot/boot0</filename>。 + 这是一个仅512字节的小文件。如果在FreeBSD安装过程中选择 + <quote>bootmanager</quote>,这个文件中的内容将被写入硬盘MBR</para> + + <para>如前所述, <literal>INT 0x19</literal> 指令装载 MBR, + 也就是 <filename>boot0</filename> 的内容至内存地址 0x7c00。 + 再看文件 <filename>sys/boot/i386/boot0/boot0.S</filename>, + 可以猜想这里面发生了什么 - 这是引导管理器, + 一段由 Robert Nordier书写的令人起敬的程序片段。</para> + + <para>MBR里,也就是<filename>boot0</filename>里, + 从偏移量0x1be开始有一个特殊的结构,称为 + <emphasis>分区表</emphasis>。其中有4条记录 + (称为<emphasis>分区记录</emphasis>),每条记录16字节。 + 分区记录表示硬盘如何被划分,在FreeBSD的术语中, + 这被称为slice(d)。16字节中有一个标志字节决定这个分区是否可引导。 + 有仅只能有一个分区可设定这一标志。否则, + <filename>boot0</filename>的代码将拒绝继续执行。</para> + + <para>一个分区记录有如下域:</para> + + <itemizedlist> + <listitem> + <para>1字节 文件系统类型</para> + </listitem> + + <listitem> + <para>1字节 可引导标志</para> + </listitem> + + <listitem> + <para>6字节 CHS格式描述符</para> + </listitem> + + <listitem> + <para>8字节 LBA格式描述符</para> + </listitem> + </itemizedlist> + + <para>一个分区记录描述符包含某一分区在硬盘上的确切位置信息。 + LBA和CHS两种描述符指示相同的信息,但是指示方式有所不同:LBA + (逻辑块寻址,Logical Block Addressing)指示分区的起始扇区和分区长度, + 而CHS(柱面 磁头 扇区)指示首扇区和末扇区</para> + + <para>引导管理器扫描分区表,并在屏幕上显示菜单,以便用户可以 + 选择用于引导的磁盘和分区。在键盘上按下相应的键后, + <filename>boot0</filename>进行如下动作:</para> + + <itemizedlist> + <listitem> + <para>标记选中的分区为可引导,清除以前的可引导标志</para> + </listitem> + + <listitem> + <para>记住本次选择的分区以备下次引导时作为缺省项</para> + </listitem> + + <listitem> + <para>装载选中分区的第一个扇区,并跳转执行之</para> + </listitem> + </itemizedlist> + + <para>什么数据会存在于一个可引导扇区(这里指FreeBSD扇区)的第一扇区里呢? + 正如你已经猜到的,那就是<filename>boot2</filename>。</para> + </sect1> + + <sect1 xml:id="boot-boot2"> + <title><literal>boot2</literal>阶段</title> + + <para>也许你想知道,为什么<literal>boot2</literal>是在 + <literal>boot0</literal>之后,而不是在boot1之后。事实上, + 也有一个512字节的文件<filename>boot1</filename>存放在目录 + <filename>/boot</filename>里,那是用来从一张软盘引导系统的。 + 从软盘引导时,<filename>boot1</filename>起着 + <filename>boot0</filename>对硬盘引导相同的作用:它找到 + <filename>boot2</filename>并运行之。</para> + + <para>你可能已经看到有一文件<filename>/boot/mbr</filename>。 + 这是<filename>boot0</filename>的简化版本。 + <filename>mbr</filename>中的代码不会显示菜单让用户选择, + 而只是简单的引导被标志的分区。</para> + + <para>实现<filename>boot2</filename>的代码存放在目录 + <filename>sys/boot/i386/boot2/</filename>里,对应的可执行文件在 + <filename>/boot</filename>里。在<filename>/boot</filename>里的文件 + <filename>boot0</filename>和<filename>boot2</filename>不会在引导过程中使用, + 只有<application>boot0cfg</application>这样的工具才会使用它们。 + <filename>boot0</filename>的内容应在MBR中才能生效。 + <filename>boot2</filename>位于可引导的FreeBSD分区的开始。 + 这些位置不受文件系统控制,所以它们不可用<application>ls</application> + 之类的命令查看。</para> + + <para><literal>boot2</literal>的主要任务是装载文件 + <filename>/boot/loader</filename>,那是引导过程的第三阶段。 + 在<literal>boot2</literal>中的代码不能使用诸如 + <function>open()</function>和<function>read()</function> + 之类的例程函数,因为内核还没有被加载。而应当扫描硬盘, + 读取文件系统结构,找到文件<filename>/boot/loader</filename>, + 用BIOS的功能将它读入内存,然后从其入口点开始执行之。</para> + + <para>除此之外,<literal>boot2</literal>还可提示用户进行选择, + loader可以从其它磁盘、系统单元、分区装载。</para> + + <para><literal>boot2</literal> 的二进制代码用特殊的方式产生:</para> + + <programlisting><filename>sys/boot/i386/boot2/Makefile</filename> +boot2: boot2.ldr boot2.bin ${BTX}/btx/btx + btxld -v -E ${ORG2} -f bin -b ${BTX}/btx/btx -l boot2.ldr \ + -o boot2.ld -P 1 boot2.bin</programlisting> + + <indexterm><primary>BTX</primary></indexterm> + <para>这个Makefile片断表明&man.btxld.8;被用来链接二进制代码。 + BTX表示引导扩展器(BooT eXtender)是给程序(称为客户(client) + 提供保护模式环境、并与客户程序相链接的一段代码。所以 + <literal>boot2</literal>是一个BTX客户,使用BTX提供的服务。</para> + + <indexterm><primary>linker(链接器)</primary></indexterm> + <para>工具<application>btxld</application>是链接器, + 它将两个二进制代码链接在一起。&man.btxld.8;和&man.ld.1; + 的区别是<application>ld</application>通常将两个目标文件 + 链接成一个动态链接库或可执行文件,而<application>btxld</application> + 则将一个目标文件与BTX链接起来,产生适合于放在分区首部的二进制代码, + 以实现系统引导。</para> + + <para><literal>boot0</literal>执行跳转至BTX的入口点。 + 然后,BTX将处理器切换至保护模式,并准备一个简单的环境, + 然后调用客户。这个环境包括:</para> + + <indexterm><primary>virtual 8086 mode(虚拟8086模式)</primary></indexterm> + <itemizedlist> + <listitem><para>虚拟8086模式。这意味着BTX是虚拟8086的监视程序。 + 实模式指令,如pushf, popf, cli, sti, if,均可被客户调用。</para></listitem> + + <listitem><para>建立中断描述符表(Interrupt Descriptor Table, IDT), + 使得所有的硬件中断可被缺省的BIOS程序处理。 + 建立中断0x30,这是系统调用关口。</para></listitem> + + <listitem><para>两个系统调用<function>exec</function>和 + <function>exit</function>的定义如下:</para> + + <programlisting><filename>sys/boot/i386/btx/lib/btxsys.s:</filename> + .set INT_SYS,0x30 # 中断号 +# +# System call: exit +# +__exit: xorl %eax,%eax # BTX系统调用0x0 + int $INT_SYS # +# +# System call: exec +# +__exec: movl $0x1,%eax # BTX系统调用0x1 + int $INT_SYS # </programlisting></listitem> + </itemizedlist> + + <para>BTX建立全局描述符表(Global Descriptor Table, GDT):</para> + + <programlisting><filename>sys/boot/i386/btx/btx/btx.s:</filename> +gdt: .word 0x0,0x0,0x0,0x0 # 以空为入口 + .word 0xffff,0x0,0x9a00,0xcf # SEL_SCODE + .word 0xffff,0x0,0x9200,0xcf # SEL_SDATA + .word 0xffff,0x0,0x9a00,0x0 # SEL_RCODE + .word 0xffff,0x0,0x9200,0x0 # SEL_RDATA + .word 0xffff,MEM_USR,0xfa00,0xcf# SEL_UCODE + .word 0xffff,MEM_USR,0xf200,0xcf# SEL_UDATA + .word _TSSLM,MEM_TSS,0x8900,0x0 # SEL_TSS</programlisting> + + <para>客户的代码和数据始于地址MEM_USR(0xa000),选择符(selector) + SEL_UCODE指向客户的数据段。选择符 SEL_UCODE 拥有第3级描述符权限 + (Descriptor Privilege Level, DPL),这是最低级权限。但是 + <literal>INT 0x30</literal> 指令的处理程序存储于另一个段里, + 这个段的选择符SEL_SCODE (supervisor code)由有着管理级权限。 + 正如代码建立IDT(中断描述符表)时进行的操作那样:</para> + + <programlisting> mov $SEL_SCODE,%dh # 段选择符 +init.2: shr %bx # 是否处理这个中断? + jnc init.3 # 否 + mov %ax,(%di) # 设置处理程序偏移量 + mov %dh,0x2(%di) # 设置处理程序选择符 + mov %dl,0x5(%di) # 设置 P:DPL:type + add $0x4,%ax # 下一个中断处理程序</programlisting> + + <para>所以,当客户调用 <function>__exec()</function>时,代码将被以最高权限执行。 + 这使得内核可以修改保护模式数据结构,如分页表(page tables)、全局描述符表(GDT)、 + 中断描述符表(IDT)等。</para> + + <para><literal>boot2</literal> 定义了一个重要的数据结构: + <literal>struct bootinfo</literal>。这个结构由 + <literal>boot2</literal> 初始化,然后被转送到loader,之后又被转入内核。 + 这个结构的部分项目由<literal>boot2</literal>设定,其余的由loader设定。 + 这个结构中的信息包括内核文件名、BIOS提供的硬盘柱面/磁头/扇区数目信息、 + BIOS提供的引导设备的驱动器编号,可用的物理内存大小,<literal>envp</literal> + 指针(环境指针)等。定义如下:</para> + + <programlisting><filename>/usr/include/machine/bootinfo.h</filename> +struct bootinfo { + u_int32_t bi_version; + u_int32_t bi_kernelname; /* 用一个字节表示 * */ + u_int32_t bi_nfs_diskless; /* struct nfs_diskless * */ + /* 以上为常备项 */ +#define bi_endcommon bi_n_bios_used + u_int32_t bi_n_bios_used; + u_int32_t bi_bios_geom[N_BIOS_GEOM]; + u_int32_t bi_size; + u_int8_t bi_memsizes_valid; + u_int8_t bi_bios_dev; /* 引导设备的BIOS单元编号 */ + u_int8_t bi_pad[2]; + u_int32_t bi_basemem; + u_int32_t bi_extmem; + u_int32_t bi_symtab; /* struct symtab * */ + u_int32_t bi_esymtab; /* struct symtab * */ + /* 以下项目仅高级bootloader提供 */ + u_int32_t bi_kernend; /* 内核空间末端 */ + u_int32_t bi_envp; /* 环境 */ + u_int32_t bi_modulep; /* 预装载的模块 */ +};</programlisting> + + <para><literal>boot2</literal> 进入一个循环等待用户输入,然后调用 + <function>load()</function>。如果用户不做任何输入,循环将在一段时间后结束, + <function>load()</function> 将会装载缺省文件(<filename>/boot/loader</filename>)。 + 函数 <function>ino_t lookup(char *filename)</function>和 + <function>int xfsread(ino_t inode, void *buf, size_t nbyte)</function> + 用来将文件内容读入内存。<filename>/boot/loader</filename>是一个ELF格式二进制文件, + 不过它的头部被换成了a.out格式中的<literal>struct exec</literal>结构。 + <function>load()</function>扫描loader的ELF头部,装载<filename>/boot/loader</filename> + 至内存,然后跳转至入口执行之:</para> + + <programlisting><filename>sys/boot/i386/boot2/boot2.c:</filename> + __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK), + MAKEBOOTDEV(dev_maj[dsk.type], 0, dsk.slice, dsk.unit, dsk.part), + 0, 0, 0, VTOP(&bootinfo));</programlisting> + </sect1> + + <sect1 xml:id="boot-loader"> + <title><application>loader</application>阶段</title> + + <para><application>loader</application>也是一个 BTX 客户,在这里不作详述。 + 已有一部内容全面的手册 &man.loader.8; ,由Mike Smith书写。 + 比loader更底层的BTX的机理已经在前面讨论过。</para> + + <para>loader 的主要任务是引导内核。当内核被装入内存后,即被loader调用:</para> + + <programlisting><filename>sys/boot/common/boot.c:</filename> + /* 从loader中调用内核中对应的exec程序 */ + module_formats[km->m_loader]->l_exec(km);</programlisting> + </sect1> + + <sect1 xml:id="boot-kernel"> + <title>内核初始化</title> + + <para>让我们来看一下链接内核的命令。 + 这能帮助我们了解 loader 传递给内核的准确位置。 + 这个位置就是内核真实的入口点。</para> + + <programlisting><filename>sys/conf/Makefile.i386:</filename> +ld -elf -Bdynamic -T /usr/src/sys/conf/ldscript.i386 -export-dynamic \ +-dynamic-linker /red/herring -o kernel -X locore.o \ +<lots of kernel .o files></programlisting> + + <indexterm><primary>ELF(可执行可链接格式)</primary></indexterm> + <para>在这一行中有一些有趣的东西。首先,内核是一个ELF动态链接二进制文件, + 可是动态链接器却是<filename>/red/herring</filename>,一个莫须有的文件。 + 其次,看一下文件<filename>sys/conf/ldscript.i386</filename>, + 可以对理解编译内核时<application>ld</application>的选项有一些启发。 + 阅读最前几行,字符串</para> + + <programlisting><filename>sys/conf/ldscript.i386:</filename> +ENTRY(btext)</programlisting> + + <para>表示内核的入口点是符号 `btext'。这个符号在<filename>locore.s</filename> + 中定义:</para> + + <programlisting><filename>sys/i386/i386/locore.s:</filename> + .text +/********************************************************************** + * + * This is where the bootblocks start us, set the ball rolling... + * 入口 + */ +NON_GPROF_ENTRY(btext)</programlisting> + + <para>首先将寄存器EFLAGS设为一个预定义的值0x00000002, + 然后初始化所有段寄存器:</para> + + <programlisting><filename>sys/i386/i386/locore.s</filename> +/* 不要相信BIOS给出的EFLAGS值 */ + pushl $PSL_KERNEL + popfl + +/* + * 不要相信BIOS给出的%fs、%gs值。相信引导过程中设定的%cs、%ds、%es、%ss值 + */ + mov %ds, %ax + mov %ax, %fs + mov %ax, %gs</programlisting> + + <para>btext调用例程<function>recover_bootinfo()</function>, + <function>identify_cpu()</function>,<function>create_pagetables()</function>。 + 这些例程也定在<filename>locore.s</filename>之中。这些例程的功能如下:</para> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="2" align="left"> + <tbody> + <row> + <entry><function>recover_bootinfo</function></entry> + + <entry>这个例程分析由引导程序传送给内核的参数。引导内核有3种方式: + 由loader引导(如前所述), 由老式磁盘引导块引导,无盘引导方式。 + 这个函数决定引导方式,并将结构<literal>struct bootinfo</literal> + 存储至内核内存。</entry> + </row> + + <row> + <entry><function>identify_cpu</function></entry> + + <entry>这个函数侦测CPU类型,将结果存放在变量 + <varname>_cpu</varname>中。</entry> + </row> + + <row> + <entry><function>create_pagetables</function></entry> + + <entry>这个函数为分页表在内核内存空间顶部分配一块空间,并填写一定内容</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>下一步是开启VME(如果CPU有这个功能):</para> + + <programlisting> testl $CPUID_VME, R(_cpu_feature) + jz 1f + movl %cr4, %eax + orl $CR4_VME, %eax + movl %eax, %cr4</programlisting> + + <para>然后,启动分页模式:</para> + <programlisting>/* Now enable paging */ + movl R(_IdlePTD), %eax + movl %eax,%cr3 /* load ptd addr into mmu */ + movl %cr0,%eax /* get control word */ + orl $CR0_PE|CR0_PG,%eax /* enable paging */ + movl %eax,%cr0 /* and let's page NOW! */</programlisting> + + <para>由于分页模式已经启动,原先的实地址寻址方式随即失效。 + 随后三行代码用来跳转至虚拟地址:</para> + + <programlisting> pushl $begin /* jump to high virtualized address */ + ret + +/* 现在跳转至KERNBASE,那里是操作系统内核被链接后真正的入口 */ +begin:</programlisting> + + <para>函数<function>init386()</function>被调用;随参数传递的是一个指针, + 指向第一个空闲物理页。随后执行<function>mi_startup()</function>。 + <function>init386</function>是一个与硬件系统相关的初始化函数, + <function>mi_startup()</function>是个与硬件系统无关的函数 + (前缀'mi_'表示Machine Independent,不依赖于机器)。 + 内核不再从<function>mi_startup()</function>里返回; + 调用这个函数后,内核完成引导:</para> + + <programlisting><filename>sys/i386/i386/locore.s:</filename> + movl physfree, %esi + pushl %esi /* 送给init386()的第一个参数 */ + call _init386 /* 设置386芯片使之适应UNIX工作 */ + call _mi_startup /* 自动配置硬件,挂接根文件系统,等 */ + hlt /* 不再返回到这里! */</programlisting> + + <sect2> + <title><function>init386()</function></title> + + <para><function>init386()</function>定义在 + <filename>sys/i386/i386/machdep.c</filename>中, + 它针对Intel 386芯片进行低级初始化。loader已将CPU切换至保护模式。 + loader已经建立了最早的任务。<tip><title>译者注</title> + <para>每个"任务"都是与其它“任务”相对独立的执行环境。 + 任务之间可以分时切换,这为并发进程/线程的实现提供了必要基础。 + 对于Intel 80x86任务的描述,详见Intel公司关于80386 CPU及后续产品的资料, + 或者在<link xlink:href="http://www.lib.tsinghua.edu.cn/">清华大学图书馆</link> + 馆藏记录中用"80386"作为关键词所查找到的系统结构方面的书目。</para></tip> + 在这个任务中,内核将继续工作。在讨论其代码前, + 我将处理器对保护模式必须完成的一系列准备工作一并列出:</para> + + <itemizedlist> + <listitem> + <para>初始化内核的可调整参数,这些参数由引导程序传来</para> + </listitem> + + <listitem> + <para>准备GDT(全局描述符表)</para> + </listitem> + + <listitem> + <para>准备IDT(中断描述符表)</para> + </listitem> + + <listitem> + <para>初始化系统控制台</para> + </listitem> + + <listitem> + <para>初始化DDB(内核的点调试器),如果它被编译进内核的话</para> + </listitem> + + <listitem> + <para>初始化TSS(任务状态段)</para> + </listitem> + + <listitem> + <para>准备LDT(局部描述符表)</para> + </listitem> + + <listitem> + <para>建立proc0(0号进程,即内核的进程)的pcb(进程控制块)</para> + </listitem> + </itemizedlist> + + <indexterm><primary>parameters(参数)</primary></indexterm> + <para><function>init386()</function>首先初始化内核的可调整参数, + 这些参数由引导程序传来。先设置环境指针(environment pointer, envp)调用, + 再调用<function>init_param1()</function>。 + envp指针已由loader存放在结构<literal>bootinfo</literal>中:</para> + + <programlisting><filename>sys/i386/i386/machdep.c:</filename> + kern_envp = (caddr_t)bootinfo.bi_envp + KERNBASE; + + /* 初始化基本可调整项,如hz等 */ + init_param1();</programlisting> + + <para><function>init_param1()</function>定义在 + <filename>sys/kern/subr_param.c</filename>之中。 + 这个文件里有一些sysctl项,还有两个函数, + <function>init_param1()</function>和<function>init_param2()</function>。 + 这两个函数从<function>init386()</function>中调用:</para> + + <programlisting><filename>sys/kern/subr_param.c</filename> + hz = HZ; + TUNABLE_INT_FETCH("kern.hz", &hz);</programlisting> + + <para>TUNABLE_<typename>_FETCH用来获取环境变量的值:</para> + + <programlisting><filename>/usr/src/sys/sys/kernel.h</filename> +#define TUNABLE_INT_FETCH(path, var) getenv_int((path), (var)) +</programlisting> + + <para>Sysctl<literal>kern.hz</literal>是系统时钟频率。同时, + 这些sysctl项被<function>init_param1()</function>设定: + <literal>kern.maxswzone, + kern.maxbcache, kern.maxtsiz, kern.dfldsiz, kern.maxdsiz, kern.dflssiz, + kern.maxssiz, kern.sgrowsiz</literal>。</para> + + <indexterm><primary>Global Descriptors Table (GDT)(全局描述符表)</primary></indexterm> + <para>然后<function>init386()</function> 准备全局描述符表 + (Global Descriptors Table, GDT)。在x86上每个任务都运行在自己的虚拟地址空间里, + 这个空间由"段址:偏移量"的数对指定。举个例子,当前将要由处理器执行的指令在 + CS:EIP,那么这条指令的线性虚拟地址就是<quote>代码段虚拟段地址CS</quote> + EIP。 + 为了简便,段起始于虚拟地址0,终止于界限4G字节。所以,在这个例子中, + 指令的线性虚拟地址正是EIP的值。段寄存器,如CS、DS等是选择符, + 即全局描述符表中的索引(更精确的说,索引并非选择符的全部, + 而是选择符中的INDEX部分)。<tip><title>译者注</title><para>对于80386, + 选择符有16位,INDEX部分是其中的高13位。</para></tip> + FreeBSD的全局描述符表为每个CPU保存着15个选择符:</para> + + <programlisting><filename>sys/i386/i386/machdep.c:</filename> +union descriptor gdt[NGDT * MAXCPU]; /* 全局描述符表 */ + +<filename>sys/i386/include/segments.h:</filename> +/* + * 全局描述符表(GDT)中的入口 + */ +#define GNULL_SEL 0 /* 空描述符 */ +#define GCODE_SEL 1 /* 内核代码描述符 */ +#define GDATA_SEL 2 /* 内核数据描述符 */ +#define GPRIV_SEL 3 /* 对称多处理(SMP)每处理器专有数据 */ +#define GPROC0_SEL 4 /* Task state process slot zero and up, 任务状态进程 */ +#define GLDT_SEL 5 /* 每个进程的局部描述符表 */ +#define GUSERLDT_SEL 6 /* 用户自定义的局部描述符表 */ +#define GTGATE_SEL 7 /* 进程任务切换关口 */ +#define GBIOSLOWMEM_SEL 8 /* BIOS低端内存访问(必须是这第8个入口) */ +#define GPANIC_SEL 9 /* 会导致全系统异常中止工作的任务状态 */ +#define GBIOSCODE32_SEL 10 /* BIOS接口(32位代码) */ +#define GBIOSCODE16_SEL 11 /* BIOS接口(16位代码) */ +#define GBIOSDATA_SEL 12 /* BIOS接口(数据) */ +#define GBIOSUTIL_SEL 13 /* BIOS接口(工具) */ +#define GBIOSARGS_SEL 14 /* BIOS接口(自变量,参数) */</programlisting> + + <para>请注意,这些#defines并非选择符本身,而只是选择符中的INDEX域, + 因此它们正是全局描述符表中的索引。 + 例如,内核代码的选择符(GCODE_SEL)的值为0x08。</para> + + <indexterm><primary>Interrupt Descriptor Table (IDT)(中断描述符表)</primary></indexterm> + <para>下一步是初始化中断描述符表(Interrupt Descriptor Table, IDT)。 + 这张表在发生软件或硬件中断时会被处理器引用。例如,执行系统调用时, + 用户应用程序提交<literal>INT 0x80</literal> 指令。这是一个软件中断, + 处理器用索引值0x80在中断描述符表中查找记录。这个记录指向处理这个中断的例程。 + 在这个特定情形中,这是内核的系统调用关口。<tip><title>译者注</title> + <para>Intel 80386支持“调用门”,可以使得用户程序只通过一条call指令 + 就调用内核中的例程。可是FreeBSD并未采用这种机制, + 也许是因为使用软中断接口可免去动态链接的麻烦吧。另外还有一个附带的好处: + 在仿真Linux时,当遇到FreeBSD内核不支持的而又并非关键性的系统调用时, + 内核只会显示一些出错信息,这使得程序能够继续运行; + 而不是在真正执行程序之前的初始化过程中就因为动态链接失败而不允许程序运行。</para></tip> + 中断描述符表最多可以有256 (0x100)条记录。内核分配NIDT条记录的内存给中断描述符表, + 这里NIDT=256,是最大值:</para> + + <programlisting><filename>sys/i386/i386/machdep.c:</filename> +static struct gate_descriptor idt0[NIDT]; +struct gate_descriptor *idt = &idt0[0]; /* 中断描述符表 */ +</programlisting> + + <para>每个中断都被设置一个合适的中断处理程序。 + 系统调用关口<literal>INT 0x80</literal>也是如此:</para> + + <programlisting><filename>sys/i386/i386/machdep.c:</filename> + setidt(0x80, &IDTVEC(int0x80_syscall), + SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL));</programlisting> + + <para>所以当一个用户应用程序提交<literal>INT 0x80</literal>指令时, + 全系统的控制权会传递给函数<function>_Xint0x80_syscall</function>, + 这个函数在内核代码段中,将被以管理员权限执行。</para> + + <para>然后,控制台和DDB(调试器)被初始化:</para> + <indexterm><primary>DDB</primary></indexterm> + + <programlisting><filename>sys/i386/i386/machdep.c:</filename> + cninit(); +/* 以下代码可能因为未定义宏DDB而被跳过 */ +#ifdef DDB + kdb_init(); + if (boothowto & RB_KDB) + Debugger("Boot flags requested debugger"); +#endif</programlisting> + + <para>任务状态段(TSS)是另一个x86保护模式中的数据结构。当发生任务切换时, + 任务状态段用来让硬件存储任务现场信息。</para> + + <para>局部描述符表(LDT)用来指向用户代码和数据。系统定义了几个选择符, + 指向局部描述符表,它们是系统调用关口和用户代码、用户数据选择符:</para> + + <programlisting><filename>/usr/include/machine/segments.h</filename> +#define LSYS5CALLS_SEL 0 /* Intel BCS强制要求的 */ +#define LSYS5SIGR_SEL 1 +#define L43BSDCALLS_SEL 2 /* 尚无 */ +#define LUCODE_SEL 3 +#define LSOL26CALLS_SEL 4 /* Solaris >=2.6版系统调用关口 */ +#define LUDATA_SEL 5 +/* separate stack, es,fs,gs sels ? 分别的栈、es、fs、gs选择符? */ +/* #define LPOSIXCALLS_SEL 5*/ /* notyet, 尚无 */ +#define LBSDICALLS_SEL 16 /* BSDI system call gate, BSDI系统调用关口 */ +#define NLDT (LBSDICALLS_SEL + 1) +</programlisting> + + <para>然后,proc0(0号进程,即内核所处的进程)的进程控制块(Process Control Block) + (<literal>struct pcb</literal>)结构被初始化。proc0是一个 + <literal>struct proc</literal> 结构,描述了一个内核进程。 + 内核运行时,该进程总是存在,所以这个结构在内核中被定义为全局变量:</para> + + <programlisting><filename>sys/kern/kern_init.c:</filename> + struct proc proc0;</programlisting> + + <para>结构<literal>struct pcb</literal>是proc结构的一部分, + 它定义在<filename>/usr/include/machine/pcb.h</filename>之中, + 内含针对i386硬件结构专有的信息,如寄存器的值。</para> + </sect2> + + <sect2> + <title><function>mi_startup()</function></title> + + <para>这个函数用冒泡排序算法,将所有系统初始化对象,然后逐个调用每个对象的入口:</para> + + <programlisting><filename>sys/kern/init_main.c:</filename> + for (sipp = sysinit; *sipp; sipp++) { + + /* ... 省略 ... */ + + /* 调用函数 */ + (*((*sipp)->func))((*sipp)->udata); + /* ... 省略 ... */ + }</programlisting> + + <para>尽管sysinit框架已经在《FreeBSD开发者手册》中有所描述, + 我还是在这里讨论一下其内部原理。</para> + + <indexterm><primary>sysinit对象</primary></indexterm> + <para>每个系统初始化对象(sysinit对象)通过调用宏建立。 + 让我们以<literal>announce</literal> sysinit对象为例。 + 这个对象打印版权信息:</para> + + <programlisting><filename>sys/kern/init_main.c:</filename> +static void +print_caddr_t(void *data __unused) +{ + printf("%s", (char *)data); +} +SYSINIT(announce, SI_SUB_COPYRIGHT, SI_ORDER_FIRST, print_caddr_t, copyright)</programlisting> + + <para>这个对象的子系统标识是SI_SUB_COPYRIGHT(0x0800001), + 数值刚好排在SI_SUB_CONSOLE(0x0800000)后面。 + 所以,版权信息将在控制台初始化之后就被很早的打印出来。</para> + + <para>让我们看一看宏<literal>SYSINIT()</literal>到底做了些什么。 + 它展开成宏<literal>C_SYSINIT()</literal>。 + 宏<literal>C_SYSINIT()</literal>然后展开成一个静态结构 + <literal>struct sysinit</literal>。结构里申明里调用了另一个宏 + <literal>DATA_SET</literal>:</para> + <programlisting><filename>/usr/include/sys/kernel.h:</filename> + #define C_SYSINIT(uniquifier, subsystem, order, func, ident) \ + static struct sysinit uniquifier ## _sys_init = { \ subsystem, \ + order, \ func, \ ident \ }; \ DATA_SET(sysinit_set,uniquifier ## + _sys_init); + +#define SYSINIT(uniquifier, subsystem, order, func, ident) \ + C_SYSINIT(uniquifier, subsystem, order, \ + (sysinit_cfunc_t)(sysinit_nfunc_t)func, (void *)ident)</programlisting> + + <para>宏<literal>DATA_SET()</literal>展开成<literal>MAKE_SET()</literal>, + 宏<literal>MAKE_SET()</literal>指向所有隐含的sysinit幻数:</para> + + <programlisting><filename>/usr/include/linker_set.h</filename> +#define MAKE_SET(set, sym) \ + static void const * const __set_##set##_sym_##sym = &sym; \ + __asm(".section .set." #set ",\"aw\""); \ + __asm(".long " #sym); \ + __asm(".previous") +#endif +#define TEXT_SET(set, sym) MAKE_SET(set, sym) +#define DATA_SET(set, sym) MAKE_SET(set, sym)</programlisting> + + <para>回到我们的例子中,经过宏的展开过程,将会产生如下声明:</para> + + <programlisting>static struct sysinit announce_sys_init = { + SI_SUB_COPYRIGHT, + SI_ORDER_FIRST, + (sysinit_cfunc_t)(sysinit_nfunc_t) print_caddr_t, + (void *) copyright +}; + +static void const *const __set_sysinit_set_sym_announce_sys_init = + &announce_sys_init; +__asm(".section .set.sysinit_set" ",\"aw\""); +__asm(".long " "announce_sys_init"); +__asm(".previous");</programlisting> + + <para>第一个<literal>__asm</literal>指令在内核可执行文件中建立一个ELF节(section)。 + 这发生在内核链接的时候。这一节将被命令为<literal>.set.sysinit_set</literal>。 + 这一节的内容是一个32位值――announce_sys_init结构的地址,这个结构正是第二个 + <literal>__asm</literal>指令所定义的。第三个<literal>__asm</literal>指令标记节的结束。 + 如果前面有名字相同的节定义语句,节的内容(那个32位值)将被填加到已存在的节里, + 这样就构造出了一个32位指针数组。</para> + + <para>用<application>objdump</application>察看一个内核二进制文件, + 也许你会注意到里面有这么几个小的节:</para> + + <screen>&prompt.user; <userinput>objdump -h /kernel</userinput> + 7 .set.cons_set 00000014 c03164c0 c03164c0 002154c0 2**2 + CONTENTS, ALLOC, LOAD, DATA + 8 .set.kbddriver_set 00000010 c03164d4 c03164d4 002154d4 2**2 + CONTENTS, ALLOC, LOAD, DATA + 9 .set.scrndr_set 00000024 c03164e4 c03164e4 002154e4 2**2 + CONTENTS, ALLOC, LOAD, DATA + 10 .set.scterm_set 0000000c c0316508 c0316508 00215508 2**2 + CONTENTS, ALLOC, LOAD, DATA + 11 .set.sysctl_set 0000097c c0316514 c0316514 00215514 2**2 + CONTENTS, ALLOC, LOAD, DATA + 12 .set.sysinit_set 00000664 c0316e90 c0316e90 00215e90 2**2 + CONTENTS, ALLOC, LOAD, DATA</screen> + + <para>这一屏信息显示表明节.set.sysinit_set有0x664字节的大小, + 所以<literal>0x664/sizeof(void *)</literal>个sysinit对象被编译进了内核。 + 其它节,如<literal>.set.sysctl_set</literal>表示其它链接器集合。</para> + + <para>通过定义一个类型为<literal>struct linker_set</literal>的变量, + 节<literal>.set.sysinit_set</literal>将被<quote>收集</quote>到那个变量里:</para> + <programlisting><filename>sys/kern/init_main.c:</filename> + extern struct linker_set sysinit_set; /* XXX */</programlisting> + + <para><literal>struct linker_set</literal>定义如下:</para> + + <programlisting><filename>/usr/include/linker_set.h:</filename> + struct linker_set { + int ls_length; + void *ls_items[1]; /* ls_length个项的数组, 以NULL结尾 */ +};</programlisting> + + <para><tip><title>译者注</title><para>实际上是说, + 用C语言结构体linker_set来表达那个ELF节。</para></tip> + 第一项是sysinit对象的数量,第二项是一个以NULL结尾的数组, + 数组中是指向那些对象的指针。</para> + + <para>回到对<function>mi_startup()</function>的讨论, + 我们清楚了sysinit对象是如何被组织起来的。 + 函数<function>mi_startup()</function>将它们排序, + 并调用每一个对象。最后一个对象是系统调度器:</para> + + <programlisting><filename>/usr/include/sys/kernel.h:</filename> +enum sysinit_sub_id { + SI_SUB_DUMMY = 0x0000000, /* 不被执行,仅供链接器使用 */ + SI_SUB_DONE = 0x0000001, /* 已被处理*/ + SI_SUB_CONSOLE = 0x0800000, /* 控制台*/ + SI_SUB_COPYRIGHT = 0x0800001, /* 最早使用控制台的对象 */ +... + SI_SUB_RUN_SCHEDULER = 0xfffffff /* 调度器:不返回 */ +};</programlisting> + + <para>系统调度器sysinit对象定义在文件<filename>sys/vm/vm_glue.c</filename>中, + 这个对象的入口点是<function>scheduler()</function>。 + 这个函数实际上是个无限循环,它表示那个进程标识(PID)为0的进程――swapper进程。 + 前面提到的proc0结构正是用来描述这个进程。</para> + + <para>第一个用户进程是<emphasis>init</emphasis>, + 由sysinit对象<literal>init</literal>建立:</para> + + <programlisting><filename>sys/kern/init_main.c:</filename> +static void +create_init(const void *udata __unused) +{ + int error; + int s; + + s = splhigh(); + error = fork1(&proc0, RFFDG | RFPROC, &initproc); + if (error) + panic("cannot fork init: %d\n", error); + initproc->p_flag |= P_INMEM | P_SYSTEM; + cpu_set_fork_handler(initproc, start_init, NULL); + remrunqueue(initproc); + splx(s); +} +SYSINIT(init,SI_SUB_CREATE_INIT, SI_ORDER_FIRST, create_init, NULL)</programlisting> + + <para><function>create_init()</function>通过调用<function>fork1()</function> + 分配一个新的进程,但并不将其标记为可运行。当这个新进程被调度器调度执行时, + <function>start_init()</function>将会被调用。 + 那个函数定义在<filename>init_main.c</filename>中。 + 它尝试装载并执行二进制代码<filename>init</filename>, + 先尝试<filename>/sbin/init</filename>,然后是<filename>/sbin/oinit</filename>, + <filename>/sbin/init.bak</filename>,最后是<filename>/stand/sysinstall</filename>:</para> + + <programlisting><filename>sys/kern/init_main.c:</filename> +static char init_path[MAXPATHLEN] = +#ifdef INIT_PATH + __XSTRING(INIT_PATH); +#else + "/sbin/init:/sbin/oinit:/sbin/init.bak:/stand/sysinstall"; +#endif</programlisting> + + </sect2> +</sect1> + +</chapter> diff --git a/zh_CN.UTF-8/books/arch-handbook/chapters.ent b/zh_CN.UTF-8/books/arch-handbook/chapters.ent new file mode 100644 index 0000000000..f914c86067 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/chapters.ent @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Creates entities for each chapter in the FreeBSD Architecture + Handbook. Each entity is named chap.foo, where foo is the value + of the id attribute on that chapter, and corresponds to the name of + the directory in which that chapter's .xml file is stored. + + Chapters should be listed in the order in which they are referenced. + + Original Revision: 1.21 + $FreeBSD$ +--> + +<!-- Part one - Kernel --> +<!ENTITY chap.boot SYSTEM "boot/chapter.xml"> +<!ENTITY chap.kobj SYSTEM "kobj/chapter.xml"> +<!ENTITY chap.sysinit SYSTEM "sysinit/chapter.xml"> +<!ENTITY chap.locking SYSTEM "locking/chapter.xml"> +<!ENTITY chap.vm SYSTEM "vm/chapter.xml"> +<!ENTITY chap.jail SYSTEM "jail/chapter.xml"> +<!ENTITY chap.mac SYSTEM "mac/chapter.xml"> +<!ENTITY chap.smp SYSTEM "smp/chapter.xml"> + +<!-- Part Two - Device Drivers --> +<!ENTITY chap.driverbasics SYSTEM "driverbasics/chapter.xml"> +<!ENTITY chap.isa SYSTEM "isa/chapter.xml"> +<!ENTITY chap.pci SYSTEM "pci/chapter.xml"> +<!ENTITY chap.scsi SYSTEM "scsi/chapter.xml"> +<!ENTITY chap.usb SYSTEM "usb/chapter.xml"> +<!ENTITY chap.newbus SYSTEM "newbus/chapter.xml"> +<!ENTITY chap.snd SYSTEM "sound/chapter.xml"> +<!ENTITY chap.pccard SYSTEM "pccard/chapter.xml"> + +<!-- Part three - Appendices --> +<!ENTITY chap.index "<index xmlns='http://docbook.org/ns/docbook'/>"> diff --git a/zh_CN.UTF-8/books/arch-handbook/driverbasics/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/driverbasics/chapter.xml new file mode 100644 index 0000000000..65bed70d1e --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/driverbasics/chapter.xml @@ -0,0 +1,555 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + The FreeBSD Documentation Project + The FreeBSD Simplified Chinese Project + + Original Revision: 1.37 + $FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="driverbasics"> + <info><title>编写 FreeBSD 设备驱动程序</title> + <authorgroup> + <author><personname><firstname>Murray</firstname><surname>Stokely</surname></personname><contrib>&cnproj.written.by;</contrib></author> + </authorgroup> + <authorgroup> + <author><personname><firstname>Jörg</firstname><surname>Wunsch</surname></personname><contrib>基础性手册intro(4):</contrib></author> + </authorgroup> + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + </info> + + + <sect1 xml:id="driverbasics-intro"> + <title>简介</title> + + <indexterm><primary>device driver(设备驱动程序)</primary></indexterm> + <indexterm><primary>pseudo-device(伪设备)</primary></indexterm> + <para>本章简要介绍了如何为FreeBSD编写设备驱动程序。术语设备在 + 这儿的上下文中多用于指代系统中硬件相关的东西,如磁盘,打印机, + 图形显式器及其键盘。设备驱动程序是操作系统中用于控制特定设备的 + 软件组件。也有所谓的伪设备,即设备驱动程序用软件模拟设备的行为, + 而没有特定的底层硬件。设备驱动程序可以被静态地编译进系统,或者 + 通过动态内核链接工具‘kld’在需要时加载。</para> + + <indexterm><primary>device nodes(设备节点)</primary></indexterm> + <indexterm><primary>MAKEDEV</primary></indexterm> + + <para>类&unix;操作系统中的大多数设备都是通过设备节点来访问的,有时也 + 被称为特殊文件。这些文件在文件系统的层次结构中通常位于 + <filename>/dev</filename>目录下。在FreeBSD 5.0-RELEASE以前的 + 发行版中, 对&man.devfs.5;的支持还没有被集成到FreeBSD中,每个设备 + 节点必须要静态创建,并且独立于相关设备驱动程序的存在。系统中大 + 多数设备节点是通过运行<command>MAKEDEV</command>创建的。</para> + + <para>设备驱动程序可以粗略地分为两类,字符和网络设备驱动程序。</para> + + </sect1> + + <sect1 xml:id="driverbasics-kld"> + <title>动态内核链接工具—KLD</title> + + <indexterm><primary>kernel linking(内核链接)</primary><secondary>dynamic(动态)</secondary></indexterm> + <indexterm><primary>kernel loadable modules (KLD, 内核可装载模块)</primary></indexterm> + <para>kld接口允许系统管理员从运行的系统中动态地添加和删除功能。 + 这允许设备驱动程序的编写者将他们的新改动加载到运行的内核中, + 而不用为了测试新改动而频繁地重启。</para> + + <para>kld接口通过下面的特权命令使用: + + <indexterm><primary>kernel modules(内核模块)</primary><secondary>loading(装载)</secondary></indexterm> + <indexterm><primary>kernel modules(内核模块)</primary><secondary>unloading(卸载)</secondary></indexterm> + <indexterm><primary>kernel modules(内核模块)</primary><secondary>listing(列清单)</secondary></indexterm> + <itemizedlist> + <listitem><simpara><command>kldload</command> - 加载新内核模块 + </simpara></listitem> + <listitem><simpara><command>kldunload</command> - 卸载内核模块 + </simpara></listitem> + <listitem><simpara><command>kldstat</command> - 列举当前加载的模块 + </simpara></listitem> + </itemizedlist> + </para> + + <para>内核模块的程序框架</para> + +<programlisting>/* + * KLD程序框架 + * 受Andrew Reiter在Daemonnews上的文章所启发 + */ + +#include <sys/types.h> +#include <sys/module.h> +#include <sys/systm.h> /* uprintf */ +#include <sys/errno.h> +#include <sys/param.h> /* kernel.h中用到的定义 */ +#include <sys/kernel.h> /* 模块初始化中使用的类型 */ + +/* + * 加载处理函数,负责处理KLD的加载和卸载。 + */ + +static int +skel_loader(struct module *m, int what, void *arg) +{ + int err = 0; + + switch (what) { + case MOD_LOAD: /* kldload */ + uprintf("Skeleton KLD loaded.\n"); + break; + case MOD_UNLOAD: + uprintf("Skeleton KLD unloaded.\n"); + break; + default: + err = EOPNOTSUPP; + break; + } + return(err); +} + +/* 向内核其余部分声明此模块 */ + +static moduledata_t skel_mod = { + "skel", + skel_loader, + NULL +}; + +DECLARE_MODULE(skeleton, skel_mod, SI_SUB_KLD, SI_ORDER_ANY);</programlisting> + + + <sect2> + <title>Makefile</title> + + <para>FreeBSD提供了一个makefile包含文件,利用它你可以快速地编译 + 你附加到内核的东西。</para> + + <programlisting>SRCS=skeleton.c +KMOD=skeleton + +.include <bsd.kmod.mk></programlisting> + + <para>简单地用这个makefile运行<command>make</command>就能够创建文件 + <filename>skeleton.ko</filename>,键入如下命令可以把它加载到内核: +<screen>&prompt.root; <userinput>kldload -v ./skeleton.ko</userinput></screen> + </para> + </sect2> + </sect1> + + <sect1 xml:id="driverbasics-access"> + <title>访问设备驱动程序</title> + + <para>&unix; 提供了一套公共的系统调用供用户的应用程序使用。当用户访问 + 设备节点时,内核的上层将这些调用分发到相应的设备驱动程序。脚本 + <command>/dev/MAKEDEV</command>为你的系统生成了大多数的设备节点, + 但如果你正在开发你自己的驱动程序,可能需要用 + <command>mknod</command>创建你自己的设备节点。 + </para> + + <sect2> + <title>创建静态设备节点</title> + + <indexterm><primary>device nodes(设备节点)</primary><secondary>static(静态)</secondary></indexterm> + <indexterm><primary>mknod</primary></indexterm> + + <para><command>mknod</command>命令需要四个参数来创建设备节点。 + 你必须指定设备节点的名字,设备的类型,设备的主号码和设备的从号码。 + </para> + </sect2> + + <sect2> + <title>动态设备节点</title> + + <indexterm><primary>device nodes(设备节点)</primary><secondary>dynamic(动态)</secondary></indexterm> + <indexterm><primary>devfs</primary></indexterm> + + <para>设备文件系统,或者说devfs,在全局文件系统名字空间中提供对 + 内核设备名字空间的访问。这消除了由于有设备驱动程序而没有静态 + 设备节点,或者有设备节点而没有安装设备驱动程序而带来的潜在问题。 + Devfs仍在进展中,但已经能够工作得相当好了。</para> + </sect2> + + </sect1> + + <sect1 xml:id="driverbasics-char"> + <title>字符设备</title> + + <indexterm><primary>character devices(字符设备)</primary></indexterm> + <para>字符设备驱动程序直接从用户进程传输数据,或传输数据到用户进程。 + 这是最普通的一类设备驱动程序,源码树中有大量的简单例子。</para> + + <para>这个简单的伪设备例子会记住你写给它的任何值,并且当你读取它的时候 + 会将这些值返回给你。下面显示了两个版本,一个适用于&os; 4.X, + 一个适用于&os; 5.X。</para> + + <example> + <title>适用于&os; 4.X的回显伪设备驱动程序实例</title> + + <programlisting>/* + * 简单‘echo’伪设备KLD + * + * Murray Stokely + */ + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +#include <sys/types.h> +#include <sys/module.h> +#include <sys/systm.h> /* uprintf */ +#include <sys/errno.h> +#include <sys/param.h> /* kernel.h中用到的定义 */ +#include <sys/kernel.h> /* 模块初始化中使用的类型 */ +#include <sys/conf.h> /* cdevsw结构 */ +#include <sys/uio.h> /* uio结构 */ +#include <sys/malloc.h> + +#define BUFFERSIZE 256 + +/* 函数原型 */ +d_open_t echo_open; +d_close_t echo_close; +d_read_t echo_read; +d_write_t echo_write; + +/* 字符设备入口点 */ +static struct cdevsw echo_cdevsw = { + echo_open, + echo_close, + echo_read, + echo_write, + noioctl, + nopoll, + nommap, + nostrategy, + "echo", + 33, /* 为lkms保留 - /usr/src/sys/conf/majors */ + nodump, + nopsize, + D_TTY, + -1 +}; + +typedef struct s_echo { + char msg[BUFFERSIZE]; + int len; +} t_echo; + +/* 变量 */ +static dev_t sdev; +static int count; +static t_echo *echomsg; + +MALLOC_DECLARE(M_ECHOBUF); +MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module"); + +/* + * 这个函数被kld[un]load(2)系统调用来调用, + * 以决定加载和卸载模块时需要采取的动作。 + */ + +static int +echo_loader(struct module *m, int what, void *arg) +{ + int err = 0; + + switch (what) { + case MOD_LOAD: /* kldload */ + sdev = make_dev(<literal>&</literal>echo_cdevsw, + 0, + UID_ROOT, + GID_WHEEL, + 0600, + "echo"); + /* kmalloc分配供驱动程序使用的内存 */ + MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK); + printf("Echo device loaded.\n"); + break; + case MOD_UNLOAD: + destroy_dev(sdev); + FREE(echomsg,M_ECHOBUF); + printf("Echo device unloaded.\n"); + break; + default: + err = EOPNOTSUPP; + break; + } + return(err); +} + +int +echo_open(dev_t dev, int oflags, int devtype, struct proc *p) +{ + int err = 0; + + uprintf("Opened device \"echo\" successfully.\n"); + return(err); +} + +int +echo_close(dev_t dev, int fflag, int devtype, struct proc *p) +{ + uprintf("Closing device \"echo.\"\n"); + return(0); +} + +/* + * read函数接受由echo_write()存储的buf,并将其返回到用户空间, + * 以供其他函数访问。 + * uio(9) + */ + +int +echo_read(dev_t dev, struct uio *uio, int ioflag) +{ + int err = 0; + int amt; + + /* + * 这个读操作有多大? + * 与用户请求的大小一样,或者等于剩余数据的大小。 + */ + amt = MIN(uio->uio_resid, (echomsg->len - uio->uio_offset > 0) ? + echomsg->len - uio->uio_offset : 0); + if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) { + uprintf("uiomove failed!\n"); + } + return(err); +} + +/* + * echo_write接受一个字符串并将它保存到缓冲区,用于以后的访问。 + */ + +int +echo_write(dev_t dev, struct uio *uio, int ioflag) +{ + int err = 0; + + /* 将字符串从用户空间的内存复制到内核空间 */ + err = copyin(uio->uio_iov->iov_base, echomsg->msg, + MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1)); + + /* 现在需要以null结束字符串,并记录长度 */ + *(echomsg->msg + MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1)) = 0; + echomsg->len = MIN(uio->uio_iov->iov_len, BUFFERSIZE); + + if (err != 0) { + uprintf("Write failed: bad address!\n"); + } + count++; + return(err); +} + +DEV_MODULE(echo,echo_loader,NULL);</programlisting> + </example> + + <example> + <title>适用于&os; 5.X回显伪设备驱动程序实例</title> + + <programlisting>/* + * 简单‘echo’伪设备 KLD + * + * Murray Stokely + * + * 此代码由Søren (Xride) Straarup转换到5.X + */ + +#include <sys/types.h> +#include <sys/module.h> +#include <sys/systm.h> /* uprintf */ +#include <sys/errno.h> +#include <sys/param.h> /* kernel.h中用到的定义 */ +#include <sys/kernel.h> /* 模块初始化中使用的类型 */ +#include <sys/conf.h> /* cdevsw结构 */ +#include <sys/uio.h> /* uio结构 */ +#include <sys/malloc.h> + +#define BUFFERSIZE 256 + + +/* 函数原型 */ +static d_open_t echo_open; +static d_close_t echo_close; +static d_read_t echo_read; +static d_write_t echo_write; + +/* 字符设备入口点 */ +static struct cdevsw echo_cdevsw = { + .d_version = D_VERSION, + .d_open = echo_open, + .d_close = echo_close, + .d_read = echo_read, + .d_write = echo_write, + .d_name = "echo", +}; + +typedef struct s_echo { + char msg[BUFFERSIZE]; + int len; +} t_echo; + +/* 变量 */ +static struct cdev *echo_dev; +static int count; +static t_echo *echomsg; + +MALLOC_DECLARE(M_ECHOBUF); +MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module"); + +/* + * 这个函数被kld[un]load(2)系统调用来调用, + * 以决定加载和卸载模块时需要采取的动作. + */ + +static int +echo_loader(struct module *m, int what, void *arg) +{ + int err = 0; + + switch (what) { + case MOD_LOAD: /* kldload */ + echo_dev = make_dev(<literal>&</literal>echo_cdevsw, + 0, + UID_ROOT, + GID_WHEEL, + 0600, + "echo"); + /* kmalloc分配供驱动程序使用的内存 */ + echomsg = malloc(sizeof(t_echo), M_ECHOBUF, M_WAITOK); + printf("Echo device loaded.\n"); + break; + case MOD_UNLOAD: + destroy_dev(echo_dev); + free(echomsg, M_ECHOBUF); + printf("Echo device unloaded.\n"); + break; + default: + err = EOPNOTSUPP; + break; + } + return(err); +} + +static int +echo_open(struct cdev *dev, int oflags, int devtype, struct thread *p) +{ + int err = 0; + + uprintf("Opened device \"echo\" successfully.\n"); + return(err); +} + +static int +echo_close(struct cdev *dev, int fflag, int devtype, struct thread *p) +{ + uprintf("Closing device \"echo.\"\n"); + return(0); +} + +/* + * read函数接受由echo_write()存储的buf,并将其返回到用户空间, + * 以供其他函数访问。 + * uio(9) + */ + +static int +echo_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + int err = 0; + int amt; + + /* + * 这个读操作有多大? + * 等于用户请求的大小,或者等于剩余数据的大小。 + */ + amt = MIN(uio->uio_resid, (echomsg->len - uio->uio_offset > 0) ? + echomsg->len - uio->uio_offset : 0); + if ((err = uiomove(echomsg->msg + uio->uio_offset, amt, uio)) != 0) { + uprintf("uiomove failed!\n"); + } + return(err); +} + +/* + * echo_write接受一个字符串并将它保存到缓冲区, 用于以后的访问. + */ + +static int +echo_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + int err = 0; + + /* 将字符串从用户空间的内存复制到内核空间 */ + err = copyin(uio->uio_iov->iov_base, echomsg->msg, + MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1)); + + /* 现在需要以null结束字符串,并记录长度 */ + *(echomsg->msg + MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1)) = 0; + echomsg->len = MIN(uio->uio_iov->iov_len, BUFFERSIZE); + + if (err != 0) { + uprintf("Write failed: bad address!\n"); + } + count++; + return(err); +} + +DEV_MODULE(echo,echo_loader,NULL);</programlisting> + </example> + + <para>在&os; 4.X上安装此驱动程序,你将首先需要用如下命令在 + 你的文件系统上创建一个节点:</para> + + <screen>&prompt.root; <userinput>mknod /dev/echo c 33 0</userinput></screen> + + <para>驱动程序被加载后,你应该能够键入一些东西,如:</para> + + <screen>&prompt.root; <userinput>echo -n "Test Data" > /dev/echo</userinput> +&prompt.root; <userinput>cat /dev/echo</userinput> +Test Data</screen> + + <para>真正的硬件设备在下一章描述。</para> + + <para>补充资源 + <itemizedlist> + <listitem><simpara><link xlink:href="http://ezine.daemonnews.org/200010/blueprints.html">Dynamic + Kernel Linker (KLD) Facility Programming Tutorial</link> - + <link xlink:href="http://www.daemonnews.org/">Daemonnews</link> October 2000</simpara></listitem> + <listitem><simpara><link xlink:href="http://ezine.daemonnews.org/200007/newbus-intro.html">How + to Write Kernel Drivers with NEWBUS</link> - <link xlink:href="http://www.daemonnews.org/">Daemonnews</link> July + 2000</simpara></listitem> + </itemizedlist> + </para> + </sect1> + + <sect1 xml:id="driverbasics-block"> + <title>块设备(消亡中)</title> + + <indexterm><primary>block devices(块设备)</primary></indexterm> + <para>其他&unix;系统支持另一类型的磁盘设备,称为块设备。块设备是内核 + 为它们提供缓冲的磁盘设备。这种缓冲使得块设备几乎没有用,或者说非常 + 不可靠。缓冲会重新安排写操作的次序,使得应用程序丧失了在任何时刻及时 + 知道准确的磁盘内容的能力。这导致对磁盘数据结构(文件系统,数据库等)的 + 可预测的和可靠的崩溃恢复成为不可能。由于写操作被延迟,内核无法向应用 + 程序报告哪个特定的写操作遇到了写错误,这又进一步增加了一致性问题。 + 由于这个原因,真正的应用程序从不依赖于块设备,事实上,几乎所有访问 + 磁盘的应用程序都尽力指定总是使用字符(或<quote>raw</quote>)设备。 + 由于实现将每个磁盘(分区)同具有不同语义的两个设备混为一谈,从而致使 + 相关内核代码极大地复杂化,作为推进磁盘I/O基础结构现代化的一部分,&os; + 抛弃了对带缓冲的磁盘设备的支持。</para> + </sect1> + + <sect1 xml:id="driverbasics-net"> + <title>网络设备驱动程序</title> + + <indexterm><primary>network devices(网络设备)</primary></indexterm> + <para>访问网络设备的驱动程序不需要使用设备节点。选择哪个驱动程序是 + 基于内核内部的其他决定而不是调用open(),对网络设备的使用通常由 + 系统调用socket(2)引入。</para> + + <para>更多细节, 请参见 ifnet(9) 联机手册、 回环设备的源代码, + 以及 Bill Paul 撰写的网络驱动程序。</para> + + </sect1> + +</chapter> diff --git a/zh_CN.UTF-8/books/arch-handbook/isa/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/isa/chapter.xml new file mode 100644 index 0000000000..04f91b4bcb --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/isa/chapter.xml @@ -0,0 +1,2146 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + The FreeBSD Documentation Project + + Original Revision: 1.20 + $FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="isa-driver"> + <info><title>ISA设备驱动程序</title> + <authorgroup> + <author><personname><firstname>Sergey</firstname><surname>Babkin</surname></personname><contrib>&cnproj.written.by;</contrib></author> + </authorgroup> + <authorgroup> + <author><personname><firstname>Murray</firstname><surname>Stokely</surname></personname><contrib>&cnproj.modified.for.handbook.by;</contrib></author> + <author><personname><firstname>Valentino</firstname><surname>Vaschetto</surname></personname></author> + <author><personname><firstname>Wylie</firstname><surname>Stilwell</surname></personname></author> + </authorgroup> + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + </info> + + + + <sect1 xml:id="isa-driver-synopsis"> + <title>概述</title> + + <indexterm><primary>ISA(Industry Standard Architecture, 工业标准结构)</primary></indexterm> + <indexterm><primary>device driver(设备驱动程序)</primary><secondary>ISA(Industry Standard Architecture, 工业标准结构)</secondary></indexterm> + + <para>本章介绍了编写ISA设备驱动程序相关的一些问题。这儿展示的伪代码 + 相当详细,很容易让人联想到真正的代码,不过这依然仅仅是伪代码。 + 它避免了与所讨论的主题无关的细节。真实的例子可以在实际驱动程序的 + 源代码中找到。<literal>ep</literal>和<literal>aha</literal> + 更是信息的好来源。</para> + </sect1> + + <sect1 xml:id="isa-driver-basics"> + <title>基本信息</title> + + <para>典型的ISA驱动程序需要以下包含文件:</para> + +<programlisting>#include <sys/module.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <isa/isavar.h> +#include <isa/pnpvar.h></programlisting> + + <para>它们描述了针对ISA和通用总线子系统的东西。</para> + + <indexterm><primary>object-oriented(面向对象)</primary></indexterm> + <para>总线子系统是以面向对象的方式实现的,其主要结构通过相关联的 + 方法函数来访问。</para> + + <indexterm><primary>bus methods(总线方法)</primary></indexterm> + <para>ISA驱动程序实现的总线方法的列表与任何其他总线的很相似。 + 对于名字为“xxx”的假想驱动程序,它们将是:</para> + + <itemizedlist> + <listitem> + <para><function>static void xxx_isa_identify (driver_t *, + device_t);</function> 通常用于总线驱动程序而不是设备驱动程序。 + 但对于ISA设备,这个方法有特殊用途:如果设备提供某些设备特定的 + (非PnP)方法自动侦测设备,这个例程可以实现它。</para> + </listitem> + + <listitem> + <para><function>static int xxx_isa_probe (device_t + dev);</function> 在已知(或PnP)位置探测设备。对于已经部分配置的 + 设备,这个例程也能够提供设备特定的对某些参数的自动侦测。</para> + </listitem> + + <listitem> + <para><function>static int xxx_isa_attach (device_t + dev);</function> 挂接和初始化设备。</para> + </listitem> + + <listitem> + <para><function>static int xxx_isa_detach (device_t + dev);</function> 卸载设备驱动模块前解挂设备。</para> + </listitem> + + <listitem> + <para><function>static int xxx_isa_shutdown (device_t + dev);</function> 系统关闭前执行设备的关闭。</para> + </listitem> + + <listitem> + <para><function>static int xxx_isa_suspend (device_t + dev);</function> 系统进入节能状态前挂起设备。也可以中止 + 切换到节能状态。</para> + </listitem> + + <listitem> + <para><function>static int xxx_isa_resume (device_t + dev);</function> 从节能状态返回后恢复设备的活动状态。</para> + </listitem> + + </itemizedlist> + + <para><function>xxx_isa_probe()</function>和 + <function>xxx_isa_attach()</function>是必须提供的,其余例程根据设备的 + 需要可以有选择地实现。</para> + + <para>使用下面一组描述符将设备驱动链接到系统。</para> + +<programlisting> /* 支持的总线方法表 */ + static device_method_t xxx_isa_methods[] = { + /* 列出驱动程序支持的所有总线方法函数 */ + /* 略去不支持的函数 */ + DEVMETHOD(device_identify, xxx_isa_identify), + DEVMETHOD(device_probe, xxx_isa_probe), + DEVMETHOD(device_attach, xxx_isa_attach), + DEVMETHOD(device_detach, xxx_isa_detach), + DEVMETHOD(device_shutdown, xxx_isa_shutdown), + DEVMETHOD(device_suspend, xxx_isa_suspend), + DEVMETHOD(device_resume, xxx_isa_resume), + + { 0, 0 } + }; + + static driver_t xxx_isa_driver = { + "xxx", + xxx_isa_methods, + sizeof(struct xxx_softc), + }; + + + static devclass_t xxx_devclass; + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, xxx_devclass, + load_function, load_argument);</programlisting> + + <indexterm><primary>softc(硬件设备的软件描述符,相关信息)</primary></indexterm> + + <para>此处的结构<varname remap="structname">xxx_softc</varname>是一个设备 + 特定的结构,它包含私有的驱动程序数据和驱动程序资源的描述符。 + 总线代码会自动按需要为每个设备分配一个softc描述符。</para> + + <indexterm><primary>kernel modules(内核模块)</primary></indexterm> + + <para>如果驱动程序作为可加载模块实现,当驱动程序被加载或卸载时, + 会调用<function>load_function()</function>函数进行驱动程序特定的 + 初始化或清理工作,并将load_argument作为函数的一个参量传递进去。 + 如果驱动程序不支持动态加载(换句话说,它必须被链接到内核中),则 + 这些值应当被设置为0,最后的定义将看起来如下所示:</para> + + <programlisting> DRIVER_MODULE(xxx, isa, xxx_isa_driver, + xxx_devclass, 0, 0);</programlisting> + + <indexterm><primary>PnP(Plug and Play, 即插即用)</primary></indexterm> + + <para>如果驱动程序是为支持PnP的设备而写的,那么就必须定义一个包含 + 所有支持的PnP ID的表。这个表由此驱动程序所支持的PnP ID的列表 + 和以人可读的形式给出的、与这些ID对应的硬件类型和型号的描述 + 组成。看起来如下:</para> + +<programlisting> static struct isa_pnp_id xxx_pnp_ids[] = { + /* 每个所支持的PnP ID占一行 */ + { 0x12345678, "Our device model 1234A" }, + { 0x12345679, "Our device model 1234B" }, + { 0, NULL }, /* 表结束 */ + };</programlisting> + + <para>如果驱动程序不支持PnP设备,它仍然需要一个空的PnP ID表, + 如下所示:</para> + +<programlisting> static struct isa_pnp_id xxx_pnp_ids[] = { + { 0, NULL }, /* 表结束 */ + };</programlisting> + + </sect1> + + <sect1 xml:id="isa-driver-device-t"> + <title>Device_t指针</title> + + <para><varname remap="structname">Device_t</varname>是为设备结构而定义的指针类型, + 这里我们只关心从设备驱动程序编写者的角度看感兴趣的方法。下面的方法 + 用来操纵设备结构中的值:</para> + + <itemizedlist> + + <listitem><para><function>device_t + device_get_parent(dev)</function> 获取设备的父总线。 + </para></listitem> + + <listitem><para><function>driver_t + device_get_driver(dev)</function> 获取指向其驱动程序结构的指针。 + </para></listitem> + + <listitem><para><function>char + *device_get_name(dev)</function> 获取驱动程序的名字,在我们的 + 例子中为<literal>"xxx"</literal>。</para></listitem> + + <listitem><para><function>int device_get_unit(dev)</function> + 获取单元号(与每个驱动程序关联的设备从0开始编号)。 + </para></listitem> + + <listitem><para><function>char + *device_get_nameunit(dev)</function> 获取设备名,包括单元号。 + 例如<quote>xxx0</quote>,<quote>xxx1</quote> + 等。</para></listitem> + + <listitem><para><function>char + *device_get_desc(dev)</function> 获取设备描述。通常它以人可读的 + 形式描述设备的确切型号。</para></listitem> + + <listitem><para><function>device_set_desc(dev, + desc)</function>设置描述信息。这使得设备描述指向desc字符串, + 此后这个字符串就不能被解除分配。</para></listitem> + + <listitem><para><function>device_set_desc_copy(dev, + desc)</function> 设置描述信息。描述被拷贝到内部动态分配的 + 缓冲区,这样desc字符串在以后可以被改变而不会产生有害的结果。 + </para></listitem> + + <listitem><para><function>void + *device_get_softc(dev)</function> 获取指向与设备关联的设备描述符 + (<varname remap="structname">xxx_softc</varname>结构)的指针。 + </para></listitem> + + <listitem><para><function>u_int32_t + device_get_flags(dev)</function> 获取配置文件中特定于设备的 + 标志。</para></listitem> + + </itemizedlist> + + <para>可以使用一个很方便的函数<function>device_printf(dev, fmt, + ...)</function>从设备驱动程序中打印讯息。它自动在讯息前添加 + 单元名和冒号。</para> + + <para>device_t的这些方法在文件<filename>kern/bus_subr.c</filename> + 中实现。</para> + + </sect1> + + <sect1 xml:id="isa-driver-config"> + <title>配置文件与自动配置期间识别和探测的顺序</title> + + <indexterm><primary>ISA(Industry Standard Architecture, 工业标准结构)</primary><secondary>probing(探测)</secondary></indexterm> + + <para>ISA设备在内核配置文件中的描述如下:</para> + + <programlisting>device xxx0 at isa? port 0x300 irq 10 drq 5 + iomem 0xd0000 flags 0x1 sensitive</programlisting> + + <indexterm><primary>IRQ(中断请求)</primary></indexterm> + + <para>端口值、IRQ值和其他值被转换成与设备关联的资源值。根据设备 + 对自动配置需要和支持程度的不同,这些值是可选的。例如, + 某些设备根本不需要读DRQ,而有些则允许设备从设备配置端口读取 + IRQ设置。如果机器有多个ISA总线,可以在配置文件中明确指定哪条 + 总线,如<literal>isa0</literal>或<literal>isa1</literal>, + 否则将在所有ISA总线上搜索设备。</para> + + <para><literal>敏感(sensitive)</literal>是一种资源请求,它指示 + 必须在所有非敏感设备之前探测设备。此特性虽被支持,但似乎从未 + 在目前的任何驱动程序中使用过。</para> + + <para>对于老的ISA设备,很多情况下驱动程序仍然能够侦测配置参数。 + 但是系统中配置的每个设备必须具有一个配置行。如果系统中装有同一 + 类型的两个设备,但对应的驱动程序却只有一个配置行,例如: + <programlisting>device xxx0 at isa?</programlisting> + 那么只有一个设备会被配置。</para> + + <para>但对于支持通过PnP或专有协议进行自动识别的设备,一个配置行 + 就足够配置系统中的所有设备,如上面的配置行,或者简单地:</para> + + <programlisting>device xxx at isa?</programlisting> + + <para>如果设备驱动程序既支持能自动识别的设备又支持老设备,并且 + 两类设备同时安装在一台机器上,那么只要在配置文件中描述老设备 + 就足够了。自动识别的设备将被自动添加。</para> + + <para>如果ISA设备是自动配置的,发生的事件如下:</para> + + <para>所有设备驱动程序的识别例程(包括识别所有PnP设备的PnP识别 + 例程)以随机顺序被调用。他们识别出设备后就把设备添加到ISA总线 + 上的列表中。通常驱动程序的识别例程将新设备与它们的驱动 + 程序关联起来。而PnP识别例程并不知道其他驱动程序,因此不能将 + 驱动程序与它所添加的新设备关联起来。</para> + + <para>使用PnP协议让PnP设备进入睡眠,以防止它们被探测为老设备。</para> + + <para>被标记为<literal>敏感(sensitive)</literal>的非PnP设备的 + 探测例程被调用。如果探测设备成功,那么就为其调用挂接(attach) + 例程。</para> + + <para>所有非PnP设备的探测和连接例程以同样的方式被调用。</para> + + <para>PnP设备从睡眠中恢复过来,并给它们分配所请求的资源:I/O、 + 内存地址范围、IRQ和DRQ,所有这些与已连接的老设备不会冲突。</para> + + <para>对于每个PnP设备,所有ISA设备驱动程序的探测例程都会被调用。 + 第一个要求此设备的驱动程序将被连接。多个驱动程序以不同的优先权 + 要求一个设备的情况是可能的,这种情况下,具有最高优先权的驱动程序 + 将获胜。探测例程必须调用<function>ISA_PNP_PROBE()</function>将 + 真实的PnP ID和驱动程序支持的ID列表作比较,如果ID不在表中则返回 + 失败。这意味着每个驱动程序,包括不支持任何PnP设备的驱动程序, + 都必须对未知的PnP设备无条件调用 + <function>ISA_PNP_PROBE()</function>,对于未知设备, 至少要用一个 + 空的PnP ID表调用并返回失败。</para> + + <para>探测例程遇到错误时会返回一个正值(错误码),成功时返回 + 零或负值。</para> + + <para>负的返回值用于PnP设备支持多个接口的情况。例如,老的兼容接口 + 和新的高级接口通过不同的驱动程序来提供支持。两个驱动程序 + 都侦测设备。在探测例程中返回较高值的驱动程序优先(换句话说, + 返回0的驱动程序具有最高的优先级,返回-1的其次,返回-2的更在 + 其后,如此下去)。如果多个驱动程序返回相同的值,那么最先调用的 + 获胜。因此,如果驱动程序返回0,就基本能够确信它获得优先权仲裁。 + </para> + + <para>设备特定的识别例程也能够将一类而不是单个驱动程序指派给设备。 + 就象使用PnP的情况一样,对于某一设备,会探测这一类中所有的驱动程序。 + 由于这个特性在任何现存的驱动程序中总均未实现,故本文档中不再予以 + 考虑。</para> + + <para>由于探测老设备的时候PnP设备被禁用,它们不会被连接两次 + (一次作为老设备,一次作为PnP)。但如果识别例程设备相关的, + 这种情况下设备驱动程序有责任确保同一设备不会被设备驱动程序 + 连接两次:一次作为老的由用户配置的,一次作为自动识别的。</para> + + <para>对于自动识别的设备(包括PnP和设备特定的)的另一个实践结论是, + 不能从内核配置文件中向它们传递旗标。因此它们必须要么根本不使用 + 旗标,要么为所有自动识别的设备使用单元号为0的设备的旗标,或者 + 使用sysctl接口而不是旗标。</para> + + <para>通过使用函数族<function>resource_query_*()</function>和 + <function>resource_*_value()</function>直接访问配置资源, + 从而可以提供其他不常用的配置。它们的实现位于 + <filename>kern/subr_bus.c</filename>。老的IDE磁盘驱动器 + <filename>i386/isa/wd.c</filename>包含这样使用的例子。 + 但必须优先使用配置的标准方法。将解析配置资源这类事情留给 + 总线配置代码。</para> + + </sect1> + + <sect1 xml:id="isa-driver-resources"> + <title>资源</title> + + <indexterm><primary>resources(资源)</primary></indexterm> + <indexterm><primary>device driver(设备驱动程序)</primary><secondary>resources(资源)</secondary></indexterm> + + <para>用户写入到内核配置文件中的信息被作为配置资源处理,并传递到内核。 + 总线配置代码解析这部分信息并将其转换为结构device_t的值和与之 + 关联的总线资源。对于复杂情况下的配置,驱动程序可以直接使用 + <function>resource_*</function> 函数访问配置资源。 然而, + 通常既不需要也不推荐这样做, + 因此这儿不再进一步讨论这个问题。</para> + + <para>总线资源与每个设备相关联。通过类型和类型中的数字标识它们。 + 对于ISA总线,定义了下面的类型:</para> + + <indexterm><primary>DMA channel(DMA通道)</primary></indexterm> + + <itemizedlist> + <listitem> + <para><emphasis>SYS_RES_IRQ</emphasis> - 中断号</para> + </listitem> + + <listitem> + <para><emphasis>SYS_RES_DRQ</emphasis> - ISA DMA通道号</para> + </listitem> + + <listitem> + <para><emphasis>SYS_RES_MEMORY</emphasis> - 映射到系统内存空间 + 的设备内存的范围 + </para> + </listitem> + + <listitem> + <para><emphasis>SYS_RES_IOPORT</emphasis> - 设备I/O寄存器的范围 + </para> + </listitem> + </itemizedlist> + + <para>类型内的枚举从0开始,因此如果设备有两个内存区域,它的 + <literal>SYS_RES_MEMORY</literal> 类型的资源编号为0和1。 + 资源类型与C语言的类型无关, + 所有资源值具有C语言 <literal>unsigned long</literal> + 类型,并且必要时必须进行类型强制转换 (cast)。资源号不必连续, + 尽管对于ISA它们一般是连续的。ISA设备允许的资源编号为:</para> + + <programlisting> IRQ: 0-1 + DRQ: 0-1 + MEMORY: 0-3 + IOPORT: 0-7</programlisting> + + <para>所有资源被表示为带有起始值和计数的范围。对于IRQ和DRQ资源, + 计数一般等于1。内存的值引用物理地址。</para> + + <para>对资源能够执行三种类型的动作:</para> + + <itemizedlist> + <listitem><para>set/get</para></listitem> + <listitem><para>allocate/release</para></listitem> + <listitem><para>activate/deactivate</para></listitem> + </itemizedlist> + + <para>Set设置资源使用的范围。Allocation保留出请求的范围,使得 + 其它设备不能再占用(并检查此范围没有被其它设备占用)。 + Activation执行必要的动作使得驱动程序可以访问资源(例如,对于 + 内存,它将被映射到内核的虚拟地址空间)。</para> + + <para>操作资源的函数有:</para> + + <itemizedlist> + <listitem> + <para><function>int bus_set_resource(device_t dev, int type, + int rid, u_long start, u_long count)</function></para> + + <para>为资源设置范围。成功则返回0,否则返回错误码。 + 一般此函数只有在<literal>type</literal>, + <literal>rid</literal>,<literal>start</literal>或 + <literal>count</literal>之一的值超出了允许的范围才会 + 返回错误。</para> + + <itemizedlist> + <listitem> + <para> dev - 驱动程序的设备</para> + </listitem> + <listitem> + <para> type - 资源类型,SYS_RES_* </para> + </listitem> + <listitem> + <para> rid - 类型内部的资源号(ID)</para> + </listitem> + <listitem> + <para> start, count - 资源范围 </para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> + <para><function>int bus_get_resource(device_t dev, int type, + int rid, u_long *startp, u_long *countp)</function></para> + + <para>取得资源范围。成功则返回0,如果资源尚未定义则返回错误码。 + </para> + </listitem> + + <listitem> + <para><function>u_long bus_get_resource_start(device_t dev, + int type, int rid) u_long bus_get_resource_count (device_t + dev, int type, int rid)</function></para> + + <para>便捷函数,只用来获取start或count。出错的情况下返回0, + 因此如果0是资源的start合法值之一,将无法区分返回 + 的0是否指示错误。幸运的是,对于附加驱动程序,没有ISA资源的 + start值从0开始。</para> + </listitem> + + <listitem> + <para><function>void bus_delete_resource(device_t dev, int + type, int rid)</function></para> + <para>删除资源,令其未定义。</para> + </listitem> + + <listitem> + <para><function>struct resource * + bus_alloc_resource(device_t dev, int type, int *rid, + u_long start, u_long end, u_long count, u_int + flags)</function></para> + + <para>在start和end之间没有被其它设备占用的地方按count值 + 的范围分配一个资源。不过,不支持对齐。如果资源尚未被设置, + 则自动创建它。start为0,end为~0(全1)的这对特殊值 + 意味着必须使用以前通过 + <function>bus_set_resource()</function>设置的固定值: + start和count就是它们自己,end=(start+count),这种情况下, + 如果以前资源没有定义,则返回错误。尽管rid通过引用传递, + 但它并不被ISA总线的资源分配代码设置(其它总线可能使用不同的 + 方法并可能修改它)。</para> + </listitem> + </itemizedlist> + + <para>旗标是一个位映射,调用者感兴趣的有:</para> + + <itemizedlist> + <listitem> + <para><emphasis>RF_ACTIVE</emphasis> - 使得资源分配后 + 被自动激活。</para> + </listitem> + + <listitem> + <para><emphasis>RF_SHAREABLE</emphasis> - 资源可以同时 + 被多个驱动程序共享。</para> + </listitem> + + <listitem> + <para><emphasis>RF_TIMESHARE</emphasis> - 资源可以被多个驱动 + 程序分时共享,也就是说,被多个驱动程序同时分配,但任何 + 给定时间只能被其中一个激活。</para> + </listitem> +<!-- XXXDONT KNOW IT THESE SHOULD BE TWO SEPARATE LISTS OR NOT --> + <listitem> + <para>出错返回0。被分配的值可以使用 + <function>rhand_*()</function>从返回的句柄获得。</para> + </listitem> + <listitem> + <para><function>int bus_release_resource(device_t dev, int + type, int rid, struct resource *r)</function></para> + </listitem> + + <listitem> + <para>释放资源,r为<function>bus_alloc_resource()</function> + 返回的句柄。成功则返回0,否则返回错误码。</para> + </listitem> + + <listitem> + <para><function>int bus_activate_resource(device_t dev, int + type, int rid, struct resource *r)</function> + <function>int bus_deactivate_resource(device_t dev, int + type, int rid, struct resource *r)</function></para> + </listitem> + + <listitem> + <para>激活或禁用资源。成功则返回0,否则返回错误码。如果 + 资源被分时共享且当前被另一驱动程序激活,则返回 <literal>EBUSY</literal>。 + </para> + </listitem> + + <listitem> + <para><function>int bus_setup_intr(device_t dev, struct + resource *r, int flags, driver_intr_t *handler, void *arg, + void **cookiep)</function> <function>int + bus_teardown_intr(device_t dev, struct resource *r, void + *cookie)</function></para> + </listitem> + + <listitem> + <para>关联/分离中断处理程序与设备。成功则返回0,否则 + 返回错误码。</para> + </listitem> + + <listitem> + <para>r - 被激活的描述IRQ的资源句柄。</para> + <para>flags - 中断优先级,如下之一:</para> + + <itemizedlist> + <listitem> + <para><function>INTR_TYPE_TTY</function> - 终端和其它 + 类似的字符类型设备。使用 + <function>spltty()</function>屏蔽它们。</para> + </listitem> + <listitem> + <para><function>(INTR_TYPE_TTY | + INTR_TYPE_FAST)</function> - 输入缓冲较小的终端类型 + 设备,而且输入上的数据丢失很关键(例如老式串口)。 + 使用<function>spltty()</function>屏蔽它们。</para> + </listitem> + <listitem> + <para><function>INTR_TYPE_BIO</function> - 块类型设备, + 不包括CAM控制器上的。使用 + <function>splbio()</function>屏蔽它们。</para> + </listitem> + <listitem> + <para><function>INTR_TYPE_CAM</function> - CAM (通用访问 + 方法Common Access Method)总线控制器。使用 + <function>splcam()</function>屏蔽它们。</para> + </listitem> + <listitem> + <para><function>INTR_TYPE_NET</function> - 网络接口 + 控制器。使用 + <function>splimp()</function>屏蔽它们。</para> + </listitem> + <listitem> + <para><function>INTR_TYPE_MISC</function> - + 各种其它设备。除了通过 + <function>splhigh()</function>没有其它方法屏蔽它们。 + <function>splhigh()</function>屏蔽所有中断。 + </para> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + + <para>当中断处理程序执行时,匹配其优先级的所有其它中断都被屏蔽, + 唯一的例外是MISC级别,它不会屏蔽其它中断,也不会被其它中断 + 屏蔽。</para> + + <itemizedlist> + <listitem> + <para><emphasis>handler</emphasis> - 指向处理程序的指针, + 类型driver_intr_t被定义为<function>void + driver_intr_t(void *)</function></para> + </listitem> + <listitem> + <para><emphasis>arg</emphasis> - 传递给处理程序的参量,标识 + 特定设备。由处理程序将它从void*转换为任何实际类型。ISA + 中断处理程序的旧约定是使用单元号作为参量,新约定(推荐) + 使用指向设备softc结构的指针。</para> + </listitem> + <listitem> + <para><emphasis>cookie[p]</emphasis> - 从 + <function>setup()</function>接收的值,当传递给 + <function>teardown()</function> + 时用于标识处理程序。</para> + </listitem> + </itemizedlist> + + <para>定义了若干方法来操作资源句柄(struct resource *)。设备驱动 + 程序编写者感兴趣的有:</para> + + <itemizedlist> + <listitem> + <para><function>u_long rman_get_start(r) u_long + rman_get_end(r)</function> 取得被分配的资源范围的起始和结束。 + </para> + </listitem> + <listitem> + <para><function>void *rman_get_virtual(r)</function> 取得 + 被激活的内存资源的虚地址。</para> + </listitem> + </itemizedlist> + + </sect1> + + <sect1 xml:id="isa-driver-busmem"> + <title>总线内存映射</title> + + <para>很多情况下设备驱动程序和设备之间的数据交换是通过内存 + 进行的。有两种可能的变体:</para> + + <para>(a) 内存位于设备卡上</para> + <para>(b) 内存为计算机的主内存</para> + + <para>情况(a)中,驱动程序可能需要在卡上的内存与主存之间来回 + 拷贝数据。为了将卡上的内存映射到内核的虚地址空间,卡上内存的 + 物理地址和长度必须被定义为<literal>SYS_RES_MEMORY</literal>资源。 + 然后资源就可以被分配并激活,它的虚地址通过使用 + <function>rman_get_virtual()</function>获取。较老的驱动程序 + 将函数<function>pmap_mapdev()</function>用于此目的,现在 + 不应当再直接使用此函数。它已成为资源激活的一个内部步骤。</para> + + <para>大多数ISA卡的内存配置为物理地位于640KB-1MB范围之间的 + 某个位置。某些ISA卡需要更大的内存范围,位于16M以下的某个 + 位置(由于ISA总线上24位地址限制)。这种情况下,如果机器有 + 比设备内存的起始地址更多的内存(换句话说,它们重叠),则 + 必须在被设备使用的内存起始地址处配置一个内存空洞。许多 + BIOS允许在起始于14MB或15MB处配置1M的内存空洞。如果BIOS + 正确地报告内存空洞,FreeBSD就能够正确处理它们(此特性 + 在老BIOS上可能会出问题)。</para> + + <para>情况(b)中,只是数据的地址被发送到设备,设备使用DMA实际 + 访问主存中的数据。存在两个限制:首先,ISA卡只能访问16MB以下 + 的内存。其次,虚地址空间中连续的页面在物理地址空间中可能不 + 连续,设备可能不得不进行分散/收集操作。总线子系统为这些问题 + 提供现成现成的解决办法,剩下的必须由驱动程序自己完成。</para> + + <para>DMA内存分配使用了两个结构, <varname>bus_dma_tag_t</varname> + 和 <varname>bus_dmamap_t</varname>。 + 标签(tag)描述了DMA内存要求的特性。映射(map)表示按照这些 + 特性分配的内存块。多个映射可以与同一标签关联。</para> + + <para>标签按照对特性的继承而被组织成树型层次结构。子标签继承父 + 标签的所有要求,可以令其更严格,但不允许放宽要求。</para> + + <para>一般地,每个设备单元创建一个顶层标签(没有父标签)。如果 + 每个设备需要不同要求的内存区,则为每个内存区都会创建一个标签,这些 + 标签作为父标签的孩子。</para> + + <para>使用标签创建映射的方法有两种。</para> + + <para>其一,分配一大块符合标签要求的连续内存(以后可以被释放)。 + 这一般用于分配为了与设备通信而存在相对较长时间的那些内存区。 + 将这样的内存加载到映射中非常容易:它总是被看作位于适当物理 + 内存范围的一整块。</para> + + <para>其二,将虚拟内存中的任意区域加载到映射中。这片内存的 + 每一页都被检查,看是否符合映射的要求。如何符合则留在原始位置。 + 如果不符合则分配一个新的符合要求的 + <quote>反弹页面(bounce page)</quote>,用作中间存储。 + 当从不符合的原始页面写入数据时,数据首先被拷贝到反弹页面, + 然后从反弹页面传递到设备。当读取时,数据将会从设备到反弹页面, + 然后被拷贝到它们不符合的原始页面。原始和反弹页面之间的拷贝 + 处理被称作同步。这一般用于单次传输的基础之上:每次传输时 + 加载缓冲区,完成传输,卸载缓冲区。</para> + + <para>工作在DMA内存上的函数有:</para> + + <itemizedlist> + <listitem> + <para><function>int bus_dma_tag_create(bus_dma_tag_t parent, + bus_size_t alignment, bus_size_t boundary, bus_addr_t + lowaddr, bus_addr_t highaddr, bus_dma_filter_t *filter, void + *filterarg, bus_size_t maxsize, int nsegments, bus_size_t + maxsegsz, int flags, bus_dma_tag_t *dmat)</function></para> + + <para>创建新标签。成功则返回0,否则返回错误码。</para> + + <itemizedlist> + <listitem> + <para><emphasis>parent</emphasis> - 父标签或者NULL, + NULL用于创建顶层标签。</para> + </listitem> + + <listitem> + <para><emphasis>alignment</emphasis> - + 对将要分配给标签的内存区的对齐要求。<quote>no specific + alignment</quote>时值为1。仅应用于以后的 + <function>bus_dmamem_alloc()</function>而不是 + <function>bus_dmamap_create()</function>调用。</para> + </listitem> + + <listitem> + <para><emphasis>boundary</emphasis> - 物理地址边界, + 分配内存时不能穿过。对于<quote>no boundary</quote> + 使用0。仅应用于以后的 + <function>bus_dmamem_alloc()</function>而不是 + <function>bus_dmamap_create()</function>调用。 + 必须为2的乘方。如果计划以非层叠DMA方式使用内存(也就是说, + DMA地址由ISA DMA控制器提供而不是设备自身),则由于DMA硬件 + 限制,边界必须不能大于64KB (64*1024)。</para> + </listitem> + + <listitem> + <para><emphasis>lowaddr, highaddr</emphasis> - 名字稍微 + 有些误导。这些值用于限制可用于内存分配的物理地址的允许 + 范围。其确切含义根据以后不同的使用而有所不同。</para> + + <itemizedlist> + <listitem> + <para>对于<function>bus_dmamem_alloc()</function>, + 从0到lowaddr-1的所有地址被视为允许,更高的地址不允许 + 使用。</para> + </listitem> + + <listitem> + <para>对于<function>bus_dmamap_create()</function>, + 闭区间[lowaddr; highaddr]之外的所有地址被视为可访问。 + 范围之内的地址页面被传递给过滤函数,由它决定是否可访问。 + 如果没有提供过滤函数,则整个区间被视为不可访问。</para> + </listitem> + + <listitem> + <para>对于ISA设备,正常值(没有过滤函数)为:</para> + <para>lowaddr = BUS_SPACE_MAXADDR_24BIT</para> + <para>highaddr = BUS_SPACE_MAXADDR</para> + </listitem> + </itemizedlist> + + </listitem> + + <listitem> + <para><emphasis>filter, filterarg</emphasis> - 过滤函数及其 + 参数。如果filter为NULL,则当调用 + <function>bus_dmamap_create()</function>时,整个区间 + [lowaddr, highaddr]被视为不可访问。 + 否则,区间[lowaddr; highaddr]内的每个被试图访问的页面的 + 物理地址被传递给过滤函数,由它决定是否可访问。过滤函数的 + 原型为:<function>int filterfunc(void *arg, + bus_addr_t paddr)</function>。当页面可以被访问时它必须 + 返回0,否则返回非零值。</para> + </listitem> + + <listitem> + <para><emphasis>maxsize</emphasis> - 通过此标签可以分配的 + 最大内存值(以字节计)。有时这个值很难估算,或者可以任意大, + 这种情况下,对于ISA设备这个值可以设为 + <literal>BUS_SPACE_MAXSIZE_24BIT</literal>。</para> + </listitem> + + <listitem> + <para><emphasis>nsegments</emphasis> - 设备支持的分散/收集段 + 的最大数目。如果不加限制,则使用应当使用值 + <literal>BUS_SPACE_UNRESTRICTED</literal>。 + 建议对父标签使用这个值,而为子孙标签指定实际限制。 + nsegments值等于 <literal>BUS_SPACE_UNRESTRICTED</literal> + 的标签不能用于实际加载映射,仅可以将它们作为父标签。 nsetments + 的实际限制大约为250-300,再高的值将导致内核堆栈溢出(硬件 + 无法正常支持那么多的分散/收集缓冲区)。</para> + </listitem> + + <listitem> + <para><emphasis>maxsegsz</emphasis> - 设备支持的分散/收集段 + 的最大尺寸。对于ISA设备的最大值为 + <literal>BUS_SPACE_MAXSIZE_24BIT</literal>。</para> + </listitem> + + <listitem> + <para><emphasis>flags</emphasis> - 旗标的位图。感兴趣的旗标 + 只有:</para> + + <itemizedlist> + <listitem> + <para><emphasis>BUS_DMA_ALLOCNOW</emphasis> - 创建标签时 + 请求分配所有可能用到的反射页面。</para> + </listitem> + + <listitem> + <para><emphasis>BUS_DMA_ISA</emphasis> - 比较神秘的一个标志, + 仅用于Alpha机器。i386机器没有定义它。Alpha机器的所有ISA设备 + 都应当使用这个标志,但似乎还没有这样的驱动程序。</para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> + <para><emphasis>dmat</emphasis> - 指向返回的新标签的存储的 + 指针。</para> + </listitem> + + </itemizedlist> + + </listitem> + + <listitem> <!-- Second entry in list alpha --> + <para><function>int bus_dma_tag_destroy(bus_dma_tag_t + dmat)</function></para> + + <para>销毁标签。成功则返回0,否则返回错误码。</para> + + <para>dmat - 被销毁的标签。</para> + + </listitem> + + <listitem> <!-- Third entry in list alpha --> + <para><function>int bus_dmamem_alloc(bus_dma_tag_t dmat, + void** vaddr, int flags, bus_dmamap_t + *mapp)</function></para> + + <para>分配标签所描述的一块连续内存区。被分配的内存的大小为标签的 + maxsize。成功则返回0,否则返回错误码。调用结果被用于获取内存的 + 物理地址,但在此之前必须用<function>bus_dmamap_load()</function> + 将其加载。</para> + + <itemizedlist> + <listitem> + <para> + <emphasis>dmat</emphasis> - 标签 + </para> + </listitem> + <listitem> + <para> + <emphasis>vaddr</emphasis> - 指向存储的指针,该存储空间 + 用于返回的分配区域的内核虚地址。 + </para> + </listitem> + <listitem> + <para> + flags - 旗标的位图。唯一感兴趣的旗标为: + </para> + <itemizedlist> + <listitem> + <para> + <emphasis>BUS_DMA_NOWAIT</emphasis> - 如果内存不能 + 立即可用则返回错误。如果此标志没有设置,则允许例程 + 睡眠,直到内存可用为止。 + </para> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <para> + <emphasis>mapp</emphasis> - 指向返回的新映射的存储的指针。 + </para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> <!-- Fourth entry in list alpha --> + <para> + <function>void bus_dmamem_free(bus_dma_tag_t dmat, void + *vaddr, bus_dmamap_t map)</function> + </para> + <para> + 释放由<function>bus_dmamem_alloc()</function>分配的内存。 + 目前,对分配的带有ISA限制的内存的释放没有实现。因此,建议的 + 使用模型为尽可能长时间地保持和重用分配的区域。不要轻易地 + 释放某些区域,然后再短时间地分配它。这并不意味着不应当使用 + <function>bus_dmamem_free()</function>:希望很快它就会被 + 完整地实现。 + </para> + + <itemizedlist> + <listitem> + <para><emphasis>dmat</emphasis> - 标签 + </para> + </listitem> + <listitem> + <para> + <emphasis>vaddr</emphasis> - 内存的内核虚地址 + </para> + </listitem> + <listitem> + <para> + <emphasis>map</emphasis> - 内存的映射(跟 + <function>bus_dmamem_alloc()</function>返回的一样) + </para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> <!-- The fifth entry in list alpha --> + <para> + <function>int bus_dmamap_create(bus_dma_tag_t dmat, int + flags, bus_dmamap_t *mapp)</function> + </para> + <para> + 为标签创建映射,以后用于 + <function>bus_dmamap_load()</function>。成功则返回0,否则 + 返回错误码。 + </para> + <itemizedlist> + <listitem> + <para> + <emphasis>dmat</emphasis> - 标签 + </para> + </listitem> + <listitem> + <para> + <emphasis>flags</emphasis> - 理论上是旗标的位图。但还 + 从未定义过任何旗标,因此目前总是0。 + </para> + </listitem> + <listitem> + <para> + <emphasis>mapp</emphasis> - 指向返回的新映射的存储的指针。 + </para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> <!-- Sixth entry in the alpha list --> + <para> + <function>int bus_dmamap_destroy(bus_dma_tag_t dmat, + bus_dmamap_t map)</function> + </para> + <para> + 销毁映射。成功则返回0,否则返回错误码。 + </para> + + <itemizedlist> + <listitem> + <para> + dmat - 与映射关联的标签 + </para> + </listitem> + <listitem> + <para> + map - 将要被销毁的映射 + </para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> <!-- Seventh entry in list alpha --> + <para> + <function>int bus_dmamap_load(bus_dma_tag_t dmat, + bus_dmamap_t map, void *buf, bus_size_t buflen, + bus_dmamap_callback_t *callback, void *callback_arg, int + flags)</function> + </para> + <para> + 加载缓冲区到映射中(映射必须事先由 + <function>bus_dmamap_create()</function>或者 + <function>bus_dmamem_alloc()</function>)创建。缓冲区的所有 + 页面都会被检查,看是否符合标签的要求,并为那些不符合的分配 + 反弹页面。会创建物理段描述符的数组,并将其传递给回调函数。 + 回调函数以某种方式处理这个数组。系统中的反弹缓冲区是受限的, + 因此如果需要的反弹缓冲区不能立即获得,则将请求入队,当反弹 + 缓冲区可用时再调用回调函数。如果回调函数立即执行则返回0, + 如果请求被排队,等待将来执行,则返回 + <errorname>EINPROGRESS</errorname>。后一种情况下, + 与排队的回调函数之间的同步由驱动程序负责。 + </para> + <!--<blockquote>--> + <itemizedlist> + <listitem> + <para> + <emphasis>dmat</emphasis> - 标签 + </para> + </listitem> + <listitem> + <para> + <emphasis>map</emphasis> - 映射 + </para> + </listitem> + <listitem> + <para> + <emphasis>buf</emphasis> - 缓冲区的内核虚地址 + </para> + </listitem> + <listitem> + <para> + <emphasis>buflen</emphasis> - 缓冲区的长度 + </para> + </listitem> + <listitem> + <para> + <emphasis>callback</emphasis>,<function> + callback_arg</function> - 回调函数及其参数 + </para> + </listitem> + </itemizedlist> + <!--</blockquote>--> + <para> + 回调函数的原型为: + </para> + <para> + <function>void callback(void *arg, bus_dma_segment_t + *seg, int nseg, int error)</function> + </para> + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + <emphasis>arg</emphasis> - 与传递给 + <function>bus_dmamap_load()</function>的callback_arg + 相同。 + </para> + </listitem> + <listitem> + <para> + <emphasis>seg</emphasis> - 段描述符的数组 + </para> + </listitem> + <listitem> + <para> + <emphasis>nseg</emphasis> - 数组中的描述符个数 + </para> + </listitem> + <listitem> + <para> + <emphasis>error</emphasis> - 表示段数目溢出:如被设为 + <errorname>EFBIG</errorname>, + 则标签允许的最大数目的段无法容纳缓冲区。 + 这种情况下数组中的描述符的数目只有标签许可的那么多。 + 对这种情况的处理由驱动程序决定:根据希望的语义,驱动 + 程序可以视其为错误,或将缓冲区分为两个并单独处理第二个。 + </para> + </listitem> + </itemizedlist> + <!-- </blockquote> --> + <para> + 段数组中的每一项包含如下字段: + </para> + + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + <emphasis>ds_addr</emphasis> - 段物理地址 + </para> + </listitem> + <listitem> + <para> + <emphasis>ds_len</emphasis> - 段长度 + </para> + </listitem> + </itemizedlist> + <!-- </blockquote>--> + </listitem> + + <listitem> <!-- Eighth entry in alpha list --> + <para> + <function>void bus_dmamap_unload(bus_dma_tag_t dmat, + bus_dmamap_t map)</function> + </para> + <para>unload the map. + </para> + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + <emphasis>dmat</emphasis> - 标签 + </para> + </listitem> + <listitem> + <para> + <emphasis>map</emphasis> - 已加载的映射 + </para> + </listitem> + </itemizedlist> + <!-- </blockquote> --> + </listitem> + + <listitem> <!-- Ninth entry list alpha --> + <para> + <function>void bus_dmamap_sync (bus_dma_tag_t dmat, + bus_dmamap_t map, bus_dmasync_op_t op)</function> + </para> + <para> + 与设备进行物理传输前后,将加载的缓冲区与其反弹页面进行同步。 + 此函数完成原始缓冲区与其映射版本之间所有必需的数据拷贝工作。 + 进行传输之前和之后必须对缓冲区进行同步。 + </para> + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + <emphasis>dmat</emphasis> - 标签 + </para> + </listitem> + <listitem> + <para> + <emphasis>map</emphasis> - 已加载的映射 + </para> + </listitem> + <listitem> + <para> + <emphasis>op</emphasis> - 要执行的同步操作的类型: + </para> + </listitem> + </itemizedlist> + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + <function>BUS_DMASYNC_PREREAD</function> - 从设备到 + 缓冲区的读操作之前 + </para> + </listitem> + <listitem> + <para> + <function>BUS_DMASYNC_POSTREAD</function> - 从设备到 + 缓冲区的读操作之后 + </para> + </listitem> + <listitem> + <para> + <function>BUS_DMASYNC_PREWRITE</function> - 从缓冲区到 + 设备的写操作之前 + </para> + </listitem> + <listitem> + <para> + <function>BUS_DMASYNC_POSTWRITE</function> - 从缓冲区到 + 设备的写操作之后 + </para> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> <!-- End of list alpha --> +<!-- </blockquote> +</blockquote> --> + + <para> + 当前PREREAD和POSTWRITE为空操作,但将来可能会改变,因此驱动程序 + 中不能忽略它们。由<function>bus_dmamem_alloc()</function> + 获得的内存不需要同步。 + </para> + <para> + 从<function>bus_dmamap_load()</function>中调用回调函数之前, + 段数组是存储在栈中的。并且是按标签允许的最大数目的段预先分配 + 好的。这样由于i386体系结构上对段数目的实际限制约为250-300 + (内核栈为4KB减去用户结构的大小,段数组条目的大小为8字节,和 + 其它必须留出来的空间)。由于数组基于最大数目而分配,因此这个值 + 必须不能设置成超出实际需要。幸运的是,对于大多数硬件而言, + 所支持的段的最大数目低很多。但如果驱动程序想处理具有非常多 + 分散/收集段的缓冲区,则应当一部分一部分地处理:加载缓冲区的 + 一部分,传输到设备,然后加载缓冲区的下一部分,如此反复。 + </para> + <para> + 另一个实践结论是段数目可能限制缓冲区的大小。如果缓冲区中的 + 所有页面碰巧物理上不连续,则分片情况下支持的最大缓冲区尺寸 + 为(nsegments * page_size)。例如,如果支持的段的最大数目为10, + 则在i386上可以确保支持的最大缓冲区大小为40K。如果希望更大的 + 则需要在驱动程序中使用一些特殊技巧。 + </para> + <para> + 如果硬件根本不支持分散/收集,或者驱动程序希望即使在严重分片的 + 情况下仍然支持某种缓冲区大小,则解决办法是:如果无法容纳下原始 + 缓冲区,就在驱动程序中分配一个连续的缓冲区作为中间存储。 + </para> + <para> + 下面是当使用映射时的典型调用顺序,根据对映射的具体使用而不同。 + 字符->用于显示时间流。 + </para> + <para> + 对于从连接到分离设备,这期间位置一直不变的缓冲区:</para> + <para> + bus_dmamem_alloc -> bus_dmamap_load -> ...use buffer... -> + -> bus_dmamap_unload -> bus_dmamem_free + </para> + + <para>对于从驱动程序外部传递进去,并且经常变化的缓冲区: + + <!-- XXX is this correct? --> + <programlisting> bus_dmamap_create -> + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + ... + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + -> bus_dmamap_destroy </programlisting> + + </para> + <para> + 当加载由<function>bus_dmamem_alloc()</function>创建的映射时,传递 + 进去的缓冲区的地址和大小必须和 + <function>bus_dmamem_alloc()</function>中使用的一样。这种情况下就 + 可以保证整个缓冲区被作为一个段而映射(因而回调可以基于此假设), + 并且请求被立即执行(永远不会返回EINPROGRESS)。这种情况下回调函数 + 需要作的只是保存物理地址。 + </para> + <para> + 典型示例如下: + </para> + + <programlisting> static void + alloc_callback(void *arg, bus_dma_segment_t *seg, int nseg, int error) + { + *(bus_addr_t *)arg = seg[0].ds_addr; + } + + ... + int error; + struct somedata { + .... + }; + struct somedata *vsomedata; /* 虚地址 */ + bus_addr_t psomedata; /* 物理总线相关的地址 */ + bus_dma_tag_t tag_somedata; + bus_dmamap_t map_somedata; + ... + + error=bus_dma_tag_create(parent_tag, alignment, + boundary, lowaddr, highaddr, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(struct somedata), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(struct somedata), /*flags*/ 0, + &tag_somedata); + if(error) + return error; + + error = bus_dmamem_alloc(tag_somedata, &vsomedata, /* flags*/ 0, + &map_somedata); + if(error) + return error; + + bus_dmamap_load(tag_somedata, map_somedata, (void *)vsomedata, + sizeof (struct somedata), alloc_callback, + (void *) &psomedata, /*flags*/0); </programlisting> + + <para> + 代码看起来有点长,也比较复杂,但那是正确的使用方法。实际结果是: + 如果分配多个内存区域,则总将它们组合成一个结构并作为整体分配 + (如果对齐和边界限制允许的话)是一个很好的主意。 + </para> + <para> + 当加载任意缓冲区到由<function>bus_dmamap_create()</function> + 创建的映射时,由于回调可能被延迟,因此必须采取特殊措施与回调 + 函数进行同步。代码看起来像下面的样子: + </para> + + <programlisting> { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load( + dmat, + dmamap, + buffer_ptr, + buffer_len, + callback, + /*callback_arg*/ buffer_descriptor, + /*flags*/0); + if (error == EINPROGRESS) { + /* + * 执行必要的操作以确保与回调的同步。 + * 回调被确保直到我们执行了splx()或tsleep()才会被调用。 + */ + } + splx(s); + } </programlisting> + + <para> + 处理请求的两种方法分别是: + </para> + <para> + 1. 如果通过显式地标记请求已经结束来完成请求(例如CAM请求),则 + 将所有进一步的处理放入回调驱动程序中会比较简单,回调结束后会 + 标记请求。之后不需要太多额外的同步。由于流控制的原因,冻结请求 + 队列直到请求完成才释放可能是个好主意。 + </para> + <para> + 2. 如果请求是在函数返回时完成(例如字符设备上传统的读写请求), + 则需要在缓冲区描述符上设置同步标志,并调用 + <function>tsleep()</function>。后面当回调函数被调用时,它将 + 执行处理并检查同步标志。如果设置了同步标志,它应该发出一个 + 唤醒操作。在这种方法中,回调函数或者进行所由必需的处理(就像 + 前面的情况),或者简单在缓冲区描述符中存储段数组。回调完成 + 后,回调函数就能使用这个存储的段数组并进行所有的处理。 + </para> + </sect1> +<!--_________________________________________________________________________--> +<!--~~~~~~~~~~~~~~~~~~~~END OF SECTION~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> + + <sect1 xml:id="isa-driver-dma"> + <title>DMA</title> + <!-- Section Marked up by Wylie --> + + <indexterm><primary>Direct Memory Access (DMA, 直接内存访问)</primary></indexterm> + + <para> + ISA总线中Direct Memory Access (DMA)是通过DMA控制器(实际上是它们 + 中的两个,但这只是无关细节)实现的。为了使以前的ISA设备简单便宜, + 总线控制和地址产生的逻辑都集中在DMA控制器中。幸运的是,FreeBSD + 提供了一套函数,这些函数大多把DMA控制器的繁琐细节对设备驱动程序 + 隐藏了起来。 + </para> + + <para> + 最简单情况是那些比较智能的设备。就象PCI上的总线主设备一样, + 它们自己能产生总线周期和内存地址。它们真正从DMA控制器需要的 + 唯一事情是总线仲裁。所以为了此目的,它们假装是级联从DMA控制器。 + 当连接驱动程序时,系统DMA控制器需要做的唯一事情就是通过调用 + 如下函数在一个DMA通道上激活级联模式。 + </para> + + <para> + <function>void isa_dmacascade(int channel_number)</function> + </para> + + <para> + 所有进一步的活动通过对设备编程完成。当卸载驱动程序时,不需要 + 调用DMA相关的函数。 + </para> + + <para> + 对于较简单的设备,事情反而变得复杂。使用的函数包括: + </para> + + <itemizedlist> + + <listitem> + <para> + <function>int isa_dma_acquire(int chanel_number)</function> + </para> + <para> + 保留一个DMA通道。成功则返回0,如果通道已经被保留或被其它 + 驱动程序保留则返回EBUSY。大多数的ISA设备都不能共享DMA通道, + 因此这个函数通常在连接设备时调用。总线资源的现代接口使得 + 这种保留成为多余,但目前仍必须使用。如果不使用,则后面其它 + DMA例程将会panic。 + </para> + </listitem> + + <listitem> + <para> + <function>int isa_dma_release(int chanel_number)</function> + </para> + <para> + 释放先前保留的DMA通道。释放通道时必须不能有正在进行中的 + 传输(另外,释放通道后设备必须不能再试图发起传输)。 + </para> + </listitem> + + <listitem> + <para> + <function>void isa_dmainit(int chan, u_int + bouncebufsize)</function> + </para> + <para> + 分配由特定通道使用的反弹缓冲区。请求的缓冲区大小不能超过 + 64KB。以后,如果传输缓冲区碰巧不是物理连续的,或超出ISA + 总线可访问的内存范围,或跨越64KB的边界,则会自动使用反弹 + 缓冲区。如果传输总是使用符合上述条件的缓冲区(例如,由 + <function>bus_dmamem_alloc()</function>分配的那些),则 + 不需要调用<function>isa_dmainit()</function>。但使用此函数 + 会让通过DMA控制器传输任意数据变得非常方便。 + </para> + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + <emphasis>chan</emphasis> - 通道号 + </para> + </listitem> + <listitem> + <para> + <emphasis>bouncebufsize</emphasis> - 以字节计数的反弹 + 缓冲区的大小 + </para> + </listitem> + </itemizedlist> +<!-- </blockquote> --> +<!--</para> --> + </listitem> + + <listitem> + <para> + <function>void isa_dmastart(int flags, caddr_t addr, u_int + nbytes, int chan)</function> + </para> + <para> + 准备启动DMA传输。实际启动设备上的传输之前必需调用此函数 + 来设置DMA控制器。它检查缓冲区是否连续的且在ISA内存范围 + 之内,如果不是则自动使用反弹缓冲区。如果需要反弹缓冲区, + 但反弹缓冲区没有用<function>isa_dmainit()</function> + 设置,或对于请求的传输大小来说太小,则系统将panic。 + 写请求且使用反弹缓冲区的情况下,数据将被自动拷贝到反弹 + 缓冲区。 + </para> + </listitem> + <listitem> + <para>flags - 位掩码,决定将要完成的操作的类型。方向位B_READ和 + B_WRITE互斥。 + </para> + <!-- <blockquote> --> + <itemizedlist> + <listitem> + <para> + B_READ - 从ISA总线读到内存 + </para> + </listitem> + <listitem> + <para> + B_WRITE - 从内存写到ISA总线上 + </para> + </listitem> + <listitem> + <para> + B_RAW - 如果设置则DMA控制器将会记住缓冲区,并在传输结束后 + 自动重新初始化它自己,再次重复传输同一缓冲区(当然,驱动 + 程序可能发起设备的另一个传输之前改变缓冲区中的数据)。 + 如果没有设置,参数只对一次传输有效,在发起下一次传输之前 + 必须再次调用<function>isa_dmastart()</function>。只有在不 + 使用反弹缓冲区时使用B_RAW才有意义。 + </para> + </listitem> + </itemizedlist> +<!-- </blockquote> --> + </listitem> + <listitem> + <para> + addr - 缓冲区的虚地址 + </para> + </listitem> + <listitem> + <para> + nbytes - 缓冲区长度。必须小于等于64KB。不允许长度为0:因为 + DMA控制器将会理解为64KB,而内核代码把它理解为0,那样就会导致 + 不可预测的效果。对于通道号等于和高于4的情况,长度必需为偶数, + 因为这些通道每次传输2字节。奇数长度情况下,最后一个字节不被 + 传输。 + </para> + </listitem> + <listitem> + <para> + chan - 通道号 + </para> + </listitem> + + <listitem> + <para> + <function>void isa_dmadone(int flags, caddr_t addr, int + nbytes, int chan)</function> + </para> + <para> + 设备报告传输完成后,同步内存。如果是使用反弹缓冲区的读操作, + 则将数据从反弹缓冲区拷贝到原始缓冲区。参量与 + <function>isa_dmastart()</function>的相同。允许使用B_RAW标志, + 但它一点也不会影响<function>isa_dmadone()</function>。 + </para> + </listitem> + + <listitem> + <para> + <function>int isa_dmastatus(int channel_number)</function> + </para> + <para> + 返回当前传输中剩余的字节数。在 + <function>isa_dmastart()</function>中设置了B_READ的情况下, + 返回的数字一定不会等于零。传输结束时它会被自动复位到缓冲区的 + 长度。正式的用法是在设备发信号指示传输已完成时检查剩余的字节数。 + 如果字节数不为0,则此次传输可能有问题。 + </para> + </listitem> + + <listitem> + <para> + <function>int isa_dmastop(int channel_number)</function> + </para> + <para> + 放弃当前的传输并返回剩余未传输的字节数。 + </para> + </listitem> + </itemizedlist> + </sect1> + + <sect1 xml:id="isa-driver-probe"> + <title>xxx_isa_probe</title> + <!-- Section marked up by Wylie --> + + <para> + 这个函数探测设备是否存在。如果驱动程序支持自动侦测设备配置的 + 某些部分(如中断向量或内存地址),则自动侦测必须在此例程中完成。 + </para> + + <para> + 对于任意其他总线,如果不能侦测到设备,或者侦测到但自检失败, + 或者发生某些其他问题,则应当返回一个正值的错误。如果设备不 + 存在则必须返回值 <errorname>ENXIO</errorname>。 + 其他错误值可能表示其他条件。零或负值 + 意味着成功。大多数驱动程序返回零表示成功。 + </para> + + <para> + 当PnP设备支持多个接口时使用负返回值。例如,不同驱动程序支持 + 老的兼容接口和较新的高级接口。则两个驱动程序都将侦测设备。 + 在探测例程中返回较高值的驱动程序获得优先(换句话说,返回0的 + 驱动程序具有最高的优先级,返回-1的其次,返回-2的更后,等等)。 + 这样,仅支持老接口的设备将被老驱动程序处理(其应当从探测例程中 + 返回-1),而同时也支持新接口的设备将由新驱动程序处理(其应当从 + 探测例程中返回0)。 + </para> + + <para> + 设备描述符结构xxx_softc由系统在调用探测例程之前分配。如果 + 探测例程返回错误,描述符会被系统自动取消分配。因此如果出现 + 探测错误,驱动程序必须保证取消分配探测期间它使用的所有资源, + 且确保没有什么能够阻止描述符被安全地取消分配。如果探测成功 + 完成,描述符将由系统保存并在以后传递给例程 + <function>xxx_isa_attach()</function>。如果驱动程序返回负值, + 就不能保证它将获得最高优先权且其连接例程会被调用。因此这种 + 情况下它也必须在返回前释放所有的资源,并在需要的时候在连接 + 例程中重新分配它们。当<function>xxx_isa_probe()</function> + 返回0时,在返回前释放资源也是一个好主意,而且中规中矩的驱动 + 程序应当这样做。但在释放资源会存在某些问题的情况下,允许驱动 + 程序在从探测例程返回0和连接例程的执行之间保持资源。 + </para> + + <para> + 典型的探测例程以取得设备描述符和单元号开始: + </para> + + <programlisting> struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int pnperror; + int error = 0; + + sc->dev = dev; /* 链接回来 */ + sc->unit = unit; </programlisting> + + <para> + 然后检查PnP设备。检查是通过一个包含PnP ID列表的表进行的。此表 + 包含这个驱动程序支持的PnP ID和以人工可读形式给出的对应这些ID的 + 设备型号的描述。 + </para> + + <programlisting> + pnperror=ISA_PNP_PROBE(device_get_parent(dev), dev, + xxx_pnp_ids); if(pnperror == ENXIO) return ENXIO; + </programlisting> + + <para> + ISA_PNP_PROBE的逻辑如下:如果卡(设备单元)没有被作为PnP侦测到, + 则返回ENOENT。如果被作为PnP侦测到,但侦测到的ID不匹配表中的 + 任一ID,则返回ENXIO。最后,如果设备能支持PnP且匹配表中的一个 + ID,则返回0,并且由<function>device_set_desc()</function>从 + 表中取得适当的描述进行设置。 + </para> + + <para> + 如果设备驱动程序仅支持PnP设备,则情况看起来如下: + </para> + + <programlisting> if(pnperror != 0) + return pnperror; </programlisting> + + <para> + 对于不支持PnP的驱动程序不需要特殊处理,因为驱动程序会传递空的 + PnP ID表,且如果在PnP卡上调用会得到ENXIO。 + </para> + + <para> + 探测例程通常至少需要某些最少量的资源,如I/O端口号,来发现并探测卡。 + 对于不同的硬件,驱动程序可能会自动发现其他必需的资源。PnP设备的 + 所有资源由PnP子系统预先设置,因此驱动程序不需要自己发现它们。</para> + + <para> + 通常访问设备所需要的最少信息就是端口号。然后某些设备允许从设备 + 配置寄存器中取得其余信息(尽管不是所有的设备都这样)。因此首先 + 我们尝试取得端口起始值: + </para> + + <programlisting> sc->port0 = bus_get_resource_start(dev, + SYS_RES_IOPORT, 0 /*rid*/); if(sc->port0 == 0) return ENXIO; + </programlisting> + + <para> + 基端口地址被保存在softc结构中,以便将来使用。如果需要经常使用 + 端口,则每次都调用资源函数将会慢的无法忍受。如果我们没有得到 + 端口,则返回错误即可。相反,一些设备驱动程序相当聪明,尝试探测 + 所有可能的端口,如下: + </para> + + <programlisting> + /* 此设备所有可能的基I/O端口地址表 */ + static struct xxx_allports { + u_short port; /* 端口地址 */ + short used; /* 旗标:此端口是否已被其他单元使用 */ + } xxx_allports = { + { 0x300, 0 }, + { 0x320, 0 }, + { 0x340, 0 }, + { 0, 0 } /* 表结束 */ + }; + + ... + int port, i; + ... + + port = bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/); + if(port !=0 ) { + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used || xxx_allports[i].port != port) + continue; + + /* 找到了 */ + xxx_allports[i].used = 1; + /* 在已知端口上探测 */ + return xxx_really_probe(dev, port); + } + return ENXIO; /* 端口无法识别或已经被使用 */ + } + + /* 仅在需要猜测端口的时候才会到达这儿 */ + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used) + continue; + + /* 标记为已被使用 - 即使我们在此端口什么也没有发现 + * 至少我们以后不会再次探测 + */ + xxx_allports[i].used = 1; + + error = xxx_really_probe(dev, xxx_allports[i].port); + if(error == 0) /* 在那个端口找到一个设备 */ + return 0; + } + /* 探测过所有可能的地址,但没有可用的 */ + return ENXIO;</programlisting> + + <para> + 当然,做这些事情通常应该使用驱动程序的 + <function>identify()</function>例程。但可能有一个正当的理由来 + 说明为什么在函数<function>probe()</function>中完成更好:如果 + 这种探测会让一些其他敏感设备发疯。探测例程按旗标 + <literal>sensitive</literal>排序:敏感设备首先被探测,然后是 + 其他设备。但<function>identify()</function>例程在所有探测之前 + 被调用,因此它们不会考虑敏感设备并可能扰乱这些设备。 + </para> + + <para> + 现在,我们得到起始端口以后就需要设置端口数(PnP设备除外),因为 + 内核在配置文件中没有这个信息。 + </para> + + <programlisting> + if(pnperror /* 只对非PnP设备 */ + && bus_set_resource(dev, SYS_RES_IOPORT, 0, sc->port0, + XXX_PORT_COUNT)<0) + return ENXIO;</programlisting> + + <para> + 最后分配并激活一片端口地址空间(特殊值start和end意思是说 + <quote>使用我们通过<function>bus_set_resource()</function> + 设置的那些值</quote>): + </para> + + <programlisting> + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, + &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO;</programlisting> + + <para> + 现在可以访问端口映射的寄存器后,我们就可以以某种方式向设备写入 + 数据并检查设备是否如我们期望的那样作出反应。如果没有,则说明 + 可能其他的设备在这个地址上,或者这个地址上根本没有设备。 + </para> + + <para> + 通常驱动程序直到连接例程才会设置中断处理函数。这之前我们替代以 + 轮询模式进行探测,超时则以<function>DELAY()</function>实现。 + 探测例程必须确保不能永久挂起,设备上的所有等待必须在超时内完成。 + 如果设备不在这段时间内响应,则可能设备出故障或配置错误,驱动程序 + 必须返回错误,当确定超时间隔时,给设备一些额外时间以确保可靠: + 尽管假定<function>DELAY()</function>在任何机器上都延时相同数量的 + 时间,但随具体CPU的不同,此函数还是有一定的误差幅度。 + </para> + + <para> + 如果探测例程真的想检查中断是否真的工作,它可以也配置和探测中断。 + 但不建议这样。 + </para> + + <programlisting> + /* 以严重依赖于具体设备的方式实现 */ + if(error = xxx_probe_ports(sc)) + goto bad; /* 返回前释放资源 */ + </programlisting> + + <para> + 依赖于所发现设备的确切型号,函数 + <function>xxx_probe_ports()</function>也可能设置设备描述。但 + 如果只支持一种设备型号,则也可以硬编码的形式完成。当然,对于 + PnP设备,PnP支持从表中自动设置描述。 + </para> + + + <programlisting> if(pnperror) + device_set_desc(dev, "Our device model 1234"); + </programlisting> + + <para> + 探测例程应当或者通过读取设备配置寄存器来发现所有资源的范围, + 或者确保由用户显式设置。我们将假定一个带板上内存的例子。 + 探测例程应当尽可能是非插入式的,这样分配和检查其余资源功能性 + 的工作就可以更好地留给连接例程来做。 + </para> + + <para> + 内存地址可以在内核配置文件中指定,或者对应某些设备可以在非易失性 + 配置寄存器中预先配置。如果两种做法均可用却不同,那么应当用 + 哪个呢?可能用户厌烦在内核配置文件中明确设置地址,但他们知道 + 自己在干什么,则应当优先使用这个。一个实现的例子可能是这样的: + </para> + <programlisting> + /* 首先试图找出配置地址 */ + sc->mem0_p = bus_get_resource_start(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_p == 0) { /* 没有,用户没指定 */ + sc->mem0_p = xxx_read_mem0_from_device_config(sc); + + + if(sc->mem0_p == 0) + /* 从设备配置寄存器也到不了这儿 */ + goto bad; + } else { + if(xxx_set_mem0_address_on_device(sc) < 0) + goto bad; /* 设备不支持那地址 */ + } + + /* 就像端口,设置内存大小, + * 对于某些设备,内存大小不是常数, + * 而应当从设备配置寄存器中读取,以适应设备的不同型号 + * 另一个选择是让用户把内存大小设置为“msize”配置资源, + * 由ISA总线自动处理 + */ + if(pnperror) { /*仅对非PnP设备 */ + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_size == 0) /* 用户没有指定 */ + sc->mem0_size = xxx_read_mem0_size_from_device_config(sc); + + if(sc->mem0_size == 0) { + /* 假定这是设备非常老的一种型号,没有自动配置特性, + * 用户也没有偏好设置,因此假定最低要求的情况 + * (当然,真实值将根据设备驱动程序而不同) + */ + sc->mem0_size = 8*1024; + } + + if(xxx_set_mem0_size_on_device(sc) < 0) + goto bad; /*设备不支持那个大小 */ + + if(bus_set_resource(dev, SYS_RES_MEMORY, /*rid*/0, + sc->mem0_p, sc->mem0_size)<0) + goto bad; + } else { + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + } </programlisting> + + <para> + 类似, 很容易检查IRQ和DRQ所用的资源。 + </para> + + <para> + 如果一切进行正常,然后就可以释放所有资源并返回成功。 + </para> + + <programlisting> xxx_free_resources(sc); + return 0;</programlisting> + + <para> + 最后,处理棘手情况。所有资源应当在返回前被释放。我们利用这样一个 + 事实:softc结构在传递给我们以前被零化,因此我们能够找出是否分配了 + 某些资源:如果分配则这些资源的描述符非零。 + </para> + + <programlisting> bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* 确切错误未知 */ + return ENXIO;</programlisting> + + <para> + 这是完整的探测例程。资源的释放从多个地方完成,因此将它挪到一个 + 函数中,看起来可能像下面的样子: + </para> + +<programlisting>static void + xxx_free_resources(sc) + struct xxx_softc *sc; + { + /* 检查每个资源,如果非0则释放 */ + + /* 中断处理函数 */ + if(sc->intr_r) { + bus_teardown_intr(sc->dev, sc->intr_r, sc->intr_cookie); + bus_release_resource(sc->dev, SYS_RES_IRQ, sc->intr_rid, + sc->intr_r); + sc->intr_r = 0; + } + + /* 我们分配过的所有种类的内存 */ + if(sc->data_p) { + bus_dmamap_unload(sc->data_tag, sc->data_map); + sc->data_p = 0; + } + if(sc->data) { /* sc->data_map等于0有可能合法 */ + /* the map will also be freed */ + bus_dmamem_free(sc->data_tag, sc->data, sc->data_map); + sc->data = 0; + } + if(sc->data_tag) { + bus_dma_tag_destroy(sc->data_tag); + sc->data_tag = 0; + } + + ... 如果有,释放其他的映射和标签 ... + + if(sc->parent_tag) { + bus_dma_tag_destroy(sc->parent_tag); + sc->parent_tag = 0; + } + + /* 释放所有总线资源 */ + if(sc->mem0_r) { + bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->mem0_rid, + sc->mem0_r); + sc->mem0_r = 0; + } + ... + if(sc->port0_r) { + bus_release_resource(sc->dev, SYS_RES_IOPORT, sc->port0_rid, + sc->port0_r); + sc->port0_r = 0; + } + }</programlisting> + + </sect1> + + <sect1 xml:id="isa-driver-attach"> + <title>xxx_isa_attach</title> + <!-- Section Marked up by Wylie --> + + <para>如果探测例程返回成功并且系统选择连接那个驱动程序,则连接例程 + 负责将驱动程序实际连接到系统。如果探测例程返回0 ,则连接例程期望 + 接收完整的设备结构softc,此结构由探测例程设置。同时,如果探测例程 + 返回0,它可能期望这个设备的连接例程应当在将来的某点被调用。如果 + 探测例程返回负值,则驱动程序可能不会作此假设。 + </para> + + <para>如果成功完成,连接例程返回0,否则返回错误码。 + </para> + + <para>连接例程的启动跟探测例程相似,将一些常用数据取到一些更容易 + 访问的变量中。 + </para> + + <programlisting> struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int error = 0;</programlisting> + + <para>然后分配并激活所需资源。由于端口范围通常在从探测返回前就 + 被释放,因此需要重新分配。我们希望探测例程已经适当地设置了 + 所有的资源范围,并将它们保存在结构softc中。如果探测例程留下了 + 一些被分配的资源,就不需要再次分配(重新分配被视为错误)。 + </para> + + <programlisting> sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + /* 板上内存 */ + sc->mem0_rid = 0; + sc->mem0_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->mem0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->mem0_r == NULL) + goto bad; + + /* 取得虚地址 */ + sc->mem0_v = rman_get_virtual(sc->mem0_r);</programlisting> + + <para>DMA请求通道(DRQ)以相似方式被分配。使用 + <function>isa_dma*()</function>函数族进行初始化。例如: + </para> + + <para><function>isa_dmacascade(sc->drq0);</function></para> + + <para>中断请求线(IRQ)有点特殊。除了分配以外,驱动程序的中断处理 + 函数也应当与它关联。在古老的ISA驱动程序中,由系统传递给中断处理 + 函数的参量是设备单元号。但在现代驱动程序中,按照约定,建议传递 + 指向结构softc的指针。一个很重要的原因在于当结构softc被动态分配后, + 从softc取得单元号很容易,而从单元号取得softc很困难。同时,这个 + 约定也使得用于不同总线的应用程序看起来统一,并允许它们共享代码: + 每个总线有其自己的探测,连接,分离和其他总线相关的例程,而它们 + 之间可以共享大块的驱动程序代码。 + </para> + + <programlisting> + sc->intr_rid = 0; + sc->intr_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->intr_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->intr_r == NULL) + goto bad; + + /* + * 假定对XXX_INTR_TYPE的定义依赖于驱动程序的类型, + * 例如INTR_TYPE_CAM用于CAM的驱动程序 + */ + error = bus_setup_intr(dev, sc->intr_r, XXX_INTR_TYPE, + (driver_intr_t *) xxx_intr, (void *) sc, &sc->intr_cookie); + if(error) + goto bad; + + </programlisting> + + + <para>如果驱动程序需要与内存进行DMA,则这块内存应当按前述方式分配: + </para> + + <programlisting> error=bus_dma_tag_create(NULL, /*alignment*/ 4, + /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ BUS_SPACE_MAXSIZE_24BIT, + /*nsegments*/ BUS_SPACE_UNRESTRICTED, + /*maxsegsz*/ BUS_SPACE_MAXSIZE_24BIT, /*flags*/ 0, + &sc->parent_tag); + if(error) + goto bad; + + /* 很多东西是从父标签继承而来 + * 假设sc->data指向存储共享数据的结构,例如一个环缓冲区可能是: + * struct { + * u_short rd_pos; + * u_short wr_pos; + * char bf[XXX_RING_BUFFER_SIZE] + * } *data; + */ + error=bus_dma_tag_create(sc->parent_tag, 1, + 0, BUS_SPACE_MAXADDR, 0, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(* sc->data), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(* sc->data), /*flags*/ 0, + &sc->data_tag); + if(error) + goto bad; + + error = bus_dmamem_alloc(sc->data_tag, &sc->data, /* flags*/ 0, + &sc->data_map); + if(error) + goto bad; + + /* 在&sc->data_p的情况下,xxx_alloc_callback()只是将物理地址 + * 保存到作为其参量传递进去的指针中。 + * 参看关于总线内存映射一节中的详细内容。 + * 其实现可以像这样: + * + * static void + * xxx_alloc_callback(void *arg, bus_dma_segment_t *seg, + * int nseg, int error) + * { + * *(bus_addr_t *)arg = seg[0].ds_addr; + * } + */ + bus_dmamap_load(sc->data_tag, sc->data_map, (void *)sc->data, + sizeof (* sc->data), xxx_alloc_callback, (void *) &sc->data_p, + /*flags*/0);</programlisting> + + + <para>分配了所有的资源后,设备应当被初始化。初始化可能包括测试 + 所有特性,确保它们起作用。</para> + + <programlisting> if(xxx_initialize(sc) < 0) + goto bad; </programlisting> + + + <para>总线子系统将自动在控制台上打印由探测例程设置的设备描述。但 + 如果驱动程序想打印一些关于设备的额外信息,也是可能的,例如:</para> + + <programlisting> + device_printf(dev, "has on-card FIFO buffer of %d bytes\n", sc->fifosize); + </programlisting> + + <para>如果初始化例程遇到任何问题,建议返回错误之前打印有关信息。</para> + + <para>连接例程的最后一步是将设备连接到内核中的功能子系统。完成 + 这个步骤的精确方式依赖于驱动程序的类型:字符设备、块设备、网络 + 设备、CAM SCSI总线设备等等。</para> + + <para>如果所有均工作正常则返回成功。</para> + + <programlisting> error = xxx_attach_subsystem(sc); + if(error) + goto bad; + + return 0; </programlisting> + + <para>最后,处理棘手情况。返回错误前,所有资源应当被取消分配。 + 我们利用这样一个事实:结构softc传递给我们之前被零化,因此我们 + 能找出是否分配了某些资源:如果分配则它们的描述符非零。</para> + + <programlisting> bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO;</programlisting> + + <para>这就是连接例程的全部。</para> + + </sect1> + + + <sect1 xml:id="isa-driver-detach"> + <title>xxx_isa_detach</title> + + <para> + 如果驱动程序中存在这个函数,且驱动程序被编译为可加载模块,则 + 驱动程序具有被卸载的能力。如果硬件支持热插拔,这是一个很重要的 + 特性。但ISA总线不支持热插拔,因此这个特性对于ISA设备不是特别 + 重要。卸载驱动程序的能力可能在调试时有用,但很多情况下只有在 + 老版本的驱动程序莫名其妙地卡住系统的情况下才需要安装新版本的 + 驱动程序,并且无论如何都需要重启,这样使得花费精力写分离例程 + 有些不值得。另一个宣称卸载允许在用于生产的机器上升级驱动程序的 + 论点看起来似乎更多的只是理论而已。升级驱动程序是一项危险的操作, + 决不不应当在用于生产的机器上实行(并且当系统运行于安全模式时这 + 也是不被允许的)。然而,出于完整性考虑,还是会提供分离例程。 + </para> + + <para> + 如果驱动程序成功分离,分离例程返回0,否则返回错误码。 + </para> + + <para> + 分离逻辑是连接的镜像。要做的第一件事情就是将驱动程序从内核 + 子系统分离。如果设备当前正打开着,驱动程序有两个选择:拒绝分离 + 或者强制关闭并继续进行分离。选用哪种方式取决于特定内核子系统 + 执行强制关闭的能力和驱动程序作者的偏好。通常强制关闭似乎是 + 更好的选择。 + <programlisting> struct xxx_softc *sc = device_get_softc(dev); + int error; + + error = xxx_detach_subsystem(sc); + if(error) + return error;</programlisting> + </para> + <para> + 下一步,驱动程序可能希望复位硬件到某种一致的状态。包括停止任何 + 将要进行的传输,禁用DMA通道和中断以避免设备破坏内存。对于大多数 + 驱动程序而言,这正是关闭例程所做的,因此如果驱动程序中包括关闭 + 例程,我们只要调用它就可以了。 + </para> + <para><function>xxx_isa_shutdown(dev);</function></para> + + <para> + 最后释放所有资源并返回成功。 + <programlisting> xxx_free_resources(sc); + return 0;</programlisting> + + </para> + </sect1> + + <sect1 xml:id="isa-driver-shutdown"> + <title>xxx_isa_shutdown</title> + + <para> + 当系统要关闭的时候调用此例程。通过它使硬件进入某种一致的状态。 + 对于大多数ISA设备而言不需要特殊动作,因此这个函数并非真正必需, + 因为不管怎样重启动时设备会被重新初始化。但有些设备必须按特定 + 步骤关闭,以确保在软重启后能被正确地检测到(对于很多使用私有 + 识别协议的设备特别有用)。很多情况下,在设备寄存器中禁用DMA和 + 中断,并停止将要进行的传输是个好主意。确切动作取决于硬件,因此 + 我们无法在此详细讨论。 + </para> + </sect1> + + <sect1 xml:id="isa-driver-intr"> + <title>xxx_intr</title> + + <indexterm><primary>interrupt handler(中断处理程序)</primary></indexterm> + + <para> + 当收到来自特定设备的中断时就会调用中断处理函数。ISA总线不支持 + 中断共享(某些特殊情况例外),因此实际上如果中断处理函数被调用, + 几乎可以确信中断是来自其设备。然而,中断处理函数必须轮询设备 + 寄存器并确保中断是由它的设备产生的。如果不是,中断处理函数应当 + 返回。 + </para> + + <para> + ISA驱动程序的旧约定是取设备单元号作为参量。现在已经废弃,当 + 调用<function>bus_setup_intr()</function>时新驱动程序接收任何 + 在连接例程中为他们指定的参量。根据新约定,它应当是指向结构 + softc的指针。因此中断处理函数通常像下面那样开始: + </para> + + <programlisting> + static void + xxx_intr(struct xxx_softc *sc) + { + + </programlisting> + + <para> + 它运行在由<function>bus_setup_intr()</function>的中断类型参数指定 + 的中断优先级上。这意味着禁用所有其他同类型的中断和所有软件中断。 + </para> + + <para> + 为了避免竞争,中断处理例程通写成循环形式: + </para> + + <programlisting> + while(xxx_interrupt_pending(sc)) { + xxx_process_interrupt(sc); + xxx_acknowledge_interrupt(sc); + } </programlisting> + + <para> + 中断处理函数必须只向设备应答中断,但不能向中断控制器应答,后者由 + 系统负责处理。 + </para> + + </sect1> +</chapter> diff --git a/zh_CN.UTF-8/books/arch-handbook/jail/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/jail/chapter.xml new file mode 100644 index 0000000000..541d9cdb67 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/jail/chapter.xml @@ -0,0 +1,636 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + The FreeBSD Documentation Project + The FreeBSD Simplified Chinese Project + + Original Revision: 1.22 + $FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="jail"> + <info><title>Jail子系统</title> + <author><personname><firstname>Evan</firstname><surname>Sarmiento</surname></personname><affiliation> + <address><email>evms@cs.bu.edu</email></address> + </affiliation></author> + <copyright> + <year>2001</year> + <holder role="mailto:evms@cs.bu.edu">Evan Sarmiento</holder> + </copyright> + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + </info> + + + <indexterm><primary>security(安全)</primary></indexterm> + <indexterm><primary>Jail(囚禁)</primary></indexterm> + <indexterm><primary>root(根用户,管理员用户)</primary></indexterm> + <para>在大多数&unix;系统中,用户<literal>root</literal>是万能的。这也就增加了许多危险。 + 如果一个攻击者获得了一个系统中的<literal>root</literal>,就可以在他的指尖掌握系统中所有的功能。 + 在FreeBSD里,有一些sysctl项削弱了<literal>root</literal>的权限, + 这样就可以将攻击者造成的损害减小到最低限度。这些安全功能中,有一种叫安全级别。 + 另一种在FreeBSD 4.0及以后版本中提供的安全功能,就是&man.jail.8;。 + <application>Jail</application>将一个运行环境的文件树根切换到某一特定位置, + 并且对这样环境中叉分生成的进程做出限制。例如, + 一个被监禁的进程不能影响这个<application>jail</application>之外的进程、不能使用一些特定的系统调用, + 也就不能对主计算机造成破坏。<tip><title>译者注</title> + <para>英文单词“jail”的中文意思是“囚禁、监禁”。</para></tip></para> + + <para><application>Jail</application>已经成为一种新型的安全模型。 + 人们可以在jail中运行各种可能很脆弱的服务器程序,如<application>Apache</application>、 + <application>BIND</application>和<application>sendmail</application>。 + 这样一来,即使有攻击者取得了<application>jail</application>中的<literal>root</literal>, + 这最多让人们皱皱眉头,而不会使人们惊慌失措。 + 本文主要关注<application>jail</application>的内部原理(源代码)。 + 如果你正在寻找设置<application>Jail</application>的指南性文档, + 我建议你阅读我的另一篇文章,发表在Sys Admin Magazine, May 2001, + 《Securing FreeBSD using <application>Jail</application>》。</para> + + <sect1 xml:id="jail-arch"> + <title>Jail的系统结构</title> + + <para><application>Jail</application>由两部分组成:用户级程序, + 也就是&man.jail.8;;还有在内核中Jail的实现代码:&man.jail.2; + 系统调用和相关的约束。我将讨论用户级程序和<application>jail</application>在内核中的实现原理。</para> + + <sect2> + <title>用户级代码</title> + + <indexterm><primary>Jail(囚禁)</primary> + <secondary>userland program(用户级程序)</secondary></indexterm> + + <para><application>Jail</application>的用户级源代码在<filename>/usr/src/usr.sbin/jail</filename>, + 由一个文件<filename>jail.c</filename>组成。这个程序有这些参数:<application>jail</application>的路径, + 主机名,IP地址,还有需要执行的命令。</para> + + <sect3> + <title>数据结构</title> + + <para>在<filename>jail.c</filename>中,我将最先注解的是一个重要结构体 + <literal>struct jail j;</literal>的声明,这个结构类型的声明包含在 + <filename>/usr/include/sys/jail.h</filename>之中。</para> + + <para><literal>jail</literal>结构的定义是:</para> + +<programlisting><filename>/usr/include/sys/jail.h</filename>: + +struct jail { + u_int32_t version; + char *path; + char *hostname; + u_int32_t ip_number; +};</programlisting> + + <para>正如你所见,传送给命令&man.jail.8;的每个参数都在这里有对应的一项。 + 事实上,当命令&man.jail.8;被执行时,这些参数才由命令行真正传入:</para> + + <programlisting><filename>/usr/src/usr.sbin/jail.c</filename> +char path[PATH_MAX]; +... +if(realpath(argv[0], path) == NULL) + err(1, "realpath: %s", argv[0]); +if (chdir(path) != 0) + err(1, "chdir: %s", path); +memset(&j, 0, sizeof(j)); +j.version = 0; +j.path = path; +j.hostname = argv[1];</programlisting> + + </sect3> + + <sect3> + <title>网络</title> + + <para>传给&man.jail.8;的参数中有一个是IP地址。这是在网络上访问<application>jail</application>时的地址。 + &man.jail.8;将IP地址翻译成网络字节顺序,并存入<literal>j</literal>(<literal>jail</literal>类型的结构体)。</para> + + <programlisting><filename>/usr/src/usr.sbin/jail/jail.c</filename>: +struct in_addr in; +... +if (inet_aton(argv[2], &in) == 0) + errx(1, "Could not make sense of ip-number: %s", argv[2]); +j.ip_number = ntohl(in.s_addr);</programlisting> + + <para>函数&man.inet.aton.3;“将指定的字符串解释为一个Internet地址, + 并将其转存到指定的结构体中”。&man.inet.aton.3;设定了结构体in, + 之后in中的内容再用&man.ntohl.3;转换成主机字节顺序, + 并置入<literal>jail</literal>结构体的<literal>ip_number</literal>成员。</para> + + </sect3> + + <sect3> + <title>囚禁进程</title> + + <para>最后,用户级程序囚禁进程。现在Jail自身变成了一个被囚禁的进程, + 并使用&man.execv.3;执行用户指定的命令。</para> + + <programlisting><filename>/usr/src/usr.sbin/jail/jail.c</filename> +i = jail(&j); +... +if (execv(argv[3], argv + 3) != 0) + err(1, "execv: %s", argv[3]);</programlisting> + + <para>正如你所见,函数<literal>jail()</literal>被调用,参数是结构体<literal>jail</literal>中被填入数据项, + 而如前所述,这些数据项又来自&man.jail.8;的命令行参数。 + 最后,执行了用户指定的命令。下面我将开始讨论<literal>jail</literal>在内核中的实现。</para> + </sect3> + </sect2> + + <sect2> + <title>相关的内核源代码</title> + + <indexterm><primary>Jail(囚禁)</primary> + <secondary>kernel architecture(内核架构)</secondary></indexterm> + + <para>现在我们来看文件<filename>/usr/src/sys/kern/kern_jail.c</filename>。 + 在这里定义了&man.jail.2;的系统调用、相关的sysctl项,还有网络函数。</para> + + <sect3> + <title>sysctl项</title> + + <indexterm><primary>sysctl(系统控制项)</primary></indexterm> + + <para>在<filename>kern_jail.c</filename>里定义了如下sysctl项:</para> + + <programlisting><filename>/usr/src/sys/kern/kern_jail.c:</filename> + +int jail_set_hostname_allowed = 1; +SYSCTL_INT(_security_jail, OID_AUTO, set_hostname_allowed, CTLFLAG_RW, + &jail_set_hostname_allowed, 0, + "Processes in jail can set their hostnames"); + /* Jail中的进程可设定自身的主机名 */ + +int jail_socket_unixiproute_only = 1; +SYSCTL_INT(_security_jail, OID_AUTO, socket_unixiproute_only, CTLFLAG_RW, + &jail_socket_unixiproute_only, 0, + "Processes in jail are limited to creating UNIX/IPv4/route sockets only"); + /* Jail中的进程被限制只能建立UNIX套接字、IPv4套接字、路由套接字 */ + +int jail_sysvipc_allowed = 0; +SYSCTL_INT(_security_jail, OID_AUTO, sysvipc_allowed, CTLFLAG_RW, + &jail_sysvipc_allowed, 0, + "Processes in jail can use System V IPC primitives"); + /* Jail中的进程可以使用System V进程间通讯原语 */ + +static int jail_enforce_statfs = 2; +SYSCTL_INT(_security_jail, OID_AUTO, enforce_statfs, CTLFLAG_RW, + &jail_enforce_statfs, 0, + "Processes in jail cannot see all mounted file systems"); + /* jail 中的进程查看系统中挂接的文件系统时受到何种限制 */ + +int jail_allow_raw_sockets = 0; +SYSCTL_INT(_security_jail, OID_AUTO, allow_raw_sockets, CTLFLAG_RW, + &jail_allow_raw_sockets, 0, + "Prison root can create raw sockets"); + /* jail 中的 root 用户是否可以创建 raw socket */ + +int jail_chflags_allowed = 0; +SYSCTL_INT(_security_jail, OID_AUTO, chflags_allowed, CTLFLAG_RW, + &jail_chflags_allowed, 0, + "Processes in jail can alter system file flags"); + /* jail 中的进程是否可以修改系统级文件标记 */ + +int jail_mount_allowed = 0; +SYSCTL_INT(_security_jail, OID_AUTO, mount_allowed, CTLFLAG_RW, + &jail_mount_allowed, 0, + "Processes in jail can mount/unmount jail-friendly file systems"); + /* jail 中的进程是否可以挂载或卸载对jail友好的文件系统 */</programlisting> + + <para>这些sysctl项中的每一个都可以用命令&man.sysctl.8;访问。在整个内核中, + 这些sysctl项按名称标识。例如,上述第一个sysctl项的名字是 + <literal>security.jail.set_hostname_allowed</literal>。</para> + </sect3> + + <sect3> + <title>&man.jail.2;系统调用</title> + + <para>像所有的系统调用一样,系统调用&man.jail.2;带有两个参数, + <literal>struct thread *td</literal>和<literal>struct jail_args *uap</literal>。 + <literal>td</literal>是一个指向<literal>thread</literal>结构体的指针,该指针用于描述调用&man.jail.2;的线程。 + 在这个上下文中,<literal>uap</literal>指向一个结构体,这个结构体中包含了一个指向从用户级 + <filename>jail.c</filename>传送过来的<literal>jail</literal>结构体的指针。 + 在前面我讲述用户级程序时,你已经看到过一个<literal>jail</literal>结构体被作为参数传送给系统调用 + &man.jail.2;。</para> + + <programlisting><filename>/usr/src/sys/kern/kern_jail.c:</filename> +/* + * struct jail_args { + * struct jail *jail; + * }; + */ +int +jail(struct thread *td, struct jail_args *uap)</programlisting> + + <para>于是<literal>uap->jail</literal>可以用于访问被传递给&man.jail.2;的<literal>jail</literal>结构体。 + 然后,&man.jail.2;使用&man.copyin.9;将<literal>jail</literal>结构体复制到内核内存空间中。 + &man.copyin.9;需要三个参数:要复制进内核内存空间的数据的地址 + <literal>uap->jail</literal>,在内核内存空间存放数据的<literal>j</literal>, + 以及数据的大小。<literal>uap->jail</literal>指向的Jail结构体被复制进内核内存空间, + 并被存放在另一个<literal>jail</literal>结构体<literal>j</literal>里。</para> + + <programlisting><filename>/usr/src/sys/kern/kern_jail.c: </filename> +error = copyin(uap->jail, &j, sizeof(j));</programlisting> + + <para>在jail.h中定义了另一个重要的结构体型prison。 + 结构体<literal>prison</literal>只被用在内核空间中。 + 下面是<literal>prison</literal>结构体的定义。</para> + + <programlisting><filename>/usr/include/sys/jail.h</filename>: +struct prison { + LIST_ENTRY(prison) pr_list; /* (a) all prisons */ + int pr_id; /* (c) prison id */ + int pr_ref; /* (p) refcount */ + char pr_path[MAXPATHLEN]; /* (c) chroot path */ + struct vnode *pr_root; /* (c) vnode to rdir */ + char pr_host[MAXHOSTNAMELEN]; /* (p) jail hostname */ + u_int32_t pr_ip; /* (c) ip addr host */ + void *pr_linux; /* (p) linux abi */ + int pr_securelevel; /* (p) securelevel */ + struct task pr_task; /* (d) destroy task */ + struct mtx pr_mtx; + void **pr_slots; /* (p) additional data */ +};</programlisting> + + <para>然后,系统调用&man.jail.2;为一个<literal>prison</literal>结构体分配一块内存, + 并在<literal>jail</literal>和<literal>prison</literal>结构体之间复制数据。</para> + + <programlisting><filename>/usr/src/sys/kern/kern_jail.c</filename>: +MALLOC(pr, struct prison *, sizeof(*pr), M_PRISON, M_WAITOK | M_ZERO); +... +error = copyinstr(j.path, &pr->pr_path, sizeof(pr->pr_path), 0); +if (error) + goto e_killmtx; +... +error = copyinstr(j.hostname, &pr->pr_host, sizeof(pr->pr_host), 0); +if (error) + goto e_dropvnref; +pr->pr_ip = j.ip_number;</programlisting> + + <para>下面,我们将讨论另外一个重要的系统调用&man.jail.attach.2;,它实现了将进程监禁的功能。</para> + <programlisting><filename>/usr/src/sys/kern/kern_jail.c</filename> +/* + * struct jail_attach_args { + * int jid; + * }; + */ +int +jail_attach(struct thread *td, struct jail_attach_args *uap)</programlisting> + <para>这个系统调用做出一些可以用于区分被监禁和未被监禁的进程的改变。 + 要理解&man.jail.attach.2;为我们做了什么,我们首先要理解一些背景信息。</para> + <para>在FreeBSD中,每个对内核可见的线程是通过其<literal>thread</literal>结构体来识别的, + 同时,进程都由它们自己的<literal>proc</literal>结构体描述。 + 你可以在<filename>/usr/include/sys/proc.h</filename>中找到<literal>thread</literal>和<literal>proc</literal>结构体的定义。 + 例如,在任何系统调用中,参数<literal>td</literal>实际上是个指向调用线程的<literal>thread</literal>结构体的指针, + 正如前面所说的那样。<literal>td</literal>所指向的<literal>thread</literal>结构体中的<literal>td_proc</literal>成员是一个指针, + 这个指针指向<literal>td</literal>所表示的线程所属进程的<literal>proc</literal>结构体。 + 结构体<literal>proc</literal>包含的成员可以描述所有者的身份 + (<literal>p_ucred</literal>),进程资源限制(<literal>p_limit</literal>), + 等等。在由<literal>proc</literal>结构体的<literal>p_ucred</literal>成员所指向的ucred结构体的定义中, + 还有一个指向<literal>prison</literal>结构体的指针(<literal>cr_prison</literal>)。</para> + <programlisting><filename>/usr/include/sys/proc.h: </filename> +struct thread { + ... + struct proc *td_proc; + ... +}; +struct proc { + ... + struct ucred *p_ucred; + ... +}; +<filename>/usr/include/sys/ucred.h</filename> +struct ucred { + ... + struct prison *cr_prison; + ... +};</programlisting> + + <para>在<filename>kern_jail.c</filename>中,函数<literal>jail()</literal>以给定的<literal>jid</literal> + 调用函数<literal>jail_attach()</literal>。随后<literal>jail_attach()</literal>调用函数<literal>change_root()</literal>以改变 + 调用进程的根目录。接下来,<literal>jail_attach()</literal>创建一个新的<literal>ucred</literal>结构体,并在 + 成功地将<literal>prison</literal>结构体连接到这个<literal>ucred</literal>结构体后,将这个<literal>ucred</literal>结构体连接 + 到调用进程上。从此时起,这个调用进程就会被识别为被监禁的。 + 当我们以新创建的这个<literal>ucred</literal>结构体为参数调用内核路径<literal>jailed()</literal>时, + 它将返回1来说明这个用户身份是和一个<application>jail</application>相连的。 + 在<application>jail</application>中叉分出来的所有进程的的公共祖先进程就是这个执行了&man.jail.2;的进程, + 因为正是它调用了&man.jail.2;系统调用。当一个程序通过&man.execve.2;而被执行时, + 它将从其父进程的<literal>ucred</literal>结构体继承被监禁的属性, + 因而它也会拥有一个被监禁的<literal>ucred</literal>结构体。</para> + + <programlisting><filename>/usr/src/sys/kern/kern_jail.c</filename> +int +jail(struct thread *td, struct jail_args *uap) +{ +... + struct jail_attach_args jaa; +... + error = jail_attach(td, &jaa); + if (error) + goto e_dropprref; +... +} + +int +jail_attach(struct thread *td, struct jail_attach_args *uap) +{ + struct proc *p; + struct ucred *newcred, *oldcred; + struct prison *pr; +... + p = td->td_proc; +... + pr = prison_find(uap->jid); +... + change_root(pr->pr_root, td); +... + newcred->cr_prison = pr; + p->p_ucred = newcred; +... +}</programlisting> + <para>当一个进程被从其父进程叉分来的时候, + 系统调用&man.fork.2;将用<literal>crhold()</literal>来维护其身份凭证。 + 这样,很自然的就保持了子进程的身份凭证于其父进程一致,所以子进程也是被监禁的。</para> + + <programlisting><filename>/usr/src/sys/kern/kern_fork.c</filename>: +p2->p_ucred = crhold(td->td_ucred); +... +td2->td_ucred = crhold(p2->p_ucred);</programlisting> + + </sect3> + </sect2> + </sect1> + + <sect1 xml:id="jail-restrictions"> + <title>系统对被囚禁程序的限制</title> + + <para>在整个内核中,有一系列对被囚禁程序的约束措施。 + 通常,这些约束只对被囚禁的程序有效。如果这些程序试图突破这些约束, + 相关的函数将出错返回。例如:</para> + + <programlisting> +if (jailed(td->td_ucred)) + return EPERM;</programlisting> + + <sect2> + <title>SysV进程间通信(IPC)</title> + + <indexterm><primary>System V IPC(系统V进程间通信)</primary></indexterm> + + <para>System V 进程间通信 (IPC) 是通过消息实现的。 + 每个进程都可以向其它进程发送消息, 告诉对方该做什么。 + 处理消息的函数是: &man.msgctl.3;、&man.msgget.3;、&man.msgsnd.3; 和 + &man.msgrcv.3;。前面已经提到,一些 sysctl 开关可以影响 <application>jail</application> 的行为, + 其中有一个是 <literal>security.jail.sysvipc_allowed</literal>。 在大多数系统上, + 这个 sysctl 项会设成0。 如果将它设为1, 则会完全失去 <application>jail</application> 的意义: + 因为那样在 <application>jail</application> 中特权进程就可以影响被监禁的环境外的进程了。 + 消息与信号的区别是:消息仅由一个信号编号组成。</para> + + <para><filename>/usr/src/sys/kern/sysv_msg.c</filename>:</para> + + <itemizedlist> + <listitem><para><literal>msgget(key, msgflg)</literal>: + <literal>msgget</literal>返回(也可能创建)一个消息描述符, + 以指派一个在其它函数中使用的消息队列。</para></listitem> + + <listitem><para><literal>msgctl(msgid, cmd, buf)</literal>: 通过这个函数, + 一个进程可以查询一个消息描述符的状态。</para></listitem> + + <listitem><para><literal>msgsnd(msgid, msgp, msgsz, msgflg)</literal>: + <literal>msgsnd</literal>向一个进程发送一条消息。</para></listitem> + + <listitem><para><literal>msgrcv(msgid, msgp, msgsz, msgtyp, msgflg)</literal>: + 进程用这个函数接收消息。</para></listitem> + + </itemizedlist> + + <para>在这些函数对应的系统调用的代码中,都有这样一个条件判断:</para> + + <programlisting><filename>/usr/src/sys/kern/sysv_msg.c</filename>: +if (!jail_sysvipc_allowed && jailed(td->td_ucred)) + return (ENOSYS);</programlisting> + + <indexterm><primary>semaphores(信号量)</primary></indexterm> + <para>信号量系统调用使得进程可以通过一系列原子操作实现同步。 + 信号量为进程锁定资源提供了又一种途径。 + 然而,进程将为正在被使用的信号量进入等待状态,一直休眠到资源被释放。 + 在<application>jail</application>中如下的信号量系统调用将会失效: &man.semget.2;, &man.semctl.2; + 和&man.semop.2;。</para> + + <para><filename>/usr/src/sys/kern/sysv_sem.c</filename>:</para> + + <itemizedlist> + <listitem> + <para><literal>semctl(semid, num, cmd, ...)</literal>: + <literal>semctl</literal>对在信号量队列中用<literal>semid</literal>标识的信号量执行<literal>cmd</literal>指定的命令。</para></listitem> + + <listitem> + <para><literal>semget(key, nsems, flag)</literal>: + <literal>semget</literal>建立一个对应于<literal>key</literal>的信号量数组。</para> + + <para><literal>参数key和flag与他们在msgget()的意义相同。</literal></para></listitem> + + <listitem><para><literal>setop(semid, array, nops)</literal>: + <literal>semop</literal>对semid标识的信号量完成一组由array所指定的操作。</para></listitem> + </itemizedlist> + + <indexterm><primary>shared memory(共享内存)</primary></indexterm> + <para>System V IPC使进程间可以共享内存。进程之间可以通过它们虚拟地址空间 + 的共享部分以及相关数据读写操作直接通讯。这些系统调用在被监禁的环境中将会失效: + &man.shmdt.2;、&man.shmat.2;、&man.shmctl.2;和&man.shmget.2;</para> + + <para><filename>/usr/src/sys/kern/sysv_shm.c</filename>:</para> + + <itemizedlist> + <listitem><para><literal>shmctl(shmid, cmd, buf)</literal>: + <literal>shmctl</literal>对<literal>id</literal>标识的共享内存区域做各种各样的控制。</para></listitem> + + <listitem><para><literal>shmget(key, size, flag)</literal>: + <literal>shmget</literal>建立/打开<literal>size</literal>字节的共享内存区域。</para></listitem> + + <listitem><para><literal>shmat(shmid, addr, flag)</literal>: + <literal>shmat</literal>将<literal>shmid</literal>标识的共享内存区域指派到进程的地址空间里。</para></listitem> + + <listitem><para><literal>shmdt(addr)</literal>: + <literal>shmdt</literal>取消共享内存区域的地址指派。</para></listitem> + + </itemizedlist> + </sect2> + + <sect2> + <title>套接字</title> + + <indexterm><primary>sockets(套接字)</primary></indexterm> + <para><application>Jail</application>以一种特殊的方式处理&man.socket.2;系统调用和相关的低级套接字函数。 + 为了决定一个套接字是否允许被创建,它先检查sysctl项 + <literal>security.jail.socket_unixiproute_only</literal>是否被设置为1。 + 如果被设为1,套接字建立时将只能指定这些协议族: + <literal>PF_LOCAL</literal>, <literal>PF_INET</literal>, + <literal>PF_ROUTE</literal>。否则,&man.socket.2;将会返回出错。</para> + + <programlisting><filename>/usr/src/sys/kern/uipc_socket.c</filename>: +int +socreate(int dom, struct socket **aso, int type, int proto, + struct ucred *cred, struct thread *td) +{ + struct protosw *prp; +... + if (jailed(cred) && jail_socket_unixiproute_only && + prp->pr_domain->dom_family != PF_LOCAL && + prp->pr_domain->dom_family != PF_INET && + prp->pr_domain->dom_family != PF_ROUTE) { + return (EPROTONOSUPPORT); + } +... +}</programlisting> + + </sect2> + + <sect2> + <title>Berkeley包过滤器</title> + + <indexterm><primary>Berkeley Packet Filter(伯克利包过滤器)</primary></indexterm> + <indexterm><primary>data link layer(数据链路层)</primary></indexterm> + + <para><application>Berkeley包过滤器</application>提供了一个与协议无关的,直接通向数据链路层的低级接口。 + 现在<application>BPF</application>是否可以在监禁的环境中被使用是通过&man.devfs.8;来控制的。</para> + </sect2> + + <sect2> + <title>网络协议</title> + + <indexterm><primary>protocols(协议)</primary></indexterm> + + <para>网络协议TCP, UDP, IP和ICMP很常见。IP和ICMP处于同一协议层次:第二层, + 网络层。当参数<literal>nam</literal>被设置时, + 有一些限制措施会防止被囚禁的程序绑定到一些网络接口上。 + <literal>nam</literal>是一个指向<literal>sockaddr</literal>结构体的指针, + 描述可以绑定服务的地址。一个更确切的定义:<literal>sockaddr</literal>“是一个模板,包含了地址的标识符和地址的长度”。 + 在函数<literal>in_pcbbind_setup()</literal>中<literal>sin</literal>是一个指向<literal>sockaddr_in</literal>结构体的指针, + 这个结构体包含了套接字可以绑定的端口、地址、长度、协议族。 + 这就禁止了在<application>jail</application>中的进程指定不属于这个进程所存在于的<application>jail</application>的IP地址。</para> + <programlisting><filename>/usr/src/sys/kern/netinet/in_pcb.c</filename>: +int +in_pcbbind_setup(struct inpcb *inp, struct sockaddr *nam, in_addr_t *laddrp, + u_short *lportp, struct ucred *cred) +{ + ... + struct sockaddr_in *sin; + ... + if (nam) { + sin = (struct sockaddr_in *)nam; + ... + if (sin->sin_addr.s_addr != INADDR_ANY) + if (prison_ip(cred, 0, &sin->sin_addr.s_addr)) + return(EINVAL); + ... + if (lport) { + ... + if (prison && prison_ip(cred, 0, &sin->sin_addr.s_addr)) + return (EADDRNOTAVAIL); + ... + } + } + if (lport == 0) { + ... + if (laddr.s_addr != INADDR_ANY) + if (prison_ip(cred, 0, &laddr.s_addr)) + return (EINVAL); + ... + } +... + if (prison_ip(cred, 0, &laddr.s_addr)) + return (EINVAL); +... +}</programlisting> + + <para>你也许想知道函数<literal>prison_ip()</literal>做什么。 + <literal>prison_ip()</literal>有三个参数,一个指向身份凭证的指针(用<literal>cred</literal>表示), + 一些标志和一个IP地址。当这个IP地址不属于这个<application>jail</application>时,返回1; + 否则返回0。正如你从代码中看见的,如果,那个IP地址确实不属于这个<application>jail</application>, + 就不再允许向这个网络地址绑定协议。</para> + + <programlisting><filename>/usr/src/sys/kern/kern_jail.c:</filename> +int +prison_ip(struct ucred *cred, int flag, u_int32_t *ip) +{ + u_int32_t tmp; + + if (!jailed(cred)) + return (0); + if (flag) + tmp = *ip; + else + tmp = ntohl(*ip); + if (tmp == INADDR_ANY) { + if (flag) + *ip = cred->cr_prison->pr_ip; + else + *ip = htonl(cred->cr_prison->pr_ip); + return (0); + } + if (tmp == INADDR_LOOPBACK) { + if (flag) + *ip = cred->cr_prison->pr_ip; + else + *ip = htonl(cred->cr_prison->pr_ip); + return (0); + } + if (cred->cr_prison->pr_ip != tmp) + return (1); + return (0); +}</programlisting> + </sect2> + + <sect2> + <title>文件系统</title> + + <indexterm><primary>filesystem(文件系统)</primary></indexterm> + <para>如果完全级别大于0,即便是<application>jail</application>里面的<literal>root</literal>, + 也不允许在Jail中取消或更改文件标志,如“不可修改”、“只可添加”、“不可删除”标志。</para> + <programlisting><filename>/usr/src/sys/ufs/ufs/ufs_vnops.c:</filename> +static int +ufs_setattr(ap) + ... +{ + ... + if (!priv_check_cred(cred, PRIV_VFS_SYSFLAGS, 0)) { + if (ip->i_flags + & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) { + error = securelevel_gt(cred, 0); + if (error) + return (error); + } + ... + } +} +<filename>/usr/src/sys/kern/kern_priv.c</filename> +int +priv_check_cred(struct ucred *cred, int priv, int flags) +{ + ... + error = prison_priv_check(cred, priv); + if (error) + return (error); + ... +} +<filename>/usr/src/sys/kern/kern_jail.c</filename> +int +prison_priv_check(struct ucred *cred, int priv) +{ + ... + switch (priv) { + ... + case PRIV_VFS_SYSFLAGS: + if (jail_chflags_allowed) + return (0); + else + return (EPERM); + ... + } + ... +}</programlisting> + + </sect2> + + </sect1> + +</chapter> diff --git a/zh_CN.UTF-8/books/arch-handbook/kobj/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/kobj/chapter.xml new file mode 100644 index 0000000000..28e1c74805 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/kobj/chapter.xml @@ -0,0 +1,284 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + The FreeBSD Documentation Project + The FreeBSD Simplified Chinese Project + + Original Revision: 1.7 + $FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="kernel-objects"> + <info><title>内核对象</title> + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + </info> + + + <indexterm><primary>Kernel Objects(内核对象)</primary></indexterm> + <indexterm><primary>Object-Oriented(面向对象)</primary></indexterm> + <indexterm><primary>binary compatibility(二进制兼容性)</primary></indexterm> + <para>内核对象,也就是<firstterm>Kobj</firstterm>,为内核提供了一种面向对象 + 的C语言编程方式。被操作的数据也承载操作它的方法。 + 这使得在不破坏二进制兼容性的前提下,某一个接口能够增/减相应的操作。</para> + + <sect1 xml:id="kernel-objects-term"> + <title>术语</title> + + <indexterm><primary>object(对象)</primary></indexterm> + <indexterm><primary>method(方法)</primary></indexterm> + <indexterm><primary>class(类)</primary></indexterm> + <indexterm><primary>interface(接口)</primary></indexterm> + + <variablelist> + <varlistentry> + <term>对象</term> + <listitem><para>数据集合-数据结构-数据分配的集合</para> + </listitem> + </varlistentry> + <varlistentry> + <term>方法</term> + <listitem> + <para>某一种操作—函数</para> + </listitem> + </varlistentry> + <varlistentry> + <term>类</term> + <listitem> + <para>一种或多种方法</para> + </listitem> + </varlistentry> + <varlistentry> + <term>接口</term> + <listitem> + <para>一种或多种方法的一个标准集合</para> + </listitem> + </varlistentry> + </variablelist> + </sect1> + + <sect1 xml:id="kernel-objects-operation"> + <title>Kobj的工作流程</title> + + <tip><title>译者注</title><para>这一小节两段落中原作者的用词有些含混, + 请参考我在括号中的注释阅读。</para></tip> + + <para>Kobj工作时,产生方法的描述。每个描述有一个唯一的标识和一个缺省函数。 + 某个描述的地址被用来在一个类的方法表里唯一的标识方法。</para> + + <para>构建一个类,就是要建立一张方法表,并将这张表关联到一个或多个函数(方法); + 这些函数(方法)都带有方法描述。使用前,类要被编译。编译时要为这个类分配一些缓存。 + 在方法表中的每个方法描述都会被指派一个唯一的标识, + 除非已经被其它引用它的类在编译时指派了标识。对于每个将要被使用的方法, + 都会由脚本生成一个函数(方法查找函数),以解析外来参数, + 并在被查询时给出方法描述的地址。被生成的函数(方法查找函数) + 凭着那个方法描述的唯一标识按Hash的方法查找对象的类的缓存。 + 如果这个方法不在缓存中,函数会查找使用类的方法表。如果这个方法被找到了, + 类里的相关函数(也就是某个方法的实现代码)就会被使用。 + 否则,这个方法描述的缺省函数将被使用。</para> + + <para>这些过程可被表示如下:</para> + + <programlisting>对象->缓存<->类</programlisting> + + </sect1> + + <sect1 xml:id="kernel-objects-using"> + <title>使用Kobj</title> + + <sect2> + <title>结构</title> + + <programlisting>struct kobj_method</programlisting> + </sect2> + + <sect2> + <title>函数</title> + + <programlisting>void kobj_class_compile(kobj_class_t cls); +void kobj_class_compile_static(kobj_class_t cls, kobj_ops_t ops); +void kobj_class_free(kobj_class_t cls); +kobj_t kobj_create(kobj_class_t cls, struct malloc_type *mtype, int mflags); +void kobj_init(kobj_t obj, kobj_class_t cls); +void kobj_delete(kobj_t obj, struct malloc_type *mtype);</programlisting> + </sect2> + + <sect2> + <title>宏</title> + + <programlisting>KOBJ_CLASS_FIELDS +KOBJ_FIELDS +DEFINE_CLASS(name, methods, size) +KOBJMETHOD(NAME, FUNC)</programlisting> + </sect2> + + <sect2> + <title>头文件</title> + + <programlisting><sys/param.h> +<sys/kobj.h></programlisting> + </sect2> + + <sect2> + <title>建立一个接口的模板</title> + + <indexterm><primary>Kernel Objects(内核对象)</primary> + <secondary>interface(接口)</secondary></indexterm> + + <para>使用Kobj的第一步是建立一个接口。建立接口包括建立模板的工作。 + 建立模板可用脚本<filename>src/sys/kern/makeobjops.pl</filename>完成, + 它会产生申明方法的头文件和代码,脚本还会生成方法查找函数。</para> + + <para>在这个模板中如下关键词会被使用: + <literal>#include</literal>, <literal>INTERFACE</literal>, + <literal>CODE</literal>, <literal>METHOD</literal>, + <literal>STATICMETHOD</literal>, 和 + <literal>DEFAULT</literal>.</para> + + <para><literal>#include</literal>语句的整行内容将被一字不差的 + 复制到被生成的代码文件的头部。</para> + + <para>例如:</para> + + <programlisting>#include <sys/foo.h></programlisting> + + <para>关键词<literal>INTERFACE</literal>用来定义接口名。 + 这个名字将与每个方法名接合在一起,形成 [interface name]_[method name]。 + 语法是:INTERFACE [接口名];</para> + + <para>例如:</para> + + <programlisting>INTERFACE foo;</programlisting> + + <para>关键词<literal>CODE</literal>会将它的参数一字不差的复制到代码文件中。 + 语法是<literal>CODE { [任何代码] };</literal></para> + + <para>例如:</para> + + <programlisting>CODE { + struct foo * foo_alloc_null(struct bar *) + { + return NULL; +} +};</programlisting> + + <para>关键词<literal>METHOD</literal>用来描述一个方法。语法是: + <literal>METHOD [返回值类型] [方法名] { [对象 [, + 参数若干]] };</literal></para> + + <para>例如:</para> + + <programlisting>METHOD int bar { + struct object *; + struct foo *; + struct bar; +};</programlisting> + + <para>关键词<literal>DEFAULT</literal>跟在关键词<literal>METHOD</literal>之后, + 是对关键词<literal>METHOD</literal>的补充。它给这个方法补充上缺省函数。语法是: + <literal>METHOD [返回值类型] [方法名] { + [对象; [其它参数]] }DEFAULT [缺省函数]; + </literal></para> + + <para>例如:</para> + + <programlisting>METHOD int bar { + struct object *; + struct foo *; + int bar; +} DEFAULT foo_hack;</programlisting> + + <para>关键词<literal>STATICMETHOD</literal>类似关键词<literal>METHOD</literal>。 + 对于每个Kobj对象,一般其头部都有一些Kobj专有的数据。 + <literal>METHOD</literal>定义的方法就假设这些专有数据位于对象头部; + 假如对象头部没有这些专有数据,这些方法对这个对象的访问就可能出错。 + 而<literal>STATICMETHOD</literal>定义的对象可以不受这个限制: + 这样描述出的方法,其操作的数据不由这个类的某个对象实例给出, + 而是全都由调用这个方法时的操作数(译者注:即参数)给出。 + 这也对于在某个类的方法表之外调用这个方法有用。 + <tip><title>译者注</title><para>这一段的语言与原文相比调整很大。 + 静态方法是不依赖于对象实例的方法。 + 参看C++类中的“静态函数”的概念。</para></tip></para> + + <para>其它完整的例子:</para> + + <programlisting>src/sys/kern/bus_if.m +src/sys/kern/device_if.m</programlisting> + + </sect2> + + <sect2> + <title>建立一个类</title> + + <indexterm><primary>Kernel Objects(内核对象)</primary> + <secondary>class(类)</secondary></indexterm> + + <para>使用Kobj的第二步是建立一个类。一个类的组有名字、方法表; + 假如使用了Kobj的“对象管理工具”(Object Handling Facilities), + 类中还包含对象的大小。建立类时使用宏<function>DEFINE_CLASS()</function>。 + 建立方法表时,须建立一个kobj_method_t数组,用NULL项结尾。 + 每个非NULL项可用宏<function>KOBJMETHOD()</function>建立。</para> + + <para>例如:</para> + + <programlisting>DEFINE_CLASS(fooclass, foomethods, sizeof(struct foodata)); + +kobj_method_t foomethods[] = { + KOBJMETHOD(bar_doo, foo_doo), + KOBJMETHOD(bar_foo, foo_foo), + { NULL, NULL} +};</programlisting> + + <para>类须被<quote>编译</quote>。根据该类被初始化时系统的状态, + 将要用到一个静态分配的缓存和<quote>操作数表</quote>(ops table, + 译者注:即<quote>参数表</quote>)。这些操作可通过声明一个结构体 + <varname remap="structname">struct kobj_ops</varname>并使用 + <function>kobj_class_compile_static()</function>, + 或是只使用<function>kobj_class_compile()</function>来完成。</para> + </sect2> + + <sect2> + <title>建立一个对象</title> + + <indexterm><primary>Kernel Objects(内核对象)</primary> + <secondary>object(对象)</secondary></indexterm> + + <para>使用Kobj的第三步是定义对象。Kobj对象建立程序假定Kobj + 专有数据在一个对象的头部。如果不是如此,应当先自行分配对象, + 再使用<function>kobj_init()</function>初始化对象中的Kobj专有数据; + 其实可以使用<function>kobj_create()</function>分配对象, + 并自动初始化对象中的Kobj专有内容。<function>kobj_init()</function> + 也可以用来改变一个对象所使用的类。</para> + + <para>将Kobj的数据集成到对象中要使用宏KOBJ_FIELDS。</para> + + <para>例如</para> + + <programlisting>struct foo_data { + KOBJ_FIELDS; + foo_foo; + foo_bar; +};</programlisting> + </sect2> + + <sect2> + <title>调用方法</title> + + <para>使用Kobj的最后一部就是通过生成的函数调用对象类中的方法。 + 调用时,接口名与方法名用'_'接合,而且全部使用大写字母。</para> + + <para>例如,接口名为foo,方法为bar,调用就是:</para> + + <programlisting>[返回值 = ] FOO_BAR(对象 [, 其它参数]);</programlisting> + + </sect2> + + <sect2> + <title>善后处理</title> + + <para>当一个用<function>kobj_create()</function>不再需要被使用时, + 可对这个对象调用<function>kobj_delete()</function>。 + 当一个类不再需要被使用时, + 可对这个类调用<function>kobj_class_free()</function>。</para> + </sect2> + </sect1> +</chapter> diff --git a/zh_CN.UTF-8/books/arch-handbook/locking/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/locking/chapter.xml new file mode 100644 index 0000000000..afe6e5a7b2 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/locking/chapter.xml @@ -0,0 +1,334 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + The FreeBSD Documentation Project + The FreeBSD SMP Next Generation Project + The FreeBSD Simplified Chinese Project + + Original Revision: 1.11 + $FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="locking"> + <info><title>内核中的锁</title> + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + </info> + + + <indexterm><primary>SMP Next Generation Project(下一代对称多处理工程)</primary></indexterm> + <para><emphasis>这一章由 FreeBSD SMP Next Generation Project 维护。 + 请将评论和建议发送给&a.smp;.</emphasis></para> + + + <indexterm><primary>locking(锁)</primary></indexterm> + <indexterm><primary>multi-processing(多处理)</primary></indexterm> + <indexterm><primary>mutexes(同时/独占, mutual exclusion)</primary></indexterm> + <indexterm><primary>lockmgr(锁管理器)</primary></indexterm> + <indexterm><primary>atomic operations(原子操作)</primary></indexterm> + <para>这篇文档提纲挈领的讲述了在FreeBSD内核中的锁,这些锁使得有效的多处理成为可能。 + 锁可以用几种方式获得。数据结构可以用mutex或&man.lockmgr.9;保护。 + 对于为数不多的若干个变量,假如总是使用原子操作访问它们,这些变量就可以得到保护。 + <tip><title>译者注</title><para>仅读本章内容,还不足以找出<quote>mutex</quote> + 和<quote>共享互斥锁</quote>的区别。似乎它们的功能有重叠之处, + 前者比后者的功能选项更多。它们似乎都是&man.lockmgr.9;的子集。</para></tip></para> + + <sect1 xml:id="locking-mutexes"> + <title>Mutex</title> + + <para>Mutex就是一种用来解决共享/排它矛盾的锁。 + 一个mutex在一个时刻只可以被一个实体拥有。如果另一个实体要获得已经被拥有的mutex, + 就会进入等待,直到这个mutex被释放。在FreeBSD内核中,mutex被进程所拥有。</para> + + <para>Mutex可以被递归的索要,但是mutex一般只被一个实体拥有较短的一段时间, + 因此一个实体不能在持有mutex时睡眠。如果你需要在持有mutex时睡眠, + 可使用一个 &man.lockmgr.9; 的锁。</para> + + <para>每个mutex有几个令人感兴趣的属性:</para> + + <variablelist> + <varlistentry> + <term>变量名</term> + <listitem> + <para>在内核源代码中<type>struct mtx</type>变量的名字</para> + </listitem> + </varlistentry> + + <varlistentry> + <term>逻辑名</term> + <listitem> + <para>由函数<function>mtx_init</function>指派的mutex的名字。 + 这个名字显示在KTR跟踪消息和witness出错与警告信息里。 + 这个名字还用于区分标识在witness代码中的各个mutex</para> + </listitem> + </varlistentry> + + <varlistentry> + <term>类型</term> + <listitem> + <para>Mutex的类型,用标志<constant>MTX_*</constant>表示。 + 每个标志的意义在&man.mutex.9;有所描述。</para> + + <variablelist> + <varlistentry> + <term><constant>MTX_DEF</constant></term> + <listitem> + <para>一个睡眠mutex</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>MTX_SPIN</constant></term> + <listitem> + <para>一个循环mutex</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>MTX_RECURSE</constant></term> + <listitem> + <para>这个mutex允许递归</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>保护对象</term> + <listitem> + <para>这个入口所要保护的数据结构列表或数据结构成员列表。 + 对于数据结构成员,将按照 + <varname remap="structname">结构名</varname>.<varname remap="structfield">成员名</varname>的形式命名。</para> + </listitem> + </varlistentry> + + <varlistentry> + <term>依赖函数</term> + <listitem> + <para>仅当mutex被持有时才可以被调用的函数</para> + </listitem> + </varlistentry> + </variablelist> + + <table frame="all" colsep="1" rowsep="1" pgwide="1"> + <title>Mutex列表</title> + + <indexterm><primary>locks(锁)</primary> + <secondary>sched_lock(调度器锁)</secondary></indexterm> + + <indexterm><primary>locks(锁)</primary> + <secondary>vm86pcb_lock(虚拟8086模式进程控制块锁)</secondary></indexterm> + + <indexterm><primary>locks(锁)</primary> + <secondary>Giant(巨锁)</secondary></indexterm> + + <indexterm><primary>locks(锁)</primary> + <secondary>callout_lock(延时调用锁)</secondary></indexterm> + + <tgroup cols="5"> + <thead> + <row> + <entry>变量名</entry> + <entry>逻辑名</entry> + <entry>类型</entry> + <entry>保护对象</entry> + <entry>依赖函数</entry> + </row> + </thead> + + <!-- The scheduler lock --> + <tbody> + <row> + <entry>sched_lock</entry> + <entry><quote>sched lock</quote>(调度器锁)</entry> + <entry> + <constant>MTX_SPIN</constant> | + <constant>MTX_RECURSE</constant> + </entry> + <entry> + <varname>_gmonparam</varname>, + <varname>cnt.v_swtch</varname>, + <varname>cp_time</varname>, + <varname>curpriority</varname>, + <varname remap="structname">mtx</varname>.<varname remap="structfield">mtx_blocked</varname>, + <varname remap="structname">mtx</varname>.<varname remap="structfield">mtx_contested</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_procq</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_slpq</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_sflag</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_stat</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_estcpu</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_cpticks</varname> + <varname remap="structname">proc</varname>.<varname remap="structfield">p_pctcpu</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_wchan</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_wmesg</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_swtime</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_slptime</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_runtime</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_uu</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_su</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_iu</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_uticks</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_sticks</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_iticks</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_oncpu</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_lastcpu</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_rqindex</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_heldmtx</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_blocked</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_mtxname</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_contested</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_priority</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_usrpri</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_nativepri</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_nice</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_rtprio</varname>, + <varname>pscnt</varname>, + <varname>slpque</varname>, + <varname>itqueuebits</varname>, + <varname>itqueues</varname>, + <varname>rtqueuebits</varname>, + <varname>rtqueues</varname>, + <varname>queuebits</varname>, + <varname>queues</varname>, + <varname>idqueuebits</varname>, + <varname>idqueues</varname>, + <varname>switchtime</varname>, + <varname>switchticks</varname> + </entry> + <entry> + <function>setrunqueue</function>, + <function>remrunqueue</function>, + <function>mi_switch</function>, + <function>chooseproc</function>, + <function>schedclock</function>, + <function>resetpriority</function>, + <function>updatepri</function>, + <function>maybe_resched</function>, + <function>cpu_switch</function>, + <function>cpu_throw</function>, + <function>need_resched</function>, + <function>resched_wanted</function>, + <function>clear_resched</function>, + <function>aston</function>, + <function>astoff</function>, + <function>astpending</function>, + <function>calcru</function>, + <function>proc_compare</function> + </entry> + </row> + + <!-- The vm86 pcb lock --> + <row> + <entry>vm86pcb_lock</entry> + <entry><quote>vm86pcb lock</quote>(虚拟8086模式进程控制块锁)</entry> + <entry> + <constant>MTX_DEF</constant> + </entry> + <entry> + <varname>vm86pcb</varname> + </entry> + <entry> + <function>vm86_bioscall</function> + </entry> + </row> + + <!-- Giant --> + <row> + <entry>Giant</entry> + <entry><quote>Giant</quote>(巨锁)</entry> + <entry> + <constant>MTX_DEF</constant> | + <constant>MTX_RECURSE</constant> + </entry> + <entry>几乎可以是任何东西</entry> + <entry>许多</entry> + </row> + + <!-- The callout lock --> + <row> + <entry>callout_lock</entry> + <entry><quote>callout lock</quote>(延时调用锁)</entry> + <entry> + <constant>MTX_SPIN</constant> | + <constant>MTX_RECURSE</constant> + </entry> + <entry> + <varname>callfree</varname>, + <varname>callwheel</varname>, + <varname>nextsoftcheck</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_itcallout</varname>, + <varname remap="structname">proc</varname>.<varname remap="structfield">p_slpcallout</varname>, + <varname>softticks</varname>, + <varname>ticks</varname> + </entry> + <entry> + </entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + + <sect1 xml:id="locking-sx"> + <title>共享互斥锁</title> + + <para>这些锁提供基本的读/写类型的功能,可以被一个正在睡眠的进程持有。 + 现在它们被统一到&man.lockmgr.9;之中。</para> + <indexterm><primary>locks(锁)</primary> + <secondary>shared exclusive(共享互斥)</secondary></indexterm> + + <table> + <title>共享互斥锁列表</title> + <indexterm><primary>locks(锁)</primary> + <secondary>allproc_lock(全进程锁)</secondary></indexterm> + <indexterm><primary>locks(锁)</primary> + <secondary>proctree_lock(进程树锁)</secondary></indexterm> + + <tgroup cols="2"> + <thead> + <row> + <entry>变量名</entry> + <entry>保护对象</entry> + </row> + </thead> + <tbody> + <row> + <entry><varname>allproc_lock</varname></entry> + <entry> + <varname>allproc</varname> + <varname>zombproc</varname> + <varname>pidhashtbl</varname> + <varname remap="structname">proc</varname>.<varname remap="structfield">p_list</varname> + <varname remap="structname">proc</varname>.<varname remap="structfield">p_hash</varname> + <varname>nextpid</varname> + </entry> + </row> + <row> + <entry><varname>proctree_lock</varname></entry> + <entry> + <varname remap="structname">proc</varname>.<varname remap="structfield">p_children</varname> + <varname remap="structname">proc</varname>.<varname remap="structfield">p_sibling</varname> + </entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + + <sect1 xml:id="locking-atomic"> + <title>原子保护变量</title> + + <indexterm><primary>atomically protected variables(原子保护变量)</primary></indexterm> + + <para>原子保护变量并非由一个显在的锁保护的特殊变量,而是: + 对这些变量的所有数据访问都要使用特殊的原子操作(&man.atomic.9;)。 + 尽管其它的基本同步机制(例如mutex)就是用原子保护变量实现的, + 但是很少有变量直接使用这种处理方式。</para> + + <itemizedlist> + <listitem> + <para><varname remap="structname">mtx</varname>.<varname remap="structfield">mtx_lock</varname></para> + </listitem> + </itemizedlist> + </sect1> +</chapter> diff --git a/zh_CN.UTF-8/books/arch-handbook/mac.ent b/zh_CN.UTF-8/books/arch-handbook/mac.ent new file mode 100644 index 0000000000..8e211b409e --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/mac.ent @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Original Revision: 1.4 +$FreeBSD$ --> + +<!ENTITY mac.mpo "mpo"> +<!ENTITY mac.thead ' + <colspec xmlns="http://docbook.org/ns/docbook" colname="first" colwidth="0"/> + <colspec xmlns="http://docbook.org/ns/docbook" colwidth="0"/> + <colspec xmlns="http://docbook.org/ns/docbook" colname="last" colwidth="0"/> + + <thead xmlns="http://docbook.org/ns/docbook"> + <row> + <entry>参数</entry> + <entry>说明</entry> + <entry>锁定</entry> + </row> + </thead> +'> + +<!ENTITY mac.externalize.paramdefs ' + <paramdef xmlns="http://docbook.org/ns/docbook">struct label *<parameter>label</parameter></paramdef> + <paramdef xmlns="http://docbook.org/ns/docbook">char *<parameter>element_name</parameter></paramdef> + <paramdef xmlns="http://docbook.org/ns/docbook">struct sbuf *<parameter>sb</parameter></paramdef> + <paramdef xmlns="http://docbook.org/ns/docbook">int <parameter>*claimed</parameter></paramdef> +'> + +<!ENTITY mac.externalize.tbody ' + <tbody xmlns="http://docbook.org/ns/docbook"> + <row> + <entry><parameter>label</parameter></entry> + <entry>将用外部形式表示的标记</entry> + </row> + + <row> + <entry><parameter>element_name</parameter></entry> + <entry>需要外部表示标记的策略的名字</entry> + </row> + + <row> + <entry><parameter>sb</parameter></entry> + <entry>用来存放标记的文本表示形式的字符buffer</entry> + </row> + + <row> + <entry><parameter>claimed</parameter></entry> + <entry>如果可以填充element_data 域,则其数值递增</entry> + </row> + </tbody> +'> + +<!ENTITY mac.externalize.para ' + <para xmlns="http://docbook.org/ns/docbook">根据传入的标记结构,产生一个以外部形式表示的标记。 + 一个外部形式标记,是标记内容的文本表示,它由用户级的应用程序使用,是用户可读的。 + 目前的MAC实现方案将依次调用策略的相应入口函数,因此, + 具体策略的实现代码,需要在填写sb之前,先检查element_name中指定的名字。 + 如果element_name中的内容与你的策略名字不相符,则直接返回0。 + 仅当转换标记数据的过程中出现错误时,才返回非0值。 + 一旦策略决定填写element_data,递增*claim的数值。</para> +'> + +<!ENTITY mac.internalize.paramdefs ' + <paramdef xmlns="http://docbook.org/ns/docbook">struct label *<parameter>label</parameter></paramdef> + <paramdef xmlns="http://docbook.org/ns/docbook">char *<parameter>element_name</parameter></paramdef> + <paramdef xmlns="http://docbook.org/ns/docbook">char *<parameter>element_data</parameter></paramdef> + <paramdef xmlns="http://docbook.org/ns/docbook">int *<parameter>claimed</parameter></paramdef> +'> + +<!ENTITY mac.internalize.tbody ' + <tbody xmlns="http://docbook.org/ns/docbook"> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被填充的标记</entry> + </row> + + <row> + <entry><parameter>element_name</parameter></entry> + <entry>需要内部表示标记的策略的名字</entry> + </row> + + <row> + <entry><parameter>element_data</parameter></entry> + <entry>需要被转换的文本数据</entry> + </row> + + <row> + <entry><parameter>claimed</parameter></entry> + <entry>如果数据被正确转换,则其数值递增</entry> + </row> + </tbody> +'> + +<!ENTITY mac.internalize.para ' + <para xmlns="http://docbook.org/ns/docbook">根据一个文本形式的外部表示标记数据,创建一个内部形式的标记结构。 + 目前的MAC方案将依次调用所有策略的相关入口函数,来响应标记的内部转换请求, + 因此,实现代码必须首先通过比较element_name中的内容和自己的策略名字, + 来确定是否需要转换element_data中存放的数据。 + 类似的,如果名字不匹配或者数据转换操作成功,该函数返回0,并递增*claimed的值。</para> +'> diff --git a/zh_CN.UTF-8/books/arch-handbook/mac/chapter.xml b/zh_CN.UTF-8/books/arch-handbook/mac/chapter.xml new file mode 100644 index 0000000000..50db789d53 --- /dev/null +++ b/zh_CN.UTF-8/books/arch-handbook/mac/chapter.xml @@ -0,0 +1,7233 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (c) 2002-2005 Networks Associates Technology, Inc. + All rights reserved. + + This software was developed for the FreeBSD Project by + Chris Costello at Safeport Network Services and Network Associates Labs, + the Security Research Division of Network Associates, Inc. under + DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the + DARPA CHATS research program. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Original Revision: 1.46 + $FreeBSD$ +--> +<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="mac"> + <info><title>TrustedBSD MAC 框架</title> + <authorgroup> + <author><personname><firstname>Chris</firstname><surname>Costello</surname></personname><affiliation> + <orgname>TrustedBSD 项目</orgname> + <address><email>chris@FreeBSD.org</email></address> + </affiliation></author> + + <author><personname><firstname>Robert</firstname><surname>Watson</surname></personname><affiliation> + <orgname>TrustedBSD 项目</orgname> + <address><email>rwatson@FreeBSD.org</email></address> + </affiliation></author> + </authorgroup> + + <authorgroup> + <author><personname/><contrib>&cnproj.translated.by;</contrib></author> + </authorgroup> + + </info> + + + + <sect1 xml:id="mac-copyright"> + <title>MAC 文档版权声明</title> + + <para>本文档是作为 DARPA CHATS 研究计划的一部分,由供职于 Security Research Division of Network Associates + 公司Safeport Network Services and Network Associates Laboratories 的Chris Costello依据 DARPA/SPAWAR 合同 + N66001-01-C-8035 (<quote>CBOSS</quote>),为 FreeBSD 项目编写的。</para> + + <para>Redistribution and use in source (SGML DocBook) and + 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) + with or without modification, are permitted provided that the + following conditions are met:</para> + + <orderedlist> + <listitem> + <para>Redistributions of source code (SGML DocBook) must + retain the above copyright notice, this list of conditions + and the following disclaimer as the first lines of this file + unmodified.</para> + </listitem> + + <listitem> + <para>Redistributions in compiled form (transformed to other + DTDs, converted to PDF, PostScript, RTF and other formats) + must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation + and/or other materials provided with the + distribution.</para> + </listitem> + </orderedlist> + + <important> + <para>THIS DOCUMENTATION IS PROVIDED BY THE NETWORKS ASSOCIATES + TECHNOLOGY, INC "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL NETWORKS ASSOCIATES TECHNOLOGY, + INC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</para> + </important> + + <important><para>本文中许可证的非官方中文翻译仅供参考, + 不作为判定任何责任的依据。如与英文原文有出入,则以英文原文为准。</para></important> + + <para>在满足下列许可条件的前提下,允许再分发或以源代码 (SGML DocBook) 或 “编译” (SGML, HTML, PDF, PostScript, RTF 等) + 的经过修改或未修改的形式:</para> + + <orderedlist> + <listitem> + <para>再分发源代码 (SGML DocBook) 必须不加修改的保留上述版权告示、本条件清单和下述弃权书作为该文件的最先若干行。</para> + </listitem> + + <listitem> + <para>再分发编译的形式 (转换为其它DTD、 PDF、 PostScript、 RTF 或其它形式),必须将上述版权告示、 + 本条件清单和下述弃权书复制到与分发品一同提供的文件,以及其它材料中。</para> + </listitem> + </orderedlist> + + <important> + <para>本文档由 NETWORKS ASSOCIATES TECHNOLOGY, INC “按现状条件”提供,并在此明示不提供任何明示或暗示的保障, + 包括但不限于对商业适销性、对特定目的的适用性的暗示保障。任何情况下, + NETWORKS ASSOCIATES TECHNOLOGY, INC 均不对任何直接、 间接、 偶然、 特殊、 惩罚性的, + 或必然的损失 (包括但不限于替代商品或服务的采购、 使用、 数据或利益的损失或营业中断) 负责, + 无论是如何导致的并以任何有责任逻辑的, 无论是否是在本文档使用以外以任何方式产生的契约、严格责任或是民事侵权行为(包括疏忽或其它)中的, + 即使已被告知发生该损失的可能性。</para> + </important> + </sect1> + + <sect1 xml:id="mac-synopsis"> + <title>术语解析</title> + + <para>FreeBSD 以一个内核安全扩展性框架(TrustedBSD MAC 框架)的方式,为若干强制访问控制策略(也称“集权式访问控制策略”) + 提供试验性支持。MAC 框架是一个插入式的访问控制框架,允许新的安全策略更方便地融入内核:安全策略可以静态链入内核,也可以 + 在引导时加载,甚至在运行时动态加载。该框架所提供的标准化接口,使得运行在其上的安全策略模块能对系统对象的安全属性进行诸如标记等一系列操作。 + MAC 框架的存在,简化了这些操作在策略模块中的实现,从而显著降低了新安全策略模块的开发难度。</para> + + <para>本章将介绍 MAC 策略框架,为读者提供一个示例性的 MAC 策略模块文档。</para> + </sect1> + + + <sect1 xml:id="mac-introduction"> + <title>概述</title> + + <para>TrustedBSD MAC 框架提供的机制,允许在其上运行的内核模块在内核编译或者运行时,对内核的访问控制模型进行扩展。 + 新的系统安全策略作为一个内核模块实现,并被链接到内核中;如果系统中同时存在多个安全策略模块,则它们的决策结果将以某种确定的方式组合。 + 为了给简化新安全策略的开发,MAC 向上提供了大量用于访问控制的基础设施,特别是,对临时的或者持久的、策略无关的对象安全标记的支持。 + 该支持目前仍是试验性质的。</para> + + <para>本章所提供的信息不仅将使在 MAC 使能环境下工作的潜在用户受益, + 也可以为需要了解 MAC 框架是如何支持对内核访问控制进行扩展的策略模块开发人员所用。</para> + </sect1> + + <sect1 xml:id="mac-background"> + <title>安全策略背景知识</title> + + <para>强制访问控制(简称 MAC),是指由操作系统强制实施的一组针对用户的访问控制策略。 + 在某些情况下,强制访问控制的策略可能会与自主访问控制(简称 DAC)所提供的保护措施发生冲突, + 后者是用来向非管理员用户对数据采取保护措施提供支持的。在传统的 UNIX 系统中, + DAC 保护措施包括文件访问模式和访问控制列表;而 MAC 则提供进程控制和防火墙等。 + 操作系统设计者和安全机制研究人员对许多经典的 MAC 安全策略作了形式化的表述,比如, + 多级安全(MLS)机密性策略,Biba 完整性策略,基于角色的访问控制策略(RBAC),域和型裁决策略(DTE),以及型裁决策略(TE)。 + 安全策略的形式化表述被称为安全模型。每个模型根据一系列条件做出安全相关的决策,这些条件包括, + 用户的身份、角色和安全信任状,以及对象的安全标记(用来代表该对象数据的机密性/完整性级别)。</para> + + <para>TrustedBSD MAC 框架所提供的对策略模块的支持,不仅可以用来实现上述所有策略, + 还能用于实现其他利用已有安全属性(如,用户和组ID、文件扩展属性等)决策的系统安全强化策略。 + 此外,因为具体策略模块在访问授权方面所拥有的高度灵活性和自主性,所以MAC 框架同样可以用来实现完全自主式的安全策略.</para> + </sect1> + + <sect1 xml:id="mac-framework-kernel-arch"> + <title>MAC 框架的内核体系结构</title> + + <para>TrustedBSD MAC 框架为大多数的访问控制模块提供基本设施,允许它们以内核模块的形式灵活地扩展系统中实施的安全策略。 + 如果系统中同时加载了多个策略,MAC 框架将负责将各个策略的授权结果以一种(某种程度上)有意义的方式组合,形成最后的决策。</para> + + <sect2 xml:id="mac-framework-kernel-arch-elements"> + <title>内核元素</title> + + <para>MAC 框架由下列内核元素组成:</para> + + <itemizedlist> + <listitem><para>框架管理接口</para></listitem> + <listitem><para>并发与同步原语</para></listitem> + <listitem><para>策略注册</para></listitem> + <listitem><para>内核对象的扩展性安全标记</para></listitem> + <listitem><para>策略入口函数的组合操作</para></listitem> + <listitem><para>标记管理原语</para></listitem> + <listitem><para>由内核服务调用的入口函数 API</para></listitem> + <listitem><para>策略模块的入口函数 API</para></listitem> + <listitem><para>入口函数的实现(包括策略生命周期管理、标记管理和访问控制检查三部分)</para></listitem> + <listitem><para>管理策略无关标记的系统调用</para></listitem> + <listitem><para>复用的<function>mac_syscall()</function> 系统调用</para></listitem> + <listitem><para>以 MAC 的策略加载模块形式实现的各种安全策略</para></listitem> + </itemizedlist> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-management"> + <title>框架管理接口</title> + + <para>对 TrustedBSD MAC 框架进行直接管理的方式有三种:通过 sysctl 子系统、通过 loader 配置, 或者使用系统调用。</para> + + <para>多数情况下,与同一个内核内部变量相关联的 sysctl 变量和 loader 参数的名字是相同的, + 通过设置它们,可以控制保护措施的实施细节,比如,某个策略在各个内核子系统中的实施与否等等。 + 另外,如果在内核编译时选择支持 MAC 调试选项,内核将维护若干计数器以跟踪标记的分配使用情况。 + 通常不建议在实用环境下通过在不同子系统上设置不同的变量或参数来实施控制,因为这种方法将会作用于系统中所有的活跃策略。 + 如果希望对具体策略实施管理而不相影响其他活跃策略,则应当使用策略级别的控制,因为这种方法的控制粒度更细, + 并能更好地保证策略模块的功能一致性。</para> + + <para>与其他内核模块一样,系统管理员可以通过系统的模块管理系统调用和其他系统接口,包括 boot loader 变量,对策略模块执行加载与卸载操作; + 策略模块可以在加载时,设置加载标志,来指示系统对其加载、卸载操作进行相应控制,比如阻止非期望的卸载操作。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-synchronization"> + <title>策略链表的并发与同步</title> + + <para>在运行时,系统中活跃的策略集合可能发生变化,然而对策略入口函数的使用操作并不是原子性的,因此,当某一个入口函数正被使用时, + 系统需要提供额外的同步机制来阻止对该策略模块的加载与卸载,以确保当前活跃的策略集合不会在此过程中发生改变。 + 通过使用"框架忙”计数器,就可以做到这一点:一旦某个入口函数被调用,计数器的值被增加1;而每当一个入口函数调用结束时,计数器的值被减少1。 + 检查计数器的值,如果其值为正,框架将阻止对策略链表的修改操作,请求操作的线程将被迫进入睡眠,直到计数器的值重新减少到0为止。 + 计数器本身由一个互斥锁保护,同时结合一个条件变量(用于唤醒等待对策略链表进行修改操作的睡眠线程)。 + 采用这种同步模型的一个副作用是,在同一个策略模块内部,允许嵌套地调用框架,不过这种情况其实很少出现。</para> + + <para>为了减少由于采用计数器引入的额外开销,设计者采用了各种优化措施。其中包括,当策略链表为空或者其中仅含有静态表项 + (那些只能在系统运行之前加载而且不能动态卸载的策略)时,框架不对计数器进行操作,其值总是为0,从而将此时的同步开销减到0。 + 另一个极端的办法是,使用一个编译选项来禁止在运行时对加载的策略链表进行修改,此时不再需要对策略链表的使用进行同步保护。</para> + + <para>因为 MAC 框架不允许在某些入口函数之内阻塞,所以不能使用普通的睡眠锁。 + 故而,加载或卸载操作可能会为等待框架空闲而被阻塞相当长的一段时间。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-label-synchronization"> + <title>标记同步</title> + + <para>MAC 框架必须对其负责维护的安全属性标记的存储访问提供同步保护。下列两种情形,可能导致对安全属性标记的不一致访问: + 第一,作为安全属性标记的持有者,内核对象本身可能同时被多个线程访问;第二,MAC 框架代码是可重入的, + 即允许多个线程同时在框架内执行。通常,MAC 框架使用内核对象数据上已有的内核同步机制来保护该其上附加的 MAC 安全标记。 + 例如,套接字上的 MAC 标记由已有的套接字互斥锁保护。类似的,对于安全标记的并发访问的过程与对其所在对象进行的并发访问在语义上是一样的, + 例如,信任状安全标记,将保持与该数据结构中其他内容一致的"写时复制"的更新过程。 + MAC 框架在引用一个内核对象时,将首先对访问该对象上的标记需要用到的锁进行断言。 + 策略模块的编写者必须了解这些同步语义, 因为它们可能会限制对安全标记所能进行的访问类型。 + 举个例子,如果通过入口函数传给策略模块的是对某个信任状的只读引用,那么在策略内部,只能读该结构对应的标记状态。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-policy-synchronization"> + <title>策略间的同步与并发</title> + + <para>FreeBSD 内核是一个可抢占式的内核,因此,作为内核一部分的策略模块也必须是可重入的,也就是说, + 在开发策略模块时必须假设多个内核线程可以同时通过不同的入口函数进入该模块。 + 如果策略模块使用可被修改的内核状态,那么还需要在策略内部使用恰当的同步原语,确保在策略内部的多个线程不会因此观察到不一致的内核状态, + 从而避免由此产生的策略误操作。为此,策略可以使用 FreeBSD 现有的同步原语,包括互斥锁、睡眠锁、条件变量和计数信号量。 + 对这些同步原语的使用必须慎重,需要特别注意两点:第一,保持现有的内核上锁次序; + 第二,在非睡眠的入口函数之内不要使用互斥锁和唤醒操作。</para> + + <para>为避免违反内核上锁次序或造成递归上锁,策略模块在调用其他内核子系统之前,通常要释放所有在策略内部申请的锁。 + 这样做的结果是,在全局上锁次序形成的拓朴结构中,策略内部的锁总是作为叶子节点, + 从而保证了这些锁的使用不会导致由于上锁次序混乱造成的死锁。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-registration"> + <title>策略注册</title> + + <para>为了记录当前使用的策略模块集合,MAC 框架维护两个链表:一个静态链表和一个动态链表。 + 两个链表的数据结构和操作基本相同,只是动态链表还额外使用了一个"引用计数"以同步对其的访问操作。 + 当包含 MAC 框架策略的内核模块被加载时,该策略模块会通过 <literal>SYSINIT</literal> 调用一个注册函数; + 相对应的,每当一个策略模块被卸载,<literal>SYSINIT</literal> 也会调用一个注销函数。 + 只有当遇到下列情况之一时,注册过程才会失败: 一个策略模块被加载多次,或者系统资源不足不能满足注册过程的需要( + 例如,策略模块需要对内核对象添加标记而可用资源不足),或者其他的策略加载前提条件不满足(有些策略要求只能在系统引导之前加载)。 + 类似的,如果一个策略被标记为不可卸载的,对其调用注销过程将会失败。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-entrypoints"> + <title>入口函数</title> + + <para>内核服务与 MAC 框架之间进行交互有两种途径: + 一是,内核服务调用一系列 API 通知 MAC 框架安全事件的发生; + 二是,内核服务向 MAC 框架提供一个指向安全对象的策略无关安全标记数据结构的指针。 + 标记指针由 MAC 框架经由标记管理入口函数进行维护, + 并且,只要对管理相关对象的内核子系统稍作修改,就可以允许 MAC 框架向策略模块提供标记服务。 + 例如,在进程、进程信任状、套接字、管道、Mbuf、网络接口、IP 重组队列和其他各种安全相关的数据结构中均增加了指向安全标记的指针。 + 另外,当需要做出重要的安全决策时,内核服务也会调用 MAC 框架,以便各个策略模块根据其自己的标准(可以使用存储在安全标记中的数据)完善这些决策。 + 绝大多数安全相关的关键决策是显式的访问控制检查; + 也有少数涉及更加一般的决策函数,比如,套接字的数据包匹配和程序执行时刻的标记转换。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-composition"> + <title>策略组合</title> + + <para>如果内核中同时加载了多个策略模块,这些策略的决策结果将由框架使用一个合成运算子来进行组合汇总,得出最终的结果。 + 目前,该算子是硬编码的,并且只有当所有的活跃策略均对请求表示同意时才会返回成功。 + 由于各个策略返回的出错条件可能并不相同(成功、访问被拒绝、请求对象不存在等等), + 需要使用一个选择子先从各个策略返回的错误条件集合中选择出一个作为最终返回结果。 + 一般情况下,与“访问被拒绝”相比,将更倾向于选择“请求对象不存在”。 + 尽管不能从理论上保证合成结果的有效性与安全性,但试验结果表明,对于许多实用的策略集合来说,事实的确如此。 + 例如,传统的可信系统常常采用类似的方法对多个安全策略进行组合。</para> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-labels"> + <title>标记支持</title> + + <para>与许多需要给对象添加安全标记的访问控制扩展一样,MAC 框架为各种用户可见的对象提供了一组用于管理策略无关标记的系统调用。 + 常用的标记类型有,partition标识符、机密性标记、完整性标记、区间(非等级类别)、域、角色和型。 + “策略无关”的意思是指,标记的语法与使用它的具体策略模块无关,而同时策略模块能够完全独立地定义和使用与对象相关联的元数据的语义。 + 用户应用程序提供统一格式的基于字符串的标记,由使用它的策略模块负责解析其内在含义并决定其外在表示。 + 如果需要,应用程序可以使用多重标记元素。</para> + + <para>内存中的标记实例被存放在由 slab 分配的<varname remap="structname">struct label</varname>数据结构中。 + 该结构是一个固定长度的数组,每个元素是由一个 <literal>void *</literal> + 指针和一个 <literal>long</literal>组成的联合结构。 + 申请标记存储的策略模块在向 MAC 注册时,将被分配一个“slot”值,作为框架分配给其使用的策略标记元素在整个标记存储结构中的位置索引。 + 而所分配的存储空间的语义则完全由该策略模块来决定:MAC 框架向策略模块提供了一系列入口函数用于对内核对象生命周期的各种事件进行控制,包括, + 对象的初始化、标记的关联/创建和对象的注销。使用这些接口,可以实现诸如访问计数等存储模型。 + MAC 框架总是给入口函数传入一个指向相关对象的指针和一个指向该对象标记的指针,因此,策略模块能够直接访问标记而无需知悉该对象的内部结构。 + 唯一的例外是进程信任状结构,指向其标记的指针必须由策略模块手动解析计算。今后的 MAC 框架实现可能会对此进行改进。</para> + + <para>初始化入口函数通常有一个睡眠标志位,用来表明一个初始化操作是否允许中途睡眠等待; + 如果不允许,则可能会失败返回,并要求撤销此次标记分配操作(乃至对象分配操作)。 + 例如,如果在网络栈上处理中断时因为不允许睡眠或者调用者持有一个互斥锁,就可能出现这种情况。 + 考虑到在处理中的网络数据包(Mbufs)上维护标记的性能损失太大,策略必须就自己对 Mbuf 进行标记的要求向 MAC 框架做出特别声明。 + 动态加载到系统中而又使用标记的策略必须为处理未被其初始化函数处理过的对象作好准备, + 这些对象在策略加载之前就已经存在,故而无法在初始化时调用策略的相关函数进行处理。 + MAC 框架向策略保证,没有被初始化的标记 slot 的值必为0或者 NULL,策略可以借此检测到未初始化的标记。 + 需要注意的是,因为对 Mbuf 标记的存储分配是有条件的,因此需要使用其标记的动态加载策略还可能需要处理 Mbuf 中值为 NULL 的标记指针。</para> + + <para>对于文件系统对象的标记,MAC 框架在文件的扩展属性中为其分配永久存储。 + 只要可能,扩展属性的原子化的事务操作就被用于保证对 vnode 上安全标记的复合更新操作的一致性--目前,该特性只被 UFS2 文件系统支持。 + 为了实现细粒度的文件系统对象标记(即每个文件系统对象一个标记),策略编写者可能选择使用一个(或者若干)扩展属性块。 + 为了提高性能, vnode 数据结构中有一个标记 (<literal>v_label</literal>)字段,用作磁盘标记的缓冲; + vnode 结构实例化时,策略可以将标记值装入该缓冲,并在需要时对其进行更新。 + 如此,不必在每次进行访问控制检查时,均无条件地访问磁盘上的扩展属性。</para> + + <note><para>目前,如果一个使用标记的策略允许被动态卸载,则卸载该模块之后,其状态 slot 尚无法被系统回收重用, + 由此导致了 MAC 框架对标记策略卸载-重载操作数目上的严格限制。</para></note> + </sect2> + + <sect2 xml:id="mac-framework-kernel-arch-syscalls"> + <title>相关系统调用</title> + + <para>MAC 框架向应用程序提供了一组系统调用:其中大多数用于向进行查询和修改策略无关标记操作的应用 API提供支持。</para> + + <para>这些标记管理系统调用,接受一个标记描述结构, <varname remap="structname">struct mac</varname>,作为输入参数。 + 这个结构的主体是一个数组,其中每个元素包含了一个应用级的 MAC 标记形式。每个元素又由两部分组成:一个字符串名字,和其对应的值。 + 每个策略可以向系统声明一个特定的元素名字,这样一来,如果需要,就可以将若干个相互独立的元素作为一个整体进行处理。 + 策略模块经由入口函数,在内核标记和用户提供的标记之间作翻译转换的工作,这种实现提供了标记元素语义上的高度灵活性。 + 标记管理系统调用通常有对应的库函数包装,这些包装函数可以提供内存分配和错误处理功能,从而简化了用户应用程序的标记管理工作。</para> + + <para>目前的FreeBSD 内核提供了下列 MAC 相关的系统调用:</para> + + <itemizedlist> + <listitem> + <para><function>mac_get_proc()</function> 用于查询当前进程的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_set_proc()</function> 用于请求改变当前进程的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_get_fd()</function> 用于查询由文件描述符所引用的对象( 文件、 + 套接字、 管道文件等等) 的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_get_file()</function> 用于查询由文件系统路径所描述的对象的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_set_fd()</function> 用于请求改变由文件描述符所引用的对象( + 文件、套接字、 管道文件等等) 的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_set_file()</function> 用于请求改变由文件系统路径所描述的对象的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_syscall()</function> 通过复用该系统调用,策略模块能够在不修改系统调用表的前提下创建新的系统调用; + 其调用参数包括:目标策略名字、 操作编号和将被该策略内部使用的参数。</para> + </listitem> + + <listitem> + <para><function>mac_get_pid()</function> 用于查询由进程号指定的另一个进程的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_get_link()</function> 与 + <function>mac_get_file()</function> 功能相同, + 只是当路径参数的最后一项为符号链接时, + 前者将返回该符号链接的安全标记, + 而后者将返回其所指文件的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_set_link()</function> 与 + <function>mac_set_file()</function> 功能相同, + 只是当路径参数的最后一项为符号链接时, + 前者将设置该符号链接的安全标记, + 而后者将设置其所指文件的安全标记。</para> + </listitem> + + <listitem> + <para><function>mac_execve()</function> 与 + <function>execve()</function> 功能类似, + 只是前者还可以在开始执行一个新程序时,根据传入的请求参数,设置执行进程的安全标记。 + 由于执行一个新程序而导致的进程安全标记的改变,被称为“转换”。</para> + </listitem> + + <listitem> + <para><function>mac_get_peer()</function>, + 通过一个套接字选项自动实现, + 用于查询一个远程套接字对等实体的安全标记。</para> + </listitem> + </itemizedlist> + + <para>除了上述系统调用之外, + 也可以通过 <literal>SIOCSIGMAC</literal> 和 <literal>SIOCSIFMAC</literal> + 网络接口的 ioctl 类系统调用来查询和设置网络接口的安全标记。</para> + </sect2> + </sect1> + + <sect1 xml:id="mac-policy-architecture"> + <title>MAC策略模块体系结构</title> + + <para>安全策略可以直接编入内核,也可以编译成独立的内核模块,在系统引导时或者运行时使用模块加载命令加载。 + 策略模块通过一组预先定义好的入口函数与系统交互。通过它们,策略模块能够掌握某些系统事件的发生,并且在必要的时候影响系统的访问控制决策。 + 每个策略模块包含下列组成部分:</para> + + <itemizedlist> + <listitem><para>可选:策略配置参数</para></listitem> + <listitem><para>策略逻辑和参数的集中实现</para></listitem> + <listitem><para>可选:策略生命周期事件的实现,比如,策略的初始化和销毁</para></listitem> + <listitem><para>可选:对所选内核对象的安全标记进行初始化、维护和销毁的支持</para></listitem> + <listitem><para>可选:对所选对象的使用进程进行监控以及修改对象安全标记的支持</para></listitem> + <listitem><para>策略相关的访问控制入口函数的实现</para></listitem> + <listitem><para>对策略标志、模块入口函数和策略特性的声明</para></listitem> + </itemizedlist> + + <sect2 xml:id="mac-policy-declaration"> + <title>策略注销</title> + + <para>策略模块可以使用 <function>MAC_POLICY_SET()</function> 宏来声明。 + 该宏完成以下工作:为该策略命名(向系统声明该策略提供的名字);提交策略定义的 MAC 入口函数向量的地址; + 按照策略的要求设置该策略的加载标志位,保证 MAC 框架将以策略所期望的方式对其进行操作; + 另外,还可能请求框架为策略分配标记状态 slot 值。</para> + + <programlisting>static struct mac_policy_ops mac_<replaceable>policy</replaceable>_ops = +{ + .mpo_destroy = mac_<replaceable>policy</replaceable>_destroy, + .mpo_init = mac_<replaceable>policy</replaceable>_init, + .mpo_init_bpfdesc_label = mac_<replaceable>policy</replaceable>_init_bpfdesc_label, + .mpo_init_cred_label = mac_<replaceable>policy</replaceable>_init_label, +/* ... */ + .mpo_check_vnode_setutimes = mac_<replaceable>policy</replaceable>_check_vnode_setutimes, + .mpo_check_vnode_stat = mac_<replaceable>policy</replaceable>_check_vnode_stat, + .mpo_check_vnode_write = mac_<replaceable>policy</replaceable>_check_vnode_write, +};</programlisting> + + <para>如上所示,MAC 策略入口函数向量,<varname>mac_<replaceable>policy</replaceable>_ops</varname>, + 将策略模块中定义的功能函数挂接到特定的入口函数地址上。 + 在稍后的“入口函数参考”小节中,将提供可用入口函数功能描述和原型的完整列表。 + 与模块注册相关的入口函数有两个:<symbol>.mpo_destroy</symbol>和<symbol>.mpo_init</symbol>。 + 当某个策略向模块框架注册操作成功时,<symbol>.mpo_init</symbol>将被调用,此后其他的入口函数才能被使用。 + 这种特殊的设计使得策略有机会根据自己的需要,进行特定的分配和初始化操作,比如对特殊数据或锁的初始化。 + 卸载一个策略模块时,将调用 <symbol>.mpo_destroy</symbol> 用来释放策略分配的内存空间或注销其申请的锁。 + 目前,为了防止其他入口函数被同时调用,调用上述两个入口函数的进程必须持有 MAC 策略链表的互斥锁:这种限制将被放开, + 但与此同时,将要求策略必须谨慎使用内核原语,以避免由于上锁次序或睡眠造成死锁。</para> + + <para>之所以向策略声明提供模块名字域,是为了能够唯一标识该模块,以便解析模块依赖关系。选择使用恰当的字符串作为名字。 + 在策略加载和卸载时,策略的完整字符串名字将经由内核日志显示给用户。另外,当向用户进程报告状态信息时也会包含该字符串。</para> + </sect2> + + <sect2 xml:id="mac-policy-flags"> + <title>策略标志</title> + + <para>在声明时提供标志参数域的机制,允许策略模块在作为模块被加载时,就自身特性向 MAC 框架提供说明。 + 目前,已经定义的标志有三个:</para> + + <variablelist> + <varlistentry> + <term>MPC_LOADTIME_FLAG_UNLOADOK</term> + + <listitem> + <para>表示该策略模块可以被卸载。 + 如果未提供该标志,则表示该策略模块拒绝被卸载。 + 那些使用安全标记的状态,而又不能在运行时释放该状态的模块可能会设置该标志。 + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>MPC_LOADTIME_FLAG_NOTLATE</term> + + <listitem> + <para>表示该策略模块必须在系统引导过程时进行加载和初始化。 + 如果该标志被设置,那么在系统引导之后注册该模块的请求将被 MAC 框架所拒绝。 + 那些需要为大范围的系统对象进行安全标记初始化工作,而又不能处理含有未被正确初始化安全标记的对象的策略模块可能会设置该标志。</para> + </listitem> + </varlistentry> + + <varlistentry> + <term>MPC_LOADTIME_FLAG_LABELMBUFS</term> + + <listitem> + <para>表示该策略模块要求为 Mbuf 指定安全标记,并且为存储其标记所需的内存空间总是提前分配好的。 + 缺省情况下,MAC 框架并不会为 Mbuf 分配标记存储,除非系统中注册的策略模块中至少有一个设置了该标志。 + 这种做法在没有策略需要对 Mbuf 做标记时,显著地提升了系统网络性能。另外,在某些特殊环境下,可以通过设置内核选项, + <literal>MAC_ALWAYS_LABEL_MBUF</literal>,强制 MAC 框架为 Mbuf 的安全标记分配存储,而不论上述标志如何设置。</para> + </listitem> + </varlistentry> + </variablelist> + + <note><para>那些使用了 + <literal>MPC_LOADTIME_FLAG_LABELMBUFS</literal> 标志但没有设置 + <literal>MPC_LOADTIME_FLAG_NOTLATE</literal> 标志的 + 策略模块必须能够正确地处理通过入口函数传入的值为 <literal>NULL</literal> + 的 Mbuf 安全标记指针。 + 这是因为那些没有分配标记存储的处理中的 Mbuf 在一个需要 Mbuf 安全标记的策略模块加载之后, + 其安全标记的指针将仍然为空。 + 如果策略在网络子系统活跃之前被加载(即,该策略不是被推迟加载的),那么所有的 Mbuf 的标记存储的分配就可以得到保证。</para></note> + </sect2> + + <sect2 xml:id="mac-policy-entry-points"> + <title>策略入口函数</title> + + <para>MAC 框架为注册的策略提供四种类型的入口函数: + 策略注册和管理入口函数; + 用于处理内核对象声明周期事件,如初始化、 + 创建和销毁,的入口函数; + 处理该策略模块感兴趣的访问控制决策事件的入口函数; + 以及用于管理对象安全标记的调用入口函数。 + 此外, + 还有一个 <function>mac_syscall()</function> 入口函数, + 被策略模块用于在不注册新的系统调用的前提下, + 扩展内核接口。</para> + + <para>策略模块的编写人员除了必须清楚在进入特定入口函数之后, + 哪些对象锁是可用的之外, + 还应该熟知内核所采用的加锁策略。 + 编程人员在入口函数之内应该避免使用非叶节点锁, + 并且遵循访问和修改对象时的加锁规程, + 以降低导致死锁的可能性。 + 特别地, + 程序员应该清楚, + 虽然在通常情况下, + 进入入口函数之后, + 已经上了一些锁, + 可以安全地访问对象及其安全标记, + 但是这并不能保证对它们进行修改( + 包括对象本身和其安全标记) + 也是安全的。 + 相关的上锁信息,可以参考 MAC 框架入口函数的相关文档。</para> + + <para>策略入口函数把两个分别指向对象本身和其安全标记的指针传递给策略模块。 + 这样一来,即使策略并不熟悉对象内部结构,也能基于标记作出正确决策。 + 只有进程信任状这个对象例外:MAC 框架总是假设所有的策略模块是理解其内部结构的。</para> + </sect2> + </sect1> + + <sect1 xml:id="mac-entry-point-reference"> + <title>MAC策略入口函数参考</title> + + <sect2 xml:id="mac-mpo-general"> + <title>通用的模块入口函数</title> + + <sect3 xml:id="mac-mpo-init"> + <title><function>&mac.mpo;_init</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init</function></funcdef> + + <paramdef>struct mac_policy_conf + *<parameter>conf</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>conf</parameter></entry> + <entry>MAC 策略定义</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>策略加载事件。当前进程正持有策略链表上的互斥锁,因此是非睡眠的,对其他内核子系统的调用也须慎重。 + 如果需要在策略初始化阶段进行可能造成睡眠阻塞的存储分配操作,可以将它们放在一个单独的模块 SYSINIT() + 过程中集中进行。</para> + </sect3> + + <sect3 xml:id="mpo-destroy"> + <title><function>&mac.mpo;_destroy</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy</function></funcdef> + + <paramdef>struct mac_policy_conf + *<parameter>conf</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>conf</parameter></entry> + <entry>MAC 策略定义</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>策略加载事件。必须持有策略链表互斥锁,因此需要慎重行事。</para> + </sect3> + + <sect3 xml:id="mac-mpo-syscall"> + <title><function>&mac.mpo;_syscall</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_syscall</function></funcdef> + + <paramdef>struct thread + *<parameter>td</parameter></paramdef> + <paramdef>int <parameter>call</parameter></paramdef> + <paramdef>void *<parameter>arg</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>td</parameter></entry> + <entry>调用线程</entry> + </row> + + <row> + <entry><parameter>call</parameter></entry> + <entry>策略特有的系统调用编号</entry> + </row> + + <row> + <entry><parameter>arg</parameter></entry> + <entry>系统调用参数的指针</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>该入口函数提供策略复用的系统调用,这样策略模块不需要为其向用户进程提供的每一个额外服务而注册专用的系统调用。 + 由应用程序提供的策略注册名字来确定提供其所申请服务的特定策略,所有参数将通过该入口函数传递给被调用的策略。 + 当实现新服务时,安全模块必须在必要时通过 MAC 框架调用相应的访问控制检查机制。 + 比方说,假如一个策略实现了某种额外的信号功能,那么它应该调用相关的信号访问控制检查,以接受 MAC 框架中注册的其他策略的检查。</para> + + <note><para>不同的模块需要并发地手动进行<function>copyin()</function>拷贝系统调用数据。</para></note> + </sect3> + + <sect3 xml:id="mac-mpo-thread-userret"> + <title><function>&mac.mpo;_thread_userret</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_thread_userret</function></funcdef> + + <paramdef>struct thread + *<parameter>td</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>td</parameter></entry> + <entry>返回线程</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <!-- XXX: Maybe rewrite this section. --> + <para>使用该入口函数,策略模块能够在线程返回用户空间时(系统调用返回、异常返回等等)进行 MAC 相关的处理工作。 + 使用动态进程标记的策略需要使用该入口函数,因为在处理系统调用的过程中,并不是在任意时刻都能申请到进程锁的; + 进程的标记可能表示传统的认证信息、进程历史记录或者其他数据。为使用该入口函数,对进程信任状所作的修改 + 可能被存放在 <literal>p_label</literal> ,该域受一个进程级自旋锁的保护;接下来,设置线程级的<literal>TDF_ASTPENDING</literal> + 标志位和进程级的<literal>PS_MACPENDM</literal>标志位,表明将调度一个对 userret 入口函数的调用。通过该入口函数, + 策略可以在相对简单的同步上下文中创建信任状的替代品。策略编程人员必须清楚,需要保证与调度一个 AST 相关的事件执行次序, + 同时所执行的 AST 可能很复杂,而且在处理多线程应用程序时可能被重入。</para> + </sect3> + </sect2> + + <sect2 xml:id="mac-label-ops"> + <title>操作标记</title> + + <sect3 xml:id="mac-mpo-init-bpfdesc"> + <title><function>&mac.mpo;_init_bpfdesc_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_bpfdesc_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被应用的新标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的 bpfdesc(BPF 描述子)初始化标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-cred-label"> + <title><function>&mac.mpo;_init_cred_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_cred_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的新标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的用户信任状初始化标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-devfsdirent"> + <title><function>&mac.mpo;_init_devfsdirent_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_devfsdirent_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被应用的新标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的 devfs 表项初始化标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-ifnet"> + <title><function>&mac.mpo;_init_ifnet_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_ifnet_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被应用的新标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的网络接口初始化标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-ipq"> + <title><function>&mac.mpo;_init_ipq_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_ipq_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + <paramdef>int <parameter>flag</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被应用的新标记</entry> + </row> + + <row> + <entry><parameter>flag</parameter></entry> + <entry>睡眠/不睡眠 &man.malloc.9;; 参见下文</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的 IP 分片重组队列初始化标记。其中的<parameter>flag</parameter>域可能取<symbol>M_WAITOK</symbol> + 或<symbol>M_NOWAIT</symbol>之一,用来避免在该初始化调用中因为 &man.malloc.9; 而进入睡眠。IP 分片重组队列的分配操作通常是在 + 对性能有严格要求的环境下进行的,因此实现代码必须小心地避免睡眠和长时间的操作。IP 分片重组队列分配操作失败时上述入口函数将失败返回。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-mbuf"> + <title><function>&mac.mpo;_init_mbuf_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_mbuf_label</function></funcdef> + + <paramdef>int <parameter>flag</parameter></paramdef> + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>flag</parameter></entry> + <entry>睡眠/不睡眠 &man.malloc.9;; 参见下文</entry> + </row> + + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的 mbuf 数据包头部(<parameter>mbuf</parameter>)初始化标记。 + 其中的<parameter>flag</parameter>的值可能取<symbol>M_WAITOK</symbol>和<symbol>M_NOWAIT</symbol>之一, + 用来避免在该初始化调用中因为 &man.malloc.9; 而进入睡眠。Mbuf 头部的分配操作常常在对性能有严格要求的环境下被频繁执行, + 因此实现代码必须小心地避免睡眠和长时间的操作。上述入口函数在 Mbuf 头部分配操作失败时将失败返回。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-mount"> + <title><function>&mac.mpo;_init_mount_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_mount_label</function></funcdef> + + <paramdef>struct label + *<parameter>mntlabel</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <!-- XXX: Wording on label descriptions. --> + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>mntlabel</parameter></entry> + <entry>将被初始化的mount 结构策略标记</entry> + </row> + + <row> + <entry><parameter>fslabel</parameter></entry> + <entry>将被初始化的文件系统策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近实例化的 mount 点初始化标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-mount-fs-label"> + <title><function>&mac.mpo;_init_mount_fs_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_mount_fs_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个新近加载的文件系统初始化标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-pipe-label"> + <title><function>&mac.mpo;_init_pipe_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_pipe_label</function></funcdef> + + <paramdef>struct + label*<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被填写的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个刚刚实例化的管道初始化安全标记。可以睡眠。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-socket"> + <title><function>&mac.mpo;_init_socket_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_socket_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + <paramdef>int <parameter>flag</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的新标记</entry> + </row> + + <row> + <entry><parameter>flag</parameter></entry> + <entry>&man.malloc.9; flags</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个刚刚实例化的套接字初始化安全标记。其中的 <parameter>flag</parameter> 域的值必须被指定为 + <symbol>M_WAITOK</symbol>和<symbol>M_NOWAIT</symbol>之一,以避免在该初始化程中使用可能睡眠的&man.malloc.9; 。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-socket-peer-label"> + <title><function>&mac.mpo;_init_socket_peer_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_socket_peer_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + <paramdef>int <parameter>flag</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的新标记</entry> + </row> + + <row> + <entry><parameter>flag</parameter></entry> + <entry>&man.malloc.9; flags</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为刚刚实例化的套接字对等体进行标记的初始化。其中的 <parameter>flag</parameter> 域的值必须被指定为 + <symbol>M_WAITOK</symbol> 和 <symbol>M_NOWAIT</symbol> 之一,以避免在该初始化程中使用可能睡眠的 + &man.malloc.9;。</para> + </sect3> + + <sect3 xml:id="mac-mpo-init-proc-label"> + <title><function>&mac.mpo;_init_proc_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_proc_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的新标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个刚刚实例化的进程初始化安全标记。可以睡眠。</para> + </sect3> + + + <sect3 xml:id="mac-mpo-init-vnode"> + <title><function>&mac.mpo;_init_vnode_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_init_vnode_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被初始化的新标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为一个刚刚实例化的 vnode 初始化安全标记。可以睡眠。</para> + </sect3> + <sect3 xml:id="mac-mpo-destroy-bpfdesc"> + <title><function>&mac.mpo;_destroy_bpfdesc_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_bpfdesc_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>bpfdesc 标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁一个 BPF 描述子上的标记。在该入口函数中,策略应当释放所有在内部分配与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-cred"> + <title><function>&mac.mpo;_destroy_cred_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_cred_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁一个信任状上的标记。在该入口函数中,策略应当释放所有在内部分配的与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + + <sect3 xml:id="mac-mpo-destroy-devfsdirent"> + <title><function>&mac.mpo;_destroy_devfsdirent_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_devfsdirent_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁一个 devfs 表项上的标记。在该入口函数中,策略应当释放所有在内部分配的与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-ifnet-label"> + <title><function>&mac.mpo;_destroy_ifnet_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_ifnet_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个已删除接口相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-ipq-label"> + <title><function>&mac.mpo;_destroy_ipq_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_ipq_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个 IP 分片队列相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-mbuf-label"> + <title><function>&mac.mpo;_destroy_mbuf_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_mbuf_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个 Mbuf 相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-mount-label"> + <title><function>&mac.mpo;_destroy_mount_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_mount_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的 Mount 点标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个 mount 点相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 <parameter>mntlabel</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-mount"> + <title><function>&mac.mpo;_destroy_mount_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_mount_label</function></funcdef> + + <paramdef>struct label + *<parameter>mntlabel</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>mntlabel</parameter></entry> + <entry>将被销毁的 Mount 点标记</entry> + </row> + + <row> + <entry><parameter>fslabel</parameter></entry> + <entry>File system label being destroyed></entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个 mount 点相关联的标记。在该入口函数中,策略应当释放所有在内部分配的,与 <parameter>mntlabel</parameter> + 和<parameter>fslabel</parameter> 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-socket"> + <title><function>&mac.mpo;_destroy_socket_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_socket_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>将被销毁的套接字标记</entry> + </row> + + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个套接字相关联的标记。在该入口函数中,策略应当释放所有在内部分配的,与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-socket-peer-label"> + <title><function>&mac.mpo;_destroy_socket_peer_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_socket_peer_label</function></funcdef> + + <paramdef>struct label + *<parameter>peerlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>peerlabel</parameter></entry> + <entry>将被销毁的套接字对等实体标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁与一个套接字相关联的对等实体标记。在该入口函数中,策略应当释放所有在内部分配的,与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-pipe-label"> + <title><function>&mac.mpo;_destroy_pipe_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_pipe_label</function></funcdef> + + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>管道标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁一个管道的标记。在该入口函数中,策略应当释放所有在内部分配的,与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-proc-label"> + <title><function>&mac.mpo;_destroy_proc_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_proc_label</function></funcdef> + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>进程标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁一个进程的标记。在该入口函数中,策略应当释放所有在内部分配的,与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-destroy-vnode-label"> + <title><function>&mac.mpo;_destroy_vnode_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_destroy_vnode_label</function></funcdef> + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>label</parameter></entry> + <entry>进程标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>销毁一个 vnode 的标记。在该入口函数中,策略应当释放所有在内部分配的,与 <parameter>label</parameter> + 相关联的存储空间,以便销毁该标记。</para> + </sect3> + + <sect3 xml:id="mac-mpo-copy-mbuf-label"> + <title><function>&mac.mpo;_copy_mbuf_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_copy_mbuf_label</function></funcdef> + + <paramdef>struct label + *<parameter>src</parameter></paramdef> + <paramdef>struct label + *<parameter>dest</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>src</parameter></entry> + <entry>源标记</entry> + </row> + + <row> + <entry><parameter>dest</parameter></entry> + <entry>目标标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>将 <parameter>src</parameter> 中的标记信息拷贝到 <parameter>dest</parameter>中。</para> + </sect3> + + <sect3 xml:id="mac-mpo-copy-pipe-label"> + <title><function>&mac.mpo;_copy_pipe_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_copy_pipe_label</function></funcdef> + + <paramdef>struct label + *<parameter>src</parameter></paramdef> + <paramdef>struct label + *<parameter>dest</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>src</parameter></entry> + <entry>源标记</entry> + </row> + + <row> + <entry><parameter>dest</parameter></entry> + <entry>目标标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>将 <parameter>src</parameter> 中的标记信息拷贝至 <parameter>dest</parameter>。</para> + </sect3> + + <sect3 xml:id="mac-mpo-copy-vnode-label"> + <title><function>&mac.mpo;_copy_vnode_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_copy_vnode_label</function></funcdef> + + <paramdef>struct label + *<parameter>src</parameter></paramdef> + <paramdef>struct label + *<parameter>dest</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>src</parameter></entry> + <entry>源标记</entry> + </row> + + <row> + <entry><parameter>dest</parameter></entry> + <entry>目标标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>将 <parameter>src</parameter> 中的标记信息拷贝至 <parameter>dest</parameter>。</para> + </sect3> + + <sect3 xml:id="mac-mpo-externalize-cred-label"> + <title><function>&mac.mpo;_externalize_cred_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_externalize_cred_label</function></funcdef> + + &mac.externalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.externalize.tbody; + </tgroup> + </informaltable> + + &mac.externalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-externalize-ifnet-label"> + <title><function>&mac.mpo;_externalize_ifnet_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_externalize_ifnet_label</function></funcdef> + + &mac.externalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.externalize.tbody; + </tgroup> + </informaltable> + + &mac.externalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-externalize-pipe-label"> + <title><function>&mac.mpo;_externalize_pipe_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_externalize_pipe_label</function></funcdef> + + &mac.externalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.externalize.tbody; + </tgroup> + </informaltable> + + &mac.externalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-externalize-socket-label"> + <title><function>&mac.mpo;_externalize_socket_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_externalize_socket_label</function></funcdef> + + &mac.externalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.externalize.tbody; + </tgroup> + </informaltable> + + &mac.externalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-externalize-socket-peer-label"> + <title><function>&mac.mpo;_externalize_socket_peer_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_externalize_socket_peer_label</function></funcdef> + + &mac.externalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.externalize.tbody; + </tgroup> + </informaltable> + + &mac.externalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-externalize-vnode-label"> + <title><function>&mac.mpo;_externalize_vnode_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_externalize_vnode_label</function></funcdef> + + &mac.externalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.externalize.tbody; + </tgroup> + </informaltable> + + &mac.externalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-internalize-cred-label"> + <title><function>&mac.mpo;_internalize_cred_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_internalize_cred_label</function></funcdef> + + &mac.internalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.internalize.tbody; + </tgroup> + </informaltable> + + &mac.internalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-internalize-ifnet-label"> + <title><function>&mac.mpo;_internalize_ifnet_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_internalize_ifnet_label</function></funcdef> + + &mac.internalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.internalize.tbody; + </tgroup> + </informaltable> + + &mac.internalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-internalize-pipe-label"> + <title><function>&mac.mpo;_internalize_pipe_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_internalize_pipe_label</function></funcdef> + + &mac.internalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.internalize.tbody; + </tgroup> + </informaltable> + + &mac.internalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-internalize-socket-label"> + <title><function>&mac.mpo;_internalize_socket_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_internalize_socket_label</function></funcdef> + + &mac.internalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.internalize.tbody; + </tgroup> + </informaltable> + + &mac.internalize.para; + </sect3> + + <sect3 xml:id="mac-mpo-internalize-vnode-label"> + <title><function>&mac.mpo;_internalize_vnode_label</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_internalize_vnode_label</function></funcdef> + + &mac.internalize.paramdefs; + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + &mac.internalize.tbody; + </tgroup> + </informaltable> + + &mac.internalize.para; + </sect3> + </sect2> + + <sect2 xml:id="mac-label-events"> + <title>标记事件</title> + + <para>策略模块使用MAC 框架提供的“标记事件”类入口函数,对内核对象的标记进行操作。策略模块将感兴趣的被标记内核对象的相关生命周期事件 + 注册在恰当的入口点上。对象的初始化、创建和销毁事件均提供了钩子点。在某些对象上还可以实现重新标记,即,允许用户进程改变对象上的标记值。 + 对某些对象可以实现其特定的对象事件,比如与 IP 重组相关的标记事件。一个典型的被标记对象在其生命周期中将拥有下列入口函数:</para> + + <programlisting>标记初始化 o +(对象相关的等待) \ +标记创建 o + \ +重新标记事件, o--<--. +各种对象相关的, | | +访问控制事件 ~-->--o + \ +标记销毁 o</programlisting> + + <para>使用标记初始化入口函数,策略可以以一种统一的、与对象使用环境无关的方式设置标记的初始值。 + 分配给一个策略的缺省 slot 值为0,这样不使用标记的策略可能并不需要执行专门的初始化操作。</para> + + <para>标记的创建事件发生在将一个内核数据结构同一个真实的内核对象相关联(内核对象实例化)的时刻。 + 例如,在真正被使用之前,在一个缓冲池内已分配的 mbuf 数据结构,将保持为“未使用”状态。 + 因此,mbuf 的分配操作将导致针对该 mbuf 的标记初始化操作,而 mbuf 的创建操作则被推迟到该 mbuf 真正与一个数据报相关联的时刻。 + 通常,调用者将会提供创建事件的上下文,包括创建环境、创建过程中涉及的其他对象的标记等。例如,当一个套接字创建一个 mbuf 时, + 除了新创建的 mbuf 及其标记之外,作为创建者的套接字与其标记也被提交给策略检查。 + 不提倡在创建对象时就为其分配内存的原因有两个:创建操作可能发生在对性能有严格要求的内核接口上; + 而且,因为创建调用不允许失败,所以无法报告内存分配失败。</para> + + <para>对象特有的事件一般不会引发其他的标记事件,但是在对象上下文发生改变时,策略使用它们可以对相关标记进行修改或更新操作。 + 例如,在<symbol>MAC_UPDATE_IPQ</symbol> 入口函数之内,某个 IP 分片重组队列的标记可能会因为队列中接收了新的 mbuf 而被更新。</para> + + <para>访问控制事件将在后续章节中详细讨论。</para> + + <para>策略通过执行标记销毁操作,释放为其分配的存储空间或维护的状态,之后内核才可以重用或者释放对象的内核数据结构。</para> + + <para>除了与特定内核对象绑定的普通标记之外,还有一种额外的标记类型:临时标记。这些标记用于存放由用户进程提交的更新信息。 + 它们的初始化和销毁操作与其他标记一样,只是创建事件,<symbol>MAC_INTERNALIZE</symbol>,略有不同: + 该函数接受用户提交的标记,负责将其转化为内核表示形式。 </para> + + <sect3 xml:id="mac-fs-label-event-ops"> + <title>文件系统对象标记事件操作</title> + + <sect4 xml:id="mac-mpo-associate-vnode-devfs"> + <title><function>&mac.mpo;_associate_vnode_devfs</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_associate_vnode_devfs</function></funcdef> + + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + <paramdef>struct devfs_dirent + *<parameter>de</parameter></paramdef> + <paramdef>struct label + *<parameter>delabel</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>mp</parameter></entry> + <entry>Devfs 挂载点</entry> + </row> + + <row> + <entry><parameter>fslabel</parameter></entry> + <entry>Devfs 文件系统标记 + (<varname>mp->mnt_fslabel</varname>)</entry> + </row> + + <row> + <entry><parameter>de</parameter></entry> + <entry>Devfs 目录项</entry> + </row> + + <row> + <entry><parameter>delabel</parameter></entry> + <entry>与 <parameter>de</parameter> 相关联的策略标记</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>与 <parameter>de</parameter> 相关联的 vnode</entry> + </row> + + <row> + <entry><parameter>vlabel</parameter></entry> + <entry>与 <parameter>vp</parameter> 相关联的策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据参数 <parameter>de</parameter> 传入的 devfs 目录项及其标记信息,为一个新近创建的 + devfs vnode 填充标记(<parameter>vlabel</parameter>)。</para> + </sect4> + + <sect4 xml:id="mac-mpo-associate-vnode-extattr"> + <title><function>&mac.mpo;_associate_vnode_extattr</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_associate_vnode_extattr</function></funcdef> + + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>mp</parameter></entry> + <entry>文件系统挂载点</entry> + </row> + + <row> + <entry><parameter>fslabel</parameter></entry> + <entry>文件系统标记</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>将被标记的 vnode</entry> + </row> + + <row> + <entry><parameter>vlabel</parameter></entry> + <entry>与 <parameter>vp</parameter> 相关联的策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>从文件系统扩展属性中读取 <parameter>vp</parameter> 的标记。成功,返回 <literal>0</literal>。 + 不成功,则在 <varname>errno</varname> 指定的相应的错误编码。 + 如果文件系统不支持扩展属性的读取操作,则可以考虑将 <parameter>fslabel</parameter> 拷贝至 <parameter>vlabel</parameter>。 + </para> + </sect4> + + <sect4 xml:id="mac-mpo-associate-vnode-singlelabel"> + <title><function>&mac.mpo;_associate_vnode_singlelabel</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_associate_vnode_singlelabel</function></funcdef> + + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>mp</parameter></entry> + <entry>文件系统挂载点</entry> + </row> + + <row> + <entry><parameter>fslabel</parameter></entry> + <entry>文件系统标记</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>将被标记的 vnode</entry> + </row> + + <row> + <entry><parameter>vlabel</parameter></entry> + <entry>与 <parameter>vp</parameter> 相关联的策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>在非多重标记文件系统上,使用该入口函数,根据文件系统标记,<parameter>fslabel</parameter>, + 为 <parameter>vp</parameter> 设置策略标记。</para> + </sect4> + + + <sect4 xml:id="mac-mpo-create-devfs-device"> + <title><function>&mac.mpo;_create_devfs_device</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_devfs_device</function></funcdef> + + <paramdef>dev_t <parameter>dev</parameter></paramdef> + <paramdef>struct devfs_dirent + *<parameter>devfs_dirent</parameter></paramdef> + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>dev</parameter></entry> + <entry><parameter>devfs_dirent</parameter> 对应的设备</entry> + </row> + + <row> + <entry><parameter>devfs_dirent</parameter></entry> + <entry>将被标记的 Devfs 目录项</entry> + </row> + + <row> + <entry><parameter>label</parameter></entry> + <entry>将被填写的 <parameter>devfs_dirent</parameter> 标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为传入设备新建的 devfs_dirent 填写标记。该函数将在设备文件系统加载、重构或添加新设备时被调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-devfs-directory"> + <title><function>&mac.mpo;_create_devfs_directory</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_devfs_directory</function></funcdef> + + <paramdef>char *<parameter>dirname</parameter></paramdef> + <paramdef>int <parameter>dirnamelen</parameter></paramdef> + <paramdef>struct devfs_dirent + *<parameter>devfs_dirent</parameter></paramdef> + <paramdef>struct label + *<parameter>label</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>dirname</parameter></entry> + <entry>新建目录的名字</entry> + </row> + + <row> + <entry><parameter>namelen</parameter></entry> + <entry>字符串 <parameter>dirname</parameter> 的长度</entry> + </row> + + <row> + <entry><parameter>devfs_dirent</parameter></entry> + <entry>新建目录在 Devfs 中对应的目录项</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为传入目录参数的新建 devfs_dirent 填写标记。该函数将在加载、重构设备文件系统,或者添加一个需要指定目录结构的新设备时被调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-devfs-symlink"> + <title><function>&mac.mpo;_create_devfs_symlink</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_devfs_symlink</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct devfs_dirent + *<parameter>dd</parameter></paramdef> + <paramdef>struct label + *<parameter>ddlabel</parameter></paramdef> + <paramdef>struct devfs_dirent + *<parameter>de</parameter></paramdef> + <paramdef>struct label + *<parameter>delabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>mp</parameter></entry> + <entry>devfs 挂载点</entry> + </row> + + <row> + <entry><parameter>dd</parameter></entry> + <entry>链接目标</entry> + </row> + + <row> + <entry><parameter>ddlabel</parameter></entry> + <entry>与 <parameter>dd</parameter> 相关联的标记</entry> + </row> + + <row> + <entry><parameter>de</parameter></entry> + <entry>符号链接项</entry> + </row> + + <row> + <entry><parameter>delabel</parameter></entry> + <entry>与 <parameter>de</parameter> 相关联的策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为新近创建的 &man.devfs.5; 符号链接项填写标记(<parameter>delabel</parameter>)。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-vnode-extattr"> + <title><function>&mac.mpo;_create_vnode_extattr</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_create_vnode_extattr</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + <paramdef>struct vnode + *<parameter>dvp</parameter></paramdef> + <paramdef>struct label + *<parameter>dlabel</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vlabel</parameter></paramdef> + <paramdef>struct componentname + *<parameter>cnp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>mount</parameter></entry> + <entry>文件系统挂载点</entry> + </row> + + <row> + <entry><parameter>label</parameter></entry> + <entry>文件系统标记</entry> + </row> + + <row> + <entry><parameter>dvp</parameter></entry> + <entry>父目录 vnode</entry> + </row> + + <row> + <entry><parameter>dlabel</parameter></entry> + <entry>与 <parameter>dvp</parameter> 相关联的策略标记</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>新创建的 vnode</entry> + </row> + + <row> + <entry><parameter>vlabel</parameter></entry> + <entry>与 <parameter>vp</parameter> 相关联的策略标记</entry> + </row> + + <row> + <entry><parameter>cnp</parameter></entry> + <entry><parameter>vp</parameter>中的子域名字</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>将 <parameter>vp</parameter> 的标记写入文件扩展属性。成功,将标记填入 <parameter>vlabel</parameter>, + 并返回 <returnvalue>0</returnvalue>。否则,返回对应的错误编码。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-mount"> + <title><function>&mac.mpo;_create_mount</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_mount</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct label + *<parameter>mnt</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>mp</parameter></entry> + <entry>客体;将被挂载的文件系统</entry> + </row> + + <row> + <entry><parameter>mntlabel</parameter></entry> + <entry>将被填写的 <parameter>mp</parameter> 的策略标记</entry> + </row> + + <row> + <entry><parameter>fslabel</parameter></entry> + <entry>将被挂载到 <parameter>mp</parameter> 的文件系统的策略标记。</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为传入的主体信任状所创建的挂载点填写标记。该函数将在文件系统挂载时被调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-root-mount"> + <title><function>&mac.mpo;_create_root_mount</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_root_mount</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct mount + *<parameter>mp</parameter></paramdef> + <paramdef>struct label + *<parameter>mntlabel</parameter></paramdef> + <paramdef>struct label + *<parameter>fslabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry namest="first" nameend="last">见 <xref linkend="mac-mpo-create-mount"/>.</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为传入的主体信任状所创建的挂载点填写标记。该函数将在挂载根文件系统时,&mac.mpo;_create_mount; 之后被调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-relabel-vnode"> + <title><function>&mac.mpo;_relabel_vnode</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_relabel_vnode</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vnodelabel</parameter></paramdef> + <paramdef>struct label + *<parameter>newlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>将被重新标记的 vnode</entry> + </row> + + <row> + <entry><parameter>vnodelabel</parameter></entry> + <entry><parameter>vp</parameter> 现有的策略标记</entry> + </row> + + <row> + <entry><parameter>newlabel</parameter></entry> + <entry>将取代<parameter>vnodelabel</parameter>的新(可能只是部分)标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据传入的新标记和主体信任状,更新参数 vnode 的标记。</para> + </sect4> + + <sect4 xml:id="mac-mpo-setlabel-vnode-extattr"> + <title><function>&mac.mpo;_setlabel_vnode_extattr</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>int + <function>&mac.mpo;_setlabel_vnode_extattr</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vlabel</parameter></paramdef> + <paramdef>struct label + *<parameter>intlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>写出标记所对应的 vnode</entry> + </row> + + <row> + <entry><parameter>vlabel</parameter></entry> + <entry><parameter>vp</parameter>的策略标记</entry> + </row> + + <row> + <entry><parameter>intlabel</parameter></entry> + <entry>将被写入磁盘的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>将参数 <parameter>intlabel</parameter> 给出的标记信息写入指定 vnode 的扩展属性。 + 该函数被 <function>vop_stdcreatevnode_ea</function> 所调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-update-devfsdirent"> + <title><function>&mac.mpo;_update_devfsdirent</function></title> + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_update_devfsdirent</function></funcdef> + + <paramdef>struct devfs_dirent + *<parameter>devfs_dirent</parameter></paramdef> + <paramdef>struct label + *<parameter>direntlabel</parameter></paramdef> + <paramdef>struct vnode + *<parameter>vp</parameter></paramdef> + <paramdef>struct label + *<parameter>vnodelabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>devfs_dirent</parameter></entry> + <entry>客体;devfs 目录项</entry> + </row> + + <row> + <entry><parameter>direntlabel</parameter></entry> + <entry>将被更新的<parameter>devfs_dirent</parameter>的策略标记</entry> + </row> + + <row> + <entry><parameter>vp</parameter></entry> + <entry>父 vnode</entry> + <entry>已锁定</entry> + </row> + + <row> + <entry><parameter>vnodelabel</parameter></entry> + <entry><parameter>vp</parameter>的策略标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据所传入的 devfs vnode 标记,对 <parameter>devfs_dirent</parameter> 的标记进行更新。 + 重新标记一个 devfs vnode 的操作成功之后,将调用该函数来确认标记的改变,如此,即使相应的 vnode 数据结构被内核回收重用, + 也不会丢失标记的新状态。另外,在 devfs 中新建一个符号链接时,紧接着<function>mac_vnode_create_from_vnode</function>, + 也将调用该函数,对 vnode 标记进行初始化操作。</para> + </sect4> + </sect3> + + <sect3 xml:id="mac-ipc-label-ops"> + <title>IPC 对象标记事件操作</title> + + + <sect4 xml:id="mac-mpo-create-mbuf-from-socket"> + <title><function>&mac.mpo;_create_mbuf_from_socket</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_mbuf_from_socket</function></funcdef> + + <paramdef>struct socket + *<parameter>so</parameter></paramdef> + <paramdef>struct label + *<parameter>socketlabel</parameter></paramdef> + <paramdef>struct mbuf *<parameter>m</parameter></paramdef> + <paramdef>struct label + *<parameter>mbuflabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>socket</parameter></entry> + <entry>套接字</entry> + <entry>套接字锁定 WIP</entry> + </row> + + <row> + <entry><parameter>socketlabel</parameter></entry> + <entry><parameter>socket</parameter> 的策略标记 </entry> + </row> + + <row> + <entry><parameter>m</parameter></entry> + <entry>客体;mbuf</entry> + </row> + + <row> + <entry><parameter>mbuflabel</parameter></entry> + <entry>将被填写的 <parameter>m</parameter> 的策略标记 </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据传入的套接字标记为新创建的mbuf头部设置标记。 + 每当套接字产生一个新的数据报或者消息,并将其存储在参数 mbuf 中时,将调用该函数。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-pipe"> + <title><function>&mac.mpo;_create_pipe</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_pipe</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct pipe + *<parameter>pipe</parameter></paramdef> + <paramdef>struct label + *<parameter>pipelabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>pipe</parameter></entry> + <entry>管道</entry> + </row> + + <row> + <entry><parameter>pipelabel</parameter></entry> + <entry><parameter>pipe</parameter> 的策略标记 </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据传入的主体信任状参数,设置新建管道的标记。每当一个新管道被创建,该函数将被调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-socket"> + <title><function>&mac.mpo;_create_socket</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_socket</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct socket + *<parameter>so</parameter></paramdef> + <paramdef>struct label + *<parameter>socketlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + <entry>不可改变</entry> + </row> + + <row> + <entry><parameter>so</parameter></entry> + <entry>客体;将被标记的套接字</entry> + </row> + + <row> + <entry><parameter>socketlabel</parameter></entry> + <entry>将被填写的 <parameter>so</parameter> 的标记</entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据传入的主体信任状参数,设置新建套接字的标记。每当新建一个套接字,该函数将被调用。</para> + </sect4> + + <sect4 xml:id="mac-mpo-create-socket-from-socket"> + <title><function>&mac.mpo;_create_socket_from_socket</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_create_socket_from_socket</function></funcdef> + + <paramdef>struct socket + *<parameter>oldsocket</parameter></paramdef> + <paramdef>struct label + *<parameter>oldsocketlabel</parameter></paramdef> + <paramdef>struct socket + *<parameter>newsocket</parameter></paramdef> + <paramdef>struct label + *<parameter>newsocketlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>oldsocket</parameter></entry> + <entry>监听套接字</entry> + </row> + + <row> + <entry><parameter>oldsocketlabel</parameter></entry> + <entry><parameter>oldsocket</parameter> 的策略标记 </entry> + </row> + + <row> + <entry><parameter>newsocket</parameter></entry> + <entry>新建套接字</entry> + </row> + + <row> + <entry><parameter>newsocketlabel</parameter></entry> + <entry><parameter>newsocket</parameter> 的策略标记 </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据 &man.listen.2; 套接字 <parameter>oldsocket</parameter>, + 为新建 &man.accept.2; 的套接字 <parameter>newsocket</parameter>,设置标记。</para> + </sect4> + + <sect4 xml:id="mac-mpo-relabel-pipe"> + <title><function>&mac.mpo;_relabel_pipe</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_relabel_pipe</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct pipe + *<parameter>pipe</parameter></paramdef> + <paramdef>struct label + *<parameter>oldlabel</parameter></paramdef> + <paramdef>struct label + *<parameter>newlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + </row> + + <row> + <entry><parameter>pipe</parameter></entry> + <entry>管道</entry> + </row> + + <row> + <entry><parameter>oldlabel</parameter></entry> + <entry><parameter>pipe</parameter> 的当前策略标记 </entry> + </row> + + <row> + <entry><parameter>newlabel</parameter></entry> + <entry>将为<parameter>pipe</parameter> 设置的新的策略标记 </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>为<parameter>pipe</parameter>设置新标记<parameter>newlabel</parameter>。</para> + </sect4> + + <sect4 xml:id="mac-mpo-relabel-socket"> + <title><function>&mac.mpo;_relabel_socket</function></title> + + <funcsynopsis> + <funcprototype> + <funcdef>void + <function>&mac.mpo;_relabel_socket</function></funcdef> + + <paramdef>struct ucred + *<parameter>cred</parameter></paramdef> + <paramdef>struct socket + *<parameter>so</parameter></paramdef> + <paramdef>struct label + *<parameter>oldlabel</parameter></paramdef> + <paramdef>struct label + *<parameter>newlabel</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <informaltable frame="none" pgwide="1"> + <tgroup cols="3"> + &mac.thead; + + <tbody> + <row> + <entry><parameter>cred</parameter></entry> + <entry>主体信任状</entry> + <entry>不可改变</entry> + </row> + + <row> + <entry><parameter>so</parameter></entry> + <entry>客体;套接字</entry> + </row> + + <row> + <entry><parameter>oldlabel</parameter></entry> + <entry><parameter>so</parameter> 的当前标记 </entry> + </row> + + <row> + <entry><parameter>newlabel</parameter></entry> + <entry>将为<parameter>socket</parameter> 设置的新标记 </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para>根据传入的标记参数,对套接字的当前标记进行更新。</para> + </sect4> |