仅本页所有页面
由 GitBook 提供支持
1 / 39

文章

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

编辑日志

  • 2025.5.6 重译

  • 2024.7.3-2024.7.5 机器翻译完成。

参与 FreeBSD

摘要

本文介绍了个人和组织各种参与 FreeBSD 项目的方式。

所以你想参与 FreeBSD 吗?那太好了!FreeBSD 依靠 用户的贡献才能生存。你的参与不仅会得到感谢,它们对于 FreeBSD 持续发展亦至关重要。

来自全球的众多贡献者,涉及各个年龄段和技术领域,都在开发 FreeBSD。要干的活总比能够提供帮助的人更多,一切帮助都将不胜感激。

作为志愿者,你所做的事仅限于你想做的。不过,我们的确希望你能够意识到,FreeBSD 社区中的其他成员对你的期望。在决定是否志愿参与之前,你可能需要考虑这一点。

FreeBSD 项目负责一项完整的操作系统环境,而不单单是一款内核和一些零散的工具。因此,我们的 TODO 列表涵盖了各种各样的任务:从文档编写、Beta 测试和展示,到系统安装器和高度专业化的内核开发。几乎所有技能水平的人,几乎在任何领域,都能为 FreeBSD 做出贡献。

也建议从事与 FreeBSD 相关的商业实体与我们联系。你是否需要一款特殊的扩展来使你的产品正常工作?只要你的请求不过于过分,我们会乐于接受。你是否正在开发一款增值产品?请告知我们!我们可能能够在某些方面合作。自由软件世界正在挑战许多现有的关于软件开发、销售和维护的假设,我们鼓励你至少重新审视它。

1. 需要什么

以下任务和子项目的列表代表了各种 TODO 列表和用户请求的结合体。

1.1. 持续的非程序员任务

许多参与 FreeBSD 的人并不是程序员。项目包括文档编写者、网页设计师和支持人员。这些人所需要做的只是投入时间并愿意学习。

  1. 定期阅读 FAQ 和手册。如果发现任何解释不清、含糊不清、过时或错误的内容,请告诉我们。更好的是,给我们提供修正(AsciiDoc 并不难学习,但也可以提交纯文本格式)。

1.2. 持续的程序员任务

这里列出的大多数任务可能需要大量的时间投入、对 FreeBSD 内核的深入了解,或者两者兼备。不过,也有很多适合“周末黑客”的有用任务。

  1. 如果你使用 FreeBSD-CURRENT 并且有良好的互联网连接,可以尝试安装 current.FreeBSD.org 上每天生成的完整版本并报告安装过程中遇到的一切失败。

  2. 如果你知道某个 bug 修复已成功应用到 -CURRENT,但经过一段合理时间后尚未合并到 -STABLE,请礼貌提醒提交者。

  3. 将第三方的软件移到源代码树中的 src/contrib 目录。

  4. 确保 src/contrib 中的代码是最新的。

  5. 修复那些使用了弃用方法(例如使用 gets() 或包含 malloc.h)的 Port 警告。

  6. 如果你参与了任何 Port,并且你做了 FreeBSD 特定的修改,发送你的补丁给原作者(这将使你在他们发布下一个版本时更加方便)。

  7. 获取正式标准的副本,如 POSIX®。比较 FreeBSD 的行为与标准要求的行为。如果行为不同,特别是在规范的微妙或晦涩角落,提交问题报告。如果可能,想办法修复它,并在问题报告中附上补丁。如果你认为标准是错的,请向标准化机构提出该问题。

  8. 为这个列表建议更多的任务!

1.3. 处理 PR 数据库

从那些尚未分配给其他人的 PR 开始。如果 PR 已分配给其他人,但看起来是你能处理的任务,可以给被分配的人发邮件,询问是否可以继续处理—他们可能已经准备好了补丁,或者有其他想法可以和你讨论。

1.4. 持续的 Port 任务

Port 是一项持续进行中的工作。我们希望为用户提供易于使用、始终保持最新的高质量第三方软件库。我们需要人们捐献一些时间和精力来帮助我们实现这一目标。

所有人都可以参与,并且有许多不同的方式来参与。参与 Port 个是回馈项目的极好的方式。无论你是想要充当持续的角色,还是某个有趣的挑战,我们都很高兴能获得你的帮助!

你可以通过以下几种简单方式为保持 Port 树的更新和良好运作做出贡献:

1.5. 从 Idea 页面选择一个项目

2. 如何参与

对系统的参与通常可以归入以下五类之一或多个:

2.1. Bug 报告和一般评论

  • 补丁已经准备好或几乎准备好提交。提交者应该能够在小于 10 分钟额外工作的情况下提交此补丁。

  • 它通过了所有 GitHub CI 作业。

  • 你可以快速响应反馈。

  • 补丁修改的文件数不超过 10 个,且修改行数少于 200 行。大于这个范围的修改可能是可以接受的,或者你可能会被要求将补丁分为多个更小的 RP。

  • 所有提交应包含你的名字和有效的电子邮件地址,并按照你希望在 FreeBSD 仓库中看到的方式标记为作者。不能使用虚假的 GitHub 地址。

  • Pull request 在审查过程中不应更改范围。如果审查提出的修改会扩展范围,请创建一个独立的 RP。

  • 修复提交应与修复的提交合并。你的分支中的每个提交都应该适合 FreeBSD 仓库。

更新 RP 时,请使用强制推送进行 rebase,而不是使用合并提交。更复杂的修改可以作为 RP 提交,但如果它们太大、难以管理、长期无进展、需要进一步讨论或需要大量修订,它们可能会被关闭。请避免创建大而广泛的清理补丁:它们太大且缺乏良好审查所需的重点。错误的补丁可能会被重定向到更合适的讨论场所。

文档团队也接受通过 GitHub 提交的 RP,但尚未为其建立政策。

提交报告后,你应该会收到确认信息和一个追踪号码。请保存此追踪号码,以便更新我们有关问题的详细信息。

2.2. 文档更改

2.3. 修改现有源代码

提交补丁时首选使用统一输出格式,由 diff -u 生成。

% diff -u oldfile newfile

或者

% diff -u -r -N olddir newdir

这将生成给定源文件或目录层次结构的统一差异。

如果你觉得合适(例如,你添加、删除或重命名了文件),可以将你的更改打包成一个 tar 文件。

2.4. 新代码和重要的增量包

在进行大量工作或向 FreeBSD 添加重要新功能的情况下,几乎总是需要将更改作为 tar 文件提交,或上传到 Web 或 FTP 站点供其他人访问。如果你没有 Web 或 FTP 站点的访问权限,可以在合适的 FreeBSD 邮件列表上请求某人来为你托管这些更改。

2.5. 捐赠资金和硬件

我们非常乐意接受捐赠,以推动 FreeBSD 项目的发展,在这样的志愿者项目中,少量的捐赠可以起到很大的作用!硬件捐赠对于扩展我们支持的外设列表也非常重要,因为我们通常没有足够的资金购买这些设备。

2.5.1. 捐赠资金

捐赠可以通过支票方式发送到:

The FreeBSD Foundation
3980 Broadway Street
STE #103-107
Boulder CO 80304
USA

2.5.2. 捐赠硬件

3. 参与 Ports

3.1. 接管未维护的 Ports

3.1.1. 选择未维护的 Port

接管未维护的 Port 是很好的参与方式。未维护的 Port 只有在有人自愿为其工作时才会更新和修复。目前有大量未维护的 Port。建议从接管一个你常用的 Port 开始。

一些 Port 由于其依赖关系和二级 Port 关系,会影响大量其他 Port。通常,我们希望在维护此类 Port 之前,维护者能有一定的经验。

你可以通过查看名为 INDEX 的主 Port 索引,来了解某 Port 是否有依赖关系或二级 Port。(该文件的名称根据 FreeBSD 的版本不同而有所不同;例如,INDEX-13)。一些 Port 有条件依赖关系,这些依赖关系不会包含在默认的 INDEX 构建中。我们希望你通过查看其他 Port 的 Makefile 来识别这些 Port。

3.1.2. 如何接管 Port

你可以随时请求接管任何未维护的 Port。只需将 MAINTAINER 设置为你的电子邮件地址,并发送包含更改的 PR(问题报告)。如果该 Port 有构建错误或需要更新,你可能希望在同一个 PR 中包含其他更改。这将有所帮助,因为许多提交者不太愿意将维护权交给没有 FreeBSD 经验的人员。提交修复构建错误或更新 Port 的 PR 是建立经验记录的最佳途径。

将你的 PR 提交到产品 Ports & Packages。提交者将审查你的 PR,提交更改,并最终关闭 PR。有时这个过程可能需要一些时间(提交者也是志愿者 :))。

3.2. Port 维护者的挑战

本节将帮助你了解为什么 Port 需要维护,并概述 Port 维护者的责任。

3.2.1. 为什么 Port 需要维护

创建 Port 是一次性的任务。确保 Port 保持最新、能继续构建和运行则需要持续的维护工作。维护者是那些愿意花费部分时间来实现这些目标的人。

Port 需要维护的最主要原因是将最新的第三方软件带给 FreeBSD 社区。另一个挑战是随着 Ports 框架的发展,保持各个 Port 的正常运行。

作为维护者,你需要管理以下挑战:

  • 新软件版本和更新。 新版本和现有 Port 软件的更新不断发布,这些需要被纳入 Ports ,以提供最新的软件。

  • 依赖关系的变化。 如果你的 Port 的依赖关系发生了重要变化,可能需要更新 Port,以确保它能够继续正常工作。

  • 影响依赖 Port 的变化。 如果其他 Port 依赖于你维护的 Port,你对该 Port 的更改可能需要与其他维护者进行协调。

  • 与其他用户、维护者和开发者的互动。 成为一个维护者的一部分工作是承担支持角色。你不需要提供一般的支持(但如果你愿意,欢迎提供)。你应该提供的是 FreeBSD 特有问题的协调点。

  • 排查 Bug。 Port 可能会受到 FreeBSD 特有 Bug 的影响。当这些 Bug 被报告时,你需要进行调查、找到并修复它们。在它们进入 Ports 之前,彻底测试 Port,找出潜在问题,当然更好。

  • Port 基础设施和政策的变化。 有时,构建 Port 和包的系统会进行更新,或者可能会提出新的基础设施建议。你应该关注这些变化,以防你的 Port 受影响,并需要更新。

  • 基本系统的变化。 FreeBSD 正在不断开发中。软件、库、内核甚至政策的变化都可能导致 Port 需要相应调整。

3.2.2. 维护者的责任

3.2.2.1. 保持 Port 的更新

本节概述了保持 Port 更新的流程。

  1. 关注更新 监控上游供应商的新版本、更新和安全修复。公告邮件列表或新闻网页在这方面很有帮助。有时用户会联系你,询问你的 Port 什么时候更新。如果你忙于其他事情或因任何原因无法及时更新,问问他们是否愿意通过提交更新来帮忙。

    你也可能会收到来自 FreeBSD Ports Version Check 的自动电子邮件,通知你 Port 的 distfile 有更新版本。更多有关该系统的信息(包括如何停止未来的邮件)将在消息中提供。

  2. 合并更改 当更新发布时,将这些更改合并到 Port 中。你需要能够生成原始 Port 与更新 Port 之间的补丁。

  3. 审查和测试 彻底审查和测试你的更改:

    • 在尽可能多的平台和架构上构建、安装并测试你的 Port。 Port 在一个分支或平台上可能正常工作,但在另一个平台上则会失败。

    • 检查打包列表是否是最新的。包括添加任何新文件和目录,删除未使用的条目。

  4. 注意

  5. 提供反馈 如果提交者发现你更改中的问题,他们很可能会将其反馈给你。迅速响应可以帮助更快地提交你的 PR,并在解决问题时保持良好的沟通。

  6. 最终 你的更改将被提交, Port 将得到更新。PR 会由提交者关闭。就这样!

3.2.2.2. 确保你的 Port 继续正确构建

本节讲述了如何发现和修复导致 Port 无法正确构建的问题。

FreeBSD 仅保证 Ports 在 -STABLE 分支上正常工作。理论上,你应该能够使用每个稳定分支的最新版本(因为 ABI 不应该变化),但如果你能运行该分支,那就更好了。

由于大多数 FreeBSD 安装运行在 PC 兼容机器上(即 i386 架构),我们期望你保持该架构上的 Port 工作。我们希望 Port 也能在 amd64 架构上原生运行。如果你没有这些机器,完全可以请求帮助。

注意

非 x86 机器的常见失败模式是原始程序员假设指针是 int 类型,或者使用了较宽松的旧版 gcc 编译器。越来越多的应用程序作者正在重构他们的代码以消除这些假设,但如果作者没有积极维护代码,你可能需要自己处理这些问题。

以下是确保你的 Port 能够成功构建所需执行的任务:

  1. 收集信息 只要意识到存在问题,收集有助于解决问题的信息。由 pkg-fallout 报告的构建错误会附带日志,你可以通过这些日志查看构建失败的位置。如果错误是由用户报告的,请要求他们提供可能帮助诊断问题的信息,例如:

    • 构建日志

    • 用于构建 Port 的命令和选项(包括在 /etc/make.conf 中设置的选项)

    • 他们的 Port 集合上次更新时间

    • 他们的 Port 树和 INDEX 上次更新时间

  2. 提交更改 与更新 Port 一样,你现在应该整合更改、进行审查和测试,提交你的更改到 PR 中,并在需要时提供反馈。

  3. 将补丁发送给上游作者 在某些情况下,你需要对 Port 做补丁,以使其在 FreeBSD 上运行。有些(但不是所有)上游作者会接受这些补丁并将其合并到下一版本中。如果是这样,这甚至可能帮助他们在其他 BSD 系统上的用户,避免重复劳动。请考虑将适用的补丁作为礼貌发送给作者。

3.2.2.3. 调查与 Port 相关的 bug 报告和 PR

本节讲述了如何发现并修复 bug。

FreeBSD 特有的 bug 通常是由于构建和运行环境的假设不适用于 FreeBSD 所引起的。你不太可能遇到这种问题,但它可能更为微妙且难以诊断。

确保你的 Port 继续按预期工作需要执行的任务如下:

  1. 你应在 14 天内回应 PR 和其他报告,但请尽量不要拖延这么长时间。尽早回应,即使只是告知你需要更多时间才能处理 PR。

    如果你 14 天后仍未回应,所有提交者都可以通过 maintainer-timeout 从你未回应的 PR 中提交更改。

  2. 收集信息 如果报告 bug 的人没有提供修复方案,你需要收集足够的信息,以便生成一个修复方案。

    如果 bug 是可重现的,你可以自己收集大部分所需的信息。如果无法重现,要求报告 bug 的人提供信息,例如:

    • 他们的操作步骤、预期的程序行为和实际行为的详细描述

    • 用于触发 bug 的输入数据副本

    • 核心转储

    • 堆栈跟踪

  3. 消除错误报告 一些 bug 报告可能是错误的。例如,用户可能仅仅是错误地使用了程序;或者他们安装的软件包已经过时,需要更新。有时,报告的 bug 并非 FreeBSD 特有。在这种情况下,将 bug 报告给上游开发者。如果问题是你可以修复的,你还可以给 Port 打补丁,以便在下一次上游发布前应用修复。

  4. 找到解决方案 与构建错误一样,你需要找出问题的解决方案。同样,如果遇到困难,记得寻求帮助!

  5. 提交或批准更改 与更新 Port 一样,你现在应该整合更改、进行审查和测试,并将你的更改提交到 PR 中(如果问题已经存在 PR,你可以发送后续消息)。如果其他用户已在 PR 中提交了更改,你也可以发送后续消息,告知你是否批准这些更改。

3.2.2.4. 提供支持

作为维护者的一部分,你需要提供支持——不仅是针对软件本身——而是针对 Port 和任何 FreeBSD 特有的奇特问题。用户可能会就问题、建议、问题和补丁联系你。大多数时候,他们的信件将与 FreeBSD 相关。

偶尔,你可能需要展现你的外交技巧,友好地将寻求一般支持的用户指引到合适的资源。较少情况下,你可能会遇到某人询问为什么 RPMS 没有更新,或者如何在 XX Linux 上运行该软件。趁机告诉他们,你的 Port 已经是最新的(如果真是这样的话!),并建议他们尝试使用 FreeBSD。

有时,用户和开发者会认为你是一个忙碌的人,你的时间很宝贵,并且会为你做一些工作。例如,他们可能会:

  • 提交一个 PR 或向你发送更新 Port 的补丁,

  • 调查并提供对 PR 的修复,或者

  • 以其他方式提交对你 Port 的更改。

在这种情况下,你的主要责任是及时回应。再次提醒,非响应维护者的超时期限是 14 天。超过这个期限,未批准的更改可能会被提交。他们为你做了这些工作,因此请尽量及时回应。然后尽快审查、批准、修改或与他们讨论更改。

如果你能够让他们感到他们的贡献受到重视(而且确实应该受到重视),那么你将更有可能说服他们未来为你做更多的事情 :-)。

3.3. 寻找并修复损坏的 Port

有一些非常好的地方可以找到需要关注的 Port。

对于维护的 Port,发送更改也是可以的,但请记得在操作之前询问维护者,看看他们是否已经在处理该问题。

只要你找到了一个 bug 或问题,收集信息、调查并修复它!如果已有 PR,请跟进该 PR。如果没有,请创建新的 PR。你的更改将会被审查,并且如果一切检查无误,将被提交。

3.4. 何时跑路

随着你的兴趣和承诺的变化,你可能会发现自己没有时间继续进行某些(或所有) Port 的贡献。这是可以理解的!如果你不再使用某个 Port 或由于时间或兴趣的变化失去了作为维护者的意愿,请告知我们。这样,我们可以继续让其他人尝试解决现有的 Port 问题,而不必等你的回应。请记住,FreeBSD 是一个志愿者项目,所以如果维护 Port 不再有趣了,可能是时候让别人来接手了!

无论如何, Port 管理团队(portmgr)保留在你长期未积极维护 Port 的情况下重置你的维护权的权利。(目前,这个时间为 3 个月。)我们所说的是,存在未解决的问题或待处理的更新,并且在这段时间内没有得到处理。

3.5. Port 维护者和贡献者的资源

4. 在其他领域入门

在本篇文章中没有提到的领域,你是否正在寻找有趣的开始项目?FreeBSD 项目有多个 Wiki 页面,列出了新贡献者可以获得灵感的领域。

FreeBSD 上的打印服务器(CUPS)

摘要

本文介绍了如何在 FreeBSD 上配置 CUPS。

1. 了解通用 UNIX 打印系统(CUPS)

CUPS(Common UNIX Printing System)为基于 UNIX® 的操作系统提供了一层可移植的打印支持。它由 Easy Software Products 开发,旨在为所有 UNIX® 系统的供应商和用户推广标准的打印解决方案。

CUPS 使用互联网打印协议(IPP)作为管理打印任务和队列的基础。它还支持 Line Printer Daemon(LPD)、Server Message Block(SMB)和 AppSocket(又名 JetDirect)协议,但功能有所简化。CUPS 增加了网络打印机浏览和基于 PostScript 打印机描述(PPD)的打印选项,支持在 UNIX® 环境中进行实际的打印操作。因此,CUPS 非常适合在 FreeBSD、Linux®、Mac OS® X 或 Windows® 的混合环境中共享和访问打印机。

2. 安装 CUPS 打印服务器

要使用预编译的二进制文件安装 CUPS,可以在 root 终端中执行以下命令:

3. 配置 CUPS 打印服务器

安装完成后,需要编辑一些文件来配置 CUPS 服务器。首先,创建或修改文件 /etc/devfs.rules,并添加以下内容,以设置所有潜在打印机设备的正确权限,并将打印机与 cups 用户组关联:

注意

接下来,在 /etc/rc.conf 中添加以下两行:

这两项设置将在启动时启动 CUPS 打印服务器,并调用上述创建的本地 devfs 规则。

为了在某些 Microsoft® Windows® 客户端下启用 CUPS 打印,应该取消注释 /usr/local/etc/cups/mime.types 和 /usr/local/etc/cups/mime.convs 文件中的以下行:

完成这些更改后,必须重新启动 devfs 和 CUPS 系统,可以通过重启计算机或在 root 终端中执行以下两个命令:

4. 配置 CUPS 打印服务器上的打印机

在安装并配置 CUPS 系统后,管理员可以开始配置附加到 CUPS 打印服务器的本地打印机。这一过程与在其他基于 UNIX® 的操作系统(如 Linux® 发行版)上配置 CUPS 打印机非常相似,甚至完全相同。

5. 配置 CUPS 客户端

CUPS 服务器已配置并将打印机添加并发布到网络之后,下一步是配置客户端,即将访问 CUPS 服务器的机器。如果有一台桌面计算机既充当服务器又充当客户端,那么很多信息可能不需要配置。

5.1. UNIX® 客户端

如果 CUPS 客户端难以发现网络上共享的其他 CUPS 打印机,有时可以创建或编辑文件 /usr/local/etc/cups/client.conf,并在其中添加以下条目:

在这种情况下,server-ip 应替换为网络上 CUPS 服务器的本地 IP 地址。

5.2. Windows® 客户端

在 XP 之前的 Windows® 版本中,原生不支持与基于 IPP 的打印机进行网络连接。然而,Windows® XP 及更高版本支持这一功能。因此,在这些版本的 Windows® 中添加 CUPS 打印机非常简单。一般来说,Windows® 管理员会运行 Windows® Add Printer 向导,选择 Network Printer,然后输入以下语法的 URI:

6. CUPS 故障排除

使用 FreeBSD 开发产品

摘要

FreeBSD 项目是一个全球性的、基于志愿者的合作项目,旨在开发一个可移植且高质量的操作系统。FreeBSD 项目以宽松的许可协议发布其产品的源代码,目的是鼓励更多的用户使用其代码。与 FreeBSD 项目合作可以帮助组织减少产品上市时间、降低工程成本,并提高产品质量。

本文探讨了在家电和软件产品中使用 FreeBSD 代码时可能遇到的问题。它突出了 FreeBSD 的一些特性,使其成为产品开发的优秀基础平台。文章最后建议了一些与 FreeBSD 项目合作时的“最佳实践”。

1. 简介

尽管 FreeBSD 的源代码是公开免费的,但为了充分享受该项目成果的好处,组织需要与该项目进行合作。在本文接下来的章节中,我们将讨论与 FreeBSD 项目有效合作的方式,以及在此过程中需要避免的陷阱。

读者警告。 作者认为,本文中列出的 FreeBSD 项目的特点在文章构思和撰写时(2005年)是大致真实的。然而,读者应当意识到,开源社区使用的实践和过程会随着时间的推移发生变化,因此本文中的信息应被视为指示性而非规范性。

1.1. 目标读者

本文适合以下广泛群体:

  • 产品公司的决策者,他们希望提高产品质量,减少上市时间,并在长期内降低工程成本。

  • 寻找利用“开源”最佳实践的技术顾问。

  • 对理解开源项目动态感兴趣的行业观察者。

  • 希望使用 FreeBSD 并寻找贡献方式的软件开发者。

1.2. 文章目标

阅读完本文后,你应当能够:

  • 理解 FreeBSD 项目的目标及其组织结构。

  • 理解其开发模式和发布工程过程。

  • 理解传统企业软件开发过程与 FreeBSD 项目中使用的过程有何不同。

  • 了解项目使用的通信渠道,以及你可以期待的透明度程度。

  • 了解与项目合作的最佳方式——如何有效地减少工程成本、提高上市时间、管理安全漏洞,并在 FreeBSD 项目发展过程中保持与你产品的未来兼容性。

1.3. 文章结构

本文的其余部分结构如下:

2. FreeBSD 作为构建模块集

FreeBSD 是构建产品的优秀基础:

  • FreeBSD 项目有着卓越的工程实践,企业可以加以利用。

  • 该项目提供了异常透明的工作流程,使得使用其代码的组织可以有效地规划未来。

2.1. 使用 FreeBSD 构建

以下是一些组织使用 FreeBSD 的方式:

  • 作为库和工具的上游源代码。 作为该项目的“下游”,企业可以利用上游代码所获得的新特性、错误修复和测试。

  • 作为嵌入式操作系统(例如,用于 OEM 路由器和防火墙设备)。在这种模型下,企业使用定制的 FreeBSD 内核和应用程序集,并为设备添加专有的管理层。OEM 可以从 FreeBSD 项目上游添加的新硬件支持中受益,并从基础系统所接受的测试中获益。 FreeBSD 提供了一个自托管的开发环境,允许轻松创建这样的配置。

  • 作为高端存储和网络设备管理功能的类 Unix 环境,运行在独立的处理器“刀片”上。 FreeBSD 提供了创建专用操作系统和应用程序镜像的工具。它对 BSD Unix API 的实现已经成熟并经过测试。FreeBSD 还可以为高端设备的其他组件提供稳定的交叉开发环境。

  • 作为在其他专有操作系统中支持类 Unix API 的方式,提高其对应用开发者的吸引力。 在这种情况下,FreeBSD 的部分内核和应用程序被“移植”到与其他任务一起运行的专有操作系统中。类 Unix API 实现的可用性可以减少将流行应用程序移植到专有操作系统所需的工作量。由于 FreeBSD 附带了高质量的内部文档,并且具有有效的漏洞管理和发布工程过程,保持系统更新的成本得以保持较低。

2.2. 技术

FreeBSD 项目支持大量的技术,以下是其中的一部分:

  • 一个模块化的对称多处理能力内核,具有可加载的内核模块和灵活易用的配置系统。

  • 支持以接近机器速度模拟 Linux™ 和 SVR4 二进制文件。支持二进制 Windows™ (NDIS) 网络驱动程序。

  • 提供多种编程任务的库:归档工具、FTP 和 HTTP 支持、线程支持,以及完整的 POSIX™ 类编程环境。

  • 超过 36000 个移植的应用程序,包括商业和开源软件,通过 FreeBSD 的 Ports 进行管理。

2.3. 组织结构

FreeBSD 的组织结构是非等级化的。

FreeBSD 的贡献者主要分为两类:普通用户和具有写权限的开发者(在术语中称为 committers)。

第一类贡献者有成千上万;绝大多数对 FreeBSD 的贡献来自于这个群体。对代码库的提交权限(写权限)授予那些对项目有持续贡献的个人。获得提交权限的开发者将承担额外的责任,且新提交者会分配导师,以帮助他们学习相关流程。

图 1. FreeBSD 组织结构

冲突解决由一个由九名成员组成的“核心团队”来执行,该团队由提交者选举产生。

2.4. FreeBSD 发布工程流程

  • 新特性和破坏性代码进入开发分支,也称为 -CURRENT 分支。

  • -STABLE 分支是从 HEAD 定期分支出来的代码线。只有经过测试的代码才能进入 -STABLE 分支。新特性在 -CURRENT 分支中经过测试和稳定后,才允许进入 -STABLE 分支。

  • -RELEASE 分支由 FreeBSD 安全团队维护。只有对关键问题的 bug 修复才能进入 -RELEASE 分支。

图 2. FreeBSD 发布分支

代码分支会保持活跃,直到用户和开发者对其仍有兴趣。

3. 与 FreeBSD 协作

像 FreeBSD 这样的开源项目提供了非常高质量的成品代码。

尽管访问优质源代码可以降低初始开发成本,但随着时间的推移,管理变更的成本将开始占主导地位。随着计算环境的变化和新安全漏洞的发现,你的产品也需要改变和适应。使用开源代码最好视为一个 持续过程 而非一次性活动。最适合合作的项目是那些 活跃的 项目;即,拥有活跃社区、明确目标和透明工作风格的项目。

  • FreeBSD 拥有一个活跃的开发者社区。截至目前,来自全球每个有人口的大陆的许多贡献者参与其中,且有 300 余名具有提交权限的个人。

    • 为流行的计算机硬件开发一个高质量的操作系统;

    • 并且以宽松的许可证将我们的工作提供给所有人。

3.1. 理解 FreeBSD 文化

为了有效地与 FreeBSD 项目合作,你需要理解该项目的文化。

志愿者驱动的项目与营利性公司运作的规则不同。公司进入开源世界时常犯的一个错误是低估了这些差异。

长期视角。 FreeBSD 的根源可以追溯到近二十年前,加利福尼亚大学伯克利分校计算机科学研究组(CSRG)的工作。[1](FreeBSD 的源代码库包含了自项目创立以来的所有历史记录,并且还有可用的 CD-ROM,里面包含了 CSRG(计算机科学研究组)早期的代码。 ) 一些最初的 CSRG 开发者仍与该项目保持联系。

开发流程。 计算机程序是沟通的工具:在一个层面上,程序员通过精确的符号与工具(编译器)沟通,工具将他们的指令翻译成可执行代码。在另一个层面上,相同的符号用于程序员之间意图的沟通。

变更日志样本

沟通渠道。 FreeBSD 的贡献者遍布全球。电子邮件(以及较少使用的 IRC)是项目中首选的沟通方式。

3.2. 与 FreeBSD 项目合作的最佳实践

接下来,我们来看一些在产品开发中最有效利用 FreeBSD 的最佳实践。

规划长期发展 设置有助于跟踪 FreeBSD 开发进展的流程。例如:

通过 svn blame 生成的注释源代码清单

使用 gatekeeper(门控者)。 任命一名 gatekeeper 来监控 FreeBSD 开发,及时发现可能影响你产品的变化。

利用 FreeBSD 的发布工程努力。 使用来自 -STABLE 开发分支的代码。这些开发分支由 FreeBSD 的发布工程和安全团队正式支持,并且包含经过测试的代码。

捐赠代码以降低成本。 开发产品的主要成本之一是维护工作。通过向项目捐赠非关键代码,你可以使你的代码得到比通常更多的曝光,从而有助于发现更多的 bug、安全漏洞以及性能问题,并且能够被修复。

公开宣传你的参与。 你不必公开宣传你使用 FreeBSD,但这样做有助于你的努力和项目的发展。

让 FreeBSD 社区知道你的公司使用 FreeBSD,有助于提高吸引高质量人才的机会。广泛的支持 FreeBSD 也意味着开发者中对它的关注度更高,这反过来为你的未来奠定了更健康的基础。

4. 结论

FreeBSD 项目的目标是创建并公开一个高质量操作系统的源代码。通过与 FreeBSD 项目的合作,你可以在多个产品开发场景中降低开发成本,并提高上市时间。

我们考察了 FreeBSD 项目的特性,阐明了它为什么是作为组织产品战略一部分的优秀选择。然后,我们探讨了该项目的主流文化,并审视了与其开发者有效互动的方法。本文最后列出了与项目合作时可能有帮助的最佳实践。

参考文献

FreeBSD 虚拟内存系统设计要素

摘要

这个标题其实只是一个花哨的说法,意味着我将尝试描述整个虚拟内存(VM)体系结构,希望能让每个人都能跟得上。过去一年,我专注于 FreeBSD 中的几个主要内核子系统,其中虚拟内存(VM)和交换(Swap)子系统是最有趣的,NFS 则是“必要的琐事”。我只重写了其中的一小部分代码。在 VM 领域,我唯一的重大重写是交换子系统。我的大部分工作是清理和维护,进行了一些中等程度的代码重写,但在 VM 子系统中并没有进行重大算法调整。VM 子系统的理论基础大部分保持不变,近几年现代化工作的大部分功劳归功于 John Dyson 和 David Greenman。我不是像 Kirk 那样的历史学家,所以不会试图给各种功能打上人物的名字标签,因为我肯定会搞错。


1. 引言

在进入实际设计之前,我们先花点时间讨论一下维护和现代化任何长久存在的代码库的必要性。在编程界,算法往往比代码更为重要,正是由于 BSD 的学术背景,从一开始就对算法设计给予了大量关注。对设计的关注通常会导致一个干净且灵活的代码库,这样的代码库可以随着时间的推移较为容易地修改、扩展或替换。虽然一些人认为 BSD 是一个“老”操作系统,但我们那些在其上工作的人,更倾向于将其视为一个“成熟”的代码库,里面的各种组件被现代化的代码修改、扩展或替换。它已经进化了,而 FreeBSD 无论代码有多老,都处在前沿。这是一个非常重要的区别,而不幸的是,这一点常常被许多人忽略。程序员可能犯的最大错误就是不从历史中学习,而这正是许多其他现代操作系统犯下的错误。Windows NT® 就是一个最好的例子,其后果是灾难性的。Linux 在某种程度上也犯了这个错误——足够多的情况让我们 BSD 人时不时地开一些小玩笑。Linux 的问题其实就是缺乏经验和历史来对比思想,这是一个问题,但 Linux 社区正迅速地解决这个问题,和 BSD 社区解决问题的方式一样——通过持续的代码开发。Windows NT® 的人则不断犯同样的错误,而这些错误在 UNIX® 中早已被解决,他们却要花费数年时间来修复。一遍又一遍。他们有着严重的“非我设计”病和“我们总是对,因为我们的营销部门这么说”这种心态。我对那些无法从历史中学习的人几乎没有容忍度。

FreeBSD 设计中,特别是在 VM/Swap 子系统中的复杂性,很多时候直接源于在各种条件下解决性能问题的需要。这些问题并非由于算法设计不佳,而是由环境因素引起的。在平台之间的直接对比中,当系统资源开始被施压时,这些问题就变得尤为明显。在我描述 FreeBSD 的 VM/Swap 子系统时,读者应该始终牢记两个要点:

  1. 性能设计中最重要的方面是“优化关键路径”。性能优化往往会让代码稍微膨胀,以便让关键路径的性能更好。

  2. 一个坚实的、通用的设计在长期来看优于一个高度优化的设计。虽然通用设计在最初实现时可能比高度优化的设计慢,但通用设计通常更容易适应变化的条件,而高度优化的设计最终可能需要被丢弃。

任何能够存活并且可以维护多年的代码库,必须从一开始就设计得当,即使这意味着牺牲一些性能。二十年前,人们仍然争论用汇编语言编程比用高级语言编程好,因为前者生成的代码速度是后者的十倍。今天,这个论点的错误已经显而易见——而且这种错误与算法设计和代码通用化之间的相似之处也不言而喻。

2. VM 对象

描述 FreeBSD VM 系统的最佳方式是从用户级进程的角度来看。每个用户进程看到的是一个单一的、私有的、连续的虚拟内存(VM)地址空间,其中包含几种类型的内存对象。这些对象有各种特征。程序代码和程序数据实际上是一个单一的内存映射文件(运行的二进制文件),但程序代码是只读的,而程序数据是写时复制(copy-on-write)的。程序的 BSS 部分只是按需分配并填充为零的内存,称为按需零页填充(demand zero page fill)。任意文件也可以被内存映射到地址空间,这也是共享库机制的工作方式。这样的映射可能需要修改,以保持对进程私有。fork 系统调用在现有复杂度的基础上,进一步增加了 VM 管理的复杂性。

一个程序二进制数据页(它是一个基本的写时复制页)说明了这种复杂性。一个程序二进制文件包含一个预初始化的数据段,这个段最初是直接从程序文件中映射的。当程序被加载到进程的 VM 空间时,这个区域最初是内存映射的,并且由程序二进制文件本身提供支持,允许 VM 系统在之后释放/重用该页,并从二进制文件中重新加载它。然而,待进程修改了这段数据,VM 系统就必须为该进程制作该页的私有副本。由于私有副本已被修改,VM 系统就不能再释放它,因为无法再从原来的二进制文件中恢复它。

你会立即注意到,原本一个简单的文件映射已经变得复杂得多。数据可能是按页修改的,而文件映射是一次性涵盖多页的。当进程执行 fork 时,复杂性进一步增加。当进程 fork 时,结果是两个进程——每个进程都有自己的私有地址空间,包括原始进程在调用 fork() 前所做的任何修改。VM 系统在 fork() 时完全复制数据是不明智的,因为很可能至少有一个进程从此以后只会读取该页,而允许原始页继续使用。原本是私有的页被重新标记为写时复制(copy-on-write),因为每个进程(父进程和子进程)都期望它们自己对 fork 后的修改保持私有,不影响另一个进程。

FreeBSD 用分层的 VM 对象模型来管理这一切。原始的二进制程序文件最终会成为最低层的 VM 对象。在它上面会有一个写时复制层,用来保存那些必须从原始文件复制出来的页。如果程序修改了属于原始文件的数据页,VM 系统会发生一个故障,并在更高层次上复制该页。当进程 fork 时,会推送额外的 VM 对象层。这可能通过一个相对简单的例子来理解。fork() 是所有 *BSD 系统中常见的操作,因此这个例子考虑了一个启动并执行 fork 的程序。当进程启动时,VM 系统创建了一个对象层,我们称之为 A:

A 代表文件——这些页可能根据需要从文件的物理介质中被分页进来和分页出去。对于程序来说,从磁盘分页进是合理的,但我们不希望将其分页出去并覆盖可执行文件。因此,VM 系统创建了第二层 B,这一层将由交换空间(swap space)提供物理支持:

在首次写入该页之后,B 中创建了一个新页,并且其内容从 A 初始化。B 中的所有页都可以被分页进出交换设备。当程序执行 fork() 时,VM 系统会创建两个新的对象层——C1(父进程)和 C2(子进程)——这些层位于 B 之上:

在这个例子中,假设 B 中的一个页被原始父进程修改。进程会发生一个写时复制故障,并在 C1 中复制该页,保持 B 中的原始页不变。现在,假设同一个页在 B 中被子进程修改。进程会发生一个写时复制故障,并在 C2 中复制该页。B 中的原始页现在被完全隐藏,因为 C1 和 C2 都有一份副本,而 B 理论上可以被销毁,如果它不代表一个“真实”的文件;然而,这种优化并不简单,因为它是细粒度的。FreeBSD 并没有进行这种优化。现在,假设(如通常情况那样)子进程执行了 exec()。它的当前地址空间通常会被一个新地址空间替代,该地址空间代表一个新的文件。在这种情况下,C2 层被销毁:

在这种情况下,B 的子进程数量降至 1,所有对 B 的访问现在都通过 C1。这样,B 和 C1 就可以合并在一起。B 中的所有与 C1 共同存在的页在合并过程中会从 B 中删除。因此,尽管前一步的优化没有做出,我们可以在进程退出或执行 exec() 时回收死掉的页。

这个模型会产生一些潜在问题。第一个问题是,你可能会遇到相对深的 VM 对象层次堆栈,这可能会在发生故障时消耗扫描时间和内存。深层堆栈通常发生在进程执行 fork 后又继续 fork(无论是父进程还是子进程)。第二个问题是,你可能会在 VM 对象的深层堆栈中遇到死页,即那些无法访问的页面。在我们最后的例子中,如果父进程和子进程修改了同一个页,它们各自得到自己私有的副本,而 B 中的原始页就不再能被任何人访问。那页就可以从 B 中释放。

FreeBSD 通过一种特殊优化解决了深层堆栈问题,称为“完全被遮蔽情况”(All Shadowed Case)。如果 C1 或 C2 执行足够多的写时复制故障,完全遮蔽了 B 中的所有页,就会发生这种情况。假设 C1 达成了这一点。此时,C1 就可以完全绕过 B,因此,不再是 C1→B→A 和 C2→B→A,而是 C1→A 和 C2→B→A。但看看发生了什么——现在 B 只有一个引用(C2),所以我们可以合并 B 和 C2。最终结果是 B 被完全删除,变成了 C1→A 和 C2→A。通常情况下,B 会包含大量的页,C1 或 C2 并不容易完全遮蔽它。然而,如果我们再次执行 fork 创建一组 D 层,那么 D 层中的一个层就更有可能完全遮蔽 C1 或 C2 所表示的较小数据集。在图中的任何节点进行这种优化都有效,因此即使在一个频繁执行 fork 的机器上,VM 对象堆栈也通常不会变得太深。无论是父进程还是子进程,在执行 fork 或子进程级联 fork 时,情况都一样。

死页问题仍然存在,特别是当 C1 或 C2 没有完全遮蔽 B 时。由于我们的其他优化,这种情况不再是一个大问题,我们允许这些死页存在。如果系统内存不足,它会将这些页交换出去,占用一点交换空间,但这就是问题的全部。

VM 对象模型的优点是 fork() 非常快速,因为不需要实际复制数据。缺点是,你可能会构建一个相对复杂的 VM 对象层,这会稍微减慢页面故障处理速度,并且需要消耗内存来管理 VM 对象结构。FreeBSD 所做的优化已将这些问题减轻到可以忽略的程度,因此没有实际的缺点。

3. SWAP 层

私有数据页最初要么是写时复制页,要么是零填充页。当发生修改并因此需要复制时,原始的后备对象(通常是文件)就无法再用于保存该页的副本,以便当 VM 系统需要将其重新用于其他目的时使用。这时,SWAP 就派上了用场。SWAP 被分配用于为没有其他后备存储的内存提供后备存储。FreeBSD 仅在实际需要时才为 VM 对象分配交换管理结构。然而,交换管理结构在历史上一直存在问题:

  • 在 FreeBSD 3.X 版本中,交换管理结构预分配了一个数组,涵盖了所有需要交换后备存储的对象——即使只有该对象的少数几个页面是交换后备的。这在映射了大对象或进程具有较大 RSS(常驻集大小)时会导致内核内存碎片问题。

  • 为了跟踪交换空间,内核内存中会保存一个“空洞列表”,这通常也会严重碎片化。由于“空洞列表”是线性列表,交换分配和释放的性能是非最优的 O(n)-每页操作。

  • 交换释放过程中需要进行内核内存分配,这会引发低内存死锁问题。

  • 由于交错算法(interleaving algorithm)所产生的空洞,这个问题会变得更加严重。

  • 此外,交换块映射可能会很容易地变得碎片化,导致非连续的分配。

  • 交换操作时,还必须动态分配内核内存用于额外的交换管理结构。

从这个列表可以看出,确实有很多改进空间。对于 FreeBSD 4.X,我对交换子系统进行了完全重写:

  • 交换管理结构通过哈希表进行分配,而不是通过线性数组进行分配,这使得它们具有固定的分配大小,并且粒度更加细化。

  • 不再使用线性链表来跟踪交换空间预留,现在它使用交换块的位图,位图排列成一棵基数树结构,并且在基数节点结构中有空闲空间的提示。这实际上使得交换分配和释放的操作变成了 O(1) 操作。

  • 整个基数树位图也会预分配,以避免在低内存交换操作中分配内核内存。毕竟,系统通常在低内存时进行交换,所以我们应该避免在这些时刻分配内核内存,以避免潜在的死锁问题。

  • 为了减少碎片化,基数树能够一次分配大块连续空间,跳过较小的碎片块。

我并没有采取最终的步骤来实现一个“分配提示指针”(allocating hint pointer),该指针将在分配过程中遍历部分交换区,以进一步保证连续的分配,或者至少保证引用的局部性,但我确保了这种添加是可以完成的。

4. 何时释放页面

由于 VM 系统使用所有可用的内存进行磁盘缓存,因此通常很少有真正空闲的页面。VM 系统依赖于能够正确选择未使用的页面,以便重新用于新的分配。选择最优的页面进行释放可能是任何 VM 系统最重要的功能之一,因为如果做出错误的选择,VM 系统可能被迫不必要地从磁盘重新读取页面,从而严重降低系统性能。

我们愿意在关键路径上忍受多少开销,以避免释放错误的页面?每一个错误的选择都会消耗数十万的 CPU 周期,并导致受影响进程明显的停顿,因此我们愿意承受相当大的开销,以确保选择的是正确的页面。这也是 FreeBSD 在内存资源紧张时通常优于其他系统的原因。

空闲页面的确定算法是建立在内存页面使用历史的基础上。为了获取这一历史,系统利用了大多数硬件页面表具有的页面使用位功能。

无论如何,页面使用位会被清除,稍后 VM 系统再次遇到该页面时,发现页面使用位已被设置。这表明该页面仍在被积极使用。如果位仍然清除,则表示该页面没有被积极使用。通过定期测试这个位,物理页面的使用历史(以计数器的形式)就被开发出来。当 VM 系统稍后需要释放一些页面时,检查这个历史成为确定最佳候选页面进行重用的关键。

对于那些没有此功能的平台,系统实际上会模拟一个页面使用位。它会取消映射或保护页面,如果页面再次被访问,则强制发生页面错误。当页面错误发生时,系统简单地标记页面为已使用,并取消保护该页面,以便它可以被使用。虽然仅仅为了确定页面是否被使用而强行触发页面错误看似是一项昂贵的操作,但它比将页面重用为其他用途后发现进程需要该页面并且不得不重新从磁盘获取要便宜得多。

FreeBSD 使用多个页面队列来进一步细化页面重用的选择,并确定何时必须将脏页面刷新到其后备存储。由于 FreeBSD 下的页面表是动态实体,因此从任何使用该页面的进程的地址空间中取消映射页面几乎不需要任何成本。当基于页面使用计数器选择了一个页面候选时,这正是系统所做的。系统必须区分可以理论上随时释放的干净页面和必须先写入其后备存储才能重新使用的脏页面。当找到一个页面候选时,如果该页面是脏的,则将其移到非活动队列;如果是干净的,则将其移到缓存队列。基于脏页面与干净页面的比例,系统会决定何时必须将非活动队列中的脏页面刷新到磁盘。待完成刷新,这些页面将从非活动队列移到缓存队列。此时,缓存队列中的页面仍然可以通过 VM 错误重新激活,且成本相对较低。然而,缓存队列中的页面被认为是“立即可释放的”,并将在系统需要分配新内存时以 LRU(最少最近使用)方式重用。

需要注意的是,FreeBSD VM 系统试图将干净页面与脏页面分开,专门避免不必要的脏页面刷新(这样可以节省 I/O 带宽),并且不会在内存子系统未受到压力时随意移动页面在各个页面队列之间。这就是为什么在执行 systat -vm 命令时,有些系统会看到非常低的缓存队列计数和高的活动队列计数。当 VM 系统的压力增大时,它会更加努力地保持各种页面队列在被认为最有效的水平。

有一个城市传说流传多年,称 Linux 在避免交换页面方面做得比 FreeBSD 更好,但实际上并非如此。实际上发生的情况是,FreeBSD 在主动将未使用的页面换出以腾出更多磁盘缓存空间,而 Linux 则保持未使用的页面在内存中,从而为缓存和进程页面留下了更少的内存。我不知道今天是否仍然如此。

5. 预错误和零填充优化

发生的大部分页面错误都是零填充错误。通过观察 vmstat -s 输出,你通常可以看到这一点。当进程访问其 BSS 区域中的页面时,就会发生这种情况。BSS 区域期望最初为零,但 VM 系统直到进程实际访问它时才会分配内存。当发生错误时,VM 系统不仅需要分配一个新页面,还需要将其填充为零。为了优化零填充操作,VM 系统能够预先将页面填充为零并标记为已零填充,并且在发生零填充错误时请求已零填充的页面。零填充操作发生在 CPU 空闲时,但系统预零填充的页面数量有限,以避免破坏内存缓存。这是一个典型的例子,展示了为了优化关键路径而向 VM 系统中增加复杂性的情况。

6. 页表优化

页表优化是 FreeBSD VM 设计中最具争议的部分,它们随着 mmap() 的广泛使用而显现出一些压力。我认为这实际上是大多数 BSD 系统的特点,尽管我不确定它是何时首次引入的。这里有两项主要优化。首先,硬件页表不包含持久状态,而是可以随时丢弃,只需少量的管理开销。其次,系统中每个活动的页表项都具有一个管理的 pv_entry 结构,该结构与 vm_page 结构相绑定。FreeBSD 可以直接遍历已知存在的映射,而 Linux 必须检查所有 可能 包含特定映射的页表,看看它是否存在,这在某些情况下可能导致 O(n^2) 的开销。正因为如此,FreeBSD 在内存受到压力时往往能够做出更好的页面重用或交换选择,从而在负载下提供更好的性能。然而,FreeBSD 需要内核调优来适应大型共享地址空间的情况,例如在新闻系统中可能会发生的情况,因为它可能会耗尽 pv_entry 结构。

Linux 和 FreeBSD 都在这个领域需要改进。FreeBSD 尝试最大化潜在稀疏活动映射模型的优势(例如并非所有进程都需要映射共享库的所有页面),而 Linux 则在简化算法方面做出了努力。FreeBSD 在这里通常具有性能优势,代价是浪费了一些额外的内存,但 FreeBSD 在处理一个大文件被大量进程共享的情况下表现不佳。另一方面,Linux 在处理许多进程稀疏映射同一共享库的情况时表现不佳,而且在决定页面是否可以重用时也未必优化。

7. 页面着色

最后,我们来讨论页面着色优化。页面着色是一种性能优化,旨在确保对虚拟内存中连续页面的访问最大程度地利用处理器缓存。在古老的时代(即10多年前),处理器缓存通常是映射虚拟内存而非物理内存的。这导致了许多问题,包括在某些情况下必须在每次上下文切换时清空缓存,并且缓存中存在数据别名问题。现代处理器缓存精确地映射物理内存来解决这些问题。这意味着进程地址空间中的两个相邻页面可能并不对应于缓存中的两个相邻页面。实际上,如果不小心,虚拟内存中的相邻页面可能会映射到处理器缓存中的同一页面,从而导致缓存中的数据被提前丢弃,降低 CPU 性能。即便是多路集合相联缓存(尽管效果有所减轻),也会出现这种情况。

FreeBSD 的内存分配代码实现了页面着色优化,意味着内存分配代码将尝试从缓存的角度寻找连续的空闲页面。例如,如果物理内存的第 16 页分配给进程的第 0 页虚拟内存,并且缓存能够容纳 4 页,页面着色代码就不会将物理内存的第 20 页分配给进程的第 1 页虚拟内存。相反,它会分配物理内存的第 21 页。页面着色代码会尽量避免分配第 20 页,因为这会与第 16 页映射到相同的缓存内存,导致缓存不最优。正如你可以想象的那样,这段代码为 VM 内存分配子系统增加了显著的复杂性,但结果是非常值得的。页面着色使得虚拟内存在缓存性能方面与物理内存一样具有确定性。

8. 结论

现代操作系统中的虚拟内存必须高效地处理许多不同的问题,并支持多种使用模式。BSD 系统历来采用的模块化和算法化方法使得我们可以研究并理解当前的实现,并且相对容易地替换掉代码的大部分部分。近年来,FreeBSD 的虚拟内存系统有了许多改进,相关工作仍在继续进行。

9. 奖励问答环节 by Allen Briggs

9.1. 你提到的 FreeBSD 3.X 交换空间的交错算法是什么?

FreeBSD 使用固定的交换交错,默认值为 4。这意味着 FreeBSD 为四个交换区域保留空间,即使你只有一个、两个或三个交换区域。由于交换是交错的,如果你实际上没有四个交换区域,那么表示“这四个交换区域”的线性地址空间就会发生碎片化。例如,如果你有两个交换区域 A 和 B,FreeBSD 对这两个交换区域的地址空间表示将以 16 页为一个块进行交错:

FreeBSD 3.X 使用“连续空闲区域列表”方法来管理空闲交换区域。这个方法的思路是,较大的空闲线性空间可以通过单个列表节点(kern/subr_rlist.c)来表示。但由于碎片化,连续列表最终会变得非常碎片化。在上面的例子中,完全未使用的交换空间将会将 A 和 B 显示为“空闲”,而 C 和 D 显示为“全部分配”。每个 A-B 序列都需要一个列表节点来进行管理,因为 C 和 D 是空洞,不能与下一个 A-B 序列合并。

为什么我们要交错交换空间,而不是直接把交换区域附加到末尾然后做一些更复杂的处理?原因是,在分配线性地址空间时,自动将其交错到多个磁盘上,比尝试将这种复杂性放到其他地方要容易得多。

碎片化会导致其他问题。由于 3.X 系统下它是一个线性列表,而且具有大量固有的碎片化,交换的分配和释放变成了 O(N) 的算法,而不是 O(1) 的算法。再加上一些其他因素(如频繁交换),你就会进入 O(N^2) 和 O(N^3) 级别的开销,这会非常糟糕。在低内存情况下,3.X 系统可能还需要在交换操作过程中分配 KVM 来创建新的列表节点,这可能会导致死锁。

在 4.X 版本中,我们不再使用连续列表,而是使用基数树和交换块的位图来代替范围列表节点。我们虽然在开始时就预分配了所有位图所需的空间,但由于使用了位图(一位代表一个块),而不是使用节点的链表,这样反而节省了更多内存。通过使用基数树而不是线性列表,即使树发生碎片化,性能几乎可以达到 O(1)。

9.2. 清洁页和脏页(非活动页)的分离与在 systat -vm 中看到低缓存队列计数和高活动队列计数的情况有什么关系?systat 的统计数据会将活动页和脏页一起算入活动队列计数吗?

是的,这是令人困惑的。关系在于“目标”与“现实”。我们的目标是将页面分开,但现实情况是,如果系统不在内存紧张的状态下,我们其实不需要特别去分开它们。

这意味着,当系统不受压力时,FreeBSD 不会努力将脏页(非活动队列)和干净页(缓存队列)分开,也不会试图将页从活动队列移动到非活动队列,即使这些页没有被使用。

一个 COW 页面错误可以是零填充的,也可以是程序数据的。无论哪种情况,机制都是一样的,因为支持程序数据的页面几乎可以肯定已经在缓存中。我确实将这两者放在一起讨论。FreeBSD 不会对程序数据或零填充进行预 COW,但它确实会对已经在缓存中的页面进行预映射。

9.4. 在你提到的页面表优化部分,能否提供更多关于 pv_entry 和 vm_page 的细节(或者是否应该是 vm_pmap,如 McKusick、Bostic、Karel 和 Quarterman 在 4.4 版的第 180-181 页所述)?具体来说,什么样的操作/反应会要求扫描这些映射?

一个 vm_page 代表一个(对象,索引号)元组。一个 pv_entry 代表一个硬件页面表项(pte)。例如,如果有五个进程共享同一个物理页面,其中三个进程的页面表实际上映射了该页面,那么这个页面将由一个 vm_page 结构和三个 pv_entry 结构来表示。

pv_entry 结构只代表 MMU 映射的页面(一个 pv_entry 代表一个 pte)。这意味着当我们需要删除一个 vm_page 的所有硬件引用(例如为了重用该页面、将其换出、清空、标记为脏页等)时,我们可以通过扫描与该 vm_page 关联的 pv_entry 链表来删除或修改它们在页面表中的 pte。

在 Linux 中,没有这样的链表。要删除一个 vm_page 的所有硬件页面表映射,Linux 必须索引到每个可能映射该页面的 VM 对象中。例如,如果有 50 个进程都映射了同一个共享库,而你想删除该库中的 X 页面,那么即使只有 10 个进程真正映射了该页面,Linux 也需要索引到这 50 个进程的页面表。因此,Linux 在简化设计的同时牺牲了性能。很多在 FreeBSD 中是 O(1) 或小 N 的虚拟内存算法,在 Linux 中则可能变成 O(N)、O(N^2) 或更差。由于表示一个特定页面的 pte 通常在所有映射了该页面的页面表中位于相同的偏移量,通过减少对页面表的访问(只针对需要修改的 pte)常常能够避免冲掉该偏移量所在的 L1 缓存行,从而提高性能。

FreeBSD 通过增加复杂性(pv_entry 机制)来提高性能(将页面表的访问限制在 仅 需要修改的 pte 上)。

然而,FreeBSD 在这方面存在扩展性问题,Linux 不存在。即使有充足的空闲内存,当数据被大量共享时,FreeBSD 也会面临 pv_entry 结构的数量限制。在这种情况下,即使有足够的内存,仍然可能耗尽 pv_entry 结构。这可以通过在内核配置中增加 pv_entry 结构的数量来解决,但我们仍然需要找到更好的方法。

关于页面表的内存开销与 pv_entry 方案的比较:Linux 使用“不丢弃”的“永久”页面表,但不需要为每个可能映射的 pte 配置一个 pv_entry。FreeBSD 使用“丢弃式”页面表,但为每个实际映射的 pte 添加一个 pv_entry 结构。我认为内存利用率最终差不多,这使得 FreeBSD 在算法上具有优势,因为它能够以非常低的开销随时丢弃页面表。

9.5. 最后,在页面着色部分,可能需要更多描述来解释你在这里的意思。我有些没有理解。

你知道 L1 硬件内存缓存是如何工作的吗?我来解释一下:假设一台机器有 16MB 的主内存,但只有 128K 的 L1 缓存。通常,这个缓存的工作方式是,每个 128K 的主内存块使用相同的 128K 缓存。如果你访问主内存中的 0 偏移地址,然后访问主内存中的 128K 偏移地址,你可能会丢弃从 0 偏移地址读取的数据!

现在,我简化了很多。刚才描述的是所谓的“直接映射”硬件内存缓存。大多数现代缓存是所谓的 2 路组相联缓存或 4 路组相联缓存。组相联方式允许你访问最多 N 个不同的内存区域,这些区域可以共享同一个缓存而不破坏先前缓存的数据。但最多只有 N 个。

因此,如果我有一个 4 路组相联缓存,我可以访问 0 偏移地址、128K 偏移地址、256K 偏移地址和 384K 偏移地址,并且仍然能够再次访问 0 偏移地址并从 L1 缓存中获取。如果我接着访问 512K 偏移地址,然而,缓存中将丢弃四个已缓存的数据对象中的一个。

这非常重要……极其重要,因为处理器的大部分内存访问必须能够从 L1 缓存中获取,因为 L1 缓存以处理器频率运行。只要发生 L1 缓存未命中的情况,必须去 L2 缓存或主内存读取数据,处理器就会停滞,并且可能会浪费数百条指令的时间,等待从主内存读取数据的完成。与现代处理器核心的速度相比,主内存(你在计算机中装的动态 RAM)是 慢 的。

好了,现在说说页面着色:所有现代内存缓存都是所谓的 物理 缓存。它们缓存的是物理内存地址,而不是虚拟内存地址。这使得缓存能够在进程上下文切换时保持不变,这是非常重要的。

但是,在 UNIX® 世界中,你处理的是虚拟地址空间,而不是物理地址空间。你写的任何程序都会看到它被分配的虚拟地址空间。实际上,支持该虚拟地址空间的 物理 页面不一定是物理连续的!事实上,你可能有两个在进程地址空间中相邻的页面,它们可能会位于 物理 内存的 0 偏移地址和 128K 偏移地址。

程序通常假设两个相邻的页面会被最优地缓存。也就是说,你可以在两个页面中访问数据对象,而不会相互覆盖彼此的缓存条目。但这只有在虚拟地址空间下的物理页面是连续的情况下才成立(就缓存而言)。

这就是页面着色的作用。页面着色并不是将 随机 物理页面分配给虚拟地址,这可能会导致不理想的缓存性能,而是将 合理连续 的物理页面分配给虚拟地址。这样,程序可以假设底层硬件缓存的特性与程序直接在物理地址空间中运行时的表现相同。

请注意,我使用了“合理连续”而不是“连续”。从 128K 直接映射缓存的角度来看,物理地址 0 与物理地址 128K 是相同的。因此,你虚拟地址空间中的两个相邻页面,可能会位于物理内存的 128K 偏移地址和 132K 偏移地址,但也很容易位于物理内存的 128K 偏移地址和 4K 偏移地址,并且仍然保持相同的缓存性能特性。所以,页面着色 不 必须将真正连续的物理内存页面分配给连续的虚拟内存页面,它只需要确保从缓存性能和操作的角度来看,分配的页面是连续的。

FreeBSD 中文文章翻译项目

正在施工

原文位于

帮助将 FreeBSD 文档翻译成你的母语。如果你的语言已经有了文档,你可以帮助翻译更多文档,或者验证现有翻译是否最新且正确。首先可以查看 。你不必承诺翻译每一篇 FreeBSD 文档,作为志愿者,你可以根据自己的时间和精力做多少翻译。通常只要有人开始翻译,其他人就会加入进来。如果你只有时间和精力翻译一部分文档,请翻译安装说明。

偶尔(或定期)阅读 。分享你的专业知识并帮助他人解决问题往往会让人很有满足感;有时你甚至会学到新东西!这些论坛也可以是改进的灵感来源。

阅读 。这里可能有你可以做出建设性评论或测试补丁的问题,或者你甚至可以尝试自己修复问题。

使用开启额外警告的方式构建源代码树(或只是部分代码),并清理警告。也可以从我们的 获取构建警告列表,选择构建并查看 “LLVM/Clang 警告”。

显示了所有当前活跃的 bug 报告和功能增强请求,这些报告和请求由 FreeBSD 用户提交。PR 数据库包括程序员和非程序员任务。浏览这些未关闭的 PR,看看是否有任何任务吸引你的兴趣。有些可能是非常简单的任务,只需要额外的眼光来审查它们,并确认 PR 中的修复是否有效。其他的可能要复杂得多,甚至可能没有任何修复内容。

找到一些有趣或有用的软件并为其。

有大量的 Port 没有维护者。成为维护者并。

如果你已经创建或接管了 Port,了解。

当你寻找一个快速挑战时,可以。

也提供给那些愿意为 FreeBSD 项目贡献的人。该列表会定期更新,包含程序员和非程序员的项目,并为每个项目提供相关信息。

所有 一般性的 技术兴趣的想法或建议,应该发送到 。同样,有兴趣参与这些讨论的人(并且能忍受 高 邮件量!)可以订阅 。有关此和其他邮件列表的更多信息,请参见 。

如果你提交的是个简单的补丁,且该补丁已经准备好提交到源代码仓库,考虑将其作为 提交到项目的 GitHub 镜像。合适的提交应满足以下要求:

每个逻辑变化都是一个单独的提交。每个提交的日志应遵循 。

提交应包括一个或多个 Signed-off-by: 行,附上全名和电子邮件地址,以证明 。

提交到 Port 仓库的 RP 可能会得到或者不会得到开发者的处理。现在,如果你遵循 Port 提交过程,,你会获得更好的体验。

如果你发现了 bug 或者提交了一个特定的更改,请使用 进行报告。尽量填写每个字段。如果补丁不超过 65KB,可以直接在报告中附带补丁。如果补丁适合应用到源代码树中,请在报告的摘要中加入 [PATCH]。提交补丁时,不要使用复制粘贴,因为复制粘贴会将制表符转为空格,导致补丁无法使用。当补丁大于 20KB 时,考虑在上传之前将其压缩(例如使用 或 )。

有关如何编写良好问题报告的更多信息,请参见 。

文档更改由 监督。请查看 获取完整的操作说明。使用与其他 bug 报告相同的方法提交文档更改(即使是小的更改也很欢迎!)。

对现有源代码的添加或更改是个稍微复杂的过程,这很大程度上取决于你与当前 FreeBSD 开发状态的同步程度。FreeBSD 有个专门的持续发布版本,称为“FreeBSD-CURRENT”,该版本通过多种方式提供,方便活跃的开发者使用。请参阅 以获取有关如何获取和使用 FreeBSD-CURRENT 的更多信息。

使用较旧的源代码意味着你的更改可能会过时,或者与 FreeBSD 的当前版本存在较大的差异,难以重新集成。为了减少这种可能性,建议订阅 和 ,这些邮件列表讨论的是系统的当前状态。

假设你能够确保基于相对更新的源代码来进行更改,接下来的步骤是使用 命令生成差异并提交给 FreeBSD 维护者。

查看 获取更多信息。

只要你有了补丁集(你可以使用 命令进行测试),你应该通过 bug 报告提交它们以供 FreeBSD 纳入。不要直接将 diff 发送到 ,否则它们会丢失!我们非常感谢你的贡献(这是一个志愿者项目!);由于我们很忙,可能无法立即处理,但它将保留在 PR 数据库中,直到我们处理。通过在报告的摘要中加入 [PATCH] 来标示你的提交。

如果你的更改涉及敏感问题(例如你不确定其进一步分发的版权问题),请直接发送到 而不是作为 bug 报告提交。 是个较小的团队,负责处理 FreeBSD 的日常工作。请注意,这个团队也非常忙,所以只有在确实需要时才应发送邮件给他们。

有关编码风格的一些信息,请参考 和 。我们希望你在提交代码之前至少能了解这些信息。

当涉及大量代码时,版权问题通常会成为一个敏感话题。FreeBSD 偏好使用自由软件许可证,例如 BSD 或 ISC 许可证。像 GPLv2 这样的 Copyleft 许可证有时也被允许。可以在 页面找到完整的许可列表。

是一家非营利的、免税的基金会,旨在促进 FreeBSD 项目的目标。作为一家 501(c)3 实体,基金会通常免于美国联邦所得税和科罗拉多州的所得税。捐赠给免税实体的款项通常可以从应纳税联邦收入中扣除。

FreeBSD 基金会也接受 ,通过多种支付方式。

有关 FreeBSD 基金会的更多信息,请参阅 。如需通过电子邮件联系基金会,请写信至 。

FreeBSD 项目非常乐意接受可以充分利用的硬件捐赠。如果你有兴趣捐赠硬件,请联系 。

未维护的 Port 的 MAINTAINER 会被设置为 ports@FreeBSD.org。许多未维护的 Port 可能有待更新,可以通过 查看。

在 上,可以看到带有错误的未维护 Port 的列表。

首先,请确保你了解 。同时阅读 。请不要承诺你无法轻松处理的任务。

这是一个概述。有关升级 Port 的更多信息,可以参见 。

确保你的 Port 依赖关系完整。推荐的做法是安装自己的 Port tinderbox。有关更多信息,请参考 。

使用 作为指南,验证你的 Port。有关使用 portlint 的重要信息,请参见 。

考虑你的更改是否会导致其他 Port 发生故障。如果是这样,与你维护的其他 Port 的维护者协调这些更改。特别是,如果你的更新更改了共享库版本,至少需要为依赖的 Port 增加 PORTREVISION,以便自动工具(如 )能够自动升级这些 Port。

提交更改 通过提交 PR 来提交你的更新,解释更改并提供原始 Port 与更新 Port 之间的差异补丁。有关如何写好 PR 的信息,请参见 。

请不要提交完整 Port 的 压缩包;而应使用 或 -ruN。这样,提交者可以更容易地查看所做的更改。有关更多信息,请参见 Port 开发者手册中的 部分。

等待 在某个时候,提交者会处理你的 PR。可能需要几分钟,也可能需要一两周,所以请耐心等待。如果超过这个时间,请在邮件列表(例如 )、IRC:#bsdports(EFNet)或 #freebsd-ports(Libera)上寻求帮助。

关注构建失败 检查你的邮件,查看来自 pkg-fallout@FreeBSD.org 的邮件和 ,以查看是否有无法构建的 Port 已经过时。

通过 显示的已安装软件包列表

通过 -a 显示的 FreeBSD 版本

调查并找到解决方案 不幸的是,这没有简单的过程可以遵循。不过请记住:如果你遇到困难,向他人寻求帮助! 是个很好的起点,且上游开发者通常非常乐于提供帮助。

回应 bug 报告 Bug 可能通过电子邮件报告给你,来自 ,也可能直接由用户报告给你。

他们的构建和执行环境信息—例如,已安装软件包的列表和 的输出

你可以使用 的 搜索和查看未解决的 PR。大多数 Port 的 PR 都是更新,但通过稍微搜索和浏览概要,你应该能找到一些有趣的工作内容。

显示了从 FreeBSD 包构建中收集的 Port 问题。

是你了解 Port 系统的好帮手。请随时查阅!

讲述了如何最佳地制定和提交 PR。在 2005 年,提交了超过 11,000 个 Port PR!遵循这篇文章将大大帮助我们减少处理你的 PR 所需的时间。

。

可以显示无法获取 distfiles 的 Port。你可以检查你自己的 Port,或者使用它查找需要更新 MASTER_SITES 的 Port。

是测试 Port 的最全面方法,涵盖了从安装、打包到卸载的整个周期。文档位于

是一个可以验证你的 Port 是否符合许多重要风格和功能准则的应用程序。portlint 是一个简单的启发式应用程序,因此你应该 仅将其作为指南。如果 portlint 提示的更改似乎不合理,请查阅 或寻求建议。

是讨论与 Port 相关的常规话题的地方。这是寻求帮助的好地方。你可以 。阅读 和 也可能会很有帮助。

是帮助查找 的地方。

页面列出了可能对刚接触 FreeBSD 并希望从事有趣工作的人的项目,适合入门。

列出了各种“理想的”或“有趣的”工作项目,适合在 FreeBSD 项目中进行。

原文链接

CUPS 的官方网站是 。

另外,一些可选的但推荐安装的包有 和 ,它们为多种打印机提供了驱动程序和实用工具。安装完成后,CUPS 的配置文件可以在目录 /usr/local/etc/cups 中找到。

注意:X、Y 和 Z 应替换为 /dev/usb 目录中与打印机对应的目标 USB 设备。要查找正确的设备,可以查看 的输出,ugenX.Y 列出的是打印机设备,它是 /dev/usb 中 USB 设备的符号链接。

管理和管理 CUPS 服务器的主要方式是通过基于 Web 的界面,可以通过启动浏览器并在浏览器的 URL 栏中输入 来访问。如果 CUPS 服务器位于网络上的另一台机器上,只需将服务器的本地 IP 地址替换 localhost。CUPS 的 Web 界面非常直观,其中有打印机和打印任务管理、用户授权等部分。此外,在管理界面的右侧有几个复选框,方便访问常用设置,如是否共享连接到系统的打印机、是否允许远程管理 CUPS 服务器,以及是否允许用户对打印机和打印任务授予额外的访问权限。

添加打印机通常很简单,只需在 CUPS Web 界面的管理屏幕上点击 Add Printer,或者点击 New Printers Found 按钮。当出现 Device 下拉框时,只需选择所需的本地连接打印机,然后继续操作。如果之前安装了 Port 或包 或 ,那么后续界面中会提供其他打印驱动程序,可能提供更多的稳定性或功能。

CUPS 也需要在 UNIX® 客户端上安装。在客户端上安装了 CUPS 后,通常桌面环境(如 GNOME 或 KDE)的打印管理器会自动发现网络上共享的 CUPS 打印机。或者,可以在客户端机器上访问本地 CUPS 界面,输入 并在管理部分点击 Add Printer。当出现 Device 下拉框时,只需选择自动发现的网络 CUPS 打印机,或者选择 ipp 或 http,然后输入网络 CUPS 打印机的 IPP 或 HTTP URI,通常有以下两种格式之一:

如果使用的是较旧版本的 Windows®,没有原生的 IPP 打印支持,那么连接到 CUPS 打印机的常见方式是将 与 CUPS 一起使用,然而这超出了本章的讨论范围。

CUPS 的常见问题通常与权限有关。首先,检查如上所述的 权限。接着,检查文件系统中创建的设备的实际权限。确保用户是 cups 组的成员也很有帮助。如果 CUPS Web 界面中的权限复选框似乎无法正常工作,可以尝试手动备份位于 /usr/local/etc/cups/cupsd.conf 的主配置文件,并编辑其中的各种配置选项,尝试不同的配置组合。以下是一个可以测试的示例 /usr/local/etc/cups/cupsd.conf 配置文件。请注意,此示例 cupsd.conf 为了更容易配置牺牲了一些安全性;若管理员成功连接到 CUPS 服务器并配置好客户端,建议重新审视该配置文件并开始限制访问。

FreeBSD 今天已广泛知名,作为一种高性能的服务器操作系统,它已在全球数百万台 Web 服务器和互联网主机上部署。FreeBSD 的代码也构成了许多产品的核心部分,从网络路由器、防火墙和存储设备等家电,到个人计算机。FreeBSD 的部分内容也已用于商业包装软件中(请参阅)。

在本文中,我们将从软件工程资源的角度探讨 ,即作为一组构建模块和过程,你可以利用它们来构建产品。

介绍了 FreeBSD 项目,探讨了其组织结构、关键技术和发布工程过程。

介绍了与 FreeBSD 项目合作的方式,并审视了企业在与 FreeBSD 等志愿项目合作时常遇到的陷阱。

进行总结。

FreeBSD 源代码以宽松的 BSD 许可协议发布,方便其在商业产品中采纳。 让其在商业化过程中几乎没有麻烦。

从加利福尼亚大学伯克利分校的计算机科学研究组所带来的文化,促进了高质量的工作。FreeBSD 中的一些特性定义了技术的前沿。

更详细地探讨了使用开源的商业原因。对于企业来说,将 FreeBSD 组件用于产品中带来的好处包括缩短上市时间、降低开发成本和降低开发风险。

作为通过来自全球开发者团队的广泛测试和支持,获取非关键“知识产权”的途径。 在这种模型下,组织将有用的基础设施框架贡献给 FreeBSD 项目(例如,参见 )。代码所获得的广泛曝光有助于快速识别性能问题和漏洞。顶尖开发者的参与也有助于对基础设施进行有益的扩展,贡献组织也能从中受益。

作为支持嵌入式操作系统如 和 的交叉开发环境。 在 FreeBSD 所移植和打包的 36000 个应用程序中,有许多完备的开发环境。

一个完整的系统,可以为进行交叉托管。

安全特性:强制访问控制()、jails()、ACLs 和内核加密设备支持。

网络特性:防火墙、QoS 管理、高性能 TCP/IP 网络,支持许多扩展。 FreeBSD 内核中的 Netgraph()框架允许内核网络模块以灵活的方式连接。

存储技术支持:光纤通道、SCSI、软件和硬件 RAID、ATA 和 SATA。 FreeBSD 支持多种文件系统,其本地的 UFS2 文件系统支持软更新、快照以及非常大的文件系统大小(每个文件系统可达 16TB)。 FreeBSD 内核中的 GEOM()框架允许内核存储模块以灵活的方式组合。

FreeBSD 没有“企业”提交者。个人提交者需要对自己引入的代码变更负责。 详细记录了提交者的规则和责任。

FreeBSD 的项目模型在中有详细的讨论。

FreeBSD 的发布工程流程在确保发布版本质量高方面起着重要作用。在任何时间点,FreeBSD 的志愿者都会支持多个代码分支():

机器架构被分为“层级”;Tier 1 架构由项目的发布工程和安全团队全面支持,Tier 2 架构按最佳努力支持,而实验性架构则属于 Tier 3。支持的是 FreeBSD 文档的一部分。

发布工程团队在项目网站上发布了。路线图中列出的日期并非截止日期;FreeBSD 会在代码和文档准备好时发布。

FreeBSD 的发布工程流程可参见。

FreeBSD 项目的目标是:

FreeBSD 享有开放且透明的工作文化。项目中的几乎所有讨论都通过电子邮件进行,在上进行讨论,这些邮件列表也会被存档以供后人查阅。项目的政策已被,并受到版本控制。项目的参与对所有人开放。

动机。 大多数对 FreeBSD 的贡献都是自愿进行的,没有金钱奖励参与其中。驱动个人参与的动机因素是复杂的,从利他主义到解决 FreeBSD 所尝试解决的问题的兴趣不等。在这种环境中,“优雅从来不是可选的”。

该项目重视长期视角 。在项目中,一个常见的缩写是 DTRT,代表“做正确的事”(Do The Right Thing)。

在项目中,很少使用正式的规格和设计文档。相反,使用清晰且编写良好的代码和变更日志()。FreeBSD 的开发通过“粗略共识和运行中的代码”进行。

程序员之间的沟通通过使用通用编码标准 得到增强。

跟踪 FreeBSD 源代码。 该项目通过使用 使镜像其 SVN 仓库变得简单。拥有完整的源代码历史对于调试复杂问题非常有用,并且可以深入了解原开发者的意图。使用一个强大的源代码管理系统,可以轻松地合并上游 FreeBSD 代码库和你自己内部代码之间的变化。

显示了由变更日志引用的文件的注释清单部分。每一行代码的祖先历史清晰可见。显示 FreeBSD 每个文件历史的注释清单 。

向上游报告 bug。 如果你注意到正在使用的 FreeBSD 代码中有 bug,请提交一个 。这一步可以确保下次你从上游获取代码时不必再次修复该 bug。

有效获取支持。 对于有紧迫期限的产品,建议你聘请或与有 FreeBSD 经验的开发者或公司达成咨询协议。你可以通过 寻找人才。FreeBSD 项目还维护了一个 ,他们提供 FreeBSD 工作相关的服务。 提供所有主要 BSD 派生操作系统的认证。

对于不太紧急的需求,你可以在 上寻求帮助。询问帮助时遵循 这篇指南。

支持 FreeBSD 开发者。 有时候,将一个所需的功能加入 FreeBSD 的最直接方法是支持已经在处理相关问题的开发者。帮助可以从硬件捐赠到直接的财政援助不等。在一些国家,捐赠 FreeBSD 项目可以享受税收优惠。该项目有专门的 来协助捐赠者。项目还维护一个网页,开发者在这里 。

作为政策,FreeBSD 项目会在其网站上 所有收到的贡献。

[Carp1996] B. Carpenter. The Internet Architecture Board.The Internet Architecture Board. Copyright® 1996.

[ComGuide] The FreeBSD Project. Copyright® 2005.

[GoldGab2005] Ron Goldman. Richard Gabriel. Copyright® 2005. Morgan-Kaufmann.

[Hub1994] Jordan Hubbard. Copyright® 1994-2005. The FreeBSD Project.

[McKu1999] Kirk McKusick. Gregory Ganger. Copyright® 1999.

[McKu1999-1] Marshall Kirk McKusick. O’Reilly Inc.. Copyright® 1993.

[Mon2005] Bruce Montague. The FreeBSD Project. Copyright® 2005.

[Nik2005] Niklas Saers. Copyright® 2005. The FreeBSD Project.

[Nor1993] Peter Norvig. Kent Pitman. Copyright® 1993.

[Nor2001] Peter Norvig. Copyright® 2001.

[Ray2004] Eric Steven Raymond. Copyright® 2004.

[RelEngDoc] Murray Stokely. Copyright® 2001. The FreeBSD Project.

原文:

Matthew Dillon []

发生 VM 错误并不昂贵,如果底层页面已经在内存中并且可以直接映射到进程中。但如果这些错误频繁发生,就可能变得非常昂贵。一个典型的例子是反复运行类似 或 这样的程序。如果程序二进制文件已经映射到内存中,但没有映射到页表中,那么程序每次运行时都需要发生错误,访问程序将访问的所有页面。对于这些已经在 VM 缓存中的页面,这是不必要的,因此 FreeBSD 会尝试预先填充进程的页表,将这些已存在于 VM 缓存中的页面映射到页表中。FreeBSD 目前还没有做的是在 exec 时预复制某些页面。例如,当你在运行 vmstat 1 时运行 程序时,你会注意到即使是反复运行,也总会发生一定数量的页面错误。这些是零填充错误,而不是程序代码错误(这些已经预错误过)。在执行 exec 或 fork 时预复制页面是一个可以进一步研究的领域。

9.3. 在 和 vmstat 1 示例中,是否不会有一些页面错误是数据页面错误(从可执行文件到私有页面的 COW)?也就是说,我期望页面错误既有零填充的,也有程序数据的。还是你在暗示 FreeBSD 确实对程序数据进行了预 COW?

由于过于冗长且无翻译意义的原因,和两篇文章略去不译。

Contributing to FreeBSD
FreeBSD 文档翻译 FAQ
FreeBSD 通用问题邮件列表
FreeBSD 问题报告邮件列表
CI
FreeBSD PR 列表
创建 Port
接管未维护的 Port
Port 维护者面临的挑战
查找并修复损坏的 Port
FreeBSD 的志愿者项目和想法列表
FreeBSD 技术讨论邮件列表
FreeBSD 技术讨论邮件列表
FreeBSD 手册
RP
提交日志指南
开发者源认证
贡献 Port
bug 提交表单
gzip(1)
bzip2(1)
此文章
FreeBSD 文档项目邮件列表
FreeBSD 文档项目指南
FreeBSD 手册
FreeBSD 公告邮件列表
FreeBSD-CURRENT 邮件列表
diff(1)
diff(1)
patch(1)
FreeBSD 技术讨论邮件列表
core@FreeBSD.org
core@FreeBSD.org
intro(9)
style(9)
core 团队的许可政策
FreeBSD 基金会
在线捐赠
FreeBSD 基金会——简介
info@FreeBSDFoundation.org
捐赠联络办公室
FreeBSD Ports distfile 扫描器
PortsFallout
Port 维护者的挑战
Porter’s Handbook
Porter’s Handbook
Port 维护者和贡献者资源
portlint(1)
Port 维护者和贡献者资源
ports-mgmt/poudriere
编写 FreeBSD 问题报告
shar(1)
git-format-patch(1)
diff(1)
升级
FreeBSD ports 邮件列表
distfiles 扫描器
pkg-info(8)
uname(1)
FreeBSD ports 邮件列表
Problem Report 数据库
env(1)
问题报告数据库
网页接口
PortsFallout
Porter’s Handbook
编写 FreeBSD 问题报告
问题报告数据库
FreeBSD Ports distfile 扫描器(portscout)
ports-mgmt/poudriere
poudriere GitHub 仓库
portlint(1)
Porter’s Handbook
FreeBSD Port 邮件列表
订阅,或阅读和搜索列表存档
FreeBSD Port bug 邮件列表
SVN 提交消息(head/ 分支的 Port 树)
PortsFallout
FreeBSD 包 Fallout 存档
Junior Jobs
Ideas Page
# pkg install cups
[system=10]
add path 'unlpt*' mode 0660 group cups
add path 'ulpt*' mode 0660 group cups
add path 'lpt*' mode 0660 group cups
add path 'usb/X.Y.Z' mode 0660 group cups
cupsd_enable="YES"
devfs_system_ruleset="system"
application/octet-stream
# service devfs restart
# service cupsd restart
ipp://server-name-or-ip/printers/printername
http://server-name-or-ip:631/printers/printername
ServerName server-ip
http://server-name-or-ip:631/printers/printername
# 在 error_log 中记录一般信息 - 若进行故障排除,请将 "info" 改为 "debug"...
LogLevel info

# 管理员用户组...
SystemGroup wheel

# 在端口 631 上监听连接。
Port 631
#Listen localhost:631
Listen /var/run/cups.sock

# 显示局域网共享的打印机。
Browsing On
BrowseOrder allow,deny
#BrowseAllow @LOCAL
BrowseAllow 192.168.1.* # 更改为本地 LAN 设置
BrowseAddress 192.168.1.* # 更改为本地 LAN 设置

# 需要身份验证时的默认身份验证类型...
DefaultAuthType Basic
DefaultEncryption Never # 注释掉此行以允许加密

# 允许 LAN 上任何计算机访问服务器
<Location />
  Order allow,deny
  #Allow localhost
  Allow 192.168.1.* # 更改为本地 LAN 设置
</Location>

# 允许 LAN 上任何计算机访问管理员页面
<Location /admin>
  #要求加密
  Order allow,deny
  #Allow localhost
  Allow 192.168.1.* # 更改为本地 LAN 设置
</Location>

# 允许 LAN 上任何计算机访问配置文件
<Location /admin/conf>
  AuthType Basic
  Require user @SYSTEM
  Order allow,deny
  #Allow localhost
  Allow 192.168.1.* # 更改为本地 LAN 设置
</Location>

# 设置默认打印机/作业策略...
<Policy default>
  # 与作业相关的操作必须由所有者或管理员执行...
  <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs \
Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription \
Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job \
CUPS-Move-Job>
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  # 所有管理操作都需要管理员身份验证...
  <Limit Pause-Printer Resume-Printer Set-Printer-Attributes Enable-Printer \
Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs \
Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer \
Promote-Job Schedule-Job-After CUPS-Add-Printer CUPS-Delete-Printer CUPS-Add-Class \
CUPS-Delete-Class CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default>
    AuthType Basic
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # 只有所有者或管理员可以取消或认证作业...
  <Limit Cancel-Job CUPS-Authenticate-Job>
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  <Limit All>
    Order deny,allow
  </Limit>
</Policy>
r151864 | bde | 2005-10-29 09:34:50 -0700 (Sat, 29 Oct 2005) | 13 lines
Changed paths:
   M /head/lib/msun/src/e_rem_pio2f.c

Use double precision to simplify and optimize arg reduction for small
and medium size args too: instead of conditionally subtracting a float
17+24, 17+17+24 or 17+17+17+24 bit approximation to pi/2, always
subtract a double 33+53 bit one.  The float version is now closer to
the double version than to old versions of itself -- it uses the same
33+53 bit approximation as the simplest cases in the double version,
and where the float version had to switch to the slow general case at
|x| == 2^7*pi/2, it now switches at |x| == 2^19*pi/2 the same as the
double version.

This speeds up arg reduction by a factor of 2 for |x| between 3*pi/4 and
2^7*pi/4, and by a factor of 7 for |x| between 2^7*pi/4 and 2^19*pi/4.
#REV         #WHO #DATE                                        #TEXT

176410        bde 2008-02-19 07:42:46 -0800 (Tue, 19 Feb 2008) #include <sys/cdefs.h>
176410        bde 2008-02-19 07:42:46 -0800 (Tue, 19 Feb 2008) __FBSDID("$FreeBSD$");
  2116        jkh 1994-08-19 02:40:01 -0700 (Fri, 19 Aug 1994)
  2116        jkh 1994-08-19 02:40:01 -0700 (Fri, 19 Aug 1994) /* __ieee754_rem_pio2f(x,y)
  8870    rgrimes 1995-05-29 22:51:47 -0700 (Mon, 29 May 1995)  *
176552        bde 2008-02-25 05:33:20 -0800 (Mon, 25 Feb 2008)  * return the remainder of x rem pi/2 in *y
176552        bde 2008-02-25 05:33:20 -0800 (Mon, 25 Feb 2008)  * use double precision for everything except passing x
152535        bde 2005-11-16 18:20:04 -0800 (Wed, 16 Nov 2005)  * use __kernel_rem_pio2() for large x
  2116        jkh 1994-08-19 02:40:01 -0700 (Fri, 19 Aug 1994)  */
  2116        jkh 1994-08-19 02:40:01 -0700 (Fri, 19 Aug 1994)
176465        bde 2008-02-22 07:55:14 -0800 (Fri, 22 Feb 2008) #include <float.h>
176465        bde 2008-02-22 07:55:14 -0800 (Fri, 22 Feb 2008)
  2116        jkh 1994-08-19 02:40:01 -0700 (Fri, 19 Aug 1994) #include "math.h"
A B C D A B C D A B C D A B C D
CUPS on FreeBSD
http://www.cups.org/
print/gutenprint
print/hplip
dmesg(8)
http://localhost:631
print/gutenprint-cups
print/hplip
http://localhost:631
net/samba416
devfs(8)
FreeBSD 作为构建模块集
FreeBSD 项目
FreeBSD 作为构建模块集
与 FreeBSD 合作
结论
为何使用 BSD 样式许可证发布开源项目
二十年的伯克利 Unix:从 AT&T 拥有到自由再分发
创新发生在其他地方:开源作为商业战略
netgraph(3)
RTEMS
eCOS
多种架构
mac(9)
jail(2)
netgraph(4)
软更新:一种消除大多数同步写入的技术
geom(4)
FreeBSD 提交者指南
提交者指南
A project model for the FreeBSD Project
FreeBSD 发布分支
架构列表
未来发布的路线图
FreeBSD 发布工程
贡献给 FreeBSD 项目
公开邮件列表
文档化
Good Lisp 编程风格教程
Teach Yourself Programming in Ten Years
变更日志样本
The Architectural Principles of the Internet
style(9)
svnsync
通过 svn blame 生成的注释源代码清单
可以在网上查看
bug 报告
FreeBSD 相关的就业邮件列表
咨询公司和开发者的画廊
BSD 认证小组
项目邮件列表
如何聪明地提问
捐赠联络员
列出他们的需求
承认
The Architectural Principles of the Internet
Committer’s Guide
Innovation Happens Elsewhere: Open Source as Business Strategy
Contributing to the FreeBSD Project
Soft Updates: A Technique for Eliminating Most Synchronous Writes in the Fast Filesystem
Twenty Years of Berkeley Unix: From AT&T-Owned to Freely Redistributable
Open Sources: Voices from the Open Source Revolution
Why you should use a BSD style license for your Open Source Project
A project model for the FreeBSD Project
Tutorial on Good Lisp Programming Style
Teach Yourself Programming in Ten Years
How to ask questions the smart way
FreeBSD Release Engineering
Design elements of the FreeBSD VM system
dillon@apollo.backplane.com
ls(1)
ps(1)
ls(1)
ls(1)
《FreeBSD 的贡献者》
《OpenPGP 密钥》

提交者指南

摘要

本文档为 FreeBSD 提交者社区提供信息。所有新的提交者在开始之前应该阅读本文档,现有的提交者也强烈建议定期回顾。

本文档也可能对希望了解 FreeBSD 项目运作方式的社区成员有所帮助。

1. 管理详细信息

登录方式

主要 Shell 主机

freefall.FreeBSD.org

参考机器

SMTP 主机

src/ Git 仓库

ssh://git@gitrepo.FreeBSD.org/src.git

doc/ Git 仓库

ssh://git@gitrepo.FreeBSD.org/doc.git

ports/ Git 仓库

ssh://git@gitrepo.FreeBSD.org/ports.git

内部邮件列表

developers(技术上称为 all-developers),doc-developers,doc-committers,ports-developers,ports-committers,src-developers,src-committers。(每个项目的仓库都有自己的 -developers 和 -committers 邮件列表。可以在 freefall.FreeBSD.org 的 /local/mail/repository-name-developers-archive 和 /local/mail/repository-name-committers-archive 中找到这些列表的归档。)

核心团队月度报告

FreeBSD.org 集群上的 /home/core/public/reports

Ports 管理团队月度报告

FreeBSD.org 集群上的 /home/portmgr/public/monthly-reports

重要的 src/ Git 分支

stable/n(n-STABLE),main(-CURRENT)

有用的链接:

2. FreeBSD 的 OpenPGP 密钥

2.1. 创建密钥

可以使用现有的密钥,但应先通过 documentation/tools/checkkey.sh 检查。此时,确保密钥具有 FreeBSD 用户 ID。

对于那些还没有 OpenPGP 密钥,或者需要新的密钥来符合 FreeBSD 安全要求的人,以下是如何生成一个密钥的步骤。

  1. # 按优先顺序列出用于签名的首选算法(从强到弱)
    personal-digest-preferences SHA512 SHA384 SHA256 SHA224
    # 新密钥的默认首选项
    default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 CAMELLIA256 AES192 CAMELLIA192 AES CAMELLIA128 CAST5 BZIP2 ZLIB ZIP Uncompressed
  2. 生成密钥:

    % gpg --full-gen-key
    gpg (GnuPG) 2.1.8; Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    
    Warning: using insecure memory!
    Please select what kind of key you want:
       (1) RSA and RSA (default)
       (2) DSA and Elgamal
       (3) DSA (sign only)
       (4) RSA (sign only)
    Your selection? 1
    RSA keys may be between 1024 and 4096 bits long.
    What keysize do you want? (2048) 2048 ①
    Requested keysize is 2048 bits
    Please specify how long the key should be valid.
    	 0 = key does not expire
          <n>  = key expires in n days
          <n>w = key expires in n weeks
          <n>m = key expires in n months
          <n>y = key expires in n years
    Key is valid for? (0) 3y ②
    Key expires at Wed Nov  4 17:20:20 2015 MST
    Is this correct? (y/N) y
    GnuPG needs to construct a user ID to identify your key.
    
    Real name: Chucky Daemon ③
    Email address: notreal@example.com
    Comment:
    You selected this USER-ID:
    "Chucky Daemon <notreal@example.com>"
    
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
    You need a Passphrase to protect your secret key.
  • ① 2048 位密钥,三年有效期目前提供足够的保护(2022-10)。

  • ② 三年的密钥有效期足够短,以使得计算能力增强后不再使用过时的密钥,但又足够长,以减少密钥管理问题。

  • ③ 在此处使用真实姓名,最好与政府签发的 ID 上显示的姓名匹配,以便其他人更容易验证你的身份。在 Comment 部分可以输入一些帮助别人识别你的文字。

新密钥的提交步骤请参见 [committers-guide](crossref:committers-guide)。

3. Kerberos 和 LDAP Web 密码用于 FreeBSD 集群

FreeBSD 集群需要 Kerberos 密码来访问某些服务。由于 LDAP 在集群中代理 Kerberos,Kerberos 密码也作为 LDAP Web 密码。一些需要此密码的服务包括:

要在 FreeBSD 集群中创建一个新的 Kerberos 账户,或使用随机密码生成器重置现有账户的 Kerberos 密码:

% ssh kpasswd.freebsd.org

注意

必须从 FreeBSD.org 集群外部的机器执行此操作。

也可以通过登录 freefall.FreeBSD.org 并运行以下命令手动设置 Kerberos 密码:

% kpasswd

注意

如果之前未使用过 FreeBSD.org 集群的 Kerberos 认证服务,则会显示 Client unknown 错误。此错误意味着必须先使用上面显示的 ssh kpasswd.freebsd.org 方法初始化 Kerberos 账户。

4. 提交权限类型

FreeBSD 仓库包含多个组件,这些组件合并后支持基本操作系统源代码、文档、第三方应用 Port 基础设施和各种维护的工具。当分配 FreeBSD 提交权限时,会指定可以使用该权限的树区域。通常,权限所在的区域反映了谁授权分配该提交权限。以后可能会添加额外的权限区域:当这种情况发生时,提交者应遵循该区域的正常提交权限分配程序,向相关实体寻求批准,并可能在该区域获得一段时间的导师支持。

提交者类型

负责

树区域组件

src

core@

src/

doc

doceng@

doc/,ports/,src/ 文档

ports

portmgr@

ports/

在开发区域权限概念之前分配的提交权限可能适用于树的许多部分。然而,常识表明,提交者如果未曾在某个区域工作过,应在提交前寻求审查,向适当的责任方寻求批准,并/或与导师合作。由于不同区域的代码维护规则不同,这既有助于提交者在不熟悉的区域工作,也有助于树上的其他人。

鼓励提交者在开发过程中寻求审查,无论工作发生在哪个区域。

4.1. 提交者在其他树区域活动的政策

  • 所有提交者都可以修改 src/share/misc/committers-*.dot,src/usr.bin/calendar/calendars/calendar.freebsd 和 ports/astro/xearth/files。

  • doc 提交者可以在没有 src 提交者批准的情况下提交文档更改,例如手册页、README 文件、fortune 数据库、日历文件以及注释修复,前提是提交时遵循正常的审查和管理程序。

  • 所有提交者都可以在拥有适当权限的非导师提交者批准的情况下对一切其他区域进行更改。导师提交者可以提供 Reviewed by 但不能提供 Approved by。

  • 提交者可以通过通常的流程获得附加的权限,寻找导师并由导师向 core、doceng 或 portmgr 提交提案,获批后将被加入到 access 并进入正常的导师期,在此期间会持续提供 Approved by。

4.1.1. 文档隐式(普遍)批准

普遍批准适用于以下类型的修复:

  • 错别字

  • 微小修复 诸如标点、URL、日期、路径和文件名的错误信息等常见错误,这些错误可能使读者困惑。

多年来,某些隐式批准已经在文档树中获得。这些最常见的情况包括:

5. Git 入门

5.1. Git 基础

本文档假设你已经阅读过这些内容,并尽量不重复基础知识(尽管会简要介绍)。

5.2. Git 简明入门

这份入门指南的范围比旧的 Subversion 入门指南小,但应该涵盖基本的操作。

5.2.1. 范围

如果你想下载 FreeBSD,编译源代码,并通过这种方式保持更新,那么这份指南适合你。它涵盖了获取源代码、更新源代码、二分搜索,并简要讲解如何处理一些本地更改。它主要覆盖基础内容,并在需要时提供深入学习的好指引。当读者觉得基础内容不足时,其他部分的指南会涉及更高级的贡献主题。

5.2.2. 开发者入门

本节介绍了提交者如何推送开发者或贡献者的提交。

5.2.2.1. 日常使用

注意

以下示例中,替换 ${repo} 为所需的 FreeBSD 仓库名称:doc、ports 或 src。

  • 克隆仓库:

    % git clone -o freebsd --config remote.freebsd.fetch='+refs/notes/*:refs/notes/*' https://git.freebsd.org/${repo}.git

    然后你应该会看到官方镜像作为远程源:

    % git remote -v
    freebsd  https://git.freebsd.org/${repo}.git (fetch)
    freebsd  https://git.freebsd.org/${repo}.git (push)
  • 配置 FreeBSD 提交者数据: 在 repo.freebsd.org 上的提交钩子会检查 Commit 字段是否与 FreeBSD.org 上的提交者信息匹配。最简单的方式是通过执行 /usr/local/bin/gen-gitconfig.sh 脚本获取推荐的配置:

    % gen-gitconfig.sh
    [...]
    % git config user.name (your name in gecos)
    % git config user.email (your login)@FreeBSD.org
  • 设置推送 URL:

    % git remote set-url --push freebsd git@gitrepo.freebsd.org:${repo}.git

    然后你应该会看到分开的 fetch 和 push URL,这是最有效的配置:

    % git remote -v
    freebsd  https://git.freebsd.org/${repo}.git (fetch)
    freebsd  git@gitrepo.freebsd.org:${repo}.git (push)

    再次注意,gitrepo.freebsd.org 已被规范化为 repo.freebsd.org。

  • 安装提交消息模板钩子: 对于 doc 仓库:

    % cd .git/hooks
    % ln -s ../../.hooks/prepare-commit-msg

    对于 ports 仓库:

    % git config --add core.hooksPath .hooks

    对于 src 仓库:

    % cd .git/hooks
    % ln -s ../../tools/tools/git/hooks/prepare-commit-msg

5.2.2.2. admin 分支

access 和 mentors 文件存储在每个仓库的孤立分支 internal/admin 中。

以下是如何将 internal/admin 分支检出到本地名为 admin 的分支的示例:

% git config --add remote.freebsd.fetch '+refs/internal/*:refs/internal/*'
% git fetch
% git checkout -b admin internal/admin

或者,你可以为 admin 分支添加一个工作树:

git worktree add -b admin ../${repo}-admin internal/admin

要在 Web 上浏览 internal/admin 分支:<a href="https://cgit.freebsd.org/$%7Brepo%7D/log/?h=internal/admin" class="bare">https://cgit.freebsd.org/${repo}/log/?h=internal/admin</a>

推送时,指定完整的 refspec:

git push freebsd HEAD:refs/internal/admin

5.2.3. 保持与 FreeBSD src 树同步

第一步:克隆树。此操作将下载整个树。下载有两种方式。大多数人会选择对仓库进行深度克隆。但是,有时你可能希望进行浅克隆。

5.2.3.1. 分支名称

FreeBSD-CURRENT 使用 main 分支。

main 是默认分支。

对于 FreeBSD-STABLE,分支名称包括 stable/12 和 stable/13。

对于 FreeBSD-RELEASE,发布工程分支名称包括 releng/12.4 和 releng/13.2。

  • main 和 stable/⋯ 分支是开放的

  • releng/⋯ 分支在发布标签时会被冻结。

示例:

5.2.3.2. 仓库

注意:该项目不使用子模块,因为子模块与我们的工作流程和开发模型不太契合。如何跟踪第三方应用程序的变化在其他地方有讨论,通常对普通用户来说并不重要。

5.2.3.3. 深度克隆

深度克隆会拉取整个树,以及所有历史和分支。这是最容易操作的方式。它还允许你使用 Git 的 worktree 功能,将所有活跃分支签出到不同的目录,但只有一个仓库副本。

% git clone -o freebsd $URL -b branch [<directory>]

— 这将创建一个深度克隆。branch 应该是前面列出的分支之一。如果没有指定 branch,将使用默认分支 (main)。如果没有指定 <directory>,新目录的名称将与仓库的名称(doc、ports 或 src)匹配。

如果你对历史感兴趣,计划进行本地更改,或者计划在多个分支上工作,那么你会需要一个深度克隆。它也是最容易保持更新的方式。如果你对历史有兴趣,但只在一个分支上工作并且空间有限,你还可以使用 --single-branch 仅下载一个分支(尽管一些合并提交将不会引用已合并的分支,这对某些关注详细历史的用户可能很重要)。

5.2.3.4. 浅克隆

浅克隆仅复制当前的代码,而不会下载历史。这在你需要构建特定版本的 FreeBSD 时非常有用,或者当你刚开始并计划全面跟踪树时。你还可以使用它来限制历史版本的数量。然而,下面会提到这种方法的一个重要限制。

% git clone -o freebsd -b branch --depth 1 $URL [dir]

这将克隆仓库,但只包含最新版本,其余历史不会下载。如果你之后改变主意,可以使用 git fetch --unshallow 来获取旧的历史。

警告

当你进行浅克隆时,uname 输出中将不会显示提交计数。这可能使得在发布安全公告时,判断系统是否需要更新变得更困难。

5.2.3.5. 构建

下载完成后,构建过程按照手册中的描述进行,例如:

% cd src
% make buildworld
% make buildkernel
% make installkernel
% make installworld

因此,这里不再深入讨论。

5.2.3.6. 更新

更新两种类型的树使用相同的命令。这会拉取自上次更新以来的所有修改。

% git pull --ff-only

这将更新树。在 Git 中,"fast forward" 合并是指仅需要设置新的分支指针,而不需要重新创建提交。通过始终执行快速前进合并/拉取,你将确保拥有 FreeBSD 树的准确副本。如果你希望维护本地补丁,这一点非常重要。

见下文如何管理本地更改。最简单的方法是使用 --autostash 选项与 git pull 命令一起使用,但也有更复杂的选项可用。

5.2.4. 选择特定版本

在 Git 中,git checkout 用于检出分支和特定版本。Git 的版本是长哈希值,而不是顺序编号。

当你检出特定版本时,只需在命令行中指定你想要的哈希值(git log 命令可以帮助你决定要选择哪个哈希值):

% git checkout 08b8197a74

然后你就会检出该版本。你将看到类似如下的消息:

Note: checking out '08b8197a742a96964d2924391bf9fdfeb788865d'.

You are in a 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 08b8197a742a hook gpiokeys.4 to the build

其中最后一行是由你检出的哈希值生成的,提交消息的第一行来自该版本的提交。哈希值可以缩写为最短的唯一长度。Git 本身对于显示的数字长度并不一致。

5.2.5. 二分查找(Bisect)

有时,事情出了问题。最后一个版本工作正常,但你刚刚更新的版本不行。开发人员可能会要求你通过二分查找来追踪是哪个提交导致了回归问题。

git bisect start --first-parent 将开始二分查找过程。接下来,你需要告诉 Git 要遍历的版本范围。git bisect good XXXXXX 会告诉它工作版本,git bisect bad XXXXX 会告诉它坏版本。坏版本通常是 HEAD(即你当前检出的版本)。好的版本是你上次检出的版本。--first-parent 参数是必需的,以确保后续的 git bisect 命令不会试图检出缺少完整 FreeBSD 源代码树的供应商分支。

技巧

如果你想知道上次检出的版本,可以使用

5ef0bd68b515 (HEAD -> main, freebsd/main, freebsd/HEAD) HEAD@{0}: pull --ff-only: Fast-forward
a8163e165c5b (upstream/main) HEAD@{1}: checkout: moving from b6fb97efb682994f59b21fe4efb3fcfc0e5b9eeb to main
...

显示我将工作树移动到 main 分支(a816…),然后从上游更新(到 5ef0…)。在这种情况下,坏版本是 HEAD(或 5ef0bd68b515),好版本是 a8163e165c5b。正如你从输出中看到的,HEAD@{1} 通常也有效,但如果在更新后做了其他操作再发现需要进行二分查找时,它并不总是可靠的。

首先设置“好”版本,然后设置“坏”版本(尽管顺序无关紧要)。当你设置坏版本时,它会提供有关过程的统计信息:

% git bisect start --first-parent
% git bisect good a8163e165c5b
% git bisect bad HEAD
Bisecting: 1722 revisions left to test after this (roughly 11 steps)
[c427b3158fd8225f6afc09e7e6f62326f9e4de7e] Fixup r361997 by balancing parens.  Duh.

然后,你需要构建并安装该版本。如果它是好的,输入 git bisect good;如果它是坏的,输入 git bisect bad。如果该版本无法编译,输入 git bisect skip。每一步之后你都会看到类似的消息。完成后,将坏版本报告给开发人员(或自己修复 bug 并提交补丁)。git bisect reset 会结束该过程,并将你带回到你开始的地方(通常是 main 的顶端)。同样,git bisect 手册(如上所述)是处理错误或特殊情况时的良好资源。

5.2.6. 使用 GnuPG 签名提交、标签和推送

Git 知道如何签名提交、标签和推送。当你签名 Git 提交或标签时,你可以证明提交的代码来自你,并且在传输过程中没有被更改。你还可以证明是你提交了代码,而不是别人。

最好的方法是告诉 Git,你总是希望签名提交、标签和推送。你可以通过设置以下几个配置变量来实现:

% git config --add user.signingKey LONG-KEY-ID
% git config --add commit.gpgSign true
% git config --add tag.gpgSign true
% git config --add push.gpgSign if-asked

注意

为了避免可能的冲突,确保给 Git 提供一个长密钥 ID。你可以通过以下命令获得长 ID:gpg --list-secret-keys --keyid-format LONG。

技巧

如果要使用特定的子密钥,而不让 GnuPG 解析为主密钥,可以在密钥后加上 !。例如,要使用子密钥 DEADBEEF 进行加密,使用 DEADBEEF!。

5.2.6.1. 验证签名

提交签名可以通过运行 git verify-commit <commit hash> 或 git log --show-signature 来验证。

标签签名可以通过 git verify-tag <tag name> 或 git tag -v <tag name> 来验证。

5.2.7. Ports 考虑事项

ports 树的操作方式相同,分支名称不同,且仓库的位置也不同。

5.2.7.1. 提交信息格式

提交信息应格式化如下:

category/port: 总结。

描述你为什么做成这些修改。

PR:      12345

重要

第一行是提交的主题,包含了更改的 port 以及提交的总结。它应该包含 50 个字符或更少。

提交信息的其余部分应该在 72 个字符的边界处换行。

应该有一个空行将它与提交信息的其余部分分开。

如果有任何元数据字段,它们应该与提交信息之间有另一个空行,以便容易区分。

5.2.8. 管理本地更改

本节讨论跟踪本地更改。如果你没有本地更改,可以跳过本节。

一个重要的事项是:所有的更改在推送之前都是本地的。与 Subversion 不同,Git 使用的是分布式模型。对于用户而言,大多数操作几乎没有区别。然而,如果你有本地更改,你可以使用相同的工具来管理它们,就像你使用相同的工具来拉取 FreeBSD 的更改一样。所有你尚未推送的更改都是本地的,可以轻松修改(如 git rebase,稍后会讨论)。

5.2.8.1. 保留本地更改

这种方法适用于你对树所做的小改动。当你有一些不那么微小的更改时,最好还是保留一个本地分支并进行 rebase。git stash 也与 git pull 命令集成:只需在命令行上添加 --autostash 即可。

5.2.8.2. 保留本地分支

与 Subversion 不同,Git 保留本地分支更为简便。在 Subversion 中,你需要合并提交并解决冲突。虽然这可以管理,但可能导致历史记录变得复杂,在上游提交时可能困难重重,或者如果你需要重新创建该过程时也很麻烦。Git 也允许合并,存在相同的问题。这是管理分支的一种方式,但它的灵活性最差。

除了合并,Git 还支持“变基”的概念,这可以避免这些问题。git rebase 命令会将一个分支的所有提交重新播放到父分支的新位置。我们将覆盖使用它时最常见的场景。

5.2.8.2.1. 创建分支

假设你想对 FreeBSD 的 ls 命令做一个更改,让它永远不显示颜色。做这个更改有很多原因,但我们用这个作为示例。FreeBSD 的 ls 命令会时不时地更新,你需要应对这些变化。幸运的是,使用 Git rebase 通常是自动完成的。

% cd src
% git checkout main
% git checkout -b no-color-ls
% cd bin/ls
% vi ls.c     # 进行更改
% git diff    # 查看更改
diff --git a/bin/ls/ls.c b/bin/ls/ls.c
index 7378268867ef..cfc3f4342531 100644
--- a/bin/ls/ls.c
+++ b/bin/ls/ls.c
@@ -66,6 +66,7 @@ __FBSDID("$FreeBSD$");
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#undef COLORLS
 #ifdef COLORLS
 #include <termcap.h>
 #include <signal.h>
% # 这些更改看起来不错,进行提交...
% git commit ls.c

提交会让你进入编辑器描述你所做的更改。完成后,你就有了一个 本地 分支。按照手册中的说明,像往常一样构建并安装它。Git 与其他版本控制系统的不同之处在于,你必须显式告诉它哪些文件需要提交。我在提交命令行上选择了文件,但你也可以使用 git add 来进行提交,许多更深入的教程会涉及到这一点。

5.2.8.2.2. 更新时

当需要引入新版本时,操作几乎与没有分支时一样。你会像上面那样更新,但在更新之前会有一个额外的命令,在更新后也有一个额外的命令。以下假设你从未修改过树。开始 rebase 操作时,必须确保树是干净的(Git 要求如此)。

% git checkout main
% git pull --ff-only
% git rebase -i main no-color-ls

这会打开一个编辑器,列出所有提交。对于这个示例,不要做任何更改。这通常是在更新基线时所做的(尽管你也可以使用 Git rebase 命令来整理分支中的提交)。

完成上述操作后,你需要将 ls.c 文件中的提交从旧版本的 FreeBSD 移动到新版本中。

有时会出现合并冲突。这是正常的,不用恐慌。你只需像处理任何其他合并冲突一样处理它们。为了简化起见,我将描述一个可能出现的常见问题。完整的处理方式可以在本节末尾找到。

假设包括文件发生了变化,且进行了一个彻底的转换,改用了 terminfo,并且选项名称发生了变化。当你更新时,可能会看到如下内容:

Auto-merging bin/ls/ls.c
CONFLICT (content): Merge conflict in bin/ls/ls.c
error: could not apply 646e0f9cda11... no color ls
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 646e0f9cda11... no color ls

这看起来可能很吓人。如果你打开编辑器,你会看到这是一个典型的三方合并冲突解决方法,你可能已经熟悉了来自其他源代码管理系统的冲突处理(其余的 ls.c 被省略):

<<<<<<< HEAD
 #ifdef COLORLS_NEW
 #include <terminfo.h>
 =======
 #undef COLORLS
 #ifdef COLORLS
 #include <termcap.h>
 >>>>>>> 646e0f9cda11... no color ls
....

新代码在前,你的代码在后。

正确的修复方法是:在 #ifdef 之前添加 #undef COLORLS_NEW,然后删除旧的更改:

#undef COLORLS_NEW
#ifdef COLORLS_NEW
#include <terminfo.h>
....

保存文件后,rebase 操作被中断,因此你需要完成它:

% git add ls.c
% git rebase --continue
....

这告诉 Git ls.c 已经修复,继续进行 rebase 操作。如果有冲突,你会被带入编辑器来更新提交信息。如果提交信息仍然准确,只需退出编辑器。

如果你在 rebase 时遇到困难,不用恐慌。运行 git rebase --abort 会将你带回到干净的状态。重要的是,开始时树必须是未修改的。顺便说一句,前面提到的 git reflog 在这里非常有用,因为它会列出所有(中间)提交,你可以查看或检查它们,或进行 cherry-pick。

5.2.8.3. 切换到另一个 FreeBSD 分支

如果你希望从 stable/12 切换到当前分支。如果你有一个深度克隆,以下命令即可:

% git checkout main
% # 在此构建并安装...

但是,如果你有本地分支,则有一两个注意事项。首先,rebase 会重写历史,因此你可能需要采取措施保存它。其次,切换分支通常会导致更多的冲突。如果我们假设上述示例相对于 stable/12,然后切换到 main,我建议执行以下操作:

% git checkout no-color-ls
% git checkout -b no-color-ls-stable-12   # 为该分支创建另一个名称
% git rebase -i stable/12 no-color-ls --onto main

上述操作会执行以下步骤:首先检出 no-color-ls 分支。然后创建一个新名称(no-color-ls-stable-12),以防你需要返回它。接着,你将进行 rebase,并将其应用到 main 分支上。这会查找所有当前 no-color-ls 分支中的提交(直到与 stable/12 分支合并的地方),然后将它们重新应用到 main 分支上,从而在 main 分支上创建一个新的 no-color-ls 分支(这就是为什么我让你创建一个占位名称)。

5.3. MFC(从当前合并)流程

5.3.1. 概述

MFC 工作流可以总结为 git cherry-pick -x 加 git commit --amend 来调整提交信息。对于多个提交,使用 git rebase -i 将它们压缩到一起并编辑提交信息。

5.3.2. 单个提交 MFC

% git checkout stable/X
% git cherry-pick -x $HASH --edit

对于 MFC 提交,例如供应商导入,你需要为 cherry-pick 操作指定一个父提交。通常情况下,这将是你从中进行 cherry-pick 的分支的“第一个父”:

% git checkout stable/X
% git cherry-pick -x $HASH -m 1 --edit

如果出现问题,你需要通过 git cherry-pick --abort 中止 cherry-pick 操作,或者修复问题后执行 git cherry-pick --continue。

完成 cherry-pick 后,使用 git push 推送。如果由于提交竞争失败而导致错误,使用 git pull --rebase 并重新尝试推送。

5.3.3. MFC 到 RELENG 分支

向需要批准的分支执行 MFC 时需要更加小心。无论是典型的合并操作还是特殊的直接提交,过程相同:

  • 首先将合并或直接提交到适当的 stable/X 分支,然后再合并到 releng/X.Y 分支。

  • 使用 stable/X 分支中的哈希值执行 MFC 到 releng/X.Y 分支。

  • 在提交信息中保留两个 "cherry picked from" 行。

  • 在编辑器中确保添加 Approved by: 行。

% git checkout releng/13.0
% git cherry-pick -x $HASH --edit

如果忘记添加 Approved by: 行,可以通过 git commit --amend 编辑提交信息,然后再推送更改。

5.3.4. 多个提交 MFC

% git checkout -b tmp-branch stable/X
% for h in $HASH_LIST; do git cherry-pick -x $h; done
% git rebase -i stable/X
# 将每个提交(除了第一个)标记为 'squash'
# 如果需要,更新提交信息以反映所有提交的元素。
# 确保保留 "cherry picked from" 行。
% git push freebsd HEAD:stable/X

如果由于提交竞争失败而导致推送失败,执行 rebase 并重新尝试:

% git checkout stable/X
% git pull
% git checkout tmp-branch
% git rebase stable/X
% git push freebsd HEAD:stable/X

MFC 完成后,你可以删除临时分支:

% git checkout stable/X
% git branch -d tmp-branch

5.3.5. MFC 供应商导入

供应商导入是唯一会在 main 分支中创建合并提交的操作。将合并提交 cherry-pick 到 stable/XX 时会遇到额外的困难,因为合并提交有两个父提交。通常,你会希望选择第一个父提交的差异,因为它是与 main 的差异(尽管也可能有一些例外)。

% git cherry-pick -x -m 1 $HASH

通常这就是你需要的命令。这将告诉 cherry-pick 应用正确的差异。

有一些情况(希望是少数)可能是 main 分支被转换脚本反向合并了。如果是这种情况(虽然我们还没有遇到过这种情况),你应该将上述命令中的 -m 1 改为 -m 2,以获取正确的父提交。操作如下:

% git cherry-pick --abort
% git cherry-pick -x -m 2 $HASH

--abort 会清理第一次失败的尝试。

5.3.6. 重新执行 MFC

如果执行了 MFC 操作并且结果非常糟糕,你希望重新开始,最简单的方式是使用 git reset --hard,如下所示:

% git reset --hard freebsd/stable/12

不过,如果你希望保留某些提交,而舍弃其他提交,使用 git rebase -i 更为合适。

5.3.7. MFC 时的注意事项

在将源代码提交到 stable 和 releng 分支时,我们有以下目标:

  • 清晰地区分直接提交与从其他分支合并的提交。

  • 避免在 stable 和 releng 分支中引入已知的破坏性更改。

  • 允许开发者确定哪些更改已经从一个分支合并到另一个分支,哪些没有。

在 Subversion 中,我们使用以下做法来实现这些目标:

  • 使用 MFC 和 MFS 标签来标记从其他分支合并的提交。

  • 在合并更改时,将修复提交压缩到主要提交中。

  • 记录合并信息,以便 svn mergeinfo --show-revs 可以工作。

在 Git 中,我们需要采用不同的策略来实现相同的目标。本文件旨在定义在使用 Git 合并源代码提交时的最佳实践,以实现这些目标。一般来说,我们更倾向于使用 Git 的原生支持来实现这些目标,而不是强制执行基于 Subversion 模型的做法。

一项通用说明:由于 Git 与 Subversion 存在技术差异,我们将不会在 stable 或 releng 分支中使用 Git“合并提交”(通过 git merge 创建的提交)。相反,当本文件提到“合并提交”时,它指的是最初在 main 分支上创建的提交,并通过某种方式使用 git cherry-pick 被复制或“落地”到 stable 分支,或者从 stable 分支复制到 releng 分支的提交。

5.3.8. 查找合适的哈希以进行 MFC

Git 提供了内建支持,使用 git cherry 和 git log --cherry 命令。这些命令比较提交的原始差异(但不比较其他元数据,如日志消息),以确定两个提交是否相同。当每个提交从 main 分支单独合并到稳定分支时,这些命令非常有效,但如果从 main 分支合并多个提交并将它们合并为一个提交到稳定分支时,这种方法就会失效。为了绕过这些困难,项目广泛使用 git cherry-pick -x 保留所有行,并且正在开发自动化工具以利用这一点。

5.3.9. 提交消息标准

5.3.9.1. 标记 MFC

项目采用了以下标记 MFC 的做法:

  • 使用 git cherry-pick 的 -x 标志。这会在提交消息中添加一行,包含合并时原始提交的哈希。由于这是 Git 直接添加的,提交者在合并时不需要手动编辑提交日志。

当合并多个提交时,保留所有的 cherry picked from 行。

5.3.9.2. 是否修剪元数据?

在 Subversion(甚至 CVS)中,并未清晰记录如何为 MFC 提交格式化日志消息中的元数据。应该保留原始提交的元数据不变,还是应该修改以反映 MFC 提交本身的信息?

历史做法有所不同,尽管某些差异与字段有关。例如,涉及 PR 的 MFC 通常会在 MFC 中包含 PR 字段,以便将 MFC 提交包含在 bug 跟踪器的审核历史中。其他字段则不太明确。例如,Phabricator 显示的是最后一个提交与评审标签相关的 diff,因此如果在 MFC 中包含 Phabricator URL,会替换主提交为已落地的提交。审查人列表也不明确。如果审查人批准了对 main 的更改,这是否意味着他们也批准了 MFC 提交?如果是完全相同的代码或者仅做了微小修改,那么答案可能是肯定的。但对于更复杂的修改来说,这显然不成立。即使代码完全相同,若提交没有冲突但引入了 ABI 更改,审查人可能会因为 ABI 改变而批准 main 提交,但可能不会批准相同的提交。对此,必须根据具体情况做出判断,直到能够就清晰的指南达成一致。

对于由 re@ 管理的 MFC,增加了新的元数据字段,如 Approved by 标签,用于批准的提交。这些新元数据必须通过 git commit --amend 或类似命令添加,在原始提交经过审核和批准后添加。我们也可能希望将一些元数据字段保留在 MFC 提交中,比如 Phabricator URL,以便将来由 re@ 使用。

保留现有元数据提供了一种非常简单的工作流。开发者使用 git cherry-pick -x,无需编辑日志消息。

如果我们选择调整 MFC 中的元数据,开发者将需要显式地通过使用 git cherry-pick --edit 或 git commit --amend 来编辑日志消息。然而,与 SVN 相比,至少现有的提交消息可以预先填充,并且可以在不重新输入整个提交消息的情况下添加或删除元数据字段。

总的来说,开发者可能需要为非平凡的 MFC 提交整理其提交消息。

5.4. 使用 Git 进行供应商导入

本节详细介绍了使用 Git 进行供应商导入的过程。

5.4.1. 分支命名约定

所有供应商分支和标签都以 vendor/ 开头。这些分支和标签默认是可见的。

注意

本章遵循的约定是,freebsd 为官方 FreeBSD Git 仓库的源名称。如果你使用不同的约定,请在以下示例中将 freebsd 替换为你使用的名称。

我们将以更新 NetBSD 的 mtree 为例,说明其在我们树中的供应商分支。该供应商分支为 vendor/NetBSD/mtree。

5.4.2. 更新旧的供应商导入

供应商树通常只包含适合 FreeBSD 的第三方软件子集。这些树通常比 FreeBSD 树要小得多。因此,Git 工作树非常小且快速,且是推荐使用的方法。确保所选目录(例如 ../mtree)尚不存在。

% git worktree add ../mtree vendor/NetBSD/mtree

5.4.3. 更新供应商分支中的源代码

准备一个完整、干净的供应商源代码树。导入所有内容,但只合并所需的部分。

以下示例假设 NetBSD 源代码已从其 GitHub 镜像克隆到 ~/git/NetBSD。请注意,“上游”可能已经添加或删除了文件,因此我们要确保删除操作也被传播。[net/rsync](https://cgit.freebsd.org/ports/tree/net/rsync/) 通常已安装,因此我将使用它。

% cd ../mtree
% rsync -va --del --exclude=".git" ~/git/NetBSD/usr.sbin/mtree/ .
% git add -A
% git status
...
% git diff --staged
...
% git commit -m "Vendor import of NetBSD's mtree at 2020-12-11"
[vendor/NetBSD/mtree 8e7aa25fcf1] Vendor import of NetBSD's mtree at 2020-12-11
 7 files changed, 114 insertions(+), 82 deletions(-)
% git tag -a vendor/NetBSD/mtree/20201211

至关重要的是验证你正在导入的源代码来自可信的来源。许多开源项目使用加密签名来签署代码更改、Git 标签和/或源代码 tarball。始终验证这些签名,并使用诸如 jail、chroot 等隔离机制,结合一个专用的、非特权用户账户,该账户与你日常使用的账户不同(有关详细信息,请参阅下文的“更新 FreeBSD 源树”部分),直到你确信所导入的源代码是安全的。跟踪上游开发并偶尔检查上游代码更改,有助于提高代码质量并使所有参与者受益。导入到供应商区域之前,检查 git diff 结果也是一个好主意。

始终运行 git diff 和 git status 命令,并仔细检查结果。如有疑问,使用 git annotate 查看供应商分支或上游 Git 仓库,了解是谁做了更改以及为什么。

上面的示例中我们使用了 -m 来说明,但你应该在编辑器中撰写适当的消息(使用提交消息模板)。

同样,重要的是使用 git tag -a 创建一个注解标签,否则推送将被拒绝。只有注解标签才能被推送。注解标签使你有机会输入提交消息,输入你导入的版本以及该版本中的任何重要新特性或修复。

5.4.4. 更新 FreeBSD 副本

此时,你可以将导入的供应商代码推送到我们的仓库中的 vendor 分支。

% git push --follow-tags freebsd vendor/NetBSD/mtree

--follow-tags 告诉 git push 也推送与本地提交版本相关联的标签。

5.4.5. 更新 FreeBSD 源树

现在你需要更新 FreeBSD 中的 mtree。源代码位于 contrib/mtree 目录下,因为它是上游软件。

有时,我们可能需要对贡献的代码进行更改,以更好地满足 FreeBSD 的需求。尽可能地,请尝试将本地更改贡献回上游项目,这有助于它们更好地支持 FreeBSD,也节省了将来导入更新时解决冲突的时间。

% cd ../src
% git subtree merge -P contrib/mtree vendor/NetBSD/mtree

这将生成一个子树合并提交,将 contrib/mtree 与本地的 vendor/NetBSD/mtree 分支合并。检查合并结果的 diff 和上游分支的内容。如果合并减少了本地更改,仅保留了空行或缩进更改等琐碎差异,请尝试修改本地更改以减少与上游的差异,或尝试将剩余的更改贡献回上游项目。如果有冲突,你需要在提交之前解决它们。在合并提交消息中包含有关合并更改的详细信息。

一些开源软件包含 configure 脚本,用于生成定义如何构建代码的文件;通常,这些生成的文件(如 config.h)应在导入过程中更新。在执行此操作时,始终记住这些脚本是在当前用户的凭据下运行的可执行代码。此过程应始终在隔离环境中运行,理想情况下是在没有网络访问权限的 jail 内,并使用非特权账户;或者,至少使用与你日常使用的账户不同的专用账户,这样可以最大限度地减少遇到可能导致数据丢失或更严重的恶意代码的风险。使用隔离的 jail 还可以防止 configure 脚本检测到本地安装的软件包,从而避免出现意外结果。

在测试你的更改时,首先在 chroot 或 jailed 环境中运行,甚至可以先在虚拟机中测试,尤其是对于内核或库的修改。这种方法有助于防止与工作环境发生不良交互。对于许多基本系统组件都使用的库修改,这尤其有益。

5.4.6. 将你的更改与最新的 FreeBSD 源树重新基准化

由于当前的政策建议避免使用合并,如果在你有机会推送之前,FreeBSD 的 main 已经向前移动,那么你将不得不重新执行合并。

常规的 git rebase 或 git pull --rebase 无法像合并提交那样重新基准化,因此你需要重新创建该提交。

以下步骤应帮助你轻松地重新创建合并提交,就好像 git rebase --merge-commits 正常工作一样:

  • 进入仓库的顶层目录

  • 创建一个包含已合并树内容的旁支 XXX

  • 更新旁支 XXX,使其与 FreeBSD 的 main 分支合并并保持最新。

    • 在最坏的情况下,你仍然需要解决合并冲突(如果有的话),但这种情况应该非常罕见。

    • 解决冲突,并在需要时将多个提交压缩为 1 个提交(如果没有冲突,则不需要压缩)

  • 切换到 main 分支

  • 创建一个分支 YYY(如果出现问题,可以更容易地撤销)

  • 重新进行子树合并

  • 在子树合并中解决冲突之前,切换到 XXX 分支,将其内容覆盖到当前的 main 分支上。

    • 末尾的 . 是很重要的,且要确保处于仓库的顶层目录。

    • 而不是切换到 XXX 分支,它会将 XXX 的内容覆盖到仓库顶部。

  • 使用之前的提交消息提交结果(假设 XXX 分支只有一次合并提交)。

  • 确保这两个分支的内容是相同的。

  • 根据需要进行任何审查,包括让其他人检查一下,如果你认为这是必要的。

  • 推送提交,如果你再次“输掉了比赛”,只需重新执行这些步骤(参见下文的食谱)。

  • 提交到上游之后,删除这些分支,它们只是临时的。

以下是使用 mtree 示例时,执行上述操作的命令(# 后面的部分是注释,用于帮助将命令与上面的描述对应起来):

% cd ../src			# 进入仓库顶层目录
% git checkout -b XXX		# 为合并创建一个新的临时 XXX 分支
% git fetch freebsd		# 获取来自上游的更改
% git merge freebsd/main	# 合并更改并解决冲突
% git checkout -b YYY freebsd/main # 创建一个新的临时 YYY 分支以重新操作
% git subtree merge -P contrib/mtree vendor/NetBSD/mtree # 重新进行子树合并
% git checkout XXX .		# XXX 分支包含了解决冲突的内容
% git commit -c XXX~1		# -c 使用重新基准化之前的提交消息
% git diff XXX YYY		# 应该为空
% git show YYY			# 应该只包含你想要的更改,并且是来自供应商分支的合并提交

注意:如果提交出现问题,你可以通过重新执行创建分支的命令,并使用 -B 参数来重置 YYY 分支,以便从头开始:

% git checkout -B YYY freebsd/main # 如果重新开始更容易,可以创建一个新的临时 YYY 分支

5.4.7. 推送更改

当你认为更改已经准备好时,可以将其推送到 GitHub 或 GitLab 上的 fork 以供他人审查。Git 的一个优点是它允许你发布草稿供其他人查看。虽然 Phabricator 很适合内容审查,但发布更新后的供应商分支和合并提交使得其他人可以查看详细信息,因为这些更改最终将出现在仓库中。

经过审查后,当你确信这是一个好的更改时,你可以将其推送到 FreeBSD 仓库:

% git push freebsd YYY:main	# 将提交推送到上游的 'main' 分支
% git branch -D XXX		# 删除临时的 XXX 分支
% git branch -D YYY		# 删除临时的 YYY 分支

注意:我使用 XXX 和 YYY 是为了使它们显得明显,它们是糟糕的名称,应该只在你的机器上使用。如果你将这样的名称用于其他工作,那么你需要选择不同的名称,否则可能会丢失其他工作。这些名称并没有什么神奇之处。上游不会允许你推送它们,但无论如何,请注意上述命令。某些命令的语法与常规用法略有不同,而这种不同的行为对这个食谱的成功至关重要。

5.4.8. 如果需要重做操作

如果你尝试在上一节中进行推送并且失败,那么你应执行以下操作来“重做”事情。这个序列会保持提交消息始终为 XXX~1,以便简化提交过程。

% git checkout -B XXX YYY	# 重新创建临时分支 XXX 并切换到它
% git merge freebsd/main	# 合并更改并解决冲突
% git checkout -B YYY freebsd/main # 重新创建新的临时分支 YYY 以重新操作
% git subtree merge -P contrib/mtree vendor/NetBSD/mtree # 重新进行子树合并
% git checkout XXX .		# XXX 分支包含了解决冲突的内容
% git commit -c XXX~1		# -c 重新使用重新基准化之前的提交消息

然后,像之前一样检查并推送。

5.5. 创建一个新的供应商分支

有多种方式可以创建一个新的供应商分支。推荐的方式是创建一个新的仓库,然后将其与 FreeBSD 合并。如果要将 glorbnitz 导入到 FreeBSD 树中,版本为 3.1415。为了简单起见,我们不对该版本进行修剪。它是一个简单的用户命令,可以将 nitz 设备置于不同的魔法 glorb 状态,且其足够小,修剪不会节省太多空间。

5.5.1. 创建仓库

% cd /some/where
% mkdir glorbnitz
% cd glorbnitz
% git init
% git checkout -b vendor/glorbnitz

此时,你已创建了一个新的仓库,所有新的提交都将在 vendor/glorbnitz 分支上进行。

如果你更熟悉,可以直接在 FreeBSD 克隆中执行此操作,使用 git checkout --orphan vendor/glorbnitz。

5.5.2. 将源代码复制进去

由于这是一个新的导入,你可以直接使用 cp、tar 或甚至 rsync 将源代码复制进去,如上所示。假设没有点文件,我们将添加所有内容。

% cp -r ~/glorbnitz/* .
% git add *

此时,你应该有一个干净的 glorbnitz 复制,准备提交。

% git commit -m "Import GlorbNitz frobnosticator revision 3.1415"

5.5.3. 现在将其导入到我们的仓库

现在,你需要将分支导入到我们的仓库。

% cd /path/to/freebsd/repo/src
% git remote add glorbnitz /some/where/glorbnitz
% git fetch glorbnitz vendor/glorbnitz

请注意,vendor/glorbnitz 分支已添加到仓库中。此时,/some/where/glorbnitz 可以删除,如果你愿意的话。它只是为了达到某个目的而存在。

5.5.4. 打标签并推送

从这里开始的步骤与更新供应商分支时类似,只是没有更新供应商分支的步骤。

% git worktree add ../glorbnitz vendor/glorbnitz
% cd ../glorbnitz
% git tag --annotate vendor/glorbnitz/3.1415
# 确保提交正确,可以使用 "git show" 查看
% git push --follow-tags freebsd vendor/glorbnitz

“正确”意味着:

  1. 所有正确的文件都存在

  2. 没有错误的文件存在

  3. 供应商分支指向一个合理的地方

  4. 标签看起来很好,且是注释的

  5. 标签的提交消息简要概述自上次标签以来的新变化

5.5.5. 最终将其合并到主树中

% cd ../src
% git subtree add -P contrib/glorbnitz vendor/glorbnitz
# 确保提交正常,可以使用 "git show"
% git commit --amend   # 最后的提交消息检查
% git push freebsd

这里“正常”意味着:

  1. 所有正确的文件,并且没有错误的文件,已合并到 contrib/glorbnitz 中。

  2. 树中没有其他更改。

  3. 如果有任何重要的用户可见更改、升级注意事项等,应更新 UPDATING 文件。

注意

这还没有将 glorbnitz 连接到构建中。如何做到这一点取决于被导入的软件,超出了本教程的范围。

5.5.5.1. 保持更新

随着时间的推移,现在是更新树以包含最新的上游更改的时刻。切换到 main 分支时,确保没有差异。在执行以下操作之前,最好先将更改提交到一个分支(或使用 git stash)。

如果你习惯使用 git pull,我们强烈建议使用 --ff-only 选项,并将其设置为默认选项。或者,如果你在 main 分支上有暂存的更改,git pull --rebase 会更有用。

% git config --global pull.ff only

如果你只希望此设置应用于当前仓库,可能需要省略 --global。

% cd freebsd-src
% git checkout main
% git pull (--ff-only|--rebase)

有一个常见的陷阱,git pull 组合命令会尝试执行合并,这有时会创建一个以前不存在的合并提交。这个过程可能更难恢复。

我们也推荐使用更长的命令形式。

% cd freebsd-src
% git checkout main
% git fetch freebsd
% git merge --ff-only freebsd/main

这些命令会将你的树重置为 main 分支,然后从你最初拉取的源更新它。在执行此操作之前,切换到 main 非常重要,这样它才能向前推进。现在,是时候将更改推进:

% git rebase -i main working

5.5.5.2. 将更改推送到上游

首先,确保推送 URL 已正确配置为上游仓库。

% git remote set-url --push freebsd ssh://git@gitrepo.freebsd.org/src.git

然后,验证你的用户名和电子邮件是否配置正确。我们要求它们与 FreeBSD 集群中的 passwd 条目完全匹配。

在 freefall.freebsd.org 上使用

freefall% gen-gitconfig.sh

可以获得你可以直接使用的配方,前提是 /usr/local/bin 在 PATH 中。

下面的命令会将 working 分支合并到上游的 main 分支。重要的是在执行此操作之前,你要确保你的更改完全符合 FreeBSD 源仓库中的要求。这种语法会将 working 分支推送到 main,将 main 分支推进。只有当这导致 main 的线性更改时(例如,没有合并)时,你才可以执行此操作。

% git push freebsd working:main

如果由于提交竞争导致你的推送被拒绝,请在重新尝试之前重新基准化你的分支:

% git checkout working
% git fetch freebsd
% git rebase freebsd/main
% git push freebsd working:main

5.5.5.3. 将更改推送到上游(替代方法)

有些人发现,在推送到远程仓库之前,将更改合并到本地 main 分支上会更容易。此外,git arc stage 会将更改从分支移到本地 main,当你只需要做分支的子集时。操作与之前部分类似:

% git checkout main
% git merge --ff-only `working`
% git push freebsd

如果你输了比赛,那么再试一次,尝试以下操作:

% git pull --rebase
% git push freebsd

这些命令会获取最新的 freebsd/main,然后将本地的 main 更改基准化到其上,这是当你丢失提交竞争时所需要的。注意:使用此技巧无法合并供应商分支的提交。

5.5.5.4. 查找 Subversion 修订版

% git log

如果你有特定版本,可以使用以下构造命令:

% git log --grep revision=XXXX

这样可以找到特定的修订版。commit 后的十六进制数字是你可以用来引用此提交的哈希值。

5.6. Git 常见问题

本节提供了许多可能会经常出现的问题的针对性答案,适用于用户和开发人员。

注意

我们使用 FreeBSD 仓库的常规约定,源为 freebsd,而不是默认的 origin,以便让人们将其用于自己的开发,并最小化将更改错误推送到错误仓库的风险。

5.6.1. 用户

5.6.1.1. 如何用一个仓库副本跟踪 -current 和 -stable?

Q: 尽管磁盘空间不是大问题,但使用仓库副本更高效。在 SVN 镜像中,我可以从同一个仓库签出多个树。如何用 Git 来实现?

A: 你可以使用 Git 工作树。这里有多种方法可以做到,但最简单的方法是使用克隆来跟踪 -current,并使用工作树来跟踪稳定版本。虽然使用“裸仓库”作为一种应对方法已被提出,但它更为复杂,且不在此文档中介绍。

首先,你需要克隆 FreeBSD 仓库,以下示例中将其克隆到 freebsd-current 以减少混淆。$URL 是适合你的镜像源:

% git clone -o freebsd --config remote.freebsd.fetch='+refs/notes/*:refs/notes/*' $URL freebsd-current

克隆完成后,你可以简单地从中创建一个工作树:

% cd freebsd-current
% git worktree add ../freebsd-stable-12 stable/12

这将把 stable/12 签出到名为 freebsd-stable-12 的目录,它与 freebsd-current 目录平行。创建后,更新工作树的方式与预期类似:

% cd freebsd-current
% git checkout main
% git pull --ff-only
# 从上游的更改现在已本地化,并且当前树已更新
% cd ../freebsd-stable-12
% git merge --ff-only freebsd/stable/12
# 现在你的 stable/12 也已更新

我建议使用 --ff-only,因为它更安全,可以避免意外进入“合并噩梦”,其中树中会有额外的更改,从而迫使你进行复杂的合并,而不是简单的合并。

5.6.2. 开发人员

5.6.2.1. 哎呀!我不小心提交到了 main 分支,而不是另一个分支。

Q: 有时,我会犯错,不小心提交到了 main 分支。我该怎么办?

A: 首先,不要惊慌。

其次,不要推送。实际上,如果你没有推送,几乎所有问题都可以解决。本节的所有答案假设没有进行推送。

以下解答假设你已经提交到了 main 分支,并且想创建一个名为 issue 的分支:

% git branch issue                # 创建 'issue' 分支
% git reset --hard freebsd/main   # 将 'main' 重置回官方的提交
% git checkout issue              # 回到原先的地方

5.6.2.2. 哎呀!我不小心提交到了错误的分支!

Q: 我正在 wilma 分支上开发一个功能,但不小心在 wilma 中提交了与 fred 分支相关的更改。我该怎么办?

A: 这个解答与之前类似,不过是使用了 cherry-pick。假设 wilma 分支上只有一次提交,但这种方法也可以推广到更复杂的情况。假设这是 wilma 上的最后一次提交(因此使用 wilma 在 git cherry-pick 命令中),但这也可以推广。

# 我们在 wilma 分支上
% git checkout fred		# 切换到 fred 分支
% git cherry-pick wilma		# 拷贝误提交的提交
% git checkout wilma		# 回到 wilma 分支
% git reset --hard HEAD^	# 将 wilma 回退到上一个提交

Git 专家通常会先将 wilma 分支回退 1 次提交,然后切换到 fred,然后使用 git reflog 查看被删除的提交是什么,再将其 cherry-pick 到 fred 分支。

Q: 如果我想将一些更改提交到 main,但出于某些原因保留其余的更改在 wilma 中,怎么办?

A: 上面相同的技巧也适用于你想将当前分支的一部分更改提交到 main,而保留分支其余部分(例如发现了一个不相关的拼写错误或修复了一个偶然的 bug)。你可以将这些更改 cherry-pick 到 main,然后推送到上游仓库。完成后,清理工作也非常简单:只需要使用 git rebase -i。Git 会注意到你已经这样做了,并自动跳过相同的更改(即使你更改了提交消息或稍微调整了提交)。你无需返回 wilma 分支来调整它:只需执行 rebase 操作即可!

Q: 我想将 wilma 分支的一些更改拆分到 fred 分支中,怎么做?

A: 更一般的答案与之前相同。你需要先检出或创建 fred 分支,逐一 cherry-pick 需要的更改,然后对 wilma 分支进行 rebase,移除你已 cherry-pick 到 fred 分支的更改。使用 git rebase -i main wilma 会打开编辑器,你可以删除与 fred 分支中已复制提交对应的 pick 行。如果一切顺利,且没有冲突,那么操作完成。如果遇到冲突,你需要在过程中解决它们。

另一种做法是先检出 wilma,然后创建 fred 分支,使其指向树中的相同位置。然后,你可以对这两个分支执行 git rebase -i,在编辑器中保留要放入 fred 或 wilma 分支的更改 pick 行,删除其他行。一些人会在开始拆分之前创建一个名为 pre-split 的标签/分支,以防拆分过程中出现问题。万一需要撤销拆分操作,可以执行以下步骤:

% git checkout pre-split	# 回到起点
% git branch -D fred		   # 删除 fred 分支
% git checkout -B wilma		# 重置 wilma 分支
% git branch -d pre-split	# 假装什么也没发生

最后一步是可选的。如果你打算重新尝试拆分,你可以省略此步骤。

Q: 但我在阅读时没有看到你的建议,没创建分支,现在 fred 和 wilma 已经完全搞乱了。我该如何找回 wilma 在开始之前的状态?我不知道我做了多少次更改。

A: 一切尚未丢失。只要不久之前做的更改太多(几百次提交的话),你依然可以找回之前的状态。

假设我创建了一个 wilma 分支并提交了一些更改,然后决定将其拆分成 fred 和 wilma。如果在拆分过程中没有出现问题,一切应该顺利。但假设有些不正常的事情发生了,你可以使用 git reflog 来查看做过的更改:

% git reflog
6ff9c25 (HEAD -> wilma) HEAD@{0}: rebase -i (finish): returning to refs/heads/wilma
6ff9c25 (HEAD -> wilma) HEAD@{1}: rebase -i (start): checkout main
869cbd3 HEAD@{2}: rebase -i (start): checkout wilma
a6a5094 (fred) HEAD@{3}: rebase -i (finish): returning to refs/heads/fred
a6a5094 (fred) HEAD@{4}: rebase -i (pick): Encourage contributions
1ccd109 (freebsd/main, main) HEAD@{5}: rebase -i (start): checkout main
869cbd3 HEAD@{6}: rebase -i (start): checkout fred
869cbd3 HEAD@{7}: checkout: moving from wilma to fred
869cbd3 HEAD@{8}: commit: Encourage contributions
...
%

在这里,我们可以看到做过的更改。你可以通过这些信息找出哪里出错了。我想指出几点:首先,HEAD@{X} 是一个“提交点”,因此可以将它作为命令的参数。虽然如果该命令对仓库做了提交操作,X 的数字会发生变化。你也可以使用哈希值(第一列)。

接下来,“Encourage contributions” 是我在决定拆分之前提交到 wilma 的最后一次提交。你也可以看到这个哈希值在我创建 fred 分支时也出现了。我从 fred 开始执行 rebase 操作,可以看到“开始”步骤、每个步骤以及完成步骤。虽然我们在这里不需要这个信息,但它可以帮助你清楚了解发生了什么。幸运的是,修复这个问题的方法是按照之前解答中的步骤执行,但使用哈希值 869cbd3 代替 pre-split。虽然这种方法看起来有点冗长,但由于你是一次处理一件事,记住它很容易。你还可以将操作堆叠在一起:

% git checkout -B wilma 869cbd3
% git branch -D fred

这样,你就准备好重新尝试了。使用 checkout -B 并指定哈希值结合了检出和为其创建分支的操作。使用 -B 而非 -b 会强制移动已经存在的分支。这两种方式都可以使用,这正是 Git 强大(也有时让人头疼)的原因之一。我更倾向于使用 git checkout -B xxxx hash 来避免先检出哈希值后再创建/移动分支,因为那样会出现令人不安的“分离头”状态警告:

% git checkout 869cbd3
M	faq.md
Note: checking out '869cbd3'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 869cbd3 Encourage contributions
% git checkout -B wilma

这将产生相同的效果,但我需要阅读更多内容,而“分离头”状态不是我喜欢思考的图像。

5.6.2.4. 混合和匹配分支

问: 我有两个分支 worker 和 async,我想把它们合并成一个名为 feature 的分支,同时保留两个分支中的提交记录,应该怎么做?

答: 这可以通过 cherry pick 来实现。

% git checkout worker
% git checkout -b feature	# 创建一个新的分支
% git cherry-pick main..async	# 将变更引入

现在你就有了一个新的分支 feature。这个分支结合了两个分支的提交。你可以通过 git rebase 进一步修改它。

问: 我有一个名为 driver 的分支,我想将其拆分为 kernel 和 userland,这样我就可以分别独立演进它们,并在每个分支准备好时提交它们。

答: 这需要一些准备工作,但 git rebase 可以完成大部分工作。

% git checkout driver		# 检出 driver 分支
% git checkout -b kernel	# 创建 kernel 分支
% git checkout -b userland	# 创建 userland 分支

现在你有了两个相同的分支。接下来是将提交拆分到这两个分支中的过程。我们假设 driver 中的所有提交都进入 kernel 或 userland,而不是两个分支。

% git rebase -i main kernel

在编辑器中,保留你想要的提交(通过 p 或 pick 行),删除不需要的提交(这看起来很吓人,但如果出错了,你可以通过 driver 分支重新开始,因为你还没有修改它)。

% git rebase -i main userland

对 userland 分支做同样的事。

问: 哇!我按照上述操作了,但忘记了 kernel 分支的一个提交。如何恢复这个提交?

答: 你可以通过 driver 分支找到缺失提交的哈希,并将其 cherry-pick 到 kernel 分支。

% git checkout kernel
% git log driver
% git cherry-pick $HASH

问: 好的。我的情况和上面一样,但我的提交都混在一起了。我需要将一个提交的部分内容放到一个分支,剩余部分放到另一个分支。事实上,我有好几个这样的提交。你提到的选择提交的 rebase 方法看起来有点棘手。

答: 在这种情况下,你最好先整理原始分支,将提交拆分开来,然后使用上述方法将分支拆分。

假设现在只有一个干净的提交。你可以使用 git rebase 中的 edit 选项,或者直接在当前提交上使用该操作。无论哪种方式,步骤都一样。首先,我们需要备份一个提交,并将更改留在工作区:

% git reset HEAD^

注意:此时不要加上 --hard,因为那样会将工作区的更改删除。

现在,如果你幸运的话,需要拆分的更改完全是基于文件的。在这种情况下,你可以像平常一样使用 git add 来将每组文件提交一次。注意:当你执行重置操作时,提交消息会丢失,所以如果需要保存消息,应该先保存一份副本(不过,你可以通过 git log $HASH 恢复提交消息)。

如果不幸运,可能需要将文件拆分开来。这时你可以使用以下工具逐个处理文件:

git add -i foo/bar.c

这将逐步显示差异,并提示你是否包含或排除每个变更块。完成后,使用 git commit 提交剩余的更改。如果有多个文件需要处理,你可以多次运行这个命令,通常我发现一次处理一个文件并使用 git rebase -i 将相关提交合并会更简单些。

5.6.3. 克隆和镜像

问: 我想镜像整个 Git 仓库,怎么做?

答: 如果你只是想镜像仓库,可以使用以下命令:

% git clone --mirror $URL

但如果你希望用它做其他事情,那么你会需要重新克隆仓库。

首先,这会创建一个 "bare 仓库",即只有仓库数据库,而没有已检出的工作区。这对于镜像非常好,但对于日常工作来说就不太合适了。你可以通过 git worktree 来解决这个问题:

% git clone --mirror https://git.freebsd.org/ports.git ports.git
% cd ports.git
% git worktree add ../ports main
% git worktree add ../quarterly branches/2020Q4
% cd ../ports

但是,如果你不打算将镜像用于进一步的本地克隆,那它就不太适合了。

第二个缺点是,Git 通常会重写上游的引用(如分支名、标签等),使得你的本地引用可以独立于上游发展。这意味着,如果你在该仓库中提交更改,除非是私人项目的分支,否则你会丢失更改。

问: 那我应该怎么办?

答: 你可以将所有上游仓库的引用放入本地仓库的私有命名空间。Git 是通过 'refspec' 来克隆所有内容的,默认的 refspec 是:

fetch = +refs/heads/*:refs/remotes/freebsd/*

这意味着它只会获取分支的引用。

然而,FreeBSD 仓库包含了很多其他内容。你可以为每个引用命名空间添加显式的 refspec,或者选择获取所有内容。要设置你的仓库来执行这一操作:

git config --add remote.freebsd.fetch '+refs/*:refs/freebsd/*'

这会将上游仓库中的所有内容放入本地仓库的 refs/freebsd/ 命名空间。请注意,这也会获取所有未转换的 vendor 分支,并且它们关联的引用数量非常庞大。

你需要使用完整的名称来引用这些 "refs",因为它们不在 Git 的常规命名空间中。

git log refs/freebsd/vendor/zlib/1.2.10

上述命令会查看 zlib 的 vendor 分支日志,从 1.2.10 开始。

5.7. 与他人合作

在像 FreeBSD 这样的大型项目中,良好的合作能力是软件开发的关键之一。在 FreeBSD 项目的 Git 仓库中,当前不允许用户创建的分支被推送到仓库中。因此,如果你希望与他人共享你的更改,你必须使用其他机制,例如托管的 GitLab 或 GitHub 来共享用户生成的分支。

以下是如何创建一个基于 FreeBSD main 分支的用户生成分支,并将其推送到 GitHub 的步骤。

% git remote -v
freebsd https://git.freebsd.org/src.git (fetch)
freebsd ssh://git@gitrepo.freebsd.org/src.git (push)

然后,在本地系统上添加一个指向你 fork 的远程源:

% git remote add github git@github.com:gvnn3/freebsd-src.git
% git remote -v
github	git@github.com:gvnn3/freebsd-src.git (fetch)
github	git@github.com:gvnn3/freebsd-src.git (push)
freebsd	https://git.freebsd.org/src.git (fetch)
freebsd	ssh://git@gitrepo.freebsd.org/src.git (push)
% git checkout -b gnn-pr2001-fix

在你的分支中进行任何修改。完成后,进行构建和测试,当你准备好与他人合作时,就可以将你的更改推送到托管的分支了。在推送之前,你需要设置适当的上游分支,因为第一次尝试推送到 GitHub 时,Git 会提示你:

% git push github
fatal: The current branch gnn-pr2001-fix has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream github gnn-pr2001-fix

按照 Git 的建议设置推送上游分支后,推送就会成功:

% git push --set-upstream github gnn-feature
Enumerating objects: 20486, done.
Counting objects: 100% (20486/20486), done.
Delta compression using up to 8 threads
Compressing objects: 100% (12202/12202), done.
Writing objects: 100% (20180/20180), 56.25 MiB | 13.15 MiB/s, done.
Total 20180 (delta 11316), reused 12972 (delta 7770), pack-reused 0
remote: Resolving deltas: 100% (11316/11316), completed with 247 local objects.
remote:
remote: Create a pull request for 'gnn-feature' on GitHub by visiting:
remote:      https://github.com/gvnn3/freebsd-src/pull/new/gnn-feature
remote:
To github.com:gvnn3/freebsd-src.git
 * [new branch]                gnn-feature -> gnn-feature
Branch 'gnn-feature' set up to track remote branch 'gnn-feature' from 'github'.

之后对同一分支的更改将默认正确推送:

% git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 314 bytes | 1024 bytes/s, done.
Total 3 (delta 1), reused 1 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:gvnn3/freebsd-src.git
   9e5243d7b659..cf6aeb8d7dda  gnn-feature -> gnn-feature

此时,你的工作已推送到 GitHub 上的分支,并且你可以与其他协作者共享该链接。

5.8. 合并 GitHub Pull 请求

本节介绍了如何将提交到 FreeBSD GitHub 镜像库的 GitHub Pull 请求合并到 FreeBSD 仓库。虽然这不是提交补丁的官方方式,但有时通过这种方式提交的修复非常有效,直接将它们合并到提交者的树上,再从那里推送到 FreeBSD 的树上是最简单的做法。类似的步骤也可以用于将其他仓库的分支合并进来。在合并他人的 Pull 请求时,特别需要小心检查所有更改,确保它们和提交时所展示的一致。

% git remote -v
freebsd https://git.freebsd.org/src.git (fetch)
freebsd ssh://git@gitrepo.freebsd.org/src.git (push)
github https://github.com/freebsd/freebsd-src (fetch)
github https://github.com/freebsd/freebsd-src (fetch)

Pull 请求通常很简单:请求只包含一个提交。在这种情况下,可以使用简化的方法,尽管上一节中介绍的方法也可以使用。这里,创建一个分支,将更改 cherry-pick(挑选提交),调整提交信息,进行测试后再推送。示例中使用了 staging 分支,但它可以是任何名称。当有多个提交时,特别是需要进行小调整时,使用 git rebase -i 比 git cherry-pick 更加有效。以下是相关命令的概述:

  1. 创建一个分支;

  2. cherry-pick Pull 请求中的更改;

  3. 测试它;

  4. 调整提交信息;

  5. 将其合并到 main 分支;

  6. 提交 Pull 请求的 GitHub 链接。

% git fetch github pull/$PR/head:staging
% git rebase -i main staging	# 将 staging 分支更新到最新,调整提交信息
<进行必要的测试>
% git checkout main
% git pull --ff-only		# 若时间已过,确保拉取最新的提交
% git checkout main
% git merge --ff-only staging
<再次测试如果需要>
% git push freebsd --push-option=confirm-author

对于复杂的 Pull 请求,如果有多个提交并且有冲突,可以按照以下步骤进行:

  1. 检出 Pull 请求 git checkout github/pull/XXX

  2. 创建一个分支进行 rebase git checkout -b staging

  3. 将 staging 分支 rebase 到最新的 main 分支 git rebase -i main staging

  4. 解决冲突并进行必要的测试

  5. 将 staging 分支快进合并到 main 分支

  6. 最后的 sanity check,确保一切正常

  7. 推送到 FreeBSD 的 Git 仓库

这些步骤同样适用于将其他地方开发的分支合并到本地树并提交。

完成 Pull 请求后,使用 GitHub 的 Web 界面关闭它。如果你的 github 原始库是通过 https:// 配置的,那么你只需要 GitHub 账户来关闭该 Pull 请求。

6. 版本控制历史

FreeBSD 源代码仓库在 2008 年 5 月 31 日从 CVS 转移到 Subversion。第一个正式的 SVN 提交是 r179447。源代码仓库在 2020 年 12 月 23 日从 Subversion 转移到 Git。最后一个正式的 SVN 提交是 r368820,第一个正式的 Git 提交哈希是 5ef5f51d2bef80b0ede9b10ad5b0e9440b60518c。

FreeBSD doc/www 仓库在 2012 年 5 月 19 日从 CVS 转移到 Subversion。第一个正式的 SVN 提交是 r38821。文档仓库在 2020 年 12 月 8 日从 Subversion 转移到 Git。最后一个 SVN 提交是 r54737,第一个正式的 Git 提交哈希是 3be01a475855e7511ad755b2defd2e0da5d58bbe。

FreeBSD ports 仓库在 2012 年 7 月 14 日从 CVS 转移到 Subversion。第一个正式的 SVN 提交是 r300894。Ports 仓库在 2021 年 4 月 6 日从 Subversion 转移到 Git。最后一个 SVN 提交是 r569609,第一个正式的 Git 提交哈希是 ed8d3eda309dd863fb66e04bccaa513eee255cbf。

7. 设置、约定与传统

作为一名新开发者,需要做一些准备工作。以下步骤仅适用于已获得提交权限的开发者。如果你没有提交权限,必须由导师来执行这些步骤。

7.1. 对于新提交者

已获得 FreeBSD 仓库提交权限的开发者,必须遵循以下步骤。

  • 在提交每个更改之前,先获得导师的批准!

  • 所有 src 的提交首先必须提交到 FreeBSD-CURRENT,然后才能合并到 FreeBSD-STABLE。FreeBSD-STABLE 分支必须保持与该分支早期版本的 ABI 和 API 兼容性。不要合并破坏这种兼容性的更改。

新提交者步骤

  1. 添加作者条目doc/shared/authors.adoc - 添加作者条目。后续步骤依赖于此条目,缺少此步骤会导致 doc/ 构建失败。这是一个相对简单的任务,但仍然是测试版本控制技能的好方法。

  2. doc/shared/contrib-additional.adoc - 删除 记录。条目按名字排序。

  3. 添加新闻项doc/website/data/en/news/news.toml - 添加一条记录。参考其他用于宣布新提交者的条目并遵循格式。使用提交权限批准邮件中的日期。

  4. 使用 doc/documentation/tools/checkkey.sh 来验证密钥是否符合最低最佳实践标准。

    添加和检查密钥后,将更新后的文件添加到源控制并提交。此文件中的条目按姓氏排序。

    注意

  5. 更新导师和学员信息src/share/misc/committers-.dot - 向当前提交者部分添加一条记录,repository 是指 doc、ports 或 src,具体取决于授予的提交权限。

    在底部部分为每个导师/学员关系添加一条记录。

  6. 可选:在 Ports 中添加个人信息ports/astro/xearth/files/freebsd.committers.markers 和 src/usr.bin/calendar/calendars/calendar.freebsd - 有些人会在这些文件中为自己添加条目,以显示自己所在的位置或生日日期。

7.2. 对所有人的要求

  1. 向其他开发者介绍自己,否则没人知道你是谁,也不知道你在 FreeBSD 中的工作内容。介绍不需要是详尽的传记,只需要写一两段关于你是谁、你计划在 FreeBSD 中做什么以及你的导师是谁的内容。将此邮件发送到 FreeBSD 开发者邮件列表,这样你就可以开始了!

  2. 登录到 freefall.FreeBSD.org 并创建一个 /var/forward/user 文件(其中 user 是你的用户名),文件内容包含你希望将邮件转发到的电子邮件地址,邮件地址格式为 你的用户名@FreeBSD.org。此邮件包括所有提交消息以及任何发送到 FreeBSD 提交者邮件列表和 FreeBSD 开发者邮件列表的邮件。由于 freefall 上有些非常大的邮箱可能会在空间不足时未经警告被截断,因此请将其转发或保存到其他地方。

    注意

    如果你的电子邮件系统使用 SPF 严格规则,你应该将 mx2.FreeBSD.org 排除在 SPF 检查之外。

    由于处理垃圾邮件给中央邮件服务器带来的巨大负担,前端服务器会进行一些基本检查,并根据这些检查丢弃一些邮件。目前,唯一的检查是连接主机的 DNS 信息,但这可能会发生变化。一些人把这些检查归咎于导致有效邮件的丢失。要关闭这些检查,可以在 freefall.FreeBSD.org 上创建文件 ~/.spam_lover。

    注意

    不是提交者但又是开发者的用户将无法订阅提交者或开发者邮件列表。订阅关系是依访问权限而定的。

7.2.1. SMTP 访问设置

对于那些愿意通过 FreeBSD.org 基础设施发送电子邮件的人,请按照以下说明操作:

  1. 将邮件客户端指向 smtp.FreeBSD.org:587。

  2. 启用 STARTTLS。

  3. 确保你的 From: 地址设置为 你的用户名@FreeBSD.org。

  4. 注意

    在输入用户名时不要带 @FreeBSD.org。 注意

    额外说明:

    • 只接受来自 你的用户名@FreeBSD.org 的邮件。如果你已作为某个用户认证,不允许你从另一个用户发送邮件。

    • 将附加一个包含 SASL 用户名的头部:(认证发件人: 用户名)。

    • 该主机对邮件发送有各种速率限制,以减少暴力攻击。

7.2.1.1. 使用本地 MTA 将电子邮件转发到 FreeBSD.org SMTP 服务

也可以使用本地 MTA 将本地发送的邮件转发到 FreeBSD.org 的 SMTP 服务器。

示例 1. 使用 Postfix

要告诉本地 Postfix 实例将来自 你的用户名@FreeBSD.org 的邮件转发到 FreeBSD.org 服务器,可以在 main.cf 文件中添加以下内容:

sender_dependent_relayhost_maps = hash:/usr/local/etc/postfix/relayhost_maps
smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/usr/local/etc/postfix/sasl_passwd
smtp_use_tls = yes

在 /usr/local/etc/postfix/relayhost_maps 中创建以下内容:

你的用户名@FreeBSD.org  [smtp.freebsd.org]:587

在 /usr/local/etc/postfix/sasl_passwd 中创建以下内容:

[smtp.freebsd.org]:587          你的用户名:你的密码

如果邮件服务器被其他人使用,你可能希望防止他们从你的地址发送邮件。为此,可以在 main.cf 中添加以下内容:

smtpd_sender_login_maps = hash:/usr/local/etc/postfix/sender_login_maps
smtpd_sender_restrictions = reject_known_sender_login_mismatch

在 /usr/local/etc/postfix/sender_login_maps 中创建以下内容:

你的用户名@FreeBSD.org 你的本地用户名

其中 你的本地用户名 是用于连接到本地 Postfix 实例的 SASL 用户名。

示例 2. 使用 OpenSMTPD

要告诉本地 OpenSMTPD 实例将来自 你的用户名@FreeBSD.org 的邮件转发到 FreeBSD.org 服务器,可以在 smtpd.conf 中添加以下内容:

action "freebsd" relay host smtp+tls://freebsd@smtp.freebsd.org:587 auth <secrets>
match from any auth 你的本地用户名 mail-from "_你的用户名_@freebsd.org" for any action "freebsd"

其中 你的本地用户名 是用于连接到本地 OpenSMTPD 实例的 SASL 用户名。

在 /usr/local/etc/mail/secrets 中创建以下内容:

freebsd	你的用户名:你的密码

示例 3. 使用 Exim

要指示本地 Exim 实例将来自 example@FreeBSD.org 的所有邮件转发到 FreeBSD.org 服务器,可以在 Exim 配置文件 中添加以下内容:

Routers section: (在列表顶部)
freebsd_send:
   driver = manualroute
   domains = !+local_domains
   transport = freebsd_smtp
   route_data = ${lookup {${lc:$sender_address}} lsearch {/usr/local/etc/exim/freebsd_send}}

Transport Section:
freebsd_smtp:
        driver = smtp
  tls_certificate=<local certificate>
  tls_privatekey=<local certificate private key>
  tls_require_ciphers = EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+AESGCM:EECDH:EDH+AESGCM:EDH+aRSA:HIGH:!MEDIUM:!LOW:!aNULL:!eNULL:!LOW:!RC4:!MD5:!EXP:!PSK:!SRP:!DSS
  dkim_domain = <local DKIM domain>
  dkim_selector = <local DKIM selector>
  dkim_private_key= <local DKIM private key>
  dnssec_request_domains = *
  hosts_require_auth = smtp.freebsd.org

Authenticators:
freebsd_plain:
  driver = plaintext
  public_name = PLAIN
  client_send = ^example/mail^examplePassword
  client_condition = ${if eq{$host}{smtp.freebsd.org}}

在 /usr/local/etc/exim/freebsd_send 中创建以下内容:

example@freebsd.org:smtp.freebsd.org::587

7.3. 导师

所有新开发者在前几个月都会被分配一个导师。导师的职责是教导学员项目的规则和约定,并引导他们在开发者社区的初步步骤。导师还需要对学员在这一初期阶段的行为负责。

对于提交者:在没有得到导师批准的情况下,不能提交任何内容。提交信息中必须注明 Approved by: 行,记录导师的批准。

8. 提交前审核

代码审查是提高软件质量的一种方式。以下准则适用于对 src 仓库的 main(-CURRENT)分支的提交。其他分支以及 ports 和 docs 树有各自的审查政策,但这些准则一般适用于需要审查的提交:

  • 所有非琐碎的更改应在提交到仓库之前进行审核。

  • 审核可以通过电子邮件、Bugzilla、Phabricator 或其他机制进行。尽可能地,审核应当公开进行。

  • 负责代码更改的开发者同样负责进行所有必要的与审核相关的更改。

  • 代码审查是一个迭代过程,直到补丁准备好提交为止。具体来说,补丁发送到审查之后,在提交前应当收到明确的“看起来不错”反馈。在这点上,只要是明确的,可以采取任何适合的反馈形式。

  • 超时不能作为审查的替代。

有时代码审查会比你预期的时间更长,尤其是对于大型功能。加速补丁审查的有效方法包括:

  • 审查他人的补丁。如果你提供帮助,其他人也更愿意为你提供帮助;良好的合作关系是我们的货币。

  • 提醒补丁。如果它很紧急,提供为什么这个补丁对你来说很重要的理由,并每隔几天提醒一次。如果不紧急,常见的礼貌提醒频率是每周一次。记住,你是在请求其他专业开发者宝贵的时间。

  • 在邮件列表、IRC 等平台寻求帮助。其他人可能能够直接帮助你,或者建议合适的审查者。

  • 将补丁分解成多个较小的补丁,这些补丁彼此依赖。补丁越小,别人快速审查它的概率越高。在做大范围更改时,从一开始就保持这一思路是有帮助的,因为事后将大改动拆分成小补丁通常很困难。

开发者应当作为审查者和被审查者参与代码审查。如果有人愿意审查你的代码,你也应该回报他人,审查他们的代码。需要注意的是,虽然任何人都可以审查并提供反馈,只有相关领域的专家才能批准更改。这通常是一个经常与相关代码一起工作的提交者。

在某些情况下,可能没有相关领域的专家。在这种情况下,由经验丰富的开发者进行审查并结合适当的测试,通常也是足够的。

9. 提交日志消息

本节提供了一些关于提交日志格式的建议和传统。

9.1. 为什么提交信息很重要?

当你在 Git、Subversion 或其他版本控制系统(VCS)中提交更改时,你需要写一段描述该提交的文本——即提交信息。这个提交信息有多重要?是否应该花费一些精力来撰写它?如果你只是写了“修复了一个 bug”,真的有关系吗?

大多数项目有多个开发者,并且持续了一段时间。提交信息是与其他开发者进行沟通的重要方式,无论是当前开发者还是未来的开发者。

FreeBSD 拥有数百名活跃开发者和数十年的历史,涉及数十万次的提交。在这段时间里,开发者社区逐渐意识到好的提交信息有多么重要;有时候,这些经验是通过痛苦的教训获得的。

提交信息至少有三个目的:

  • 与其他开发者沟通 FreeBSD 的提交会生成邮件发送到多个邮件列表。这些邮件包括提交信息和补丁本身的副本。提交信息还可以通过如 git log 等命令查看。这些信息有助于让其他开发者了解正在进行的更改;这些开发者可能希望测试该更改、对该主题有兴趣并希望更详细地审查,或者正在进行的项目可能会受益于与之的互动。

  • 使更改可发现 在一个拥有悠久历史的大型项目中,当调查某个问题或行为变化时,可能很难找到相关的更改。详细且冗长的提交信息可以使搜索与之相关的更改变得容易。例如,使用 git log --since 1year --grep 'USB timeout' 可以查找相关的历史更改。

  • 提供历史文档 提交信息为未来的开发者提供了更改的文档,也许是多年或几十年后。未来的开发者甚至可能是你,作为原作者。当今天看似显而易见的更改,可能在将来就变得不再那么明显了。

git blame 命令会标注源代码文件中的每一行,显示将其引入的更改(哈希值和主题行)。

在明确了提交信息的重要性之后,接下来是一个好的 FreeBSD 提交信息的要素:

9.2. 从主题行开始

提交信息应以一行简短的主题开始,简要总结该更改。主题本身应该允许读者快速判断更改是否值得关注。

9.3. 保持主题行简短

主题行应尽可能简短,同时保留必要的信息。这是为了使浏览 Git 日志更高效,并确保 git log --oneline 能在一个 80 列的行内显示短哈希和主题。一个好的经验法则是保持在 63 个字符以内,并尽可能控制在 50 个字符以内。

9.4. 如果适用,前缀主题行以组件名

如果更改涉及某个特定组件,主题行可以以该组件名称和冒号(:)为前缀。

✓ foo: 添加 -k 选项以保持临时数据

将前缀包含在建议的 63 字符限制内,以便 git log --oneline 不会换行。

9.5. 主题行首字母大写

主题行的首字母应大写。前缀(如果有)除非必要,否则不需要大写(例如,USB: 需要大写)。

9.6. 不要以标点符号结尾

主题行不要以句号或其他标点符号结尾。在这方面,主题行像报纸头条一样。

9.7. 使用空行分隔主题和正文

主题和正文之间应使用空行进行分隔。

一些简单的提交不需要正文,只有主题。

✓ ls: 修复使用文本中的拼写错误

9.8. 限制消息的行宽为 72 列

git log 和 git format-patch 会将提交信息缩进 4 个空格。限制在 72 列内能确保右侧有匹配的边距。将消息限制在 72 个字符以内还能够使提交信息在格式化补丁中低于 RFC 2822 提出的电子邮件行长度限制(78 字符)。这个限制对多种工具有良好的兼容性,避免了较长行长度带来的换行不一致问题。

9.9. 使用现在时,命令式语气

这有助于简短的主题行,并保持一致性,包括自动生成的提交信息(例如,git revert 生成的提交信息)。当阅读一系列提交主题时,这一点尤为重要。将主题视作完成句子“当应用时,该更改将会……”的部分。

✓ foo: 实现 -k(keep)选项 ✗ foo: 实现了 -k 选项 ✗ 该更改在 foo 中实现了 -k 选项 ✗ 添加了 -k 选项

9.10. 聚焦于“做了什么”和“为什么做”,而不是“如何做”

解释更改实现了什么,以及为什么要做这个更改,而不是陈述具体如何实现。

不要假设读者熟悉该问题。解释更改的背景和动机。如果有基准数据,提供这些数据。

如果更改有任何限制或不完整的方面,在提交信息中描述它们。

9.11. 考虑是否可以将提交信息中的某些部分写成代码注释

有时在编写提交信息时,你可能会写下一两句话,解释某些复杂或令人困惑的更改。在这种情况下,考虑是否将这些解释作为代码中的注释,这样它们可以直接帮助未来的开发者(包括你自己)理解代码。

9.12. 为将来的自己编写提交信息

当你为一个更改编写提交信息时,你心中已经掌握了所有相关背景——是什么促使了这次更改,考虑过并拒绝的其他方案、更改的限制等等。想象一下自己在一两年后再次查看这个更改时,写提交信息的方式应该包含所有这些必要的背景信息,以帮助未来的你理解为什么做出这个更改。

9.13. 提交信息应该是自包含的

你可以在提交信息中引用邮件列表帖子、基准测试结果网页或代码审查链接。然而,提交信息应包含所有相关信息,以防这些引用在未来不再可用。

类似地,一个提交可能会引用之前的提交,比如修复 bug 或回退更改的情况。除了提交标识符(修订号或哈希值),还应包括引用提交的主题行(或其他合适的简短引用)。随着每次版本控制系统(VCS)迁移(从 CVS 到 Subversion,再到 Git),早期系统的修订标识符可能变得难以追溯。

9.14. 在脚注中包含适当的元数据

除了在每个提交中包含有意义的消息外,可能还需要附加一些额外的信息。

这些信息由一行或多行构成,每行包含关键字或短语、一个冒号、制表符进行格式化,然后是附加信息。

对于那些可以有多个值的关键字(例如,PR: 后跟以逗号分隔的 PR 列表),可以多次使用相同的关键字,以避免歧义或提高可读性。

关键字或短语如下:

PR:

受此提交影响的缺陷报告(如果有的话)(通常是通过关闭此报告)。多个 PR 可以在一行中列出,使用逗号或空格分隔。

Reported by:

报告问题的人的姓名和电子邮件地址;对于开发者,通常仅使用 FreeBSD 集群上的用户名。通常在没有 PR 的情况下使用,例如问题是通过邮件列表报告的。

Submitted by:(已废弃)

该字段在 Git 中已被废弃;提交的补丁应通过 git commit --author 设置作者,提供完整姓名和有效电子邮件地址。

Reviewed by:

审查此更改的人员的姓名和电子邮件地址;对于开发者,只需提供 FreeBSD 集群上的用户名。如果补丁提交到邮件列表进行审查,并且审查结果是积极的,那么只需包括列表名称。如果审查者不是项目成员,则提供姓名、电子邮件,并在 Port 的情况下,如果是外部角色,如维护者: 开发者审查:

非开发者的 Port 维护者审查:

Tested by:

测试此更改的人员的姓名和电子邮件地址;对于开发者,只需提供 FreeBSD 集群上的用户名。

Discussed with:

提供有意义反馈、对补丁做出贡献的人员的姓名和电子邮件地址;对于开发者,只需提供 FreeBSD 集群上的用户名。通常用于感谢那些没有明确审查、测试或批准更改,但仍然为更改周围的讨论做出贡献,从而改进并更好地理解其对 FreeBSD 项目的影响的人。

Approved by:

批准更改的人员的姓名和电子邮件地址;对于开发人员,仅为 FreeBSD 集群中的用户名。有几种情况是常见的需要批准:

  • 当一个新提交者处于导师指导下

  • 提交到被 LOCKS 文件(src)覆盖的区域

  • 在发布周期期间

  • 提交到你没有提交权限的仓库(例如,src 提交者提交到 docs)

  • 提交到由其他人维护的 Port

在导师指导下时,在提交之前获得导师的批准。在此字段中输入导师的用户名,并注明他们是导师:

如果是团队批准了这些提交,则应包含团队名称,后跟括号中的批准者用户名。例如:

Obtained from:

获取代码的项目名称(如果有的话)。不要使用此行来注明个人的姓名。

Fixes:

此更改修复的提交的 Git 短哈希和标题行,通过 git log -n 1 --pretty=format:'%h ("%s")' GIT-COMMIT-HASH 返回。

MFC after:

若要接收在稍后日期 MFC 的电子邮件提醒,请指定计划进行 MFC 的天数、周数或月数。

MFC to:

如果提交应合并到某些稳定分支,请指定分支名称。

MFH:

如果提交将合并到某个 ports 季度分支名称,请指定季度分支。例如 2021Q2。

Relnotes:

如果该更改是下一个发布版本的发布说明的候选项,请设置为 yes。

Security:

如果更改与安全漏洞或安全暴露相关,请包括一个或多个参考或问题描述。如果可能,包含 VuXML URL 或 CVE ID。

Event:

进行此提交的事件描述。如果这是一个定期事件,请在其中添加年份甚至月份。例如,可以写为 FooBSDcon 2019。这一行的目的是对会议、聚会及其他类型的聚集给予认可,并展示这些活动的意义。请不要将此行用于 Sponsored by:,该行用于注明资助特定功能或开发人员的组织。

Sponsored by:

为此更改提供赞助的组织(如果有的话)。多个组织请用逗号分隔。如果仅部分工作得到资助,或不同作者获得了不同程度的资助,请在每个赞助商名称后给出适当的说明。例如,Example.com (alice, code refactoring), Wormulon (bob), Momcorp (cindy) 表示 Alice 得到了 Example.com 的资助以进行代码重构,Wormulon 资助了 Bob 的工作,Momcorp 资助了 Cindy 的工作。其他作者未获得资助或选择不列出资助。

Pull Request:

该更改作为拉取请求或合并请求提交到 FreeBSD 的公共只读 Git 仓库。应包括拉取请求的完整 URL,因为它们通常作为代码审查。例如:https://github.com/freebsd/freebsd-src/pull/745

Co-authored-by:

Signed-off-by:

Differential Revision:

Phabricator 审查的完整 URL。此行 必须是最后一行。例如:https://reviews.freebsd.org/D1708。

示例 4. 基于 PR 的提交日志

该提交基于 John Smith 提交的 PR 中的补丁。提交消息中的 "PR" 字段已填写。

...

PR:		12345

提交者使用 git commit --author "John Smith <John.Smith@example.com>" 设置补丁的作者。

示例 5. 需要审核的提交日志

虚拟内存系统正在进行更改。补丁已发布到适当的邮件列表(在此案例中为 freebsd-arch),并且更改已获得批准。

...

Reviewed by:	-arch

示例 6. 需要批准的提交日志

提交一个 Port,在与列出的 MAINTAINER 一起工作后,MAINTAINER 允许继续提交。

...

Approved by:	abc (maintainer)

其中 abc 是批准人的账户名。

示例 7. 引入来自 OpenBSD 的代码的提交日志

提交一些基于 OpenBSD 项目工作的代码。

...

Obtained from:	OpenBSD

示例 8. 提交到 FreeBSD-CURRENT 的更改,计划在稍后日期合并到 FreeBSD-STABLE

提交一些代码,该代码将在两周后从 FreeBSD-CURRENT 合并到 FreeBSD-STABLE 分支。

...

MFC after:	2 weeks

其中 2 是计划进行 MFC 的天数、周数或月数。weeks 选项可以是 day、days、week、weeks、month、months。

这些信息常常需要组合在一起。

考虑以下情况:一个用户提交了包含来自 NetBSD 项目的代码的 PR。查看 PR 后,开发人员发现这是他们通常不工作的区域,因此他们让 arch 邮件列表审核了该更改。由于该更改复杂,开发人员决定在一个月后进行 MFC,以便进行充分的测试。

提交中应包含的额外信息如下:

示例 9. 组合提交日志示例

PR:		54321
Reviewed by:	-arch
Obtained from:	NetBSD
MFC after:	1 month
Relnotes:	yes

10. 新文件的首选许可证

FreeBSD 项目建议并使用以下文本作为首选许可证方案:

/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) [year] [your name]
 *
 * 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 AUTHOR 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 AUTHOR 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.
 *
 * [id for your version control system, if any]
 */

FreeBSD 项目强烈不鼓励在新代码中使用所谓的 "广告条款"。由于 FreeBSD 项目有大量的贡献者,对于许多商业供应商来说,遵守该条款变得困难。如果代码中包含广告条款,请考虑将其删除。实际上,请考虑使用上述许可证为你的代码授权。

项目政策要求,某些非 BSD 许可证下的代码只能放置在仓库的特定部分,在某些情况下,编译必须是有条件的,甚至默认禁用。例如,GENERIC 内核必须仅在与 BSD 许可证相同或实质上相似的许可证下编译。GPL、APSL、CDDL 等许可证的软件不得编译到 GENERIC 中。

开发者需注意,在开源中,正确处理 "开源" 和 "源代码" 一样重要,因为不当处理知识产权会带来严重后果。任何问题或疑虑应立即向核心团队报告。

11. 跟踪授予 FreeBSD 项目的许可证

  • 授予特殊许可证的个人或组织的联系信息。

  • 仓库中哪些文件、目录等受该许可证授权,包括提交的任何特别授权材料的修订号。

  • 该许可证生效的日期。除非另有约定,否则此日期应为软件作者授予许可证的日期。

  • 许可证文本。

  • 适用于 FreeBSD 使用授权材料的任何限制、局限或例外。

  • 任何其他相关信息。

12. 树中的 SPDX 标签

截至 2021 年 3 月,树中的大约 25,000 个文件已被标记。

13. 开发者关系

当你在自己的代码上直接工作,或者在已经明确由你负责的代码上工作时,通常无需在提交前与其他提交者进行确认。当你在系统中某个明显无人维护的区域修复 bug(我们为此感到羞愧的确存在一些此类区域)时,同样适用。修改系统中由正式或非正式方式维护的部分时,可以考虑请求审查,就像开发者在成为提交者之前会做的一样。对于 ports,联系 Makefile 中列出的 MAINTAINER。

要判断某个区域是否被维护,可以查看树根目录中的 MAINTAINERS 文件。如果没有列出任何人,请扫描修订历史,查看过去谁提交了更改。要列出过去两年内给定文件的所有提交作者的名字和电子邮件地址,并按提交次数降序排列,可以使用:

% git -C /path/to/repo shortlog -sne --since="2 years" -- relative/path/to/file

如果查询没有得到回应,或者提交者以其他方式表明对该区域缺乏兴趣,可以继续提交。

重要

避免向维护者发送私人邮件。其他人可能对这次对话感兴趣,而不仅仅是最终的结果。

如果对提交有任何疑虑,请在提交之前进行审查。最好在提交前得到批评,而不是等到它已经是仓库的一部分后再遭到批评。如果提交导致争议爆发,可能需要考虑在问题解决之前撤销更改。记住,使用版本控制系统我们总是可以把它恢复回来。

不要诋毁他人的想法。如果他们看到的问题解决方案不同,甚至问题本身不同,可能并不是因为他们愚蠢,或者有可疑的出身,或者他们想摧毁辛勤的工作、个人形象或 FreeBSD,而是因为他们有不同的世界观。和而不同。

诚实地表达分歧。从解决方案的优点出发辩论,诚实地指出其不足,并且愿意以开放的心态看待他们的解决方案,甚至是他们对问题的看法。

接受修正。我们都是容易犯错的。当你犯错时,道歉并继续前进。不要自责,也不要因为错误去责怪别人。不要浪费时间在尴尬或自责上,赶紧修正问题并继续前进。

寻求帮助。寻找(并提供)同行审查。开源软件的一个优势是有更多人参与其中的审查;如果没有人愿意审查代码,那就失去了这一优势。

14. 如果有疑虑……

当你不确定某件事时,无论是技术问题还是项目约定,都要确保向他人请教。如果你保持沉默,你将永远无法取得进展。

如果是技术问题,请在公共邮件列表上提问。避免私下给知道答案的个人发送邮件。这样,所有人都可以从问题和答案中学习。

对于与项目相关的或行政性的问题,请按照以下顺序提问:

  • 你的导师或前导师。

  • 在 IRC、邮件等平台上的有经验的提交者。

  • 任何拥有“帽子”的团队,他们能给你一个权威的答案。

  • 如果仍然不确定,可以在 FreeBSD 开发者邮件列表上提问。

如果你的问题得到解答,而且没有人指出有文档明确回答了你的问题,请记录下来,因为其他人也会有同样的问题。

15. Bugzilla

FreeBSD 项目使用 Bugzilla 来追踪 bug 和变更请求。如果你提交了一个修复或建议,并且该内容已经在 PR 数据库中存在,请确保关闭该 PR。如果你有时间,也可以考虑关闭与你的提交相关的其他 PR。

拥有非 FreeBSD.org Bugzilla 账户的提交者,可以通过以下步骤将旧账户与 FreeBSD.org 账户合并:

  1. 使用旧账户登录。

  2. 打开一个新的 bug,选择 Services 作为产品,选择 Bug Tracker 作为组件。在 bug 诉说中列出你希望合并的账户。

  3. 如果要合并的账户超过两个,请从每个账户发布评论。

你可以在以下链接了解更多关于 Bugzilla 的信息:

16. Phabricator

拥有非 FreeBSD.org Phabricator 账户的提交者,可以通过以下步骤将旧账户重命名为 FreeBSD.org 账户:

  1. 将你的 Phabricator 账户电子邮件更改为你的 FreeBSD.org 邮件。

重要

Phabricator 账户不能合并,请勿创建新账户。

17. 谁是谁

除了仓库管理者之外,还有其他 FreeBSD 项目成员和团队,你可能会在作为提交者的过程中接触到。简要介绍如下(并非全面列举):

Konstantin Belousov (kib@FreeBSD.org), Dave Cottlehuber (dch@FreeBSD.org), Marc Fonvieille (blackend@FreeBSD.org), John Hixson (jhixson@FreeBSD.org), Xin Li (delphij@FreeBSD.org), Ed Maste (emaste@FreeBSD.org), Mahdi Mokhtari (mmokhi@FreeBSD.org), Colin Percival (cperciva@FreeBSD.org), Doug Rabson (dfr@FreeBSD.org), Muhammad Moinur Rahman (bofh@FreeBSD.org):这些成员属于 Release Engineering Team (re@FreeBSD.org)。该团队负责设置发布截止日期和控制发布过程。在代码冻结期间,发布工程师对即将发布版本的系统更改拥有最终权力。如果你希望从 FreeBSD-CURRENT 合并更改到 FreeBSD-STABLE(无论在任何给定时刻这些值为何),这些人是你应该联系的对象。

FreeBSD 提交者邮件列表 {dev-src-all}、{dev-ports-all} 和 {dev-doc-all} 是版本控制系统用来发送提交消息的邮件列表。永远不要直接向这些列表发送邮件。只有在邮件简短且与提交直接相关时,才可以回复这些列表。

FreeBSD 开发者邮件列表:所有提交者都已订阅该列表。这个列表是为提交者的“社区”问题设立的论坛。举例来说,核心投票、公告等。

FreeBSD 开发者邮件列表仅供 FreeBSD 提交者使用。为了开发 FreeBSD,提交者必须能够公开讨论将要解决的问题,直到它们公开发布之前。正在进行中的工作讨论不适合公开发布,否则可能会对 FreeBSD 产生不利影响。

所有 FreeBSD 提交者应确保未经所有作者许可,不将 FreeBSD 开发者邮件列表的消息公开或转发给列表外的人。违规者将被从 FreeBSD 开发者邮件列表中移除,从而暂停提交权限。反复或严重违反者可能会被永久撤销提交权限。

该列表 不是 用于代码审查或任何技术讨论的地方。实际上,使用该列表进行此类讨论会对 FreeBSD 项目造成伤害,因为它给人一种错误印象,认为所有影响到 FreeBSD 使用社区的决策都是在一个封闭的列表中做出的,而没有做到“公开”。最后但同样重要的是 绝对不要 向 FreeBSD 开发者邮件列表发送电子邮件并将其他 FreeBSD 邮件列表作为抄送/密件抄送对象。这样做会极大地削弱该列表的作用。

18. SSH 快速入门指南

  1. 重要

    仅支持 ECDSA、Ed25519 或 RSA 密钥。

  2. 将你的公钥($HOME/.ssh/id_ecdsa.pub、$HOME/.ssh/id_ed25519.pub 或 $HOME/.ssh/id_rsa.pub)发送给设置你为提交者的人,以便将其放入 freefall 上的 /etc/ssh-keys/ 目录中的 yourlogin。

通过一个简单的远程命令进行测试:ssh freefall.FreeBSD.org ls /usr。

19. FreeBSD 提交者的 Coverity® 可用性

最后,所有打算使用 Coverity® 的 FreeBSD 开发者都应随时通过向 FreeBSD 开发者的邮件列表提问来获取更多的详细信息和使用信息。

20. FreeBSD 提交者的详细规则清单

  1. 尊重其他提交者。

  2. 尊重其他贡献者。

  3. 在提交之前讨论任何重大更改。

  4. 尊重现有的维护者(若在 Makefile 中的 MAINTAINER 字段或顶层目录中的 MAINTAINER 字段中列出)。

  5. 如果维护者要求,一切有争议的更改必须在争议解决之前撤回。与安全相关的更改可以在安全官员的裁定下覆盖维护者的意见。

  6. 更改应先提交到 FreeBSD-CURRENT,再提交到 FreeBSD-STABLE,除非发布工程师特别允许,或者它们不适用于 FreeBSD-CURRENT。任何非琐碎或非紧急的更改应该在 FreeBSD-CURRENT 中至少等待 3 天,以便进行充分的测试。发布工程师对 FreeBSD-STABLE 分支拥有与规则 #5 中维护者相同的权力。

  7. 不要与其他提交者公开争吵;这会显得不好。

  8. 尊重所有代码冻结,及时阅读 committers 和 developers 邮件列表,了解何时实施代码冻结。

  9. 对于任何程序有疑问时,先询问!

  10. 提交更改之前请先进行测试。

  11. 在没有 明确 获得相关维护者的批准的情况下,不要提交外部软件。

如上所述,违反这些规则可能会导致暂停提交权限,或者在多次违规后永久撤销提交权限。核心成员有权暂时暂停提交权限,直到核心小组有机会审查该问题。在“紧急情况”(如提交者对代码库造成损害)下,代码库管理员也可以暂时暂停权限。只有 2/3 多数的核心成员有权暂停提交权限超过一周,或永久撤销权限。此规则的存在并不是为了让核心成为一群残酷的独裁者,而是为了给项目提供一种安全保护措施。如果某人失控,能够立即采取行动而不被辩论拖延是非常重要的。在所有情况下,提交者的权限被暂停或撤销时,有权要求由核心进行“听证”,暂停的总时长将在那时确定。提交者在暂停权限后还可以在 30 天后请求复审,并且每 30 天复审一次(除非暂停期少于 30 天)。完全撤销权限的提交者可以在 6 个月后请求复审。该复审政策是 严格非正式的,在所有情况下,核心小组保留在认为原决策正确时可以选择是否审理复审请求的权利。

在项目运营的其他方面,核心小组是提交者的一个子集,必须遵守 相同的规则。仅仅因为某人是核心成员,并不意味着他们可以越过任何已定的规则;核心小组的“特别权力”仅在作为小组行动时才生效,而非个人行为。作为个体,核心团队成员首先是提交者,其次才是核心成员。

20.1. 细节

  1. 尊重其他提交者。 这意味着你需要将其他提交者视为同行开发者。尽管我们偶尔会试图证明相反的事情,但成为提交者并不是因为愚蠢,而最让人不爽的事情就是被同行如此对待。无论我们是否总是感到彼此尊重(每个人都有情绪低落的时候),我们仍然必须始终以尊重的态度对待其他提交者,无论是在公开论坛上还是在私人邮件中。

    长期合作的能力是这个项目最宝贵的资产,它远比任何代码更重要,将关于代码的争论转化为影响我们长期和谐合作的问题,是无法接受的。

    为了遵守这一规则,当你生气时,不要发送电子邮件,或者以可能让他人感到不必要对抗的方式行事。先冷静下来,然后思考如何以最有效的方式沟通,来让对方相信你的论点是正确的,而不是为了短期内的宣泄而进行一场长期的争论。这不仅是非常不好的“能量经济学”,反复在公众场合表现出攻击性行为会严重影响我们合作的能力,项目领导层会严肃处理,并可能导致提交权限的暂停或终止。项目领导层会考虑到公开和私下的沟通,但不会主动披露私人沟通,除非相关提交者自愿提供。

    这些都不是项目领导层希望看到的,但团结至上。没有任何代码或好建议值得为此做出妥协。

  2. 尊重其他贡献者。 你曾不是提交者。曾几何时,你也是位贡献者。请始终记住这一点。记住曾经尝试寻求帮助和关注的感受。不要忘记你作为贡献者时的感受。记住那个时候,自己是多么重要。不要打击、贬低或轻视贡献者。尊重他们。他们是我们未来的提交者。与提交者一样,他们对项目的贡献同样重要。毕竟,在成为提交者之前,你也是如此贡献过。切记这一点。

  3. 在提交任何重大更改之前进行讨论。 仓库不是初次提交更改的地方,那里是用来纠正错误或者争论的,通常是在邮件列表或通过使用 Phabricator 服务时进行。提交只有在达成一定共识后才会发生。这并不意味着在纠正每个明显的语法错误或手册页拼写错误之前都需要得到许可,只是要培养一种感觉,当提议的更改不是那么显而易见时,最好先征求一些反馈。在一般情况下,如果某个变更明显比之前的版本更好,人们不介意大规模的更改,但他们不喜欢被 突然 改变。确保事情走上正轨的最佳方式是让一个或多个提交者进行代码审查。

    如果不确定,请请求审查!

  4. 任何有争议的更改必须在维护者要求时撤回,直到争议解决。如果涉及安全相关的更改,则可以在安全官员的裁量下覆盖维护者的意愿。 在冲突时(每一方都坚信自己是对的),这可能很难接受,但版本控制系统让我们不必让争论继续下去,直接撤回有争议的更改,冷静下来后再找出最佳的解决方案。如果更改最终被证明是正确的,完全可以轻松地恢复。如果结果证明这不是最好的选择,那么用户就不必在争论期间忍受无效的更改。很少会有人在仓库中要求撤回更改,因为大多数争议都在提交前通过讨论解决,但在这种罕见的情况下,撤回更改应毫不犹豫,以便我们立即着手解决它是否合理的问题。

  5. 更改应提交到 FreeBSD-CURRENT 而不是 FreeBSD-STABLE,除非发布工程师特别许可,或除非更改与 FreeBSD-CURRENT 无关。任何重要或非紧急的更改也应在 FreeBSD-CURRENT 中放置至少3天,确保充分的测试。发布工程师对 FreeBSD-STABLE 分支拥有与规则5相同的权威。 这是一个“不要争论”的问题,因为最终如果更改导致问题,责任由发布工程师承担。请尊重这一点,并在涉及 FreeBSD-STABLE 分支时全力配合发布工程师。FreeBSD-STABLE 的管理可能对外人来说显得过于保守,但要记住保守主义是 FreeBSD-STABLE 的标志,不同规则适用于 FreeBSD-STABLE 分支,不能立即将更改合并到 FreeBSD-STABLE。FreeBSD-CURRENT 开发者需要一定的时间进行测试,因此请允许时间过去,除非该更改对 FreeBSD-STABLE 至关重要、时间敏感或明显到不需要更多测试(例如拼写修正、显而易见的bug或拼写错误修复等)。换句话说,运用常识。

    对于安全分支(例如 releng/9.3)的更改,必须经过 Security Officer Team 成员或在某些情况下,Release Engineering Team 成员的批准。

  6. 不要在公开场合与其他提交者争斗;这看起来很不好。 FreeBSD 项目有公共形象,必须维护好这个形象,这对我们所有人都很重要,特别是在我们吸引新成员时。即便大家都极力控制情绪,也难免会发生情绪失控、言辞激烈的情况。最好的做法是在此类情况发生时尽量减少影响,直到大家冷静下来为止。不要在公开场合发泄愤怒的言辞,也不要将私人通信或其他私人交流转发到公开邮件列表、邮件别名、即时通讯渠道或社交媒体网站上。一个人在私下发送的言辞通常比在公开场合更直白、尖锐,这些内容不适合公开场合——它们只会加剧已经不好的局势。如果发送者至少有尊重,不将其公之于众,那么你也应当同样尊重这一点,保持私密。如果你觉得自己受到其他开发者的不公对待,导致痛苦,请将此事报告给核心小组,而不是公开化。核心小组会尽力充当和平使者,恢复理性。如果争论涉及对代码库的更改,且参与者未能达成和解,核心小组可能会指定一个双方同意的第三方来解决争端。所有相关方必须同意接受该第三方的决定。

  7. 尊重所有代码冻结并及时阅读 committers 和 developers 邮件列表,以便了解代码冻结的生效时间。 在代码冻结期间提交未经批准的更改是一个非常大的错误,提交者需要保持最新的状态,了解发生的事情,避免在长时间缺席后跳进来并提交积累的 10 兆字节的内容。那些经常滥用此行为的人,将被暂停提交权限,直到他们从我们在格林兰岛举办的 FreeBSD 快乐再教育营返回。

  8. 对于任何程序不确定时,先询问! 许多错误都是因为某人在匆忙中假设自己知道正确的做法。如果你以前没有做过,通常你并不知道我们是如何操作的,真的需要先问一问,否则你会在公开场合完全让自己出丑。问“我到底该怎么做?”没有什么可耻的。我们已经知道你是个聪明人,否则你不会成为提交者。

  9. 未经相应维护者 明确 批准,不要提交第三方软件。 第三方软件是指所有位于 src/contrib、src/crypto 或 src/sys/contrib 目录下的软件。

上述目录用于通常从供应商分支导入的第三方软件。将内容提交到这些目录可能会在导入新版本的软件时带来不必要的麻烦。一般来说,考虑将补丁发送到上游供应商。补丁可以在维护者许可下先提交到 FreeBSD。

修改上游软件的原因包括希望对紧密耦合的依赖关系进行严格控制,或是上游代码的可移植性问题。无论原因如何,尽量减少复刻的维护负担对其他维护者是有益的。避免提交琐碎或外观上的更改,因为这些会使之后的合并变得更加困难:这些补丁需要在每次导入时手动重新验证。

20.2. 多架构政策

FreeBSD 在最近的发布周期中新增了多个架构 Port,真正不再是一个以 i386™ 为中心的操作系统。为了使 FreeBSD 在我们支持的平台上保持良好的可移植性,核心团队制定了以下规定:

我们的 32 位参考平台是 i386,64 位参考平台是 amd64。重大设计工作(包括重大 API 和 ABI 更改)必须在至少一个 32 位平台和一个 64 位平台上证明其可行性,最好是在主要参考平台上,才能提交到源码树中。

开发者还应了解我们的架构长期支持的 Tier 政策。这里的规则是为了在开发过程中提供指导,并且与发布时列出的特性和架构的要求不同。在开发过程中,架构的支持规则比发布时的规则要宽松得多。

20.3. 多编译器政策

FreeBSD 使用 Clang 和 GCC 进行构建。该项目以一种谨慎且可控的方式进行,以最大化这种额外工作的好处,同时将额外工作的量降到最低。支持 Clang 和 GCC 提高了我们用户的灵活性。这些编译器有不同的优缺点,支持这两者让用户可以根据自己的需求选择最佳的编译器。Clang 和 GCC 支持类似的 C 和 C++ 方言,只需相对较少的条件代码。该项目通过使用两种编译器的特性,增加了代码覆盖率并提高了代码质量。通过支持这种范围,项目能够在更多用户环境中构建并利用更多 CI 环境,从而提高了用户的便利性,并为他们提供了更多测试工具。通过严格限制支持的版本范围为这些编译器的现代版本,项目避免了过度增加测试矩阵。旧的和冷门的编译器以及旧的语言方言有极其有限的支持,允许用户程序使用它们进行构建,但不要求基本系统必须使用这些编译器进行构建。具体的平衡仍在不断演变,以确保额外工作的好处大于它所带来的负担。该项目曾经支持非常旧的 Intel 编译器或旧版本的 GCC,但我们用精心选择的现代编译器替代了对这些过时编译器的支持。本节记录了我们在哪里使用不同的编译器,以及对此的期望。

FreeBSD 项目提供了一个内嵌的 Clang 编译器。由于它在源码树中,因此这是最受支持的编译器。所有更改在提交之前必须与它编译通过。根据更改的性质,应使用此编译器进行完整的测试。

FreeBSD 项目还在 GitHub 上拥有一些 CI 管道。对于 GitHub 上的拉取请求和推送到 GitHub 分支的某些代码,运行了一些交叉编译作业。这些作业测试 FreeBSD 使用一个可能比内嵌编译器版本落后一个大版本的 Clang 版本进行构建。

FreeBSD 项目还在升级编译器。Clang 和 GCC 都是快速发展的目标。一些更改可能会在编译器到达之前先在源码树中实施,例如移除旧式的 K&R 函数声明和定义。提交者应尽量注意这一点,并乐于调查新编译器可能带来的代码问题或更改。此外,在新的编译器版本加入源码树后,若怀疑存在未被检测的回归,可能需要使用旧版本进行编译。

除了编译器,LLVM 的 LLD 和 GNU 的 binutils 也由编译器间接使用。提交者应注意汇编语法和链接器特性的差异,并确保两者的变种都能正常工作。这些组件将作为 FreeBSD CI 任务的一部分进行测试,适用于 Clang 或 GCC。

FreeBSD 项目提供了头文件和库,允许使用其他编译器来构建非基本系统中的软件。这些头文件支持将环境尽可能严格地与标准一致,支持 ANSI-C 先前方言,直到 C89,并处理我们庞大的 Ports 中所发现的其他边缘情况。这个支持限制了旧标准在头文件中的淘汰,但不限制基本系统更新到更新的方言,也不要求基本系统整体上使用这些旧标准进行编译。破坏这一支持将导致 Ports 中的软件包构建失败,因此应尽量避免,并在容易修复时尽快修复。

FreeBSD 构建系统目前适应这些不同的环境。随着编译器中新警告的加入,项目尝试修复它们。然而,有时这些警告需要大量重构,因此会通过使用根据编译器版本评估的 make 变量某种方式被抑制。开发者应注意这一点,确保任何特定于编译器的标志都能正确地进行条件处理。

20.3.1. 当前编译器版本

目前,内嵌的编译器是 Clang 15.x。目前,GCC 12 和 Clang 12、13、14、15 在 GitHub 和项目的 CI Jenkins 任务中进行了测试。相关工作正在进行,以准备将 Clang 16 版本加入源码树。最旧的项目支持分支使用 Clang 12,因此构建的引导部分必须适用于 Clang 12 至 15 的主版本。

20.4. 其他建议

不要将样式修复与新功能混合。样式修复是指不修改代码功能的任何更改。混合这些更改会在询问修订差异时混淆功能性更改,这可能会隐藏任何新引入的 bug。不要在提交 doc/ 时将空白更改与内容更改混合。差异中的额外杂乱会使翻译人员的工作更加困难。相反,请将任何样式或空白更改放在单独的提交中,并在提交消息中明确标明这些更改。

20.5. 弃用功能

当需要从基本系统中删除功能时,请尽可能遵循以下指南:

  1. 在手册页和可能的发布说明中提到该选项、工具或接口已弃用。使用弃用的功能会生成警告。

  2. 该选项、工具或接口在下一个主要(零点版本)发布之前保留。

  3. 该选项、工具或接口被移除且不再文档化。它现在已过时。通常,最好在发布说明中指出其移除。

20.6. 隐私与保密

  1. 大多数 FreeBSD 的事务是公开的。 FreeBSD 是一项 开源 项目。这意味着不仅任何人都可以使用源代码,而且大多数开发过程都向公众开放,接受公众审查。

  2. 某些敏感事项必须保持私密或处于禁运状态。 不幸的是,无法实现完全透明。作为 FreeBSD 开发者,你将拥有一定程度的特权信息访问权限。因此,你需要遵守某些保密要求。有时保密的需求来自外部合作者或具有特定时间限制的要求。大多数情况下,这是关于不公开私人通信的。

  3. 安全官员单独控制安全公告的发布。 当有影响多个操作系统的安全问题时,FreeBSD 经常依赖提前获取信息,以便为协调发布准备安全公告。除非可以信任 FreeBSD 开发者维护安全性,否则不会提前提供此类信息。安全官员负责控制漏洞信息的预发布访问,并决定所有公告的发布时机。如果有相关知识的开发者能够在保密条件下提供帮助,安全官员可以请求协助准备安全修复。

  4. 与核心小组的通信应保密,直到必要时。 向核心小组的通信最初将被视为保密。然而,核心小组的大多数事务最终会被总结在每月或每季度的核心报告中。但会小心避免公开任何敏感细节。某些特别敏感的议题可能完全不会报告,并且只会保留在核心小组的私人档案中。

  5. 对于访问某些商业敏感数据,可能需要签署保密协议。 访问某些商业敏感数据可能仅在签署保密协议的情况下才可获得。FreeBSD 基金会的法律人员必须在任何具有约束力的协议签署之前进行咨询。

  6. 未经许可不得公开私人通信。 除了上述特定要求外,一般期望未经所有相关方的同意,不得公开开发者之间的私人通信。在将消息转发到公共邮件列表或发布到可能被原始通信者之外的其他人访问的论坛或网站之前,请先征得许可。

  7. 项目专用或受限访问渠道的通信必须保持私密。 类似于个人通信,某些内部通信渠道(包括 FreeBSD 提交者专用邮件列表和受限访问的 IRC 渠道)被视为私人通信。发布这些来源的材料需要获得许可。

  8. 核心小组可以批准公开。 如果由于通信者数量过多或因不合理地拒绝发布而难以获得许可,核心小组可以批准发布这些私密事务,前提是这些事务值得更广泛的公开。

21. 支持多种架构

FreeBSD 是一个高度可移植的操作系统,旨在支持多种不同类型的硬件架构。保持机器相关(MD)代码与机器无关(MI)代码的清晰分离,并尽量减少 MD 代码,是我们保持对当前硬件趋势灵活性的战略重要组成部分。每增加一种硬件架构的支持,都会显著增加代码维护、工具链支持和发布工程的成本。它还会大幅提高对内核更改进行有效测试的成本。因此,强烈的动机是区分各种架构支持的不同级别,同时在一些被视为 FreeBSD “目标受众”的关键架构上保持强大的支持。

21.1. 一般意图声明

FreeBSD 项目专注于“生产质量的商业现货(COTS)工作站、服务器和高端嵌入式系统”。通过将重点保持在这些环境中感兴趣的有限架构集上,FreeBSD 项目能够维持高水平的质量、稳定性和性能,同时最小化项目中各个支持团队的负担,如 Port 团队、文档团队、安全官员和发布工程团队。硬件支持的多样性通过提供新特性和使用机会,拓宽了 FreeBSD 用户的选择,但这些好处必须始终仔细考虑与额外平台支持相关的实际维护成本。

FreeBSD 项目将平台目标分为四个级别。每个级别包括消费者可以依赖的保证清单,以及项目和开发者为履行这些保证所承担的义务。这些清单定义了每个级别的最低保证。项目和开发者可能提供超出最低保证的额外支持,但这些额外支持不作保证。每个平台目标会根据每个稳定分支分配到一个特定的级别。因此,某个平台目标可能会在并行的稳定分支中分配到不同的级别。

21.2. 平台目标

硬件平台的支持由两个组件组成:内核支持和用户空间应用程序二进制接口(ABI)。内核平台支持包括使 FreeBSD 内核能够在硬件平台上运行所需的内容,如机器相关的虚拟内存管理和设备驱动程序。用户空间 ABI 指定了用户进程与 FreeBSD 内核和基本系统库进行交互的接口。用户空间 ABI 包括系统调用接口、公共数据结构的布局和语义、以及传递给子程序的参数的布局和语义。某些 ABI 组件可能由规范定义,如 C++ 异常对象的布局或 C 函数的调用约定。

FreeBSD 内核也使用 ABI(有时称为内核二进制接口(KBI)),包括公共数据结构的语义和布局,以及内核中公共函数的参数的语义和布局。

FreeBSD 内核可以支持多个用户空间 ABI。例如,FreeBSD 的 amd64 内核支持 FreeBSD amd64 和 i386 用户空间 ABI,以及 Linux x86_64 和 i386 用户空间 ABI。FreeBSD 内核应支持一个“本地” ABI 作为默认 ABI。本地 ABI 通常与内核 ABI 共享某些特性,如 C 调用约定、基本类型的大小等。

内核和用户空间 ABI 都有相应的级别定义。在常见的情况下,平台的内核和 FreeBSD ABI 被分配到相同的级别。

21.2.1. 第一层:完全支持的架构

第一层平台是最成熟的 FreeBSD 平台。它们由安全官员、发布工程和 Ports 管理团队支持。第一层架构预计在 FreeBSD 操作系统的所有方面(包括安装和开发环境)都具有生产质量。

FreeBSD 项目为第一层平台的消费者提供以下保证:

  • 发布工程团队将提供官方的 FreeBSD 发布镜像。

  • 将为受支持的版本提供安全通告和勘误通知的二进制更新和源代码补丁。

  • 将为受支持的分支提供安全通告的源代码补丁。

  • 跨平台的安全通告的二进制更新和源代码补丁通常会在公告时提供。

  • 用户空间 ABI 的变化通常会包含兼容性补丁,以确保在该平台为第一层的任何稳定分支上编译的二进制文件正确运行。这些补丁可能不会在默认安装中启用。如果对 ABI 更改没有提供兼容性补丁,缺少补丁的情况将会在发布说明中清楚地记录。

  • 某些内核 ABI 部分的变化将包括兼容性补丁,以确保在该分支上最早支持的版本中编译的内核模块正确运行。请注意,并非所有内核 ABI 部分都受到保护。

  • 第三方软件的官方二进制包将由 Port 团队提供。对于嵌入式架构,这些包可能会从不同架构交叉构建。

  • 大多数相关的 Port 应该能够构建,或者具有适当的过滤器,防止不适当的 Port 被构建。

  • 不特定于平台的新功能将在所有第一层架构上完全功能。

  • 用于与较旧稳定分支编译的二进制文件的功能和兼容性补丁可能会在较新的主版本中删除。这些删除将会在发布说明中清楚地记录。

  • 第一层平台应该有完整的文档。基本操作将在 FreeBSD 手册中记录。

  • 第一层平台将包含在源代码树中。

  • 第一层平台应该能够自我托管,无论是通过内建工具链还是外部工具链。如果需要外部工具链,将提供外部工具链的官方二进制包。

为了保持第一层平台的成熟,FreeBSD 项目将维护以下资源来支持开发:

  • 在 FreeBSD.org 集群或其他易于所有开发者访问的位置提供构建和测试自动化支持。嵌入式平台可能使用 FreeBSD.org 集群中的仿真器代替实际硬件。

  • 包含在 make universe 和 make tinderbox 目标中。

  • 在 FreeBSD 集群中为包构建提供专用硬件(无论是本地构建还是通过 qemu-user)。

为了维持第一层平台的状态,开发者需要提供以下内容:

  • 对源代码树的更改不得故意破坏第一层平台的构建。

  • 第一层架构必须拥有成熟、健康的用户生态系统和活跃的开发者。

  • 开发者应该能够在常见的非嵌入式第一层系统上构建包。这可以意味着,如果平台在该平台上常见,使用本地构建,或者可以意味着在其他第一层架构上进行交叉构建。

  • 更改不能破坏用户空间 ABI。如果需要更改 ABI,应通过符号版本控制或共享库版本提升提供对现有二进制文件的 ABI 兼容性。

  • 合并到 Stable 分支的更改不能破坏内核 ABI 的受保护部分。如果需要更改内核 ABI,应对更改进行修改,以保持现有内核模块的功能。

21.2.2. 第二层:开发性和小众架构

第二层平台是功能性存在,但成熟度较低的 FreeBSD 平台。它们不受安全官员、发布工程和 Ports 管理团队的支持。

第二层平台可能是仍在积极开发中的第一层平台候选者。达到生命周期终结的架构也可能从第一层状态转为第二层状态,因为持续保持系统在生产质量状态下的资源可用性减少。受到良好支持的小众架构也可以是第二层。

FreeBSD 项目为第二层平台的消费者提供以下保证:

  • Port 基础设施应包括对第二层架构的基本支持,足以支持构建 Port 和包。这包括对基本包(如 ports-mgmt/pkg)的支持,但不能保证所有 Port 都能够构建或正常运行。

  • 不特定于平台的新功能,如果未实现,应该在所有第二层架构上可行。

  • 第二层平台将包含在源代码树中。

  • 第二层平台应该能够自我托管,无论是通过内建工具链还是外部工具链。如果需要外部工具链,将提供外部工具链的官方二进制包。

  • 第二层平台应提供功能性内核和用户空间,即使未提供官方发布的分发版。

为了保持第二层平台的成熟,FreeBSD 项目将维护以下资源来支持开发:

  • 包含在 make universe 和 make tinderbox 目标中。

为了维持第二层平台的状态,开发者需要提供以下内容:

  • 对源代码树的更改不得故意破坏第二层平台的构建。

  • 第二层架构必须拥有活跃的用户和开发者生态系统。

  • 虽然允许更改破坏用户空间 ABI,但不应无故破坏 ABI。重大用户空间 ABI 更改应限制在主版本中。

  • 尚未在第二层架构上实现的新功能,应提供在这些架构上禁用它们的方式。

21.2.3. 第三层:实验性架构

第三层平台至少有部分 FreeBSD 支持。它们不受安全官员、发布工程和 Ports 管理团队的支持。

第三层平台是处于开发初期的架构,面向非主流硬件平台,或者是被认为不太可能在未来广泛使用的遗留系统。第三层平台的初步支持可能存在于一个独立的仓库中,而非主源代码仓库。

FreeBSD 项目不为第三层平台的消费者提供任何保证,也不承诺维护支持开发的资源。第三层平台可能并不总是可构建的,内核或用户空间 ABI 也不被视为稳定的。

21.2.4. 不支持的架构

其他平台在任何形式上都不受项目支持。项目以前将这些平台称为第四层系统。

平台转为不受支持后,所有对该平台的支持将从源代码、Ports 和文档树中移除。请注意,Ports 支持应在平台仍受 Ports 支持的分支中保持。

21.3. 改变架构层级的政策

系统只能在获得 FreeBSD 核心团队批准后,才能从一个层级移动到另一个层级。核心团队将与安全官员、发布工程和 Ports 管理团队合作做出这一决定。要将平台晋升到更高的层级,必须在晋升完成之前满足任何缺失的支持保证。

22. Port 相关常见问题

22.1. 添加新 Port

22.1.1. 如何添加新 Port ?

SUBDIR += newport

Port 及其类别的 Makefile 准备好后,可以提交新 Port :

% git add category/Makefile category/newport
% git commit
% git push

22.1.2. 添加新 Port 时需要注意的其他事项?

检查 Port,最好确保它能够正确编译和打包。

你不必消除所有警告,但请确保修复简单的警告。

如果 Port 是通过 PR 提交的,请关闭 PR。关闭 PR 时,状态改为 Issue Resolved,并将解决方案设为 Fixed。

注意

# make package
# make deinstall
# pkg add 上述构建的包
# make deinstall
# make reinstall
# make package`

请注意,poudriere 是包构建的参考工具,如果无法在 poudriere 中构建 Port,Port 将被移除。

22.2. 移除现有 Port

22.2.1. 如何移除现有 Port?

首先,请阅读关于仓库副本的章节。在移除 Port 之前,你需要验证是否没有其他 Port 依赖于它。

  • 确保该 Port 在 Ports 中没有依赖:

    • 该 Port 的 PKGNAME 在最近的 INDEX 文件中只出现一次。

    • 没有其他 Port 在其 Makefile 中包含该 Port 的目录或 PKGNAME。

      技巧

  • 然后,移除 Port:

    • 使用 git rm 移除该 Port 的文件和目录。

    • 从父目录的 Makefile 中移除该 Port 的 SUBDIR 列表。

    • 在 ports/MOVED 中添加条目。

    • 如果该 Port 在 ports/LEGAL 中,移除该 Port。

22.3. 如何将 Port 移到新位置?

  1. 从旧类别的 Makefile 中移除 SUBDIR 条目,并将 SUBDIR 条目添加到新类别的 Makefile 中。

  2. 在 ports/MOVED 中添加条目。

  3. 在 ports/security/vuxml 中搜索 xml 文件中的条目,并相应地调整它们。特别是,检查以前的包是否有可能包含新 Port 的版本。

  4. 使用 git mv 移动 Port。

  5. 提交更改。

22.4. 如何将 Port 复制到新位置?

  1. 使用 cp -R old-cat/old-port new-cat/new-port 复制 Port。

  2. 将新 Port 添加到 new-cat/Makefile 中。

  3. 修改 new-cat/new-port 中的内容。

  4. 提交更改。

22.5. Port 冻结

22.5.1. 什么是“ Port 冻结”?

“Port 冻结”是指在发布之前将 Port 树置于受限状态。这是为了确保发布的包具有更高的质量,通常持续几周。在此期间,修复构建问题并构建发布包。这个做法现在不再使用,因为发布包是从当前稳定的季度分支构建的。

22.6. 季度分支

22.6.1. 请求将提交合并到季度分支的程序是什么?

自 2020 年 11 月 30 日起,无需明确请求批准即可提交到季度分支。

22.6.2. 如何将提交合并到季度分支?

将提交合并到季度分支的过程(我们称之为 MFH,历史原因)与将提交合并到 src 仓库的 MFC 非常相似,因此基本流程如下:

% git checkout 2021Q2
% git cherry-pick -x $HASH
(验证一切是否正常,例如通过构建测试)
% git push

其中 $HASH 是你希望复制到季度分支的提交的哈希值。-x 参数确保将 main 分支的 $HASH 哈希值包含在季度分支的新提交信息中。

22.7. 创建新类别

22.7.1. 创建新类别的程序是什么?

  1. 执行任何必要的迁移(仅适用于物理类别)。

  2. 更新 ports/Mk/bsd.port.mk 中的 VALID_CATEGORIES 定义。

  3. 将 PR 返还给你。

22.7.2. 我需要做什么来实现一个新的物理类别?

  1. 升级每个已迁移 Port 的 Makefile。此时不要将新类别连接到构建。 要做到这一点,你需要:

    1. 修改 Port 的 CATEGORIES(记住,这是该步骤的目标)。将新类别列在第一位。这样有助于确保 PKGORIGIN 正确。

    2. 运行 make describe。由于接下来你将在几步中运行的顶层 make index 是对整个 Port 层次结构执行 make describe,在此步骤中捕获错误可以避免之后再次运行该步骤。

  2. 在本地系统上测试所提议的更改:首先,注释掉旧 Port 类别 Makefile 中的 SUBDIR 条目;然后在 ports/Makefile 中启用新类别的构建。运行 make checksubdirs 来检查 SUBDIR 条目。接下来,在 ports/ 目录中运行 make index。即使在现代系统上,这个过程也可能需要超过 40 分钟;但是,这是一个必要的步骤,以防止其他人遇到问题。

  3. 完成上述步骤后,你可以提交更新后的 ports/Makefile,以将新类别连接到构建中,并提交旧类别的 Makefile 更改。

  4. 在 ports/MOVED 中添加适当的条目。

  5. 通过修改以下内容更新文档:

  6. 只有在完成上述所有步骤并且没有人再报告新 Port 的问题时,才能从其原来的位置删除旧 Port 。

22.7.3. 我需要做什么来实现一个新的虚拟类别?

这比物理类别简单得多,仅需进行一些修改:

22.8. 杂项问题

22.8.1. 是否可以在没有维护者批准的情况下提交更改?

对于大多数 Port ,以下类型的修复可以在没有维护者批准的情况下提交:

  • Port 的基础设施更改(即现代化,但不改变功能)。例如,涵盖了转换为新的 USES 宏、启用详细构建以及切换到新的 Port 系统语法。

  • 微小且已 测试 的构建和运行时修复。

  • Port 的文档或元数据更改,如 pkg-descr 或 COMMENT。

22.8.2. 我如何知道我的 Port 是否构建正确?

每周会多次构建包。如果 Port 失败,维护者将收到来自 pkg-fallout@FreeBSD.org 的电子邮件。

22.8.3. 我添加了一个新 Port 。是否需要将其添加到 INDEX 文件中?

不需要。可以通过运行 make index 来生成该文件,或者通过 make fetchindex 下载一个预生成的版本。

22.8.4. 是否还有其他我不能触碰的文件?

22.8.5. 当分发文件更改但版本没有改变时,更新 Port 的校验和的正确程序是什么?

当由于作者更新文件而不更改 Port 修订时,更新分发文件的校验和,提交信息中应包含原始文件和新文件之间相关差异的摘要,以确保分发文件没有被损坏或恶意更改。如果当前版本的 Port 已经在 Port 树中存在较长时间,通常可以在 ftp 服务器上找到旧的分发文件;否则,应联系作者或维护者了解为何分发文件发生更改。

22.8.6. 如何请求 Port 树的实验性测试构建(exp-run)?

在提交对 Port 树有重大影响的补丁之前,必须完成 exp-run。补丁可以针对 Port 树或基本系统。

使用提交者提供的补丁将进行完整的包构建,提交者需要在提交前修复检测到的问题(fallout)。

  1. 选择补丁涉及的产品。

  2. 像往常一样填写缺陷报告,记得附上补丁。

  3. 如果顶部显示“Show Advanced Fields”,点击它。它现在会显示为“Hide Advanced Fields”。此时将有更多的字段可用。如果已经显示为“Hide Advanced Fields”,则无需进行任何操作。

  4. 在“Flags”部分,将“exp-run”设置为 ?。对于所有其他字段,将鼠标悬停在任何字段上可以查看更多详细信息。

  5. 提交。等待构建运行。

  6. 根据 fallout:

    • 如果没有 fallout,程序到此为止,变更可以提交,前提是需要其他批准。

      1. 如果有 fallout,必须 修复它,你可以直接修复 Port 树中的 Port ,或将其添加到提交的补丁中。

      2. 完成修复后,返回第 6 步并说明已修复 fallout,然后等待 exp-run 再次运行。只要有破损的 Port ,就重复此过程。

23. 非提交者开发者的特定问题

一些有权访问 FreeBSD 机器的人没有提交权限。几乎本文件的所有内容同样适用于这些开发者(除非是特定于提交的内容以及与提交相关的邮件列表成员资格)。特别是,我们建议你阅读:

  • 注意

    请确保你的导师将你添加到“附加贡献者”(doc/shared/contrib-additional.adoc)中——如果尚未列出你。

24. 关于 Google Analytics 的信息

自 2012 年 12 月 12 日起,FreeBSD 项目网站启用了 Google 分析,以收集关于网站使用的匿名统计数据。

注意

自 2022 年 3 月 3 日以降,FreeBSD 项目从网站中移除了 Google 分析。

25. 杂项问题

25.1. 如何访问 people.FreeBSD.org 来发布个人或项目信息?

25.2. 邮件列表归档存储在哪里?

邮件列表归档存储在 freefall.FreeBSD.org 上的 /local/mail 目录下。

25.3. 我想指导一位新提交者。我需要遵循什么流程?

26. FreeBSD 提交者的福利和特权

26.1. 认可

作为一名优秀的软件工程师的认可是最持久的价值。此外,有机会与每个工程师梦寐以求的最佳人合作,亦是巨大的特权!

26.2. FreeBSD 商城

26.3. Gandi.net

26.4. rsync.net

BSD 简介

摘要

在开源世界中,“Linux”一词几乎与“操作系统”同义,但它并不是唯一的开源 UNIX® 操作系统。

那么,秘密是什么?为什么 BSD 不那么为人所知?本文将解答这些问题以及其他相关问题。

在整篇文章中,将会指出 BSD 和 Linux 之间的差异,如这样。


1. 什么是 BSD?

BSD 代表“伯克利软件发行版”。它是加利福尼亚大学伯克利分校的源代码发行版的名称,最初是 AT&T Research UNIX® 操作系统的扩展。几个开源操作系统项目是基于被称为 4.4BSD-Lite 的源代码发布的。此外,这些项目还包含了来自其他开源项目的多个软件包,尤其是 GNU 项目。整体操作系统包括:

  • BSD 内核,处理进程调度、内存管理、对称多处理(SMP)、设备驱动程序等。

  • C 库,系统的基础 API。BSD C 库基于伯克利的代码,而非 GNU 项目的代码。

  • 工具程序,如 shell、文件工具、编译器和链接器。一些工具源自 GNU 项目,另一些则不是。

  • 许多其他程序和工具。

2. 真正的 UNIX®?

BSD 操作系统不是克隆版,而是 AT&T 研究 UNIX® 操作系统的开源衍生版,它也是现代 UNIX® System V 的祖先。这可能会让你感到惊讶。如何会这样,毕竟 AT&T 从未将其代码作为开源发布?

确实,AT&T UNIX® 并非开源,从版权的角度来看,BSD 绝对 不是 UNIX®,但另一方面,AT&T 引入了来自其他项目的源代码,尤其是加利福尼亚大学伯克利分校的计算机科学研究小组(CSRG)。从 1976 年开始,CSRG 开始发布他们的软件磁带,称之为 伯克利软件发行版 或 BSD。

最初的 BSD 版本主要包含用户程序,但随着 CSRG 获得了国防高级研究计划局(DARPA)的合同,用于升级其网络 ARPANET 的通信协议,这一情况发生了剧变。新的协议被称为 互联网协议,后来的 TCP/IP 即是指最重要的协议。第一次广泛分发的实现是 4.2BSD 的一部分,发生在 1982 年。

在 1980 年代的过程中,出现了许多新工作站公司。许多公司更倾向于授权 UNIX®,而不是自行开发操作系统。特别是 Sun Microsystems 授权 UNIX® 并实现了 4.2BSD 的一个版本,称为 SunOS™。当 AT&T 被允许商业销售 UNIX® 时,他们推出了一个简化版的实现,称为 System III,随后迅速推出了 System V。System V 的代码库不包括网络功能,因此所有实现都包括来自 BSD 的附加软件,包括 TCP/IP 软件,还包括 csh shell 和 vi 编辑器等工具。这些增强功能被统称为 伯克利扩展。

3. 为什么 BSD 不那么为人所知?

有多种原因,BSD 相对较不为人知:

  1. BSD 开发者通常更关注打磨代码,而非推广它。

  2. Linux 的流行很大程度上源于 Linux 项目外部的因素,例如媒体和为提供 Linux 服务而成立的公司。直到最近,开源的 BSD 项目并没有类似的支持者。

4. 比较 BSD 和 Linux

那么,Debian Linux 和 FreeBSD 之间的真正区别是什么呢?对于普通用户来说,差异惊人地小:它们都是类 UNIX® 操作系统。它们都是由非商业项目开发的(当然,这不适用于许多其他 Linux 发行版)。在接下来的部分中,我们将讨论 BSD,并将其与 Linux 进行比较。以下介绍最接近于 FreeBSD,它占据了估计 80% 的 BSD 安装,但与 NetBSD、OpenBSD 和 DragonFlyBSD 的差异不大。

4.1. 谁拥有 BSD?

没有任何个人或公司拥有 BSD。它是由全球一群技术精湛且高度投入的贡献者共同创建和分发的。BSD 的一些组件本身就是开源项目,由不同的项目维护者管理。

4.2. BSD 如何开发和更新?

BSD 内核按照开源开发模型进行开发和更新。每个项目都维护一个公开可访问的 源代码树,其中包含项目的所有源文件,包括文档和其他附带文件。用户可以获得任何版本的完整副本。

全球有大量开发者为 BSD 做出贡献,他们被分为三类:

  • 贡献者 编写代码或文档。他们不能直接向源代码树提交(添加代码)。为了让他们的代码包含在系统中,必须由一名注册的开发者审核并提交,这些开发者被称为 提交者。

  • 提交者 是具有写权限的开发者。要成为提交者,个人必须在其活跃的领域展示能力。 提交者是否在提交更改到源代码树之前需要获得授权由个人决定。通常,经验丰富的提交者可以在没有获得共识的情况下做出显然正确的更改。例如,文档项目的提交者可以在没有审核的情况下修正排版或语法错误。另一方面,做出深远或复杂更改的开发者应该在提交更改之前进行审核。极端情况下,像首席架构师这样的核心团队成员可能会命令将更改从源代码树中撤回,这一过程被称为 撤销。所有提交者都会收到描述每次提交的邮件,因此不可能偷偷提交代码。

  • 核心团队。FreeBSD 和 NetBSD 都有一个核心团队来管理项目。核心团队在项目过程中发展起来,它们的角色并不总是定义明确的。成为核心团队成员并不一定需要是开发者,尽管通常是。核心团队的规则因项目而异,但通常,他们在项目方向上的发言权大于非核心团队成员。

这种安排与 Linux 有几个不同之处:

  1. 没有人控制系统的内容。实际上,这个差异被高估了,因为首席架构师可以要求撤销代码,更何况在 Linux 项目中,也有几个人被允许做出更改。

  2. 另一方面,确实存在一个中央仓库,一个可以找到整个操作系统源代码的地方,包括所有旧版本。

  3. BSD 项目维护整个“操作系统”,而不仅仅是内核。这个区别仅在某种程度上有用:无论是 BSD 还是 Linux,没有应用程序都无法使用。BSD 使用的应用程序通常与 Linux 使用的应用程序相同。

  4. 由于对单一 Git 源代码树的正式维护,BSD 的开发过程清晰明确,并且可以通过发布号或日期访问任何版本的系统。Git 还允许对系统进行增量更新。例如,FreeBSD 仓库每天大约更新 100 次。这些更改大多数是小规模的。

4.3. BSD 发布版

FreeBSD、NetBSD 和 OpenBSD 提供了三种不同的“发布版”。与 Linux 类似,发布版会分配一个版本号,如 1.4.1 或 3.5。此外,版本号后有一个后缀,用于指示其用途:

  1. 系统的开发版被称为 CURRENT。FreeBSD 为 CURRENT 分配一个版本号,例如 FreeBSD 5.0-CURRENT。NetBSD 使用稍有不同的命名方式,并附加一个单字母后缀,指示内部接口的更改,例如 NetBSD 1.4.3G。OpenBSD 不分配版本号(“OpenBSD-current”)。所有新的开发工作都进入这个分支。

  2. 在常规间隔下,每年发布两到四次 RELEASE 版本的系统,用户可以从 CD-ROM 或 FTP 网站免费下载,例如 OpenBSD 2.6-RELEASE 或 NetBSD 1.4-RELEASE。RELEASE 版本是面向最终用户的,通常是系统的标准版本。NetBSD 还提供带有第三位数字的 补丁发布,例如 NetBSD 1.4.2。

  3. 当在 RELEASE 版本中发现错误时,错误会被修复,并将修复内容添加到 Git 树中。在 FreeBSD 中,结果版本被称为 STABLE 版本,而在 NetBSD 和 OpenBSD 中,它继续称为 RELEASE 版本。在经过 CURRENT 分支测试一段时间后,较小的新特性也可以添加到此分支中。安全和其他重要的错误修复也会应用到所有支持的 RELEASE 版本。

相比之下,Linux 维护两个独立的代码树:稳定版本和开发版本。稳定版本具有偶数的小版本号,如 2.0、2.2 或 2.4。开发版本具有奇数的小版本号,如 2.1、2.3 或 2.5。在每个版本号后面,还有一个进一步的数字来指定精确的发布版本。此外,每个发行商都会添加自己的用户空间程序和工具,因此发行版的名称也很重要。每个发行商还会为其发行版指定版本号,因此完整的描述可能是“TurboLinux 6.0,内核 2.2.14”

4.4. 有哪些版本的 BSD 可用?

与众多 Linux 发行版不同,只有四个主要的开源 BSD 系统。每个 BSD 项目维护自己的源代码树和内核。不过,实际上,各个项目的用户空间代码之间的差异比 Linux 中的差异要少。

很难对每个项目的目标进行分类:这些差异是非常主观的。基本上,

  • NetBSD 旨在实现最大的可移植性:“当然它能运行 NetBSD”。它支持从掌中宝到大型服务器的各种机器,甚至被用于 NASA 的太空任务。它特别适合在旧的非英特尔®硬件上运行。

  • OpenBSD 旨在实现安全性和代码纯洁性:它结合了开源概念和严格的代码审查,创造了一个明显正确的系统,成为了对安全高度关注的组织(如银行、证券交易所和美国政府部门)的首选。与 NetBSD 一样,它也支持多种平台。

  • DragonFlyBSD 旨在实现从单节点 UP 系统到大规模集群系统下的高性能和可扩展性。DragonFlyBSD 有几个长期的技术目标,但重点是提供一个易于理解、维护和开发的 SMP 支持基础设施。

此外,还有两个非开源的 BSD UNIX® 操作系统,分别是 BSD/OS 和 Apple 的 Mac OS® X:

  • BSD/OS 是 4.4BSD 衍生版本中最早的一个。它不是开源的,尽管源代码许可可以以相对较低的价格获得。它在许多方面与 FreeBSD 类似。Wind River Systems 收购 BSDi 后,BSD/OS 未能作为独立产品生存。Wind River 可能仍然提供支持和源代码,但所有新的开发都集中在 VxWorks 嵌入式操作系统上。

4.5. BSD 许可证与 GNU 公共许可证有什么不同?

4.6. 我还需要知道什么?

由于 BSD 上的应用程序比 Linux 少,BSD 开发者创建了一个 Linux 兼容包,允许 Linux 程序在 BSD 上运行。该包包括内核修改,以正确执行 Linux 系统调用,以及 Linux 兼容文件,如 C 库。在同样速度的机器上,运行 Linux 应用程序的 BSD 机器与运行 Linux 应用程序的 Linux 机器在执行速度上没有明显区别。

BSD 的“全由一个供应商提供”的特性意味着与 Linux 相比,升级更加容易处理。BSD 通过提供早期版本库的兼容模块来处理库版本的升级,因此可以毫无问题地运行几年前的二进制文件。

4.7. 我应该使用 BSD 还是 Linux?

这在实际中意味着什么?谁应该使用 BSD,谁应该使用 Linux?

这是个非常难以回答的问题。以下是一些指导原则:

  • “如果它没有坏,别修理它”:如果你已经在使用一个开源操作系统,并且你对此感到满意,那么可能没有充分的理由去更换。

  • BSD 系统,特别是 FreeBSD,通常比 Linux 拥有更高的性能。但这并非在所有情况下都成立。在许多情况下,性能差异很小或根本没有差别。在某些情况下,Linux 的性能可能优于 FreeBSD。

  • 一般来说,BSD 系统因其更加成熟的代码库而具有更好的可靠性声誉。

  • BSD 项目在文档的质量和完整性方面有更好的声誉。各种文档项目旨在提供更新的文档,涵盖多个语言,并涵盖系统的各个方面。

  • BSD 许可证可能比 GPL 更具吸引力。

  • BSD 可以执行大多数 Linux 二进制文件,而 Linux 无法执行 BSD 二进制文件。许多 BSD 实现还可以执行来自其他类 UNIX® 系统的二进制文件。因此,与 Linux 相比,BSD 可能提供了更容易从其他系统迁移的途径。

4.8. 谁提供 BSD 的支持、服务和培训?

字体与 FreeBSD

摘要

本文档包含了与 FreeBSD 和 syscons 驱动程序、X11、Ghostscript 和 Groff 配合使用的各种字体文件的描述。提供了示例,展示如何将 syscons 显示模式切换到 80x60 模式,以及如何在上述应用程序中使用 Type 1 字体。


1. 引言

有许多字体来源可供选择,可能会有人问这些字体如何在 FreeBSD 上使用。答案可以通过仔细查阅文档来找到,特别是针对希望使用的组件。这是一个非常耗时的过程,因此本文尝试为其他感兴趣的人提供捷径。

2. 基本术语

有许多不同的字体格式和相关的字体文件后缀,下面列出了一些将要讨论的字体格式:

.pfa, .pfb PostScript® Type 1 字体。.pfa 是 Ascii 格式,.pfb 是 Binary 格式。

.afm 与 Type 1 字体相关的字体度量信息。

.pfm 与 Type 1 字体相关的打印字体度量信息。

.ttf TrueType® 字体

.fot 指向 TrueType 字体的间接引用(不是实际的字体)

.fon, .fnt 位图屏幕字体

.fot 文件是 Windows® 用来作为指向实际 TrueType® 字体(.ttf)文件的符号链接。.fon 字体文件也由 Windows 使用。我不知道如何在 FreeBSD 上使用这种字体格式。

3. 我可以使用哪些字体格式?

哪种字体文件格式有用,取决于所使用的应用程序。FreeBSD 本身不使用任何字体。应用程序和/或驱动程序可能会使用字体文件。以下是应用程序/驱动程序与字体类型后缀的简要对照:

驱动程序 vt.hex

syscons.fnt

应用程序 Ghostscript.pfa, .pfb, .ttf

X11.pfa, .pfb

Groff.pfa, .afm

Povray.ttf

.fnt 后缀被广泛使用。我怀疑每当有人想为他们的应用程序创建一个特殊的字体文件时,他们往往会选择这个后缀。因此,很可能具有此后缀的文件并不全是相同的格式;特别是,FreeBSD 下 syscons 使用的 .fnt 文件可能与在 MS-DOS®/Windows® 环境中遇到的 .fnt 文件格式不同。我并没有尝试使用除 FreeBSD 提供的 .fnt 文件之外的其他 .fnt 文件。

4. 将虚拟控制台设置为 80x60 行模式

首先,必须加载一个 8x8 字体。为此,/etc/rc.conf 文件应包含以下行(根据你的语言环境更改字体名称):

font8x8="iso-8x8"		# 从 /usr/share/syscons/fonts/* 中获取 8x8 字体(或者 NO)。
% vidcontrol VGA_80x60

为了使这一过程更加顺畅,可以将这些命令嵌入到启动脚本中,以便系统启动时自动执行。为此,可以在 /etc/rc.conf 中添加以下行:

allscreens_flags="VGA_80x60"	# 为所有虚拟屏幕设置此 vidcontrol 模式

5. 在 X11 中使用 Type 1 字体

X11 可以使用 .pfa 或 .pfb 格式的字体。X11 字体位于 /usr/X11R6/lib/X11/fonts 目录下的多个子目录中。每个字体文件都通过各个目录中的 fonts.dir 文件与其 X11 名称进行交叉引用。

已经有一个名为 Type1 的目录。最直接的添加新字体的方式是将其放入此目录。更好的方法是将所有新字体保存在单独的目录中,并使用符号链接来引用额外的字体。这样可以更轻松地跟踪自己的字体,而不会与最初提供的字体混淆。例如:

创建一个目录来存放字体文件
% mkdir -p /usr/local/share/fonts/type1
% cd /usr/local/share/fonts/type1

将 .pfa、.pfb 和 .afm 文件放在这里

也可以想要在这里保留 readme 文件和其他文档

% cp /cdrom/fonts/atm/showboat/showboat.pfb .
% cp /cdrom/fonts/atm/showboat/showboat.afm .

维护一个索引以交叉引用字体
% echo showboat - InfoMagic CICA, Dec 1994, /fonts/atm/showboat >>INDEX

现在,要在 X11 中使用新字体,必须使字体文件可用并更新字体名称文件。X11 字体名称看起来像这样:

-bitstream-charter-medium-r-normal-xxx-0-0-0-0-p-0-iso8859-1
     |        |      |    |   |     |  | | | | | |    \    \
     |        |      |    |   |     \  \ \ \ \ \ \     +----+- 字符集
     |        |      |    |   \      \  \ \ \ \ \ +- 平均宽度
     |        |      |    |    \      \  \ \ \ \ +- 间距
     |        |      |    \	\      \  \ \ \ \ +- 垂直分辨率
     |        |      |     \	 \	\  \ \ \ +- 水平分辨率
     |        |      |      \	  \	 \  \ +- 点数
     |        |      |       \     \	  \  +- 像素
     |        |      |        \     \	   \
  字体厂商  字体家族  粗细   倾斜  宽度  额外风格
% strings showboat.pfb | more
%!FontType1-1.0: Showboat 001.001
%%CreationDate: 1/15/91 5:16:03 PM
%%VMusage: 1024 45747
% Generated by Fontographer 3.1
% Showboat
 1991 by David Rakowski.  Alle Rechte Vorbehalten.
FontDirectory/Showboat known{/Showboat findfont dup/UniqueID known{dup
/UniqueID get 4962377 eq exch/FontType get 1 eq and}{pop false}ifelse
{save true}{false}ifelse}{false}ifelse
12 dict begin
/FontInfo 9 dict dup begin
 /version (001.001) readonly def
 /FullName (Showboat) readonly def
 /FamilyName (Showboat) readonly def
 /Weight (Medium) readonly def
 /ItalicAngle 0 def
 /isFixedPitch false def
 /UnderlinePosition -106 def
 /UnderlineThickness 16 def
 /Notice (Showboat
 1991 by David Rakowski.  Alle Rechte Vorbehalten.) readonly def
end readonly def
/FontName /Showboat def
--stdin--

使用这些信息,一个可能的字体名称是:

-type1-Showboat-medium-r-normal-decorative-0-0-0-0-p-0-iso8859-1

我们名称的组成部分如下:

Foundry(字体厂商) 我们将所有新字体命名为 type1。

Family(字体家族) 字体的名称。

Slant(倾斜)roman、italic、oblique 等。由于 ItalicAngle 为零,使用 roman。

Width(宽度) 正常、宽、紧凑、扩展等。假设为 normal,直到字体能被检查为止。

Additional style(额外风格) 通常被省略,但这将指示字体包含装饰性的大写字母。

Spacing(间距) 比例间距或等宽。由于 isFixedPitch 为 false,使用 proportional。

所有这些名称都是任意的,但应尽量与现有的命名约定兼容。字体是通过名称和可能的通配符由 X11 程序引用的,因此所选名称应具有一定的合理性。可以从简单地使用

...-normal-r-normal-...-p-...

因此,完成我们的示例:

使字体对 X11 可访问
% cd /usr/X11R6/lib/X11/fonts/Type1
% ln -s /usr/local/share/fonts/type1/showboat.pfb .

编辑 fonts.dir 和 fonts.scale,添加描述字体的行,并在第一行中递增字体的数量。
% ex fonts.dir
:1p
25
:1c
26
.
:$a
showboat.pfb -type1-showboat-medium-r-normal-decorative-0-0-0-0-p-0-iso8859-1
.
:wq

fonts.scale 看起来与 fonts.dir 相同...
% cp fonts.dir fonts.scale

告诉 X11 发生了变化
% xset fp rehash

检查新字体
% xfontsel -pattern -type1-*

6. 使用 Type 1 字体与 Ghostscript

Ghostscript 通过其 Fontmap 引用字体。这个文件必须以类似于 X11 fonts.dir 的方式进行修改。Ghostscript 可以使用 .pfa 或 .pfb 格式的字体。使用之前示例中的字体,以下是如何将其与 Ghostscript 一起使用:

将字体放入 Ghostscript 的字体目录
% cd /usr/local/share/ghostscript/fonts
% ln -s /usr/local/share/fonts/type1/showboat.pfb .

编辑 Fontmap 以便 Ghostscript 知道字体
% cd /usr/local/share/ghostscript/4.01
% ex Fontmap
:$a
/Showboat        (showboat.pfb) ; % 来自 CICA /fonts/atm/showboat
.
:wq

使用 Ghostscript 检查字体
% gs prfont.ps
Aladdin Ghostscript 4.01 (1996-7-10)
Copyright (C) 1996 Aladdin Enterprises, Menlo Park, CA.  All rights
reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
从 /usr/local/share/ghostscript/fonts/tir_____.pfb 加载 Times-Roman 字体...
 /1899520 581354 1300084 13826 0 完成。
GS>Showboat DoFont
从 /usr/local/share/ghostscript/fonts/showboat.pfb 加载 Showboat 字体...
 1939688 565415 1300084 16901 0 完成。
>>showpage, 按 <return> 键继续<<
>>showpage, 按 <return> 键继续<<
>>showpage, 按 <return> 键继续<<
GS>quit

参考资料:Ghostscript 4.01 分发包中的 fonts.txt

7. 使用 Type 1 字体与 Groff

现在,我们可以在 X11 和 Ghostscript 中使用新字体,那么如何在 Groff 中使用这个新字体呢?首先,由于我们使用的是 Type 1 PostScript® 字体,适用于 Groff 的设备是 ps 设备。必须为每个字体创建一个 Groff 字体文件。Groff 字体名实际上就是 /usr/share/groff_font/devps 目录中的一个文件。对于我们的示例,字体文件可以是 /usr/share/groff_font/devps/SHOWBOAT。该文件必须使用 Groff 提供的工具创建。

第一个工具是 afmtodit。这个工具通常没有安装,因此必须从源代码分发版中获取。我发现需要修改文件的第一行,所以我做了如下操作:

% cp /usr/src/gnu/usr.bin/groff/afmtodit/afmtodit.pl /tmp
% ex /tmp/afmtodit.pl
:1c
#!/usr/bin/perl -P-
.
:wq

这个工具将根据字体度量文件(.afm 后缀)创建 Groff 字体文件。继续我们的示例:

许多 .afm 文件是 Mac 格式的,行由 ^M 分隔
我们需要将它们转换为 UNIX(R) 风格的 ^J 分隔行
% cd /tmp
% cat /usr/local/share/fonts/type1/showboat.afm |
	tr '\015' '\012' >showboat.afm

现在创建 Groff 字体文件
% cd /usr/share/groff_font/devps
% /tmp/afmtodit.pl -d DESC -e text.enc /tmp/showboat.afm generate/textmap SHOWBOAT

现在,字体可以通过名称 SHOWBOAT 引用。

如果系统使用 Ghostscript 驱动打印机,那么不需要做更多的操作。然而,如果使用真正的 PostScript® 打印机,则必须将字体下载到打印机中才能使用该字体(除非打印机内建 Showboat 字体或字体磁盘可访问)。最后一步是创建一个可下载的字体。使用 pfbtops 工具创建 .pfa 格式的字体,并修改 download 文件以引用新字体。download 文件必须引用字体的内部名称。可以通过查看 Groff 字体文件轻松确定这个名称,如下所示:

创建 .pfa 字体文件
% pfbtops /usr/local/share/fonts/type1/showboat.pfb >showboat.pfa

当然,如果 .pfa 已经存在,只需使用符号链接引用它。

获取内部字体名称
% fgrep internalname SHOWBOAT
internalname Showboat
告诉 Groff 字体需要被下载
% ex download
:$a
Showboat      showboat.pfa
.
:wq

测试字体:

% cd /tmp
% cat >example.t <<EOF
.sp 5
.ps 16
这是 Showboat 字体的示例:
.br
.ps 48
.vs (\n(.s+2)p
.sp
.ft SHOWBOAT
ABCDEFGHI
.br
JKLMNOPQR
.br
STUVWXYZ
.sp
.ps 16
.vs (\n(.s+2)p
.fp 5 SHOWBOAT
.ft R
要使用它作为段落的第一个字母,它会显示如下:
.sp 50p
\s(48\f5H\s0\fRere 是段落的第一个字母,使用 Showboat 字体。
需要额外的垂直空间来为较大的字母留出空间。
EOF
% groff -Tps example.t >example.ps

使用 Ghostscript/Ghostview
% ghostview example.ps

打印
% lpr -Ppostscript example.ps

8. 将 TrueType 字体转换为适用于 Groff/PostScript 格式的字体

这可能需要一些工作,因为它依赖于一些并没有作为基础系统一部分安装的工具。它们包括:

ttf2pf:TrueType 到 PostScript 转换工具。它允许将 TrueType 字体转换为 ASCII 字体度量文件(.afm)。

感兴趣的文件包括:

  • GS_TTF.PS

  • PF2AFM.PS

  • ttf2pf.ps 这些文件的奇怪大小写是因为它们也旨在支持 DOS shell。因此,任何重命名必须与此一致。(实际上,GS_TTF.PS 和 PFS2AFM.PS 应该是 Ghostscript 分发版的一部分,但使用这些作为独立工具同样有效。FreeBSD 似乎没有包含后者。)你也可能希望将它们安装到 /usr/local/share/groff_font/devps(?)目录下。

afmtodit:从 ASCII 字体度量文件创建 Groff 字体文件。通常该工具位于目录 /usr/src/contrib/groff/afmtodit 中,且需要一些工作才能启动。

注意

如果你不想在 /usr/src 目录下工作,只需将上述目录的内容复制到工作位置即可。

在工作目录中,需要构建该工具。只需输入以下命令:

# make -f Makefile.sub afmtodit

你可能还需要将 /usr/contrib/groff/devps/generate/textmap 复制到 /usr/share/groff_font/devps/generate,如果该文件尚不存在。

这些工具准备好后,你就可以开始操作:

  1. 创建 .afm 文件,输入以下命令:

    % gs -dNODISPLAY -q -- ttf2pf.ps TTF_name PS_font_name AFM_name

    其中,TTF_name 是你的 TrueType 字体文件,PS_font_name 是 .pfa 的文件名,AFM_name 是你希望为 .afm 文件指定的名称。如果你没有为 .pfa 或 .afm 文件指定输出文件名,那么将使用 TrueType 字体文件名生成默认名称。

    这也会生成一个 .pfa 文件,即 ASCII PostScript 字体度量文件(.pfb 是二进制形式)。虽然这个文件不一定需要,但它(我认为)对于字体服务器可能会有用。

    例如,若要将 30f9 Barcode 字体转换为默认文件名,可以使用以下命令:

    % gs -dNODISPLAY -- ttf2pf.ps 3of9.ttf
    Aladdin Ghostscript 5.10 (1997-11-23)
    Copyright (C) 1997 Aladdin Enterprises, Menlo Park, CA.  All rights reserved.
    This software comes with NO WARRANTY: see the file PUBLIC for details.
    Converting 3of9.ttf to 3of9.pfa and 3of9.afm.

    如果你希望将转换后的字体存储为 A.pfa 和 B.afm,可以使用以下命令:

    % gs -dNODISPLAY -- ttf2pf.ps 3of9.ttf A B
    Aladdin Ghostscript 5.10 (1997-11-23)
    Copyright (C) 1997 Aladdin Enterprises, Menlo Park, CA.  All rights reserved.
    This software comes with NO WARRANTY: see the file PUBLIC for details.
    Converting 3of9.ttf to A.pfa and B.afm.
  2. 创建 Groff PostScript 文件: 切换到 /usr/share/groff_font/devps 目录以便执行以下命令。你可能需要 root 权限来执行这些命令。(如果你不想在该目录中工作,确保引用 DESC、text.enc 和 generate/textmap 文件,并在此目录中找到它们。)

    % afmtodit -d DESC -e text.enc file.afm generate/textmap PS_font_name
    % afmtodit -d DESC -e text.enc 3of9.afm generate/textmap 3of9

    确保将生成的 PS_font_name 文件(例如上述示例中的 3of9)放置在 /usr/share/groff_font/devps 目录中,可以通过复制或移动来完成。

9. TrueType 字体能否与其他程序一起使用?

TrueType 字体格式被 Windows、Windows 95 和 Mac 操作系统使用,十分流行,目前有大量的字体可供选择。

遗憾的是,目前我所知道的能够使用这种格式的应用程序不多:Ghostscript 和 Povray 就是其中之一。根据文档,Ghostscript 对 TrueType 字体的支持比较初步,效果可能不如 Type 1 字体。Povray 版本 3 也能使用 TrueType 字体,但我怀疑很少有人会创建一系列的光线追踪页面来生成文档 :-)。

  • 以及其他工具...

10. 从哪里可以获得其他的字体?

许多字体可以在互联网上找到。这些字体要么是完全免费的,要么是共享软件。此外,许多字体也可以在 Ports 中的 x11-fonts/ 目录中找到。

11. 其他问题

  • .pfm 文件有什么用途?

  • 是否可以从 .pfa 或 .pfb 文件生成 .afm 文件?

  • 如何为具有非标准字符名称的 PostScript 字体生成 groff 字符映射文件?

  • 是否可以设置 xditview 和 devX?? 设备来访问所有新的字体?

  • 为 Povray 和 Ghostscript 提供使用 TrueType 字体的示例会很有用。

FreeBSD 和固态硬盘

摘要

本文讨论了在 FreeBSD 上使用固态磁盘设备来创建嵌入式系统。

嵌入式系统由于缺少固有的活动部件(如硬盘),因此具有更高的稳定性。然而,需要考虑的是,系统中的磁盘空间通常较小,并且存储介质的耐用性问题。

本文将涵盖以下具体内容:适合在 FreeBSD 中使用的固态存储介质的类型和属性,相关内核选项,自动初始化这些系统的 rc.initdiskless 机制,文件系统只读需求,以及从零开始构建文件系统。最后,文章将总结一些适用于小型和只读 FreeBSD 环境的通用策略。

1. 固态磁盘设备

本文的范围将仅限于基于闪存的固态磁盘设备。闪存是一种固态存储介质(没有活动部件),并且具有非易失性(即使断电,数据仍能保持)。闪存可以承受巨大的物理冲击,且读写速度相对较快(本文讨论的闪存解决方案在写操作上略慢于 EIDE 硬盘,但在读取操作上要快得多)。闪存的一个非常重要的特性,是每个扇区具有有限的重写次数。每个扇区只能进行有限次数的写入、擦除和重写,超过这个次数后,扇区将变得无法使用。尽管许多闪存产品会自动映射坏块,并且一些产品甚至会将写操作均匀分布到整个设备上,但无法忽视设备的写入次数限制。竞争性产品的规格通常为每个扇区支持 1,000,000 到 10,000,000 次写入,这个数字会受到环境温度的影响。

本文特别讨论的对象是 ATA 兼容的紧凑型闪存单元,这些设备广泛用于数字相机存储。其特别之处在于它们直接与 IDE 总线连接,并且兼容 ATA 命令集。因此,使用非常简单且低成本的适配器,这些设备可以直接连接到计算机的 IDE 总线。这样连接后,FreeBSD 等操作系统会将该设备视作一个正常的硬盘(虽然容量较小)。

其他类型的固态磁盘解决方案虽然存在,但由于它们的高昂价格、相对不常见以及使用难度,超出了本文的讨论范围。

2. 内核选项

对于那些创建嵌入式 FreeBSD 系统的人来说,有一些特定的内核选项值得关注。

所有使用闪存作为系统磁盘的嵌入式 FreeBSD 系统都应关注内存磁盘和内存文件系统。由于闪存的写入次数有限,磁盘和文件系统通常会被挂载为只读。在这种环境中,/tmp 和 /var 等文件系统通常会挂载为内存文件系统,以便系统能够创建日志、更新计数器和临时文件。内存文件系统是成功实现固态 FreeBSD 系统的关键组件。

在内核配置文件中,你应确保以下行存在:

options         MD_ROOT         # md 设备可作为潜在的根设备

3. rc 子系统与只读文件系统

嵌入式 FreeBSD 系统的启动后初始化是由 /etc/rc.initdiskless 控制的。

varsize=8192

请记住,该值默认为以扇区为单位。

# 设备                挂载点      文件系统类型  选项         Dump    Pass#
/dev/ad0s1a             /               ufs     ro              1       1

需要记住的是,使用 /etc/fstab 挂载的只读文件系统,可以随时通过以下命令切换为读写模式:

# /sbin/mount -uw partition

如果需要切换回只读模式,可以使用以下命令:

# /sbin/mount -ur partition

4. 从零开始构建文件系统

由于 ATA 兼容的紧凑型闪存卡被 FreeBSD 视为普通的 IDE 硬盘,因此理论上,你可以通过网络使用 kern 和 mfsroot 启动盘安装 FreeBSD,或使用 CD 安装。

然而,即使是通过常规安装程序进行的小型 FreeBSD 安装,也会生成超过 200MB 的系统大小。大多数人会使用较小的闪存设备(128MB 被认为相对较大,32MB 甚至 16MB 都是常见的),因此使用常规机制进行安装是不可行的——因为空间根本不足以容纳最小的传统安装。

克服这一空间限制的最简单方法是,通过常规方式将 FreeBSD 安装到普通硬盘上。在安装完成后,将操作系统精简至适合闪存媒体的大小,然后将整个文件系统打包成 tar 文件。以下步骤将指导你如何为 tar 打包的文件系统准备闪存。请记住,因为没有执行常规安装,所以需要手动进行分区、标签和文件系统创建等操作。除了 kern 和 mfsroot 启动盘外,你还需要使用 fixit 启动盘。

1. 为闪存设备分区

在使用 kern 和 mfsroot 启动盘启动后,从安装菜单中选择 custom。在自定义安装菜单中,选择 partition。在分区菜单中,使用 d 删除所有现有分区。删除所有现有分区后,使用 c 创建一个新分区,并接受分区大小的默认值。当询问分区类型时,请确保将其设置为 165。然后通过按 w 将分区表写入磁盘(这是该屏幕上的隐藏选项)。如果你使用的是 ATA 兼容的紧凑型闪存卡,请选择 FreeBSD 启动管理器。然后按 q 退出分区菜单。启动管理器菜单将再次出现——重复之前的选择。

2. 在闪存设备上创建文件系统

退出自定义安装菜单后,从主安装菜单选择 fixit 选项。进入 fixit 环境后,输入以下命令:

# disklabel -e /dev/ad0c

此时,你将进入 vi 编辑器,进行 disklabel 命令的编辑。接下来,你需要在文件的末尾添加一个 a: 行。这个 a: 行应该类似于以下内容:

a:      123456  0       4.2BSD  0       0

其中,123456 是与现有 c: 项的大小相同的数字。基本上,你是将现有的 c: 行复制为 a: 行,确保文件系统类型为 4.2BSD。保存文件并退出。

# disklabel -B -r /dev/ad0c
# newfs /dev/ad0a

3. 将文件系统放到闪存上

挂载刚刚准备好的闪存设备:

# mount /dev/ad0a /flash

将这台机器接入网络,以便我们可以传输 tar 文件并将其解压到闪存文件系统中。以下是一个示例:

# ifconfig xl0 192.168.0.10 netmask 255.255.255.0
# route add default 192.168.0.1

现在机器已经接入网络,传输你的 tar 文件。在这个过程中,你可能会遇到一些问题——例如,如果你的闪存介质为 128MB,并且 tar 文件大于 64MB,你就不能同时将 tar 文件和解压后的内容放在闪存中——这时你会遇到空间不足的问题。如果你使用 FTP 进行传输,解决方案之一是,在传输过程中直接解压 tar 文件。如果你以这种方式进行传输,tar 文件和解压后的内容就不会同时存在于磁盘上:

ftp> get tarfile.tar "| tar xvf -"

如果你的 tar 文件是 gzipped 的,你可以这样做:

ftp> get tarfile.tar "| zcat | tar xvf -"

在 tar 文件的内容被解压到闪存文件系统后,你可以卸载闪存并重启:

# cd /
# umount /flash
# exit

假设你在正常硬盘上构建文件系统时正确配置了文件系统(包括将文件系统挂载为只读,并将必要的选项编译到内核中),你现在应该能够成功启动你的 FreeBSD 嵌入式系统。

5. 小型和只读环境的系统策略

5.1. Cron

在启动时,/var 会通过 /etc/rc.d/var 根据 /etc/mtree/BSD.var.dist 中的列表进行填充,因此 cron、cron/tabs、at 以及一些其他标准目录将被创建。

然而,这并不能解决跨重启维护 cron 表的问题。当系统重启时,内存中的 /var 文件系统将消失,你可能在其中的 cron 表也会消失。因此,一个解决方案是为需要的用户创建 cron 表,将 / 文件系统挂载为读写,并将这些 cron 表复制到一个安全的位置,例如 /etc/tabs,然后在 /etc/rc.initdiskless 的末尾添加一行,将这些 cron 表在系统初始化期间创建 /var/cron/tabs 目录后复制进去。你可能还需要添加一行,使用 /etc/rc.initdiskless 来更改你创建的目录和复制的文件的模式和权限。

5.2. Syslog

syslog.conf 指定了存在于 /var/log 中的某些日志文件的位置。这些文件在系统初始化时不会由 /etc/rc.d/var 创建。因此,你需要在 /etc/rc.d/var 中的某个位置,在创建 /var 中的目录后,添加如下内容:

# touch /var/log/security /var/log/maillog /var/log/cron /var/log/messages
# chmod 0644 /var/log/*

5.3. 安装 Port

为了能够进入 Port 目录并成功运行make install,我们必须在一个非内存文件系统上创建一个包数据库目录,该目录将跨重启跟踪我们的包。由于安装包时需要将文件系统挂载为读写,因此可以合理地假设,闪存媒体上的某个区域也可以用来写入包信息。

首先,创建一个包数据库目录。通常这是在 /var/db/pkg 中,但由于它会在每次系统启动时消失,因此我们不能将其放在那里。

# mkdir /etc/pkg

接下来,在 /etc/rc.d/var 中添加一行,将 /etc/pkg 目录链接到 /var/db/pkg。示例如下:

# ln -s /etc/pkg /var/db/pkg

现在,每次你将文件系统挂载为读写并安装包时,make install将正常工作,包信息将成功写入 /etc/pkg(因为在此时,文件系统将挂载为读写),并将始终以 /var/db/pkg 的形式可供操作系统访问。

5.4. Apache 网络服务器

注意

本节中的步骤仅在 Apache 设置为将其 pid 或日志信息写入 /var 之外时需要。默认情况下,Apache 将其 pid 文件保存在 /var/run/httpd.pid 中,并将其日志文件保存在 /var/log 中。

现在假设 Apache 将其日志文件保存在 apache_log_dir 目录中,该目录位于 /var 之外。当此目录位于只读文件系统上时,Apache 将无法保存任何日志文件,并可能出现无法正常工作的情况。如果是这样,就需要在 /etc/rc.d/var 中将 /var 中的目录创建列表中添加一个新目录,并将 apache_log_dir 链接到 /var/log/apache。还需要对这个新目录设置权限和所有权。

首先,将 log/apache 目录添加到 /etc/rc.d/var 中要创建的目录列表中。

其次,在 /etc/rc.d/var 中的目录创建部分后添加以下命令:

# chmod 0774 /var/log/apache
# chown nobody:nobody /var/log/apache

最后,删除现有的 apache_log_dir 目录,并将其替换为链接:

# rm -rf apache_log_dir
# ln -s /var/log/apache apache_log_dir

FreeBSD 许可政策

1. 新文件的首选许可证

本节的其余部分旨在帮助你入门。作为规则,当不确定时,请咨询别人。获得建议比修复源代码树要容易得多。FreeBSD 项目使用显式许可证(其中许可证的原文被复制作在每个文件中)和分离许可证(其中文件中的标签指定许可证,如本文所述)。

FreeBSD 项目使用以下文本作为首选许可证:

/*
 * Copyright (c) [年份] [你的名字]
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

FreeBSD 项目不允许在新代码中使用“广告条款”(译者注:即必须声明该软件的开发者或版权所有者)。由于大量的贡献者,许多商业供应商已经发现遵守这一条款变得困难。如果你在树中有包含广告条款的代码,请考虑切换到没有该条款的许可证。对 FreeBSD 的新贡献应使用 BSD-2-Clause 许可证。

此外,项目政策要求根据某些非 BSD 许可证授权的代码必须放置在存储库的特定部分。对于某些许可证,编译必须是有条件的,或者默认禁用。例如,GENERIC 内核中的静态部分的代码必须根据 BSD 或实质上相似的许可证授权。使用 GPL、APSL、CDDL 等许可证的软件不能编译进静态 GENERIC 内核中。然而,具有这些许可证的代码可以用于预编译的模块中。

2. 软件许可证政策

2.1. 指导原则

FreeBSD 项目旨在生产一款完整的、BSD 许可的操作系统,让系统的消费者在没有约束或进一步许可证义务的情况下,生产衍生产品。我们邀请并非常感激在双条 BSD 许可证下的变更和新增贡献,并建议其他开源项目采用这一许可证。使用 BSD 许可证对于推动先进操作系统技术的采用至关重要,且在许多值得注意的场合中,对新技术的广泛应用起到了关键作用。

然而,我们也承认,确实存在合理的理由让不同许可证授权的软件包含在 FreeBSD 源树中。

我们要求根据某些非 BSD 许可证授权的软件在源树中小心隔离,以便其不会污染仅限 BSD 的组件。这样的谨慎管理有助于许可证的清晰性,并促进生产仅限 BSD 的衍生产品。

除非有特别例外,否则不能用更为限制性的许可证软件替换现有的 BSD 许可组件。我们建议 FreeBSD 和第三方开发者寻求对关键组件进行重新许可、双重许可或使用 BSD 许可证重新实现的方式。这样有助于这些组件更为顺利地集成到 FreeBSD 操作系统中。

2.2. 政策

  • 导入任何非 BSD 许可证和 BSD 类似许可证(如下所定义)授权的新软件,必须事先获得 FreeBSD 核心团队的批准。导入请求必须包含:

    • 新版本或补丁所包含的功能或修复列表,并提供证据表明我们的用户需要这些功能。理想的证据形式是 PR 或邮件列表讨论的引用。

    • 这个过程应该适用于所有软件导入,而不仅仅是那些需要核心团队审查的软件。仅仅因为有新版本的存在,并不能证明就应导入该软件到源代码或 Ports。

    • 可能受到影响的 FreeBSD 分支列表。如果范围扩大,则需要重新提交请求并获得 FreeBSD 核心团队的批准。

  • 在某些情况下,Apache 许可证 2.0 是可以接受的。核心团队必须批准新组件的 Apache 许可证导入,或者现有组件更改为 Apache 许可证。

    • 已批准以下组件使用该许可证:

      • LLVM 工具链和(带有 LLVM 异常)运行时组件。

  • BSD+Patent 许可证在某些情况下是可以接受的。核心团队必须批准新组件的 BSD+Patent 许可证导入,或者现有组件更改为 BSD+Patent 许可证。

    • 已批准以下组件使用该许可证:

      • 与 UEFI 功能相关的 EDK2 衍生代码

  • 在某些情况下,通用开发和分发许可证(CDDL)是可以接受的。核心团队必须批准新组件的 CDDL 许可证导入,或者现有组件更改为 CDDL 许可证。

    • 已批准以下组件使用该许可证:

      • DTrace

      • ZFS 文件系统,包括内核支持和用户空间工具

  • 最初,许多 FreeBSD 树中的条目被标记为 BSD-2-Clause-FreeBSD。然而,SPDX 已将该许可证作为一个变种废弃;且该废弃标签的 SPDX 文本与标准的 FreeBSD 许可证有相当的差异,因此不应使用。其目前的使用情况正在审查中。

2.2.1. 可接受的许可证

以下许可证被认为是符合本政策的 BSD 类似许可证。任何偏差或使用其他许可证必须获得 FreeBSD 核心团队的批准:

  • BSD 许可证的 2 条款版本

/*
 * Copyright (c) [年份] [your name]
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */
  • BSD 许可证的 3 条款版本

/*
 * Copyright (c) [年份] [你的名字]
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
  • ISC 许可证

/*
 * Copyright (c) [年份] [版权持有者]
 *
 * SPDX-License-Identifier: ISC
 */
  • MIT 许可证

/*
 * Copyright (c) [年份] [版权持有者]
 *
 * SPDX-License-Identifier: MIT
 */

3. Ports 许可证

FreeBSD 项目将其软件汇编授权为 COPYRIGHT 下的 BSD-2-Clause 许可证。此许可证不会取代各个文件的许可证,如下所述。没有明确许可证的文件将使用 BSD-2-Clause 许可证进行授权。

4. 许可证文件位置

FreeBSD 项目目前没有使用 REUSE Software 标准中陈述的 DEP5 文件。FreeBSD 项目尚未根据该标准标记树中的所有文件,如本文后面所述。FreeBSD 项目尚未将这些文件包含在其仓库中,因为此政策仍在发展中。

5. 个别文件许可证

FreeBSD Ports 中的每个文件都有其自己的版权和许可证。它们的标记方式各不相同,本节将进行说明。

版权声明标识了谁拥有该文件的法律版权。项目尽力提供这些版权声明。由于版权可以依法转让,因此当前的版权持有者可能与文件中列出的不同。

对于没有明确许可证的修复和增强贡献,贡献者同意按照修改文件所适用的条款对这些更改进行授权。与行业惯例一致,项目政策仅包含对集合中文件的重大贡献者的版权声明。

FreeBSD Ports 中的文件可以分为四种类型:

  1. 仅具有明确版权声明和许可证的文件。

  2. 同时具有明确版权声明和许可证,以及 SPDX-License-Identifier 标记的文件。

  3. 仅具有版权声明和 SPDX-License-Identifier 标记,但没有明确许可证的文件。

  4. 完全没有版权声明或许可证的文件。

5.1. 仅版权和许可证

FreeBSD Ports 中的许多文件同时包含版权声明和明确的许可证。在这些情况下,文件中包含的许可证具有优先权。

5.2. 带有 SPDX-License-Identifier 表达式的版权和许可证

5.3. 仅有版权声明和 SPDX-License-Identifier 表达式

仅包含 SPDX-License-Identifier 的文件的许可证应理解为:

  1. 以文件中的版权声明开始许可证。包括所有版权持有者。

  2. 对于每个子表达式,从 LICENSE/text/id.txt 复制许可证文本。如果有例外情况,请从 src/share/license/exceptions/id.txt 追加它们。SPDX-License-Identifier 表达式应按 SPDX 标准中的描述进行理解。

在阅读与文件分离的许可证文本时,必须考虑以下几点,以确保分离的许可证能够成立:

  1. 任何提到版权声明的地方,应当引用构建自已授权文件的版权声明,而不是许可证文本文件本身中的任何版权声明。许多 SPDX 文件具有示例版权声明,应该理解为仅作为示例。

  2. 当许可证文本中提到实体名称时,应理解为适用于已授权文件中的所有版权持有者。例如,BSD-4-Clause 许可证包含短语 "This product includes software developed by the organization"。此处的 'the organization' 应替换为版权持有者。

  3. 当 SPDX 提供许可证的变体时,应理解 LICENSE/ 文件中的许可证表示所选的许可证的确切版本。SPDX 标准旨在匹配许可证族,这些变体有助于匹配 SPDX 组织认为在法律上等同的相似许可证。

对于文本略有变化的许可证,SPDX 提供了匹配的指南。此处的指南不相关。希望使用 LICENSE/ 中未明确列出的 SPDX 许可证变体的贡献者,不能使用分离选项,必须明确指定许可证。

5.4. 没有版权声明或任何许可证标记的文件

某些文件无法添加适当的注释。在这种情况下,可以在 file.ext.license 中找到许可证。例如,名为 foo.jpg 的文件可能会在 foo.jpg.license 中找到许可证,遵循 REUSE 软件规范。

由项目创建的缺少版权声明的文件,理解为属于 COPYRIGHT 中的通用版权和许可。该文件可能仅仅是事实的陈述,无法通过版权法保护,或者内容如此微不足道,以至于不需要明确的许可证。

缺少标记且包含非微不足道版权材料的文件,或者其作者认为标记不当的文件,应引起 FreeBSD 核心团队的注意。FreeBSD 项目的强烈政策是遵守所有适当的许可证。

未来,所有此类文件将被明确标记,或遵循 REUSE 软件的 .license 约定。

5.5. SPDX-License-Identifier 表达式

SPDX 许可证表达式 在 FreeBSD Ports 中有两个使用场景。首先,它的完整形式用于那些文件,这些文件包含明确的许可证声明以及总结性 SPDX-License-Identifier 表达式。在这种情况下,可以使用这些表达式的全部功能。其次,在上述的限制形式中,它用于标示给定文件的实际许可证。在第二种情形下,项目只允许使用此表达式的一个子集。

一些许可证标识符,如 [L]GPL,具有使用该版本或任何更高版本的选项。SPDX 定义了后缀 -or-later,表示该版本的许可证或更高版本。它还定义了 -only,表示仅该特定版本的许可证。过去有一种旧的约定,不使用后缀(这意味着新定义的 -only 后缀所表示的意思,但人们常常将其误解为 -or-later)。此外,添加一个 + 后缀意味着 -or-later。FreeBSD 中的新文件不应使用这两种约定。使用这些约定的旧文件应根据需要进行转换。

// SPDX-License-Identifier: GPL-2.0-only
      // SPDX-License-Identifier: LGPL-2.1-or-later

当需要许可证修饰符时,应使用 WITH。在 FreeBSD 项目中,一些来自 LLVM 的文件对 Apache 2.0 许可证有一个例外:

// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

如果文件有选择许可证并选择了其中一个许可证,则应使用 OR。例如,一些 dtsi 文件可以通过双重许可证获得:

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause

如果文件有多个许可证,且所有许可证条款都适用于使用该文件,则应使用 AND。例如,如果代码被多个项目合并,每个项目都有自己的许可证:

// SPDX-License-Identifier: BSD-2-Clause AND MIT

桥接过滤器

摘要

通常,将一个物理网络(如以太网)划分为两个独立的网络段而不需要创建子网,并使用路由器将它们连接起来是很有用的。以这种方式连接两个网络的设备称为桥接器。具有两个网络接口的 FreeBSD 系统就足够充当桥接器。

桥接器通过扫描连接到其每个网络接口的设备的 MAC 层地址(以太网地址)来工作,然后只有当源地址和目标地址位于不同的网络段时,才会转发流量。从许多角度来看,桥接器类似于只有两个端口的以太网交换机。


1. 为什么使用过滤桥接器?

由于宽带互联网连接(xDSL)成本的降低以及 IPv4 地址的减少,越来越多的公司实现了 24 小时全天候连接到互联网,并且只有少量(有时甚至不是 2 的幂)IP 地址。在这些情况下,通常希望有一个防火墙来过滤进出互联网的流量,但基于路由器的包过滤解决方案可能不适用,可能是因为子网问题、路由器是由连接提供商(ISP)拥有,或者路由器不支持此类功能。在这些场景中,强烈建议使用过滤桥接器。

基于桥接器的防火墙可以配置并插入到 xDSL 路由器和以太网集线器/交换机之间,而无需任何 IP 编号问题。

2. 如何安装

为 FreeBSD 系统添加桥接功能并不困难。从 4.5 版本开始,可以将这些功能作为模块加载,而无需重新编译内核,从而大大简化了操作。接下来的小节中,我将介绍两种安装方式。

重要

不要同时遵循这两种指令:一个过程 排除 另一个过程。根据你的需求和能力选择最佳选项。

在继续之前,请确保至少拥有两块支持混杂模式(既支持接收又支持传输)的以太网卡,因为它们必须能够发送任何地址的以太网数据包,而不仅仅是它们自己的地址。此外,为了获得良好的吞吐量,卡片应该是 PCI 总线主控卡。最好的选择仍然是 Intel EtherExpress™ Pro,其次是 3Com® 3c9xx 系列。为了简化防火墙配置,使用不同厂商的两张卡(使用不同的驱动程序)可能是有用的,这样可以清楚地区分连接到路由器的接口和连接到内网的接口。

2.1. 内核配置

所以你决定使用较旧但经过良好测试的安装方法。首先,你需要将以下几行添加到内核配置文件中:

options BRIDGE
options IPFIREWALL
options IPFIREWALL_VERBOSE

第一行用于编译桥接支持,第二行是防火墙,第三行是防火墙的日志记录功能。

2.2. 模块加载

如果你选择使用新且更简单的安装方法,现在要做的就是在 /boot/loader.conf 中添加以下行:

bridge_load="YES"

通过这种方式,在系统启动时,bridge.ko 模块将与内核一起加载。无需为 ipfw.ko 模块添加类似的行,因为它将在执行以下步骤后自动加载。

3. 最终准备

在重新启动以加载新内核或所需的模块(根据先前选择的安装方法)之前,你必须对 /etc/rc.conf 配置文件进行一些更改。防火墙的默认规则是拒绝所有 IP 包。最初,我们将设置一个“开放”防火墙,以验证其操作,而不会遇到与包过滤相关的任何问题(如果你打算远程执行此过程,这种配置将避免你被隔离在网络之外)。将以下行添加到 /etc/rc.conf 文件中:

firewall_enable="YES"
firewall_type="open"
firewall_quiet="YES"
firewall_logging="YES"

第一行将启用防火墙(并在内核中没有编译时加载 ipfw.ko 模块),第二行将其设置为 open 模式(如 /etc/rc.firewall 中所解释),第三行用于不显示规则加载信息,第四行启用日志记录支持。

关于网络接口的配置,最常用的方法是仅为其中一张网卡分配 IP 地址,但即使两张网卡或没有网卡配置 IP,桥接器仍然可以正常工作。在没有 IP 的情况下,桥接主机将更加隐蔽,因为它无法从网络访问:要配置它,你需要通过控制台或通过一个与桥接器分开的第三个网络接口登录。有时,在系统启动期间,某些程序需要网络访问,例如用于域名解析:在这种情况下,必须为外部接口(连接到 Internet 的接口,DNS 服务器所在的接口)分配一个 IP 地址,因为桥接器将在启动程序的最后激活。这意味着必须在 /etc/rc.conf 文件的 ifconfig 部分中提到 fxp0 接口(在我们的示例中),而 xl0 接口则不需要提到。为两张网卡分配 IP 地址并没有太大意义,除非在启动过程中,某些应用程序需要访问两个以太网段上的服务。

还有一点需要了解。在以太网上运行 IP 时,实际上有两个以太网协议在使用:一个是 IP,另一个是 ARP。ARP 用于将主机的 IP 地址转换为其以太网地址(MAC 层)。为了使通过桥接器隔开的两台主机之间能够通信,必须确保桥接器能够转发 ARP 数据包。该协议不包含在 IP 层中,因为它仅在以太网上运行的 IP 协议中存在。FreeBSD 防火墙仅在 IP 层上进行过滤,因此所有非 IP 包(包括 ARP)将被转发,而不会被过滤,即使防火墙配置为不允许任何内容。

现在是时候重新启动系统并像之前一样使用它了:会有一些关于桥接器和防火墙的新消息,但桥接器将不会被激活,防火墙处于 open 模式时,不会阻止任何操作。

如果有任何问题,你应该在继续之前解决它们。

4. 启用桥接器

此时,为了启用桥接器,你需要执行以下命令(务必替换 fxp0 和 xl0 这两个网络接口名称为你自己使用的接口):

# sysctl net.link.ether.bridge.config=fxp0:0,xl0:0
# sysctl net.link.ether.bridge.ipfw=1
# sysctl net.link.ether.bridge.enable=1

第一行指定了应该由桥接器启用的接口,第二行将在桥接器上启用防火墙,最后一行将启用桥接器。

此时,你应该能够将这台计算机插入到两个主机组之间,而不会妥协它们之间的任何通信能力。如果成功,下一步是将这些行的 net.link.ether.bridge.[blah]=[blah] 部分添加到 /etc/sysctl.conf 文件中,以便在启动时自动执行。

5. 配置防火墙

现在是时候创建你自己的文件,并编写自定义的防火墙规则来保护内部网络了。由于并非所有防火墙功能都可以应用于桥接的数据包,因此配置可能会有些复杂。此外,正在转发的数据包与被本地计算机接收的数据包在处理方式上有所不同。通常,传入的数据包只会经过一次防火墙处理,而不是像平常那样经过两次;实际上,它们仅在接收到时被过滤,因此使用 out 或 xmit 的规则将永远无法匹配。个人而言,我使用的是 in via 语法,尽管这是较旧的语法,但理解起来是有意义的。另一个限制是,你只能使用 pass 或 drop 命令来过滤桥接的数据包。像 divert、forward 或 reject 这样的复杂命令无法使用。虽然这些选项仍然可以使用,但仅限于桥接器本机(如果它有 IP 地址)上下行的流量。

FreeBSD 4.0 中引入了状态过滤的概念,这是对 UDP 流量的一大改进。UDP 流量通常是一个请求,随后立即会返回一个响应,具有相同的 IP 地址和端口号(当然,源和目的地是反向的)。对于没有状态跟踪的防火墙,几乎无法处理这种类型的流量作为一个单独的会话。但对于可以“记住”出去的 UDP 数据包,并在接下来的几分钟内允许响应的防火墙来说,处理 UDP 服务就变得非常简单。下面的示例展示了如何操作。对 TCP 数据包也可以做同样的事情。这使你能够避免一些拒绝服务攻击和其他恶意操作,但通常会导致你的状态表迅速增长。

让我们看一个示例配置。首先注意,在 /etc/rc.firewall 的顶部,已经有针对环回接口 lo0 的标准规则,因此我们不需要再关心它们。自定义规则应放在单独的文件中(例如 /etc/rc.firewall.local),并通过修改 /etc/rc.conf 文件中定义 open 防火墙的行,在系统启动时加载:

firewall_type="/etc/rc.firewall.local"

重要

必须指定 完整 路径,否则它将无法加载,可能导致你无法连接到网络。

假设我们的例子中,fxp0 接口连接到外部(Internet),xl0 接口连接到内部(LAN)。桥接器的 IP 地址是 1.2.3.4(虽然你的 ISP 不太可能给你这样一个地址,但在本示例中它是有效的)。

# 以前我们已经保存状态的事项,赶紧通过
add check-state

# 丢弃 RFC 1918 网络
add drop all from 10.0.0.0/8 to any in via fxp0
add drop all from 172.16.0.0/12 to any in via fxp0
add drop all from 192.168.0.0/16 to any in via fxp0

# 允许桥接机器发送任何内容
# (如果机器没有 IP,请不要包含这些行)
add pass tcp from 1.2.3.4 to any setup keep-state
add pass udp from 1.2.3.4 to any keep-state
add pass ip from 1.2.3.4 to any

# 允许内部主机发送任何内容
add pass tcp from any to any in via xl0 setup keep-state
add pass udp from any to any in via xl0 keep-state
add pass ip from any to any in via xl0

# TCP 部分
# 允许 SSH
add pass tcp from any to any 22 in via fxp0 setup keep-state
# 仅允许邮件服务器接收 SMTP
add pass tcp from any to relay 25 in via fxp0 setup keep-state
# 仅允许由次级名称服务器 [dns2.nic.it] 执行区域传输
add pass tcp from 193.205.245.8 to ns 53 in via fxp0 setup keep-state
# 通过 ident 探针。它比等待它们超时更好
add pass tcp from any to any 113 in via fxp0 setup keep-state
# 通过“隔离”范围
add pass tcp from any to any 49152-65535 in via fxp0 setup keep-state

# UDP 部分
# 仅允许 DNS 向名称服务器发送请求
add pass udp from any to ns 53 in via fxp0 keep-state
# 通过“隔离”范围
add pass udp from any to any 49152-65535 in via fxp0 keep-state

# ICMP 部分
# 通过‘ping’
add pass icmp from any to any icmptypes 8 keep-state
# 通过由‘traceroute’生成的错误信息
add pass icmp from any to any icmptypes 3
add pass icmp from any to any icmptypes 11

# 其他一切都可疑
add drop log all from any to any

之前有设置过防火墙的朋友可能会注意到有些内容缺失。特别是,没有反欺骗规则,事实上我们并没有添加:

add deny all from 1.2.3.4/8 to any in via fxp0

也就是说,丢弃来自外部、声称来自我们网络的数据包。这是你常见的一种做法,用以确保有人不会通过生成看起来像是来自内部的恶意数据包,试图绕过数据包过滤器。这样做的问题在于,外部接口上至少有一台主机你不希望忽略:路由器。但通常情况下,ISP 在他们的路由器上已经做了反欺骗处理,所以我们不需要过多担心。

最后一条规则看起来是默认规则的精确副本,也就是不允许任何不被明确允许的流量通过。但有所不同的是,所有可疑流量都会被记录日志。

习惯于设置防火墙的人可能也习惯在 ident 数据包(TCP 端口 113)上使用 reset 或 forward 规则。不幸的是,桥接的情况下,这个选项不适用,因此最好的做法是简单地将这些数据包传递到它们的目的地。只要目标机器没有运行 ident 守护进程,这基本是无害的。另一种选择是丢弃端口 113 的连接,这会给像 IRC 这样的服务带来一些问题(ident 探测必须超时)。

你可能注意到的唯一有些奇怪的事情是,允许桥接机器发送流量的规则,以及允许内部主机的规则。记住,这是因为这两种流量会通过内核的不同路径并进入数据包过滤器。内部网络会通过桥接,而本地机器会使用常规的 IP 栈进行通信。因此,需要两条规则来处理这两种情况。in via fxp0 规则适用于两种路径。一般来说,如果在整个过滤器中使用 in via 规则,你需要为本地生成的数据包做一个例外,因为这些数据包并没有通过我们的任何接口进入。

6. 贡献者

本文的许多部分来自一篇旧文,内容关于桥接,由 Nick Sayer 编辑、更新和改编。灵感来源于 Steve Peterson 对桥接的介绍。

特别感谢 Luigi Rizzo,感谢他在 FreeBSD 中实现了桥接代码,并且花时间回答我关于这方面的所有问题。

同时感谢 Tom Rhodes,他审查了我将这篇文章从意大利语(原文语言)翻译成英语的工作。

面向 FreeBSD 和 UNIX® 的新用户的简介

摘要

恭喜你安装了 FreeBSD!本简介适合那些对 FreeBSD 以及 UNIX® 都不熟悉的用户,因此它从基础开始。

1. 登录与退出

当你看到 login: 提示符时,作为在安装过程中创建的用户或 root 登录。(你的 FreeBSD 安装中会自动创建一个 root 账户,root 可以访问任何地方并执行任何操作,包括删除重要文件,所以请小心!)以下的 % 和 # 表示提示符(你的提示符可能不同),% 表示普通用户,# 表示 root。

要退出(并获得新的 login: 提示符),输入:

根据需要多次输入。是的,输入命令后按 enter,并记住 UNIX® 是区分大小写的——exit,而不是 EXIT。

要关闭计算机,输入:

要重启计算机,输入:

或者

你也可以通过 Ctrl+Alt+Delete 重启计算机。稍等片刻,它就会完成工作。这等同于最近版本的 FreeBSD 中的 /sbin/reboot,并且比按重置按钮要好得多。你可不希望重新安装 FreeBSD,对吧?

2. 添加具有 Root 权限的用户

如果在安装系统时未创建任何用户,而你目前是以 root 身份登录,那么现在应该创建一个用户,可以使用以下命令:

假设你创建了一个名为 jack 的用户,完整名称为 Jack Benimble。如果安全性是一个问题(例如周围有可能乱敲键盘的孩子),可以为 jack 设置一个密码。当它询问是否邀请 jack 加入其他组时,输入 wheel:

你可以随时按 Ctrl+C 退出 adduser,结束时会有机会确认新用户,或者直接输入 n 来取消。你可能还想创建第二个用户,这样在编辑 jack 的登录文件时,如果出现问题,至少有一个备用账户可以使用。

完成后,使用 exit 返回登录提示符,并以 jack 用户身份登录。通常,作为一个普通用户进行尽可能多的工作是一个好习惯,这样就没有 root 所带来的权限和风险。

要删除用户,可以使用 rmuser 命令。

3. 四处看看

作为普通用户登录后,四处看看并尝试一些命令,这些命令将帮助你获取 FreeBSD 系统中的帮助和信息。

以下是一些命令及其作用:

id

告诉你当前是谁!

pwd

显示你所在的目录,即当前工作目录。

ls

列出当前目录中的文件。

ls -F

列出当前目录中的文件,对于可执行文件在后面加上 *,对于目录加上 /,对于符号链接加上 @。

ls -l

以长格式列出文件——显示文件大小、日期和权限。

ls -a

列出包括隐藏的“点”文件在内的所有文件。如果你是 root 用户,隐藏的“点”文件在没有 -a 参数时就会显示。

cd

更改目录。cd .. 返回上一层目录;注意 cd 后面的空格。cd /usr/local 会进入该目录。cd ~ 会进入当前登录用户的主目录,例如 /usr/home/jack。尝试 cd /cdrom,然后使用 ls 命令,查看你的 CD-ROM 是否已挂载并正常工作。

less filename

让你查看一个文件(名为 filename),而不更改文件内容。尝试 less /etc/fstab。输入 q 退出。

cat filename

显示 filename 文件的内容。如果文件过长,你只能看到文件的最后部分,可以按 ScrollLock 并使用 上箭头 向上滚动;你也可以在查看手册页时使用 ScrollLock。再次按 ScrollLock 退出滚动。你可能想尝试在家目录中的某些点文件上使用 cat 命令——例如 cat .cshrc、cat .login、cat .profile。

你会注意到 .cshrc 中有些 ls 命令的别名(这些别名非常方便)。你可以通过编辑 .cshrc 来创建其他别名。如果你想让这些别名对系统中的所有用户可用,可以将它们添加到系统范围的 csh 配置文件 /etc/csh.cshrc 中。

4. 获取帮助和信息

以下是一些有用的帮助来源。Text 表示你输入的内容,通常是命令或文件名。

apropos text

在 whatis 数据库中查找包含 text 字符串的所有内容。

man text

查看 text 的手册页。UNIX® 系统文档的主要来源。比如,man ls 会告诉你如何使用 ls 命令。按 Enter 键浏览文本,按 Ctrl+B 返回上一页,按 Ctrl+F 前进一页,按 q 或 Ctrl+C 退出。

which text

告诉你命令 text 在用户路径中的位置。

locate text

查找所有包含字符串 text 的路径。

whatis text

告诉你命令 text 的功能及其手册页。输入 whatis * 可以查看当前目录下所有二进制文件的信息。

whereis text

找到文件 text,并给出它的完整路径。

你可能想尝试使用 whatis 查找一些常见有用的命令,如 cat、more、grep、mv、find、tar、chmod、chown、date 和 script。more 命令让你逐页阅读文件,就像在 DOS 中一样,例如 ls -l | more 或 more filename。* 作为通配符使用,例如 ls w* 会显示以 w 开头的文件。

这些命令中的一些没有很好地工作吗?locate(1) 和 whatis(1) 都依赖于一个每周重建的数据库。如果你的机器在周末不会长时间开启(并且不会运行 FreeBSD),你可能需要定期运行每日、每周和每月维护命令。以 root 身份运行这些命令,并且每次运行时给它们足够的时间完成。

如果你等得不耐烦,可以按 Alt+F2 获取另一个 虚拟控制台 并重新登录。毕竟,这是一个多用户、多任务的系统。尽管如此,这些命令运行时可能会在屏幕上显示消息;你可以在提示符下输入 clear 清除屏幕。它们运行完成后,你可能想查看 /var/mail/root 和 /var/log/messages。

运行这些命令是系统管理的一部分——作为 UNIX® 系统的唯一用户,你就是自己的系统管理员。几乎所有需要 root 权限的操作都属于系统管理工作。即使是那些厚厚的 UNIX® 手册,也往往没有很好地涵盖这些内容,因为它们似乎更多地关注于窗口管理器中的菜单操作。你可能想参考两本系统管理的经典书籍,分别是 Evi Nemeth 等人编写的《UNIX 系统管理手册》(Prentice-Hall,1995年,ISBN 0-13-15051-7,第二版,红色封面)或 Æleen Frisch 的《Essential System Administration》(O’Reilly & Associates,2002年,ISBN 0-596-00343-9)。我用的是 Nemeth 的书。

5. 编辑文本

为了配置你的系统,你需要编辑文本文件。大多数文件都会在 /etc 目录下,你需要通过 su 切换到 root 用户才能修改它们。你可以使用简单的 ee 编辑器,但从长远来看,学习文本编辑器 vi 是值得的。如果你安装了系统源代码,可以在 /usr/src/contrib/nvi/docs/tutorial 找到 vi 的优秀教程。

在编辑文件之前,你应该备份它。假设你要编辑 /etc/rc.conf 文件,你可以先通过 cd /etc 进入 /etc 目录,然后执行:

这样会将 rc.conf 复制为 rc.conf.orig,以后你可以将 rc.conf.orig 复制回 rc.conf 来恢复原始文件。但更好的方法是先移动(重命名)文件,然后再复制回来:

因为 mv 命令会保留文件的原始日期和所有者。现在你可以编辑 rc.conf 文件。如果想恢复原文件,可以将修改后的文件命名为 rc.conf.myedit(假设你想保留修改版本),然后执行:

这样就能将文件恢复到原来的状态。

要编辑文件,输入:

通过方向键移动光标。按下 <kbd>Esc</kbd> 键(即逃逸键)进入 vi 的命令模式。以下是一些常用命令:

x

删除光标所在的字符

dd

删除整行(即使它在屏幕上换行)

i

在光标位置插入文本

a

在光标后插入文本

你按下 i 或 a 后,就可以开始输入文本。按下 Esc 键返回命令模式,然后你可以输入:

:w

保存修改并继续编辑

:wq

保存并退出

:q!

不保存修改退出

/文本

将光标移动到指定的 text 处;按 / Enter(回车键)可以找到下一个出现的 文本。

G

跳转到文件末尾

nG

跳转到文件中的第 n 行,n 是一个数字

Ctrl+L

重新绘制屏幕

Ctrl+b 和 Ctrl+f

像 more 和 view 一样翻页,前进和后退。

你可以在你的家目录里练习使用 vi,通过 vi filename 创建一个新文件,添加和删除文本,保存文件,并再次打开它。vi 有一些让人吃惊的地方,因为它其实相当复杂,有时你会无意中执行一个命令,导致一些你没有预料到的结果。(有些人实际上喜欢 vi——它比 DOS 的编辑器更强大——你可以了解一下 :r 命令。)确保经常按 <kbd>Esc</kbd> 键回到命令模式,遇到问题时从命令模式开始,频繁保存文件(使用 :w),当需要重新开始时使用 :q! 放弃修改(从你上次的 :w 开始)。

现在你可以 cd 到 /etc 目录,su 切换到 root,使用 vi 编辑 /etc/group 文件,并将一个用户添加到 wheel 组中,从而赋予该用户 root 权限。只需在文件的第一行末尾添加一个逗号和用户的登录名,按 <kbd>Esc</kbd> 键,然后使用 :wq 保存文件并退出。立刻生效。(你没有在逗号后加空格吧?)

6. 其他有用的命令

df

显示文件空间和挂载的系统。

ps aux

显示正在运行的进程。ps ax 是更简洁的形式。

rm filename

删除 filename 文件。

rm -R dir

删除目录 dir 及其所有子目录——小心使用!

ls -R

列出当前目录及所有子目录中的文件;我使用过一个变体 ls -AFR > where.txt,它可以列出 / 和(分别)/usr 中的所有文件,直到我找到更好的方法来查找文件。

passwd

更改用户的密码(或 root 的密码)。

man hier

查看 UNIX® 文件系统的手册页。

使用 find 在 /usr 或其任何子目录中查找文件名:

你可以在 "filename" 中使用 * 作为通配符(应该加引号)。如果你让 find 从 / 目录开始搜索,它会在所有挂载的文件系统中查找文件,包括 CDROM 和 DOS 分区。

一本很好的书,讲解了 UNIX® 命令和工具,是 Abrahams & Larson 的《Unix for the Impatient》(第二版,Addison-Wesley,1996)。互联网上也有大量的 UNIX® 信息。

7. 下一步

如果你发现手册中关于从 CDROM 安装 Port 的内容过于复杂(涉及 lndir 等),通常可以按以下方法进行:

找到你需要的 Port,比如 kermit。它在 CDROM 上有个对应的目录。将该子目录复制到 /usr/local(这是添加的软件存放位置,所有用户都可以访问):

这样就会在 /usr/local/kermit 子目录下生成一个与 CDROM 上 kermit 子目录相同的文件夹。

接下来,使用 mkdir 创建目录 /usr/ports/distfiles(如果它尚不存在)。然后在 /cdrom/ports/distfiles 中查找一个名称指示它是你需要的 Port 的文件。将该文件复制到 /usr/ports/distfiles 目录;在最新版本中,你可以跳过这一步,因为 FreeBSD 会自动为你完成。如果是 kermit,则没有 distfile 文件。

然后 cd 到 /usr/local/kermit 子目录下,进入包含 Makefile 的目录。输入:

在此过程中,Port 将通过 FTP 下载它需要的任何压缩文件,如果这些文件在 CDROM 或 /usr/ports/distfiles 中没有找到。如果你还没有启动网络,并且 /cdrom/ports/distfiles 中没有该 Port 的文件,你需要使用另一台机器下载该文件并将其复制到 /usr/ports/distfiles。你可以通过 cat、more 或 view 阅读 Makefile,了解该文件的主分发站点和文件名。(使用二进制文件传输!)然后返回到 /usr/local/kermit,找到包含 Makefile 的目录,再次执行 make all install。

8. 你的工作环境

你的 shell 是工作环境中最重要的部分。shell 解释你在命令行输入的命令,从而与操作系统的其他部分进行通信。你还可以编写 shell 脚本——一系列无需干预即可运行的命令。

FreeBSD 默认安装了两个 shell:csh 和 sh。csh 适合命令行操作,但脚本应该使用 sh(或 bash)编写。你可以通过输入 echo $SHELL 来查看你正在使用的 shell。

可以用 csh shell,但 tcsh 做了 csh 所做的所有事情,并且做得更多。它能让你用箭头键回顾命令并编辑它们。它还支持通过 tab 键完成文件名(而 csh 使用 Esc 键),并且可以使用 cd - 切换到你最后访问的目录。你还可以更轻松地修改提示符。tcsh 让生活更轻松。

安装新 shell 的三个步骤:

  1. 将该 shell 作为 Port 或包安装,就像安装其他任何 Port 或包一样。

  2. 使用 chsh 命令永久更改你的 shell 为 tcsh,或者在提示符下输入 tcsh 以便在不重新登录的情况下临时更改 shell。

注意

更改 root 的 shell 为 sh 或 csh 以外的其他 shell 在早期版本的 FreeBSD 和许多其他 UNIX® 版本中可能是危险的;当系统将你置于单用户模式时,可能无法使用有效的 shell。解决方案是使用 su -m 成为 root,这样你就可以作为 root 使用 tcsh,因为 shell 是环境的一部分。你可以通过将以下内容添加到你的 .tcshrc 文件中作为别名来使其永久生效:

当 tcsh 启动时,它将读取 /etc/csh.cshrc 和 /etc/csh.login 文件,就像 csh 一样。它还会读取你家目录中的 .login 和 .cshrc 文件,除非你提供了一个 .tcshrc 文件。你可以通过简单地将 .cshrc 复制为 .tcshrc 来实现这一点。

现在你已经安装了 tcsh,你可以调整你的提示符。你可以在 tcsh 的手册页中找到详细信息,但这里有一行代码可以放入你的 .tcshrc 中,它将告诉你输入了多少条命令、当前时间和你所在的目录。如果你是普通用户,它还会显示 >,如果你是 root 用户,它会显示 #,但无论如何 tcsh 都会这么做:

如果已有 set prompt 行,这行代码应该放在同一位置,或者如果没有,就放在 if(\$?prompt) then 语句下。注释掉旧的行;如果你更喜欢它,你随时可以切换回来。不要忘记空格和引号。你可以通过输入 source .tcshrc 来重新读取 .tcshrc。

你可以通过在提示符下输入 env 来列出已设置的其他环境变量。结果将显示你的默认编辑器、分页程序和终端类型等,可能还有很多其他信息。如果你从远程位置登录,并且由于终端不支持,无法运行某个程序,可以使用 setenv TERM vt100 命令来设置终端类型。

9. 其他

作为 root 用户,你可以使用 /sbin/umount /cdrom 来卸载 CDROM,取出光盘,插入另一张,然后使用 /sbin/mount_cd9660 /dev/cd0a /cdrom 来挂载它,假设 cd0a 是你的 CDROM 驱动器的设备名称。最新版本的 FreeBSD 允许你仅使用 /sbin/mount /cdrom 来挂载 CDROM。

如果你的空间有限,使用 FreeBSD 的 CDROM 上的实时文件系统(第二张光盘)是很有用的。实时文件系统的内容因版本而异。你可以尝试从 CDROM 上玩游戏。这涉及使用 lndir,它与 X Window 系统一起安装,用来告诉程序在哪里找到必要的文件,因为它们在 /cdrom 中,而不是在预期的 /usr 和其子目录中。阅读 man lndir。

10. 欢迎提出意见

如果你使用了本指南,我很想知道哪些地方不清楚,以及你认为应该包括哪些被遗漏的内容,或者它是否有帮助。感谢 Eugene W. Stark(纽约州立大学石溪分校计算机科学教授)和 John Fieber 提供的有益意见。

FreeBSD 对闰秒的支持

1. 介绍

闰秒是每年在特定时刻对 UTC 进行的一秒钟调整,用以将原子时间与地球自转的变化同步。本文介绍了 FreeBSD 如何处理闰秒。

截至本文写作时,下一个闰秒将在 2015 年 6 月 30 日 23:59:60 UTC 发生。这个闰秒将在北美、南美和亚太地区的工作日内发生。

2. FreeBSD 默认的闰秒处理

如果没有使用 NTP,在闰秒过去后需要手动调整系统时钟。

3. 注意事项

闰秒是在全球范围内同一时刻插入的:UTC 午夜。在日本是上午,在太平洋地区是中午,在美洲是傍晚,而在欧洲是晚上。

我们相信并期望,FreeBSD 在提供正确且稳定的 NTP 服务时,会在这次闰秒时正常工作,就像之前的闰秒一样。

然而,我们要提醒的是,几乎没有应用程序曾经向内核询问过关于闰秒的信息。我们的经验是,按照设计,闰秒本质上是对闰秒前一秒的重播,而这对大多数应用程序开发人员来说是一个意外。

其他操作系统和其他计算机可能会或不会像 FreeBSD 一样处理闰秒,没有正确和稳定的 NTP 服务的系统根本无法了解闰秒。

计算机因闰秒而崩溃并非罕见,经验表明,许多公共 NTP 服务器可能会错误地处理并宣布闰秒。

请尽量确保闰秒事件不会造成严重问题。

4. 测试

可以测试是否会使用闰秒。由于 NTP 的性质,测试可能在闰秒前最多 24 小时内有效。一些主要的参考时钟源只会在事件发生前一小时宣布闰秒。查询 NTP 守护进程:

包含 leap_add_sec 的输出表示正确支持闰秒。在闰秒前的 24 小时内或闰秒过后,将显示 leap_none。

5. 结论

实际上,闰秒通常不会在 FreeBSD 上造成问题。我们希望这篇概述能帮助澄清预期情况,并确保闰秒事件的顺利进行。

FreeBSD 状态报告流程

FreeBSD 状态报告每季度发布一次,向公众提供了项目的最新动态,它们通常会附带来自开发者峰会的特别报告。由于状态报告是我们最为显眼的沟通形式之一,因此它们非常重要。

在本文档中以及与 FreeBSD 状态报告相关的其他地方,状态报告 这个术语既指代每季度发布的文档,也指其中的单个条目。

1. 为作者提供的写作指南

本节提供了一些写作状态报告条目的建议,也包括如何提交条目的说明。

不要担心自己的母语不是英语。状态团队会检查你的条目,并修正拼写和语法错误。

1.1. 介绍你的工作

不要假设阅读报告的人了解你的项目。

状态报告的受众广泛。它们通常是 FreeBSD 网站上的头条新闻,是人们了解 FreeBSD 相关信息时首先阅读的内容。考虑这个例子:

如果读者熟悉 UNIX 手册页,他们会知道 abc(4) 是某种设备。但读者为什么需要关心呢?它是何种设备?对比以下版本:

现在读者知道 abc 是一款网络接口驱动程序。即便他们不使用 Yoyodyne 的产品,你已经传达了 FreeBSD 在网络设备方面的支持正在不断增强。

1.2. 显示你工作的意义

状态报告不仅仅是告诉大家做了什么,它们还需要解释为什么要做这些事情。

继续前面的例子。现在我们支持 Yoyodyne Frobnicator 卡,为什么这件事值得一提?它们广泛使用吗?是否在某些流行设备中使用?是否在 FreeBSD 希望进入的特定领域有重要地位?它们是否是世界上最快的网卡?状态报告通常会包含类似这样的内容:

然后就停了,也许读者是 Cyberdyne 的忠实粉丝,知道 T800 带来了哪些激动人心的新特性,但这并不常见。大多数情况下,他们可能对你导入的内容不太了解(尤其是对于 Ports:记住那里面有 35,000 余项其他条目)。列出一些新特性或修复的 bug,告诉他们为什么我们获得了新版本是件好事。

1.3. 告诉我们一些新信息

不要重复相同的状态报告条目。

请记住,状态报告不仅仅是对项目状态的报告,它们还是项目状态变化的报告。如果某个项目正在进行中,简要介绍一下它,但接下来要专注于新工作。自上次报告以来有什么进展?还剩下什么工作?什么时候可能完成(或者如果“完成”并不适用,那什么时候可能准备好供更广泛的使用、测试、生产部署等)?

1.4. 赞助

不要忘记提及你的赞助商。

如果你或你的项目获得了赞助,或者你已经作为承包商或员工为某公司工作,请在报告中提到。赞助商总是很高兴你感谢他们的资助,同时他们也会从中受益,展示他们在积极支持 FreeBSD 项目。最后,这有助于 FreeBSD 更好地了解其重要的用户。

1.5. 待办事项

如果需要帮助,请明确说明!

是否需要别人提供帮助?有没有任务是其他人可以完成的?你可以在状态报告中的待办事项部分用两种方式来描述:请求帮助,或者快速概述剩余的工作量。如果已经有足够多的人参与项目,或者项目处于一个不适合增加人手的状态,那么后一种方式更为合适。列出正在进行的大任务,可能还可以标明每个任务的负责人。

列出任务,提供足够的细节让人们知道他们是否能够做这些事情,并邀请他们联系你。

1.6. 提交报告

你可以通过以下方法提交你的报告:

2. 编辑指南

本节介绍了状态报告的审阅和发布流程。

2.1. 时间线

状态团队始终接受报告,但主要的收集过程发生在每个季度的最后一个月,即 3 月、6 月、9 月和 12 月。在这些月份,会明确发出状态报告的征集通知。1 月、4 月、7 月和 10 月则专注于整理上一季度提交的报告,这期间可能会等待迟交的报告。状态报告的发布会在报告准备好后尽快完成,通常是在同一个季度内。

提交报告的审阅工作通常应在 1 月、4 月、7 月和 10 月的中旬完成(第三方审阅期)。也就是说,除了错别字或轻微的文稿编辑外,状态团队应该在每月 15 日之后开始整理提交的报告。请注意,这并不是完全冻结,状态团队仍然可以接受审阅。

2.2. 征集报告

状态报告的征集通知会发送给以下收件人:

  • 所有上次提交状态报告的人(他们可能有更新或进一步的改进);

  • 根据季节变化:

    • 各种会议组织者:

    • 各种会议参与者:

      • 9 月至 10 月的 EuroBSDcon(第三、四季度);EuroBSDcon 作为一家组织对写 FreeBSD 状态报告不感兴趣——至少在 2019 年 10 月时并非如此:其原因是该会议并非专门针对 FreeBSD。因此,应向参与会议的 FreeBSD 社区成员征集关于该事件的报告。

警告 、 如果你负责发送状态报告的调用,并且确实使用了 cron 作业,请在 freefall 上运行它并签上你的名字,以便在出现问题时能够推断出是谁配置了该 cron 作业。同时,请将上面的示例更新为包含你的名字,作为额外的安全措施。

2.3. 构建报告

提交的报告会在收到后进行审查,并合并到 doc/website/content/en/status/ 的适当子目录中。在报告更新的同时,状态团队之外的人也可以审查各个条目并提出修正建议。

通常,内容审查过程的最后一步是写一个名为 intro.adoc 的文件中的介绍:只有在所有报告收集完毕后,才能写出好的介绍。如果可能,最好让不同的人写介绍,以增加多样性:不同的人会带来不同的观点,有助于保持新鲜感。

待所有报告和介绍准备好后,需要创建 _index.adoc 文件:这是报告按类别分发并排序的文件。

2.4. 发布报告

当状态报告的所有文件准备就绪后,就可以发布它了。

首先,编辑 doc/website/content/en/status/_index.adoc:更新下一个截止日期并添加新报告的链接。然后将更改推送到仓库,状态团队检查一切是否按预期工作。

接着,将新闻条目添加到 doc/website/data/en/news/news.toml 中。

以下是新闻条目的示例:

链接到渲染后的报告会添加在介绍和第一个条目之间。

最后,报告准备好发送,切换邮件的显示方式(报告应嵌入邮件正文中),并确保它以 UTF-8 编码。

发送两封电子邮件,主题格式为 FreeBSD 状态报告 - <第一/第二/第三/第四> 季度 <年份>:

重要

构建你自己的 FreeBSD 更新服务器

警告

本文中的说明涉及较旧版本的 FreeBSD,可能无法在较新的操作系统版本中正常工作。随着 pkgbase 的出现,freebsd-update 工具预计将在未来从 FreeBSD 中移除。当这种情况发生时,本文将更新为反映新的程序,或者完全删除。

摘要

1. 致谢

2. 介绍

有经验的用户或管理员通常负责多个机器或环境。他们理解维持这种基础设施的困难要求和挑战。运行 FreeBSD 更新服务器可以更容易地将安全性和软件补丁部署到选定的测试机器上,再推广到生产环境中。它还意味着多个系统可以通过本地网络而不是可能较慢的互联网连接来更新。本文概述了创建内部 FreeBSD 更新服务器的步骤。

3. 前提条件

要构建内部 FreeBSD 更新服务器,需满足一些要求。

  • 一个正在运行的 FreeBSD 系统。

    注意

    至少,更新需要在一个大于或等于目标发布版本的 FreeBSD 版本上构建。

  • 至少 4 GB 可用空间的用户账户。这能创建 7.1 和 7.2 的更新,但确切的空间要求可能会根据版本的不同而有所变化。

4. 配置:安装与设置

适当地更新 scripts/build.conf 文件。该文件在所有构建操作中都会被引用。

以下是默认的 build.conf,应根据你的环境进行修改。

考虑的参数包括:

  • ② 构建主机的名称。当更新系统时,执行 % uname -v 时会显示此信息。

  • ④ 用于上传文件到更新服务器的账户。

  • ⑤ 更新服务器中上传文件的目录。

默认的 build.conf 文件适用于构建 FreeBSD 的 i386 版本。作为构建其他架构更新服务器的示例,以下步骤概述了为 amd64 配置所需的更改:

  1. 为 amd64 创建构建环境:

  2. 在新创建的构建目录中安装 build.conf 文件。FreeBSD 7.2-RELEASE 版本在 amd64 上的构建配置选项应类似于:

5. 构建更新代码

第一步是运行 scripts/make.sh。这将构建一些二进制文件,创建目录,并生成用于批准构建的 RSA 签名密钥。在此步骤中,最终创建签名密钥时需要提供一个密码短语。

注意

请记下生成的密钥指纹。该值在进行二进制更新时需要在 /etc/freebsd-update.conf 中使用。

此时,我们已准备好进行构建阶段。

接下来是 初始 构建运行的示例。

然后,世界(world)的构建将再次执行,并应用世界补丁。更详细的说明可以在 scripts/build.subr 中找到。

警告

最后,构建完成。

如果一切正确,则批准构建。有关如何确认这一点的更多信息,可以在分发的源文件 USAGE 中找到。按照指示执行 scripts/approve.sh,这将签署该发布,并将组件移动到适合上传的暂存区。

批准过程完成后,可以开始上传程序。

注意

如果需要重新上传更新代码,可以通过切换到目标发布的公共分发目录,并更新已上传文件的属性来完成此操作。

重要

为了使 FreeBSD 更新服务器正常工作,需要为 当前 发布版本和 目标升级版本 构建更新。这对于确定不同版本之间的文件差异是必要的。例如,当将 FreeBSD 系统从 7.1-RELEASE 升级到 7.2-RELEASE 时,需要为这两个版本构建并上传更新到分发服务器。

6. 构建补丁

在这个示例中,将使用 7.1-RELEASE。

以下是针对不同发布版本构建补丁的几个假设:

  • 设置正确的目录结构以进行初始构建。

  • 为 7.1-RELEASE 执行初始构建。

在 /usr/local/freebsd-update-server/patches/ 下创建相应发布版本的补丁目录。

注意

在运行补丁级别构建时,假定之前的补丁已经到位。当运行补丁构建时,它会运行补丁目录中包含的所有补丁。

可以向任何构建添加自定义补丁。使用数字零,或其他任何数字。

警告

FreeBSD 更新服务器的管理员有责任采取适当措施验证每个补丁的真实性。

此时,diff 已准备好进行构建。软件首先检查在运行差异构建之前,是否已经在相应的发布版本上运行过 scripts/init.sh。

接下来是 差异 构建运行的示例。

更新会被打印出来,并请求批准。

按照之前提到的相同流程来批准构建:

7. 提示

  • 在 scripts/build.subr 脚本中的 buildworld 和 obj 目标添加 -j<span> NUMBER</span> 标志,可以加速处理,具体取决于使用的硬件,但这不是必须的。不建议在其他目标中使用这些标志,因为它可能导致构建变得不可靠。

面向 Linux® 用户的 FreeBSD 快速入门指南

摘要

本文档旨在帮助中级到高级的 Linux® 用户快速了解 FreeBSD 的基础知识。

1. 介绍

本文档突出了 FreeBSD 和 Linux® 之间的一些技术差异,以帮助中级到高级的 Linux® 用户快速熟悉 FreeBSD 的基础知识。

2. 默认 Shell

3. Port 和包:在 FreeBSD 中添加软件

FreeBSD 提供了两种安装应用程序的方法:二进制包和编译 Port。每种方法都有其优点:

二进制包

  • 相比编译大型应用程序,安装速度更快。

  • 不需要了解如何编译软件。

  • 不需要安装编译器。

Port

  • 可以自定义安装选项。

  • 可以应用自定义补丁。

如果应用程序的安装无需任何定制,安装二进制包即可。如果应用程序需要定制默认选项,则应编译 Port。必要时,可以通过 make package 从 Port 编译自定义包。

3.1. 包

包是预编译的应用程序,相当于基于 Debian/Ubuntu 系统的 .deb 文件和基于 Red Hat/Fedora 系统的 .rpm 文件。包通过 pkg 安装。例如,以下命令安装 Apache 2.4:

3.2. Port

FreeBSD Ports 是一套专门为从源代码安装应用程序而定制的 Makefile 和补丁框架。在安装 Port 时,系统会获取源代码、应用所需的补丁、编译代码,并安装应用程序及其所需的依赖项。

要编译一个 Port,请切换到该 Port 的目录并启动构建过程。以下示例展示了如何从 Ports 安装 Apache 2.4:

使用 Port 安装软件的一个好处是能够自定义安装选项。例如,以下命令指定还要安装 mod_ldap 模块:

4. 系统启动

只要在 /etc/rc.conf 中启用了服务,就可以在不重启系统的情况下启动它:

如果某个服务尚未启用,则可以通过命令行使用 onestart 启动它:

5. 网络配置

以下是配置 DHCP 接口的条目:

6. 防火墙

FreeBSD 并不使用 Linux® 的 IPTABLES 防火墙,而是提供了三种内核级防火墙的选择:

以下是允许入站 SSH 的 PF 示例条目:

IPFILTER 是 Darren Reed 开发的防火墙应用程序。它并非专门为 FreeBSD 开发,已经移植到多个操作系统,包括 NetBSD、OpenBSD、SunOS、HP/UX 和 Solaris。

允许入站 SSH 的 IPFILTER 语法如下:

允许入站 SSH 的 IPFW 语法如下:

7. 更新 FreeBSD

更新 FreeBSD 系统有两种方法:从源代码更新或二进制更新。

从源代码更新是最复杂的更新方法,但却是最灵活的。该过程涉及将本地的 FreeBSD 源代码副本与 FreeBSD Git 仓库同步。在本地源代码更新到最新版本后,就可以编译新的内核和用户空间。

注意

8. procfs: 已消失但未被遗忘

例如,使用以下命令来确定 FreeBSD 系统是否启用了 IP 转发:

使用 -a 列出所有系统设置:

如果某个应用程序需要 procfs,可以将以下条目添加到 /etc/fstab 中:

包括 noauto 可以防止 /proc 在启动时自动挂载。

如果不重启系统,可以使用以下命令挂载文件系统:

9. 常见命令

以下是一些常见命令的等效命令:

10. 结论

如何从 FreeBSD-questions 邮件列表中获得最佳结果

摘要

本文档为那些希望向 FreeBSD-questions 邮件列表提交电子邮件的人提供了有用的信息。提供了一些建议和提示,帮助读者最大化收到有用回复的机会。

本文档会定期发布到 FreeBSD-questions 邮件列表。

1. 介绍

FreeBSD-questions 是由 FreeBSD 项目维护的邮件列表,旨在帮助那些有关于 FreeBSD 正常使用问题的人。另一个邮件列表,FreeBSD-hackers,讨论的是一些更为高级的问题,例如未来的开发工作。

注意

这是定期发布的文章,旨在帮助那些向 FreeBSD-questions 提问的人(“新手”)以及那些回答问题的人(“黑客”)。

不可避免地,存在一些摩擦,源自这两个群体的不同观点。新手指责黑客傲慢、自以为是、没有帮助,而黑客则指责新手愚蠢、无法理解简单的英语、并期待所有问题都能迎刃而解。当然,这些指责中确实有些道理,但大多数情况下,这些观点源于一种挫败感。

在本文中,我希望做一些事情,以缓解这种挫败感,并帮助大家从 FreeBSD-questions 中获得更好的结果。在接下来的部分,我将推荐如何提问;之后,我们将讨论如何回答问题。

2. 如何订阅 FreeBSD-questions

你将收到来自 mlmmj 的确认邮件;请按照邮件中的说明完成订阅。

3. 如何退订 FreeBSD-questions

你将收到来自 mlmmj 的确认邮件;请按照邮件中的说明完成退订。

4. 我该问 -questions 还是 -hackers?

有两个邮件列表处理 FreeBSD 的一般性问题,分别是 FreeBSD-questions 和 FreeBSD-hackers。在某些情况下,可能并不清楚该向哪个列表提问。以下标准适用于 99% 的问题:

  1. 如果问题是一般性的,请向 FreeBSD-questions 提问。例如,可能是关于安装 FreeBSD 或使用某个特定 UNIX® 工具的问题。

  2. 如果你认为问题涉及到一个 bug,但不确定,或者你不知道如何查找它,请将问题发送到 FreeBSD-questions。

  3. 如果你认为问题是 bug 且你 确信 它是 bug(例如,你能准确定位到代码中的问题所在,并且可能有修复方法),则发送到 FreeBSD-hackers。

  4. 如果问题涉及对 FreeBSD 的增强功能,并且你能提出实现的建议,那么请向 FreeBSD-hackers 提问。

5. 提问前的准备

在向邮件列表提问之前,你可以(并且应该)做一些准备工作:

  • 尝试自己解决问题。如果你提问时表明你已经尝试过解决问题,那么你的问题通常会吸引更多读者的关注。尝试自己解决问题也会增强你对 FreeBSD 的理解,并最终使你能通过回答邮件列表中的问题帮助其他人。

6. 如何提交问题

在向 FreeBSD-questions 提交问题时,请考虑以下几点:

  • 请记住,回答 FreeBSD 问题的人并没有得到报酬,他们是自愿回答的。你可以通过提交结构良好的问题,提供尽可能多的相关信息,积极影响他们的意愿。你也可以通过提交不完整、难以理解或无礼的问题,打消他们的意愿。即使你遵循这些规则,仍然有可能没有得到回答;如果你不遵循这些规则,得不到答案的可能性会更大。接下来的部分将介绍如何从 FreeBSD-questions 获得最好的问题回复。

  • 并不是所有回答 FreeBSD 问题的人都会阅读每一封邮件:他们会查看主题行并决定是否感兴趣。显然,指定一个清晰的主题对你有利。“FreeBSD 问题”或“帮助”是不够的。如果你根本不提供主题,许多人甚至不会阅读它。如果你的主题不够具体,能够回答的人可能也不会阅读。

  • 格式化你的邮件,使其可读,并且请不要大写字母!我们理解许多人英语不是母语,我们会尽量宽容,但如果邮件充满错别字或没有换行符,阅读起来会非常痛苦。不要低估不当格式的邮件带来的影响,这不仅仅是对于 FreeBSD-questions 邮件列表的影响。邮件是别人对你的第一印象,如果邮件格式不好,段落每行只有一行、拼写错误或错误百出,别人会对你留下不好的印象。

  • exmh

  • Microsoft® Exchange

  • Microsoft® Outlook®

尽量避免使用 MIME:许多人使用的邮件客户端与 MIME 不太兼容。

  • 确保你的时间和时区设置正确。虽然你的邮件仍然会发送出去,但许多人每天会收到几百封邮件。他们通常会按主题和日期对邮件进行排序,如果你的邮件没有排在第一个回复之前,他们可能会以为漏掉了邮件,进而不再查看。

  • 不要在同一封邮件中包含不相关的问题。首先,冗长的邮件会让人害怕;其次,涉及多个问题的邮件很难让所有能回答这些问题的人都阅读。

  • 尽可能提供更多的信息。这是一个难题,我们需要详细说明你需要提交的信息,但这里是一些开始的建议:

    • 在几乎所有情况下,了解你运行的 FreeBSD 版本都很重要。尤其是 FreeBSD-CURRENT 版本,你还应该指定源码的日期,当然,你不应该将 -CURRENT 版本的问题提交到 FreeBSD-questions。

    • 如果你收到错误信息,不要只是说“我收到了错误信息”,请明确说出(例如)“我收到的错误信息是‘No route to host’”。

    • 如果你的系统崩溃了,不要只是说“我的系统崩溃了”,请明确说出(例如)“我的系统崩溃并显示消息‘free vnode isn’t’”。

    • 如果你在安装 FreeBSD 时遇到问题,请告诉我们你使用的硬件配置。特别是,需要知道你机器中安装的板卡的 IRQ 和 I/O 地址。

    • 如果你在运行 PPP 时遇到问题,请描述你的配置。你使用的是哪一版本的 PPP?什么类型的身份验证?你有静态还是动态 IP 地址?在日志文件中你看到了什么消息?

  • 这会将信息重定向到 /tmp/dmesg.out 文件中。

  • 如果你做了这些准备,仍然没有得到回答,可能是由于其他原因。例如,问题非常复杂,没有人知道答案,或者知道答案的人暂时没有在线。如果你在一周后仍没有收到回复,可以尝试重新发送消息。然而,如果你第二次发送消息后仍然没有得到答复,可能你就不太可能从这个论坛获得帮助。重复发送相同的消息只会让你不受欢迎。

总结一下,假设你知道下面这个问题的答案(是的,两个例子是一样的)。你选择哪个问题更愿意回答:

示例 1:邮件 1

示例 2:邮件 2

7. 如何跟进问题

通常情况下,你可能需要向已经发送的问题提供附加信息。最好的方法是回复你的原始邮件,这有三个优点:

  1. 你包含了原始邮件的文本,这样大家就知道你在谈论什么。但请记得删除不必要的文本。

  2. 主题行保持不变(你确实记得设置一个主题吧?)。许多邮件客户端会按主题对邮件进行排序,这有助于将邮件归为一组。

8. 如何回答问题

在回答 FreeBSD-questions 上的问题时,请考虑以下几点:

  1. 提交问题时的很多要点同样适用于回答问题。请仔细阅读这些要点。

  2. 是否已经有人回答了这个问题?检查是否有人已经回答,可以通过按主题对收件箱中的邮件进行排序来完成:希望你能看到问题和所有的回答,彼此都在一起。如果有人已经回答了问题,这并不意味着你不能再提供一个答案,但最好先阅读其他人的回答。

  3. 你是否有其他贡献,超出已经给出的答案?一般来说,“是的,我也遇到过”这样的回答帮助不大,尽管也有例外。例如,某人介绍了他们遇到的问题,他们不知道是自己的错还是硬件或软件有问题。如果你发送了“我也遇到过”的回答,应该同时提供其他相关信息。

  4. 你是否确定理解了问题?很常见,提问的人会感到困惑或表达不清。即使你对系统有最好的理解,仍然很容易给出一个不能回答问题的回复。这没有帮助,反而会让提问者感到更加困惑。如果没有其他人回答,并且你自己也不太确定,可以请求更多的信息。

  5. 你是否确定你的答案是正确的?如果不确定,可以等一天左右。如果没有其他人给出更好的答案,你仍然可以回答,并说,比如,“我不确定这是否正确,但既然没有其他人回复,为什么不试着用青蛙替换掉你的 ATAPI CDROM 呢?”。

  6. 除非有充分的理由,否则请同时回复发件人和 FreeBSD-questions。许多 FreeBSD-questions 列表上的人是“潜水者”(lurkers):他们通过阅读他人发送和回复的邮件来学习。如果你把某个具有普遍兴趣的问题发到私下而不发到列表,你就剥夺了这些人获取信息的机会。小心群发回复;很多人会发送带有上百个抄送的邮件。如果是这种情况,请确保恰当地修剪抄送(Cc:)行。

  7. 包含原始邮件中相关的文本。将其修剪到最小,但不要过度。即使某人没有阅读原始邮件,他们仍然应该能理解你在说什么。

  8. 使用某种方式区分原始邮件中的文本和你添加的文本。我个人觉得在原始邮件文本前加上 “>” 最有效。通过在“>”后留白,并在你的文本和原始文本之间留空行,能够使邮件更易于阅读。

  9. 将你的回复放在正确的位置(即回复的文本之后)。如果每个回复都出现在它所回复的文本之前,阅读邮件线程会变得非常困难。

  10. 大多数邮件客户端会在回复时更改主题行,前面加上类似 “Re: ” 的文本。如果你的邮件客户端没有自动做到这一点,你应该手动修改。

  11. 如果提问者没有遵循格式规定(比如行太长、主题不合适),请修正。如果是错误的主题行(如“HELP!!??”),请将主题行改为(例如)“Re: 同步 PPP 的困难(原主题:HELP!!??)”。这样其他人跟踪邮件线程时会更容易。 在这种情况下,适当地说明你做了什么以及为什么这样做是合适的,但请尽量避免显得无礼。如果你发现自己无法在不显得无礼的情况下回答,可以选择不回答。

    如果你只是因为格式不好而想回复邮件,可以仅回复给提问者,而不是回复整个列表。你也可以选择仅向提问者发送这封邮件。

FreeBSD 发布工程

摘要

本文介绍了 FreeBSD 项目的发布工程过程。

1. FreeBSD 发布工程过程介绍

FreeBSD 的开发有一个非常具体的工作流程。一般来说,所有对 FreeBSD 基础系统的更改都会提交到主分支,该分支反映了源代码树的顶部。

经过合理的测试期后,更改可以合并到 stable/ 分支。合并到 stable/ 分支的默认最短时间框架是三(3)天。

虽然合并到 stable/ 分支前通常需要等待至少三天,但在一些特殊情况下,可能需要立即合并,例如关键的安全修复或直接阻碍发布构建过程的 bug 修复。

经过几个月的开发,stable/ 分支中的更改数量显著增加时,就到了发布下一个版本的 FreeBSD 的时候。这些发布通常被称为 点 版本发布。

在 stable/ 分支发布之间,大约每两(2)年一次,发布将直接从主分支切出。这些发布通常被称为 点零 版本发布。

本文将重点介绍 FreeBSD 发布工程团队在 点零 和 点 发布中的工作流程和责任。

本文的以下章节介绍了:

2. 一般信息和准备

在发布周期开始前大约两个月,FreeBSD 发布工程团队会决定发布的时间表。该时间表包括发布周期中的各个里程碑节点,例如冻结日期、分支日期和构建日期。例如:

注意

标记为 [*] 的项目是“按需”的。

  1. doc/ 树冻结由 FreeBSD 文档工程团队协调。

  2. 使用的 Ports 季度分支由最终的 RC 构建计划决定。新的季度分支会在每季度的第一天创建,因此在考虑发布周期的里程碑时应使用该指标。季度分支由 FreeBSD Ports 管理团队创建。

  3. doc/ 树的标签由 FreeBSD 文档工程团队创建。

  4. 最终的 Ports 包构建由 FreeBSD Ports 管理团队在最终(或预期为最终的)RC 构建之后进行。

注意

如果发布是从现有的 stable/ 分支创建的,则可以省略 KBI 冻结日期,因为在已经建立的 stable/ 分支上,KBI 已经被视为冻结。

在编写发布周期的时间表时,需要考虑一些因素,特别是那些目标日期依赖于已定义的里程碑节点的情况。例如,Ports 的发布标签来自于在最后一个 RC 时的活跃季度分支。这部分决定了使用哪个季度分支,何时可以进行发布标签,以及用于最终 RELEASE 构建的 ports 树的修订版本。

在时间表达成一般共识后,FreeBSD 发布工程团队会将该时间表通过电子邮件发送给 FreeBSD 开发人员。

许多开发人员通常会通知 FreeBSD 发布工程团队他们正在进行的各种工作。在某些情况下,可能会请求延长正在进行的工作的时间,而在其他情况下,可能会请求对特定子树的 blanket approval(通行许可)。

在这些请求中,重要的是确保讨论时间表(即使是估算的)。对于 blanket approval,应该明确许可的时间范围。例如,FreeBSD 开发人员可能会请求从代码冻结开始到 RC 构建开始期间对 release/doc/ 提供 blanket approval 以便更新发布说明和其他与发布相关的文档。

注意

为了跟踪 blanket approval,FreeBSD 发布工程团队使用内部仓库来记录这些请求,记录内容包括:授予 blanket approval 的区域、作者、批准过期时间以及批准原因。一个例子是,在最终的 RC 构建之前,授予 FreeBSD 发布工程团队所有成员对 release/doc/ 的 blanket approval,以便更新发布说明和其他与发布相关的文档。

注意

FreeBSD 发布工程团队还使用这个仓库来跟踪在发布周期开始各个构建之前收到的待批准请求,发布工程师会通过电子邮件通知 FreeBSD 开发人员批准的截止时间。

根据所涉及的底层代码集及其对 FreeBSD 整体的影响,FreeBSD 发布工程团队可能会批准或拒绝这些请求。

同样适用于正在进行的扩展。例如,对于一个新设备驱动的正在进行的工作,若该工作与树中的其他部分没有直接关联,可能会获得延期。然而,一个新的调度程序可能无法实现,尤其是当这种剧烈的变化在其他分支中不存在时。

该时间表还会添加到项目网站的 doc/ 仓库中的 ~/website/content/en/releases/13.0R/schedule.adoc 文件中。随着发布周期的进展,此文件会不断更新。

注意

在大多数情况下,schedule.adoc 可以从先前的发布中复制并相应更新。

除了将 schedule.adoc 添加到网站中,~/shared/releases.adoc 还会更新,以便在各个子页面中添加指向时间表的链接,并使该链接能够在项目网站的主页上显示。

该时间表还会链接到 ~/website/content/en/releng/_index.adoc。

大约在计划的“代码冻结”前一个月,FreeBSD 发布工程团队会向 FreeBSD 开发人员发送提醒邮件。

3. 发布工程术语

本节介绍了本文件中使用的一些术语。

3.1. 代码冻结前的准备阶段(Code Slush)

尽管代码冻结前的准备阶段并不是对树的硬性冻结,FreeBSD 发布工程团队要求将现有代码库中的错误修复优先于新功能的开发。

代码冻结前的准备阶段并不强制要求对分支的提交进行审批。

3.2. 代码冻结(Code Freeze)

代码冻结标志着一个时间点,从此刻起,所有提交到分支的代码都需要得到 FreeBSD 发布工程团队的明确批准。

FreeBSD Git 仓库包含多个钩子,以在提交到树之前执行有效性检查。其中一个钩子将检查提交到特定分支是否需要特定的批准。

为了执行 FreeBSD 发布工程团队的提交批准,发布工程团队必须批准对该分支的任何更改,此时提交日志中必须包括一行 Approved by: re (login),其中 "login" 是批准人的登录 ID。

注意

3.3. KBI/KPI 冻结(KBI/KPI Freeze)

KBI/KPI 的稳定性意味着在两个不同版本的软件中调用实现相同功能的函数时,调用方(无论是进程、线程还是函数)都期望该函数以某种方式运行,否则分支上的 KBI/KPI 稳定性将被破坏。

4. 发布周期中的网站更改

本节介绍了在发布周期进行过程中,网站应进行的更改。

注意

本节中指定的文件是相对于 doc 仓库的 main 分支的。

4.1. 发布周期开始前的网站更改

当发布周期时间表可用时,需要更新以下文件以启用 FreeBSD 项目网站上的不同功能:

4.2. 在 BETA 或 RC 阶段的网站更改

从 PRERELEASE 过渡到 BETA 时,需要更新以下文件以启用下载页面上的“帮助测试”模块。所有文件相对于 head/ 在 doc 仓库中:

创建了 releng/13.0 分支之后,相关的发布文档需要添加到 doc/ 仓库中。

注意

相关的发布文档存在于 FreeBSD 12.x 及以后的 doc 仓库中。

4.3. 在 BETA、RC 和最终 RELEASE 期间的 Ports 更改

5. 从 main 发布

本节介绍了从 main 分支进行 FreeBSD 发布周期的一般程序。

5.1. FreeBSD “ALPHA” 构建

从 FreeBSD 10.0-RELEASE 开始,引入了“ALPHA”构建的概念。与 BETA 和 RC 构建不同,ALPHA 构建不包含在 FreeBSD 发布计划中。

ALPHA 构建的目的是在创建 stable/ 分支之前提供定期的 FreeBSD 提供的构建版本。

FreeBSD ALPHA 快照应该大约每周构建一次。

对于第一次 ALPHA 构建,需要将 sys/conf/newvers.sh 中的 BRANCH 值从 CURRENT 更改为 ALPHA1。对于随后的每个 ALPHA 构建,将每个 ALPHAN 值增加 1。

5.2. 创建 stable/13 分支

在创建 stable/ 分支时,既需要对新的 stable/ 分支进行更改,也需要对 main 分支进行更改。以下列出的文件是相对于仓库根目录的。要在 Git 中创建新的 stable/13 分支:

注意

确保你在 main 分支中

创建 stable/13 分支后,进行以下编辑:

然后,在现在将成为新主版本的 main 分支中:

6. 从 stable/ 分支发布

本节介绍了 FreeBSD 发布周期的基本流程,从一个已建立的 stable/ 分支开始。

6.1. FreeBSD stable 分支代码冻结准备

在 stable 分支的代码冻结之前,需要更新几个文件,以反映发布周期的正式开始。这些文件都位于 stable 分支的最顶层:

6.2. FreeBSD BETA 构建

注意

在发布周期中,有两个通常的例外情况不需要提交批准。第一个是任何发布工程师需要提交的更改,以便继续发布周期的日常工作流,另一个是发布周期中可能出现的安全修复。

代码冻结生效后,下一次从该分支构建的版本被标记为 BETA1。这通过在 sys/conf/newvers.sh 中将 BRANCH 值从 PRERELEASE 更新为 BETA1 来完成。

完成此操作后,第一组 BETA 构建开始。随后的 BETA 构建只需更新 sys/conf/newvers.sh 中的 BETA 构建编号,不需要更新其他文件。

6.3. 创建 releng/13.0 分支

当第一个 RC(发布候选版)构建准备好开始时,releng/ 分支将被创建。这是一个多步骤的过程,必须按特定顺序进行,以避免例如与 __FreeBSD_version 值重叠等异常情况。以下路径相对于仓库根目录。提交的顺序和需要更改的内容如下:

注意

请确保你处于 stable/13 分支

现在已经存在两个新的 __FreeBSD_version 值,接着更新文档项目仓库中的 ~/documentation/content/en/books/porters-handbook/versions/chapter.adoc 文件。

在第一个 RC 可用后,应向 FreeBSD Bugmeister Team 发送电子邮件,要求将新的 FreeBSD -RELEASE 添加到 bug 跟踪器中下拉菜单的 versions 列表中。

7. 构建 FreeBSD 安装介质

本节介绍了生成 FreeBSD 开发快照和发布版本的基本流程。

7.1. 发布构建脚本

在 FreeBSD 9.0-RELEASE 之前,src/release/Makefile 已更新以支持,并且引入了 src/release/generate-release.sh 脚本,作为自动调用目标的包装器。

在 FreeBSD 9.2-RELEASE 之前,src/release/release.sh 被引入,该脚本基于 src/release/generate-release.sh,并包括支持指定配置文件以覆盖各种选项和环境变量的功能。配置文件的支持使得通过为每次调用指定一个单独的配置文件来支持交叉构建每个架构的发布版本。

以下是使用 src/release/release.sh 构建单个发布版本并放置于 /scratch 的简短示例:

以下是使用 src/release/release.sh 构建单个交叉构建的发布版本并使用不同的目标目录的简短示例,首先创建一个自定义的 release.conf 配置文件,其中包含:

然后使用以下命令调用 src/release/release.sh:

有关更多详细信息和示例用法,请参见 src/release/release.conf.sample。

7.2. 构建 FreeBSD 发布版本

在准备发布构建时,需要更新以下几个文件:

在构建完成最终的 RELEASE 后,releng/13.0 分支会被标记为 release/13.0.0,使用构建 RELEASE 时的修订版本。与创建 stable/13 和 releng/13.0 分支类似,使用 git tag 来完成此操作。从仓库根目录执行:

注意

确保你处于 releng/13.0 分支

8. 将 FreeBSD 安装介质发布到项目镜像

本节介绍了将 FreeBSD 开发快照和发布版本发布到项目镜像的流程。

8.1. 临时存储 FreeBSD 安装介质镜像

临时存储 FreeBSD 快照和发布版本是一个两步过程:

  • 创建目录结构以匹配 ftp-master 上的层次结构 如果在构建配置文件中定义了 EVERYTHINGISFINE,例如在上面提到的构建脚本的 main.conf 中,这将在构建完成后自动发生,创建一个路径结构匹配 ftp-master 上预期的目录结构,存放在 ${DESTDIR}/R/ftp-stage。这相当于在该目录下运行:

    在每个架构构建完成后,thermite.sh 将通过 rsync 将构建中的 ${DESTDIR}/R/ftp-stage 从构建主机同步到 /snap/ftp/snapshots 或 /snap/ftp/releases。

  • 在将文件移至 pub/ 以开始传播到项目镜像之前,将文件复制到 ftp-master 上的临时存储目录 所有构建完成以后,/snap/ftp/snapshots 或发布版本的 /snap/ftp/releases 将通过 rsync 被 ftp-master 拉取到 /archive/tmp/snapshots 或 /archive/tmp/releases。

    注意

    在 FreeBSD 项目基础设施中的 ftp-master 上,这一步需要 root 级别的访问权限,因为此步骤必须以 archive 用户身份执行。

    8.2. 发布 FreeBSD 安装介质

镜像在 /archive/tmp/ 中暂存后,它们就准备好通过将其放入 /archive/pub/FreeBSD 来公开发布。为了减少传播时间,使用硬链接将 /archive/tmp 中的文件链接到 /archive/pub/FreeBSD。

注意

为了有效执行此操作,/archive/tmp 和 /archive/pub 必须位于同一逻辑文件系统上。

然而有一个注意事项,必须在此后使用 rsync 来修正 pub/FreeBSD/snapshots/ISO-IMAGES 中的符号链接,这将被硬链接替换,从而增加传播时间。

注意

与暂存步骤一样,这需要 root 级别的访问权限,因为此步骤必须以 archive 用户身份执行。

作为 archive 用户执行:

根据需要,将 snapshots 替换为 releases。

9. 发布周期的收尾工作

本节介绍了发布后的常规任务。

9.1. 发布后勘误通知

随着发布周期的结束,通常会有几个勘误通知(Errata Notice)候选项,以解决在周期末期发现的问题。在发布之后,FreeBSD 发布工程团队和 FreeBSD 安全团队会重新审视在最终发布前未批准的更改,根据问题的范围,可能会发布勘误通知。

注意

实际发布勘误通知的过程由 FreeBSD 安全团队处理。

完成的勘误通知模板应通过电子邮件与针对 releng/ 分支的补丁或来自 stable/ 分支的修订列表一起发送。

9.2. 交接到 FreeBSD 安全团队

在发布后的大约两周,发布工程师更新 Git 仓库,将 releng/13.0 分支的审批人从发布工程团队更改为安全官。

10. 发布生命周期结束

本节描述当发布版本达到生命周期结束(EoL)时,需要更新的与网站相关的文件。

10.1. 生命周期结束的 Website 更新

当一个发布版本达到生命周期结束时,应该在网站上删除和/或更新对该发布版本的引用:

FreeBSD 邮件列表常见问题

摘要

1. 介绍

作为常见问题解答(FAQ)的惯例,本文件旨在涵盖关于 FreeBSD 邮件列表的最常见问题(并当然提供答案!)。尽管最初目的是减少带宽消耗,避免重复询问相同的问题,但 FAQ 已成为有价值的信息资源。

1.1. FreeBSD 邮件列表的目的是什么?

FreeBSD 邮件列表作为 FreeBSD 社区的主要沟通渠道,涵盖了许多不同的主题领域和社区兴趣。

1.2. FreeBSD 邮件列表的受众是谁?

邮件列表是使用英语的,除非另有说明。

1.3. FreeBSD 邮件列表是否对任何人开放参与?

同样,这取决于每个邮件列表的章程。请在发帖前阅读邮件列表的章程,并在发帖时遵守它。这样可以帮助每个人更好地使用邮件列表。

如果在阅读上述列表后,你仍然不知道该发帖到哪个邮件列表,你可能会希望将问题发到 freebsd-questions(但请先查看下面的内容)。

请注意,你必须先订阅邮件列表才能发帖。你可以选择只订阅而不接收邮件列表中的信息。

1.4. 如何订阅?

1.5. 如何退订?

你可以使用上面的相同界面;或者,你可以按照每个邮件列表消息底部的说明进行操作。

请不要直接向公共邮件列表发送退订邮件。首先,这样做不会达到你的目的;其次,它会激怒现有的订阅者,并且你很可能会受到谴责。这是使用邮件列表时的经典错误;请尽量避免。

1.6. 是否提供邮件归档?

1.7. 是否有邮件列表的摘要格式?

2. 邮件列表礼仪

参与邮件列表,就像参与任何社区一样,需要一个共同的沟通基础。请仅发布适当的内容,并遵循常见的礼仪规则。

2.1. 在发帖前我应该做什么?

询问已经在上述文档中回答的问题通常被认为是不合适的。这并不是因为参与这个项目的志愿者是特别刻薄的人,而是当一个问题被反复回答若干次后,志愿者可能会感到沮丧。尤其是当问题已有现成的答案时。请始终牢记,几乎所有 FreeBSD 的工作都是由志愿者完成的,我们也只是普通人。

2.2. 什么是不合适的发帖?

  • 发帖必须符合邮件列表的章程。

  • 个人攻击是不建议的。作为良好的网络公民,我们应该尽力保持高标准的行为。

  • 垃圾邮件是绝对不允许的。邮件列表会积极处理违反此规则的用户并将其封禁。

2.3. 发帖时应遵循的礼仪

  • 请将行宽限制在 75 个字符,因为并非每个人都使用功能强大的图形邮件阅读程序。

  • 请尊重带宽的有限性。并非每个人都通过高速连接阅读电子邮件,因此,如果你的发帖涉及类似 config.log 的内容或大量的堆栈跟踪,请考虑将这些信息上传到某个网站,然后只提供 URL。请记住,这些帖子将被无限期归档,因此,庞大的帖子会在其目的已过期后依然增加归档的大小。

  • 格式化你的信息以便易于阅读,且请不要大声喊叫!!!!!。不要低估格式不当的邮件消息的影响,这不仅仅是对 FreeBSD 邮件列表的影响。你的邮件消息是别人对你的唯一印象,如果它格式差、拼写错误、满是错误/有大量的感叹号,别人会对你留下不好的印象。

    • exmh

    • Microsoft® Exchange

    • Microsoft® Outlook® 尽量避免使用 MIME(译者注:如多媒体内容):许多人使用的邮件客户端不兼容 MIME。

  • 确保你的时间和时区设置正确。这可能看起来有些愚蠢,因为你的邮件最终还是会到达,但很多邮件列表的成员每天会收到几百封邮件。他们经常根据主题和日期对来信进行排序,如果你的邮件没有在第一个回复之前到达,他们可能会认为错过了邮件而不再去查看。

  • 这将信息重定向到 /tmp/dmesg.out 文件中。

2.4. 回复现有邮件时的特殊礼仪

  • 请包含原始邮件中的相关文本。将其修剪到最小,但不要过度。这样,即使有人没有阅读原始邮件,也应该能够理解你在说什么。 这对类型为“是的,我也遇到这个问题”的帖子尤其重要,其中最初的邮件可能有几十行或几百行。

  • 使用某种方式标明原始消息的文本和你添加的文本。一个常见的约定是在原始消息前加上“>”。在“>”后留空白并在你的文本与原文之间留空行,都能使结果更易于阅读。

  • 请确保你引用的文本归属正确。如果你将别人没有写过的话归到他们名下,可能会引起他们的不满。

  • 请不要使用“顶部回复”。我们指的是,如果你回复一封邮件,请将你的回复放在复制的文本之后。

    • A:因为这会反转对话的逻辑流程。

    • Q:为什么顶部回复不受欢迎? (感谢 Randy Bush 提供这个笑话。)

3. 邮件列表中的常见话题

参与邮件列表,就像参与任何社区一样,需要有共同的沟通基础。许多邮件列表都假设参与者对项目的历史有一定了解。特别是,对于新加入社区的人来说,有一些话题似乎经常出现。每个发帖者都有责任确保自己的发帖不会落入这些类别。通过这样做,你不仅能帮助邮件列表保持主题相关性,还可能避免在过程中被批评。

避免这些话题的最佳方法是熟悉邮件列表归档,这将帮助你了解以前讨论过的内容。在这方面,邮件列表搜索界面非常有用。(如果该方法没有产生有用的结果,你可以用你喜欢的搜索引擎进行补充搜索)。

通过熟悉归档,你不仅能了解之前讨论过的话题,还能知道讨论是如何进行的、参与者是谁以及目标受众是谁。在你向任何邮件列表发帖之前,这些都是需要了解的重要内容,不仅仅是 FreeBSD 邮件列表。

毫无疑问,归档非常广泛,有些问题比其他问题更频繁地出现,有时作为后续讨论,其中的主题行不再准确地反映新的内容。然而,避免这些常见话题的责任在于你,发帖者,做足功课有助于避免这些问题。

4. 什么是“自行车棚”(Bikeshed)?

字面上,“自行车棚”是一个小型户外棚屋,人们可以将自己的两轮交通工具存放在其中。然而,在 FreeBSD 的术语中,这个词指的是那些足够简单的主题,几乎每个人都可以发表意见,并且通常几乎每个人都会发表意见。这个术语的来源在这篇文档中有更详细的解释。在你向任何 FreeBSD 邮件列表发帖之前,你必须具备一定的理解。

更广泛地说,自行车棚是指那些会迅速引发元讨论和争论的话题,如果你没有了解其历史,这些话题很容易引起争议。

请在可能的情况下避免讨论自行车棚,以帮助我们保持邮件列表对尽可能多的人有用。谢谢。

5. 致谢

Mark Linimon:该 FAQ 的初稿作者。

freebsd organization
FreeBSD 分支
A 图
fig2
fig3
fig4

原文地址:

几乎所有的 FreeBSD 开发者都有一个或多个代码库的提交权限。然而,也有一些开发者没有权限,文中某些内容同样适用于他们。(例如,有些人只有权限操作问题报告数据库。)有关更多信息,请参见。

,仅支持协议 2

ref*.FreeBSD.org,universe*.freeBSD.org(请参见 )

smtp.FreeBSD.org:587(请参见 )

连接到项目主机需要使用 。有关更多信息,请参见 。

FreeBSD 项目使用符合 OpenPGP(Pretty Good Privacy)标准的加密密钥来验证提交者的身份。携带重要信息(如公钥)的消息可以使用 OpenPGP 密钥进行签名,以证明它们确实来自提交者。有关更多信息,请参见 和 。

安装 security/gnupg。在 ~/.gnupg/gpg.conf 中输入以下内容,以设置签名和新密钥的最低默认首选项(有关更多详细信息,请参见 ):

输入电子邮件地址后,会请求设置密码短语。创建安全密码短语的方法存在争议。为了避免建议一种单一的方式,以下是一些描述不同方法的链接:,,,。

保护好私钥和密码短语。如果私钥或密码短语可能已被泄露或披露,请立即通知 并撤销密钥。

某些类型的修复被文档工程团队()授予了“普遍批准”,允许所有提交者修复这些类别的问题,而不需要文档提交者的批准或审查。

更改 documentation/content/en/books/porters-handbook/versions/_index.adoc,主要用于 src 提交者。

更改 doc/shared/contrib-additional.adoc 维护。

所有 ,文档相关。

安全通告;错误通告;发布; 由安全官团队 [] 和发布工程团队 [] 使用。

更改 website/content/en/donations/donors.adoc 用于捐赠联络办公室 []。

在提交前,必须进行构建测试;有关更多信息,请参见 中的“概述”和“FreeBSD 文档构建过程”部分。

当搜索 “Git Primer” 时,会出现很多优秀的资源。Daniel Miessler 的 和 Willie Willus 的 都是很好的概述。Git 书籍虽然更长一些,但也非常完整:。如果你需要关于 Git 的常见问题和陷阱的指导,可以参考这个网站 。最后,一篇面向计算机科学家的 也对一些人解释 Git 的世界观有所帮助。

本节的目标是突出 Git 中跟踪源代码所需的功能。假设你对 Git 有基本了解。互联网上有许多 Git 入门资料,但 提供了较为全面的介绍。

上显示:

在 分支上标记

在 分支上标记 。

请参见 获取最新的 FreeBSD 源代码获取地址。$URL 可以从该页面获取。

如果你想构建自定义内核,FreeBSD 手册中的 建议在 sys/${ARCH}/conf 下创建一个文件 MYKERNEL,针对 GENERIC 进行修改。为了让 Git 忽略 MYKERNEL,可以将它添加到 .git/info/exclude。

Git 通过强大的 git bisect 命令使得二分查找变得容易。以下是如何使用它的简要概述。你可以查看 或 获取更多详细信息。git bisect 手册页面非常适合描述可能出错的情况,如何处理无法构建的版本,何时使用除 'good' 和 'bad' 之外的术语等,这里不再涉及。

关于签名提交和标签的更详细文档可以在 章节中找到。

签名推送的原因可以在 中找到。

用于浏览器的 cgit 仓库 Web 界面位于 。生产 Git 仓库位于 和 ssh://anongit@git.FreeBSD.org/ports.git(或 :ports.git)。

GitHub 上也有一个镜像,查看 以了解概况。最新分支是 main。季度分支命名为 yyyyQn,其中 yyyy 为年份,n 为季度。

在 ports 仓库中,有一个钩子帮助你编写提交信息,位于 。你可以通过运行 git config --add core.hooksPath .hooks 来启用它。

保留本地更改(尤其是微小更改)最简单的方法是使用 git stash。在最简单的形式下,你可以使用 git stash 来记录更改(这会将它们推送到暂存栈)。大多数人使用它来在更新树之前保存更改。然后,他们使用 git stash apply 将它们重新应用到树上。stash 是一个更改栈,可以通过 git stash list 查看。更多细节可以参考 git-stash 手册页 ()。

有关此主题的更多信息,参考 ,它提供了相当全面的介绍。对于偶尔出现但本指南中未涉及的问题,它是一个很好的资源。

如上所示,我使用了 -m 选项以简化操作,但实际上你应该创建一个解释什么是 Glorb,以及为什么要使用 Nitz 来获得它的提交消息。并非每个人都会知道,因此在实际提交时,你应该遵循 部分,而不是模仿这里使用的简短风格。

提交消息看起来。应该包含自上次向 FreeBSD main 分支合并以来的更改总结以及任何警告。

这将弹出一个交互式屏幕,你可以在其中更改默认设置。现在,直接退出编辑器即可。一切应该顺利应用。如果没有,你需要解决冲突。可以帮助你解决这个问题。

你需要确保已获取备注(有关详细信息,请参阅 )。获取这些备注后,它们将在 git log 命令中显示,如下所示:

这里有一篇 ,可以更深入地探讨此话题。

在开始之前,确保你的本地 Git 仓库是最新的,并且设置了正确的远程源 。

第一步是按照这些 在 GitHub 上创建一个 的 fork。这个 fork 的目标应该是你的个人 GitHub 账户(比如我的是 gvnn3)。

现在,你可以创建一个分支 。

开始之前,确保本地 Git 仓库是最新的,并且正确设置了源仓库 。另外,请确保本地仓库有以下远程仓库设置:

项目已经迁移到 。

更新开发者和贡献者列表doc/shared/contrib-committers.adoc - 添加一条记录,这样就能在 的“开发者”部分中看到它。条目按姓氏排序。

添加 PGP 密钥 Dag-Erling Smørgrav des@FreeBSD.org 已编写一个脚本 (doc/documentation/tools/addkey.sh) 使得此过程更简单。有关更多信息,请参见 文件。

确保在仓库中有一个当前的 PGP/GnuPG 密钥非常重要。这个密钥可能会被用来进行提交者的身份验证。例如,FreeBSD Administrators <admins@FreeBSD.org> 可能需要它来恢复账户。完整的 FreeBSD.org 用户密钥环可以从 下载。

生成 Kerberos 密码 请参阅 ,为使用 FreeBSD 集群的其他服务(如 )生成或设置 Kerberos 账户(在此步骤中,你将获得 bug-tracking 账户)。

可选:启用 Wiki 账户 账户 - Wiki 账户允许分享项目和想法。尚未拥有账户的人可以按照 上的说明获取账户。如果需要帮助,可以联系 。

可选:更新 Wiki 信息 Wiki 信息 - 获得 Wiki 访问权限后,有些人会在 、、 或 页面上添加条目。

可选:避免重复邮件 订阅 、 或 的用户,可能希望取消订阅,以避免接收到提交消息和后续邮件的重复副本。

对于身份验证,你可以使用 FreeBSD Kerberos 用户名和密码(请参见 )。首选使用 你的用户名/mail 主体,因为它仅用于验证邮件资源。

当导师认为学员已经掌握了基础,并准备独立提交时,导师会在 mentors 文件中发布提交。这一文件位于每个代码库的 admin 孤立分支中。有关如何访问这些分支的详细信息,请参见 。

提交的其他作者的姓名和电子邮件地址。GitHub 对 Co-authored-by 说明有详细描述,详情见 。

该 ID 认证符合 的要求。

FreeBSD 项目的完整许可证政策可以在 找到。本节的其余部分旨在帮助你入门。作为一条规则,若有疑问,请询问。提供建议比修复源代码树更容易。

FreeBSD 项目不鼓励使用全新的许可证或标准许可证的变体。新许可证需要得到 的批准才能存入 src 仓库。树中使用的许可证越多,给那些希望利用这些代码的人带来的问题就越多,通常是由于不清晰的许可证条款导致的意外后果。

FreeBSD 项目的仓库中存在一些软件或数据,已授予特殊许可证以便我们使用。一个例子是 Terminus 字体,用于 。在这里,作者 Dimitar Zhekov 允许我们使用 Terminus BSD Console 字体,并为其提供了一个 2 条款 BSD 许可证,而不是他通常使用的开放字体许可证。

显然,记录所有此类许可证授权是明智的。为此, 决定保持它们的档案。每当 FreeBSD 项目被授予特殊许可证时,我们要求通知 。任何参与安排此类许可证授权的开发者,请将以下详细信息发送给 :

待 确认所有必要的详细信息已收集并正确无误,秘书将发送一封包含许可证详细信息的 PGP 签名确认函。此确认函将永久存档,并作为我们永久的许可证授予记录。

许可证档案应仅包含许可证授权的详细信息;此处不应讨论任何关于许可证或其他主题的内容。访问许可证档案中的数据将根据请求提供给 。

该项目在我们的源代码库中使用 标签。目前,这些标签旨在帮助自动化工具机械地重建许可证要求。树中所有 SPDX-License-Identifier 标签应视为信息性标签。FreeBSD 源代码树中所有带有这些标签的文件也包含治理该文件使用的许可证副本。如果出现不一致,以原文许可证为准。该项目力图遵循 。如何标记源文件和有效的代数表达式,见 和 。该项目从 SPDX 的有效 中抽取标识符。该项目仅使用 SPDX-License-Identifier 标签。

使用 FreeBSD.org 账户登录,并在新开 bug 中发表评论以确认所有权。有关如何为 FreeBSD.org 账户生成或设置密码的更多信息,请参见 。

FreeBSD 项目使用 来处理代码审查请求。详细信息请参见 。

使用你的 FreeBSD.org 账户,在我们的 bug 跟踪系统中打开一个新的 bug,更多信息请参见 。选择 Services 作为产品,选择 Code Review 作为组件。在 bug 描述中请求将你的 Phabricator 账户重命名,并提供你的 Phabricator 用户链接。例如,https://reviews.freebsd.org/p/bob_example.com/

Documentation Engineering Team (doceng@FreeBSD.org):doceng 团队负责文档构建基础设施,批准新的文档提交者,并确保 FreeBSD 网站和 FTP 站点上的文档与 Subversion 树保持同步。该团队不处理冲突解决问题。绝大多数与文档相关的讨论发生在 上。关于 doceng 团队的更多信息,请参见其 。希望参与文档工作的提交者应熟悉 。

Gordon Tetlow (gordon@FreeBSD.org):Gordon Tetlow 是 ,负责管理 Security Officer Team (security-officer@FreeBSD.org)。

如果你不希望每次使用 时都输入密码,并且你使用密钥进行身份验证, 可以为你提供便利。如果你想使用 ,请确保在运行其他应用程序之前先启动它。例如,X 用户通常会在他们的 .xsession 或 .xinitrc 中执行此操作。有关详细信息,请参阅 。

使用 生成密钥对。密钥对将保存在你的 $HOME/.ssh/ 目录中。

现在,你可以使用 进行一次会话的身份验证。它会提示输入私钥的密码短语,然后将其存储在身份验证代理()中。使用 ssh-add -d 可以从代理中移除存储的密钥。

有关更多信息,请参阅 、、、、 和 。

有关添加、修改或删除 密钥的信息,请参阅 。

所有 FreeBSD 开发者都可以访问 FreeBSD 项目软件的 Coverity 分析结果。所有有兴趣获取自动化 Coverity 运行的分析结果的人,可以在 注册。

FreeBSD 维基包含了一个小指南,供有兴趣使用 Coverity® 分析报告的开发者参考:。请注意,此小指南仅供 FreeBSD 开发者阅读,因此如果你无法访问此页面,你需要请求某人将你添加到适当的 Wiki 访问列表中。

所有参与 FreeBSD 项目的人员都应遵守 行为规范,可以从 获取该规范。作为提交者,你是项目的公众面貌,你的行为对公众对该项目的看法有着至关重要的影响。本指南扩展了 行为规范 中与提交者相关的部分。

请参考中的要点,并同样适用于贡献者。

尊重现有的维护者(如果已列出)。 FreeBSD 的许多部分并不是“由某个特定的个人拥有”,换句话说,没人会跳出来大喊“不要改动我的区域”,但在某些情况下,最好先确认一下。我们使用的一种惯例是在 Makefile 中为任何正在积极维护的包或子树列出维护者;请参阅中的相关文档。如果某些代码段有多个维护者,一个维护者对相关区域的提交需要至少由另一个维护者进行审查。在不清楚“维护者身份”的情况下,可以查看相应文件的仓库日志,看看是否有某个人最近在该区域进行过大量工作。

在提交之前测试你的更改。 如果你的更改涉及内核,确保你能编译 GENERIC 和 LINT。如果你的更改涉及其他地方,确保你仍然能够进行 make world。如果你的更改涉及某个分支,确保在运行该代码的机器上进行测试。如果你的更改可能影响到其他架构,务必在所有受支持的架构上进行测试。请确保你的更改适用于。请参阅获取可用资源的列表。随着其他架构被添加到 FreeBSD 支持的平台列表中,适当的共享测试资源也将提供。

如果某个软件没有维护者,建议你承担起责任。如果不确定当前的维护者是谁,可以发送邮件至 进行询问。

在任何时候,FreeBSD 项目也支持一个或多个树外编译器。目前,这个编译器是 GCC 12.x。理想情况下,提交者应使用该编译器进行编译测试,尤其是对于大规模或风险较高的更改。此编译器作为 ${TARGET_ARCH}-gcc${VERSION} 包提供,例如 或 。该项目运行自动化 CI 任务,以使用这些编译器构建所有内容。提交者应修复他们的更改破坏的 CI 任务。提交者可以在必要时使用 CROSS_TOOLCHAIN=aarch64-gcc12 或 CROSS_TOOLCHAIN=llvm15 进行测试。

在提交文档更改时,在提交之前使用拼写检查工具。对于所有 XML 文档,通过运行 make lint 和 验证格式化指令是否正确。

对于手册页,运行 和 来验证所有交叉引用和文件引用是否正确,并且手册页是否安装了所有适当的 MLINKS。

将 Port 添加到树中相对简单。Port 准备好添加后,如后文所述,你需要在该类别的 Makefile 中添加 Port 的目录条目。在该 Makefile 中, Port 按字母顺序列出,并添加到 SUBDIR 变量中,如下所示:

不要忘记,用于验证类别的 Makefile。

中的测试章节包含更详细的说明。请参阅 和 部分。

如果 Port 来自于一个之前没有为项目做过贡献的提交者,将该人的名字添加到 部分的 FreeBSD 贡献者列表中。

如果由于某些原因无法使用 来测试新 Port,则最基本的测试包括以下步骤:# make install

使用 Git 时,考虑使用 ,它比 grep -r 更快速。

另外,你也可以使用 ports/Tools/scripts 中的 rmport 脚本。此脚本由 Vasil Dimov 编写 []。如有关于此脚本的问题,请将问题发送到 ,并请抄送当前维护者 Chris Rees []。

对 Ports 进行彻底检查,确保没有其他 Port 依赖于旧的 Port 位置/名称,并更新它们。仅运行 grep 在 INDEX 上是不够的,因为一些 Port 的依赖项是通过编译时选项启用的。建议对 Ports 执行完整的 。

有关如何将提交合并到季度分支的更多信息,请参阅 。

请参阅 以了解在 Porter’s Handbook 中的详细步骤。完成该程序并将 PR 分配给 Port 管理团队 [] 后,是否批准该请求由他们决定。如果他们批准了,责任在于:

如果你想更彻底,可以在此时运行 。

检查 PKGORIGIN 是否正确。 Port 系统使用每个 Port 的 CATEGORIES 条目来创建其 PKGORIGIN,该值用于将已安装的包连接到它们构建时使用的 Port 目录。如果此条目错误,常用的 Port 工具,如 和 ,将无法正常工作。 要检查这一点,请使用 chkorigin.sh 工具:env PORTSDIR=/path/to/ports sh -e /path/to/ports/Tools/scripts/chkorigin.sh。这将检查 Port 树中的每个 Port ,甚至是那些尚未连接到构建的 Port ,因此你可以在迁移操作后直接运行它。提示:别忘了检查你刚刚迁移的 Port 的从属 Port 的 PKGORIGIN!

在 Porter’s Handbook 中更新 。

在 Porter’s Handbook 中更新 。

以下情况除外:由 Ports 管理团队 <> 或安全官员团队 <> 维护的任何内容。永远不允许未经授权的提交对这些团队管理的 Port 进行更改。

所有包构建的报告(官方的、实验性的和非回归的)都汇总在 。

任何直接位于 ports/ 下的文件,或者位于以大写字母开头的子目录中的文件(如 Mk/、Tools/ 等)。特别是, Port 管理团队 [] 非常在意 ports/Mk/bsd.port.mk* 文件,因此除非你愿意面对他们的怒火,否则不要对这些文件进行提交更改。

访问 。

Port 管理团队 [] 会回复可能的 fallout。

people.FreeBSD.org 就是 freefall.FreeBSD.org。只需创建一个 public_html 目录。你放入该目录中的任何内容将自动显示在 下。

请参阅内部页面上的 文档。

FreeBSD 提交者可以在会议上从 获得免费的 4-CD 或 DVD 套件。

提供网站托管、云计算、域名注册和 X.509 证书服务。

Gandi 向所有 FreeBSD 开发者提供 E-rate 折扣。为了简化获取折扣的过程,首先设置 Gandi 账户,填写账单信息并选择货币。然后,使用你的 @freebsd.org 邮件地址发送邮件至 ,并注明你的 Gandi 账户。

提供针对 UNIX 用户优化的云存储服务,适用于外部备份。其服务完全在 FreeBSD 和 ZFS 上运行。

rsync.net 向 FreeBSD 开发者提供免费的 500 GB 永久账户。只需使用你的 @freebsd.org 邮件地址在 上注册,即可获得此免费账户。

原文:

X Window 系统,负责图形显示。 大多数 BSD 版本中使用的 X Window 系统由 维护。FreeBSD 允许用户从多种桌面环境中选择,如 Gnome、KDE 或 Xfce;以及轻量级窗口管理器,如 Openbox、Fluxbox 或 Awesome。

BSD 磁带包含 AT&T 的源代码,因此需要 UNIX® 源代码许可。到 1990 年,CSRG 的资金已经用尽,面临关闭的局面。该小组的一些成员决定发布 BSD 代码,这些代码是开源的,但去除了 AT&T 的专有代码。这最终发生在 网络磁带 2,通常被称为 Net/2。Net/2 并不是一个完整的操作系统:约 20% 的内核代码缺失。CSRG 的一位成员 William F. Jolitz 编写了剩余的代码,并在 1992 年初发布了 386BSD。与此同时,另一些前 CSRG 成员组成了一个商业公司 ,并发布了基于相同源代码的操作系统 ,该操作系统后被更名为 BSD/OS。

386BSD 从未成为一个稳定的操作系统。相反,在 1993 年,两个项目从其分裂出来: 和 。这两个项目最初因对 386BSD 改进的耐心不同而分道扬镳:NetBSD 的人们早在年初就开始了,而 FreeBSD 的第一个版本直到年底才准备好。与此同时,代码库已经足够分化,难以合并。此外,两个项目有不同的目标,正如我们将在下面看到的。1996 年, 从 NetBSD 分裂出来,2003 年, 从 FreeBSD 分裂出来。

1992 年,AT&T 起诉了 ,即 BSD/386 的供应商,指控该产品包含 AT&T 版权代码。此案于 1994 年庭外和解,但诉讼的阴影依然困扰着人们。2000 年 3 月,网上发布了一篇文章,声称该案件“最近已经解决”。 该诉讼澄清的一个细节是命名问题:在 1980 年代,BSD 被称为“BSD UNIX®”。随着最后一部分 AT&T 代码的从 BSD 中去除,它也失去了使用 UNIX® 名称的权利。因此,你会在书名中看到对“4.3BSD UNIX® 操作系统”和“4.4BSD 操作系统”的提及。

FreeBSD 旨在为最终用户提供高性能和易用性,并且是网站内容提供商的首选。它支持 ,并且拥有比其他项目更多的用户。

是 Apple® Mac® 系列计算机的最新版本操作系统。该操作系统的 BSD 核心,,是一个完全功能的开源操作系统,支持 x86 和 PPC 计算机。然而,Mac OS® X 的 Aqua/Quartz 图形系统和许多其他专有部分仍然是闭源的。几位 Darwin 开发者也是 FreeBSD 的提交者,反之亦然。

Linux 是根据 (GPL)发布的,该许可证旨在消除闭源软件。特别是,任何基于 GPL 发布的产品的衍生作品,如果有人要求,必须提供源代码。与此不同, 的限制较少:可仅发布二进制版本。这对于嵌入式应用程序特别有吸引力。

提供 FreeBSD 的支持合同。

此外,每个项目都有一份可聘请的顾问名单:、 和 。

原文:

实际切换模式的命令是 :

各种基于屏幕的程序,如 ,必须能够确定当前的屏幕尺寸。由于这是通过 ioctl 调用到控制台驱动程序(如 )来实现的,因此它们将正确地确定新的屏幕尺寸。

参考资料:,。

每个新字体都需要创建一个新的名称。如果你有字体文档中附带的信息,可以将其作为创建名称的基础。如果没有相关信息,可以通过使用 命令查看字体文件来获取一些线索。例如:

Weight(粗细) 如正常、粗体、中等、半粗体等。从上面使用 命令输出来看,这个字体的粗细是 medium。

作为名称开始,然后使用 查看字体并根据字体的外观调整名称。

参考资料:,,《The X Windows System in a Nutshell》,。

参考资料:/usr/src/gnu/usr.bin/groff/afmtodit/afmtodit.man, , , .

目前可通过 下载。注意:这些文件是 PostScript 程序,必须通过按住 Shift 键并点击链接来下载,否则浏览器可能会尝试启动 Ghostview 来查看它们。

其中,file.afm 是上面使用 ttf2pf.ps 创建的 AFM_name 文件,PS_font_name 是该命令使用的字体名称,也是 用于引用此字体的名称。例如,假设你使用了第一个 ttf2pf.ps,那么可以使用以下命令创建 3of9 Barcode 字体:

注意,如果 ttf2pf.ps 使用 TrueType 字体文件中的字体名称来指定字体名称,并且你希望使用不同的名称,必须在运行 afmtodit 之前编辑 .afm 文件。此名称还必须与 Fontmap 文件中的名称匹配,如果你希望将 输入管道传递给 。

这种情况可能很快会有所改变。 正在开发一套有用的 FreeType 工具:

xfsft 字体服务器可为 X11 提供 TrueType 字体服务,除了常规字体外。虽然目前仍处于测试阶段,但据说它非常实用。更多信息请见 。FreeBSD 的移植说明可以在 找到。

xfstt 是另一个 X11 字体服务器,可以在 获取。

一个名为 ttf2bdf 的程序可以从 TrueType 字体文件生成适合 X 环境使用的 BDF 文件。据说 Linux 的二进制文件可以从 获取。

原文:

/etc/rc.d/var 会将 /var 挂载为内存文件系统,并使用 命令创建 /var 下的配置目录,并更改某些目录的权限。在执行 /etc/rc.d/var 时,另一个与之相关的 rc.conf 变量 varsize 会发挥作用。/var 分区会根据此变量的值在 rc.conf 中由 /etc/rc.d/var 创建:

/var 是一个可读写的文件系统,这一点非常重要,因为 / 分区(以及你可能在闪存介质上创建的其他分区)应该挂载为只读。请记住,在 部分,我们详细介绍了闪存的限制,尤其是写入次数的限制。不能强调的是,不应该将闪存上的文件系统挂载为读写模式,同时应避免使用交换文件。因为在一个繁忙的系统中,交换文件可能会在不到一年内把闪存烧坏。大量的日志记录或临时文件的创建和销毁也会造成类似的影响。因此,除了从 /etc/fstab 中移除 swap 条目外,还应将每个文件系统的选项字段更改为 ro,如下所示:

由于这个更改,系统中的一些应用程序将立即出现故障。例如,由于缺少 cron 表,cron 将无法正常运行,syslog 和 dhcp 等程序也会遇到问题,因为 /var 中缺少 /etc/rc.d/var 创建的项。这些问题仅是暂时的,在 中,我们会介绍如何解决这些问题,并让其他常见软件包正常运行。

在 中,指出了由 /etc/rc.d/var 构建的 /var 文件系统以及只读根文件系统的存在,会导致许多与 FreeBSD 一起使用的常见软件包出现问题。在本文中,将提供一些成功运行 cron、syslog、 Port 安装和 Apache web 服务器的建议。

在讨论成功使用 Port 树所需的更改之前,必须提醒你有关闪存媒体上文件系统只读性质的事项。由于它们是只读的,你需要使用 中显示的挂载语法暂时将其挂载为读写。在完成任何维护后,你应始终将这些文件系统重新挂载为只读——不必要的写入闪存媒体可能会大大缩短其使用寿命。

原文:

FreeBSD 项目不建议使用全新的许可证和标准许可证的变种。新许可证需要得到 的批准才能存放在主存储库中。过去,非标准许可证比标准许可证产生了更多问题。非标准许可证的草拟不当通常会导致更多意外后果,因此这些许可证不太可能得到 的批准。FreeBSD 项目正在标准化使用由 SPDX 发布的 BSD-2-Clause 许可证。

开发者需要注意,在开源软件中,正确处理“开放”与正确处理“源代码”同样重要。不当处理知识产权会带来严重后果。任何问题或疑虑应立即报告给 。

以下部分详细概述了项目的软件许可证政策。在大多数情况下,我们希望开发者阅读、理解并利用本节以上的部分,针对自己的贡献应用适当的许可证。文档的其余部分详细阐述了政策的哲学背景以及政策的详细内容。如有疑问或需要帮助,请联系 。

历史上,“All Rights Reserved.”(保留所有权利)这个短语曾出现在所有版权声明中。所有 BSD 版本都包含了该短语,以遵守 (美洲地区)。随着尼加拉瓜在 2000 年批准了 ,布宜诺斯艾利斯公约和该短语变得过时。因此,FreeBSD 项目建议新代码中省略该短语,并建议现有的版权持有人移除此短语。FreeBSD 项目在 2018 年更新了模板,移除了这一短语。

为了尽可能符合 ,所有许可证文件将存储在仓库的 LICENSES/ 目录下。该顶级目录下有三个子目录。LICENSES/text/ 子目录包含以分离形式存储的所有 FreeBSD Ports 中允许的许可证文本。这些文件以 SPDX-License-Identifier 名称后跟 .txt 文件格式存储。LICENSES/exceptions/ 子目录包含 FreeBSD Ports 中允许的所有异常的文本,这些文件以异常标识符名称后跟 .txt 格式存储。LICENSES/other/ 包含以分离形式存储的许可证文件,引用了 SPDX-License-Identifier 表达式,但在其他情况下不允许作为分离许可证。这些文件必须至少出现在 FreeBSD Ports 中,并应在引用它们的最后一个文件被移除时删除。没有合适 SPDX 匹配许可证的许可证必须放在 LICENSES/other/ 中,文件名以 LicenseRef- 开头,后跟唯一的 idstring。目前还没有标识出这样的文件,但如果有,将会在此列出。

许可证是贡献者与软件用户之间的法律文件,授权用户在遵守许可证中规定的条款和条件的前提下使用软件的版权部分。许可证在 FreeBSD Ports 中可以通过两种方式表示。许可证可以在文件中明确表示。当文件中明确授予许可证时,可以根据该许可证使用、复制和修改该文件。许可证也可以通过间接方式表示,其中许可证的文本位于其他地方。为此,项目使用了软件包数据交换(SPDX)许可证标识符,具体如以下小节所述。SPDX 许可证标识符由 Linux 基金会下的 SPDX 工作组管理,并得到了业界各方、工具供应商和法律团队的共同认可。有关详细信息,请参见 以及以下小节,了解 FreeBSD 项目如何使用它们。

FreeBSD Ports 中的某些文件包含版权声明、SPDX-License-Identifier 标记和明确的许可证。明确的许可证优先于 SPDX-License-Identifier 标记。SPDX-License-Identifier 标记是项目在自动化工具中用于描述许可证的最佳努力尝试,仅供自动化工具参考。有关如何解释该表达式,请参见 。

树中的一些文件包含分离的许可证。这些文件仅包含版权声明和 SPDX-License-Identifier 表达式,而没有明确的许可证。请参阅 了解如何解释该表达式。注意:项目允许的用于分离许可证的表达式是用于信息目的或由标准定义的表达式的子集。

其中,id 是 或 中 Identifier 列的 SPDX 简短许可证标识符。如果在 LICENSE/ 中没有该文件,则无法将该许可证或例外作为本节下的分离许可证。

SPDX 许可证子表达式 要么是来自 的 SPDX 简短许可证标识符,要么是当 适用时,两个 SPDX 简短许可证标识符通过 "WITH" 组合而成。当多个许可证适用时,表达式由 "AND" 或 "OR" 关键字分隔的子表达式组成,并被括号 "(", ")" 包围。 详细说明了所有细节,当它与本节的简化处理冲突时,以此为准。

由 SPDX 管理。许可证例外只能应用于特定的许可证,如例外中所指定。

原文:

现在,需要构建并安装新内核。你可以在 FreeBSD 手册的 部分找到详细说明。

有两条规则允许 SMTP 和 DNS 流量指向邮件服务器和名称服务器(如果你有这些服务器的话)。显然,整个规则集应该根据个人口味进行调整,这只是一个特定的示例(规则格式在 手册页中有准确描述)。注意,对于“relay”和“ns”规则来说,名称服务查找必须在桥接启用之前完成。这是一个确保你在正确的网络卡上设置 IP 的示例。或者,也可以指定 IP 地址而不是主机名(如果机器没有 IP 地址,则需要这样做)。

原文:

第一次使用 adduser 时,它可能会要求保存一些默认值。如果它建议默认使用 作为默认的 shell,你可能想要改为 。否则只需按 Enter 键接受每个默认值。这些默认值会保存在 /etc/adduser.conf 文件中,这是一个可编辑的文件。

这将使你能够以 jack 身份登录并使用 命令切换到 root。这样,你就不必再因为直接以 root 身份登录而被责骂了。

如果你已经创建了一个用户,并希望该用户能够通过 su 切换为 root,可以以 root 用户身份登录并编辑 /etc/group 文件,将 jack 添加到第一行(即 wheel 组)。但首先,你需要练习 编辑器——或者使用 FreeBSD 新版本中安装的更简单的文本编辑器 。

现在你应该拥有了必要的工具来浏览和编辑文件,能够让系统顺利运行。FreeBSD 手册中有大量的信息(它可能已经存储在你的硬盘上),还有 。CDROM 和网站上也有各种各样的包和 Port 。手册会告诉你如何使用它们(如果存在包,可以使用 pkg add packagename 安装,其中 packagename 是包的文件名)。CDROM 中有包和 Port 的列表,简短的描述位于 cdrom/packages/index、cdrom/packages/index.txt 和 cdrom/ports/index 中,完整的描述位于 /cdrom/ports/*/*/pkg/DESCR,其中 * 代表程序种类和程序名称的子目录。

Annelise Anderson,

原文:

闰秒由 在 中宣布。

标准的闰秒行为介绍见 。也请参考 。

处理闰秒的最简单方法是使用 FreeBSD 默认的 POSIX 时间规则,结合 。当 运行时,且系统时间与正确处理闰秒的上游 NTP 服务器同步,闰秒将导致系统时间自动重复当天的最后一秒。无需其他调整。

如果上游 NTP 服务器没有正确处理闰秒, 会在上游服务器注意到并自行调整后,将系统时间调整一秒。

原文:

提交 ,并将 status 组添加到审阅者列表。你应该将报告放在 doc/website/content/en/status/ 的适当子目录中(如果缺少此子目录,创建它);

通过 提交 pull request 到文档仓库。你应该将报告放在 doc/website/content/en/status 的适当子目录中(如果缺少此子目录,创建它);

发送电子邮件至 ,并附上你的报告。

可以参考 。

所有报告的提交截止日期可以通过 请求延期,延期时间为季度结束后 8 天。由于状态报告和季度 Ports 分支之间的重叠,的条目默认为延期截止。

第一季度
第二季度
第三季度
第四季度

;

3 月份的 (第一季度);

5 月份的 (第二季度);

谷歌编程之夏的和他们的。

发送状态报告征集通知的最简单方法是使用 ,该脚本位于文档 git 仓库的 tools/sendcalls 目录中。该脚本会自动发送通知给所有预定的收件人。也可以通过定时任务来使用,例如:

也可以考虑像在论坛上发布状态报告的调用。

报告的 HTML 版本已经构建并上线后,使用 将网站转储为纯文本,例如:

完全支持 Unicode。-dump 仅输出 HTML 代码的文本渲染,之后可以删除其中的一些元素,而 -cols 确保所有内容都换行到 80 列。

一封发送到 ;

这封邮件必须获得批准,因此,如果你负责发送此邮件,请确保有人批准(如果处理时间过长,请邮件联系 )。

一封发送到 ,并抄送到 、 和 developers@FreeBSD.org。

原文链接:

本文介绍了构建内部 FreeBSD 更新服务器的过程。 由 FreeBSD 安全官员荣誉成员 Colin Percival 编写。对于那些认为从官方更新服务器更新系统较为方便的用户,构建自己的 FreeBSD 更新服务器可以通过支持手动调整的 FreeBSD 版本,或通过提供一个本地镜像来帮助加速多个机器的更新,从而扩展其功能。

本文随后在 上印刷发布。

一个 账户,用于上传分发更新。

一台 Web 服务器,比如 ,其空间要求至少为构建所需空间的一半。例如,7.1 和 7.2 的测试构建总共消耗 4 GB,分发这些更新所需的 Web 服务器空间为 2.6 GB。

基本的 Bourne shell 脚本知识,。

通过安装 和 ,下载 软件,并执行:

① 这是从中下载 ISO 镜像的位置(由 scripts/build.subr 中的 fetchiso() 子程序处理)。配置的地址不限于 FTP URI,任何由标准的 工具支持的 URI 方案都应当能正常工作。可以通过将默认的 build.subr 脚本复制到发布和架构特定的目录(即 scripts/RELEASE/ARCHITECTURE/build.subr)并进行本地更改来安装 fetchiso() 代码的自定义。

③用于上传文件到更新服务器的 SSH 密钥。可以通过输入 ssh-keygen -t dsa 来创建密钥对。此参数是可选的;当未定义 SSHKEY 时,将使用标准的密码身份验证作为备用身份验证方法。有关 SSH 和创建与使用密钥的详细信息,参见 手册。

① 所需发布版的 哈希值会在相应的 中发布。

② 要生成 build.conf 中的“生命周期结束”值,请参考 上发布的“预估 EOL”日期。EOL 的值可以通过使用 工具从网站上列出的日期生成,例如:

在第二次构建周期中,网络时间协议守护进程 会被关闭。根据 FreeBSD 安全官员荣誉成员 Colin Percival 的说法,“freebsd-update-server 构建代码需要识别存储在文件中的时间戳,以便在比较构建时忽略这些时间戳,从而确定哪些文件需要更新。这一时间戳查找过程通过进行两次相隔 400 天的构建,并比较其结果来实现。”

上传的文件需要位于 Web 服务器的文档根目录中,以便更新能够分发。具体的配置将根据使用的 Web 服务器有所不同。对于 Apache Web 服务器,请参考手册中的 部分。

更新客户端的 KeyPrint 和 ServerName 配置项在 /etc/freebsd-update.conf 中,并按照手册中 部分的说明进行更新。

作为参考,附上了整个 运行过程。

每次发布 或 时,都可以构建一个补丁更新。

作为示例,获取 的补丁。阅读公告,并从 获取所需文件。有关如何解读公告的更多信息,可以参考 。

在 中,此公告被称为 SA-09:12.bind。下载文件后,需要将文件重命名为适当的补丁级别。建议与官方 FreeBSD 补丁级别保持一致,但文件名可以自由选择。对于此构建,遵循 FreeBSD 当前的做法,命名为 p7。重命名该文件:

作为参考,已附上整个运行过程的 。

如果使用本地 make release 构建了自定义发布版本,freebsd-update-server 代码将能从你的发布版本中运行。例如,可以通过清除与文档子程序 findextradocs ()、addextradocs () 相关的功能,并分别在 scripts/build.subr 中更改 fetchiso () 的下载位置,来构建一个不包含 Port 和文档的版本。最后一步,在相应的发布和架构下更改 build.conf 中的 哈希值,之后即可开始从自定义发布版本进行构建。

为更新服务器创建一个适当的 SRV 记录,并将其他服务器放置在其后,设置不同的权重。使用此功能可以提供更新镜像,但除非你希望提供冗余服务,否则此提示并非必需。

原文:

本文档假设 FreeBSD 已经安装完毕。有关安装过程的帮助,请参考 FreeBSD 手册中的 章节。

Linux® 用户通常会感到惊讶,因为 FreeBSD 中的默认 shell 不是 Bash。事实上,Bash 并未包含在默认系统中。相反,FreeBSD 使用与 Bourne shell 兼容的 作为默认用户 shell。对于 FreeBSD 13 及之前版本,root 用户的默认 shell 是 ,而对于 FreeBSD 14 及之后版本,则是 。虽然 与 Bash 十分相似,但其功能集要小得多。通常,为 编写的 shell 脚本可以在 Bash 中运行,但反之并不总是成立。

不过,可以通过 FreeBSD 的 来安装 Bash 和其他 shell。

安装了其他 shell 后,可以使用 来更改用户的默认 shell。建议不要更改 root 用户的默认 shell,因为不包含在基础发行版中的 shell 是安装到 /usr/local/bin 目录下的。如果出现问题,可能会导致包含 /usr/local/bin 的文件系统未挂载,在这种情况下,root 用户将无法访问默认 shell,无法登录并修复问题。

可以在 找到所有可用的 Port 和包的完整列表。

有关包的更多信息,请参考 FreeBSD 手册第 5.4 章:。

Ports 有时也称为 Ports 树,可以使用 安装到 /usr/ports。有关安装 Ports 的详细说明,请参见 FreeBSD 手册第 4.5.1 节。

有关更多信息,请参考 章节。

许多 Linux® 发行版使用 SysV init 系统,而 FreeBSD 使用传统的 BSD 风格的 。在 BSD 风格的 下,没有运行级别,并且 /etc/inittab 文件不存在。相反,启动过程由 脚本控制。系统启动时,/etc/rc 会读取 /etc/rc.conf 和 /etc/defaults/rc.conf 来确定要启动哪些服务。指定的服务通过运行位于 /etc/rc.d/ 和 /usr/local/etc/rc.d/ 中的相应服务初始化脚本来启动。这些脚本类似于 Linux® 系统中 /etc/init.d/ 中的脚本。

/etc/rc.d/ 中的脚本用于那些属于“基本”系统的应用程序,如 、 和 。/usr/local/etc/rc.d/ 中的脚本用于用户安装的应用程序,如 Apache 和 Squid。

由于 FreeBSD 被开发为一个完整的操作系统,用户安装的应用程序不被认为是“基础”系统的一部分。用户安装的应用程序通常通过 安装。为了将它们与基础系统分开,用户安装的应用程序会被安装到 /usr/local/ 下。因此,用户安装的二进制文件位于 /usr/local/bin/,配置文件位于 /usr/local/etc/,以此类推。

通过在 /etc/rc.conf 中添加相应的条目来启用服务。系统默认设置位于 /etc/defaults/rc.conf 中,这些默认设置可以通过 /etc/rc.conf 中的设置进行覆盖。有关可用条目的更多信息,请参见 。安装附加应用程序时,请查看应用程序的安装信息,以确定如何启用相关服务。

以下是 /etc/rc.conf 中的条目,启用 ,启用 Apache 2.4,并指定 Apache 应该以 SSL 启动。

与 Linux® 使用通用的 ethX 标识符来识别网络接口不同,FreeBSD 使用驱动程序名称后跟数字的方式来标识网络接口。以下是 输出的两个 Intel® Pro 1000 网络接口(em0 和 em1)的示例:

可以使用 为接口分配 IP 地址。为了在重启后保持配置不变,必须将 IP 配置包含在 /etc/rc.conf 中。以下是 /etc/rc.conf 中的条目,指定了主机名、IP 地址和默认网关:

PF 由 OpenBSD 项目开发,并移植到 FreeBSD。PF 作为 IPFILTER 的替代品创建,语法与 IPFILTER 类似。PF 可以与 配合使用,提供 QoS 特性。

IPFW 是由 FreeBSD 开发和维护的防火墙。它可以与 配合使用,提供流量整形功能,并模拟不同类型的网络连接。

二进制更新类似于使用 yum 或 apt-get 更新 Linux® 系统。在 FreeBSD 中,可以使用 获取新的二进制更新并安装它们。这些更新可以使用 定时。

在使用 定时更新时,请使用 freebsd-update cron 在 中,减少多个机器同时拉取更新的可能性:

有关源代码和二进制更新的更多信息,请参阅 FreeBSD 手册中的 。

在一些 Linux® 发行版中,可以通过查看 /proc/sys/net/ipv4/ip_forward 来确定是否启用了 IP 转发。而在 FreeBSD 中,则使用 来查看此项以及其他系统设置。

Linux® 命令 (Red Hat/Debian)
FreeBSD 等效命令
目的

本文提供了 FreeBSD 的概述。有关这些主题的更深入内容以及本文未涵盖的许多主题,请参考 。

原文:

“黑客”(hacker)一词与入侵他人计算机无关。后者活动的正确术语是“破解者(cracker)”,但大众媒体尚未意识到这一点。FreeBSD 的黑客强烈反对破解安全,且与之毫无关系。有关黑客的更长描述,请参见 Eric Raymond 的

FreeBSD-questions 是个邮件列表,因此你需要邮件访问权限。请将你的 WWW 浏览器指向 。在标题为“Subscribe or unsubscribe online”部分填写“Your email address”字段并点击“Subscribe”。或者发送邮件至 。

请用你的网络浏览器打开 。在标题为“Subscribe or unsubscribe online”部分填写“Your email address”字段并点击“Unsubscribe”。或者发送邮件至 。

此外,还有许多其他 ,它们专门面向更具体的兴趣。上述标准仍然适用,建议你遵循它们,因为这样你更有可能获得好的结果。

阅读手册页和 FreeBSD 文档(无论是安装在 /usr/doc 中,还是可以通过 WWW 访问 ),特别是 和 。

浏览和/或搜索邮件列表的归档,查看你的问题是否已被问过(并且可能已被回答)。你可以在 和 分别浏览和/或搜索邮件列表归档。

使用 或 等搜索引擎查找你问题的答案。

许多格式不当的邮件来自 。以下是一些已知会发送格式不正确邮件而你无法察觉的邮件客户端:

对于任何可能与硬件有关的问题,告诉我们你的硬件配置。如果你不确定,最好假设是硬件问题。你使用的是什么 CPU?多快?什么主板?多少内存?什么外设? 这里需要一些判断,但 命令的输出通常非常有用,因为它不仅告诉你正在使用的硬件,还能告诉你 FreeBSD 的版本。

你需要提供的许多信息是程序的输出,例如 命令的输出,或是通常出现在 /var/log/messages 中的控制台消息。不要通过手动输入再次复制这些信息;这既麻烦又容易出错。为了发送日志文件内容,你可以复制文件并使用编辑器只保留相关信息,或者直接将内容复制粘贴到你的邮件中。对于类似 这样的程序输出,你可以将输出重定向到一个文件中并附上该文件。例如,

邮件头部的消息引用编号会指向前一封邮件。一些邮件客户端,如 ,可以 线程化 邮件,显示邮件之间的确切关系。

原文:

发布周期开始前的一般信息和准备。

发布周期中的网站更改。

本文中使用的术语和一般信息,如 "代码冻结" 和 "代码冻结期"。

点零 版本发布的发布工程过程。

点 版本发布的发布工程过程。

构建安装介质的具体程序。

发布安装介质的程序。

完成发布周期。

里程碑
预计日期

在代码冻结期间,FreeBSD 提交者应遵循 。

文件待编辑
需要更改的内容
文件待编辑
需要更改的内容

在发布周期中的每个构建过程中,包含各种分发集的 SHA256 值的 MANIFEST 文件(如 base.txz、kernel.txz 等)将被添加到 Port 中。这使得除了,像 等工具能够安全地使用这些分发集,通过提供一个机制来验证校验和。

有关构建 ALPHA 镜像的信息,请参阅 。

文件待编辑
需要更改的内容
文件待编辑
需要更改的内容
要编辑的文件
需要更改的内容

在代码冻结后,发布周期的下一阶段是代码冻结阶段。这时,所有对 stable 分支的提交都需要 FreeBSD 发布工程团队的明确批准。此过程由 执行,负责管理仓库。

要编辑的文件
需要更改的内容

然后, 会为 releng 分支添加新的批准者,就像之前为 stable 分支所做的那样。

在第一个 RC 构建完成并测试后,stable/ 分支可以由 进行“解冻”。

在发布周期中,每个架构的 CHECKSUM.SHA512 和 CHECKSUM.SHA256 副本将存储在 FreeBSD 发布工程团队的内部仓库中,并且也会包含在各种发布公告电子邮件中。每个 MANIFEST,其中包含 base.txz、kernel.txz 等文件的哈希值,也会添加到 Ports Collection 中的 。

要编辑的文件
需要更改的内容

要请求发布后勘误通知,开发人员应填写 ,特别是 Background、Problem Description、Impact,以及(如适用)Workaround 部分。

对于发布后立即请求勘误通知,应将请求同时发送给 FreeBSD 发布工程团队和 FreeBSD 安全团队。releng/ 分支已根据 的说明交接给 FreeBSD 安全团队之后,应发送勘误通知请求给 FreeBSD 安全团队。

文件
需要更改的内容

原文:

这是 FreeBSD 邮件列表的常见问题解答 (FAQ)。如果你有兴趣参与此项目,请发送电子邮件至 。该文档的最新版本始终可以从 获取。它也可以从 以一个大 文件下载,或者以纯文本、PostScript、PDF 等格式下载。你也可以选择 。

本文件试图代表社区共识,因此它不能被视为完全权威的文件。然而,如果你发现本文件中的技术错误,或者有关于应该添加的条目的建议,请提交一个 PR,或者通过电子邮件联系 。谢谢。

这取决于每个邮件列表的章程。一些邮件列表更侧重于开发者,而有些则更面向 FreeBSD 社区的整体。请查看 以获取当前的总结。

你可以使用 订阅任何公开的邮件列表。

是的。自 1994 年以来的所有邮件都以线程归档的形式提供,可以在 查阅。你还可以直接访问 和 。

是的。请参见 。

你已经通过阅读本文档迈出了最重要的一步。然而,如果你是 FreeBSD 新手,你可能需要先通过阅读可用的众多 来熟悉软件及其相关的社会历史。特别值得关注的内容包括 文档、 和文章 、 和 。

请为特定的邮件列表使用适当的语言。许多非英语邮件列表是 。 对于那些不可用的列表,我们理解许多人不是以英语为母语,并尽力在这方面作出让步。特别糟糕的是批评非母语者的拼写或语法错误。FreeBSD 在这方面有着优秀的记录;请帮助我们保持这个传统。

请使用符合标准的邮件用户代理(MUA)。许多格式不正确的邮件来自 。以下邮件客户端错误的格式发送邮件而你可能无法察觉:

许多你需要提供的信息是程序的输出,如 或控制台消息,通常出现在 /var/log/messages 中。不要试图重新键入这些信息;这不仅很麻烦,而且很容易出错。要发送日志文件内容,可以复制文件并用编辑器修剪相关信息,或者直接将其剪切并粘贴到你的邮件中。对于像 dmesg 这样的程序输出,可以将输出重定向到一个文件并包含该文件。例如,

在使用剪切和粘贴时,请注意某些操作可能会破坏邮件内容。特别是在发布 Makefile 内容时,tab 是一个重要的字符。这在提交到 时是一个非常常见且非常烦人的问题。Makefile 中将 tab 改为空格,或变成令人烦恼的 =3B 转义序列,会给提交者带来很大的麻烦。

Greg Lehey:大部分邮件列表礼仪材料的原作者,取自文章 。

Reviewed by: 用户名
Reviewed by: 全名 <有效的@email> (maintainer)
Approved by: 导师的用户名 (mentor)
Approved by: re (用户名)
Committer's Guide
非提交者特定问题
ssh(1)
SSH 快速入门指南
FreeBSD 项目内部页面
FreeBSD 项目主机
FreeBSD 项目管理组
Michael Lucas 的《PGP 和 GPG:实用偏执的电子邮件》
维基百科上的 PGP 介绍
GnuPG 选项文档
https://world.std.com/~reinhold/diceware.html
https://www.iusmentis.com/security/passphrasefaq/
https://xkcd.com/936/
https://en.wikipedia.org/wiki/Passphrase
accounts@FreeBSD.org
Bugzilla
Jenkins
doceng@FreeBSD.org
__FreeBSD_version 值(Porter’s Handbook)
Additional FreeBSD Contributors
新提交者步骤
security-officer@FreeBSD.org
re@FreeBSD.org
donations@FreeBSD.org
FreeBSD 文档项目初学者指南
A git primer
Git - Quick Primer
https://git-scm.com/book/en/v2
https://dangitgit.com/
Git 介绍
Git 书籍
https://www.freebsd.org/releng/
releng/13.1
release/13.1.0
releng/13.2
release/13.2.0
管理详情
内核配置部分
https://www.metaltoad.com/blog/beginners-guide-git-bisect-process-elimination
https://git-scm.com/docs/git-bisect
Git Tools - Signing Your Work
引入此功能的提交
https://cgit.FreeBSD.org/ports/
https://git.FreeBSD.org/ports.git
anongit@git.FreeBSD.org
外部镜像
.hooks/prepare-commit-message
https://git-scm.com/docs/git-stash
https://www.freecodecamp.org/news/the-ultimate-guide-to-git-merge-and-git-rebase/
提交日志消息
正常
这篇 GitHub 文档
日常使用
详细的写作
如上所示
指南
FreeBSD
如上所示
如上所示
git
贡献者列表
README
https://docs.FreeBSD.org/pgpkeys/pgpkeys.txt
Kerberos 和 LDAP Web 密码配置
bug-tracking 数据库
FreeBSD Wiki
Wiki/About 页面
wiki-admin@FreeBSD.org
How We Got Here
IRC Nicks
Dogs of FreeBSD
Cats of FreeBSD
doc 仓库所有分支的提交消息
ports 仓库所有分支的提交消息
src 仓库所有分支的提交消息
Kerberos 和 LDAP Web 密码配置
admin
https://www.FreeBSD.org/internal/software-license
core@FreeBSD.org
vt(4)
core@FreeBSD.org
core@FreeBSD.org
core@FreeBSD.org
core@FreeBSD.org
core@FreeBSD.org
SPDX
SPDX 规范,版本 2.2
附件 D
附件 E
短许可证标识符列表
Kerberos 和 LDAP Web 密码
FreeBSD 问题报告处理指南
https://www.FreeBSD.org/support
Phabricator
Phabricator wiki 页面
Bugzilla
FreeBSD 文档项目邮件列表
章程
文档项目入门指南
FreeBSD 安全官
ssh(1)
ssh-agent(1)
ssh-agent(1)
ssh-agent(1)
ssh-keygen(1)
ssh-add(1)
ssh-agent(1)
security/openssh-portable
ssh(1)
ssh-add(1)
ssh-agent(1)
ssh-keygen(1)
scp(1)
ssh(1)
此文章
Coverity Scan
https://wiki.freebsd.org/CoverityPrevent
https://www.FreeBSD.org/internal/code-of-conduct
尊重其他提交者
源代码树指南和政策
支持的工具链
FreeBSD 内部页面
FreeBSD 架构和设计邮件列表
aarch64-gcc12
riscv64-gcc12
textproc/igor
sysutils/manck
textproc/igor
这里
Porters Handbook
Portclippy / Portfmt
poudriere
Additional Contributors
poudriere
git-grep(1)
vd@FreeBSD.org
FreeBSD ports 邮件列表
crees@FreeBSD.org
git-grep(1)
如何请求合并提交到季度分支的授权?
Proposing a New Category
portmgr@FreeBSD.org
portlint(1)
pkg-version(8)
portupgrade(1)
类别列表
类别列表
portmgr@FreeBSD.org
security-officer@FreeBSD.org
pkg-status.FreeBSD.org
portmgr@FreeBSD.org
Bugzilla 新 PR 页面
portmgr@FreeBSD.org
行政详情
适用于所有人
开发者关系
SSH 快速入门指南
FreeBSD 提交者规则大全
https://people.FreeBSD.org/
新帐户创建流程
FreeBSD Mall, Inc.
Gandi
non-profit@gandi.net
rsync.net
https://www.rsync.net/freebsd.html
Explaining BSD
X.Org 项目
伯克利软件设计公司
BSD/386
NetBSD
FreeBSD
OpenBSD
DragonFlyBSD
BSDI
多种平台
Mac OS® X
Darwin
GNU 通用公共许可证
BSD 许可证
iXsystems, Inc.
FreeBSD
NetBSD
OpenBSD
Fonts and FreeBSD
vidcontrol(1)
vi(1)
syscons(4)
rc.conf(5)
vidcontrol(1)
strings(1)
strings(1)
xfontsel(1)
xfontsel(1)
xset(1)
O’Reilly & Associates
groff_font(5)
groff_char(7)
pfbtops(1)
http://sunsite.icm.edu.pl/pub/GUST/contrib/BachoTeX98/ttf2pf/
groff(1)
groff(1)
gs(1)
FreeType 项目
Juliusz Chroboczek 的页面
Stephen Montgomery 的软件页面
ftp://sunsite.unc.edu/pub/Linux/X11/fonts/
ftp://crl.nmsu.edu/CLR/multiling/General/
FreeBSD and Solid State Devices
mkdir(1)
固态磁盘设备
小型和只读环境的系统策略
rc 子系统和只读文件系统
rc 子系统和只读文件系统
FreeBSD Licensing Policy
core@FreeBSD.org
core@FreeBSD.org
core@FreeBSD.org
core@FreeBSD.org
1910 年布宜诺斯艾利斯公约
伯尔尼公约
REUSE 软件标准
https://spdx.org/
SPDX-License-Identifier 表达式
SPDX-License-Identifier 表达式
SPDX 标识符
许可证例外
SPDX 许可证列表
许可证例外
表达式的完整规范
例外标签
Filtering Bridges
构建和安装自定义内核
ipfw(8)
# exit
# /sbin/shutdown -h now
# /sbin/shutdown -r now
# /sbin/reboot
# adduser
Login group is "jack". Invite jack into other groups: wheel
# periodic daily
输出省略
# periodic weekly
输出省略
# periodic monthly
输出省略
# cp rc.conf rc.conf.orig
# mv rc.conf rc.conf.orig
# cp rc.conf.orig rc.conf
# mv rc.conf rc.conf.myedit
# mv rc.conf.orig rc.conf
# vi filename
% find /usr -name "filename"
# cp -R /cdrom/ports/comm/kermit /usr/local
# make all install
alias su su -m
set prompt = "%h %t %\~ %# "
% ntpq -c 'rv 0 leap'
已添加 abc(4) 支持,包括对 frobnicator 兼容性的支持。
新增了 abc(4) 驱动程序,带来了对 Yoyodyne 公司 Frobnicator 网络接口的支持。
我们将 Cyberdyne Systems T800 导入到树中。

第一次报告征集通知

3 月 1 日

6 月 1 日

9 月 1 日

12 月 1 日

剩余 2 周提醒

3 月 15 日

6 月 15 日

9 月 15 日

12 月 15 日

最后提醒

3 月 24 日

6 月 24 日

9 月 24 日

12 月 24 日

标准截止日期

3 月 31 日

6 月 30 日

9 月 30 日

12 月 31 日

延期截止日期

4 月 8 日

7 月 8 日

10 月 8 日

1 月 8 日

第三方审阅期

4 月 15 日

7 月 15 日

10 月 15 日

1 月 15 日

0      0       1,15,24 3,6,9,12        *       cd ~/doc/tools/sendcalls && git pull && ./sendcalls -s 'Lorenzo Salvadore'
[[news]]
date = "2021-01-16"
title = "2020 年 10月-12月状态报告"
description = "2020 年 10 月到 12 月的状态报告现已发布,包含 42 个条目。"
% w3m -cols 80 -dump https://www.FreeBSD.org/status/report-2021-01-2021-03/ > /tmp/report-2021-01-2021-03.txt
% git clone https://github.com/freebsd/freebsd-update-build.git freebsd-update-server
# FreeBSD 更新构建的主配置文件。特定版本的配置数据位于
# 脚本树的下方。

# 用于获取发布版本的位置
export FTP=ftp://ftp2.freebsd.org/pub/FreeBSD/releases ①

# 主机平台
export HOSTPLATFORM=`uname -m`

# 用于 jail 内的主机名
export BUILDHOSTNAME=${HOSTPLATFORM}-builder.daemonology.net ②

# SSH 密钥的位置
export SSHKEY=/root/.ssh/id_dsa ③

# 上传文件的 SSH 账户
MASTERACCT=builder@wadham.daemonology.net ④

# 上传文件的目录
MASTERDIR=update-master.freebsd.org ⑤
% mkdir -p /usr/local/freebsd-update-server/scripts/7.2-RELEASE/amd64
# RELEASE disc1.iso 镜像的 SHA256 哈希值。
export RELH=1ea1f6f652d7c5f5eab7ef9f8edbed50cb664b08ed761850f95f48e86cc71ef5 ①
# 系统、源代码和内核的组件
export WORLDPARTS="base catpages dict doc games info manpages proflibs lib32"
export SOURCEPARTS="base bin contrib crypto etc games gnu include krb5  \
                lib libexec release rescue sbin secure share sys tools  \
                ubin usbin cddl"
export KERNELPARTS="generic"

# 生命周期结束日期
export EOL=1275289200 ②
% date -j -f '%Y%m%d-%H%M%S' '20090401-000000' '+%s'
# sh scripts/make.sh
cc -O2 -fno-strict-aliasing -pipe   findstamps.c  -o findstamps
findstamps.c: In function 'usage':
findstamps.c:45: warning: incompatible implicit declaration of built-in function 'exit'
cc -O2 -fno-strict-aliasing -pipe   unstamp.c  -o unstamp
install findstamps ../bin
install unstamp ../bin
rm -f findstamps unstamp
Generating RSA private key, 4096 bit long modulus
................................................................................++
...................++
e is 65537 (0x10001)

Public key fingerprint:
27ef53e48dc869eea6c3136091cc6ab8589f967559824779e855d58a2294de9e

Encrypting signing key for root
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
# cd /usr/local/freebsd-update-server
# sh scripts/init.sh amd64 7.2-RELEASE
# sh scripts/init.sh amd64 7.2-RELEASE
Mon Aug 24 16:04:36 PDT 2009 Starting fetch for FreeBSD/amd64 7.2-RELEASE
/usr/local/freebsd-update-server/work/7.2-RELE100 of  588 MB  359 kBps 00m00s
Mon Aug 24 16:32:38 PDT 2009 Verifying disc1 hash for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 16:32:44 PDT 2009 Extracting components for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 16:34:05 PDT 2009 Constructing world+src image for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 16:35:57 PDT 2009 Extracting world+src for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 23:36:24 UTC 2009 Building world for FreeBSD/amd64 7.2-RELEASE
Tue Aug 25 00:31:29 UTC 2009 Distributing world for FreeBSD/amd64 7.2-RELEASE
Tue Aug 25 00:32:36 UTC 2009 Building and distributing kernels for FreeBSD/amd64 7.2-RELEASE
Tue Aug 25 00:44:44 UTC 2009 Constructing world components for FreeBSD/amd64 7.2-RELEASE
Tue Aug 25 00:44:56 UTC 2009 Distributing source for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 17:46:18 PDT 2009 Moving components into staging area for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 17:46:33 PDT 2009 Identifying extra documentation for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 17:47:13 PDT 2009 Extracting extra docs for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 17:47:18 PDT 2009 Indexing release for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 17:50:44 PDT 2009 Indexing world0 for FreeBSD/amd64 7.2-RELEASE

Files built but not released:
Files released but not built:
Files which differ by more than contents:
Files which differ between release and build:
kernel|generic|/GENERIC/hptrr.ko
kernel|generic|/GENERIC/kernel
src|sys|/sys/conf/newvers.sh
world|base|/boot/loader
world|base|/boot/pxeboot
world|base|/etc/mail/freebsd.cf
world|base|/etc/mail/freebsd.submit.cf
world|base|/etc/mail/sendmail.cf
world|base|/etc/mail/submit.cf
world|base|/lib/libcrypto.so.5
world|base|/usr/bin/ntpq
world|base|/usr/lib/libalias.a
world|base|/usr/lib/libalias_cuseeme.a
world|base|/usr/lib/libalias_dummy.a
world|base|/usr/lib/libalias_ftp.a
...
Mon Aug 24 17:54:07 PDT 2009 Extracting world+src for FreeBSD/amd64 7.2-RELEASE
Wed Sep 29 00:54:34 UTC 2010 Building world for FreeBSD/amd64 7.2-RELEASE
Wed Sep 29 01:49:42 UTC 2010 Distributing world for FreeBSD/amd64 7.2-RELEASE
Wed Sep 29 01:50:50 UTC 2010 Building and distributing kernels for FreeBSD/amd64 7.2-RELEASE
Wed Sep 29 02:02:56 UTC 2010 Constructing world components for FreeBSD/amd64 7.2-RELEASE
Wed Sep 29 02:03:08 UTC 2010 Distributing source for FreeBSD/amd64 7.2-RELEASE
Tue Sep 28 19:04:31 PDT 2010 Moving components into staging area for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 19:04:46 PDT 2009 Extracting extra docs for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 19:04:51 PDT 2009 Indexing world1 for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 19:08:04 PDT 2009 Locating build stamps for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 19:10:19 PDT 2009 Cleaning staging area for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 19:10:19 PDT 2009 Preparing to copy files into staging area for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 19:10:20 PDT 2009 Copying data files into staging area for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 12:16:57 PDT 2009 Copying metadata files into staging area for FreeBSD/amd64 7.2-RELEASE
Mon Aug 24 12:16:59 PDT 2009 Constructing metadata index and tag for FreeBSD/amd64 7.2-RELEASE

Files found which include build stamps:
kernel|generic|/GENERIC/hptrr.ko
kernel|generic|/GENERIC/kernel
world|base|/boot/loader
world|base|/boot/pxeboot
world|base|/etc/mail/freebsd.cf
world|base|/etc/mail/freebsd.submit.cf
world|base|/etc/mail/sendmail.cf
world|base|/etc/mail/submit.cf
world|base|/lib/libcrypto.so.5
world|base|/usr/bin/ntpq
world|base|/usr/include/osreldate.h
world|base|/usr/lib/libalias.a
world|base|/usr/lib/libalias_cuseeme.a
world|base|/usr/lib/libalias_dummy.a
world|base|/usr/lib/libalias_ftp.a
...
Values of build stamps, excluding library archive headers:
v1.2 (Aug 25 2009 00:40:36)
v1.2 (Aug 25 2009 00:38:22)
@()FreeBSD 7.2-RELEASE 0: Tue Aug 25 00:38:29 UTC 2009
FreeBSD 7.2-RELEASE 0: Tue Aug 25 00:38:29 UTC 2009
    root@server.myhost.com:/usr/obj/usr/src/sys/GENERIC
7.2-RELEASE
Mon Aug 24 23:55:25 UTC 2009
Mon Aug 24 23:55:25 UTC 2009
 built by root@server.myhost.com on Tue Aug 25 00:16:15 UTC 2009
 built by root@server.myhost.com on Tue Aug 25 00:16:15 UTC 2009
 built by root@server.myhost.com on Tue Aug 25 00:16:15 UTC 2009
 built by root@server.myhost.com on Tue Aug 25 00:16:15 UTC 2009
Mon Aug 24 23:46:47 UTC 2009
ntpq 4.2.4p5-a Mon Aug 24 23:55:53 UTC 2009 (1)
 * Copyright (c) 1992-2009 The FreeBSD Project.
Mon Aug 24 23:46:47 UTC 2009
Mon Aug 24 23:55:40 UTC 2009
Aug 25 2009
ntpd 4.2.4p5-a Mon Aug 24 23:55:52 UTC 2009 (1)
ntpdate 4.2.4p5-a Mon Aug 24 23:55:53 UTC 2009 (1)
ntpdc 4.2.4p5-a Mon Aug 24 23:55:53 UTC 2009 (1)
Tue Aug 25 00:21:21 UTC 2009
Tue Aug 25 00:21:21 UTC 2009
Tue Aug 25 00:21:21 UTC 2009
Mon Aug 24 23:46:47 UTC 2009

FreeBSD/amd64 7.2-RELEASE initialization build complete.  Please
review the list of build stamps printed above to confirm that
they look sensible, then run
 sh -e approve.sh amd64 7.2-RELEASE
to sign the release.
# cd /usr/local/freebsd-update-server
# sh scripts/mountkey.sh
# sh -e scripts/approve.sh amd64 7.2-RELEASE
Wed Aug 26 12:50:06 PDT 2009 Signing build for FreeBSD/amd64 7.2-RELEASE
Wed Aug 26 12:50:06 PDT 2009 Copying files to patch source directories for FreeBSD/amd64 7.2-RELEASE
Wed Aug 26 12:50:06 PDT 2009 Copying files to upload staging area for FreeBSD/amd64 7.2-RELEASE
Wed Aug 26 12:50:07 PDT 2009 Updating databases for FreeBSD/amd64 7.2-RELEASE
Wed Aug 26 12:50:07 PDT 2009 Cleaning staging area for FreeBSD/amd64 7.2-RELEASE
# cd /usr/local/freebsd-update-server
# sh scripts/upload.sh amd64 7.2-RELEASE
# cd /usr/local/freebsd-update-server/pub/7.2-RELEASE/amd64
# touch -t 200801010101.01 uploaded
% mkdir -p /usr/local/freebsd-update-server/patches/7.1-RELEASE/
% cd /usr/local/freebsd-update-server/patches/7.1-RELEASE
% cd /usr/local/freebsd-update-server/patches/7.1-RELEASE/; mv bind.patch 7-SA-09:12.bind
# cd /usr/local/freebsd-update-server
# sh scripts/diff.sh amd64 7.1-RELEASE 7
# sh -e scripts/diff.sh amd64 7.1-RELEASE 7
Wed Aug 26 10:09:59 PDT 2009 Extracting world+src for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 17:10:25 UTC 2009 Building world for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 18:05:11 UTC 2009 Distributing world for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 18:06:16 UTC 2009 Building and distributing kernels for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 18:17:50 UTC 2009 Constructing world components for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 18:18:02 UTC 2009 Distributing source for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 11:19:23 PDT 2009 Moving components into staging area for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 11:19:37 PDT 2009 Extracting extra docs for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 11:19:42 PDT 2009 Indexing world0 for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 11:23:02 PDT 2009 Extracting world+src for FreeBSD/amd64 7.1-RELEASE-p7
Thu Sep 30 18:23:29 UTC 2010 Building world for FreeBSD/amd64 7.1-RELEASE-p7
Thu Sep 30 19:18:15 UTC 2010 Distributing world for FreeBSD/amd64 7.1-RELEASE-p7
Thu Sep 30 19:19:18 UTC 2010 Building and distributing kernels for FreeBSD/amd64 7.1-RELEASE-p7
Thu Sep 30 19:30:52 UTC 2010 Constructing world components for FreeBSD/amd64 7.1-RELEASE-p7
Thu Sep 30 19:31:03 UTC 2010 Distributing source for FreeBSD/amd64 7.1-RELEASE-p7
Thu Sep 30 12:32:25 PDT 2010 Moving components into staging area for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:32:39 PDT 2009 Extracting extra docs for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:32:43 PDT 2009 Indexing world1 for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:35:54 PDT 2009 Locating build stamps for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:36:58 PDT 2009 Reverting changes due to build stamps for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:37:14 PDT 2009 Cleaning staging area for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:37:14 PDT 2009 Preparing to copy files into staging area for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:37:15 PDT 2009 Copying data files into staging area for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:43:23 PDT 2009 Copying metadata files into staging area for FreeBSD/amd64 7.1-RELEASE-p7
Wed Aug 26 12:43:25 PDT 2009 Constructing metadata index and tag for FreeBSD/amd64 7.1-RELEASE-p7
...
Files found which include build stamps:
kernel|generic|/GENERIC/hptrr.ko
kernel|generic|/GENERIC/kernel
world|base|/boot/loader
world|base|/boot/pxeboot
world|base|/etc/mail/freebsd.cf
world|base|/etc/mail/freebsd.submit.cf
world|base|/etc/mail/sendmail.cf
world|base|/etc/mail/submit.cf
world|base|/lib/libcrypto.so.5
world|base|/usr/bin/ntpq
world|base|/usr/include/osreldate.h
world|base|/usr/lib/libalias.a
world|base|/usr/lib/libalias_cuseeme.a
world|base|/usr/lib/libalias_dummy.a
world|base|/usr/lib/libalias_ftp.a
...
Values of build stamps, excluding library archive headers:
v1.2 (Aug 26 2009 18:13:46)
v1.2 (Aug 26 2009 18:11:44)
@()FreeBSD 7.1-RELEASE-p7 0: Wed Aug 26 18:11:50 UTC 2009
FreeBSD 7.1-RELEASE-p7 0: Wed Aug 26 18:11:50 UTC 2009
    root@server.myhost.com:/usr/obj/usr/src/sys/GENERIC
7.1-RELEASE-p7
Wed Aug 26 17:29:15 UTC 2009
Wed Aug 26 17:29:15 UTC 2009
 built by root@server.myhost.com on Wed Aug 26 17:49:58 UTC 2009
 built by root@server.myhost.com on Wed Aug 26 17:49:58 UTC 2009
 built by root@server.myhost.com on Wed Aug 26 17:49:58 UTC 2009
 built by root@server.myhost.com on Wed Aug 26 17:49:58 UTC 2009
Wed Aug 26 17:20:39 UTC 2009
ntpq 4.2.4p5-a Wed Aug 26 17:29:42 UTC 2009 (1)
 * Copyright (c) 1992-2009 The FreeBSD Project.
Wed Aug 26 17:20:39 UTC 2009
Wed Aug 26 17:29:30 UTC 2009
Aug 26 2009
ntpd 4.2.4p5-a Wed Aug 26 17:29:41 UTC 2009 (1)
ntpdate 4.2.4p5-a Wed Aug 26 17:29:42 UTC 2009 (1)
ntpdc 4.2.4p5-a Wed Aug 26 17:29:42 UTC 2009 (1)
Wed Aug 26 17:55:02 UTC 2009
Wed Aug 26 17:55:02 UTC 2009
Wed Aug 26 17:55:02 UTC 2009
Wed Aug 26 17:20:39 UTC 2009
...
New updates:
kernel|generic|/GENERIC/kernel.symbols|f|0|0|0555|0|7c8dc176763f96ced0a57fc04e7c1b8d793f27e006dd13e0b499e1474ac47e10|
kernel|generic|/GENERIC/kernel|f|0|0|0555|0|33197e8cf15bbbac263d17f39c153c9d489348c2c534f7ca1120a1183dec67b1|
kernel|generic|/|d|0|0|0755|0||
src|base|/|d|0|0|0755|0||
src|bin|/|d|0|0|0755|0||
src|cddl|/|d|0|0|0755|0||
src|contrib|/contrib/bind9/bin/named/update.c|f|0|10000|0644|0|4d434abf0983df9bc47435670d307fa882ef4b348ed8ca90928d250f42ea0757|
src|contrib|/contrib/bind9/lib/dns/openssldsa_link.c|f|0|10000|0644|0|c6805c39f3da2a06dd3f163f26c314a4692d4cd9a2d929c0acc88d736324f550|
src|contrib|/contrib/bind9/lib/dns/opensslrsa_link.c|f|0|10000|0644|0|fa0f7417ee9da42cc8d0fd96ad24e7a34125e05b5ae075bd6e3238f1c022a712|
...
FreeBSD/amd64 7.1-RELEASE update build complete.  Please review
the list of build stamps printed above and the list of updated
files to confirm that they look sensible, then run
 sh -e approve.sh amd64 7.1-RELEASE
to sign the build.
# sh -e scripts/approve.sh amd64 7.1-RELEASE
Wed Aug 26 12:50:06 PDT 2009 Signing build for FreeBSD/amd64 7.1-RELEASE
Wed Aug 26 12:50:06 PDT 2009 Copying files to patch source directories for FreeBSD/amd64 7.1-RELEASE
Wed Aug 26 12:50:06 PDT 2009 Copying files to upload staging area for FreeBSD/amd64 7.1-RELEASE
Wed Aug 26 12:50:07 PDT 2009 Updating databases for FreeBSD/amd64 7.1-RELEASE
Wed Aug 26 12:50:07 PDT 2009 Cleaning staging area for FreeBSD/amd64 7.1-RELEASE

The FreeBSD/amd64 7.1-RELEASE update build has been signed and is
ready to be uploaded.  Remember to run
 sh -e umountkey.sh
to unmount the decrypted key once you have finished signing all
the new builds.
# 比较 ${WORKDIR}/release 和 ${WORKDIR}/$1,找出缺少的世界或文档子组件,并
# 将其打包。
findextradocs () {
}
# 将额外的文档添加到 ${WORKDIR}/$1
addextradocs () {
}
# 构建世界
		   log "Building world"
		   cd /usr/src &&
		   make -j 2 ${COMPATFLAGS} buildworld 2>&1
		# 分发世界
		   log "Distributing world"
		   cd /usr/src/release &&
		   make -j 2 obj &&
		   make ${COMPATFLAGS} release.1 release.2 2>&1
_http._tcp.update.myserver.com.		IN SRV   0 2 80   host1.myserver.com.
					IN SRV   0 1 80   host2.myserver.com.
					IN SRV   0 0 80   host3.myserver.com.
# pkg install apache24
# cd /usr/ports/www/apache24
# make install clean
# cd /usr/ports/www/apache24
# make WITH_LDAP="YES" install clean
# 启用 SSHD
sshd_enable="YES"
# 启用带 SSL 的 Apache
apache24_enable="YES"
apache24_flags="-DSSL"
# service sshd start
# service apache24 start
# service sshd onestart
% ifconfig
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
hostname="server1.example.com"
ifconfig_em0="inet 10.10.10.100 netmask 255.255.255.0"
defaultrouter="10.10.10.1"
hostname="server1.example.com"
ifconfig_em0="DHCP"
pass in on $ext_if inet proto tcp from any to ($ext_if) port 22
pass in on $ext_if proto tcp from any to any port = 22
ipfw add allow tcp from any to me 22 in via $ext_if
0 3 * * * root /usr/sbin/freebsd-update cron
% sysctl net.inet.ip.forwarding
net.inet.ip.forwarding: 0
% sysctl -a | more
proc                /proc           procfs  rw,noauto       0       0
# mount /proc

yum install package / apt-get install package

pkg install package

从远程仓库安装软件包

rpm -ivh package / dpkg -i package

pkg add package

安装本地软件包

rpm -qa / dpkg -l

pkg info

列出已安装的软件包

lspci

pciconf

列出 PCI 设备

lsmod

kldstat

列出已加载的内核模块

modprobe

kldload / kldunload

加载/卸载内核模块

strace

truss

跟踪系统调用

% dmesg > /tmp/dmesg.out
主题:HELP!!?!??
我就是搞不懂这个该死的 FreeBSD 系统,
我很擅长这个东西,但我从未见过
这么难安装的东西,尽管我做了很多尝试,
就是没有成功,为什么你们不告诉我
我哪里做错了?
主题:安装 FreeBSD 时遇到问题

我刚从 Walnut Creek 买到了 FreeBSD 2.1.5 CDROM,现在安装时遇到了很多困难。我有一台 66 MHz 的 486 机器,内存 16MB,安装了 Adaptec 1540A SCSI 控制器,一块 1.2GB 的 Quantum Fireball 硬盘和 Toshiba 3501XA CDROM 驱动器。安装过程一切正常,但在重启系统时,我收到了报错“Missing Operating System”。

主分支代码冻结前的准备阶段 (main slush)

2016 年 5 月 27 日

主分支冻结 (main freeze)

2016 年 6 月 10 日

主分支 KBI 冻结 (main KBI freeze)

2016 年 6 月 24 日

doc/ 树冻结 [1]

2016 年 6 月 24 日

Ports 季度分支 [2]

2016 年 7 月 1 日

stable/13 分支

2016 年 7 月 8 日

doc/ 树标签 [3]

2016 年 7 月 8 日

BETA1 构建开始

2016 年 7 月 8 日

主分支解冻 (main thaw)

2016 年 7 月 9 日

BETA2 构建开始

2016 年 7 月 15 日

BETA3 构建开始 [*]

2016 年 7 月 22 日

releng/13.0 分支

2016 年 7 月 29 日

RC1 构建开始

2016 年 7 月 29 日

stable/13 解冻 (stable/13 thaw)

2016 年 7 月 30 日

RC2 构建开始

2016 年 8 月 5 日

最终 Ports 包构建 [4]

2016 年 8 月 6 日

Ports 发布标签

2016 年 8 月 12 日

RC3 构建开始 [*]

2016 年 8 月 12 日

RELEASE 构建开始

2016 年 8 月 19 日

RELEASE 公告发布

2016 年 9 月 2 日

~/shared/releases.adoc

将 beta-upcoming 从 IGNORE 更改为 INCLUDE

~/shared/releases.adoc

将 beta-testing 从 IGNORE 更改为 INCLUDE

~/shared/releases.adoc

将 betarel-vers 更新为 BETA1

~/website/data/en/news/news.toml

添加一条宣布 BETA 的条目

~/website/static/security/advisory-template.txt

将新的 BETA、RC 或最终 RELEASE 添加到模板中

~/website/static/security/errata-template.txt

将新的 BETA、RC 或最终 RELEASE 添加到模板中

% git checkout -b stable/13

UPDATING

更新 FreeBSD 版本,移除关于 WITNESS 的说明

contrib/jemalloc/include/jemalloc/jemalloc_FreeBSD.h

#ifndef MALLOC_PRODUCTION
#define MALLOC_PRODUCTION
#endif

lib/clang/llvm.build.mk

取消注释 -DNDEBUG

sys/*/conf/GENERIC*

移除调试支持

sys/*/conf/MINIMAL

移除调试支持

release/release.conf.sample

更新 SRCBRANCH

sys/*/conf/GENERIC-NODEBUG

移除这些内核配置

sys/arm/conf/std.arm*

移除调试选项

sys/conf/newvers.sh

更新 BRANCH 值以反映 BETA1

share/mk/src.opts.mk

将 REPRODUCIBLE_BUILD 从 __DEFAULT_NO_OPTIONS 移动到 __DEFAULT_YES_OPTIONS

share/mk/src.opts.mk

将 LLVM_ASSERTIONS 从 __DEFAULT_YES_OPTIONS 移动到 __DEFAULT_NO_OPTIONS

libexec/rc/rc.conf

将 dumpdev 设置为 NO,而不是 AUTO(对于那些希望默认启用的用户,可以通过配置来启用)

release/Makefile

移除 debug.witness.trace 条目

UPDATING

更新 FreeBSD 版本

sys/conf/newvers.sh

更新 BRANCH 值以反映 CURRENT,并增加 REVISION

Makefile.inc1

更新 TARGET_TRIPLE 和 MACHINE_TRIPLE

sys/sys/param.h

更新 __FreeBSD_version

gnu/usr.bin/cc/cc_tools/freebsd-native.h

更新 FBSD_MAJOR 和 FBSD_CC_VER

contrib/gcc/config.gcc

追加 freebsdversion.h 部分

lib/clang/llvm.build.mk

更新 OS_VERSION 的值

lib/clang/freebsd_cc_version.h

更新 FREEBSD_CC_VERSION

lib/clang/include/lld/Common/Version.inc

更新 LLD_REVISION_STRING

Makefile.libcompat

更新 LIB32CPUFLAGS

sys/conf/newvers.sh

更新 BRANCH 值为 PRERELEASE

Makefile.inc1

更新 TARGET_TRIPLE

lib/clang/llvm.build.mk

更新 OS_VERSION

Makefile.libcompat

更新 LIB32CPUFLAGS

% git checkout -b releng/13.0

sys/conf/newvers.sh

将 BETAX 更改为 RC1

sys/sys/param.h

更新 __FreeBSD_version

sys/conf/kern.opts.mk

将 REPRODUCIBLE_BUILD 从 DEFAULT_NO_OPTIONS 移到 DEFAULT_YES_OPTIONS

etc/pkg/FreeBSD.conf

将 latest 替换为 quarterly 作为默认的包仓库位置

release/pkg_repos/release-dvd.conf

将 latest 替换为 quarterly 作为默认的包仓库位置

sys/conf/newvers.sh

更新 BETAX 为 PRERELEASE

sys/sys/param.h

更新 __FreeBSD_version

% git add .
% git commit
# /bin/sh /usr/src/release/release.sh
# release.sh 配置用于 powerpc/powerpc64
CHROOTDIR="/scratch-powerpc64"
TARGET="powerpc"
TARGET_ARCH="powerpc64"
KERNEL="GENERIC64"
# /bin/sh /usr/src/release/release.sh -c $HOME/release.conf

sys/conf/newvers.sh

更新 BRANCH 值为 RELEASE

UPDATING

添加预期的公告日期

lib/csu/common/crtbrand.S

将 __FreeBSD_version 替换为 sys/sys/param.h 中的值

% git tag release/13.0.0
# make -C /usr/src/release -f Makefile.mirrors EVERYTHINGISFINE=1 ftp-stage
% cd /archive/tmp/snapshots
% pax -r -w -l . /archive/pub/FreeBSD/snapshots
% /usr/local/bin/rsync -avH /archive/tmp/snapshots/* /archive/pub/FreeBSD/snapshots/

~/website/themes/beastie/layouts/index.html

删除 u-relXXX-announce 和 u-relXXX-announce 的引用。

~/website/content/en/releases/_index.adoc

将 u-relXXX-* 变量从支持的发布版本列表移动到遗留版本列表。

~/website/content/en/releng/_index.adoc

更新相应的 releng 分支,表明该分支不再支持。

~/website/content/en/security/_index.adoc

从支持的分支列表中删除该分支。

~/website/content/en/security/unsupported.adoc

将该分支添加到不受支持的分支列表。

~/website/content/en/where.adoc

删除该发布版本的 URL。

~/website/themes/beastie/layouts/partials/sidenav.html

删除 u-relXXX-announce 和 u-relXXX-announce 的引用。

~/website/static/security/advisory-template.txt

删除与该发布版本和 releng 分支相关的引用。

~/website/static/security/errata-template.txt

删除与该发布版本和 releng 分支相关的引用。

% dmesg > /tmp/dmesg.out
ssh(1)
FreeBSD 项目主机
SMTP 访问设置
https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors
https://developercertificate.org/
设置 Git 钩子
For People New to Both FreeBSD and UNIX®
sh(1)
csh(1)
su(1)
vi(1)
ee(1)
FreeBSD 的官方网站
andrsn@andrsn.stanford.edu
FreeBSD Support for Leap Seconds
IERS
公告 C
RFC 7164
time2posix(3)
NTP
ntpd(8)
ntpd(8)
FreeBSD Status Report Process
Phabricator review
GitHub 镜像
status-submissions@FreeBSD.org
AsciiDoc 示例报告模板
向状态团队发送电子邮件
Ports 管理团队
freebsd-status-calls@FreeBSD.org 邮件列表
AsiaBSDCon
BSDCan
学生
导师
sendcalls perl 脚本
过去一样
w3m(1)
w3m(1)
freebsd-announce@FreeBSD.org
postmaster
freebsd-hackers@FreeBSD.org
freebsd-current@FreeBSD.org
freebsd-stable@FreeBSD.org
Build Your Own FreeBSD Update Server
freebsd-update-server
BSD Magazine
ssh(1)
Apache
sh(1)
devel/git
security/ca_root_nss
freebsd-update-server
fetch(1)
ssh-keygen(1)
sha256(1)
发布公告
FreeBSD 安全网站
date(1)
ntpd(8)
Apache 服务器配置
FreeBSD 更新
init.sh
安全公告
安全通知
named(8)
FreeBSD 安全公告
FreeBSD 手册
安全简报
diff.sh
过程
sha256(1)
DNS
FreeBSD Quickstart Guide for Linux® Users
安装 FreeBSD
sh(1)
tcsh(1)
sh(1)
sh(1)
sh(1)
Port 和包
chsh(1)
这里
使用 pkgng 进行二进制包管理
Git
使用 Ports
init(8)
init(8)
rc(8)
cron(8)
sshd(8)
syslog(3)
Port 和包
rc.conf(5)
sshd(8)
ifconfig(8)
ifconfig(8)
PF
IPFILTER
IPFW
altq(4)
dummynet(4)
freebsd-update(8)
cron(8)
cron(8)
crontab(1)
更新章节
sysctl(8)
FreeBSD 手册
How to get Best Results from the FreeBSD-questions Mailing List
如何成为黑客
FreeBSD general questions mailing list
freebsd-questions+subscribe@freebsd.org
FreeBSD general questions mailing list
freebsd-questions+unsubscribe@freebsd.org
专业化的邮件列表
http://www.FreeBSD.org
手册
FAQ
https://www.FreeBSD.org/mail
https://www.FreeBSD.org/search/#mailinglists
Google
Yahoo
不良邮件客户端或配置不当的邮件客户端
dmesg(8)
dmesg(8)
dmesg(8)
mutt
FreeBSD Release Engineering
一般信息和准备
发布周期中的网站更改
发布工程术语
从主分支发布
从 stable/ 分支发布
构建 FreeBSD 安装介质
将 FreeBSD 安装介质发布到项目镜像
完成发布周期
变更请求指南
misc/freebsd-release-manifests
ports-mgmt/poudriere
构建 FreeBSD 安装介质
gitadm@FreeBSD.org
gitadm@FreeBSD.org
gitadm@FreeBSD.org
misc/freebsd-release-manifests
勘误通知模板
交接到 FreeBSD 安全团队
Frequently Asked Questions About The FreeBSD Mailing Lists
FreeBSD 文档项目邮件列表
FreeBSD 世界宽网服务器
FreeBSD FTP 服务器
HTML
搜索常见问题解答
FreeBSD 文档项目邮件列表
这个列表
Mlmmj Web 界面
这里
mailman 归档
mlmmj 归档
Mlmmj Web 界面
书籍和文章
FreeBSD 常见问题解答 (FAQ)
FreeBSD 手册
如何从 FreeBSD-questions 邮件列表中获得最佳结果
解释 BSD
FreeBSD 入门
可用的
不良邮件客户端或配置不当的邮件客户端
dmesg(8)
问题报告数据库
如何从 FreeBSD-questions 邮件列表中获得最佳结果

状态报告主页

状态报告存档 GitHub 仓库(用于 2017Q4 至 2022Q4 的报告)

状态团队主要电子邮件地址

提交报告的电子邮件地址

用于接收状态报告通知的邮件列表

Phabricator 状态团队主页

LDAP 身份验证

摘要

本文旨在为在 FreeBSD 上配置用于身份验证的 LDAP 服务器(主要是 OpenLDAP 服务器)提供指南。这在需要多个服务器使用相同用户账户的场景下非常有用,例如替代 NIS 的情况。

1. 前言

阅读完毕后,读者应能够配置并部署一台可以托管 LDAP 目录的 FreeBSD 服务器,并能够配置并部署一台可以通过 LDAP 目录进行身份验证的 FreeBSD 服务器。

本文并不旨在详尽讨论配置 LDAP 或其它相关服务时的安全性、稳健性或最佳实践。尽管作者尽力保持内容正确,但安全性问题仅在一般范围内予以处理。本文应视为理论基础,任何实际部署都应伴随仔细的需求分析。

2. 配置 LDAP

LDAP 服务基本上有两个方面需要配置:第一是设置服务器以正确接收连接,第二是向服务器的目录添加条目,以便 FreeBSD 工具能够与之交互。

2.1. 设置服务器以接收连接

注意

本节内容特定于 OpenLDAP。如果使用其它服务器,请查阅相应文档。

2.1.1. 安装 OpenLDAP

首先,安装 OpenLDAP:

示例 1. 安装 OpenLDAP

# cd /usr/ports/net/openldap26-server
# make install clean

这将安装 slapd 与 slurpd 可执行文件,以及所需的 OpenLDAP 库。

2.1.2. 配置 OpenLDAP

接下来我们需要配置 OpenLDAP。

你应当要求 LDAP 连接使用加密,否则用户的密码将以明文传输,这是不安全的。我们将使用的工具支持两种非常相似的加密方式:SSL 与 TLS。

TLS 是“传输层安全协议”(Transportation Layer Security)的缩写。使用 TLS 的服务通常监听与非加密服务相同的端口;例如支持 TLS 的 SMTP 服务监听 25 端口,LDAP 服务监听 389 端口。

SSL 是“安全套接字层”(Secure Sockets Layer)的缩写,使用 SSL 的服务不会与其非加密版本监听相同端口。例如 SMTPS 使用 465(而非 25),HTTPS 使用 443,LDAPS 使用 636。

SSL 与 TLS 使用不同端口的原因在于:TLS 连接以明文开始,在接收到 STARTTLS 指令后切换为加密;而 SSL 连接从一开始就是加密的。除此之外,两者并无本质区别。

注意

本节内容特定于 OpenLDAP。如果使用其它服务器,请查阅相应文档。我们将配置 OpenLDAP 使用 TLS,因为 SSL 被认为已过时。

OpenLDAP 安装完成后,在 /usr/local/etc/openldap/slapd.conf 中添加以下配置参数以启用 TLS:

security ssf=128

TLSCertificateFile /path/to/your/cert.crt
TLSCertificateKeyFile /path/to/your/cert.key
TLSCACertificateFile /path/to/your/cacert.crt

其中,ssf=128 表示 OpenLDAP 要求所有连接(包括查询与更新)使用 128 位加密。该参数可根据站点的安全需求调整,但通常无需降低强度,因为大多数 LDAP 客户端库都支持强加密。

cert.crt、cert.key 与 cacert.crt 文件是为了让客户端能够认证 你 是合法的 LDAP 服务器。如果你仅仅想要一个能运行的服务器,可以使用 OpenSSL 创建自签名证书:

示例 2. 生成 RSA 密钥

% openssl genrsa -out cert.key 1024
Generating RSA private key, 1024 bit long modulus
....................++++++
...++++++
e is 65537 (0x10001)

% openssl req -new -key cert.key -out cert.csr

此时会提示你输入一些值。可以输入任意内容,但“Common Name” 字段必须填写 OpenLDAP 服务器的完整主机名。在本文与示例中,该服务器为 server.example.org。此值设置错误会导致客户端连接失败,常成为令人沮丧的问题源头,因此务必仔细确认。

最后,需要对证书请求进行签名:

示例 3. 自签证书

% openssl x509 -req -in cert.csr -days 365 -signkey cert.key -out cert.crt
Signature ok
subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd
Getting Private key

完成后,在 /etc/rc.conf 中加入以下内容:

slapd_enable="YES"

然后运行 /usr/local/etc/rc.d/slapd start。这将启动 OpenLDAP。使用以下命令确认其监听 389 端口:

% sockstat -4 -p 389
ldap     slapd      3261  7  tcp4   *:389                 *:*

2.1.3. 配置客户端

OpenLDAP 库的配置文件为 /usr/local/etc/openldap/ldap.conf。编辑该文件,加入如下内容:

base dc=example,dc=org
uri ldap://server.example.org/
ssl start_tls
tls_cacert /path/to/your/cacert.crt

注意

本节内容特定于 OpenLDAP。如果使用其它服务器,请查阅相应文档。客户端必须能够访问 cacert.crt 文件,否则将无法建立连接。

注意

本节内容特定于 OpenLDAP。如果使用其它服务器,请查阅相应文档。系统中有两个名为 ldap.conf 的文件。第一个是上述用于 OpenLDAP 库的文件,用于定义如何连接服务器;第二个是 /usr/local/etc/ldap.conf,用于 pam_ldap。

2.2. 数据库中的条目

对 LDAP 目录的认证通常是通过尝试以连接用户的身份绑定到目录来完成的。这是通过在目录上建立一个“简单”绑定并提供用户名来实现的。如果存在一个条目的 uid 等于该用户名,并且该条目的 userPassword 属性与提供的密码匹配,那么绑定就会成功。

我们首先需要弄清楚用户在目录中的位置。

我们的数据库的基本条目是 dc=example,dc=org。大多数客户端默认期望用户位于 ou=people,base 这样的路径下,因此我们也采用这个结构。不过要注意,这一点是可以配置的。

因此,people 这个组织单位的 ldif 条目应如下所示:

dn: ou=people,dc=example,dc=org
objectClass: top
objectClass: organizationalUnit
ou: people

所有用户都将作为这个组织单位的子条目被创建。

你需要考虑你的用户将属于哪个对象类。大多数工具默认使用 person,如果你只想提供认证条目,这样就足够了。但如果你还想在 LDAP 数据库中存储用户信息,那么你很可能需要使用 inetOrgPerson,它包含许多有用的属性。无论选择哪一种,都需要在 slapd.conf 中加载相关的 schema。

在本例中我们使用 person 对象类。如果你使用 inetOrgPerson,步骤基本相同,但需要额外提供 sn 属性。

为了添加一个名为 tuser 的测试用户,ldif 文件应如下:

dn: uid=tuser,ou=people,dc=example,dc=org
objectClass: person
objectClass: posixAccount
objectClass: shadowAccount
objectClass: top
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/tuser
loginShell: /bin/csh
uid: tuser
cn: tuser

我从 10000 开始为 LDAP 用户分配 UID,以避免与系统账户冲突;你可以根据需要配置任何小于 65536 的数字。

我们还需要创建组条目。它们和用户条目一样可以配置,但我们这里使用默认配置如下:

dn: ou=groups,dc=example,dc=org
objectClass: top
objectClass: organizationalUnit
ou: groups

dn: cn=tuser,ou=groups,dc=example,dc=org
objectClass: posixGroup
objectClass: top
gidNumber: 10000
cn: tuser

客户端上的 ldapsearch 工具现在应该可以返回这些条目。如果可以,说明你的数据库已经正确配置,可以作为 LDAP 认证服务器使用。

3. 客户端配置

3.1. 认证

注意

这个文件与 OpenLDAP 库函数的配置文件 /usr/local/etc/openldap/ldap.conf 是 不同的文件;不过它接受许多相同的选项;事实上,它是后者的超集。下文中提到的 ldap.conf 均指 /usr/local/etc/ldap.conf。

我们通过 uid 属性来识别用户。要配置这一点(尽管它是默认值),请在 ldap.conf 中设置 pam_login_attribute 指令:

示例 4. 设置 pam_login_attribute

pam_login_attribute uid

如果用户的 shell 不在 /etc/shells 中,将无法登录。这一点在 LDAP 服务器为用户设置 Bash shell 时尤为重要。FreeBSD 的默认安装并不包含 Bash。当通过包或 Port 安装 Bash 时,它位于 /usr/local/bin/bash。需要确认服务器上的 shell 路径设置正确:

% getent passwd username

如果输出的最后一列显示为 /bin/bash,有两种解决方式:一种是将 LDAP 服务器上的用户条目改为 /usr/local/bin/bash;另一种是在 LDAP 客户端机器上创建一个符号链接:

# ln -s /usr/local/bin/bash /bin/bash

同时请确保 /etc/shells 包含 /usr/local/bin/bash 和 /bin/bash 两个条目。这样用户就可以使用 Bash 作为 shell 登录系统。

3.1.1. PAM

PAM 是“可插拔认证模块”(Pluggable Authentication Modules)的缩写,是 FreeBSD 用于认证大多数会话的机制。要告诉 FreeBSD 我们希望使用 LDAP 服务器,需要向合适的 PAM 文件添加一行配置。

通常情况下,如果你希望通过 SSH 使用 LDAP 认证,则应修改 /etc/pam.d/sshd 文件(同时记得在 /etc/ssh/sshd_config 中设置相关选项,否则 SSH 不会使用 PAM)。

要启用 PAM 认证,请添加如下行:

auth  sufficient  /usr/local/lib/pam_ldap.so  no_warn

通过此配置,你应能成功通过 LDAP 目录认证用户。PAM 会使用你的凭据尝试绑定,如果成功,则允许 SSH 访问。

然而,允许 每个 目录中的用户登录 每台 客户端机器并不是个好主意。按当前配置,只要用户在 LDAP 中有条目,就能登录任意机器。幸运的是,有几种方法可以限制用户访问。

ldap.conf 支持 pam_groupdn 指令;每个连接到该机器的账户都必须属于指定的组。例如,如果你写入以下内容:

pam_groupdn cn=servername,ou=accessgroups,dc=example,dc=org

到 ldap.conf 中,那么只有该组的成员才能登录。不过需要注意以下几点:

该组的成员由一个或多个 memberUid 属性指定,而且每个属性必须为成员的完整可分辨名称(DN)。因此 memberUid: someuser 是无效的,必须写为:

memberUid: uid=someuser,ou=people,dc=example,dc=org

此外,该指令并不会在认证阶段(auth)检查,而是在账号管理阶段(account)检查,因此你还需要在 PAM 文件中添加一行 account 配置。这将导致 每个 用户都必须被列入该组,这并非总是我们想要的行为。为了避免阻止不在 LDAP 中的用户,应启用 ignore_unknown_user 选项。最后,建议设置 ignore_authinfo_unavail,以防 LDAP 服务器不可用时导致无法登录所有机器。

你的 pam.d/sshd 文件可能最终如下所示:

示例 5. pam.d/sshd 示例

auth            required        pam_nologin.so          no_warn
auth            sufficient      pam_opie.so             no_warn no_fake_prompts
auth            requisite       pam_opieaccess.so       no_warn allow_local
auth            sufficient      /usr/local/lib/pam_ldap.so      no_warn
auth            required        pam_unix.so             no_warn try_first_pass

account         required        pam_login_access.so
account         required        /usr/local/lib/pam_ldap.so      no_warn ignore_authinfo_unavail ignore_unknown_user

注意

因为我们是专门向 pam.d/sshd 添加这些行,因此这些配置只对 SSH 会话生效。LDAP 用户将无法在控制台登录。要改变这一行为,请检查 /etc/pam.d 中的其他文件并进行相应修改。

3.2. 名称服务切换(Name Service Switch)

NSS 是一款将属性映射为名称的服务。例如,如果一个文件归属用户 1001,某个应用程序就会通过 NSS 查询 1001 对应的用户名,可能返回的是 bob、ted 或者其他名称。

现在我们的用户信息存储在 LDAP 中了,因此我们需要告诉 NSS,在被查询时应从那里查找。

group: compat
passwd: compat

替换为:

group: files ldap
passwd: files ldap

这将使你能够将用户名映射到 UID,或将 UID 映射为用户名。

恭喜!你现在应该拥有了可用的 LDAP 认证功能。

3.3. 注意事项(Caveats)

示例 6. 用于更改密码的 Shell 脚本

#!/bin/sh

stty -echo
read -p "Old Password: " oldp; echo
read -p "New Password: " np1; echo
read -p "Retype New Password: " np2; echo
stty echo

if [ "$np1" != "$np2" ]; then
  echo "Passwords do not match."
  exit 1
fi

ldappasswd -D uid="$USER",ou=people,dc=example,dc=org \
  -w "$oldp" \
  -a "$oldp" \
  -s "$np1"

小心

该脚本几乎不进行任何错误检查,更重要的是它对密码的处理非常草率。如果你确实打算使用此类方法,至少应调整 security.bsd.see_other_uids 这个 sysctl 参数:

# sysctl security.bsd.see_other_uids=0

一种更灵活(也可能更安全)的方法是编写一个自定义程序,甚至是 Web 接口。以下是一个 Ruby 库的部分内容,它可以更改 LDAP 密码,既可用于命令行,也可用于 Web。

示例 7. 用于更改密码的 Ruby 脚本

require 'ldap'
require 'base64'
require 'digest'
require 'password' # ruby-password

ldap_server = "ldap.example.org"
luser = "uid=#{ENV['USER']},ou=people,dc=example,dc=org"

# 获取新密码、进行检查并生成带盐的哈希值
def get_password
  pwd1 = Password.get("New Password: ")
  pwd2 = Password.get("Retype New Password: ")

  raise if pwd1 != pwd2
  pwd1.check # 检查密码强度

  salt = rand.to_s.gsub(/0\./, '')
  pass = pwd1.to_s
  hash = "{SSHA}"+Base64.encode64(Digest::SHA1.digest("#{pass}#{salt}")+salt).chomp!
  return hash
end

oldp = Password.get("Old Password: ")
newp = get_password

# 我们将直接替换旧密码。能够成功绑定说明我们要么知道旧密码,要么有管理员权限。

replace = LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE | LDAP::LDAP_MOD_BVALUES,
                        "userPassword",
                        [newp])

conn = LDAP::SSLConn.new(ldap_server, 389, true)
conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
conn.bind(luser, oldp)
conn.modify(luser, [replace])

尽管它不能保证绝对安全(比如密码仍保存在内存中),但相较于简单的 sh 脚本,这种方法更清晰、也更灵活。

4. 安全注意事项(Security Considerations)

现在你的机器(甚至可能包括其他服务)已经开始依赖 LDAP 服务器进行身份验证,这台服务器的安全性至少应与常规服务器上的 /etc/master.passwd 文件持平,甚至更高。因为待 LDAP 服务器被破坏或攻破,所有客户端服务也将随之瘫痪。

请注意,本节并不穷尽所有内容。你应持续审查自己的配置和操作流程,以便改进。


4.1. 设置只读属性(Setting Attributes Read-only)

LDAP 中的多个属性应设为只读。如果允许用户自行写入,比如用户可以将其 uidNumber 改为 0,这将获得 root 权限!

首先,userPassword 属性不应向任何人公开读取。默认情况下,任何能连接 LDAP 服务器的人都可以读取此属性。为了禁止这一点,请在 slapd.conf 中添加如下内容:

示例 8. 隐藏密码

access to dn.subtree="ou=people,dc=example,dc=org"
  attrs=userPassword
  by self write
  by anonymous auth
  by * none

access to *
  by self write
  by * read

这将禁止读取 userPassword 属性,但仍允许用户更改自己的密码。

此外,你还需要防止用户更改某些自身的属性。默认情况下,用户可以修改任何属性(除非 LDAP 架构自身禁止修改),例如 uidNumber。为堵住这一安全漏洞,可以将上述配置修改为:

示例 9. 设为只读的属性

access to dn.subtree="ou=people,dc=example,dc=org"
  attrs=userPassword
  by self write
  by anonymous auth
  by * none

access to attrs=homeDirectory,uidNumber,gidNumber
  by * read

access to *
  by self write
  by * read

这样就能阻止用户冒充其他用户。

4.2. root 账户定义

LDAP 服务的 root 或管理账户通常会直接写在配置文件中。以 OpenLDAP 为例,它支持这种做法,也确实可行,但待 slapd.conf 被泄露,就会带来麻烦。更好的做法是只在最初引导 LDAP 系统时使用这个账户,之后在 LDAP 数据库中创建一个 root 账户。

进一步的改进是完全不使用 root 账户,而是创建权限受限的账户。例如:被授权添加或删除用户账户的用户属于一个特定的组,但他们自己却不能修改该组的成员。这类安全策略有助于降低密码泄露带来的风险。

4.2.1. 创建管理组(Creating a Management Group)

假设你希望 IT 部门的成员能够修改用户的 home 目录,但不希望他们拥有添加或删除用户的权限。实现这一目标的方法是为这些管理员添加一个组:

示例 10. 创建管理组

dn: cn=homemanagement,dc=example,dc=org
objectClass: top
objectClass: posixGroup
cn: homemanagement
gidNumber: 121 # posixGroup 所需
memberUid: uid=tuser,ou=people,dc=example,dc=org
memberUid: uid=user2,ou=people,dc=example,dc=org

然后在 slapd.conf 中更改权限属性:

示例 11. home 目录管理组的 ACL

access to dn.subtree="ou=people,dc=example,dc=org"
  attr=homeDirectory
  by dn="cn=homemanagement,dc=example,dc=org"
  dnattr=memberUid write

现在,tuser 和 user2 就可以更改其他用户的 home 目录了。

在这个例子中,我们将部分管理权限赋予某些用户,而没有授予他们在其他领域的权限。其思想是:最终不再有单个用户账户拥有类似 root 的权限,但 root 曾拥有的每一项权限,都至少有一个用户拥有。如此一来,root 账户就变得不再必要,可以移除。

4.3. 密码存储(Password Storage)

默认情况下,OpenLDAP 会将 userPassword 属性的值以与其他数据相同的方式存储:明文存储。大多数时候,它只是被 base64 编码,这种保护措施仅仅足以让诚实的管理员看不到你的密码,而几乎无法抵御其他威胁。

因此,更好的做法是将密码以更安全的格式存储,比如 SSHA(加盐的 SHA)。这通常由你用来修改用户密码的程序负责完成。

附录 A:有用工具(Useful Aids)

有一些额外的程序可能会对你有所帮助,尤其当你拥有大量用户而又不希望手动配置一切时。

附录 B:用于 LDAP 的 OpenSSL 证书(OpenSSL Certificates for LDAP)

如果你在托管两个或更多的 LDAP 服务器,你很可能不希望使用自签名证书,因为这会要求每个客户端都为每个证书进行配置。虽然这并非不可能,但远不如创建你自己的证书颁发机构(CA)并使用它为你的服务器证书签名来得简单。

要创建一个证书颁发机构,我们只需要一个自签名证书和对应密钥。具体步骤如下:

示例 12. 创建证书

% openssl genrsa -out root.key 1024
% openssl req -new -key root.key -out root.csr
% openssl x509 -req -days 1024 -in root.csr -signkey root.key -out root.crt

这将生成你的根 CA 密钥和证书。你应该对该密钥进行加密,并将其保存在阴凉干燥的安全地点;任何获得该密钥的人都可以伪装成你的 LDAP 服务器。

接下来,使用上述前两步创建密钥 ldap-server-one.key 和证书签名请求 ldap-server-one.csr。待使用 root.key 对签名请求进行签名,你就可以在 LDAP 服务器上使用 ldap-server-one.* 文件了。

注意

生成证书签名请求时,不要忘记将“通用名称”(common name)属性设置为完全限定域名(FQDN);否则客户端会拒绝与你的连接,而且诊断这种问题非常困难。

要对密钥进行签名,请使用 -CA 和 -CAkey 参数,而不是 -signkey:

示例 13. 以证书颁发机构身份签名

% openssl x509 -req -days 1024 \
-in ldap-server-one.csr -CA root.crt -CAkey root.key \
-out ldap-server-one.crt

生成的文件将是你可以在 LDAP 服务器上使用的证书。

最后,为了让客户端信任你的所有服务器,请将 root.crt(注意是 证书,不是密钥!)分发给每个客户端,并在 ldap.conf 文件中通过 TLSCACertificateFile 指令指定它。

旧版 FreeBSD 发布工程

摘要

注意

本文介绍了 FreeBSD 发布工程团队用于制作 FreeBSD 操作系统生产质量发布版本的方法。它详细介绍了用于官方 FreeBSD 发布的流程,并介绍了那些有兴趣为企业推广或商业化产品化制作定制 FreeBSD 发布版本的工具。

1. 介绍

由于 FreeBSD 开发进程非常快速,主开发分支不适合大众日常使用。特别是,需要进行稳定化工作,将开发系统打磨成生产质量的发布版本。为了解决这个冲突,开发继续在多个并行的轨道上进行。主开发分支是 Subversion 树的 HEAD 或 trunk,称为 "FreeBSD-CURRENT" 或简写为 -CURRENT。

另外,FreeBSD 项目还维护着一组更加稳定的分支,称为 FreeBSD-STABLE 或简写为 -STABLE。所有分支都存在于 FreeBSD 项目维护的主 Subversion 仓库中。FreeBSD-CURRENT 是 FreeBSD 开发的“前沿”,所有的新更改首先进入系统。FreeBSD-STABLE 是发布的开发分支。更改以不同的速度进入该分支,并且一般假设它们已经首先进入 FreeBSD-CURRENT,并经过了我们的用户社区的广泛测试。

分支名中的 stable 术语指的是项目承诺的应用二进制接口(ABI)稳定性。这意味着,在相同分支的较旧版本上编译的用户应用程序可以在较新的系统上运行,而无需修改。在大多数情况下,来自旧版 STABLE 系统的二进制文件在较新的系统上可以不做修改地运行,包括 HEAD,前提是没有使用系统管理接口。

为了服务于最保守的用户,FreeBSD 4.3 引入了独立的发布分支。这些发布分支在最终发布版本之前创建。发布版本发布后,仅有最关键的安全修复和更新会合并到该发布分支。除了通过 Subversion 更新源代码外,还提供二进制补丁包,以帮助保持 releng/X.Y 分支的系统更新。

1.1. 本文介绍的内容

本文的以下章节介绍了:

2. 发布流程

FreeBSD 的新版本大约每四个月从 -STABLE 分支发布一次。FreeBSD 的发布流程在预计的发布日期前约 70-80 天开始加速,这时发布工程师会向开发邮件列表发送电子邮件,提醒开发人员他们只有 15 天时间将新的更改集成到代码库中,之后将会冻结代码。在此期间,许多开发人员会进行所谓的 "MFC 扫描"(MFC sweep)。

MFC 代表 Merge From CURRENT,即从我们的 -CURRENT 开发分支合并经过测试的更改到 -STABLE 分支。项目政策要求任何更改必须首先应用到主干分支,然后在经过 -CURRENT 用户的充分外部测试后再合并到 -STABLE 分支(开发人员被要求在提交到 -CURRENT 之前进行广泛测试,但由于一个人无法覆盖通用操作系统的所有使用情况,因此无法保证所有使用场景都能被测试)。最小的 MFC 时间是 3 天,通常仅用于修复微小或关键的 bug。

2.1. 代码审查

预计发布日期前 60 天,源代码仓库进入“代码冻结”阶段。在此期间,所有提交到 -STABLE 分支的更改都必须得到发布工程团队 re@FreeBSD.org 的批准。该批准过程通过一个预提交钩子(pre-commit hook)技术上进行强制执行。此期间允许进行的更改包括:

  • 错误修复。

  • 文档更新。

  • 任何类型的安全相关修复。

  • 对设备驱动程序的轻微更改,如添加新的设备 ID。

  • 来自供应商的驱动程序更新。

  • 发布工程团队认为合理的任何其他更改,考虑到潜在的风险。

在代码冻结开始后不久,构建并发布 BETA1 镜像以进行广泛测试。在代码冻结期间,每两周至少发布一个 beta 镜像或发布候选版本(RC),直到最终版本准备好。在最终发布前的几天里,发布工程团队会与安全官员团队、文档维护人员和 Ports 维护人员保持密切联系,以确保发布所需的所有不同组件都已准备就绪。

当 BETA 镜像的质量足够令人满意且不再计划进行大规模且潜在风险较高的更改时,发布分支将被创建,并从发布分支构建 Release Candidate(RC)镜像,而不是从 STABLE 分支构建的 BETA 镜像。此外,STABLE 分支上的冻结期将解除,发布分支将进入 "硬代码冻结"(hard code freeze)状态,在此状态下,除非涉及严重的 bug 修复或安全问题,否则很难再为系统做出新的更改。

2.2. 最终发布清单

当多个 BETA 镜像发布并进行广泛测试,且所有重大问题已解决时,最终发布的 "打磨" 阶段将开始。

2.2.1. 创建发布分支

注意

以下所有示例中的 $FSVN 指的是 FreeBSD Subversion 仓库的位置,svn+ssh://svn.FreeBSD.org/base/。

# svn log -v $FSVN/stable/9

接下来的步骤是创建 发布分支:

# svn cp $FSVN/stable/9@REVISION $FSVN/releng/9.2

可以通过以下命令检出此分支:

# svn co $FSVN/releng/9.2 src

注意

2.2.2. 更新版本号

在最终版本可以标记、构建并发布之前,以下文件需要修改,以反映正确的 FreeBSD 版本:

  • doc/en_US.ISO8859-1/books/handbook/mirrors/chapter.xml

  • doc/en_US.ISO8859-1/books/porters-handbook/book.xml

  • doc/en_US.ISO8859-1/htdocs/cgi/ports.cgi

  • ports/Tools/scripts/release/config

  • doc/shared/xml/freebsd.ent

  • src/Makefile.inc1

  • src/UPDATING

  • src/gnu/usr.bin/groff/tmac/mdoc.local

  • src/release/Makefile

  • src/release/doc/en_US.ISO8859-1/shared/xml/release.dsl

  • src/release/doc/shared/examples/Makefile.relnotesng

  • src/release/doc/shared/xml/release.ent

  • src/sys/conf/newvers.sh

  • src/sys/sys/param.h

  • src/usr.sbin/pkg_install/add/main.c

  • doc/en_US.ISO8859-1/htdocs/search/opensearch/man.xml

还需要调整发布说明和 Errata 文件,以适应新版本(在发布分支上),并适当截断(在 stable/current 分支上):

  • src/release/doc/en_US.ISO8859-1/relnotes/common/new.xml

  • src/release/doc/en_US.ISO8859-1/errata/article.xml

构建发布后,应更新若干文件以向世界宣布发布。这些文件相对于 head/ 目录位于 doc/ 子版本库中:

  • share/images/articles/releng/branches-relengX.pic

  • head/shared/xml/release.ent

  • en_US.ISO8859-1/htdocs/releases/*

  • en_US.ISO8859-1/htdocs/releng/index.xml

  • share/xml/news.xml

另外,还需要更新“BSD 家族树”文件:

  • src/shared/misc/bsd-family-tree

2.2.3. 创建发布标签

当最终发布准备就绪时,以下命令将创建 release/9.2.0 标签。

# svn cp $FSVN/releng/9.2 $FSVN/release/9.2.0

文档和 Ports 管理者负责使用 tags/RELEASE_9_2_0 标签标记他们各自的树。

当使用 Subversion svn cp 命令创建 发布标签 时,这会标识源代码在特定时间点的状态。通过创建标签,我们确保未来的发布构建者能够始终使用我们用于创建官方 FreeBSD 项目发布的相同源代码。

3. 发布构建

3.1. 构建发布

# cd /usr/src/release
# sh generate-release.sh release/9.2.0 /local3/release

运行这些命令后,所有准备好的发布文件将保存在 /local3/release/R 目录中。

发布 Makefile 可以分为几个不同的步骤。

  • 在一个单独的目录层次结构中创建一个干净的系统环境,通过 make installworld。

  • 从 Subversion 检出一个干净的系统源代码、文档和 Ports 到发布构建层次结构中。

  • 在 chroot 环境中填充 /etc 和 /dev。

  • 进入发布构建层次结构的 chroot 环境,以减少外部环境对构建的污染。

  • 在 chroot 环境中执行 make world。

  • 构建与 Kerberos 相关的二进制文件。

  • 构建 GENERIC 内核。

  • 创建一个用于构建和打包二进制分发版的暂存目录树。

  • 构建和安装文档工具链,用于将文档源(SGML)转换为 HTML 和文本文档,这些将随发布一起提供。

  • 构建和安装实际文档(用户手册、教程、发布说明、硬件兼容性列表等)。

  • 打包二进制和源代码的分发 tarball。

  • 创建 FTP 安装层次结构。

  • (可选) 创建适合 CDROM/DVD 媒体的 ISO 镜像。

注意

从 /etc/make.conf 中删除任何特定站点的设置非常重要。例如,分发在具有特定处理器设置的 CPUTYPE 构建的二进制文件是不明智的。

3.2. 第三方软件(Ports)

3.3. 发布 ISO

/stage/cdrom# find . -type f | sed -e 's/^\.\///' | sort > filename.txt

每张光盘的具体要求如下所述。

3.3.1. DVD 1

第一张光盘几乎完全由 make release 创建。唯一应该对 disc1 目录做的更改是添加一个 tools 目录,并添加尽可能多的流行第三方软件包以适应光盘空间。tools 目录包含允许用户从其他操作系统创建安装软盘的软件。此光盘应制作成可启动的,以便现代 PC 用户无需创建安装软盘。

3.3.2. DVD 2

3.3.3. 多卷支持

Sysinstall 支持多卷软件包安装。这要求每张光盘有一个 INDEX 文件,包含所有卷中的软件包,并附加一个额外字段,指示该软件包所在的卷。每个卷还必须在 cdrom.inf 文件中设置 CD_VOLUME 变量,以便 bsdinstall 可以区分哪个卷是哪个。当用户尝试安装当前光盘上没有的软件包时,bsdinstall 会提示用户插入相应的光盘。

4. 分发

4.1. FTP 站点

当发布版本经过彻底测试并完成打包后,必须更新主 FTP 站点。所有官方的 FreeBSD 公共 FTP 站点都是某个主服务器的镜像,该主服务器仅对其他 FTP 站点开放,称为 ftp-master。当发布版本准备就绪后,必须在 ftp-master 上修改以下文件:

/pub/FreeBSD/releases/arch/X.Y-RELEASE/ 这是由 make release 生成的可安装 FTP 目录。

/pub/FreeBSD/ports/arch/packages-X.Y-release/ 这是该版本的完整包构建内容。

/pub/FreeBSD/releases/arch/X.Y-RELEASE/tools 指向 ../../../tools 的符号链接。

/pub/FreeBSD/releases/arch/X.Y-RELEASE/packages 指向 ../../../ports/arch/packages-X.Y-release 的符号链接。

/pub/FreeBSD/releases/arch/ISO-IMAGES/X.Y/X.Y-RELEASE-arch-*.iso ISO 镜像文件。其中的“*”表示 disc1、disc2 等。如有 disc1,且存在另一个精简版本的安装光盘(例如不带图形系统的安装盘),则还可能存在一个 mini 光盘。

4.2. 复制光盘

敬请期待:向复制商提供 FreeBSD ISO 文件的相关建议,以及需要采取的质量控制措施。

5. 可扩展性

虽然 FreeBSD 本身是一个完整的操作系统,但我们并不强求用户必须按照我们打包发布的方式使用它。我们努力将系统设计得尽可能具有可扩展性,使其能够作为其他商业产品的构建平台。我们唯一的“规则”是:如果你打算分发包含非小幅更改的 FreeBSD,我们鼓励你记录你的改动!FreeBSD 社区只能支持我们所提供的软件版本。我们当然鼓励各种创新,例如开发更先进的安装和管理工具,但我们无法负责回答有关这些改动产生的问题。

5.1. 脚本化 bsdinstall

6. 来自 FreeBSD 4.4 的经验教训

我们的用户群体明确表示,FreeBSD 发布版本的安全性与稳定性不应为任何自定的截止日期或目标发布日期所牺牲。随着 FreeBSD 项目的不断壮大,对标准化发布工程流程的需求愈发突出。随着 FreeBSD 被移植到更多新平台,这一点将变得更加重要。

7. 后续方向

我们的发布工程活动必须随着不断增长的用户基础而扩展。为此,我们正努力记录 FreeBSD 发布过程中所涉及的各项流程。

  • 交叉构建发布版本 —— 想在 x86 硬件上构建 IA-64 或 Alpha 的版本?使用 make TARGET=ia64 release 即可。

  • 回归测试 —— FreeBSD 需要更完善的自动化正确性测试机制。

  • 安装工具 —— 我们的安装程序早已超出其原设计寿命。目前已有多个项目正在开发更先进的安装机制。libh 项目便是其中之一,旨在提供一个智能化的新软件包框架与图形化安装程序。

8. 致谢

在台机上实现 UFS 日志功能

摘要

日志文件系统使用日志记录文件系统中发生的所有事务,并在系统崩溃或断电时保持其完整性。尽管仍然可能丢失未保存的文件更改,日志几乎完全消除了因非正常关机而导致的文件系统损坏的可能性。它还将故障后的文件系统检查所需的时间缩短到最小。尽管 FreeBSD 使用的 UFS 文件系统本身并未实现日志记录,但 FreeBSD 7.X 中的 GEOM 框架的新日志类可以提供与文件系统无关的日志功能。本文将解释如何在典型的桌面 PC 场景中实现 UFS 日志记录。

1. 介绍

虽然专业服务器通常能很好地防范突发的关机情况,但典型的桌面 PC 往往容易受到断电、意外重启和其他用户相关事件的影响,导致非正常关机。软更新通常能够有效地保护文件系统,尽管在大多数情况下仍然需要进行长时间的后台检查。在少数情况下,文件系统损坏可能严重到需要用户干预,并且可能导致数据丢失。

GEOM 提供的新日志功能在这种情况下可以大大帮助,通过几乎消除文件系统检查所需的时间,并确保文件系统迅速恢复到一致的状态。

本文介绍了在典型桌面 PC 场景下实现 UFS 日志记录的过程(一个硬盘同时用于操作系统和数据)。它应该在 FreeBSD 的全新安装过程中进行。在安装过程中,步骤足够简单,不需要过于复杂的命令行交互。

阅读本文后,你将了解:

  • 如何在新安装的 FreeBSD 中为日志记录预留空间。

  • 如何加载并启用 geom_journal 模块(或在自定义内核中构建对其的支持)。

  • 如何将现有文件系统转换为使用日志记录,并在 /etc/fstab 中使用哪些选项进行挂载。

  • 如何在新(空)分区中实现日志记录。

  • 如何排查与日志记录相关的常见问题。

在阅读本文之前,你应该能够:

  • 理解基本的 UNIX® 和 FreeBSD 概念。

  • 熟悉 FreeBSD 的安装过程和 sysinstall 工具。

警告

本文所述的过程旨在为全新安装做准备,在此过程中磁盘上尚未存储任何实际用户数据。尽管可以修改和扩展此过程以适应已投入生产的系统,但在进行此操作之前,你应当 备份 所有重要数据。低级别操作磁盘和分区可能导致致命错误和数据丢失。

2. 在 FreeBSD 中理解日志记录

FreeBSD 7.X 中由 GEOM 提供的日志记录功能并非特定于某个文件系统(例如 Linux® 中的 ext3 文件系统),而是作用于块级别。尽管这意味着它可以应用于不同的文件系统,但对于 FreeBSD 7.0-RELEASE,它仅能用于 UFS2。

此功能通过将 geom_journal.ko 模块加载到内核中(或将其构建到自定义内核中)并使用 gjournal 命令来配置文件系统。通常,你可能希望对大型文件系统(如 /usr)进行日志记录。你需要(见下一节)预留一些空闲磁盘空间。

当一个文件系统启用日志记录时,需要一些磁盘空间来存储日志本身。存储实际数据的磁盘空间称为 数据提供者,而存储日志的空间称为 日志提供者。在对现有(非空)分区进行日志记录时,数据和日志提供者需要位于不同的分区中。而在对新分区进行日志记录时,你可以选择使用单一提供者来同时存储数据和日志。无论哪种情况,gjournal 命令都会将两个提供者合并以创建最终的日志文件系统。例如:

  • 你希望对 /usr 文件系统进行日志记录,该文件系统存储在 /dev/ad0s1f 中(该分区已经包含数据)。

  • 你在 /dev/ad0s1g 分区中预留了一些空闲磁盘空间。

  • 使用 gjournal,会创建一个新的 /dev/ad0s1f.journal 设备,其中 /dev/ad0s1f 是数据提供者,/dev/ad0s1g 是日志提供者。然后,这个新设备将用于所有后续的文件操作。

你需要为日志提供者预留的磁盘空间量取决于文件系统的使用负载,而不是数据提供者的大小。例如,在典型的办公室桌面环境中,为 /usr 文件系统预留 1 GB 的日志提供者空间就足够了,而处理大量磁盘 I/O(如视频编辑)的机器可能需要更多。如果在日志空间未能提交之前就耗尽了空间,系统会发生内核恐慌。

注意

这里建议的日志大小,在典型的桌面使用中(例如浏览网页、文字处理和播放媒体文件)不太可能引起问题。如果你的工作负载包括密集的磁盘活动,则可以遵循以下规则来最大化可靠性:你的内存大小应该占日志提供者空间的 30%。例如,如果你的系统有 1 GB RAM,则创建一个大约 3.3 GB 的日志提供者。(将你的内容大小乘以 3.3 即可获得日志的大小)。

3. 在安装 FreeBSD 过程中执行的步骤

3.1. 为日志记录预留空间

一台典型的桌面计算机通常有一块硬盘,用于存储操作系统和用户数据。可以说,sysinstall 选择的默认分区方案或多或少是合适的:一台桌面计算机不需要一个大的 /var 分区,而 /usr 分区则分配了大部分磁盘空间,因为用户数据和许多包都安装在其子目录中。

默认的分区(通过按 A 键在 FreeBSD 分区编辑器中获得的,称为 Disklabel)没有留下任何未分配的空间。每个将进行日志记录的分区都需要另一个分区来存储日志。由于 /usr 分区是最大的,因此稍微缩小这个分区以获得日志所需的空间是有意义的。

在我们的示例中,使用了一个 80 GB 的硬盘。以下截图显示了安装过程中 Disklabel 创建的默认分区:

如果这基本符合你的需求,那么调整分区以进行日志记录非常简单。只需使用箭头键将光标移到 /usr 分区并按 D 删除它。

现在,将光标移到屏幕顶部的磁盘名称上,按 C 创建一个新的 /usr 分区。这个新分区应该比原来的分区小 1 GB(如果只打算为 /usr 创建日志),或者小 2 GB(如果打算为 /usr 和 /var 都创建日志)。在弹出的窗口中,选择创建文件系统,并将挂载点设置为 /usr。

注意

是否应该为 /var 分区创建日志?通常,日志记录对于较大的分区更为合适。你可以决定是否为 /var 创建日志,尽管在典型的桌面环境中这样做不会造成任何问题。如果文件系统使用量较轻(桌面环境中很常见),你可能希望为其日志分配较少的磁盘空间。在我们的示例中,我们同时为 /usr 和 /var 创建日志。你当然可以根据自己的需求调整此过程。

为了尽可能简化操作,我们将使用 sysinstall 来创建日志所需的分区。然而,在安装过程中,sysinstall 会要求为每个创建的分区指定一个挂载点。在此阶段,你没有任何挂载点用于存放日志的分区,实际上你甚至不需要挂载它们。这些分区不是我们将来会挂载到某个位置的分区。

为避免在 sysinstall 中出现这些问题,我们将把日志分区创建为交换空间。交换空间从不挂载,sysinstall 没有问题创建任意数量的交换分区。在首次重启后,你需要编辑 /etc/fstab 文件,删除多余的交换空间条目。

要创建交换空间,再次使用箭头键将光标移到 Disklabel 屏幕顶部,选中磁盘名称。然后按 N,输入所需的大小(1024M),并从弹出的菜单中选择“交换空间”选项。对于每个要创建的日志空间重复该过程。在我们的示例中,我们创建了两个分区,分别用于 /usr 和 /var 的日志。最终结果如下所示:

完成分区创建后,我们建议你记录下分区名称和挂载点,以便在配置阶段轻松参考这些信息。这将有助于减少可能损坏安装的错误。下表显示了我们的示例配置的笔记:

表 1. 分区和日志

分区
挂载点
日志

ad0s1d

/var

ad0s1h

ad0s1f

/usr

ad0s1g

继续按常规方式进行安装。不过,我们建议在完全设置日志记录之前,推迟安装第三方软件(包)。

3.2. 第一次启动

系统将正常启动,但你需要编辑 /etc/fstab 文件并删除为日志创建的额外交换分区。通常,你实际使用的交换分区是带有后缀 b 的那个(即我们的示例中的 ad0s1b)。删除所有其他交换空间条目并重启,以便 FreeBSD 停止使用它们。

系统再次启动时,你将准备好配置日志记录。

4. 设置日志记录

4.1. 执行 gjournal

准备好所有必要的分区后,配置日志记录非常简单。我们需要切换到单用户模式,因此以 root 用户登录并输入:

# shutdown now

按回车键进入默认的 shell。我们需要卸载将进行日志记录的分区,在我们的示例中是 /usr 和 /var:

# umount /usr /var

加载日志记录所需的模块:

# gjournal load

现在,使用你的记录确定每个日志将使用哪个分区。在我们的示例中,/usr 是 ad0s1f,它的日志是 ad0s1g,而 /var 是 ad0s1d,它的日志是 ad0s1h。需要执行以下命令:

# gjournal label ad0s1f ad0s1g
GEOM_JOURNAL: Journal 2948326772: ad0s1f contains data.
GEOM_JOURNAL: Journal 2948326772: ad0s1g contains journal.

# gjournal label ad0s1d ad0s1h
GEOM_JOURNAL: Journal 3193218002: ad0s1d contains data.
GEOM_JOURNAL: Journal 3193218002: ad0s1h contains journal.

注意

如果某个分区的最后一个扇区已被使用,gjournal 会返回错误。你需要使用 -f 标志强制覆盖,例如:

# gjournal label -f ad0s1d ad0s1h

由于这是全新安装,实际上不太可能有任何内容会被覆盖。

此时,将创建两个新设备,即 ad0s1d.journal 和 ad0s1f.journal。它们分别表示我们必须挂载的 /var 和 /usr 分区。在挂载之前,我们必须先在它们上设置日志标志,并清除 Soft Updates 标志:

# tunefs -J enable -n disable ad0s1d.journal
tunefs: gjournal set
tunefs: soft updates cleared

# tunefs -J enable -n disable ad0s1f.journal
tunefs: gjournal set
tunefs: soft updates cleared

现在,将新设备手动挂载到各自的位置(请注意,现在我们可以使用 async 挂载选项):

# mount -o async /dev/ad0s1d.journal /var
# mount -o async /dev/ad0s1f.journal /usr

编辑 /etc/fstab 文件并更新 /usr 和 /var 的条目:

/dev/ad0s1f.journal     /usr            ufs     rw,async      2       2
/dev/ad0s1d.journal     /var            ufs     rw,async      2       2

警告

请确保上述条目正确无误,否则在重启后你将无法正常启动!

geom_journal_load="YES"

恭喜!你的系统现在已启用日志记录。你可以输入 exit 返回到多用户模式,或者重启以测试你的配置(推荐)。在启动时,你将看到类似如下的消息:

ad0: 76293MB XEC XE800JD-00HBC0 08.02D08 at ata0-master SATA150
GEOM_JOURNAL: Journal 2948326772: ad0s1g contains journal.
GEOM_JOURNAL: Journal 3193218002: ad0s1h contains journal.
GEOM_JOURNAL: Journal 3193218002: ad0s1d contains data.
GEOM_JOURNAL: Journal ad0s1d clean.
GEOM_JOURNAL: Journal 2948326772: ad0s1f contains data.
GEOM_JOURNAL: Journal ad0s1f clean.

在非正常关机后,消息会略有不同,例如:

GEOM_JOURNAL: Journal ad0s1d consistent.

4.2. 日志记录新创建的分区

虽然上述过程适用于已经包含数据的分区进行日志记录,但对于空分区进行日志记录则相对简单,因为数据和日志提供者可以存储在同一分区中。例如,假设安装了一个新磁盘,并且创建了一个新分区 /dev/ad1s1d,创建日志的过程就简单多了,你只需要执行:

# gjournal label ad1s1d

默认情况下,日志的大小将为 1 GB。你可以使用 -s 选项来调整它。该值可以以字节为单位给出,或者附加 K、M 或 G 来分别表示千字节、兆字节或吉字节。请注意,gjournal 不允许你创建不合适的小日志大小。

例如,要创建一个 2 GB 的日志,你可以使用以下命令:

# gjournal label -s 2G ad1s1d

然后,你可以在新分区上创建文件系统,并使用 -J 选项启用日志记录:

# newfs -J /dev/ad1s1d.journal

4.3. 将日志记录功能集成到自定义内核中

如果你不希望加载 geom_journal 模块,可以将其功能直接构建到内核中。编辑你的自定义内核配置文件,并确保包含以下两行:

options UFS_GJOURNAL # 注意:此项已包含在 GENERIC 中

options GEOM_JOURNAL # 需要你添加此项

如果你之前使用过此模块,请不要忘记从 /boot/loader.conf 中删除相关的 "load" 条目。

5. 日志记录故障排除

以下部分涵盖了有关日志记录问题的常见问题解答。

5.1. 在磁盘活动高峰期,我遇到了内核恐慌。这与日志记录有什么关系?

5.2. 我在配置过程中犯了一些错误,现在无法正常启动。这个问题能解决吗?

你可能忘记(或拼写错误)了 /boot/loader.conf 中的条目,或者 /etc/fstab 文件中有错误。这些问题通常很容易修复。按回车键进入默认的单用户 shell。然后找到问题的根源:

# cat /boot/loader.conf

如果缺少或拼写错误了 geom_journal_load 条目,则不会创建日志设备。手动加载模块,挂载所有分区,然后继续多用户启动:

# gjournal load

GEOM_JOURNAL: Journal 2948326772: ad0s1g contains journal.
GEOM_JOURNAL: Journal 3193218002: ad0s1h contains journal.
GEOM_JOURNAL: Journal 3193218002: ad0s1d contains data.
GEOM_JOURNAL: Journal ad0s1d clean.
GEOM_JOURNAL: Journal 2948326772: ad0s1f contains data.
GEOM_JOURNAL: Journal ad0s1f clean.

# mount -a
# exit
(boot continues)

另一方面,如果该条目是正确的,请检查 /etc/fstab。你可能会发现拼写错误或缺失的条目。在这种情况下,手动挂载所有剩余分区并继续多用户启动。

5.3. 我可以删除日志记录并恢复到标准的带软更新的文件系统吗?

当然可以。使用以下步骤可以撤销之前的更改。你为日志提供者创建的分区可以在之后用于其他目的(如果需要的话)。

以 root 用户身份登录并切换到单用户模式:

# shutdown now

卸载已进行日志记录的分区:

# umount /usr /var

同步日志:

# gjournal sync

停止日志记录提供者:

# gjournal stop ad0s1d.journal
# gjournal stop ad0s1f.journal

清除所有使用的设备上的日志元数据:

# gjournal clear ad0s1d
# gjournal clear ad0s1f
# gjournal clear ad0s1g
# gjournal clear ad0s1h

清除文件系统的日志记录标志,并恢复软更新标志:

# tunefs -J disable -n enable ad0s1d
tunefs: gjournal cleared
tunefs: soft updates set

# tunefs -J disable -n enable ad0s1f
tunefs: gjournal cleared
tunefs: soft updates set

手动重新挂载旧设备:

# mount -o rw /dev/ad0s1d /var
# mount -o rw /dev/ad0s1f /usr

编辑 /etc/fstab 并恢复到原始状态:

/dev/ad0s1f     /usr            ufs     rw      2       2
/dev/ad0s1d     /var            ufs     rw      2       2

最后,编辑 /boot/loader.conf,删除加载 geom_journal 模块的条目并重启。

6. 进一步阅读

日志记录是 FreeBSD 中一个相对较新的功能,因此它的文档还不十分完善。然而,以下一些额外的参考资料可能会对你有所帮助:

NanoBSD 简介

摘要

本文撰写了有关 NanoBSD 工具的信息,NanoBSD 用于为嵌入式应用程序创建 FreeBSD 系统映像,适用于 USB 密钥、存储卡或其他大容量存储介质。

1. NanoBSD 介绍

它可以用来构建专门的安装映像,旨在便于安装和维护通常称为“计算机家电”的系统。计算机家电将硬件和软件捆绑在一起,这意味着所有应用程序都是预先安装好的。家电设备连接到现有网络后,可以(几乎)立即开始工作。

NanoBSD 的特点包括:

  • Port 和包的使用与 FreeBSD 相同 - 每个应用程序都可以像在 FreeBSD 中一样安装并使用。

  • 无功能丢失 - 如果在 FreeBSD 中可以做某件事,那么在 NanoBSD 中也能做到,除非在创建 NanoBSD 映像时明确移除了特定的功能或特性。

  • 易于构建和定制 - 仅需一个 shell 脚本和一个配置文件,就可以构建满足任意需求的精简定制映像。

2. NanoBSD 使用手册

2.1. NanoBSD 的设计

映像文件被放置在介质上后,就可以启动 NanoBSD。大容量存储介质默认分为三个部分:

  • 两个映像分区:code#1 和 code#2。

  • 配置文件分区,可以在运行时挂载到 /cfg 目录下。

这些分区通常是只读挂载的。

配置文件分区在 /cfg 目录下持久化。它包含 /etc 目录的文件,在系统启动后会简短地以只读方式挂载,因此,如果希望修改后的文件在系统重启后仍然存在,需要将修改后的文件从 /etc 复制回 /cfg 目录。

示例 1. 对 /etc/resolv.conf 进行持久化更改

# vi /etc/resolv.conf
[...]
# mount /cfg
# cp /etc/resolv.conf /cfg
# umount /cfg

注意

配置文件分区 /cfg 应该仅在启动时或覆盖配置文件时挂载。始终保持 /cfg 挂载并不是一个好主意,尤其是在 NanoBSD 系统运行在可能因大量写入而受影响的大容量存储介质上时(例如,当文件系统同步器将数据刷新到系统磁盘时)。

2.2. 构建 NanoBSD 映像

构建 NanoBSD 需要 FreeBSD 的源代码。获取源代码的命令为:

# git clone https://git.FreeBSD.org/src.git /usr/src

构建 NanoBSD 映像所需的命令为:

# cd /usr/src/tools/tools/nanobsd ①
# sh nanobsd.sh ②
# cd /usr/obj/nanobsd.full ③
# dd if=_.disk.full of=/dev/da0 bs=64k ④
  • ① 将当前目录更改为 NanoBSD 构建脚本的基础目录。

  • ② 启动构建过程。

  • ③ 将当前目录更改为存放构建好映像的目录。

  • ④ 将 NanoBSD 安装到存储介质上。

2.2.1. 构建 NanoBSD 映像时的选项

在构建 NanoBSD 映像时,可以在命令行传递多个构建选项。这些选项对构建过程有显著影响。

一些选项用于控制输出的详细程度:

  • -h:打印帮助摘要页面。

  • -q:使输出更静默。

  • -v:使输出更加详细。

还有一些选项可以限制构建过程。有时不需要从源代码重建所有内容,特别是当映像已经构建并且只做了少量修改时。

  • -k:不构建内核。

  • -w:不构建世界。

  • -b:既不构建内核也不构建世界。

  • -f:不构建第一个分区的磁盘映像(对于升级目的很有用)。

  • -n:向 buildworld 和 buildkernel 添加 -DNO_CLEAN 参数。还会保留先前构建中已经构建的所有文件。

可以使用配置文件调整任何所需的元素。使用 -c 加载配置文件。

最后的选项是:

  • -K:不安装内核。没有内核的磁盘映像将无法完成正常的启动过程。

2.2.2. 完整的映像构建过程

完整的映像构建过程经过多个步骤。实际执行的步骤将取决于开始运行脚本时所选择的选项。如果脚本在没有任何特定选项的情况下运行,以下是将发生的步骤:

  1. run_early_customize:在提供的配置文件中定义的命令。

  2. clean_build:通过删除先前构建的文件来清理构建环境。

  3. make_conf_build:从 CONF_WORLD 和 CONF_BUILD 变量组合生成 make.conf 文件。

  4. build_world:构建世界。

  5. build_kernel:构建内核文件。

  6. clean_world:清理目标目录。

  7. make_conf_install:从 CONF_WORLD 和 CONF_INSTALL 变量组合生成 make.conf 文件。

  8. install_world:安装 buildworld 期间构建的所有文件。

  9. install_etc:根据 make distribution 命令安装 /etc 目录中的必要文件。

  10. setup_nanobsd_etc:此时开始进行与 NanoBSD 相关的首次配置。创建 /etc/diskless 并将根文件系统定义为只读。

  11. install_kernel:安装内核和模块文件。

  12. run_customize:调用用户定义的所有自定义例程。

  13. setup_nanobsd:设置特殊的配置目录布局。将 /usr/local/etc 移动到 /etc/local,并从 /etc/local 创建符号链接到 /usr/local/etc。

  14. prune_usr:删除 /usr 中的空目录。

  15. run_late_customize:此时可以运行最后的自定义脚本。

  16. fixup_before_diskimage:列出所有安装的文件并生成 metalog。

  17. create_diskimage:根据提供的磁盘几何参数创建实际的磁盘映像。

  18. last_orders:目前无操作。

2.3. 定制 NanoBSD 映像

这可能是 NanoBSD 最重要也是最有趣的功能。在使用 NanoBSD 开发时,你将花费大部分时间在这一部分。

以下命令会强制 nanobsd.sh 从当前目录中的 myconf.nano 配置文件读取配置:

# sh nanobsd.sh -c myconf.nano

定制有两种方式:

  • 配置选项

  • 自定义函数

2.3.1. 配置选项

通过配置设置,可以配置传递给 NanoBSD 构建过程中的 buildworld 和 installworld 阶段的选项,以及传递给 NanoBSD 主构建过程的内部选项。通过这些选项,可以将系统缩减至仅适用于 64MB 的存储空间。你可以使用配置选项进一步精简 FreeBSD,直到只剩下内核和两个或三个用户空间文件。

配置文件由配置选项组成,这些选项会覆盖默认值。最重要的指令包括:

  • NANO_NAME - 构建名称(用于构造工作目录名称)。

  • NANO_SRC - 用于构建映像的源代码树路径。

  • NANO_KERNEL - 用于构建内核的内核配置文件名称。

  • CONF_BUILD - 传递给 buildworld 阶段的选项。

  • CONF_INSTALL - 传递给 installworld 阶段的选项。

  • CONF_WORLD - 传递给 buildworld 和 installworld 阶段的选项。

  • FlashDevice - 定义使用的媒体类型。有关更多细节,请查看 FlashDevice.sub。

根据所需的 NanoBSD 类型,还有许多其他可能相关的配置选项。

2.3.1.1. 一般定制

设计上有三个阶段可以进行更改,这些更改会影响构建过程,只需在提供的配置文件中设置相应的变量:

  • run_early_customize:在其他任何操作之前。

  • run_customize:所有标准文件已布置之后。

  • run_late_customize:在实际 NanoBSD 映像构建之前的最后阶段。

为了在任何这些步骤中定制 NanoBSD 映像,最好为相应的变量添加一个特定的值。

NANO_EARLY_CUSTOMIZE 变量在构建过程的第一步使用。此时,尚无示例说明如何使用该变量,但未来可能会有所变化。

NANO_CUSTOMIZE 变量在内核、世界和 etc 配置文件已安装,并且 etc 文件已设置为 NanoBSD 安装时使用。因此,这是构建过程中的正确步骤,可以在此步骤调整配置选项并添加包,例如在 cust_nobeastie 示例中所示。

NANO_LATE_CUSTOMIZE 变量在磁盘映像创建之前使用,因此这是更改任何内容的最后时刻。请记住,setup_nanobsd 例程已执行,etc、conf 和 cfg 目录及其子目录已修改,因此此时不应更改它们。相反,可以在此时添加或删除特定的文件。

2.3.1.2. 启动选项

  • NANO_BOOT0CFG

  • NANO_BOOTLOADER

值得注意的是,NANO_BOOT2CFG 变量仅在 cust_comconsole 例程中使用,当设备有串口且所有控制台输入输出都必须通过串口进行时,可以在 NANO_CUSTOMIZE 步骤中调用此例程。请确保检查串口的相关参数,设置错误的参数可能会导致其无法使用。

2.3.1.3. 磁盘映像创建

在启动过程的最后,是磁盘映像的创建。通过此步骤,NanoBSD 脚本会生成一个可以直接复制到设备上的磁盘映像文件,这样设备就可以启动并开始工作。

有许多变量需要正确设置,才能使脚本生成一个可用的磁盘映像。

  • NANO_DRIVE 变量必须在运行时设置为媒体的驱动器名称。通常,默认值 ada0 代表设备上的第一个 IDE/ATA/SATA 设备是正确的,但也可以使用不同类型的存储设备,如 USB 驱动器,此时应使用 da0。

  • NANO_MEDIASIZE 变量必须设置为将要使用的存储媒体的大小(以 512 字节扇区为单位)。如果设置错误,NanoBSD 映像可能无法启动,启动时会显示有关磁盘几何形状不正确的警告信息。

  • 通过使用 NANO_CODESIZE、NANO_CONFSIZE 和 NANO_DATASIZE 变量,可以设置不同的分区大小,单位为 512 字节扇区。NANO_CODESIZE 定义了前两个映像分区的大小:code#1 和 code#2。它们必须足够大,以容纳 buildworld 和 buildkernel 过程产生的所有文件。NANO_CONFSIZE 定义了配置文件分区的大小,因此不需要很大,但不要设置得太小,以至于无法容纳所有配置文件。最后,NANO_DATASIZE 定义了一个可选分区的大小,可以在设备上使用。最后一个分区可以用来存储动态生成的文件。

2.3.2. 自定义函数

可以使用配置文件中的 shell 函数来精细调整 NanoBSD。以下示例演示了自定义函数的基本模型:

cust_foo () (
	echo "bar=baz" > \
		${NANO_WORLDDIR}/etc/foo
)
customize_cmd cust_foo

一个更有用的自定义函数示例如下,它将 /etc 目录的默认大小从 5MB 更改为 30MB:

cust_etc_size () (
	cd ${NANO_WORLDDIR}/conf
	echo 30000 > default/etc/md_size
)
customize_cmd cust_etc_size

有一些预定义的默认自定义函数可以直接使用:

  • cust_install_files - 从 nanobsd/Files 目录安装文件,该目录包含一些用于系统管理的有用脚本。

  • cust_pkgng - 从 nanobsd/Pkg 目录安装包(还需要 pkg-* 包来引导安装)。

2.3.3. 添加包

可以向 NanoBSD 映像添加包,以提供特定的功能。为此,可以:

  • 将 cust_pkgng 添加到 NANO_CUSTOMIZE 变量中,或

  • 在自定义配置文件中添加 'customize_cmd cust_pkgng' 命令。

这两种方法都能达到相同的结果:启动 cust_pkgng 例程。该例程将遍历 NANO_PACKAGE_DIR 目录,查找所有包或仅查找 NANO_PACKAGE_LIST 变量中的包列表。

在标准的 FreeBSD 环境中通过 pkg 安装应用程序时,安装过程通常会将配置文件放入 /usr/local/etc 目录,将启动脚本放入 /usr/local/etc/rc.d 目录。因此,在安装所需的包之后,需要对它们进行配置,以便它们可以直接开箱即用。为此,必须将必要的配置文件安装到正确的目录中。这可以通过编写专门的例程来实现,或者可以使用通用的 cust_install_files 例程,从 /usr/src/tools/tools/nanobsd/Files 目录正确放置文件。通常,还需要在 /etc/rc.conf 文件中为每个包添加一条语句,有时需要多条语句。

2.3.4. 配置文件示例

构建自定义 NanoBSD 映像的完整配置文件示例如下:

NANO_NAME=custom
NANO_SRC=/usr/src
NANO_KERNEL=MYKERNEL
NANO_IMAGES=2

CONF_BUILD='
WITHOUT_KLDLOAD=YES
WITHOUT_NETGRAPH=YES
WITHOUT_PAM=YES
'

CONF_INSTALL='
WITHOUT_ACPI=YES
WITHOUT_BLUETOOTH=YES
WITHOUT_FORTRAN=YES
WITHOUT_HTML=YES
WITHOUT_LPR=YES
WITHOUT_MAN=YES
WITHOUT_SENDMAIL=YES
WITHOUT_SHAREDOCS=YES
WITHOUT_EXAMPLES=YES
WITHOUT_INSTALLLIB=YES
WITHOUT_CALENDAR=YES
WITHOUT_MISC=YES
WITHOUT_SHARE=YES
'

CONF_WORLD='
WITHOUT_BIND=YES
WITHOUT_MODULES=YES
WITHOUT_KERBEROS=YES
WITHOUT_GAMES=YES
WITHOUT_RESCUE=YES
WITHOUT_LOCALES=YES
WITHOUT_SYSCONS=YES
WITHOUT_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

例如,ftp 客户端和服务器可能不需要。将 WITHOUT_FTP=TRUE 添加到 CONF_BUILD 部分的配置文件中,可以避免构建这些组件。此外,如果 NanoBSD 器件不用于构建程序,则可以在 CONF_INSTALL 部分中添加 WITHOUT_BINUTILS=TRUE,但不要在 CONF_BUILD 部分中添加,因为它们将在构建 NanoBSD 映像时使用。

通过编译选项不构建某些特定程序,能缩短整体构建时间并减小磁盘映像的所需大小,而不安装这些特定程序则不会减少整体构建时间。

2.4. 更新 NanoBSD

NanoBSD 的更新过程相对简单:

  1. 按照通常的方式构建新的 NanoBSD 映像。

  2. 将新映像上传到运行中的 NanoBSD 设备的一个未使用的分区。 这一步与最初安装 NanoBSD 的主要区别在于,现在使用的是 _.disk.image 映像(它包含的是单一系统分区的映像),而不是 _.disk.full(它包含整个磁盘的映像)。

  3. 重启,并从新安装的分区启动系统。

  4. 如果一切顺利,升级完成。

  5. 如果出现问题,重新启动回到以前的分区(它包含旧的、正常工作的映像),以尽快恢复系统功能。修复新版本中的任何问题,然后重复该过程。

要将新映像安装到正在运行的 NanoBSD 系统中,可以使用 /root 目录中的 updatep1 或 updatep2 脚本,具体取决于当前系统运行的分区。

根据托管新 NanoBSD 映像的主机上可用的服务和首选的传输类型,可以选择以下三种方式之一:

如果传输速度是首要考虑因素,请使用以下示例:

# ftp myhost
get _.disk.image "| sh updatep1"

如果首选安全传输,请考虑使用以下示例:

# ssh myhost cat _.disk.image.gz | zcat | sh updatep1
  1. 首先,在提供映像的主机上打开一个 TCP 监听端口,并使其将映像发送到客户端:

    myhost# nc -l 2222 < _.disk.image

    注意

    确保没有防火墙阻止使用的端口,以便 NanoBSD 主机能够接收来自该端口的连接。

  2. 连接到提供新映像的主机,并执行 updatep1 脚本:

    # nc myhost 2222 | sh updatep1

FreeBSD 中的 Linux® 仿真

摘要

本篇硕士论文讨论了更新 Linux® 仿真层(即 Linuxulator)。任务是将该层更新,以匹配 Linux® 2.6 的功能。作为参考实现,选择了 Linux® 2.6.16 内核。该概念大致基于 NetBSD 实现。大部分工作在 2006 年夏季完成,作为谷歌编程之夏学生项目的一部分。重点是将 NPTL(新的 POSIX® 线程库)支持引入仿真层,包括 TLS(线程本地存储)、futexes(快速用户空间互斥锁)、PID 混淆等其他一些小问题。在这个过程中,识别并修复了许多小问题。我的工作已经被整合到 FreeBSD 的主源代码库,并将在即将发布的 7.0R 版本中发布。我们仿真开发团队,正在努力使 Linux® 2.6 仿真成为 FreeBSD 中的默认仿真层。

1. 引言

近年来,基于 UNIX® 的开源操作系统已广泛部署在服务器和客户端机器上。在这些操作系统中,我特别想提到两个:FreeBSD,它具有 BSD 传统、经过时间考验的代码库和许多有趣的特性;以及 Linux®,它具有广泛的用户基础、热情的开源开发者社区和大公司的支持。FreeBSD 通常用于服务器级机器,承担重型网络任务,在桌面类机器上的使用较少,而 Linux® 在服务器上也有相同的用途,但更多地被家庭用户使用。这导致了许多仅适用于 Linux® 的二进制程序,而 FreeBSD 缺乏对此的支持。

因此,在 FreeBSD 系统上运行 Linux® 二进制程序的需求自然产生了,而本篇论文正是探讨这一问题:在 FreeBSD 操作系统中仿真 Linux® 内核。

在 2006 年夏季,谷歌公司资助了一项项目,旨在扩展 FreeBSD 中的 Linux® 仿真层(即 Linuxulator),以包括 Linux® 2.6 的功能。本文作为该项目的一部分而编写。

2. 内部探讨……

在这一部分,我们将介绍每个相关操作系统,如何处理系统调用、陷阱帧等底层内容。我们还将概述它们如何理解常见的 UNIX® 原语,比如 PID 是什么,线程是什么等。在第三个小节中,我们讨论了 UNIX® 与 UNIX® 之间仿真的一般方法。

2.1. 什么是 UNIX®

UNIX® 是一款具有悠久历史的操作系统,几乎影响了当前使用的所有操作系统。自 1960 年代开始,它的开发至今仍在继续(尽管已分叉为不同的项目)。UNIX® 的发展很快复刻成两大主流:BSD 系列和 System III/V 系列。它们通过共同的 UNIX® 标准相互影响。在 BSD 系列中,值得一提的贡献有虚拟内存、TCP/IP 网络、FFS 等,而 System V 分支则贡献了 SysV 进程间通信原语、写时复制等。虽然 UNIX® 本身已不存在,但其思想已被世界上许多操作系统所采用,形成了所谓的类 UNIX® 操作系统。目前,最具影响力的操作系统包括 Linux®、Solaris 和可能在一定程度上包括 FreeBSD。还有一些公司内部的 UNIX® 派生操作系统(如 AIX、HP-UX 等),但这些系统越来越多地迁移到了上述操作系统。让我们总结一下 UNIX® 的典型特征。

2.2. 技术细节

每个运行的程序构成一个进程,代表计算的某个状态。运行中的进程分为内核空间和用户空间。某些操作只能在内核空间执行(如与硬件交互等),但进程应该大部分时间运行在用户空间。内核负责管理进程、硬件和底层细节,并为用户空间提供统一的 UNIX® API。下面列出了最重要的几个。

2.2.1. 内核与用户空间进程之间的通信

另一种可能的通信方式是通过使用 trap(陷阱)。陷阱是由于某些事件发生后异步触发的(例如,除零错误、页面错误等)。陷阱可能对进程透明(如页面错误),或者可能导致某些反应,如发送一个 signal(信号)(例如,除零错误)。

2.2.2. 进程间通信

除了其他 API(如 System V IPC、共享内存等)之外,最重要的 API 是信号。信号由进程或内核发送,并由进程接收。某些信号可以被忽略或由用户提供的例程处理,其他信号则会导致预定义的动作,这些动作无法改变或忽略。

2.2.3. 进程管理

2.2.4. 线程管理

传统的 UNIX® 并未定义任何关于线程的 API 或实现,而 POSIX® 定义了其线程 API,但未定义其实现。传统上,线程有两种实现方式。将其作为独立进程处理(1:1 线程)或将整个线程组封装在一个进程中并在用户空间管理线程(1:N 线程)。比较这两种方法的主要特点:

1:1 线程

  • 重型线程

  • 用户无法修改调度(通过 POSIX® API 稍微可以缓解)

  • 无需系统调用包装

  • 可以利用多个 CPU

1:N 线程

  • 轻量级线程

  • 可以轻松修改调度

  • 需要包装系统调用

  • 无法利用多个 CPU

2.3. 什么是 FreeBSD?

FreeBSD 项目是目前可供日常使用的最古老的开源操作系统之一。它是正统 UNIX® 的直接后代,因此可以声称它是一个真正的 UNIX®,尽管由于许可证问题不能这样称呼。该项目始于 1990 年代初,当时一群 BSD 用户修补了 386BSD 操作系统。在这个补丁包的基础上,一个新的操作系统应运而生,名为 FreeBSD,因其宽松的许可证而得名。另一组人则创建了 NetBSD 操作系统,目标有所不同。我们将重点讨论 FreeBSD。

FreeBSD 是一个现代的基于 UNIX® 的操作系统,具备 UNIX® 的所有特性。包括抢占式多任务处理、多用户功能、TCP/IP 网络、内存保护、对称多处理支持、虚拟内存与合并的 VM 和缓冲区缓存等功能,它们都包含在内。一个有趣且非常有用的特性是能够模拟其他 UNIX® 类操作系统。截至 2006 年 12 月和 7-CURRENT 开发版本,以下模拟功能已经得到支持:

  • FreeBSD/i386 在 FreeBSD/amd64 上的模拟

  • FreeBSD/i386 在 FreeBSD/ia64 上的模拟

  • Linux® 模拟:在 FreeBSD 上模拟 Linux® 操作系统

  • NDIS 模拟:Windows 网络驱动接口的模拟

  • NetBSD 模拟:模拟 NetBSD 操作系统

  • PECoff 支持:支持 PECoff 格式的 FreeBSD 可执行文件

  • SVR4 模拟:模拟 System V 第四版 UNIX®

目前正在积极开发的模拟功能有 Linux® 层和各种 FreeBSD-on-FreeBSD 层。其他的功能目前不被认为是能够正常工作或可用的。

2.3.1. 技术细节

FreeBSD 是传统的 UNIX® 版本,在进程运行方面将其分为两个部分:内核空间和用户空间。进程进入内核有两种方式:系统调用和陷阱。返回的方式只有一种。在接下来的章节中,我们将介绍进出内核的三个入口。整段简介适用于 i386 架构,因为 Linuxulator 仅存在于此,但在其他架构上的概念类似。以下信息来源于 [1] 和源代码。

2.3.1.1. 系统入口

2.3.1.2. 系统调用

FreeBSD 中的系统调用通过执行中断 0x80 发起,其中寄存器 %eax 被设置为所需的系统调用号,参数则通过堆栈传递。

2.3.1.3. 陷阱

在 FreeBSD 中,陷阱的处理与系统调用的处理类似。每当发生陷阱时,会调用一个汇编处理程序。这个处理程序的选择依据陷阱类型而不同,可以是 alltraps、alltraps with regs pushed 或 calltrap。该处理程序准备好参数后,调用 C 函数 trap()(定义在 sys/i386/i386/trap.c 中),然后处理发生的陷阱。处理完成后,可能会向进程发送信号和/或使用 userret() 返回到用户空间。

2.3.1.4. 退出

从内核到用户空间的退出是通过汇编例程 doreti 完成的,无论是通过陷阱还是系统调用进入内核。该过程会从堆栈中恢复程序状态并返回到用户空间。

2.3.1.5. UNIX® 原语

FreeBSD 中目前有两种实现线程的方法。第一种是 M:N 线程模型,接下来是 1:1 线程模型。默认使用的库是 M:N 线程模型(libpthread),并且可以在运行时切换到 1:1 线程模型(libthr)。计划是尽快将默认库切换为 1:1 库。尽管这两个库使用相同的内核原语,但它们通过不同的 API 访问。M:N 库使用 kse_* 系列系统调用,而 1:1 库使用 thr_* 系列系统调用。因此,内核和用户空间之间没有共享的线程 ID 概念。当然,这两个线程库都实现了 pthread 线程 ID API。每个内核线程(由 struct thread 描述)都有一个 td tid 标识符,但它不能直接从用户空间访问,只供内核使用。它也被用作 1:1 线程库中的 pthread 线程 ID,但其处理方式是库内部的,不能依赖于此。

如前所述,FreeBSD 中有两种线程实现。M:N 库将工作分配给内核空间和用户空间。线程是一个在内核中调度的实体,但它可以表示多个用户空间线程。M 个用户空间线程映射到 N 个内核线程,从而节省资源,同时保持利用多处理器并行性的能力。有关实现的更多信息可以从手册页或 [1] 获取。1:1 库则直接将用户空间线程映射到内核线程,从而大大简化了方案。这些设计都没有实现公平机制(虽然曾经实现过,但由于造成了严重的性能下降并使代码更难维护,最近已被移除)。

2.4. 什么是 Linux®?

Linux® 是一款类似 UNIX® 的内核,最初由 Linus Torvalds 开发,现在由全球大量程序员贡献。自从它的起步到今天,得到了 IBM 和谷歌等公司的广泛支持,Linux® 与其快速的开发速度、全面的硬件支持和仁慈独裁的组织模式相关联。

Linux® 的开发始于 1991 年,在芬兰赫尔辛基大学作为一个爱好者项目启动。从那时起,它就获得了现代 UNIX® 类操作系统的所有特性:多处理、支持多用户、虚拟内存、网络功能,基本上所有功能都具备了。还有一些先进的特性,如虚拟化等。

截至 2006 年,Linux® 已成为最广泛使用的开源操作系统,得到了独立软件厂商如 Oracle、RealNetworks、Adobe 等的支持。大多数为 Linux® 分发的商业软件只能以二进制形式获得,因此无法重新编译为其他操作系统。

大多数 Linux® 的开发都发生在 Git 版本控制系统中。Git 是一款分布式系统,因此 Linux® 代码没有中央来源,但一些分支被认为是突出和官方的。Linux® 实现的版本号方案由四个数字 A.B.C.D 组成。目前开发版本为 2.6.C.D,其中 C 代表主要版本,添加或更改新特性,而 D 是次要版本,仅用于修复 bug。

更多信息可以从 [3] 获取。

2.4.1. 技术细节

Linux® 遵循传统的 UNIX® 方案,将进程的执行分为两个部分:内核空间和用户空间。内核可以通过两种方式进入:通过陷阱或通过系统调用。返回只有一种方式。以下描述适用于 i386™ 架构上的 Linux® 2.6。该信息来源于 [2]。

2.4.1.1. 系统调用

Linux® 中的系统调用(在用户空间中)使用 syscallX 宏来执行,其中 X 是表示给定系统调用参数数量的数字。此宏会转换为一个代码,该代码将 %eax 寄存器加载为系统调用的编号,并执行中断 0x80。在系统调用返回后,负返回值会转换为正的 errno 值,并且在发生错误时将 res 设置为 -1。每当调用中断 0x80 时,进程就会进入内核,进入系统调用陷阱处理程序。该程序将所有寄存器保存到堆栈中,并调用选定的系统调用入口。需要注意的是,Linux® 的调用约定要求通过寄存器传递系统调用的参数,具体如下:

  1. 参数 → %ebx

  2. 参数 → %ecx

  3. 参数 → %edx

  4. 参数 → %esi

  5. 参数 → %edi

  6. 参数 → %ebp

有些系统调用例外,使用了不同的调用约定(最显著的例子是 clone 系统调用)。

2.4.1.2. 陷阱

陷阱处理程序定义在 arch/i386/kernel/traps.c 中,处理大部分陷阱的代码位于 arch/i386/kernel/entry.S,其中进行陷阱的处理。

2.4.1.3. 退出

2.4.1.4. UNIX® 原语

实现的 clone 标志包括:

  • CLONE_VM - 进程共享内存空间

  • CLONE_FS - 共享 umask、当前工作目录和命名空间

  • CLONE_FILES - 共享打开的文件

  • CLONE_SIGHAND - 共享信号处理程序和被阻塞的信号

  • CLONE_PARENT - 共享父进程

  • CLONE_THREAD - 作为线程(后续解释)

  • CLONE_NEWNS - 新命名空间

  • CLONE_SYSVSEM - 共享 SysV undo 结构

  • CLONE_SETTLS - 在提供的地址设置 TLS

  • CLONE_PARENT_SETTID - 在父进程中设置 TID

  • CLONE_CHILD_CLEARTID - 清除子进程中的 TID

  • CLONE_CHILD_SETTID - 在子进程中设置 TID

CLONE_PARENT 将实际父进程设置为调用者的父进程。这对于线程非常有用,因为如果线程 A 创建了线程 B,那么我们希望线程 B 的父进程是整个线程组的父进程。CLONE_THREAD 完全执行与 CLONE_PARENT 相同的操作,CLONE_VM 和 CLONE_SIGHAND,将 PID 修改为与调用者的 PID 相同,设置退出信号为空,并进入线程组。CLONE_SETTLS 设置 TLS 处理的 GDT 条目。CLONE_*_*TID 系列标志设置/清除 TID 或 0 的用户提供地址。

正如你所见,CLONE_THREAD 完成了大部分工作,并且似乎并不完全适应当前的方案。其原始意图不明确(根据代码中的注释,甚至连作者也不清楚),但我认为最初可能有一个线程标志,后来被拆分为许多其他标志,但这种拆分从未完全完成。对于这一划分的意义也不清楚,因为 glibc 并没有使用它,所以只有通过手写的 clone 调用,程序员才能访问这些功能。

对于非线程程序,PID 和 TID 是相同的。对于线程程序,第一个线程的 PID 和 TID 是相同的,每个创建的线程共享相同的 PID,并获得一个唯一的 TID(因为传递了 CLONE_THREAD),同时所有进程共享父进程,形成该线程程序。

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
 | CLONE_SETTLS | CLONE_PARENT_SETTID
 | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
#if __ASSUME_NO_CLONE_DETACHED == 0
 | CLONE_DETACHED
#endif
 | 0);

CLONE_SIGNAL 定义为:

#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)

最后的 0 表示在任何线程退出时不会发送信号。

2.5. 什么是仿真

根据词典定义,仿真是程序或设备模仿另一个程序或设备的能力。这是通过对给定的刺激作出与被仿真对象相同的反应来实现的。在实际中,软件世界通常看到三种类型的仿真——用于仿真机器的程序(如 QEMU,各种游戏机仿真器等)、硬件设施的仿真软件(如 OpenGL 仿真器,浮点单元仿真等)以及操作系统仿真(无论是在操作系统的内核中还是作为用户空间程序)。

仿真通常在无法或完全不可能使用原始组件的地方使用。例如,有人可能希望使用为不同操作系统开发的程序。这时仿真就派上了用场。有时别无选择,只能使用仿真——例如当你试图使用的硬件设备不存在(还不存在或不再存在)时,那么唯一的选择就是仿真。通常在将操作系统移植到一个新平台(该平台尚不存在)时会发生这种情况。有时仿真只是更便宜。

从实现的角度来看,仿真的实现方法主要有两种。你可以仿真整个组件——接受原始对象的可能输入,维护内部状态并根据状态和/或输入发出正确的输出。这种仿真不需要特殊的条件,基本上可以在任何地方为任何设备/程序实现。缺点是,实现这种仿真相当困难、耗时且容易出错。在某些情况下,我们可以使用更简单的方法。假设你想在一台从右向左打印的打印机上仿真一台从左向右打印的打印机。显然,没有必要使用复杂的仿真层,只需简单地反转打印的文本即可。也有可能仿真环境与被仿真环境非常相似,因此只需要一层薄薄的转换即可提供完整的仿真!正如你所见,这种方法比前一种方法更不要求实现,因此也更不耗时且更不容易出错。但必要的条件是两个环境必须足够相似。第三种方法结合了前两种方法。大多数情况下,仿真对象提供的能力并不相同,因此在更强大的对象上仿真较弱的对象时,我们必须使用上述完全仿真所描述的方式来仿真缺失的功能。

本硕士论文涉及 UNIX® 在 UNIX® 上的仿真,这正是一个只需薄薄的转换层就能提供完整仿真的案例。UNIX® API 由一组系统调用组成,这些调用通常是自包含的,并不会影响全局的内核状态。

有一些系统调用会影响内部状态,但这可以通过提供一些结构来处理,这些结构维护额外的状态。

没有任何仿真是完美的,仿真通常会缺少一些部分,但这通常不会造成严重的缺陷。想象一个游戏机仿真器,仿真所有内容但没有声音输出。毫无疑问,游戏仍然是可以玩的,尽管不如原始游戏机那样舒适,但它是价格与舒适度之间的一个可以接受的折衷。

3. 模拟

3.1. FreeBSD 中模拟的工作原理

在 FreeBSD 中,二进制文件大致分为两种类型。第一种是类似 shell 的文本脚本,它们的前两个字符是 #!;第二种是正常的(通常是 ELF 格式的)二进制文件,表示编译后的可执行对象。绝大多数(可以说所有)FreeBSD 中的二进制文件都是 ELF 类型。ELF 文件包含一个头部,其中指定了该 ELF 文件的操作系统 ABI(应用二进制接口)。通过读取这些信息,操作系统可以准确地确定给定文件的二进制类型。

不同操作系统(以及其他一些子系统)模拟的特点促使开发者引入了事件处理机制。内核中的多个地方允许注册事件处理程序,这些处理程序会按需被调用。例如,当一个进程退出时,会调用一个处理程序,清理该子系统需要清理的内容。

这些简单的设施基本上提供了模拟基础架构所需的一切,实际上,它们是实现 Linux® 模拟层所必需的唯一内容。

3.2. FreeBSD 内核中的常见原语

模拟层需要操作系统的支持。接下来我将描述一些 FreeBSD 操作系统中支持的原语。

3.2.1. 锁原语

贡献者:Attilio Rao <attilio@FreeBSD.org>

FreeBSD 的同步原语集基于提供大量不同原语的理念,以便在每种特定情况下可以使用最合适的原语。

从高层次来看,FreeBSD 内核中可以视为三种同步原语:

  • 原子操作和内存屏障

  • 锁

  • 调度屏障

以下是这三类原语的描述。对于每种锁,你应该查阅相应的手册页(如果可能的话)以获取更详细的解释。

3.2.1.1. 原子操作和内存屏障

3.2.1.2. 引用计数

引用计数是用于处理引用计数器的接口。它们通过原子操作实现,旨在仅在引用计数器是唯一需要保护的内容时使用,因此即使是旋转互斥锁(spin-mutex)也不再推荐使用。对于已经使用互斥锁保护的结构,使用引用计数接口通常是不正确的,因为我们可能应该在一些已经保护的路径中关闭引用计数器。当前没有关于引用计数的手册页面,请查看 sys/refcount.h 以获取现有 API 的概述。

3.2.1.3. 锁

FreeBSD 内核有大量的锁类型。每种锁都由一些特定的属性定义,但最重要的属性可能是与争用持有者相关的事件(或换句话说,线程无法获取锁时的行为)。FreeBSD 的锁定方案为竞争者提供了三种不同的行为:

  1. 自旋

  2. 阻塞

  3. 睡眠

注意

数字并非随意

3.2.1.4. 自旋锁

自旋锁允许等待的线程自旋,直到它能够获取锁。需要处理的一个重要问题是,当线程在自旋锁上发生争用时,如果线程没有被重新调度,会发生什么情况。由于 FreeBSD 内核是抢占式的,这使得自旋锁面临死锁的风险,唯一的解决办法是禁用中断来获取锁。由于这些原因(例如缺乏优先级传播支持、CPU 之间负载均衡方案的不足等),自旋锁应该仅保护非常小的代码路径,或者如果没有明确要求,最好根本不使用它们(稍后将解释)。

3.2.1.5. 阻塞

阻塞锁允许等待的线程被重新调度并阻塞,直到锁的拥有者释放锁并唤醒一个或多个竞争者。为了避免饥饿问题,阻塞锁会将优先级从等待者传递给锁的拥有者。阻塞锁必须通过转门(turnstile)接口实现,并且应该是内核中最常用的锁类型,除非满足某些特定条件。

3.2.1.6. 睡眠

获取锁的顺序非常重要,不仅因为锁顺序反转可能导致死锁,还因为获取锁时应遵循特定的规则,这些规则与锁的性质有关。如果你查看上面的表格,可以得出一个实际规则:如果一个线程持有级别为 n 的锁(其中级别是与锁类型相对应的数字),则不允许获取更高级别的锁,因为这会破坏路径的语义。例如,如果一个线程持有一个阻塞锁(级别 2),它可以获取自旋锁(级别 1),但不能获取睡眠锁(级别 3),因为阻塞锁旨在保护比睡眠锁更小的路径(然而这些规则与原子操作或调度屏障无关)。

以下是锁及其相应行为的列表:

在这些锁中,只有互斥锁、sx 锁、读写锁和 lockmgr 锁是设计用来处理递归的,但目前只有互斥锁和 lockmgr 锁支持递归。

3.2.1.7. 调度屏障

调度屏障用于驱动线程的调度。它们主要由三种不同的存根组成:

  • 临界区(和抢占)

  • sched_bind

  • sched_pin

3.2.1.8. Critical sections

3.2.1.9. sched_pin/sched_unpin

另一种处理抢占的方法是使用 sched_pin() 接口。如果一段代码被包含在 sched_pin() 和 sched_unpin() 这对函数中,保证该线程即使可以被抢占,也将始终在同一个 CPU 上执行。固定(pinning)在某些情况下非常有效,特别是当我们需要访问每个 CPU 特有的数据,并假设其他线程不会更改这些数据时。后者的条件将决定临界区对我们代码来说过于强的条件。

3.2.1.10. sched_bind/sched_unbind

sched_bind 是一种 API,用于将线程绑定到特定的 CPU 上,直到 sched_unbind 函数调用将其解除绑定。这一功能在你无法信任当前 CPU 状态的情况下非常重要(例如,在引导的早期阶段),因为你希望避免线程迁移到非活动的 CPU 上。由于 sched_bind 和 sched_unbind 操作内部调度器结构,因此在使用时需要在 sched_lock 获取/释放之间进行封装。

3.2.2. Proc 结构

各种仿真层有时需要一些额外的每个进程数据。可以为每个进程管理单独的结构(如列表、树等)来存储这些数据,但这往往效率低下且消耗内存。为了解决这个问题,FreeBSD 的 proc 结构包含了 p_emuldata,它是一个指向某些仿真层特定数据的 void 指针。这个 proc 条目受到 proc 锁的保护。

FreeBSD 的 proc 结构包含 p_sysent 条目,标识该进程正在运行的 ABI。事实上,它是指向上述 sysentvec 的一个指针。所以,通过将这个指针与给定 ABI 的 sysentvec 结构的存储地址进行比较,我们可以有效地判断该进程是否属于我们的仿真层。代码通常如下所示:

if (__predict_true(p->p_sysent != &elf_Linux(R)_sysvec))
	  return;

如你所见,我们有效地使用 __predict_true 修饰符将最常见的情况(FreeBSD 进程)压缩为简单的返回操作,从而保持高性能。由于当前不支持 Linux®64 仿真或 i386 上的 A.OUT Linux® 进程,因此这段代码应当转换为宏,因为它目前不够灵活。

3.2.3. VFS

FreeBSD 的 VFS 子系统非常复杂,但 Linux® 仿真层仅通过一个小子集通过明确定义的 API 使用它。它可以操作 vnodes 或文件句柄。Vnode 代表一个虚拟 vnode,即 VFS 中节点的表示。另一种表示是文件句柄,它从进程的角度表示一个已打开的文件。文件句柄可以代表套接字或普通文件。一个文件句柄可以指向其 vnode。多个文件句柄可以指向同一个 vnode。

3.2.3.1. namei

3.2.3.2. vn_fullpath

3.2.3.3. Vnode 操作

  • fgetvp - 给定线程和文件描述符号,它返回关联的 vnode

  • vn_unlock - 解锁 vnode

3.2.3.4. 文件句柄操作

  • fget - 给定线程和文件描述符号,它返回关联的文件句柄并引用它

  • fdrop - 删除对文件句柄的引用

  • fhold - 引用文件句柄

4. Linux® 仿真层——机器相关部分

本节讨论了 FreeBSD 操作系统中 Linux® 仿真层的实现。首先介绍了与用户态和内核交互的机器相关部分,涉及系统调用、信号、ptrace、陷阱、栈修正等内容。尽管本节以 i386 为例,但其编写方式是通用的,因此其他架构的实现应不会有很大不同。接下来是 Linuxulator 的机器无关部分,本节仅覆盖 i386 和 ELF 处理,A.OUT 已不再使用且未经测试。

4.1. 系统调用处理

系统调用处理大部分写在 linux_sysvec.c 中,该文件覆盖了 sysentvec 结构中列出的绝大多数例程。当在 FreeBSD 上运行的 Linux® 进程发出系统调用时,通用系统调用例程会调用 Linux® 的 prepsyscall 例程。

4.1.1. Linux® prepsyscall

Linux® 通过寄存器传递系统调用的参数(这就是为什么它在 i386 上仅限于 6 个参数的原因),而 FreeBSD 则使用栈。Linux® 的 prepsyscall 例程必须将参数从寄存器复制到栈中。寄存器的顺序是:%ebx、%ecx、%edx、%esi、%edi、%ebp。需要注意的是,这对于 大多数 系统调用是成立的。有些(尤其是 clone)使用不同的顺序,但幸运的是,可以通过在 linux_clone 原型中插入一个虚拟参数来轻松修复。

4.1.2. 系统调用写入

Linuxulator 中实现的每个系统调用必须在 syscalls.master 中定义其原型,并附带各种标志。该文件的格式如下:

...
	AUE_FORK STD		{ int linux_fork(void); }
...
	AUE_CLOSE NOPROTO	{ int close(int fd); }
...

第一列表示系统调用编号。第二列用于审计支持。第三列表示系统调用类型。它可以是 STD、OBSOL、NOPROTO 或 UNIMPL。STD 是标准系统调用,具有完整的原型和实现。OBSOL 是过时的,仅定义原型。NOPROTO 表示系统调用在其他地方实现,因此不需要添加 ABI 前缀等。UNIMPL 表示该系统调用将被 nosys 系统调用替代(该系统调用仅打印一条关于该系统调用未实现的消息,并返回 ENOSYS)。

从 syscalls.master 文件中,脚本会生成三个文件:linux_syscall.h、linux_proto.h 和 linux_sysent.c。linux_syscall.h 包含系统调用名称及其数值的定义,例如:

...
#define LINUX_SYS_linux_fork 2
...
#define LINUX_SYS_close 6
...

linux_proto.h 包含每个系统调用的参数结构定义,例如:

struct linux_fork_args {
  register_t dummy;
};

最后,linux_sysent.c 包含描述系统入口表的结构,该表用于实际调度系统调用,例如:

{ 0, (sy_call_t *)linux_fork, AUE_FORK, NULL, 0, 0 }, /* 2 = linux_fork */
{ AS(close_args), (sy_call_t *)close, AUE_CLOSE, NULL, 0, 0 }, /* 6 = close */

4.1.3. 虚拟系统调用

Linux® 仿真层并不完整,因为某些系统调用没有正确实现,有些则根本没有实现。仿真层通过 DUMMY 宏标记未实现的系统调用。这些虚拟定义存在于 linux_dummy.c 中,形式为 DUMMY(syscall);,然后会被转换到各种系统调用辅助文件中,实际实现就是打印一条消息,表明该系统调用未实现。UNIMPL 原型没有使用,因为我们希望能够识别被调用的系统调用名称,以了解哪些系统调用更需要实现。

4.2. 信号处理

信号处理在 FreeBSD 内核中通常是针对所有二进制兼容性进行的,调用了与兼容性相关的层。Linux® 兼容性层定义了 linux_sendsig 例程来处理这一任务。

4.2.1. Linux® sendsig

该例程首先检查信号是否已安装并带有 SA_SIGINFO,如果是,它会调用 linux_rt_sendsig 例程。除此之外,它会分配(或重用已存在的)信号处理上下文,然后为信号处理程序构建参数列表。它根据信号翻译表翻译信号编号,分配处理程序,翻译信号集。接着,它为 sigreturn 例程保存上下文(各种寄存器、翻译后的陷阱号和信号掩码)。最后,它将信号上下文复制到用户空间并准备实际运行的信号处理程序上下文。

4.2.2. linux_rt_sendsig

这个例程与 linux_sendsig 类似,只是信号上下文的准备过程不同。它添加了 siginfo、ucontext 和一些 POSIX® 部分。值得考虑的是,是否可以将这两个函数合并,以减少代码重复,并可能提高执行效率。

4.2.3. linux_sigreturn

这个系统调用用于从信号处理程序返回。它执行一些安全检查并恢复原始进程上下文,同时取消对进程信号掩码的屏蔽。

4.3. Ptrace

4.4. 陷阱

当在仿真层中运行的 Linux® 进程发生陷阱时,陷阱会被透明地处理,唯一的例外是陷阱的翻译。Linux® 和 FreeBSD 对于什么是陷阱有不同的看法,因此在这里进行处理。代码实际上非常简短:

static int
translate_traps(int signal, int trap_code)
{

  if (signal != SIGBUS)
    return signal;

  switch (trap_code) {

    case T_PROTFLT:
    case T_TSSFLT:
    case T_DOUBLEFLT:
    case T_PAGEFLT:
      return SIGSEGV;

    default:
      return signal;
  }
}

4.5. 栈修复

RTLD(运行时链接编辑器)期望在 execve 调用期间栈上存在所谓的 AUX 标签,因此必须进行修复以确保这一点。当然,每个 RTLD 系统都是不同的,因此仿真层必须提供自己的栈修复例程来执行此操作。Linuxulator 也不例外。elf_linux_fixup 简单地将 AUX 标签复制到栈上,并调整用户空间进程的栈,使其指向这些标签之后的位置。这样 RTLD 就能正常工作。

4.6. A.OUT 支持

在 i386 上,Linux® 仿真层还支持 Linux® A.OUT 二进制文件。前面描述的大多数内容(除了陷阱翻译和信号发送)都必须实现以支持 A.OUT。A.OUT 二进制文件的支持已经不再维护,特别是 2.6 版本的仿真并不支持它,但这并不会造成问题,因为在 Ports 中的 linux-base 可能根本不支持 A.OUT 二进制文件。此支持可能会在未来移除。加载 Linux® A.OUT 二进制文件所需的大部分内容都在 imgact_linux.c 文件中。

5. Linux® 仿真层——机器无关部分

本节讨论 Linuxulator 的机器无关部分。它涵盖了 Linux® 2.6 仿真所需的仿真基础设施、线程局部存储(TLS)实现(在 i386 上)以及 futex。然后简要讨论一些系统调用。

5.1. NPTL 的描述

Linux® 2.6 发展中的一个主要进展领域是线程支持。在 2.6 之前,Linux® 线程支持是通过 linuxthreads 库实现的。该库是 POSIX® 线程的部分实现。线程是通过为每个线程创建单独的进程来实现的,使用 clone 系统调用让它们共享地址空间(以及其他资源)。这种方法的主要弱点是每个线程都有不同的 PID,信号处理出现问题(从 pthreads 角度看),等等。并且性能不佳(使用 SIGUSR 信号进行线程同步,内核资源消耗等)。为了解决这些问题,开发了一个新的线程系统,命名为 NPTL。

NPTL 库专注于两件事,但第三件事也随之而来,因此通常认为它是 NPTL 的一部分。这两件事是将线程嵌入到进程结构中和 futex。第三件事是 TLS,虽然它不是 NPTL 直接要求的,但整个 NPTL 用户空间库都依赖于它。这些改进大大提升了性能并符合标准。NPTL 目前是 Linux® 系统中的标准线程库。

FreeBSD Linuxulator 的实现通过三个主要领域来接近 NPTL:TLS、futex 和 PID 混乱,这些是为了模拟 Linux® 线程。接下来的章节将描述这些领域。

5.2. Linux® 2.6 仿真基础设施

这些部分讨论了 Linux® 线程是如何管理的,以及我们如何在 FreeBSD 中模拟这一过程。

5.2.1. 运行时确定 2.6 仿真版本

5.2.2. Linux® 进程和线程标识符

Linux® 线程的语义有些令人困惑,并且使用与 FreeBSD 完全不同的术语。Linux® 中的一个进程由一个 struct task 组成,其中嵌套了两个标识符字段——PID 和 TGID。PID 不是 进程 ID,而是线程 ID。TGID 用来标识一个线程组,也就是一个进程。对于单线程进程,PID 等于 TGID。

在 NPTL 中,线程只是一个普通的进程,只是它的 TGID 不等于 PID,并且其组领导者不等于它本身(当然,还共享虚拟内存等)。其他一切和普通进程相同。并没有像 FreeBSD 中那样将共享状态分离到某个外部结构中。这就导致了信息的重复和可能的数据不一致。Linux® 内核似乎在某些地方使用 task → group 信息,而在其他地方使用 task 信息,这种做法并不一致,容易出错。

每个 NPTL 线程都是通过调用 clone 系统调用并传入一组特定的标志来创建的(更多内容见下一小节)。NPTL 实现了严格的 1:1 线程模型。

在 FreeBSD 中,我们通过普通的 FreeBSD 进程来模拟 NPTL 线程,这些进程共享虚拟内存空间等,而 PID 操作则通过附加到进程的仿真特定结构来模拟。附加到进程的结构如下所示:

struct linux_emuldata {
  pid_t pid;

  int *child_set_tid; /* 在 clone() 中:子进程的 TID 设置地址 */
  int *child_clear_tid;/* 在 clone() 中:子进程的 TID 清除地址 */

  struct linux_emuldata_shared *shared;

  int pdeath_signal; /* 父进程死亡信号 */

  LIST_ENTRY(linux_emuldata) threads; /* linux 线程的链表 */
};

PID 用于标识附加此结构的 FreeBSD 进程。child_set_tid 和 child_clear_tid 用于在进程退出和创建时进行 TID 地址的拷贝。shared 指针指向一个在线程之间共享的结构体。pdeath_signal 变量标识父进程死亡信号,而 threads 指针用于将此结构链接到线程列表中。linux_emuldata_shared 结构如下所示:

struct linux_emuldata_shared {

  int refs;

  pid_t group_pid;

  LIST_HEAD(, linux_emuldata) threads; /* linux 线程链表的头部 */
};

refs 是一个引用计数器,用于确定何时可以释放结构体,以避免内存泄漏。group_pid 用于标识整个进程(即线程组)的 PID(=TGID)。threads 指针是该进程中所有线程的链表头。

可以通过 em_find 函数从进程中获取 linux_emuldata 结构。该函数的原型如下:

struct linux_emuldata *em_find(struct proc *, int locked);

其中,proc 是我们想要从中获取仿真数据结构的进程,locked 参数决定是否需要加锁。接受的值为 EMUL_DOLOCK 和 EMUL_DOUNLOCK。关于加锁的内容稍后讨论。

5.2.3. PID 混乱

由于 FreeBSD 和 Linux® 在进程 ID 和线程 ID 的概念上有所不同,我们必须以某种方式转换这种视图。我们通过 PID 混乱来实现这一点。这意味着我们在内核和用户空间之间伪造 PID(=TGID)和 TID(=PID)。大致规则是,在内核中(在 Linuxulator 中)PID = PID,而 TGID = shared → group_pid,在用户空间中,我们呈现 PID = shared → group_pid 和 TID = proc → p_pid。linux_emuldata 结构中的 PID 是一个 FreeBSD PID。

上述内容主要影响 getpid、getppid 和 gettid 系统调用。在这些调用中,我们分别使用 PID/TGID。在 child_clear_tid 和 child_set_tid 的 TID 拷贝中,我们拷贝的是 FreeBSD PID。

5.2.4. Clone 系统调用

clone 系统调用是 Linux® 中创建线程的方式。该系统调用的原型如下所示:

int linux_clone(l_int flags, void *stack, void *parent_tidptr, int dummy,
void * child_tidptr);

5.2.5. 锁

锁的实现是按子系统来进行的,因为我们不期望会有大量的竞争。这里有两个锁:emul_lock 用于保护 linux_emuldata 的操作,emul_shared_lock 用于操作 linux_emuldata_shared。emul_lock 是一个不可睡眠的阻塞互斥锁,而 emul_shared_lock 是一个可睡眠的阻塞 sx_lock。由于按子系统锁定的方式,我们可以合并一些锁,因此 em_find 提供了不锁定的访问方式。

5.3. TLS

本节讨论 TLS,即线程局部存储。

5.3.1. 线程简介

5.3.2. i386 上的段

i386 架构实现了所谓的段。段是描述一块内存区域的结构。该内存区域的基址(底部)、结束地址(顶端)、类型、保护等信息。可以使用段选择器寄存器(%cs、%ds、%ss、%es、%fs、%gs)来访问该内存区域。例如,假设我们有一个基地址为 0x1234 且长度为 0x1000 的段,执行以下代码:

mov %edx,%gs:0x10

这将把 %edx 寄存器的内容加载到内存位置 0x1244。一些段寄存器有特殊用途,例如 %cs 用于代码段,%ss 用于栈段,但 %fs 和 %gs 通常未被使用。段通常存储在全局 GDT 表中,或者存储在本地 LDT 表中。LDT 通过 GDT 中的一个条目来访问。LDT 可以存储更多类型的段,可以为每个进程单独设置 LDT。两个表最多可以定义 8191 个条目。

5.3.3. 在 Linux® i386 上的实现

在 Linux® 中设置 TLS 主要有两种方式。可以在克隆进程时通过 clone 系统调用设置,或者可以调用 set_thread_area。当进程传递 CLONE_SETTLS 标志给 clone 时,内核期望 %esi 寄存器指向 Linux® 用户空间表示的段,该段被转换为机器表示的段,并加载到 GDT 插槽中。可以使用数字指定 GDT 插槽,也可以使用 -1,表示系统应该选择第一个空闲插槽。实际上,大多数程序只使用一个 TLS 项目,并且不关心条目的数量。我们在模拟中利用了这一点,实际上也依赖于这一点。

5.3.4. Linux® TLS 的仿真

5.3.4.1. i386

当前线程的 TLS 加载通过调用 set_thread_area 实现,而第二个进程的 TLS 加载则在 clone 中的单独块中完成。这两个函数非常相似,唯一的区别是 GDT 段的实际加载,这发生在为新创建的进程进行下一个上下文切换时,而 set_thread_area 必须直接加载该段。代码基本上是这样做的:它将 Linux® 形式的段描述符从用户空间复制出来,检查描述符的数量,但由于 FreeBSD 和 Linux® 之间的差异,我们对其进行伪造。我们只支持 6、3 和 -1 索引。6 是 Linux® 的真实数字,3 是 FreeBSD 的真实数字,-1 表示自动选择。然后我们将描述符号设置为常数 3,并将其复制到用户空间。我们依赖于用户空间进程使用来自描述符的数字,但大多数情况下这没有问题(我们从未见过不工作的情况),因为用户空间进程通常传入 1。接着我们将描述符从 Linux® 形式转换为机器无关的形式(即操作系统无关形式),并将其复制到 FreeBSD 定义的段描述符中。最后,我们加载它。我们将该描述符分配给线程的 PCB(进程控制块),并使用 load_gs 加载 %gs 段。加载必须在临界区内进行,以确保没有中断我们。CLONE_SETTLS 情况下,操作与此完全相同,只是加载操作使用 load_gs 并没有执行。此用途的段(段号 3)在 FreeBSD 进程和 Linux® 进程之间共享,因此 Linux® 仿真层不会比纯 FreeBSD 增加额外的开销。

5.3.4.2. amd64

amd64 的实现与 i386 类似,但最初没有为此目的使用 32 位段描述符(因此甚至本地 32 位 TLS 用户也无法工作),所以我们不得不添加这样的段并在每次上下文切换时实现其加载(当设置了使用 32 位的标志时)。除此之外,TLS 加载完全相同,只是段号不同,描述符格式和加载方式稍有不同。

5.4. Futex

5.4.1. 同步介绍

线程需要某种同步机制,而 POSIX® 提供了其中一些:用于互斥的互斥锁、具有偏置读写比率的读写锁以及用于信号状态变化的条件变量。有趣的是,POSIX® 线程 API 不支持信号量。同步例程的实现很大程度上依赖于我们拥有的线程支持类型。在纯 1:M(用户空间)模型中,实施可以完全在用户空间进行,因此非常快速(条件变量可能最终通过信号实现,即不是非常快速),并且很简单。在 1:1 模型中,情况也很清楚——线程必须使用内核设施进行同步(这非常慢,因为必须执行系统调用)。混合的 M:N 场景则结合了前两种方法,或者完全依赖于内核。线程同步是线程启用编程的一个关键部分,它的性能可能极大地影响结果程序。最近 FreeBSD 操作系统的基准测试表明,改进的 sx_lock 实现使 ZFS(一个重度使用 sx 的应用)性能提高了 40%,这是内核中的内容,但它清楚地表明了同步原语的性能是多么重要。

线程程序应尽量减少对锁的竞争。否则,线程只是等待锁,而没有做有用的工作。因此,编写得最好的线程程序通常表现出较少的锁竞争。

5.4.2. Futex 介绍

Linux® 实现了 1:1 线程模型,即必须使用内核同步原语。如前所述,编写得很好的线程程序锁竞争较少。因此,一个典型的序列可能是执行两次原子增加/减少互斥锁引用计数,这非常快速,如下面的示例所示:

pthread_mutex_lock(&mutex);
...
pthread_mutex_unlock(&mutex);

1:1 线程模型迫使我们为这些互斥锁调用执行两个系统调用,这非常慢。

Linux® 2.6 实现的解决方案被称为 futexes。Futexes 在用户空间中检查是否发生竞争,仅在发生竞争时才调用内核原语。因此,典型情况下不会有内核干预。这使得同步原语的实现既快速又灵活。

5.4.3. Futex API

futex 系统调用的原型如下所示:

int futex(void *uaddr, int op, int val, struct timespec *timeout, void *uaddr2, int val3);

在这个例子中,uaddr 是用户空间中互斥锁的地址,op 是我们要执行的操作,其他参数根据操作的不同有不同的含义。

Futexes 实现了以下操作:

  • FUTEX_WAIT

  • FUTEX_WAKE

  • FUTEX_FD

  • FUTEX_REQUEUE

  • FUTEX_CMP_REQUEUE

  • FUTEX_WAKE_OP

5.4.3.1. FUTEX_WAIT

此操作验证在地址 uaddr 处的值是否为 val。如果不是,返回 EWOULDBLOCK,否则线程会被排队到 futex 并被挂起。如果 timeout 参数非零,它指定最大睡眠时间,否则睡眠将是无限期的。

5.4.3.2. FUTEX_WAKE

此操作会在 uaddr 处取出一个 futex,并唤醒排队在此 futex 上的 val 个 futex。

5.4.3.3. FUTEX_FD

此操作将文件描述符与给定的 futex 关联。

5.4.3.4. FUTEX_REQUEUE

此操作会从 uaddr 处的 futex 上唤醒 val 个排队的线程,并将 val2 个线程重新排队到 uaddr2 处的 futex 上。

5.4.3.5. FUTEX_CMP_REQUEUE

此操作与 FUTEX_REQUEUE 相同,但首先会检查 val3 是否等于 val。

5.4.3.6. FUTEX_WAKE_OP

该操作对 val3(其中包含某些其他值)和 uaddr 执行原子操作。然后,它唤醒 uaddr 上的 val 个 futex 线程,如果原子操作返回一个正数,则它会唤醒 uaddr2 上的 val2 个 futex 线程。

FUTEX_WAKE_OP 中实现的操作如下:

  • FUTEX_OP_SET

  • FUTEX_OP_ADD

  • FUTEX_OP_OR

  • FUTEX_OP_AND

  • FUTEX_OP_XOR

注意

futex 原型中没有 val2 参数。val2 通过 struct timespec *timeout 参数获取,适用于操作 FUTEX_REQUEUE、FUTEX_CMP_REQUEUE 和 FUTEX_WAKE_OP。

5.4.4. Futex 在 FreeBSD 中的仿真

FreeBSD 中的 futex 仿真来源于 NetBSD,并由我们进一步扩展。它位于 linux_futex.c 和 linux_futex.h 文件中。futex 结构如下:

struct futex {
  void *f_uaddr;
  int f_refcount;

  LIST_ENTRY(futex) f_list;

  TAILQ_HEAD(lf_waiting_paroc, waiting_proc) f_waiting_proc;
};

waiting_proc 结构如下:

struct waiting_proc {

  struct thread *wp_t;

  struct futex *wp_new_futex;

  TAILQ_ENTRY(waiting_proc) wp_list;
};

5.4.4.1. futex_get / futex_put

使用 futex_get 函数获取 futex,它在 futex 的线性列表中搜索并返回找到的 futex,或者创建一个新的 futex。释放 futex 时调用 futex_put 函数,该函数减少 futex 的引用计数,如果引用计数为零,则释放该 futex。

5.4.4.2. futex_sleep

5.4.4.3. futex_wake

在 futex_wake 函数中唤醒在 futex 上睡眠的线程。首先,我们在此函数中模仿了 Linux® 的奇怪行为,所有操作都会唤醒 N 个线程,唯一的例外是 REQUEUE 操作,它会唤醒 N+1 个线程。但通常这不会产生任何差异,因为我们唤醒了所有线程。接下来,在循环中唤醒 n 个线程后,我们会检查是否有新的 futex 需要重新排队。如果有,我们会将最多 n2 个线程重新排队到新的 futex 上。这与 futex_sleep 协作。

5.4.4.4. futex_wake_op

FUTEX_WAKE_OP 操作比较复杂。首先,我们获取地址为 uaddr 和 uaddr2 的两个 futex,然后使用 val3 和 uaddr2 执行原子操作。然后,唤醒 val 个等待在第一个 futex 上的线程,如果原子操作条件成立,我们会唤醒 val2(即 timeout)个等待在第二个 futex 上的线程。

5.4.4.5. futex 原子操作

原子操作接受两个参数:encoded_op 和 uaddr。编码操作将操作本身、比较值、操作参数和比较参数进行编码。该操作的伪代码如下所示:

oldval = *uaddr2
*uaddr2 = oldval OP oparg

这个操作是原子执行的。首先,从 uaddr 复制数字,然后执行操作。代码处理了页错误,如果没有发生页错误,则 oldval 会与 cmparg 参数进行比较,使用指定的比较操作符。

5.4.4.6. Futex 锁定

Futex 实现使用了两种锁列表来保护 sx_lock 和全局锁(无论是 Giant 还是其他 sx_lock)。每个操作从开始到结束都在锁定状态下进行。

5.5. 各种系统调用实现

在本节中,我将描述一些较小的系统调用,这些系统调用的实现比较特殊,或者从其他角度来看,它们的实现值得关注。

5.5.1. *at 系列系统调用

在 Linux® 2.6.16 内核的开发过程中,添加了 *at 系列的系统调用。例如,openat 就是这样一个系统调用,它的工作方式与没有 at 的对应调用几乎相同,唯一的区别在于 dirfd 参数。该参数决定了给定文件的位置,在执行系统调用时,filename 参数是相对路径时,dirfd 就变得非常重要。dirfd 参数是一个目录文件描述符,或者是 AT_FDCWD。例如,openat 系统调用可以像下面这样:

文件描述符 123 = /tmp/foo/, 当前工作目录 = /tmp/

openat(123, /tmp/bah, flags, mode)    /* 打开 /tmp/bah */
openat(123, bah, flags, mode)         /* 打开 /tmp/foo/bah */
openat(AT_FDCWD, bah, flags, mode)    /* 打开 /tmp/bah */
openat(stdio, bah, flags, mode)       /* 返回错误,因为 stdio 不是一个目录 */

这种机制的必要性在于避免在打开文件时发生竞争条件,尤其是在工作目录之外。假设一个进程包含两个线程,线程 A 和线程 B。线程 A 执行 open(./tmp/foo/bah, flags, mode) 系统调用,但在返回之前,线程 A 被抢占,接着线程 B 执行并不关心线程 A 的操作,可能会重命名或删除 /tmp/foo/ 目录。这样就会发生竞争条件。为了解决这个问题,我们可以先打开 /tmp/foo 目录,并使用它作为 dirfd,然后再执行 openat 系统调用。这样还可以实现每个线程的工作目录。

5.5.1.1. 实现

openat() --> kern_openat() --> vn_open() -> namei()

因此,kern_open 和 vn_open 必须做相应的修改,以支持额外的 dirfd 参数。对于这些系统调用,并没有创建兼容层,因为它们的用户不多,且用户可以容易地进行转换。这种通用的实现方式使 FreeBSD 可以实现自己的 *at 系统调用。目前,这项工作仍在讨论中。

5.5.2. Ioctl

ioctl 接口由于其通用性而变得相当脆弱。我们必须考虑到设备在 Linux® 和 FreeBSD 之间的差异,因此在执行 ioctl 仿真时必须小心。ioctl 的处理在 linux_ioctl.c 文件中实现,其中定义了 linux_ioctl 函数。该函数简单地遍历一组 ioctl 处理程序,查找能够实现给定命令的处理程序。ioctl 系统调用有三个参数:文件描述符、命令和一个参数。命令是一个 16 位数字,理论上高 8 位确定命令的类别,低 8 位则是该类别内的具体命令。仿真程序利用这个划分实现。我们为每个类别实现处理程序,比如 sound_handler 或 disk_handler。每个处理程序都有最大和最小命令的定义,用于确定使用哪个处理程序。尽管如此,这种方法也存在一些问题,因为 Linux® 并不总是按类别划分 ioctl 命令,有时不同类别的 ioctl 会被放在不应属于的类别中(例如,将 SCSI 通用 ioctl 放到 cdrom 类别中)。目前,FreeBSD 并没有实现很多 Linux® 的 ioctl(与 NetBSD 相比),但计划从 NetBSD 移植一些。趋势是,即便在 FreeBSD 原生驱动中,也开始使用 Linux® 的 ioctl,因为这使得应用程序的移植变得更加容易。

5.5.3. 调试

每个系统调用都应该支持调试。为此,我们引入了一个小的调试基础设施。我们有一个 ldebug 功能,它会告知是否应对某个系统调用进行调试(可以通过 sysctl 设置)。为了打印调试信息,我们使用了 LMSG 和 ARGS 宏。这些宏用于调整打印的字符串,以实现统一的调试信息输出。

6. 结论

6.1. 结果

截至 2007 年 4 月,Linux® 仿真层已经能够相当好地仿真 Linux® 2.6.16 内核。剩余的问题主要集中在 futex、未完成的 *at 系列系统调用、信号传递问题、缺失的 epoll 和 inotify,以及可能存在的未发现的 BUG。尽管如此,我们已经能够运行几乎所有 FreeBSD Ports 中包含的 Linux® 程序(如 Fedora Core 4 版本的 2.6.16),并且有一些成功的初步报告表明 Fedora Core 6 版本的 2.6.16 也能运行。最近提交的 Fedora Core 6 linux_base 使得我们能够进一步测试仿真层,并为我们提供了一些线索,指出了在哪些地方我们应该投入更多精力来实现缺失的功能。

我们希望在 FreeBSD 7.0 发布后不久默认启用 2.6.16 仿真,以便进行更广泛的测试。完成这一切后,我们就可以切换到 Fedora Core 6 linux_base,这也是我们的最终计划。

6.2. 后续工作

未来的工作应集中在修复与 futex 相关的剩余问题,完成其余 *at 系列系统调用的实现,修复信号传递问题,并可能实现 epoll 和 inotify 功能。

我们希望能够尽快无缝运行最重要的程序,这样我们就能默认切换到 2.6 仿真,并将 Fedora Core 6 设为默认的 linux_base,因为我们当前使用的 Fedora Core 4 已不再受支持。

另一个可能的目标是与 NetBSD 和 DragonflyBSD 共享我们的代码。NetBSD 对 2.6 仿真有一些支持,但还远未完成且没有经过充分测试。DragonflyBSD 已表达出将 2.6 改进移植过来的兴趣。

一般来说,随着 Linux® 的发展,我们希望能跟进其发展,实施新添加的系统调用,首先想到的是 splice。一些已实现的系统调用也存在一些优化空间,例如 mremap 等。还可以进行一些性能改进,精细化锁等。

6.3. 团队

我在这个项目中与以下人员合作(按字母顺序排列):

  • Emmanuel Dreyfus

  • Scot Hetzel

  • Li Xiao

我想感谢所有这些人对我的建议、代码审查和一般支持。

7. 文献

  1. Marshall Kirk McKusick - George V. Nevile-Neil. 《FreeBSD 操作系统的设计与实现》。Addison-Wesley, 2005。

对 FreeBSD 中 IPsec 功能的独立验证

摘要

你已安装了 IPsec,并且它似乎在工作。你如何确认这一点呢?我将介绍一种实验方法来验证 IPsec 是否工作。

1. 问题

2. 解决方案

首先,来一些与加密相关的信息理论:

  1. 加密数据是均匀分布的,即每个符号具有最大的熵;

  2. 原始未压缩的数据通常是冗余的,即具有次最大熵。

假设你可以测量通过你的网络接口的进出数据的熵。那么你就可以看到未加密数据与加密数据之间的区别。即使在“加密模式”中某些数据没有被加密——因为如果数据包需要被路由,最外层的 IP 头部必须是加密的——这也是成立的。

2.1. MUST

2.2. Tcpdump

命令:

tcpdump -c 4000 -s 10000 -w dumpfile.bin

将捕获 4000 个原始数据包并保存到 dumpfile.bin 文件中。在此示例中,每个数据包最多捕获 10,000 字节。

3. 实验

这是实验步骤:

  1. 打开一个连接到 IPsec 主机的窗口和一个连接到不安全主机的窗口。

  2. % tcpdump -c 4000 -s 10000 -w ipsecdemo.bin
    % uliscan ipsecdemo.bin
    Uliscan 21 Dec 98
    L=8 256 258560
    Measuring file ipsecdemo.bin
    Init done
    Expected value for L=8 is 7.1836656
    6.9396 --------------------------------------------------------
    6.6177 -----------------------------------------------------
    6.4100 ---------------------------------------------------
    2.1101 -----------------
    2.0838 -----------------
    2.0983 -----------------

4. 警告

该实验表明,IPsec 确实 似乎在分发负载数据时是 均匀 的,正如加密应该做的那样。然而,本文所述的实验 不能 检测出系统中的许多潜在缺陷(目前我没有任何证据表明存在这些缺陷)。这些缺陷包括糟糕的密钥生成或交换、数据或密钥被他人看到、使用弱算法、内核被篡改等。请研究源代码,了解代码的实现。

5. IPsec 定义

IPsec 是对 IPv4 的扩展;IPv6 中是必需的。它是一种在 IP(主机到主机)层面上协商加密和身份验证的协议。SSL 仅保护一个应用程序套接字;SSH 仅保护登录;PGP 仅保护指定的文件或消息。IPsec 加密主机之间的一切。

6. 安装 IPsec

7. src/sys/i386/conf/KERNELNAME

device	bpf

8. Maurer 的通用统计测试(MUST,对于块大小=8位)

/*
  ULISCAN.c   --- 8 位块大小

  1998 年 10 月1 日
  1998 年 12 月 1 日
  1998 年 12 月 21 日       uliscan.c 源自 ueli8.c

  该版本已去除 // 注释,以便于 Sun cc 编译器使用

  该程序实现了 Ueli M Maurer 的 "随机比特生成器的通用统计测试",使用 L=8

  接受命令行上的文件名;将结果和其他信息写入标准输出(stdout)。

  能够优雅地处理输入文件耗尽的情况。

  参考文献:J. Cryptology v 5 no 2, 1992 页 89-105
  也可以在某些网站上找到,那里是我找到它的地方。

  -David Honig
  honig@sprynet.com

  用法:
  ULISCAN 文件名
  输出到标准输出(stdout)
*/

#define L 8
#define V (1<<L)
#define Q (10*V)
#define K (100   *Q)
#define MAXSAMP (Q + K)

#include <stdio.h>
#include <math.h>

int main(argc, argv)
int argc;
char **argv;
{
  FILE *fptr;
  int i,j;
  int b, c;
  int table[V];
  double sum = 0.0;
  int iproduct = 1;
  int run;

  extern double   log(/* double x */);

  printf("Uliscan 21 Dec 98 \nL=%d %d %d \n", L, V, MAXSAMP);

  if (argc < 2) {
    printf("Usage: Uliscan filename\n");
    exit(-1);
  } else {
    printf("Measuring file %s\n", argv[1]);
  }

  fptr = fopen(argv[1],"rb");

  if (fptr == NULL) {
    printf("Can't find %s\n", argv[1]);
    exit(-1);
  }

  for (i = 0; i < V; i++) {
    table[i] = 0;
  }

  for (i = 0; i < Q; i++) {
    b = fgetc(fptr);
    table[b] = i;
  }

  printf("Init done\n");

  printf("Expected value for L=8 is 7.1836656\n");

  run = 1;

  while (run) {
    sum = 0.0;
    iproduct = 1;

    if (run)
      for (i = Q; run && i < Q + K; i++) {
        j = i;
        b = fgetc(fptr);

        if (b < 0)
          run = 0;

        if (run) {
          if (table[b] > j)
            j += K;

          sum += log((double)(j-table[b]));

          table[b] = i;
        }
      }

    if (!run)
      printf("Premature end of file; read %d blocks.\n", i - Q);

    sum = (sum/((double)(i - Q))) /  log(2.0);
    printf("%4.4f ", sum);

    for (i = 0; i < (int)(sum*8.0 + 0.50); i++)
      printf("-");

    printf("\n");

    /*重新填充初始表格 */
    if (0) {
      for (i = 0; i < Q; i++) {
        b = fgetc(fptr);
        if (b < 0) {
          run = 0;
        } else {
          table[b] = i;
        }
      }
    }
  }
}

镜像 FreeBSD

摘要

这篇正在进行中的文章介绍了如何镜像 FreeBSD,旨在为集群管理员提供帮助。

注意

我们现在不接受新的社区镜像。

1. 联系信息

2. FreeBSD 镜像要求

2.1. 磁盘空间

磁盘空间是最重要的要求之一。根据你希望镜像的发布版本、架构和完整性要求,可能会消耗大量的磁盘空间。还要记住,官方镜像可能要求必须是完整的。网页应该始终被完整地镜像。请注意,这里列出的数字反映了当前的状态(在 12.0-RELEASE/11.3-RELEASE 时)。进一步的开发和发布只会增加所需的空间量。同时,确保预留一些(大约 10-20%)额外空间,以防万一。以下是一些近似数字:

  • 完整的 FTP 发行版:1.4 TB

  • CTM 增量:10 GB

  • 网页:1 GB

2.2. 网络连接/带宽

当然,你需要连接到互联网。所需的带宽取决于你计划如何使用镜像。如果你只是想为本地站点/内网镜像 FreeBSD 的某些部分,需求可能会比想要将文件公开提供的需求小。如果你打算成为官方镜像,所需的带宽将更高。我们只能在这里给出粗略的估算:

  • 本地站点,无公开访问:基本上没有最低要求,但带宽小于 2 Mbps 可能会导致同步太慢。

  • 非官方公共站点:大约 34 Mbps 是一个不错的起点。

  • 官方站点:推荐带宽大于 100 Mbps,并且你的主机应尽可能靠近边界路由器连接。

2.3. 系统要求,CPU,RAM

这取决于预计的客户端数量,这由服务器的政策决定。它还受你想要提供的服务类型的影响。提供普通的 FTP 或 HTTP 服务可能不需要大量的资源。如果你提供 rsync,这会对 CPU 和内存的需求产生巨大影响,因为 rsync 被认为是个内存消耗大户。以下只是一些示例,给出一个大概的提示。

对于适度访问的站点,提供 rsync,你可能需要一颗约 800 MHz 到 1 GHz 的当前 CPU,以及至少 512 MB 的内存。这大概是你为 官方 站点所需要的最低配置。

对于频繁访问的站点,你肯定需要更多的内存(考虑 2 GB 作为一个不错的起点)和可能更多的 CPU,这也可能意味着你需要一个 SMP 系统。

你还需要考虑一个快速的磁盘子系统。SVN 仓库的操作需要一个快速的磁盘子系统(强烈建议使用 RAID)。具有自己缓存的 SCSI 控制器也能加速操作,因为这些服务大多会对磁盘进行大量的小修改。

2.4. 提供的服务

每个镜像站点必须提供一组核心服务。除了这些必需的服务外,服务器管理员还可以选择提供其他可选服务。本节将解释你可以提供哪些服务,以及如何实施它们。

2.4.1. FTP(FTP 文件集必需)

这是最基本的服务之一,每个提供公共 FTP 发行版的镜像都必须提供此服务。FTP 访问必须是匿名的,并且不允许上传/下载比率(这本来就是个荒谬的要求)。不需要上传权限(并且 绝不允许对 FreeBSD 文件空间进行上传)。FreeBSD 存档应该位于路径 /pub/FreeBSD 下。

有很多软件可以用来设置匿名 FTP(按字母顺序排列):

FreeBSD 的 ftpd、proftpd 以及可能的 ncftpd 是最常用的 FTP 服务器。其他的在镜像站点中用户不多。需要考虑的一点是,你可能需要灵活限制允许的同时连接数,从而限制消耗的网络带宽和系统资源。

2.4.2. Rsync(FTP 文件集的可选服务)

Rsync 通常用于访问 FreeBSD FTP 区域的内容,这样其他镜像站点可以使用你的系统作为源。该协议与 FTP 在许多方面不同。它更加节省带宽,因为仅在文件发生变化时传输文件的差异,而不是传输整个文件。Rsync 对每个实例要求大量的内存。内存大小取决于同步模块的大小(即目录和文件的数量)。Rsync 可以使用 rsh 和 ssh(现在是默认的)作为传输方式,或者使用其自己的协议进行独立访问(这是公共 rsync 服务器的首选方法)。可以应用认证、连接限制和其他限制。只有一个软件包可供使用:

2.4.3. HTTP(网页必需,FTP 文件集的可选服务)

如果你希望提供 FreeBSD 网页,你需要安装一台 Web 服务器。你也可以选择通过 HTTP 提供 FTP 文件集。Web 服务器软件的选择由镜像管理员决定。一些最流行的选择包括:

3. 如何镜像 FreeBSD

现在你已经了解了要求以及如何提供服务,但还不知道如何获取这些内容。:-) 本节将解释如何实际镜像 FreeBSD 的各个部分,使用什么工具,以及从哪里镜像。

3.1. 镜像 FTP 站点

FTP 区域是需要镜像的最大数据量。它包括用于网络安装的 分发集、实际检出的源树快照的 分支、用于写入安装分发的 ISO 镜像、一个实时文件系统以及 Ports 树的快照。所有这些内容涵盖不同版本的 FreeBSD,以及不同架构。

注意

由于 rsync 客户端数量对服务器机器有显著影响,大多数管理员会对其服务器施加限制。对于镜像站,你应该向你同步的站点管理员询问他们的政策,并可能为你的主机申请一个例外(因为你是镜像站)。

镜像 FreeBSD 的命令行可能如下所示:

% rsync -vaHz --delete rsync://ftp4.de.FreeBSD.org/FreeBSD/ /pub/FreeBSD/

3.2. 镜像 WWW 页面

警告

由于自 2021 年 1 月 25 日文档迁移到 Hugo/Asciidoctor,使用 rsync 镜像网站不再有效。

对于以前的网站镜像,今天实现网站镜像的一种方法是使用相应的地址在本地构建网站,并使其可托管。

% cd website && env HUGO_baseURL="https://www.XX.freebsd.org/" make

注意

3.3. 镜像包

% pkg fetch -d -o /usr/local/mirror vim

这些包被获取后,必须通过运行以下命令来生成仓库元数据:

% pkg repo /usr/local/mirror

3.4. 我应该多久镜像一次?

  1. 将镜像应用程序的命令放入一个脚本中。建议使用纯粹的 /bin/sh 脚本。

  2. 添加一些输出重定向,以便将诊断信息记录到文件中。

  3. 测试脚本是否有效,检查日志。

以下是一些推荐的更新频率:

  • FTP 文件集:每日

  • WWW 页面:每日

4. 从哪里镜像

这是一个重要的问题。因此,本节将花一些时间解释背景。我们会多次提到:在任何情况下,都不应从 ftp.FreeBSD.org 镜像。

4.1. 关于组织的一些话

镜像按国家组织。所有官方镜像都有类似 ftpN.CC.FreeBSD.org 的 DNS 条目。CC(即国家代码)是该镜像所在国家的 顶级域名(TLD)。N 是一个数字,表示该主机在该国家的 第 N 个镜像。(同样适用于 wwwN.CC.FreeBSD.org 等)。也有没有 CC 部分的镜像站。这些镜像站连接非常好,能大量并发用户。ftp.FreeBSD.org 实际上是两台机器,一台位于丹麦,另一台位于美国。它并不是主站,绝不能用来作为镜像源。许多在线文档将“交互式”用户引导到 ftp.FreeBSD.org,因此自动化的镜像系统应从其他机器获取镜像。

4.2. 好的,那我应该从哪里获取镜像?

在任何情况下,都不应从 ftp.FreeBSD.org 镜像。简短的答案是:从离你最近的站点,或者给你最快访问的站点获取。

4.2.1. 我只想从某个地方镜像!

  1. 检查哪些站点提供最快的访问(跳数、往返时间),并提供你打算使用的服务(如 rsync)。

  2. 联系你选择的站点的管理员,说明你的请求,并询问他们的条款和政策。

  3. 按照上面描述的方式设置你的镜像。

4.2.2. 我是一个官方镜像,适合我的正确站点是哪个?

4.2.3. 我想访问主站点!

有一个用于 FTP 文件集的主站点。

4.2.3.1. ftp-master.FreeBSD.org

这是 FTP 文件集的主站点。

还鼓励镜像站点允许访问 FTP 内容的 rsync,因为它们是 Tier-1 镜像站点。

5. 官方镜像

官方镜像是指:

  • a) 具有 FreeBSD.org DNS 条目(通常是 CNAME)。

  • b) 在 FreeBSD 文档(如手册)中列为官方镜像。

目前官方镜像的区分标准就是这些。官方镜像不一定是 Tier-1 镜像。但你很可能不会找到不是官方的 Tier-1 镜像。

5.1. 官方(Tier-1)镜像的特殊要求

对于所有官方镜像来说,要求并不容易一概而论,因为该项目对此有一定的宽容度。更容易说明的是 官方 Tier-1 镜像 的要求。其他官方镜像可以认为这些要求是一个很大的 应该。

Tier-1 镜像需要:

  • 托管完整的文件集。

  • 允许其他镜像站访问。

  • 提供 FTP 和 rsync 访问。

重要

5.2. 如何成为官方镜像?

6. 来自镜像站点的一些统计数据

以下是你喜欢的镜像站点的统计页面链接(即那些愿意提供统计数据的站点)。

6.1. FTP 站点统计数据

Port 导师指南

  • 作者:Thomas Abthorpe, Chris Rees

1. 导师/学员关系指南

本节旨在帮助解开导师关系的谜团,并作为公开促进建设性讨论的方式,以适应和发展这些指南。在我们的生活中有太多的规则;我们不是个强制执行法规的政府组织,而是由志同道合的人们组成的集体,致力于共同目标,保持我们称之为 Ports 的产品质量保证。

1.1. 为什么做导师?

  • 对于我们中的大多数人来说,都是在项目中得到了导师的指导,因此回报这个恩惠,向其他人提供指导。

  • 你有一种不可抗拒的冲动,想要将知识传授给别人。

  • 通常的“惩罚”是因为你已经厌倦了贡献别人做的好工作!

1.2. 导师/共同导师

共同导师的理由:

  • 显著的时区差异。通过即时通讯可以方便互动的导师非常有帮助!

  • 可能存在语言障碍。是的,FreeBSD 主要以英语为主,就像大多数软件开发一样,但是,如果有一个能讲母语的导师,那将非常有用。

  • 没有时间!直到有了 30 小时的一天和 8 天的一周,我们中的一些人只能提供有限的时间。与其他人分担负担会更容易。

  • 初级导师可以从资深提交者/导师那里获得经验。

  • 两个头脑总比一个好。

单独担任导师的理由:

  • 你不喜欢与他人合作。

  • 你更喜欢一对一的关系。

  • 共同导师的理由对你不适用。

1.3. 期望

我们期望导师能够审查并测试构建所有提出的补丁,至少在最初的一个多星期或两周内进行。

我们期望导师应对其学员的行为负责。导师应跟进学员所做的所有提交,无论是已批准的还是隐性提交。

1.4. 选择学员

没有明确的规则来判断候选人是否准备好;它可以是他们提交的 PR 数量、维护的 Port 数量、Port 更新的频率或在 GNOME、KDE、Gecko 等特定领域的参与程度的组合。

候选人应该几乎没有超时,能够及时回应请求,并且在支持其 Port 上通常是乐于助人的。

必须有一定的参与历史,因为普遍认为培训一名提交者需要时间和精力。如果某人已经在社区里呆了更长时间,并花时间观察事情是如何做的,那么可以预期其积累了一些知识。我们常常看到有的维护者提交了几个 PR,出现在 IRC 上,问什么时候可以获得提交权限。

订阅并关注邮件列表非常有益。没有真正的期望认为在邮件列表上发布帖子会使某人成为提交者,但它展示了参与。有些邮件能提供候选人的知识见解,以及他们如何与他人互动。同样,参与 IRC 也可以让某人提高知名度。

如果你问六个不同的提交者,一个维护者在被提名之前应该提交多少个 PR,你将得到六个不同的答案。如果问这些人应该参与多久,也会是同样的困境。最低应该有多少个 Port?现在我们有“分歧”!有些事情很难量化,导师只能凭借他们的最佳判断,期望 portmgr 同意。

1.5. 导师关系的持续时间

随着信任水平的发展,学员可能会被授予“隐性”提交权限。这可以包括对 Makefile、pkg-descr 等的小修改。同样,它也可以包括不包含 plist 更改的 PORTVERSION 更新。在导师的酌情决定下,可能会形成其他情况。然而,在导师关系期间,影响依赖 Port 的版本提升应该由导师检查。

正如我们每个人都是不同的个体,每位学员有不同的学习曲线、时间承诺和其他影响因素,这些因素会影响他们何时能够“独立操作”。经验上,学员应该观察至少 3 个月。90-100 次提交是导师可以使用的另一目标,之后可以考虑让学员独立。其他考虑因素包括他们可能犯过的错误、收到的 QAT 等。如果他们仍然犯新手错误,仍然需要导师指导。

1.6. 导师/共同导师辩论

当一个请求提交给 portmgr 时,通常是:“我提议将 'foo' 提名为 Port 提交者,我将与 'bar' 一起共同担任导师。” 提案接收、投票并通过。

导师是主要的联络人,或者说是“首席”,共同导师是备用。

1.7. 期望

我们期望学员准备好接受来自社区的建设性批评。社区中仍有许多“经验”,但这些经验并未写下来。我们希望通过首先审查学员在 IRC 和邮件列表上的现有贡献来挑选那些能够很好回应建设性批评的人。

我们警告学员,某些收到的批评可能不那么“建设性”(可能是语言沟通问题,或者过于挑剔),并且优雅地处理这些批评是成为大型社区一员的一部分。如果与某些人有特定问题,或有任何疑问,我们希望学员能够通过 IRC 或电子邮件联系 portmgr 成员。

在没有远程控制台的情况下远程安装 FreeBSD 操作系统

摘要

1. 背景

世界上有许多服务器托管提供商,但罕有提供官方支持 FreeBSD 的公司。他们通常提供支持 Linux® 发行版安装的服务器。

在某些情况下,如果你提出请求,这些公司会为你安装你首选的 Linux® 发行版。通过这种方式,我们将尝试安装 FreeBSD。在其他情况下,他们可能会提供一个应急救援系统,这通常在发生紧急情况时使用。我们也可以将其用于我们的目的。

本文介绍了所需的基本安装和配置步骤,以便通过 RAID-1 和 ZFS 功能进行远程安装 FreeBSD。

2. 介绍

本节将总结本文的目的,并更好地解释其中的内容。本文中的说明将对使用不支持 FreeBSD 的托管服务的用户有所帮助。

  1. 本文的下一部分将描述如何在本地计算机上配置并构建最小化的 FreeBSD 系统。这个版本最终将通过 ramdisk 运行在远程计算机上,这将允许我们使用 sysinstall 工具从 FTP 镜像安装完整的 FreeBSD 操作系统。

  2. 本文的其余部分将描述安装过程本身,以及 ZFS 文件系统的配置。

2.1. 要求

为了成功继续,你必须:

  • 拥有一台可以访问网络的操作系统,并且支持 SSH 访问

  • 理解 FreeBSD 的安装过程

  • 熟悉 sysinstall(8) 工具

  • 手头有 FreeBSD 安装 ISO 映像或 CD

3. 准备——mfsBSD

在 FreeBSD 可以安装到目标系统之前,必须构建一个最小化的 FreeBSD 操作系统映像,该映像将从硬盘启动。这样,新的系统就可以通过网络进行访问,并且可以在没有远程访问系统控制台的情况下完成剩余的安装过程。

可以使用 mfsBSD 工具集来构建一个小型的 FreeBSD 映像。正如 mfsBSD 名称所示("mfs" 意味着 "内存文件系统"),生成的映像完全运行在 ramdisk 中。由于这个特性,硬盘的操作不受限制,因此可以安装完整的 FreeBSD 操作系统。mfsBSD 的主页包含指向工具集最新版本的链接。

请注意,mfsBSD 的内部实现及其如何运作超出了本文的范围。有兴趣的读者应该查阅 mfsBSD 的原始文档,以了解更多详细信息。

下载并解压最新的 mfsBSD 版本,并将你的工作目录切换到存放 mfsBSD 脚本的目录:

# fetch http://mfsbsd.vx.sk/release/mfsbsd-2.1.tar.gz
# tar xvzf mfsbsd-2.1.tar.gz
# cd mfsbsd-2.1/

3.1. mfsBSD 配置

在启动 mfsBSD 之前,需要设置一些重要的配置选项。最重要的配置是网络设置。配置网络选项的最佳方法取决于我们是否事先知道要使用的网络接口类型,以及需要为硬件加载的网络接口驱动程序。接下来,我们将介绍如何在这两种情况下配置 mfsBSD。

另一个需要设置的重要项是 root 密码。可以通过编辑 conf/loader.conf 来完成这项配置。请查看包含的注释内容。

3.1.1. conf/interfaces.conf 方法

当已安装的网络接口卡未知时,可以使用 mfsBSD 的自动检测功能。mfsBSD 的启动脚本可以根据接口的 MAC 地址自动检测要使用的正确驱动程序。我们只需要在 conf/interfaces.conf 中设置以下选项:

mac_interfaces="ext1"
ifconfig_ext1_mac="00:00:00:00:00:00"
ifconfig_ext1="inet 192.168.0.2/24"

不要忘记在 conf/rc.conf 中添加 defaultrouter 信息:

defaultrouter="192.168.0.1"

3.1.2. conf/rc.conf 方法

defaultrouter="192.168.0.1"
ifconfig_re0="inet 192.168.0.2/24"

3.2. 构建 mfsBSD 映像

构建 mfsBSD 映像的过程非常简单。

# mdconfig -a -t vnode -u 10 -f FreeBSD-10.1-RELEASE-amd64-disc1.iso
# mount_cd9660 /dev/md10 /cdrom

由于最近的 FreeBSD 发行版不包含常规的分发包,因此需要从 ISO 映像中的分发档案中提取 FreeBSD 分发文件:

# mkdir DIST
# tar -xvf /cdrom/usr/freebsd-dist/base.txz -C DIST
# tar -xvf /cdrom/usr/freebsd-dist/kernel.txz -C DIST

接下来,构建可启动的 mfsBSD 映像:

# make BASE=DIST

注意

必须从 mfsBSD 目录树的顶层目录(如 ~/mfsbsd-2.1/)运行上述 make 命令。

3.3. 启动 mfsBSD

现在 mfsBSD 映像已经准备好,必须将其上传到运行实时救援系统或预安装 Linux® 发行版的远程系统。完成这项任务最合适的工具是 scp:

# scp disk.img root@192.168.0.2:.

为了正确启动 mfsBSD 映像,它必须放置在给定机器的第一个(可启动)设备上。假设 sda 是第一个可启动的磁盘设备,可以使用以下命令来完成:

# dd if=/root/disk.img of=/dev/sda bs=1m

4. 安装 FreeBSD 操作系统

4.1. 硬盘准备

首先,将所有系统磁盘标记为空。对每个硬盘重复以下命令:

# dd if=/dev/zero of=/dev/ad0 count=2
# fdisk -BI /dev/ad0 ①
# fdisk -BI /dev/ad1
# bsdlabel -wB /dev/ad0s1 ②
# bsdlabel -wB /dev/ad1s1
# bsdlabel -e /dev/ad0s1 ③
# bsdlabel /dev/ad0s1 > /tmp/bsdlabel.txt && bsdlabel -R /dev/ad1s1 /tmp/bsdlabel.txt ④
# gmirror label root /dev/ad[01]s1a ⑤
# gmirror label var /dev/ad[01]s1d
# gmirror label usr /dev/ad[01]s1e
# gmirror label -F swap /dev/ad[01]s1b ⑥
# newfs /dev/mirror/root ⑦
# newfs /dev/mirror/var
# newfs /dev/mirror/usr
  • ① 创建一个覆盖整个磁盘的切片,并初始化给定磁盘的第 0 扇区中的引导代码。对系统中的所有硬盘重复此命令。

  • ② 为每个磁盘写入标准标签,包括启动代码。

  • ④ 导入最近创建的标签到第二个硬盘,使两个硬盘的标签相同。

  • ⑦ 在每个镜像分区上创建 UFS2 文件系统。

4.2. 系统安装

这是最重要的部分。本节将描述如何将 FreeBSD 的最小发行版实际安装到我们在上一节中准备好的硬盘上。为了完成此目标,需要将所有文件系统挂载,以便 sysinstall 可以将 FreeBSD 的内容写入硬盘:

# mount /dev/mirror/root /mnt
# mkdir /mnt/var /mnt/usr
# mount /dev/mirror/var /mnt/var
# mount /dev/mirror/usr /mnt/usr

警告

请注意,这一步非常重要,如果跳过此步骤,sysinstall 将无法安装 FreeBSD。

进入 Distributions 菜单,使用箭头键将光标移至 Minimal,按空格键选择它。本文使用最小发行版以节省网络流量,因为系统将通过 ftp 安装。通过选择 Exit 退出该菜单。

注意

Partition 和 Label 菜单将被跳过,因为它们现在无用。

在 Media 菜单中,选择 FTP。选择最近的镜像,并让 sysinstall 假定网络已配置好。你将返回到 Custom 菜单。

最后,通过选择最后一个选项 Commit 来执行系统安装。安装完成后,退出 sysinstall。

4.3. 安装后步骤

现在 FreeBSD 操作系统应该已经安装完成;然而,过程尚未结束。需要执行一些安装后步骤,以确保 FreeBSD 在未来能够启动,并且能够登录系统。

# chroot /mnt

完成我们的目标,执行以下步骤:

  • 将 GENERIC 内核复制到 /boot/kernel 目录:

    # cp -Rp /boot/GENERIC/* /boot/kernel
  • 创建 /etc/rc.conf、/etc/resolv.conf 和 /etc/fstab 文件。不要忘记正确设置网络信息,并在 /etc/rc.conf 中启用 sshd。/etc/fstab 的内容将类似于以下内容:

    # 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
  • 创建 /boot/loader.conf,其内容如下:

    geom_mirror_load="YES"
    zfs_load="YES"
  • 执行以下命令,使 ZFS 在下次启动时可用:

    # sysrc zfs_enable="YES"
  • 仔细检查你的所有设置。

5. ZFS

如果系统在重启后仍然能够正常启动,现在应该可以登录。欢迎进入刚刚完成的 FreeBSD 系统,整个过程都是通过远程操作完成的,没有使用远程控制台!

# zpool create tank mirror /dev/ad[01]s1f

接下来,创建一些文件系统:

# zfs create tank/ports
# zfs create tank/src
# zfs set compression=gzip tank/ports
# zfs set compression=on tank/src
# zfs set mountpoint=/usr/ports tank/ports
# zfs set mountpoint=/usr/src tank/src

可插拔认证模块(PAM)

摘要

本文介绍了可插拔认证模块(PAM)库的基本原理与机制,解释了如何配置 PAM、如何将 PAM 集成进应用程序、以及如何编写 PAM 模块。

1. 引言

可插拔认证模块(PAM)库是一个通用的、用于认证相关服务的 API,它允许系统管理员仅通过安装新的 PAM 模块来添加新的认证方法,并且可以通过编辑配置文件来修改认证策略。

PAM 由 Sun Microsystems 的 Vipin Samar 和 Charlie Lai 于 1995 年设计和开发,自那以后基本未发生变化。1997 年,Open Group 发布了 X/Open Single Sign-on(XSSO)初步规范,它对 PAM API 进行了标准化,并增加了单点(或者说集成)登录的扩展功能。截至撰写本文时,这项规范尚未成为正式标准。

尽管本文主要聚焦于使用 OpenPAM 的 FreeBSD 5.x,但其内容同样适用于使用 Linux-PAM 的 FreeBSD 4.x,以及其他操作系统,如 Linux 和 Solaris™。

2. 术语与约定

2.1. 定义

PAM 相关术语相当混乱。Samar 与 Lai 的原始论文以及 XSSO 规范都未尝试对参与 PAM 的各方和实体进行正式定义,他们所使用(但未定义)的术语有时具有误导性或模糊性。第一个建立一致且明确术语体系的尝试是 Andrew G. Morgan(Linux-PAM 作者)在 1999 年撰写的一篇白皮书。尽管 Morgan 的术语选择是一次巨大进步,但在本文作者看来仍不尽完善。以下定义在很大程度上受到 Morgan 的启发,试图为所有参与 PAM 的行为体与实体制定精确而明确的术语。

  • account(账户) 申请者希望仲裁者授予的一组凭据。

  • applicant(申请者) 发起认证请求的用户或实体。

  • arbitrator(仲裁者) 拥有验证申请者凭据的权限以及授予或拒绝请求权力的用户或实体。

  • chain(链) 响应 PAM 请求而调用的一系列模块。链中包括了调用模块的顺序、传递给模块的参数,以及如何解释模块返回结果的信息。

  • client(客户端) 代表申请者发起认证请求的应用程序,负责从申请者处获取所需认证信息。

  • facility(功能组) PAM 提供的四类基本功能之一:认证(authentication)、账户管理(account management)、会话管理(session management)以及认证令牌更新(authentication token update)。

  • module(模块) 实现某一特定认证功能组的一组相关函数,集合成一个单独(通常是可动态加载)的二进制文件,并以一个名称标识。

  • policy(策略) 描述如何处理某一服务的 PAM 请求的完整配置语句集。一个策略通常由四条链组成,每条链对应一个功能组,尽管某些服务并不使用全部四个功能组。

  • server(服务器) 代表仲裁者执行操作的应用程序,用于与客户端交互、获取认证信息、验证申请者凭据,并决定是否授予请求。

  • service(服务) 提供相似或相关功能,并具有类似认证需求的一类服务器。PAM 策略以服务为单位定义,所有使用相同服务名的服务器都将遵循相同的策略。

  • session(会话) 服务器为申请者提供服务的上下文环境。PAM 的四个功能组之一 —— 会话管理(session management)—— 专门负责该上下文的创建与销毁。

  • token(令牌) 与账户关联的一段信息,如密码或口令,申请者需提供此信息以证明其身份。

  • transaction(事务) 同一申请者对同一服务器实例发起的一系列请求,从认证与会话建立开始,到会话结束为止。

2.2. 使用示例

本节通过一些简单的示例,说明上述定义的一些术语的含义。

2.2.1. 客户端和服务器为同一实体

% whoami
alice

% ls -l `which su`
-r-sr-xr-x  1 root  wheel  10744 Dec  6 19:06 /usr/bin/su

% su -
Password: xi3kiune
# whoami
root
  • 申请者是 alice。

  • 账户是 root。

  • 认证令牌是 xi3kiune。

2.2.2. 客户端和服务器为分离实体

% whoami
eve

% ssh bob@login.example.com
bob@login.example.com's password:
% god
Last login: Thu Oct 11 09:52:57 2001 from 192.168.0.1
Copyright (c) 1980, 1983, 1986, 1988, 1990, 1991, 1993, 1994
	The Regents of the University of California.  All rights reserved.
FreeBSD 4.4-STABLE (LOGIN) 4: Tue Nov 27 18:10:34 PST 2001

Welcome to FreeBSD!
%
  • 申请者是 eve。

  • 账户是 bob。

  • 认证令牌是 god。

  • 尽管本示例中未显示,但仲裁者是 root。

2.2.3. 示例策略

以下是 FreeBSD 默认的 sshd 策略:

sshd	auth		required	pam_nologin.so	no_warn
sshd	auth		required	pam_unix.so	no_warn try_first_pass
sshd	account		required	pam_login_access.so
sshd	account		required	pam_unix.so
sshd	session		required	pam_lastlog.so	no_fail
sshd	password	required	pam_permit.so
  • auth、account、session 和 password 是四个功能模块。

  • pam_nologin.so、pam_unix.so、pam_login_access.so、pam_lastlog.so 和 pam_permit.so 是模块。通过这个示例可以看出,pam_unix.so 至少提供了两个功能模块(认证和账户管理)。

3. PAM 基本概念

3.1. 功能和原语

PAM API 提供了六个不同的认证原语,这些原语被分组到四个功能模块中,具体描述如下。

auth认证。 该功能模块关注于认证申请者并建立账户凭证。它提供了两个原语:

account账户管理。 该功能模块处理与认证无关的账户可用性问题,如基于一天中的时间或服务器工作负载的访问限制。它提供了一个原语:

session会话管理。 该功能模块处理与会话设置和拆卸相关的任务,如登录会计。它提供了两个原语:

password密码管理。 该功能模块用于更改与账户关联的认证令牌,可能是因为令牌已过期,或者用户希望更改它。它提供了一个原语:

3.2. 模块

模块是 PAM 中一个非常核心的概念;毕竟,它们是 “PAM” 中的 "M"。PAM 模块是一个自包含的程序代码,负责实现一个或多个功能模块中的原语,针对特定的机制;例如,认证功能的机制可能包括 UNIX® 密码数据库、NIS、LDAP 和 Radius 等。

3.2.1. 模块命名

FreeBSD 每个机制都实现为一个单独的模块,命名为 pam_mechanism.so(例如,针对 UNIX® 机制的模块是 pam_unix.so)。其他实现有时会为不同的功能提供单独的模块,并将功能名称与机制名称一起包括在模块名称中。例如,Solaris™ 有一个名为 pam_dial_auth.so.1 的模块,通常用于认证拨号用户。

3.2.2. 模块版本控制

FreeBSD 最初基于 Linux-PAM 的 PAM 实现没有为 PAM 模块使用版本号。这通常会导致与旧版应用程序相关的问题,因为这些应用程序可能链接到较旧版本的系统库,而无法加载所需模块的匹配版本。

另一方面,OpenPAM 会查找与 PAM 库(当前版本为 2)具有相同版本号的模块,如果找不到对应版本的模块,则回退到没有版本号的模块。因此,可以为旧版应用程序提供旧版模块,同时允许新(或新构建的)应用程序利用最新的模块。

虽然 Solaris™ PAM 模块通常会有版本号,但它们并没有真正的版本控制,因为版本号是模块名称的一部分,必须在配置中包含该版本号。

3.3. 链和策略

一个策略由四个链组成,每个链对应一个 PAM 功能模块。每个链都是一个配置语句的序列,每个语句指定要调用的模块、传递给模块的(可选)参数以及描述如何解释模块返回码的控制标志。

理解控制标志对于理解 PAM 配置文件至关重要。控制标志有四种不同的类型:

binding 如果模块成功并且链中没有任何先前的模块失败,则链会立即终止,请求被批准。如果模块失败,则会执行链中的其余部分,但请求最终会被拒绝。

该控制标志由 Sun 在 Solaris™ 9(SunOS™ 5.9)中引入,OpenPAM 也支持此标志。

required 如果模块成功,则执行链中的其余部分,并且请求被批准,除非其他某个模块失败。如果模块失败,则链中的其余部分也会被执行,但请求最终会被拒绝。

requisite 如果模块成功,则执行链中的其余部分,请求被批准,除非其他某个模块失败。如果模块失败,链会立即终止,请求被拒绝。

sufficient 如果模块成功并且链中的先前模块没有失败,则链会立即终止,请求被批准。如果模块失败,则该模块会被忽略,链中的其余部分会被执行。

由于该标志的语义可能有些混淆,特别是在用于链中的最后一个模块时,如果实现支持,建议使用 binding 控制标志来代替。

optional 模块会被执行,但其结果会被忽略。如果链中的所有模块都标记为 optional,所有请求将始终被批准。

当服务器调用六个 PAM 原语中的一个时,PAM 会检索与该原语所属功能模块对应的链,并按链中列出的顺序依次调用每个模块,直到到达链的末尾,或确定不再需要进一步处理(无论是因为 binding 或 sufficient 模块成功,还是因为 requisite 模块失败)。请求只有在至少一个模块被调用并且所有非 optional 模块都成功时才会被批准。

请注意,虽然不常见,但在同一链中列出相同模块多次是可能的。例如,一个用于查找用户名称和密码的模块,可以多次调用,指定不同的目录服务器进行查询。PAM 会将同一链中同一模块的不同出现视为不同、无关的模块。

3.4. 事务

典型 PAM 事务的生命周期如下所述。请注意,如果这些步骤中的任何一个失败,服务器应该向客户端报告适当的错误消息并中止事务。

  1. 如有必要,服务器通过 PAM 独立机制获取裁判凭证——最常见的情况是通过 root 启动,或通过 setuid root。

  2. 服务器现在执行客户端请求的任何服务——例如,提供一个 shell 给申请人。

4. PAM 配置

4.1. PAM 策略文件

4.1.1. /etc/pam.conf

传统的 PAM 策略文件是 /etc/pam.conf。该文件包含系统的所有 PAM 策略。文件中的每一行介绍了链中的一步,如下所示:

login   auth    required        pam_nologin.so  no_warn

字段顺序如下:服务名称、功能名称、控制标志、模块名称和模块参数。任何额外的字段都被解释为额外的模块参数。

为每个服务 / 功能对构建单独的链,因此虽然相同服务和功能的行顺序是重要的,但列出服务和功能的顺序并不重要。原始 PAM 论文中的示例按功能分组配置行,Solaris™ 的默认 pam.conf 文件仍然这样做,但 FreeBSD 的默认配置将配置行按服务分组。两者都可以,任何一种方式都合理。

4.1.2. /etc/pam.d

OpenPAM 和 Linux-PAM 支持一种替代配置机制,这是 FreeBSD 推荐的机制。在这种方案中,每个策略都包含在一个独立的文件中,文件名是应用该策略的服务名。这些文件存储在 /etc/pam.d/ 目录下。

这些每个服务的策略文件只有四个字段,而不是 pam.conf 中的五个:服务名称字段被省略了。因此,在 /etc/pam.d/login 文件中,你会看到如下行,而不是之前的 pam.conf 示例:

auth    required        pam_nologin.so  no_warn

由于这种简化的语法,可以通过将每个服务名称链接到相同的策略文件来为多个服务使用相同的策略。例如,要为 su 和 sudo 服务使用相同的策略,可以按如下方式操作:

# cd /etc/pam.d
# ln -s su sudo

之所以可行,是因为服务名称是通过文件名确定的,而不是在策略文件中指定的,因此相同的文件可以用于多个不同命名的服务。

由于每个服务的策略存储在单独的文件中,pam.d 机制还使为第三方软件包安装额外策略变得非常容易。

4.1.3. 策略搜索顺序

正如我们在前面所看到的,PAM 策略可以在多个地方找到。如果相同服务的策略存在于多个位置,会发生什么呢?

理解 PAM 的配置系统是基于链的这一点至关重要。

4.2. 配置行的解析

服务名称通常是(但不总是)该语句适用的应用程序的名称。如果不确定,请参考各个应用程序的文档,以确定它使用的服务名称。

请注意,如果使用 /etc/pam.d/ 而不是 /etc/pam.conf,服务名称由策略文件的名称指定,并且在实际的配置行中省略,配置行从功能名称开始。

4.3. 策略

为了正确配置 PAM,理解策略的解释方式至关重要。

当应用程序稍后调用其中一个 PAM 原语时,PAM 库检索对应功能的链,并按配置中列出的顺序调用链中每个模块的相应服务函数。在每次调用服务函数后,模块类型和服务函数返回的错误代码将用于决定接下来发生的事情。以下表格适用于大多数情况,除了几个例外,我们将在下面讨论:

表 1. PAM 链执行摘要

PAM_SUCCESS
PAM_IGNORE
其他

binding

如果(!fail)则跳出;

-

fail = true;

required

-

-

fail = true;

requisite

-

-

fail = true; 跳出;

sufficient

如果(!fail)则跳出;

-

-

optional

-

-

-

如果在链的末尾,或者遇到 "break" 时,fail 为 true,则调度器返回第一个失败模块返回的错误代码。否则,返回 PAM_SUCCESS。

第一个值得注意的例外是,错误代码 PAM_NEW_AUTHTOK_REQD 被视为成功,除非没有模块失败,并且至少有一个模块返回了 PAM_NEW_AUTHTOK_REQD,此时调度器将返回 PAM_NEW_AUTHTOK_REQD。

5. FreeBSD PAM 模块

6. PAM 应用程序编程

本节尚未编写。

7. PAM 模块编程

本节尚未编写。

附录 A:示例 PAM 应用程序

/*-
 * Copyright (c) 2002,2003 Networks Associates Technology, Inc.
 * All rights reserved.
 *
 * 本软件由 ThinkSec AS 和 Network Associates Laboratories(Network Associates, Inc. 的安全研究部门)为 FreeBSD 项目开发,
 * 由 DARPA/SPAWAR 合同 N66001-01-C-8035(“CBOSS”)支持,作为 DARPA CHATS 研究项目的一部分。
 *
 * 允许在源代码和二进制形式中进行再分发和使用,无论是否修改,前提是满足以下条件:
 * 1. 源代码的再分发必须保留上述版权声明、此条件列表和以下免责声明。
 * 2. 二进制形式的再分发必须在分发的文档和/或其他材料中重现上述版权声明、此条件列表和以下免责声明。
 * 3. 未经特定的书面许可,不得使用作者的名字来支持或推广基于此软件的产品。
 *
 * 本软件由作者和贡献者“按原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性和适用性保证,
 * 对于任何直接、间接、附带、特殊、示范性或间接损害(包括但不限于采购替代商品或服务;使用、数据或利润的丧失;
 * 或商业中断)在任何理论的责任下,无论是基于合同、严格责任或侵权(包括过失或其他)产生的,
 * 即使在已被告知此类损害的可能性的情况下,也不承担责任。
 *
 * $P4: //depot/projects/openpam/bin/su/su.c#10 $
 * $FreeBSD: head/en_US.ISO8859-1/articles/pam/su.c 38826 2012-05-17 19:12:14Z hrs $
 */

#include <sys/param.h>
#include <sys/wait.h>

#include <err.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include <security/pam_appl.h>
#include <security/openpam.h>	/* openpam_ttyconv() 需要 */

extern char **environ;

static pam_handle_t *pamh;
static struct pam_conv pamc;

static void
usage(void)
{

	fprintf(stderr, "Usage: su [login [args]]\n");
	exit(1);
}

int
main(int argc, char *argv[])
{
	char hostname[MAXHOSTNAMELEN];
	const char *user, *tty;
	char **args, **pam_envlist, **pam_env;
	struct passwd *pwd;
	int o, pam_err, status;
	pid_t pid;

	while ((o = getopt(argc, argv, "h")) != -1)
		switch (o) {
		case 'h':
		default:
			usage();
		}

	argc -= optind;
	argv += optind;

	if (argc > 0) {
		user = *argv;
		--argc;
		++argv;
	} else {
		user = "root";
	}

	/* 初始化 PAM */
	pamc.conv = &openpam_ttyconv;
	pam_start("su", user, &pamc, &pamh);

	/* 设置一些项目 */
	gethostname(hostname, sizeof(hostname));
	if ((pam_err = pam_set_item(pamh, PAM_RHOST, hostname)) != PAM_SUCCESS)
		goto pamerr;
	user = getlogin();
	if ((pam_err = pam_set_item(pamh, PAM_RUSER, user)) != PAM_SUCCESS)
		goto pamerr;
	tty = ttyname(STDERR_FILENO);
	if ((pam_err = pam_set_item(pamh, PAM_TTY, tty)) != PAM_SUCCESS)
		goto pamerr;

	/* 验证申请人 */
	if ((pam_err = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
		goto pamerr;
	if ((pam_err = pam_acct_mgmt(pamh, 0)) == PAM_NEW_AUTHTOK_REQD)
		pam_err = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
	if (pam_err != PAM_SUCCESS)
		goto pamerr;

	/* 建立请求的凭证 */
	if ((pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS)
		goto pamerr;

	/* 认证成功;打开会话 */
	if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
		goto pamerr;

	/* 获取映射的用户名;PAM 可能已更改它 */
	pam_err = pam_get_item(pamh, PAM_USER, (const void **)&user);
	if (pam_err != PAM_SUCCESS || (pwd = getpwnam(user)) == NULL)
		goto pamerr;

	/* 导出 PAM 环境变量 */
	if ((pam_envlist = pam_getenvlist(pamh)) != NULL) {
		for (pam_env = pam_envlist; *pam_env != NULL; ++pam_env) {
			putenv(*pam_env);
			free(*pam_env);
		}
		free(pam_envlist);
	}

	/* 构建参数列表 */
	if ((args = calloc(argc + 2, sizeof *args)) == NULL) {
		warn("calloc()");
		goto err;
	}
	*args = pwd->pw_shell;
	memcpy(args + 1, argv, argc * sizeof *args);

	/* fork 和 exec */
	switch ((pid = fork())) {
	case -1:
		warn("fork()");
		goto err;
	case 0:
		/* 子进程:放弃权限并启动一个 shell */

		/* 设置 uid 和组 */
		if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
			warn("initgroups()");
			_exit(1);
		}
		if (setgid(pwd->pw_gid) == -1) {
			warn("setgid()");
			_exit(1);
		}
		if (setuid(pwd->pw_uid) == -1) {
			warn("setuid()");
			_exit(1);
		}
		execve(*args, args, environ);
		warn("execve()");
		_exit(1);
	default:
		/* 父进程:等待子进程退出 */
		waitpid(pid, &status, 0);

		/* 关闭会话并释放 PAM 资源 */
		pam_err = pam_close_session(pamh, 0);
		pam_end(pamh, pam_err);

		exit(WEXITSTATUS(status));
	}

pamerr:
	fprintf(stderr, "抱歉\n");
err:
	pam_end(pamh, pam_err);
	exit(1);
}

Appendix B: 示例 PAM 模块

/*-
* 版权所有 (c) 2002 Networks Associates Technology, Inc.
* 保留所有权利。
*
* 本软件是由 ThinkSec AS 和 Network Associates Laboratories(Network Associates, Inc. 安全研究部门)为 FreeBSD 项目开发的,
* 根据 DARPA/SPAWAR 合同 N66001-01-C-8035 ("CBOSS"),作为 DARPA CHATS 研究计划的一部分。
*
* 允许以源代码或二进制形式进行再分发和使用,无论是否修改,前提是满足以下条件:
* 1. 源代码的再分发必须保留上述版权声明、此条件列表和以下免责声明。
* 2. 二进制形式的再分发必须在分发的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
* 3. 未经特定的书面许可,不得使用作者的姓名来支持或推广基于本软件的产品。
*
* 本软件由作者和贡献者以“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性和适合某一特定用途的暗示保证。
* 在任何情况下,作者或贡献者都不对因使用本软件而导致的任何直接、间接、偶然、特殊、示范性或间接损害负责,
* 包括但不限于替代商品或服务的采购;使用、数据或利润的损失;或业务中断,不论是在合同、严格责任或侵权(包括过失或其他)下,
* 即使已经被告知可能发生此类损害,也不承担任何责任。
*
* $P4: //depot/projects/openpam/modules/pam\_unix/pam\_unix.c#3 \$
* $FreeBSD: head/en\_US.ISO8859-1/articles/pam/pam\_unix.c 38826 2012-05-17 19:12:14Z hrs \$
  */


#include <sys/param.h>

#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <security/pam_modules.h>
#include <security/pam_appl.h>

#ifndef _OPENPAM
static char password_prompt[] = "Password:";
#endif

#ifndef PAM_EXTERN
#define PAM_EXTERN
#endif

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
	int argc, const char *argv[])
{
#ifndef _OPENPAM
	struct pam_conv *conv;
	struct pam_message msg;
	const struct pam_message *msgp;
	struct pam_response *resp;
#endif
	struct passwd *pwd;
	const char *user;
	char *crypt_password, *password;
	int pam_err, retry;

	/* identify user */
	if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
		return (pam_err);
	if ((pwd = getpwnam(user)) == NULL)
		return (PAM_USER_UNKNOWN);

	/* get password */
#ifndef _OPENPAM
	pam_err = pam_get_item(pamh, PAM_CONV, (const void **)&conv);
	if (pam_err != PAM_SUCCESS)
		return (PAM_SYSTEM_ERR);
	msg.msg_style = PAM_PROMPT_ECHO_OFF;
	msg.msg = password_prompt;
	msgp = &msg;
#endif
	for (retry = 0; retry < 3; ++retry) {
#ifdef _OPENPAM
		pam_err = pam_get_authtok(pamh, PAM_AUTHTOK,
		    (const char **)&password, NULL);
#else
		resp = NULL;
		pam_err = (*conv->conv)(1, &msgp, &resp, conv->appdata_ptr);
		if (resp != NULL) {
			if (pam_err == PAM_SUCCESS)
				password = resp->resp;
			else
				free(resp->resp);
			free(resp);
		}
#endif
		if (pam_err == PAM_SUCCESS)
			break;
	}
	if (pam_err == PAM_CONV_ERR)
		return (pam_err);
	if (pam_err != PAM_SUCCESS)
		return (PAM_AUTH_ERR);

	/* compare passwords */
	if ((!pwd->pw_passwd[0] && (flags & PAM_DISALLOW_NULL_AUTHTOK)) ||
	    (crypt_password = crypt(password, pwd->pw_passwd)) == NULL ||
	    strcmp(crypt_password, pwd->pw_passwd) != 0)
		pam_err = PAM_AUTH_ERR;
	else
		pam_err = PAM_SUCCESS;
#ifndef _OPENPAM
	free(password);
#endif
	return (pam_err);
}

PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags,
	int argc, const char *argv[])
{

	return (PAM_SUCCESS);
}

PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
	int argc, const char *argv[])
{

	return (PAM_SUCCESS);
}

PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
	int argc, const char *argv[])
{

	return (PAM_SUCCESS);
}

PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags,
	int argc, const char *argv[])
{

	return (PAM_SUCCESS);
}

PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
	int argc, const char *argv[])
{

	return (PAM_SERVICE_ERR);
}

#ifdef PAM_MODULE_ENTRY
PAM_MODULE_ENTRY("pam_unix");
#endif

Appendix C: 示例 PAM 对话功能

/*-
 * 版权所有 (c) 2002 Networks Associates Technology, Inc.
 * 保留所有权利。
 *
 * 本软件是由 ThinkSec AS 和 Network Associates Laboratories(Network Associates, Inc. 安全研究部门)为 FreeBSD 项目开发的,
 * 根据 DARPA/SPAWAR 合同 N66001-01-C-8035 ("CBOSS"),作为 DARPA CHATS 研究计划的一部分。
 *
 * 允许以源代码或二进制形式进行再分发和使用,无论是否修改,前提是满足以下条件:
 * 1. 源代码的再分发必须保留上述版权声明、此条件列表和以下免责声明。
 * 2. 二进制形式的再分发必须在分发的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
 * 3. 未经特定的书面许可,不得使用作者的姓名来支持或推广基于本软件的产品。
 *
 * 本软件由作者和贡献者以“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性和适合某一特定用途的暗示保证。
 * 在任何情况下,作者或贡献者都不对因使用本软件而导致的任何直接、间接、偶然、特殊、示范性或间接损害负责,
 * 包括但不限于替代商品或服务的采购;使用、数据或利润的损失;或业务中断,不论是在合同、严格责任或侵权(包括过失或其他)下,
 * 即使已经被告知可能发生此类损害,也不承担任何责任。
 *
 * $FreeBSD: head/en_US.ISO8859-1/articles/pam/converse.c 38826 2012-05-17 19:12:14Z hrs $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <security/pam_appl.h>

int
converse(int n, const struct pam_message **msg,
	struct pam_response **resp, void *data)
{
	struct pam_response *aresp;
	char buf[PAM_MAX_RESP_SIZE];
	int i;

	data = data;
	if (n <= 0 || n > PAM_MAX_NUM_MSG)
		return (PAM_CONV_ERR);
	if ((aresp = calloc(n, sizeof *aresp)) == NULL)
		return (PAM_BUF_ERR);
	for (i = 0; i < n; ++i) {
		aresp[i].resp_retcode = 0;
		aresp[i].resp = NULL;
		switch (msg[i]->msg_style) {
		case PAM_PROMPT_ECHO_OFF:
			aresp[i].resp = strdup(getpass(msg[i]->msg));
			if (aresp[i].resp == NULL)
				goto fail;
			break;
		case PAM_PROMPT_ECHO_ON:
			fputs(msg[i]->msg, stderr);
			if (fgets(buf, sizeof buf, stdin) == NULL)
				goto fail;
			aresp[i].resp = strdup(buf);
			if (aresp[i].resp == NULL)
				goto fail;
			break;
		case PAM_ERROR_MSG:
			fputs(msg[i]->msg, stderr);
			if (strlen(msg[i]->msg) > 0 &&
			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
				fputc('\n', stderr);
			break;
		case PAM_TEXT_INFO:
			fputs(msg[i]->msg, stdout);
			if (strlen(msg[i]->msg) > 0 &&
			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
				fputc('\n', stdout);
			break;
		default:
			goto fail;
		}
	}
	*resp = aresp;
	return (PAM_SUCCESS);
 fail:
        for (i = 0; i < n; ++i) {
                if (aresp[i].resp != NULL) {
                        memset(aresp[i].resp, 0, strlen(aresp[i].resp));
                        free(aresp[i].resp);
                }
        }
        memset(aresp, 0, n * sizeof *aresp);
	*resp = NULL;
	return (PAM_CONV_ERR);
}

进一步阅读

论文

  • Making Login Services Independent of Authentication Technologies Vipin Samar. Charlie Lai. Sun Microsystems.

用户手册

相关网页

  • Solaris PAM主页*. Sun Microsystems。

BSD 中的实用 rc.d 脚本编程

摘要

初学者可能会觉得很难将来自正式文档中关于 BSD rc.d 框架的事实与实际的 rc.d 脚本任务联系起来。本文将考虑几个典型的、逐渐复杂的案例,展示适用于每个案例的 rc.d 特性,并讨论它们如何工作。这样的探讨应该为进一步研究 rc.d 的设计和高效应用提供参考点。

1. 引言

单一脚本方法的真正问题在于,它无法控制从 /etc/rc 启动的各个组件。例如,/etc/rc 无法重新启动单个守护进程。系统管理员必须手动找到守护进程,终止它,等待它实际退出,然后浏览 /etc/rc 查找标志,最后输入完整的命令行来重新启动守护进程。如果要重新启动的服务包含多个守护进程或需要其他操作,这项任务会变得更加困难且容易出错。简而言之,单一脚本未能实现脚本的基本目的:使系统管理员的工作更轻松。

在没有干净且设计良好的框架的情况下,启动脚本不得不竭尽全力满足快速发展的基于 BSD 的操作系统的需求。最终,越来越明显的是,要实现一个细粒度且可扩展的 rc 系统,还需要更多的步骤。于是,BSD rc.d 应运而生。它的公认创始人是 Luke Mewburn 和 NetBSD 社区。后来它被引入到 FreeBSD。它的名称来源于系统脚本的位置,这些脚本用于单独的服务,位于 /etc/rc.d。很快我们将了解 rc.d 系统的更多组件,并查看如何调用单个脚本。

本文侧重于 FreeBSD 分支的 rc.d。尽管如此,它对于 NetBSD 开发人员也可能有用,因为两个 BSD rc.d 分支不仅共享相同的设计,而且在脚本作者可见的方面也保持相似。

2. 概述任务

在开始编辑 $EDITOR 之前,稍作思考不会有坏处。为了编写一个调和良好的 rc.d 脚本来管理系统服务,我们应该首先能够回答以下问题:

  • 这个服务是必须的还是可选的?

  • 脚本是为单个程序(例如守护进程)服务,还是执行更复杂的操作?

  • 我们的服务依赖于哪些其他服务,反之亦然?

从以下的示例中,我们将看到为什么了解这些问题的答案如此重要。

3. 示范脚本

下面的脚本在系统每次启动时都会发出一条消息:

#!/bin/sh ①

. /etc/rc.subr ②

name="dummy" ③
start_cmd="${name}_start" ④
stop_cmd=":" ⑤

dummy_start() ⑥
{
	echo "Nothing started."
}

load_rc_config $name ⑦
run_rc_command "$1" ⑧

需要注意的事项如下:

① 一个解释型脚本应该以魔法的“shebang”行开始。该行指定脚本的解释器程序。由于 shebang 行,脚本可以像二进制程序一样被调用,只要设置了执行位。例如,系统管理员可以从命令行手动运行我们的脚本:

# /etc/rc.d/dummy start

注意

技巧

注意

有关网络的某些有用函数由另一个包含文件 /etc/network.subr 提供。

注意

注意

为了使 rc.d 代码更统一,通常在适当的地方使用 ${name}。这样,许多行可以直接从一个脚本复制到另一个脚本。

⑥ 一个复杂方法的主体可以作为函数来实现。为函数命名时最好有意义。

重要

4. 可配置的虚拟脚本

#!/bin/sh

. /etc/rc.subr

name=dummy
rcvar=dummy_enable ①

start_cmd="${name}_start"
stop_cmd=":"

load_rc_config $name ②
: ${dummy_enable:=no} ③
: ${dummy_msg="Nothing started."} ④

dummy_start()
{
	echo "$dummy_msg" ⑤
}

run_rc_command "$1"

在这个例子中有什么变化呢?

① 变量 rcvar 指定了 ON/OFF 开关变量的名称。

注意

注意

# /etc/rc.d/dummy onestart

重要

注意

⑤ 现在我们使用 dummy_msg 来控制脚本,即显示一个变量消息。这里使用 shell 函数是多余的,因为它只运行一条命令;一个同样有效的替代方法是:

start_cmd="echo \"$dummy_msg\""

5. 简单守护进程的启动与关闭

#!/bin/sh

. /etc/rc.subr

name=mumbled
rcvar=mumbled_enable

command="/usr/sbin/${name}" ①

load_rc_config $name
run_rc_command "$1"

这个脚本看起来简单而又简洁,不是吗?让我们分析一下这个小脚本。唯一需要注意的新事物是:

注意

6. 启动和关闭一个高级守护进程

让我们为之前的脚本增加一些内容,使其变得更加复杂和功能丰富。默认方法对我们来说已经足够好,但有时我们可能需要调整它们的一些方面。现在我们将学习如何根据我们的需求来调整默认方法。

#!/bin/sh

. /etc/rc.subr

name=mumbled
rcvar=mumbled_enable

command="/usr/sbin/${name}"
command_args="mock arguments > /dev/null 2>&1" ①

pidfile="/var/run/${name}.pid" ②

required_files="/etc/${name}.conf /usr/share/misc/${name}.rules" ③

sig_reload="USR1" ④

start_precmd="${name}_prestart" ⑤
stop_postcmd="echo Bye-bye" ⑥

extra_commands="reload plugh xyzzy" ⑦

plugh_cmd="mumbled_plugh" ⑧
xyzzy_cmd="echo 'Nothing happens.'"

mumbled_prestart()
{
	if checkyesno mumbled_smart; then ⑨
		rc_flags="-o smart ${rc_flags}" ⑩
	fi
	case "$mumbled_mode" in
	foo)
		rc_flags="-frotz ${rc_flags}"
		;;
	bar)
		rc_flags="-baz ${rc_flags}"
		;;
	*)
		warn "Invalid value for mumbled_mode" ⑪
		return 1 ⑫
		;;
	esac
	run_rc_command xyzzy ⑬
	return 0
}

mumbled_plugh() ⑭
{
	echo 'A hollow voice says "plugh".'
}

load_rc_config $name
run_rc_command "$1"

① 除了 $command 外,还可以通过 command_args 传递额外的参数。这些参数将添加到 $mumbled_flags 后面。由于最终的命令行会通过 eval 执行,因此可以在 command_args 中指定输入输出重定向。

注意

注意

注意

可以通过在命令行中使用 forcestart 参数来强制跳过这些前置检查。

④ 如果守护进程需要的信号与默认的不同,我们可以自定义信号。例如,sig_reload 指定了用于重新加载守护进程配置的信号,默认是 SIGHUP。另一个信号是用于停止守护进程的,默认是 SIGTERM,但也可以通过设置 sig_stop 来改变它。

注意

注意

即使我们重写了默认方法并定义了自定义的 argument_cmd,也可以使用 argument_precmd 或 argument_postcmd。特别地,前者非常适合用来检查自定义的复杂条件,只有在条件满足时才执行命令本身。结合使用 argument_precmd 和 argument_cmd 可以将检查与动作逻辑上分开。

⑦ 如果我们想实现自定义的命令(也可以称为 命令),只需将它们列在 extra_commands 中,并提供相应的处理方法。

注意

⑧⑭ 我们的脚本支持两个非标准命令,plugh 和 xyzzy。它们在 extra_commands 中列出,现在是时候为它们提供方法了。xyzzy 的方法就是内联定义的,而 plugh 则由 mumbled_plugh 函数实现。

# /etc/rc.d/mumbled
Usage: /etc/rc.d/mumbled [fast|force|one](start|stop|restart|rcvar|reload|plugh|xyzzy|status|poll)

重要

checkyesno 函数接受的是 变量名。不要将变量的展开 值 传给它;那样不会按预期工作。

下面是 checkyesno 的正确用法:

if checkyesno mumbled_enable; then
       foo
fi

相反,如下所示调用 checkyesno 将不起作用 —— 至少不会按预期工作:

if checkyesno "${mumbled_enable}"; then
       foo
fi

⑩ 我们可以通过在 $start_precmd 中修改 rc_flags 来影响传递给 $command 的参数。

⑫ 方法及其 pre-command 返回的退出码默认不会被忽略。如果 argument_precmd 返回一个非零的退出码,则主方法将不会被执行。反过来,除非主方法返回零退出码,argument_postcmd 也不会被调用。

注意

不过,可以通过在命令行参数前加上 force,如 forcestart,来指示 [rc.subr(8)] 忽略这些退出码并无论如何执行所有命令。

7. 将脚本接入 rc.d 框架

不过,在此之前,我们应该先考虑脚本在系统启动序列中的位置。脚本所管理的服务很可能依赖于其他服务。例如,一个网络守护进程在网络接口和路由尚未启动之前是无法工作的。即使某个服务看似不依赖其他内容,它也不可能在基本文件系统被检查和挂载之前启动。

  • 它所 提供 的“条件”(即我们所说的服务);

  • 它所 需要 的“条件”;

  • 它应该在其前运行的“条件”;

  • 额外的 关键字,这些关键字可用于从整个文件集中选择子集(可以通过参数告知 [rcorder(8)] 包括或排除含特定关键字的文件)。

了解了以上这些基础知识之后,我们来看一个添加了依赖信息的简单守护进程脚本示例:

#!/bin/sh

# PROVIDE: mumbled oldmumble ①
# REQUIRE: DAEMON cleanvar frotz ②
# BEFORE:  LOGIN ③
# KEYWORD: nojail shutdown ④

. /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 ⑤
	fi
	return 0
}

load_rc_config $name
run_rc_command "$1"

如前所述,接下来是详细分析:

① 这一行声明了我们的脚本所 提供 的“条件”名称。现在其他脚本就可以通过这些名称来记录对我们脚本的依赖关系。

注意

通常,一个脚本只会声明一个所提供的条件。不过,并没有什么限制我们在此列出多个条件,例如为了兼容性原因。

无论如何,主要的(或唯一的)PROVIDE: 条件名称应当与 ${name} 相同。

注意

BEFORE: 行不应被滥用于绕过其他脚本中不完整的依赖列表。使用 BEFORE: 的合理情形是:另一个脚本并不关心我们的脚本是否存在,但如果我们的脚本能在它之前运行,则可以更好地完成任务。一个典型的现实示例是网络接口与防火墙:虽然接口在工作时并不依赖防火墙,但若防火墙在网络流量出现前就已就绪,系统安全性将受益。

nostart 该服务应由手动启动,或根本不启动。自动启动流程将忽略此脚本。若与 shutdown 关键字同时使用,可用于编写仅在系统关机时执行操作的脚本。

shutdown 若服务需要在系统关机前被停止,必须显式列出此关键字。

注意

⑤ 首先,force_depend 应当谨慎使用。通常情况下,如果你的 rc.d 脚本之间有依赖关系,最好是重新审视它们的配置变量层次结构。

8. 给 rc.d 脚本提供更多灵活性

为了说明这个机会,让我们修改这个原始的虚拟脚本,使得其消息依赖于传递的额外参数。我们来看一下:

#!/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 ①
                echo "Greeting message: $*"
        else
                echo "Nothing started."
        fi
}

dummy_kiss()
{
        echo -n "A ghost gives you a kiss"
        if [ $# -gt 0 ]; then ②
                echo -n " and whispers: $*"
        fi
        case "$*" in
        *[.!?])
                echo
                ;;
        *)
                echo .
                ;;
        esac
}

load_rc_config $name
run_rc_command "$@" ③

我们可以注意到脚本中的哪些关键变化?

# /etc/rc.d/dummy start
Nothing started.

# /etc/rc.d/dummy start Hello world!
Greeting message: Hello world!

② 同样的方式适用于脚本提供的任何方法,而不仅仅是标准方法。我们添加了一个名为 kiss 的自定义方法,它同样可以利用额外的参数,就像 start 方法那样。例如:

# /etc/rc.d/dummy kiss
A ghost gives you a kiss.

# /etc/rc.d/dummy kiss Once I was Etaoin Shrdlu...
A ghost gives you a kiss and whispers: Once I was Etaoin Shrdlu...

③ 如果我们只想将所有额外的参数传递给某个方法,我们可以简单地在脚本的最后一行将 "$@" 替换为 "$1",在该行调用 run_rc_command。

重要

注意

当前 run_rc_command 可能存在一个 bug,导致它无法保持参数之间的原始边界。也就是说,包含空格的参数可能无法正确处理。该 bug 源于 $* 的误用。

9. 使脚本准备好用于服务 Jail

启动长期运行服务的脚本适合用于服务 Jail,并应配有适当的服务 Jail 配置。

以下是一些不适合在服务 Jail 中运行的脚本示例:

  • 仅在 start 命令中更改程序或内核的运行时设置的任何脚本,

  • 尝试挂载某些内容的脚本,

  • 查找并删除文件的脚本。

不适合在服务 Jail 中运行的脚本需要防止在服务 Jail 中使用。

如果一个脚本具有长期运行的服务,在启动或停止之前需要执行上述操作中的某些操作,则可以将其拆分为两个有依赖关系的脚本,或使用脚本的 precommand 和 postcommand 部分来执行该操作。

默认情况下,只有脚本的 start 和 stop 部分会在服务 Jail 中运行,其余部分则在 Jail 外部运行。因此,任何在 start/stop 部分中使用的设置不能通过例如 precommand 来设置。

#!/bin/sh

. /etc/rc.subr

name="dummy"
start_cmd="${name}_start"
stop_cmd=":"

: ${dummy_svcj_options:=""} ①

dummy_start()
{
        echo "Nothing started."
}

load_rc_config $name
run_rc_command "$1"

① 如果脚本需要在 Jail 中运行,它必须具有可覆盖的服务 Jail 配置。如果它不需要网络访问或访问 Jail 中受限的任何其他资源,则像显示的那样使用空配置即可。

如果出于某种原因,脚本无法在服务 Jail 中运行,例如因为无法运行或在 Jail 中运行没有意义,则使用以下方法:

#!/bin/sh

. /etc/rc.subr

name="dummy"
start_cmd="${name}_start"
stop_cmd=":"

dummy_start()
{
        echo "Nothing started."
}

load_rc_config $name
dummy_svcj="NO"		# 在 svcj 中运行没有意义 ①
run_rc_command "$1"

10. 高级 rc 脚本:实例化

有时需要运行多个实例的服务。通常,你希望能够独立启动/停止这些实例,并且希望每个实例有一个单独的配置文件。每个实例应在启动时启动,能够在更新后存活,并受益于更新。

以下是支持此功能的 rc 脚本示例:

#!/bin/sh

#
# PROVIDE: dummy
# REQUIRE: NETWORKING SERVERS
# KEYWORD: shutdown
#
# 将以下行添加到 /etc/rc.conf.local 或 /etc/rc.conf
# 以启用此服务:
#
# dummy_enable (bool):	设置为 YES 以在启动时启用 dummy。
#			默认值:NO
# dummy_user (string):	运行时使用的用户账户。
#			默认值:www
#

. /etc/rc.subr

case $0 in ①
/etc/rc*)
	# 在启动(关机)时,$0 是 /etc/rc (/etc/rc.shutdown),
	# 所以从 $_file 获取脚本的名称
	name=$_file
	;;
*)
	name=$0
	;;
esac

name=${name##*/} ②
rcvar="${name}_enable" ③
desc="该服务的简短描述"
command="/usr/local/sbin/dummy"

load_rc_config "$name"

eval "${rcvar}=\${${rcvar}:-'NO'}" ④
eval "${name}_svcj_options=\${${name}_svcj_options:-'net_basic'}" ⑤
eval "_dummy_user=\${${name}_user:-'www'}" ⑥

_dummy_configname=/usr/local/etc/${name}.cfg ⑦
pidfile=/var/run/dummy/${name}.pid
required_files ${_dummy_configname}
command_args="-u ${_dummy_user} -c ${_dummy_configfile} -p ${pidfile}"

run_rc_command "$1"

③ 指定用于 rc.conf 中启用该服务的变量名,基于此脚本的文件名。在此示例中,它解析为 dummy_enable。

④ 确保 _enable 变量的默认值为 NO。

⑤ 是为服务特定的框架变量提供一些默认值的示例,在此示例中是服务监狱选项。

⑥ 和 ⑦ 设置脚本内部的变量(注意在 _dummy_user 前的下划线,它使其与可以在 rc.conf 中设置的 dummy_user 区分开)。

⑤ 部分用于脚本内部未使用但在 rc 框架中使用的变量。所有在脚本中作为参数使用的变量都分配给一个通用变量,如⑦所示,以便更容易引用它们(不需要在每个使用位置重新评估它们)。

现在,如果启动脚本有不同的名称,此脚本将表现不同。这允许创建它的符号链接:

# ln -s dummy /usr/local/etc/rc.d/dummy_foo
# sysrc dummy_foo_enable=YES
# service dummy_foo start

上述命令创建了一个名为 dummy_foo 的 dummy 服务实例。它不会使用配置文件 /usr/local/etc/dummy.cfg,而是使用配置文件 /usr/local/etc/dummy_foo.cfg(⑦),并且它使用 /var/run/dummy/dummy_foo.pid 作为 PID 文件,而不是 /var/run/dummy/dummy.pid。

dummy 和 dummy_foo 服务可以独立管理,而启动脚本会在包更新时自动更新(由于符号链接)。这不会更新 REQUIRE 行,因此没有简单的方法依赖特定的实例。为了在启动顺序中依赖特定实例,必须进行复制,而不是使用符号链接。这将防止在安装更新时自动拾取启动脚本的更改。

11. 深入阅读

问题报告处理指南

商标

FreeBSD 是 FreeBSD 基金会的注册商标。

制造商和销售商用来区分其产品的许多名称都被声明为商标。在本文档中出现这些名称时,如果 FreeBSD 项目意识到其商标声明,这些名称将后跟“™”或“®”符号。

摘要

这些指南介绍了针对 FreeBSD 问题报告(PRs)的建议处理实践。虽然这些指南是为 FreeBSD PR 数据库维护团队 freebsd-bugbusters@FreeBSD.org 制定的,但任何与 FreeBSD PRs 一起工作的人都应遵循这些指南。


1. 介绍

Bugzilla 是 FreeBSD 项目使用的问题管理系统。准确跟踪未解决的软件缺陷对 FreeBSD 的质量至关重要,正确使用该软件对项目的进展至关重要。

Bugzilla 的访问权限开放给整个 FreeBSD 社区。为了维护数据库的一致性并提供一致的用户体验,已经制定了涵盖 Bug 管理常见方面的准则,如提交后续处理、处理关闭请求等。

2.问题报告生命周期

  • 报告人在网站上提交了一个 bug 报告。该 bug 处于 Needs Triage 状态。

  • 简·随机 BugBuster 确认 bug 报告具有足够的信息以便重现。如果没有,她会与报告人来回沟通以获取所需信息。此时 bug 被设置为 Open 状态。

  • 乔·随机提交者对 PR 感兴趣并将其分配给自己,或者简·随机 BugBuster 决定乔最适合处理,并将其分配给他。应将 bug 设置为 In Discussion 状态。

  • 乔与发起人进行简短交流(确保所有内容都记录在审计记录中),并确定问题的原因。

  • 乔通宵达旦,草拟了一个他认为修复问题的补丁,并在后续提交中请求发起人测试它。然后,他将 PR 的状态设置为 Patch Ready 。

  • 几次迭代后,乔和发起人都满意于补丁,乔将其提交到 -CURRENT (如果问题不存在于 -CURRENT 中,则直接提交到 -STABLE ),确保在提交日志中引用问题报告(如果提交者提交了全部或部分补丁,还要给予发起人荣誉),并且必要时开始 MFC 倒计时。该错误被设置为 Needs MFC 状态。

  • 如果补丁不需要进行 MFC,则 Joe 会将 PR 关闭为 Issue Resolved 。

许多 PR 提交的信息很少,关于问题的信息很少,有些问题解决起来非常复杂,或者只是触及更大问题的表面;在这些情况下,获取解决问题所需的所有必要信息非常重要。如果无法解决所含问题,或者问题再次出现,则有必要重新打开 PR。

3. 问题报告状态

更新 PR 状态是非常重要的,当采取某些操作时。状态应准确反映 PR 上的工作当前状态。

示例 1。关于何时更改 PR 状态的小例子

当开发人员已经处理过 PR 并且负责的人感觉修复工作正常时,他们将提交 PR 的后续,并将其状态更改为“反馈”。此时,发起者应在其上下文中评估修复情况,并响应指示缺陷是否已修复。

问题报告可能处于以下状态之一:

开放初始状态;问题已指出,需要进行审查。

分析问题已经审查,正在寻找解决方案。

进一步的反馈需要来自创始人或社区的额外信息;可能涉及所提出解决方案的信息。

补丁已提交,但还有一些事情(MFC,或者可能需要创始人的确认)仍在等待中。

暂停由于缺乏信息或资源而未解决该问题。这是寻找可承担项目的人的首选。如果无法解决问题,则会关闭而不是暂停。文档项目使用“暂停”来表示愿望清单项,这需要大量工作,目前没有人有时间。

一个问题报告在集成任何更改、记录和测试后关闭,或在放弃修复问题时关闭。

“已修复”状态直接与反馈相关,因此,如果原始提交者无法测试补丁,但它在你的测试中有效,你可以直接进入“已关闭”状态。

4. 问题报告类型

处理问题报告时,无论是作为直接访问问题报告数据库的开发人员,还是作为浏览数据库并提交补丁、评论、建议或更改请求的贡献者,你都会遇到几种不同类型的 PR。

以下各节介绍了每种不同类型的 PR 用于什么,当一个 PR 属于这些类型之一时,以及每种不同类型接收到的处理。

5. 未分配的 PR

当 PR 到达时,它们最初被分配给一个通用(占位符)受让人。这些总是以 freebsd- 开头。此默认值的确切值取决于类别;在大多数情况下,它对应于特定的 FreeBSD 邮件列表。以下是当前的列表,最常见的列表首先列出:

默认受让人 - 最常见的

类型
类别
默认受让人

基本系统

bin, conf, gnu, kern, misc

freebsd-bugs

架构特定

alpha,amd64,arm,i386,ia64,powerpc,sparc64

freebsd-arch

ports

ports

freebsd-ports-bugs

随系统提供的文档

docs

freebsd-doc

FreeBSD 网页(不包括文档)

Website

FreeBSD

表 2. 默认的指派者 - 其他

类型
分类
默认受理人

advocacy efforts

advocacy

freebsd-advocacy

Java Virtual Machine™ problems

java

freebsd-java

standards compliance

standards

freebsd-standards

threading libraries

threads

freebsd-threads

usb

freebsd-usb

不要感到惊讶,发起 PR 的人员将其分配给了错误的类别。如果你更正了类别,请不要忘记修复分配关系。(特别是,我们的提交者似乎很难理解他们的问题在 i386 系统上显现,这可能是适用于所有 FreeBSD 系统的通用问题,因此更适合于... 当然,相反的情况也成立。)

某些 PR 可能会被任何人重新分配,远离这些通用的受让人。有几种类型的受让人:专门的邮件列表;邮件别名(用于某些限定兴趣项目);和个人。

对于那些邮件列表为受让人的情况,请在进行分配时使用长形式(例如, freebsd-foo 而不是 foo );这将避免向邮件列表发送重复邮件。

由于自愿成为某些类型的 PR 的默认受让人的个人名单经常发生变化,因此更适合用于 FreeBSD 维基。

这是一份此类实体的示例列表;它可能不完整。

常见受让人 - 基础系统

类型
建议类别
建议受让人
受让人类型

ARM® 架构特定问题

arm

freebsd-arm

邮寄清单

特定于 MIPS® 体系结构的问题

kern

freebsd-mips

邮件列表

问题特定于 PowerPC® 架构

kern

freebsd-ppc

邮件列表

高级配置和电源管理问题(acpi(4))

kern

freebsd-acpi

邮件列表

异步传输模式(ATM)驱动程序问题

kern

freebsd-atm

邮件列表

在嵌入式或小型 FreeBSD 系统中出现问题(例如 NanoBSD/PicoBSD/FreeBSD-arm)

kern

freebsd-embedded

邮件列表

FireWire® 驱动程序问题

kern

freebsd-firewire

邮件列表

文件系统代码问题

kern

freebsd-fs

邮件列表

与 geom(4)子系统的问题

kern

freebsd-geom

邮件列表

kern

freebsd-ipfw

邮件列表

集成服务数字网络(ISDN)驱动程序问题

kern

freebsd-isdn

邮件列表

jail(8) 子系统

kern

freebsd-jail

邮件列表

与 Linux® 或 SVR4 仿真的问题

kern

freebsd-emulation

邮件列表

网络堆栈问题

kern

freebsd-net

邮件列表

pf(4) 子系统问题

kern

freebsd-pf

邮件列表

SCSI(4) 子系统存在问题

kern

freebsd-scsi

邮件列表

声音(4)子系统存在问题

kern

freebsd-multimedia

邮件列表

wlan(4) 子系统和无线驱动程序的问题

kern

freebsd-wireless

邮件列表

sysinstall(8) 或 bsdinstall(8) 的问题

bin

freebsd-sysinstall

邮件列表

系统启动脚本出现问题(rc(8))

kern

freebsd-rc

邮件列表

有关 VIMAGE 或 VNET 功能及相关代码的问题

kern

freebsd-virtualization

邮件列表

Xen 仿真问题

kern

freebsd-xen

邮件列表

表 4. 常见受让人 - Ports

类型
推荐的类别
建议的被分配人
分配人类型

problem with the ports framework (not with an individual port!)

ports

portmgr

alias

ports

apache

邮件列表

ports

autotools

alias

ports

doceng

alias

ports

freebsd-eclipse

邮件列表

ports

gecko

邮件列表

ports

gnome

邮件列表

ports

hamradio

alias

ports

haskell

alias

ports

freebsd-java

邮件列表

ports

kde

邮件列表

ports

mono

邮件列表

ports

freebsd-office

邮件列表

ports

perl

邮件列表

ports

freebsd-python

邮件列表

ports

freebsd-ruby

邮件列表

ports

secteam

alias

ports

vbox

alias

ports

freebsd-x11

邮件列表

Ports 具有是 ports 合作者的 PR 可能被任何人重新分配(但请注意,并非每个 FreeBSD 合作者必然是 ports 合作者,因此你不能仅通过电子邮件地址来判断。)

对于其他 PR,请不要将其重新分配给个人(除非你确信受让人真的想跟踪该 PR。这将有助于避免这样一种情况:由于每个人都认为受让人已经在解决特定的问题,所以没有人去解决这个问题的情况。

表 5. 常见受让人 - 其他

类型
建议类别
建议的受让人
受让人类型

problem with PR database

bin

bugmeister

alias

doc

bugmeister

alias

6. 已分配的 PR

如果一个 PR 的 responsible 字段设置为一个 FreeBSD 开发人员的用户名,则表示该 PR 已移交给该具体人员进行进一步工作。

任何人都不应该触碰已分配的 PR,除了受让人或缺陷大师。如果你有评论,请提交后续。如果出于某种原因你认为该 PR 应更改状态或重新分配,请向受让人发送消息。如果受让人在两周内未回复,请取消分配该 PR,然后随意处理。

7. 重复的 PR

如果你找到描述同一问题的多个 PR,请选择包含最多有用信息的一个,并关闭其他 PR,明确说明替代 PR 的编号。如果几个 PR 包含互不重叠的有用信息,请在后续中向其中一个提交所有缺失的信息,包括对其他 PR 的引用;然后关闭其他 PR(这些 PR 现在完全被替代)。

8. 过期的 PR

如果 PR 在六个月内未被修改,则视为过期。执行以下过程处理过期的 PR:

  • 如果 PR 包含足够的细节,请尝试在 -CURRENT 和 -STABLE 中重现问题。如果成功,请提交后续报告详细描述你的发现,并尝试找人来分配任务。如适当,将状态设置为“已分析”。

  • 如果 PR 描述的问题是你知道是使用错误 (不正确的配置或其他原因) 的结果,请提交一篇后续说明原始作者做错了什么,然后关闭 PR,并注明理由为“用户错误”或“配置错误”。

  • 如果 PR 描述的错误已在 -CURRENT 和 -STABLE 中修正,请关闭并附上修正日期的消息。

  • 如果 PR 描述的错误已在 -CURRENT 中修正,但在 -STABLE 中未修正,请尝试查明修正者计划何时 MFC,或尝试找到其他人(也许是你自己?)来完成。将状态设置为“已修补”,将其分配给将执行 MFC 的人。

  • 在其他情况下,请询问发起人确认问题在更新版本中是否仍然存在。如果发起人一个月内没有回复,请使用“反馈超时”注释关闭 PR。

9. 非错误修复 PR

开发人员发现看起来应该已发布到 FreeBSD 问题报告邮件列表或其他列表的 PR,应关闭该 PR,并在评论中告知提交者为什么这并不真正是一个 PR,以及该消息应该发布到何处。

Bugzilla 监听用于接收 PR 的电子邮件地址已作为 FreeBSD 文档的一部分发布,并已在网站上公布和列出。这意味着垃圾邮件发送者找到了它们。

每当你关闭这些 PR 之一时,请执行以下操作:

  • 将组件设置为 junk (在 Supporting Services 下。

  • 设置责任人为 nobody@FreeBSD.org 。

  • 将状态设置为 Issue Resolved 。

将类别设为 junk 可以明显表明 PR 中没有有用内容,并有助于减少主要类别中的混乱。

10. 进一步阅读

这是一个与正确编写和处理问题报告相关的资源列表。这并不是完整的清单。

  • 如何编写 FreeBSD 问题报告 - PR 发起者指南。

编写 GEOM 类

摘要

本文记录了开发 GEOM 类和内核模块的一些起点。假设读者已经熟悉 C 用户空间编程。

1. 引言

1.1. 文档

内核编程的文档较为匮乏,这是少数几个几乎没有友好教程的领域之一,而“使用源码!”这句话也确实适用。然而,还是有一些零散的资源(其中一些已经严重过时),在开始编码之前应该进行学习:

  • 第9节的手册页 - 提供内核函数的文档。

2. 准备工作

进行内核开发的最佳方式是拥有(至少)两台独立的计算机。其中一台包含开发环境和源码,另一台用于通过网络启动并从第一台计算机网络挂载文件系统来测试新编写的代码。这样,如果新代码包含错误并导致机器崩溃,它将不会破坏源代码(和其他“实时”数据)。第二台计算机甚至不需要显示器。它可以通过串行电缆或 KVM 连接到第一台计算机。

2.1. 为开发修改系统

对于任何内核编程,启用 INVARIANTS 的内核是必须的。因此,在内核配置文件中添加以下内容:

为了更好的调试,你还应当包括 WITNESS 支持,它会在锁定时提醒你犯下的错误:

为了调试崩溃转储,需要一个带有调试符号的内核:

使用常规的安装内核方式(make installkernel),调试内核不会被自动安装。它被称为 kernel.debug,并位于 /usr/obj/usr/src/sys/KERNELNAME/。为了方便,它应当被复制到 /boot/kernel/。

另一个便利功能是启用内核调试器,这样你可以在内核崩溃时进行检查。为此,请在内核配置文件中添加以下行:

为了使其生效,你可能需要设置一个 sysctl(如果它默认未开启):

内核崩溃是不可避免的,因此需要注意文件系统缓存。特别是,启用软更新可能意味着,如果在提交到存储之前发生崩溃,最新的文件版本可能会丢失。禁用软更新会导致性能显著下降,并且仍然无法保证数据一致性。需要使用“sync”选项挂载文件系统。作为折衷,可以缩短软更新缓存的延迟。有三个 sysctl 对此非常有用(最好在 /etc/sysctl.conf 中设置):

这些数字表示秒数。

为了调试内核崩溃,内核核心转储是必需的。由于内核崩溃可能使文件系统无法使用,因此该崩溃转储首先写入一个原始分区。通常,这是交换分区。此分区必须至少与机器的物理内存大小相同。在下次启动时,转储将被复制到常规文件中。这发生在文件系统被检查和挂载之后,交换区启用之前。这个过程通过两个 /etc/rc.conf 变量控制:

dumpdev 变量指定了交换分区,dumpdir 告诉系统在重启时将核心转储移动到文件系统的哪个位置。

写入内核核心转储是非常缓慢的,并且需要很长时间,所以如果你有大量内存(>256M)并且经常遇到崩溃,那么等待它完成可能会令人沮丧(两次 - 首先写入交换分区,然后将其移到文件系统)。因此,限制系统将使用的内存量会很方便,可以通过 /boot/loader.conf 调整:

如果崩溃频繁发生且文件系统较大(或者你根本不信任软更新+后台 fsck),建议通过 /etc/rc.conf 变量禁用后台 fsck:

这样,文件系统将在需要时始终进行检查。请注意,启用后台 fsck 时,在检查磁盘时可能会发生新的崩溃。再次强调,最安全的做法是使用另一台计算机作为 NFS 服务器,而不是拥有多个本地文件系统。

2.2. 启动项目

为了创建一个新的 GEOM 类,必须在一个任意用户可访问的目录下创建一个空子目录。你不必在 /usr/src 下创建模块目录。

2.3. Makefile

为每个非平凡的编码项目(当然包括内核模块)创建 Makefile 是一种良好的实践。

由于系统提供了一套完善的辅助例程,创建 Makefile 非常简单。简而言之,下面是一个内核模块的最小 Makefile:

这个 Makefile(文件名可以更改)适用于任何内核模块,并且一个 GEOM 类可以仅包含一个内核模块。如果需要多个文件,只需在 SRCS 变量中列出它们,文件名之间用空格分隔。

3. FreeBSD 内核编程

3.1. 内存分配

必须在源文件的声明部分声明 "malloc 类型",例如:

要使用此宏,必须包含 sys/param.h、sys/kernel.h 和 sys/malloc.h 头文件。

3.2. 列表和队列

3.3. BIO

结构 bio 用于所有与 GEOM 相关的输入/输出操作。它基本上包含有关哪个设备(“提供者”)应满足请求、请求类型、偏移量、长度、缓冲区指针以及一些“用户特定”的标志和字段的信息,这些字段有助于实现各种黑客技术。

异步编程模型(也称为“事件驱动”)比用户空间中更常用的命令式编程模型要困难一些(至少需要一段时间才能适应)。在某些情况下,可以使用辅助例程 g_write_data() 和 g_read_data(),但 并不总是。特别是当持有互斥锁时,它们无法使用;例如,在 GEOM 拓扑互斥锁或者在 .start() 和 .stop() 函数执行期间持有的内部互斥锁下。

4. GEOM 编程

4.1. Ggate

如果不需要最高性能,可以通过在用户空间实现 ggate(GEOM 门)设施来执行数据转换。遗憾的是,两种方法之间没有简单的转换方式,甚至没有共享代码的方式。

4.2. GEOM 类

GEOM 类是对数据的变换。这些变换可以以树状结构组合在一起。GEOM 类的实例称为 geoms。

每个 GEOM 类都有几个“类方法”,这些方法在没有可用 geom 实例时(或者它们未绑定到单个实例时)会被调用:

  • .init 在 GEOM 意识到一个 GEOM 类时调用(当内核模块被加载时)。

  • .fini 在 GEOM 放弃该类时调用(当模块被卸载时)。

  • .taste 会被调用一次,每次针对系统可用的每个提供者。如果适用,这个函数通常会创建并启动一个 geom 实例。

  • .destroy_geom 在 geom 应该被解散时调用。

  • .ctlconf 在用户请求重新配置现有 geom 时调用。

还定义了 GEOM 事件函数,这些函数会被复制到 geom 实例中。

g_class 结构中的 .geom 字段是一个从类实例化的 geoms 列表。

这些函数是从 g_event 内核线程中调用的。

4.3. Softc

"softc" 这个名称是 "driver private data" 的遗留术语,最有可能来源于过时的术语 "software control block"。在 GEOM 中,它是一个结构体(更准确地说,是指向一个结构体的指针),可以附加到 geom 实例上,用来存储 geom 实例私有的数据。大多数 GEOM 类具有以下成员:

  • struct g_provider *provider:这个 geom 实例化的“提供者”

  • uint16_t n_disks:这个 geom 消耗的消费者数量

  • struct g_consumer **disks:struct g_consumer* 的数组。(因为 struct g_consumer* 是由 GEOM 为我们创建的,所以无法使用单一间接指针)

softc 结构体包含了 geom 实例的所有状态。每个 geom 实例都有自己的 softc。

4.4. 元数据

元数据的格式多少依赖于类,但必须以以下内容开始:

  • 16 字节的缓冲区,用于存储以 null 结尾的签名(通常是类名)

  • uint32 版本 ID

假设 geom 类能够处理低于其版本 ID 的元数据。

元数据位于提供者的最后一个扇区(因此必须适合它)。

(所有这些都是实现相关的,但现有的代码都以此方式工作,并且得到了库的支持。)

4.5. 标记/创建 GEOM

事件的顺序是:

  • 实用程序确定它应该处理的 GEOM 类,并搜索 geom_CLASSNAME.so 库(通常在 /lib/geom 中)。

在创建/标记一个新的 geom 时,发生的步骤如下:

  • 辅助函数检查参数并收集元数据,随后将其写入所有相关提供者。

  • 这会“破坏”现有的 geoms(如果有的话),并初始化新一轮的“尝试”提供者。目标 geom 类会识别元数据并启动 geom。

(上述事件序列是实现相关的,但现有的代码都按照这种方式工作,并且得到了库的支持。)

4.6. GEOM 命令结构

辅助 geom_CLASSNAME.so 库导出 class_commands 结构体,它是 struct g_command 元素数组。命令具有统一的格式,看起来像:

常见的动词有:

  • label - 将元数据写入设备,以便它们可以在尝试时被识别并在 geom 中启动

  • destroy - 销毁元数据,以便 geoms 被销毁

常见的选项有:

  • -v:详细模式

  • -f:强制执行

许多操作,如标记和销毁元数据,可以在用户空间中执行。为此,struct g_command 提供了 gc_func 字段,可以设置为一个函数(位于同一个 .so 文件中),该函数将被调用来处理动词。如果 gc_func 为 NULL,命令将传递给内核模块,调用 geom 类的 .ctlreq 函数。

4.7. Geoms

Geoms 是 GEOM 类的实例。它们有内部数据(一个 softc 结构)以及一些函数,用于响应外部事件。

事件函数包括:

  • .access:计算权限(读取/写入/独占)

  • .dumpconf:返回关于 geom 的 XML 格式信息

  • .orphan:当某个底层提供者被断开连接时调用

  • .spoiled:当某个底层提供者被写入时调用

  • .start:处理 I/O

这些函数是从 g_down 内核线程中调用的,并且在这个上下文中不能进行睡眠操作(参见其他地方对睡眠的定义),这限制了可以做的事情,但也强制要求处理速度要快。

其中,最重要的函数是 .start(),它在收到针对 geom 类实例管理的提供者的 BIO 请求时被调用,用于执行实际的有用工作。

4.8. GEOM 线程

GEOM 框架创建并运行了三个内核线程:

  • g_down:处理来自高级实体(如用户空间请求)到物理设备的请求

  • g_up:处理来自设备驱动程序的响应,这些响应是对高级实体发出的请求的回答

  • g_event:处理所有其他情况:geom 实例的创建、访问计数、“损坏”事件等

当一个用户进程发出“读取文件中偏移量 Y 处的数据 X”请求时,发生的过程如下:

  • 文件系统将请求转换为一个 struct bio 实例,并将其传递给 GEOM 子系统。它知道哪个 geom 实例应该处理该请求,因为文件系统直接托管在 geom 实例上。

  • 请求最终会调用 g_down 线程中的 .start() 函数,并到达顶层 geom 实例。

  • 这个顶层 geom 实例(例如分区切片器)确定该请求应该路由到较低级别的实例(例如磁盘驱动程序)。它会复制该 bio 请求(bio 请求 总是 需要在实例间复制,使用 g_clone_bio()!),修改数据偏移量和目标提供者字段,并使用 g_io_request() 执行复制。

  • 磁盘驱动程序同样会收到一个 bio 请求,作为对 g_down 线程中 .start() 的调用。它与硬件通信,获取数据并调用 g_io_deliver() 以完成该 bio 请求。

  • 现在,bio 完成的通知在 g_up 线程中“上浮”。首先,分区切片器会在 g_up 线程中调用 .done(),它使用 bio 中存储的信息来释放克隆的 bio 结构(使用 g_destroy_bio()),并在原始请求上调用 g_io_deliver()。

  • 文件系统获取数据并将其传输到用户空间。

一个重要的特点是:在 G_UP 和 G_DOWN 线程中不能进行睡眠操作。这意味着不能在这些线程中执行以下任何操作(这个列表当然不完整,但只是提供信息):

  • 调用 msleep() 和 tsleep(),显然。

  • 调用 g_write_data() 和 g_read_data(),因为这些操作在将数据传递给消费者并返回时会进行睡眠。

  • 等待 I/O。

  • sx 和其他可睡眠锁

这一限制的目的是防止 GEOM 代码在 I/O 请求路径中造成阻塞,因为睡眠操作通常没有时间限制,且无法保证会花费多少时间(还有一些其他更为技术性的原因)。这也意味着在这些线程中可以做的事情非常有限;例如,几乎任何复杂的操作都需要内存分配。幸运的是,有一种解决方法:创建额外的内核线程。

4.9. GEOM 代码中的内核线程

在 GEOM 代码中,线程的常见用途是将请求的处理从 g_down 线程(即 .start() 函数)中卸载出去。这些线程像是“事件处理程序”:它们有一个与之关联的事件链表(该链表中的事件可以由不同线程中的各种函数发布,因此它必须通过互斥锁保护),它们逐一从链表中获取事件并在一个大的 switch() 语句中处理它们。

使用线程来处理 I/O 请求的主要好处是它可以在需要时进行睡眠。现在,这听起来不错,但需要仔细考虑。睡眠是非常方便的,但它会非常有效地摧毁 geom 转换的性能。对性能极为敏感的类可能应该将所有工作都放在 .start() 函数调用中,并特别小心地处理内存不足和类似的错误。

拥有一个这样的事件处理线程的另一个好处是,它可以将来自不同 geom 线程的所有请求和响应序列化到一个线程中。这也非常方便,但可能会很慢。在大多数情况下,.done() 请求的处理可以留给 g_up 线程。

串口和 UART 教程

摘要

本文讨论了如何在 FreeBSD 上使用串口硬件。

1. UART:它是什么以及如何工作

版权 ® 1996 Frank Durda IV uhclem@FreeBSD.org,版权所有。1996年1月13日。

通用异步接收/发送器(UART)控制器是计算机串行通信子系统的关键组件。UART 将数据字节转换成单独的比特,并按顺序进行传输。在接收端,第二个 UART 将比特重新组装成完整的字节。

串行传输通常与调制解调器一起使用,以及在计算机、终端和其他设备之间进行非网络通信。

串行传输有两种主要形式:同步和异步。根据硬件支持的模式,通信子系统的名称通常会包含一个 A,表示支持异步通信;如果支持同步通信,则会包含一个 S。这两种形式将在下文中描述。

一些常见的缩写有:

  • UART:通用异步接收/发送器

  • USART:通用同步/异步接收/发送器

1.1. 同步串行传输

同步串行传输要求发送方和接收方共享时钟,或者发送方提供一个时钟脉冲或其他定时信号,以便接收方知道何时“读取”下一个数据位。在大多数同步串行通信中,如果在某一时刻没有可传输的数据,必须发送填充字符,以确保数据始终处于传输状态。同步通信通常更高效,因为发送方和接收方之间仅传输数据比特;如果需要额外的线路和电路来共享时钟信号,同步通信可能会更加昂贵。

一种同步传输形式被用于打印机和固定磁盘设备,其中数据通过一组线路传输,而时钟或脉冲信号通过另一条线路传输。打印机和固定磁盘设备通常不是串行设备,因为大多数固定磁盘接口标准通过使用为每个字节位单独的线路,在每个时钟脉冲或脉冲信号下传输一个完整的字数据。在 PC 行业中,这些设备被称为并行设备。

PC 上的标准串行通信硬件不支持同步操作。本模式仅用于比较目的。

1.2. 异步串行传输

异步传输允许数据在不需要发送时钟信号给接收方的情况下进行传输。相反,发送方和接收方必须事先约定好时间参数,并且在每个数据字中添加特殊的比特,用于同步发送和接收单元。

当一个数据字被送入 UART 进行异步传输时,会在每个要传输的数据字的开始添加一个称为“起始位”的比特。起始位用于提醒接收方即将发送一个数据字,并将接收方的时钟与发送方的时钟同步。这两个时钟必须足够准确,以确保在传输剩余比特时其频率偏差不超过 10%(这个要求是在机械电传打印机时代设定的,现代电子设备容易满足)。

在起始位之后,数据字的各个比特依次被传输,最低有效位(LSB)首先被传送。每个比特的传输时间都与其他比特相同,接收方在每个比特的周期大约过了一半的时间后,检查线路上的信号来判断该比特是 1 还是 0。例如,如果每个比特的传输时间是两秒钟,接收方将在一秒钟后检查信号,以确定它是 1 还是 0,然后等待两秒钟再检查下一个比特,依此类推。

发送方并不知道接收方何时“检查”该比特的值。发送方仅知道何时根据时钟开始传输下一个比特。

当整个数据字传输完毕后,发送方可能会添加一个奇偶校验位。该奇偶校验位由发送方生成,接收方可以用它来进行简单的错误检查。然后,发送方至少会发送一个停止位。

当接收方接收到所有数据比特后,它可以检查奇偶校验位(发送方和接收方必须约定是否使用奇偶校验位),然后接收方检查停止位。如果停止位未按时出现,UART 将认为整个数据字已经损坏,并在读取数据字时报告一个帧错误。帧错误的常见原因是发送方和接收方的时钟速度不同,或者信号中断。

无论数据是否正确接收,UART 会自动丢弃起始位、奇偶校验位和停止位。如果发送方和接收方配置一致,这些位不会传递给主机。

如果另一个数据字已准备好传输,新的数据字的起始位可以在上一个数据字的停止位发送完毕后立即发送。

由于异步数据是“自同步”的,如果没有数据需要传输,传输线路可以保持空闲。

1.3. 其他 UART 功能

除了将数据从并行转换为串行进行传输,并将串行数据转换为并行接收之外,UART 通常还提供额外的电路来指示传输介质的状态,并在远程设备无法接受更多数据时调节数据流。例如,当连接到 UART 的设备是调制解调器时,调制解调器可能会报告电话线上载波的存在,同时计算机可以通过提升或降低这些额外信号中的一个来指示调制解调器重置或不接听电话。每个额外信号的功能在 EIA RS232-C 标准中有所定义。

1.4. RS232-C 和 V.24 标准

在大多数计算机系统中,UART 连接到符合 EIA RS232-C 规范的电路上。还有一个名为 V.24 的 CCITT 标准,它与 RS232-C 中的规格相对应。

1.4.1. RS232-C 位分配(标记和空格)

在 RS232-C 中,1 的值称为 Mark(标记),0 的值称为 Space(空格)。当通信线路空闲时,该线路被认为处于“标记”状态,或持续传输 1 值。

起始位的值始终为 0(空格)。停止位的值始终为 1(标记)。这意味着在每个数据字的开始,线路上总会出现从标记(1)到空格(0)的过渡,即使多个数据字是连续传输的。这保证了发送方和接收方能够重新同步它们的时钟,无论传输的数据比特内容如何。

停止位和起始位之间的空闲时间不必是通信链路位速率的精确倍数(包括零倍),但为了简便,大多数 UART 都是按此方式设计的。

在 RS232-C 中,“标记”信号(1)由 -2 VDC 到 -12 VDC 之间的电压表示,“空格”信号(0)由 0 VDC 到 +12 VDC 之间的电压表示。发送器应发送 +12 VDC 或 -12 VDC,而接收器应允许长电缆中的一些电压损失。一些低功耗设备(如便携式计算机)中的发送器有时仅使用 +5 VDC 和 -5 VDC,但只要电缆长度较短,这些值仍然对 RS232-C 接收器是可接受的。

1.4.2. RS232-C 中的断开信号(Break)

RS232-C 还规定了一种名为 Break 的信号,其通过连续发送空格值(没有起始位或停止位)来产生。当数据线路上没有电流时,线路被认为正在发送 Break。

Break 信号的持续时间必须超过发送一个完整字节及其起始位、停止位和奇偶校验位的时间。大多数 UART 可以区分帧错误和断开信号,但如果 UART 无法区分这两者,则可以使用帧错误检测来识别断开信号。

在电传打印机时代,全国各地的许多打印机通过串联连接(如新闻服务)工作,任何设备都可以通过暂时断开整个电路来引发 Break,从而打断其他正在发送信息的位置。这种方式用于允许有紧急新闻的地方中断当前正在传送信息的位置。

在现代系统中,断开信号有两种类型。如果断开的时间超过 1.6 秒,则认为是“调制解调器断开”,一些调制解调器可以被编程在检测到此信号时终止对话并挂起电话或进入调制解调器命令模式。如果断开的时间小于 1.6 秒,则表示数据断开,远程计算机需要对此信号做出响应。有时,这种断开形式被用作注意或中断信号,有时也被视为 ASCII 控制字符 C 的替代信号。

标记和空格在纸带系统中也等同于“孔”和“无孔”。

注意

纸带或任何其他字节值无法生成断开信号,因为字节始终以起始位和停止位发送。UART 通常能够响应主机处理器的特殊命令生成连续的空格信号。

1.4.3. RS232-C DTE 和 DCE 设备

RS232-C 规范定义了两种设备类型:数据终端设备(DTE)和数据载体设备(DCE)。通常,DTE 设备是终端(或计算机),而 DCE 设备是调制解调器。在电话线路的另一端,接收方调制解调器也是一个 DCE 设备,而连接到该调制解调器的计算机是一个 DTE 设备。DCE 设备在 DTE 设备传输的引脚上接收信号,反之亦然。

当两个设备都是 DTE 或都是 DCE 且必须连接在一起时,没有调制解调器或类似的媒体转换器介入,就必须使用 NULL 调制解调器。NULL 调制解调器会电气上重新排列电缆连接,使得发送器输出连接到另一个设备的接收器输入,反之亦然。类似的转换也会在所有控制信号上进行,使得每个设备都能看到另一个设备认为是 DCE(或 DTE)的信号。

DTE 和 DCE 设备生成的信号数量是不对称的。DTE 设备为 DCE 设备生成的信号少于 DTE 设备从 DCE 设备接收到的信号。

1.4.4. RS232-C 引脚分配

EIA RS232-C 规范(以及 ITU 等效的 V.24)要求使用 25 针连接器(通常是 DB25),并定义了该连接器中大多数引脚的用途。

在 IBM 个人计算机及类似系统中,通过 9 针连接器(DB9)提供了 RS232-C 信号的一个子集。PC 连接器未包括的信号主要涉及同步操作,而 IBM 为 IBM PC 选择的 UART 并不支持这种传输模式。

根据计算机制造商的不同,可能会使用 DB25、DB9 或两种类型的连接器进行 RS232-C 通信。(IBM PC 还使用 DB25 连接器进行并行打印机接口,这可能会导致一些混淆。)

以下是 DB25 和 DB9 连接器中 RS232-C 信号的引脚分配表:

1.5. 比特、波特率和符号

波特率是异步通信中传输速度的衡量标准。由于调制解调器通信技术的进步,这个术语在描述新设备的数据传输速率时经常被误用。

传统上,波特率表示的是实际通过媒体发送的比特数,而不是实际从一个 DTE 设备传输到另一个设备的数据量。波特计数包括由发送 UART 生成并由接收 UART 移除的开销位(起始位、停止位和校验位)。这意味着七位数据字实际需要 10 位才能完全传输。因此,一个能够以每秒 300 比特的速度传输数据的调制解调器,通常只能传输 30 个 7 位数据字(如果使用了校验位,并且包含了一个起始位和停止位)。

如果使用 8 位数据字并且使用了校验位,则数据传输速率下降到每秒 27.27 个数据字,因为此时发送一个 8 位数据字需要 11 位,而调制解调器仍然以每秒 300 比特的速度发送数据。

直到错误修正调制解调器出现之前,将字节每秒转换为波特率的公式很简单。这些调制解调器从主机计算机的 UART 接收串行位流(即使使用的是内部调制解调器,数据仍然通常是串行化的),并将这些位转换回字节。然后,这些字节被组合成数据包,通过电话线使用同步传输方法发送。这意味着 DTE(计算机)中 UART 添加的起始位、停止位和校验位,在发送调制解调器发送数据之前已被移除。当这些字节被远程调制解调器接收时,远程调制解调器会为数据字添加起始位、停止位和校验位,将其转换为串行格式,然后发送给远程计算机中的接收 UART,后者再剥离起始位、停止位和校验位。

进行所有这些额外转换的原因是为了让两个调制解调器执行错误校正,这意味着接收调制解调器能够要求发送调制解调器重新发送没有正确校验和的块数据。这一检查是由调制解调器处理的,而 DTE 设备通常并不知道这一过程正在发生。

通过剥离起始位、停止位和校验位,两个调制解调器之间共享的用于执行错误校正的附加数据位大多被隐藏在发送和接收 DTE 设备所看到的有效传输速率中。例如,如果一个调制解调器将十个 7 位数据字发送到另一个调制解调器,而不包括起始位、停止位和校验位,发送调制解调器将能够添加 30 位自己的信息,接收调制解调器可以用来进行错误校正,而不会影响实际数据的传输速度。

波特这一术语的使用进一步被调制解调器压缩技术所混淆。通过电话线传输的单个 8 位数据字可能表示多个传输给发送调制解调器的词汇。接收调制解调器将数据恢复到其原始内容,并将数据传递给接收 DTE。

现代调制解调器还包括缓冲区,允许比特在电话线(DCE 到 DCE)上的传输速率与比特在 DTE 和 DCE 之间的传输速率不同。通常,DTE 和 DCE 之间的传输速率高于 DCE 到 DCE 之间的传输速率,因为调制解调器使用了压缩技术。

由于描述字节所需的比特数在两个机器之间的传输过程中有所不同,加之 DTE-DCE 和 DCE-DCE 链路上使用的不同比特每秒速度,使用波特来描述总体通信速度会引起问题,可能会误传实际的传输速率。因此,比特每秒(bps)是描述 DCE 到 DCE 接口传输速率的正确术语,波特或比特每秒(bps)都是在连接两个系统的有线连接中使用的合适术语,或者如果使用的调制解调器没有执行错误修正或压缩技术。

现代高速调制解调器(2400、9600、14,400 和 19,200bps)实际上仍然以或低于 2400 波特,或者更准确地说,以 2400 符号每秒的速度运行。高速调制解调器能够使用一种叫做星座填充(Constellation Stuffing)的方法,在每个符号中编码更多的数据比特,这就是为什么调制解调器的有效比特每秒速率更高的原因,但调制解调器仍然在电话系统提供的有限音频带宽内运行。以 28,800 及更高速度运行的调制解调器具有可变的符号率,但技术是相同的。

1.6. IBM 个人计算机 UART

从原始的 IBM 个人计算机开始,IBM 选择了 National Semiconductor 的 INS8250 UART,用于 IBM PC 并行/串行适配器。IBM 及其他厂商的兼容计算机后续版本继续使用 INS8250 或改进版的 National Semiconductor UART 系列。

1.6.1. National Semiconductor UART 系列图

INS8250 UART 有多个版本和后续的更新。以下是每个主要版本的描述。

INS8250 此部分用于原始的 IBM PC 和 IBM PC/XT。该部分的原名为 INS8250 ACE(异步通信元素),采用 NMOS 技术制造。

8250 使用八个 I/O 端口,具有一个字节的发送缓冲区和一个字节的接收缓冲区。这个原始 UART 存在多个竞争条件和其他缺陷。原始的 IBM BIOS 包含代码来解决这些缺陷,但这使得 BIOS 依赖于这些缺陷的存在,因此后来的部件如 8250A、16450 或 16550 无法在原始的 IBM PC 或 IBM PC/XT 上使用。

INS8250-B 这是由 NMOS 技术制造的较慢版本的 INS8250。它包含与原始 INS8250 相同的问题。

INS8250A 这是一个改进版的 INS8250,使用 XMOS 技术并纠正了各种功能缺陷。INS8250A 最初被 PC 克隆计算机厂商使用,这些厂商使用了“清洁”的 BIOS 设计。由于芯片的修正,这个部分不能与兼容 INS8250 或 INS8250B 的 BIOS 一起使用。

INS82C50A 这是 INS8250A 的 CMOS 版本(低功耗),具有相似的功能特性。

NS16450 与 NS8250A 相同,进行了一些改进,使其可以与更快的 CPU 总线设计一起使用。IBM 在 IBM AT 中使用了这个部分,并更新了 IBM BIOS,使其不再依赖于 INS8250 中的缺陷。

NS16C450 这是 NS16450 的 CMOS 版本(低功耗)。

NS16550 与 NS16450 相同,具有 16 字节的发送和接收缓冲区,但该缓冲区设计存在缺陷,无法可靠使用。

NS16550A 与 NS16550 相同,但修正了缓冲区缺陷。NS16550A 及其后续产品已成为 PC 行业中最受欢迎的 UART 设计,主要是因为它能够可靠地处理操作系统中响应较慢的中断时的较高数据速率。

NS16C552 该组件由两个 NS16C550A CMOS UART 组成,封装在一个单一的封装内。

PC16550D 与 NS16550A 相同,修正了细微的缺陷。这是 16550 系列的 D 版本,是 National Semiconductor 提供的最新设计。

1.6.2. NS16550AF 和 PC16550D 是相同的

National 在几年前重新组织了他们的零件编号系统,NS16550AFN 不再以这个名称存在。(如果你有一个 NS16550AFN,查看部件上的日期代码,这通常是一个四位数字,通常以 9 开头。数字的前两位表示年份,后两位表示该部件包装的年份中的周数。如果你有一个 NS16550AFN,它可能已经是几年前的部件。)

新的编号形式为 PC16550DV,后缀字母有细微差别,取决于封装材料及其形状。(编号系统的描述可以在下面找到。)

需要理解的是,在一些商店中,你可能需要支付 $15(美国)购买 1990 年制造的 NS16550AFN,而在旁边的货架上是新的 PC16550DN 部件,包含了 National 自 AFN 部件开始生产以来做的一些小修正,PC16550DN 可能是在过去六个月内生产的,并且其价格是 NS16550AFN 的一半(在批量购买时最低可达 $5),因为这些部件比较容易获得。

随着 NS16550AFN 芯片的供应继续减少,价格可能会继续上涨,直到更多人发现并接受 PC16550DN 实际上与旧部件编号具有相同功能。

1.6.3. National Semiconductor 部件编号系统

1.6.4. National Semiconductor 部件编号系统

旧的 NSnnnnnrqp 部件编号现在采用 PCnnnnnrgp 格式。

  • r 是修订字段。当前 National Semiconductor 的 16550 修订版本是 D。

  • p 是封装类型字段。类型如下所示:

  • g 是产品等级字段。如果封装类型字母前有一个 I,则表示“工业”等级部件,它具有比标准部件更高的规格,但不如军规(Milspec)部件高。这个字段是可选的。

因此,我们以前称为 NS16550AFN(DIP 封装)现在被称为 PC16550DN 或 PC16550DIN。

1.7. 其他供应商和类似的 UART

多年来,8250、8250A、16450 和 16550 被其他芯片供应商许可或复制。在 8250、8250A 和 16450 的情况下,精确的电路(“大单元”)被许可给许多供应商,包括 Western Digital 和 Intel。其他供应商则通过逆向工程获取该部件或制作出具有类似行为的仿真件。

在内部调制解调器中,调制解调器设计师通常会使用调制解调器微处理器来模拟 8250A/16450,而模拟的 UART 通常会有一个隐藏的缓冲区,包含数百个字节。由于缓冲区的大小,这些仿真可以像 16550A 一样可靠地处理高速数据。然而,大多数操作系统仍然报告该 UART 只是一个 8250A 或 16450,并且可能不会有效利用仿真 UART 中存在的额外缓冲区,除非使用特定的驱动程序。

一些调制解调器制造商受到市场压力,放弃了具有数百字节缓冲区的设计,转而使用 16550A UART,这样产品在市场比较中会有更好的表现,尽管这一做法可能会降低有效性能。

一个常见的误解是,所有标有“16550A”的部件在性能上都是相同的。实际上,存在差异,而且在这些 16550A 克隆中,大多数都存在明显的缺陷。

当开发 NS16550 时,National Semiconductor 获得了该设计的多个专利,并且限制了许可,使得其他供应商很难提供具有类似功能的芯片。由于专利的原因,逆向工程设计和仿真必须避免侵犯专利的权利。因此,这些仿制品几乎从未能与 NS16550A 或 PC16550D 完全一致,而这两个部件是大多数计算机和调制解调器制造商希望购买的部件,但有时不愿意支付获得正品部件所需的价格。

这些克隆 16550A 部件中的一些差异无关紧要,而另一些差异则可能导致该设备根本无法在特定的操作系统或驱动程序中使用。这些差异可能出现在使用其他驱动程序时,或者在发生某些未经过充分测试的事件时。这是因为大多数调制解调器供应商和 16550 克隆制造商使用 Windows® for Workgroups 3.11 中的 Microsoft 驱动程序和 Microsoft® MS-DOS® 实用程序作为与 NS16550A 兼容性的主要测试方法。由于这个过于简单的标准,如果使用不同的操作系统,则可能会由于克隆和正品部件之间的细微差异而出现问题。

National Semiconductor 提供了一款名为 COMTEST 的程序,该程序执行独立于操作系统驱动程序的兼容性测试。需要记住的是,这种程序的目的是展示竞争对手产品的缺陷,因此该程序会报告在被测试部件中出现的重大差异以及极其微妙的行为差异。

在本文档作者于 1994 年进行的一系列测试中,测试了由 National Semiconductor、TI、StarTech 和 CMD 以及嵌入在内部调制解调器中的大单元和仿真件制造的部件。以下列出了其中一些组件的差异计数。由于这些测试是在 1994 年进行的,因此它们可能无法反映当前来自供应商的特定产品的性能。

需要注意的是,COMTEST 在检测到过多问题或某些类型的问题时通常会中止。在这项测试中,COMTEST 已经过修改,以便无论遇到多少差异都不会中止。

1.7.1. COMTEST 测试结果

说明: 到目前为止,本文档的作者没有发现任何非 National 的部件在使用 COMTEST 程序时报告零差异。还应注意,National 曾推出过五个版本的 16550,而最新的部件在行为上与经典的 NS16550AFN(被认为是功能基准)有所不同。COMTEST 对 National 产品线的差异视而不见,并在 National 部件(除了原始 16550)上报告零错误,即使在 A、B 和 C 版本的部件中有官方的错误描述。因此,必须考虑到 COMTEST 中的这种偏见。

1.7.2. COMTEST 报告差异的含义

需要理解的是,COMTEST 报告的差异数量并不能揭示哪些差异是重要的,哪些是无关紧要的。例如,上述列出的两个具有内部 UART 的调制解调器报告的差异中,有大约一半是由于克隆 UART 不支持五位和六位字符模式所致。真实的 16550、16450 和 8250 UART 都支持这些模式,并且 COMTEST 会检查这些模式的功能,因此报告了五十多个差异。然而,几乎没有现代调制解调器支持五位或六位字符,特别是那些具有错误纠正和压缩功能的调制解调器。因此,与五位和六位字符模式相关的差异可以忽略不计。

COMTEST 报告的许多差异与时序有关。在许多克隆设计中,当主机从一个端口读取时,某些其他端口中的状态位可能不会像 真实的 NS16550AFN 那样在相同的时间内更新(有些更新得更快,有些则更慢),而 COMTEST 会查找这些差异。这意味着,差异的数量可能具有误导性,一个设备可能只有一到两个差异,但它们非常严重,而另一个设备可能在更新状态寄存器的速度上与参考部件有所不同(但这通常不会影响正确编写的驱动程序的操作),可能会报告几十个差异。

COMTEST 可以作为一种筛选工具,提醒管理员可能存在不兼容的部件,这些部件可能会导致问题,或者需要作为特殊情况来处理。

如果你在调制解调器中运行 COMTEST,或者调制解调器连接到串口,你需要先向调制解调器发送 ATE0&W 命令,以便调制解调器不会回显任何测试字符。如果忘记执行此操作,COMTEST 至少会报告以下一个差异:

1.8. 8250/16450/16550 寄存器

8250/16450/16550 UART 占用八个连续的 I/O 端口地址。在 IBM PC 中,这些端口有两个定义的位置,通常统称为 COM1 和 COM2。PC 克隆机和附加卡的制造商创建了另外两个区域,称为 COM3 和 COM4,但是这些额外的 COM 端口在某些系统上与其他硬件发生冲突。最常见的冲突是与提供 IBM 8514 模拟的显卡发生冲突。

COM1 位于从 0x3f8 到 0x3ff,通常使用 IRQ 4。COM2 位于从 0x2f8 到 0x2ff,通常使用 IRQ 3。COM3 位于从 0x3e8 到 0x3ef,没有标准的 IRQ。COM4 位于从 0x2e8 到 0x2ef,没有标准的 IRQ。

下面是 8250/16450/16550 UART 的 I/O 端口介绍。

1.9. 超越 16550A UART

尽管国家半导体公司(National Semiconductor)并未提供任何兼容 16550 的附加功能的组件,但其他一些供应商提供了此类组件。以下是其中的一些组件的描述。需要理解的是,要有效利用这些改进,可能需要由芯片供应商提供驱动程序,因为大多数流行的操作系统不支持超出 16550 所提供功能的特性。

ST16650 默认情况下,该组件与 NS16550A 相似,但可以选择启用扩展的 32 字节发送和接收缓冲区。由 StarTech 制造。

TIL16660 默认情况下,该组件的行为与 NS16550A 相似,但可以选择启用扩展的 64 字节发送和接收缓冲区。由德州仪器(Texas Instruments)制造。

Hayes ESP 这款专有插件卡包含 2048 字节的发送和接收缓冲区,支持最高 230.4Kbit/sec 的数据传输速率。由 Hayes 制造。

除了这些“傻”UART,许多供应商还生产智能串行通信板。此类设计通常提供一个微处理器,用于与多个 UART 连接,处理并缓存数据,然后在必要时通知主机处理器。由于 UART 并非直接由 PC 处理器访问,因此在这种通信系统中,供应商无需使用与 8250、16450 或 16550 UART 兼容的 UART。这使得设计人员可以自由选择具有更好性能特征的组件。

2. 配置 sio 驱动程序

2.1. Digi International (DigiBoard) PC/8

由 Andrew Webster awebster@pubnix.net 提供。1995年8月26日

以下是来自一台配有 Digi International PC/8 16550 的机器的配置片段。该设备连接了 8 台调制解调器,且运行良好。不要忘记添加 options COM_MULTIPORT,否则它将无法很好地工作!

设置此配置的诀窍在于标志的最高有效位(MSB)表示最后一个 SIO 端口,在此示例中为 11,因此标志为 0xb05。

2.2. Boca 16

由 Don Whiteside whiteside@acm.org提供。1995年8月26日

使用 FreeBSD 配置 Boca 16 端口板的步骤非常直接,但你需要做几件事才能使其正常工作:

  1. 你需要安装内核源代码,以便重新编译必要的选项,或者你需要其他人为你编译。默认的 2.0.5 内核没有启用多端口支持,你将需要为每个端口添加一个设备条目。

  2. 你需要知道 Boca 板的中断和 IO 设置,以便在内核中正确设置这些选项。

一个重要的注意事项 - Boca 16 的实际 UART 芯片位于连接盒中,而不是内部板上。所以如果你将连接盒拔掉,探测这些端口会失败。我从未测试过在断开连接盒后启动并重新连接的情况,我建议你也不要这样做。

  1. 将以下行添加到配置文件中:

  2. 在当前的 device sion 行所在的位置,你需要添加 16 个设备。以下示例适用于中断为 3,基础 IO 地址为 100h 的 Boca Board。每个端口的 IO 地址是从前一个端口加上 8 十六进制数,因此地址分别为 100h、108h、110h…。

    如果你使用的端口分配与示例完全相同,flags 条目 必须 与示例中的相同。flags 设置为 0xMYY,其中 M 表示主端口的次要编号(Boca 16 的最后一个端口),YY 表示是否启用了 FIFO(启用),是否使用 IRQ 共享(是),以及是否存在 AST/4 兼容的 IRQ 控制寄存器(没有)。在此示例中,

    表示主端口是 sio16。如果我添加了另一个板卡并分配了 sio17 到 sio28,那么该板卡上所有 16 个端口的 flags 将为 0x1C05,其中 1C 表示主端口的次要编号。不要更改 05 设置。

  3. 保存并完成内核配置,重新编译、安装并重启。假设你已经成功安装了重新编译的内核,并将其设置为正确的地址和 IRQ,你的启动信息应该会显示 Boca 端口的成功探测,如下所示:(显然,sio 数字、IO 和 IRQ 可能不同)

    如果信息滚动得太快看不清楚,运行:

    可以查看启动信息。

  4. 如果你出于某些原因不需要调用设备(call-out devices),则可以跳过创建 cua* 设备。

  5. 如果你想快速且不太精确地确认设备是否正常工作,可以将调制解调器插入每个端口,并且(以 root 身份)运行:

    对于你创建的每个设备,你应该看到 RX 灯闪烁,表示每个端口都正常工作。

2.3. 支持廉价的多 UART 卡

是否曾想过 FreeBSD 对你的 20 美元多功能 I/O 卡的支持,这种卡有两个(或更多)COM 端口,并且共享 IRQ?下面是如何做的:

通常,支持这类板卡的唯一选项是为每个端口使用独立的 IRQ。例如,如果你的 CPU 板上有一个板载 COM1 端口(即 sio0,I/O 地址 0x3F8,IRQ 4),并且你有一个扩展板带有两个 UART,通常需要将它们配置为 COM2(即 sio1,I/O 地址 0x2F8 和 IRQ 3),第三个端口(即 sio2)的 I/O 地址为 0x3E8 和 IRQ 5。显然,这浪费了 IRQ 资源,因为理论上应该可以通过 COM_MULTIPORT 配置(在前述部分中已介绍)使用单个 IRQ 来运行两个扩展板端口。

这类廉价 I/O 板通常具有一个 4x3 跳线矩阵,用于 COM 端口,如下所示:

上图显示了端口 A 连接到 IRQ 5,端口 B 连接到 IRQ 3。你具体板上的 IRQ 列可能有所不同——其他板可能会提供 IRQ 3、4、5 和 7 等跳线。

有人可能会认为,将两个端口都连接到 IRQ 3,使用手工制作的跳线将 IRQ 3 列中的三个连接点全部连接起来,就能解决问题,但事实并非如此。你不能复制 IRQ 3,因为每个 UART 的输出驱动器是以 "totem pole" 方式连接的,因此,如果其中一个 UART 驱动 IRQ 3,输出信号将不是你所预期的。根据扩展板或主板的实现,IRQ 3 线将始终保持高电平或始终保持低电平。

二极管的阴极连接到一个公共点,并且与一个 1 kOhm 的下拉电阻一起连接。必须将电阻连接到地面,以避免 IRQ 线在总线上漂浮。

现在我们可以配置内核了。以这个例子为例,我们可以配置:

尽管 /sys/i386/isa/sio.c 在上述 "irq maps" 数组的使用上有些晦涩,但基本思想是你在第一个、第三个和第四个位置观察到 0x1。这意味着相应的 IRQ 在输出时已设置,并在之后被清除,这正是我们所期望的。如果你的内核没有显示这种行为,那么大概率是你的接线出了问题。

3. 配置 cy 驱动

由 Alex Nash 提供,1996 年 6 月 6 日

Cyclades 多端口卡基于 cy 驱动,而不是其他多端口卡常用的 sio 驱动。配置过程很简单,步骤如下:

  1. 在内核配置文件中添加 cy 设备(请注意,你的 IRQ 和 I/O 内存设置可能不同)。

  2. 重新构建并安装新内核。

  3. 通过以下命令创建设备节点(以下示例假设是一个 8 端口板):

  4. 如果需要,可以通过复制串口设备(ttyd)条目并使用 ttyc 代替 ttyd,将拨号条目添加到 /etc/ttys 文件中。例如:

  5. 使用新内核重启。

4. 配置 si 驱动

由 Nick Sayer mailto:nsayer@FreeBSD.org 提供,1998 年 3 月 25 日

Specialix SI/XIO 和 SX 多端口卡使用 si 驱动。单台机器最多可以安装 4 张主卡。支持的主卡如下:

  • ISA SI/XIO 主卡(2 个版本)

  • EISA SI/XIO 主卡

  • PCI SI/XIO 主卡

  • ISA SX 主卡

  • PCI SX 主卡

尽管 SX 和 SI/XIO 主卡外观有所不同,但它们的功能基本相同。这些主卡不使用 I/O 地址,而是需要一个 32K 的内存区域。ISA 卡的出厂配置将内存区域放置在 0xd0000-0xd7fff。它们还需要一个 IRQ。PCI 卡将自动配置。

每张主卡最多可以连接 4 个外部模块。外部模块包含 4 个或 8 个串口。它们有以下几种类型:

  • SI 4 或 8 端口模块。每个端口支持最高 57600 bps。

  • XIO 8 端口模块。每个端口支持最高 115200 bps。一种类型的 XIO 模块有 7 个串口和 1 个并口。

  • SXDC 8 端口模块。每个端口支持最高 921600 bps。像 XIO 一样,某些模块也有一个并口。

要配置 ISA 主卡,请在内核配置文件中添加以下行,按需要修改数字:

有效的 IRQ 号码对于 SX ISA 主卡是 9、10、11、12 和 15,对于 SI/XIO ISA 主卡是 11、12 和 15。

要配置 EISA 或 PCI 主卡,请使用以下行:

添加配置条目后,重新构建并安装新内核。

注意

在使用新内核重启后,你需要在 /dev 目录中创建设备节点。MAKEDEV 脚本会处理此操作。计算你有多少个端口,并输入以下命令:

(其中 nn 为端口数量)

如果希望在这些端口上出现登录提示,你需要将类似以下的行添加到 /etc/ttys 文件中:

根据需要更改终端类型。对于调制解调器,dialup 或 unknown 就可以了。

为什么要为开源项目选用类 BSD 许可证?

1. 引言

本文主张使用 BSD 风格的许可证来处理软件和数据,特别推荐用 BSD 风格许可证来取代 GPL。你也可以将其看作是 BSD 与 GPL 开源许可证的概述和总结。

2. 开源简史

这种模式在 1960 年代发生了变化。1965 年,ADR 开发了第一款独立于硬件公司之外的授权软件产品。ADR 与原由 IBM 客户开发的免费 IBM 软件包竞争。1968 年,ADR 对其软件进行了专利保护。为了阻止软件共享,他们通过设备租赁的方式提供软件,租赁付款按产品生命周期分期支付。ADR 保留了所有权并控制转售和再利用。

1969 年,美国司法部指控 IBM 通过将免费软件与 IBM 硬件捆绑,摧毁了其他企业。作为诉讼的结果,IBM 解除了其软件捆绑,即软件变成了与硬件分离的独立产品。

1968 年,Informatics 推出了第一款商业“杀手级应用”,并迅速确立了软件产品、软件公司以及非常高的回报率的概念。Informatics 开发了现在在计算机行业中标准化的永久许可证,客户永远无法获得所有权。

3. 从 BSD 许可证的角度看 Unix

AT&T 拥有原始的 Unix 实现,它是一家受到公共监管的垄断公司,因此在反垄断诉讼中,它无法将产品销售到软件市场。然而,它可以以介质费用的形式提供给学术机构。

在一次操作系统会议上,Unix 的可用性被广泛宣传,大学们迅速采用了 Unix。Unix 能运行在 PDP-11 上,这是一款非常实惠的 16 位计算机,并且它是用一种高级语言编写的,证明这种语言非常适合系统编程。DEC 的 PDP-11 事实上具有开放的硬件接口,旨在让客户方便地编写自己的操作系统,这在当时是常见的。正如 DEC 创始人 Ken Olsen 所著名的宣称,“当你有了好硬件,软件就像是从天堂降临一样”。

Unix 作者 Ken Thompson 于 1975 年回到他的母校——加州大学伯克利分校(UCB),并逐行讲解内核代码。这最终催生了一款名为 BSD(伯克利标准发行版)的系统的演变。UCB 将 Unix 转换为 32 位,添加了虚拟内存,并实现了 TCP/IP 协议栈,这成为了互联网的基石。加州大学伯克利分校将 BSD 提供给用户,收取介质费用,并在 BSD 许可证下发布。

1980 年代中期,美国政府对 AT&T 提起了反垄断诉讼,最终导致了 AT&T 的拆分。AT&T 仍然持有 Unix,并且现在可以将其出售。AT&T 开始进行积极的许可工作,许多当时的商业 Unix 都源自 AT&T。

1990 年代初,AT&T 因与 BSD 相关的许可侵权问题起诉了加州大学伯克利分校。伯克利分校发现 AT&T 在未进行承认或支付的情况下,将 BSD 的许多改进全面引入到了 AT&T 的产品中,随之而来的是一场长期的诉讼,主要在 AT&T 和伯克利分校之间进行。在此期间,一些伯克利分校的程序员开始重新编写与 BSD 相关的 AT&T 代码。这一项目最终形成了一款名为 BSD 4.4-lite 的系统(因为它不是一个完整的系统,缺少了 6 个关键的 AT&T 文件)。

稍后,《Dr. Dobbs》杂志上发布了一系列文章,介绍了一款基于 BSD 的 386 PC 版本的 Unix,它使用了 BSD 许可证下的替代文件来替代 4.4 lite 中缺失的 6 个文件。这个系统由前伯克利分校程序员 William Jolitz 开发,成为了今天所有 PC BSD 系统的原始基石。

1990 年代中期,Novell 收购了 AT&T 的 Unix 权利,并达成了一个(当时保密的)协议来终止诉讼。加州大学伯克利分校随后终止了对 BSD 的支持。

4. FreeBSD 及 BSD 许可证的当前状态

不要将新 BSD 许可证与“公共领域”混淆。尽管公共领域的内容也是供所有人自由使用,但它没有所有者。

5. GPL 的起源

在 1980 年代末至 1990 年代初,Unix 的未来变得非常模糊时,GPL(另一项重要的许可证发展)最终得以实现。

GPL 被设计为标准专有许可证的对立面。为此,对 GPL 程序所做的任何修改都必须回馈给 GPL 社区(要求该程序的源代码对用户可用),任何使用或链接到 GPL 代码的程序也必须使用 GPL 许可证。GPL 旨在防止软件变成专有软件。正如 GPL 的最后一段所说:

  • 你能对分发、支持或文档编写软件收取任何费用,但你不能出售软件本身。

  • 基本规则指出,如果编译程序需要 GPL 源代码,则该程序必须遵循 GPL。静态链接到 GPL 库的程序必须遵循 GPL。

  • GPL 要求与 GPL 软件相关的任何专利必须为所有人免费使用。

  • 简单地将软件聚合在一起,例如将多个程序放在同一磁盘上,并不算是将 GPL 程序包含在非 GPL 程序中。

  • 程序的输出不算作衍生作品。这使得 gcc 编译器可以在商业环境中使用,而不会遇到法律问题。

  • 由于 Linux 内核是 GPL 许可的,任何与 Linux 内核静态链接的代码必须也遵循 GPL。这个要求可以通过动态链接可加载的内核模块来规避。这允许公司分发二进制驱动程序,但通常有一个缺点,即这些驱动程序只能在特定版本的 Linux 内核上运行。

由于其复杂性,今天在许多国家/地区,有关 Linux 和相关软件的 GPL 法律问题已被忽视。其长期后果尚不明确。

6. Linux 的起源与 LGPL

在商业 Unix 战争激烈进行时,Linux 内核作为一款 PC Unix 克隆系统被开发出来。Linus Torvalds 归功于 GNU C 编译器及相关的 GNU 工具,使 Linux 得以存在。他将 Linux 内核发布为 GPL。

如果你将应用程序与 glibc 静态链接,例如嵌入式系统中常常需要的那样,你不能将你的应用程序保持专有,也就是说,源代码必须发布。GPL 和 LGPL 都要求对直接在许可证下的代码进行的任何修改都必须发布。

7. 开源许可证与孤儿问题

一个专有软件相关的严重问题是“孤儿”(orphaning)。这发生在一个单一的商业失败或产品策略变化导致一大批依赖的系统和公司因超出其控制的原因而失败。数十年的经验表明,软件供应商的瞬时规模或成功并不能保证其软件始终可用,因为当前市场条件和策略可以迅速变化。

GPL 尝试通过切断与专有知识产权的联系来防止孤儿问题。

BSD 许可证为小公司提供了类似软件托管的效果,而没有任何法律复杂性或成本。如果一款 BSD 许可证的软件变成孤儿,企业可以简单地接管该程序,以专有方式使用它,前提是他们依赖于此程序。更理想的情况是,当 BSD 代码库由一家小型非正式的联盟维护时,因为开发过程不依赖于单一公司或产品线的生存。当开发团队在精神上处于活跃状态时,其生存能力远比源代码的物理可用性更为重要。

8. 许可证无法做到的事情

没有任何许可证能够保证软件的未来可用性。尽管版权持有者通常可以随时更改版权的条款,但在 BSD 社区中,假设这种尝试会导致源代码复刻。

9. GPL 的优缺点

使用 GPL 的常见原因之一是在修改或扩展 gcc 编译器时。这尤其适用于在一些特殊的 CPU 环境中工作,在这些环境中,所有软件成本可能都被视为开销,并且对其他人使用所产生的编译器的期望很低。

GPL 对于小公司销售 CD 的环境也非常有吸引力,尤其是在“低买高卖”的情况下,最终用户可能会获得非常廉价的产品。它对于那些预期通过为 GPL 知识产权世界提供各种形式的技术支持(包括文档)来生存的公司也很有吸引力。

GPL 的一个不太为人知的并且非故意的用途是,它对那些想要打击软件公司的大公司非常有利。换句话说,GPL 非常适合用作营销武器,有可能减少整体经济效益并助长垄断行为。

对于那些希望将软件商业化并从中获利的人来说,GPL 可能会带来真正的问题。例如,GPL 增加了研究生直接成立公司以商业化其研究成果的难度,或者增加了学生加入公司的难度,假设一个有前景的研究项目将被商业化。

对于那些必须使用多个软件标准的静态链接实现的人来说,GPL 通常不是个好的许可证,因为它排除了使用专有实现标准的可能性。GPL 从而最小化了可以使用 GPL 标准构建的程序数量。GPL 旨在不提供机制来开发一个可以让工程师开发专有产品的标准。(这不适用于 Linux 应用程序,因为它们不进行静态链接,而是使用基于陷阱的 API。)

GPL 尝试让程序员对一款不断发展的程序套件做出贡献,然后在该套件的分发和支持中进行竞争。对于许多必需的核心系统标准,这种情况并不现实,因为它们可能会在广泛变化的环境中应用,这些环境需要商业定制或与现有(非 GPL)许可证下的遗留标准集成。实时系统通常是静态链接的,因此 GPL 和 LGPL 对许多嵌入式系统公司来说,肯定是潜在问题。

GPL 是一种试图将努力保持在研究和开发阶段的机制。这最大化了研究人员和开发人员的好处,而对那些希望更广泛分发的利益方则可能造成未知的成本。

GPL 旨在防止研究成果转变为专有产品。这个步骤通常被认为是传统技术转移流程中的最后一步,在最好的情况下通常也足够困难;GPL 的目的是使这个过程变得不可能。

10. BSD 的优点

BSD 风格的许可证是长时间研究或其他项目的好选择,尤其是当这些项目需要一个开发环境时:

  • 几乎零成本

  • 可以长期发展

  • 允许任何人保留将最终结果商业化的选择,且没有法律问题。

这一最终考量可能经常是主导因素,就像 Apache 项目决定使用该许可证时一样:

“这种许可证非常适合推广实现通用服务协议的参考代码的使用。这也是我们为 Apache 团队选择它的原因之一——我们中的许多人希望看到 HTTP 存活并成为一个真正的多方标准,甚至不介意微软或 Netscape 将我们的 HTTP 引擎或代码的任何其他部分纳入他们的产品中,如果这有助于保持 HTTP 的普及……所有这些意味着,从战略上讲,项目需要保持足够的动力,参与者通过为项目贡献他们的代码来实现更大的价值,甚至是那些如果保密仍然有价值的代码。”

开发人员通常会发现 BSD 许可证有吸引力,因为它远离了法律问题,能让他们随意使用代码。相比之下,那些主要期望使用系统而不是编程的人,或者期望其他人发展代码的人,或不期望从与系统相关的工作中谋生的人(例如政府雇员),则会发现 GPL 更具吸引力,因为它强迫其他人开发的代码必须提供给他们,并且防止他们的雇主保留版权,从而可能“埋葬”或孤儿软件。如果你想迫使竞争对手帮助你,GPL 会更具吸引力。

BSD 许可证并非简单的赠与。关于 BSD 许可证,常常会有人提出“我们为什么要帮助竞争对手或让他们偷走我们的工作?”这个问题。在 BSD 许可证下,如果一家公司开始主导某个被认为是战略性产品细分的市场,其他公司可以通过最小的努力,组成一家小型联盟,旨在通过贡献竞争性的 BSD 变体重新恢复平衡,从而增加市场竞争力和公平性。这能让每家公司相信它将能够从其提供的某种优势中获利,同时也促进经济灵活性和效率。合作成员可以越快速、越容易地完成这一点,它们就会越成功。BSD 许可证本质上是一种简化的许可证,使这种行为成为可能。

GPL 的一个关键作用是,以媒体成本广泛提供一个完整且竞争力强的开源系统,是一个合理的目标。BSD 风格的许可证,结合个体自发的联盟,可以实现这一目标,而不会破坏围绕技术转移管道部署端建立的经济假设。

11. 使用 BSD 许可证的具体建议

  • BSD 许可证在将研究成果转化为广泛部署并对经济产生最大效益的方式方面更为优越。因此,研究资助机构(如 NSF、ONR 和 DARPA)应在资助的研究项目的初期阶段,建议采纳 BSD 风格的许可证用于软件、数据、成果和开放硬件。同时,他们还建议基于已实现的开源系统和持续进行的开源项目应形成标准。

  • 政府政策应尽量减少从研究到部署的成本和难度。在可能的情况下,资助应要求成果能够以有利于商业化的 BSD 风格许可证提供。

  • 在许多情况下,BSD 风格许可证的长期结果更能准确反映大学研究章程中所宣称的目标,而不是那些受专有大学许可的成果(如版权或专利)。有经验的证据表明,大学通过发布研究成果并通过商业成功的校友寻求捐赠,往往能够获得更好的长期财务回报。

  • 公司早已认识到,创建事实标准是一个重要的营销手段。BSD 许可证很好地担任了这一角色,特别是当公司确实拥有独特的优势来发展系统时。该许可证对最广泛的受众具有法律吸引力,而公司的专业知识则确保了他们的控制。在某些时候,GPL 可能是创建此类标准的合适工具,尤其是在试图削弱或收购他人时。然而,GPL 会惩罚该标准的演化,因为它促进的是某款套件,而不是商业适用的标准。使用这种套件会不断引发商业化和法律问题。如果一些标准受 GPL 约束而其他标准不受其约束,可能无法混合标准。真正的技术标准不应因为非技术原因强制排除其他标准。

  • 有兴趣推动一家不断发展的标准的公司,该标准可以成为其他公司商业产品核心的公司,应谨慎使用 GPL。无论使用何种许可证,最终的结果通常会归属于那些实际做出大部分工程更改并且最了解系统状态的人。GPL 只是给结果增加了更多的法律摩擦。

  • 在开放源代码开发的公司中,大公司应意识到程序员欣赏开源,因为它使员工在换工作时仍能访问软件。一些公司将这种行为作为一种就业福利,特别是当涉及的软硬件不直接具有战略意义时。实际上,这是一种前期退休福利,尽管存在潜在的机会成本,但没有直接成本。鼓励员工在公司外为同行认同而工作,是公司可以提供的一个低成本、可转移的福利,几乎没有任何负面影响。

  • 有软件项目易受孤儿问题困扰的小公司应尽可能使用 BSD 许可证。所有规模的公司都应考虑在对维护最小法律和组织开销的真实 BSD 风格开源项目有共同利益时,发起此类开源项目。

  • 非营利组织应尽可能参与开源项目。为避免混合不同许可证代码所带来的软件工程问题,应鼓励使用 BSD 风格的许可证。尤其是与发展中国家互动的非营利组织应特别谨慎对待 GPL。在某些地区,法律的适用可能成为一项昂贵的工作,与 GPL 相比,新 BSD 许可证的简洁性可能带来相当大的优势。

12. 结论

与旨在防止开源代码专有化的 GPL 不同,BSD 许可证对未来行为的限制非常少。这使得 BSD 代码可以在项目或公司需求变化时,既保持开源,也可以整合到商业解决方案中。换句话说,BSD 许可证在开发过程中不会变成一个法律定时炸弹。

此外,由于 BSD 许可证不具备 GPL 或 LGPL 许可证的法律复杂性,它使得开发者和公司可以将时间用于创建和推广优质代码,而不是担心该代码是否违反了许可条款。

参考文献

  • [3] 《开源:未授权的白皮书》,Donald K. Rosenberg,IDG Books,2000。引用自第 114 页,“GNU GPL 的影响”。

使用 FreeBSD 源代码树中的语言服务器进行开发

1. 介绍

本指南介绍了如何设置带有源代码索引的 FreeBSD src 树,使用语言服务器进行代码索引。该指南涵盖了 Vim/NeoVim 和 VSCode 的设置步骤。如果你使用其他文本编辑器,可以将本指南作为参考,并搜索适用于你首选编辑器的相应命令。

2. 系统要求

为了跟随本指南,我们需要安装一些必备组件。我们需要一个语言服务器,ccls 或 clangd,以及一个可选的编译数据库。

语言服务器的安装可以通过 pkg 或者 Ports 来完成。如果我们选择 clangd,则需要安装 llvm。

使用 pkg 安装 ccls:

如果我们想使用 clangd,需要安装 llvm(示例命令使用 llvm15,但可以根据需要选择版本):

通过 Ports 安装,可以根据下列类别选择你最喜欢的工具组合:

  • 语言服务器实现

  • 编辑器

  • 编译数据库生成器

3. 编辑器设置

3.1. Vim/Neovim

3.1.1. LSP 客户端插件

为 Neovim 设置 LSP 客户端插件:

为 Vim 设置:

启用编辑器中的 LSP 客户端插件时,在 Neovim 中将以下代码片段添加到 ~/.config/nvim/init.vim,在 Vim 中添加到 ~/.vim/vimrc:

对于 ccls

对于 clangd

根据你安装的 clangd 版本,可能需要更新 server-info 以指向正确的二进制文件。

以下是关键绑定和代码补全的参考设置。将以下代码片段添加到 ~/.config/nvim/init.vim,或者 Vim 用户可以将其添加到 ~/.vim/vimrc 以使用:

3.2. VSCode

3.2.1. LSP 客户端插件

LSP 客户端插件是启动语言服务器守护进程所必需的。按 Ctrl+Shift+X 显示扩展在线搜索面板。当使用 clangd 时,输入 llvm-vs-code-extensions.vscode-clangd,或者使用 ccls 时,输入 ccls-project.ccls。

接着,按 Ctrl+Shift+P 显示编辑器命令面板,输入 Preferences: Open Settings (JSON),然后按 Enter 打开 settings.json。根据所使用的语言服务器实现,在 settings.json 中添加以下 JSON 键值对之一:

对于 clangd

对于 ccls

4. 编译数据库

编译数据库包含一个编译命令对象数组。每个对象指定了一种编译源文件的方式。编译数据库文件通常为 compile_commands.json。该数据库由语言服务器实现用于索引。

4.1. 生成器

4.1.1. 使用 scan-build-py

4.1.1.1. 安装

scan-build-py 中的 intercept-build 工具用于生成编译数据库。

其中 /path/to/llvm-project/ 是你希望存放仓库的路径。为了方便起见,可以在 shell 配置文件中为其创建别名:

4.1.1.2. 使用方法

在 FreeBSD src 树的顶级目录中,使用 intercept-build 生成编译数据库:

--append 标志告诉 intercept-build 读取现有的编译数据库(如果存在),并将结果追加到数据库中。具有重复命令键的条目将被合并。默认情况下,生成的编译数据库保存在当前工作目录下,文件名为 compile_commands.json。

4.1.2. 使用 devel/bear

4.1.2.1. 使用方法

在 FreeBSD src 树的顶级目录中,使用 bear 生成编译数据库:

--append 标志告诉 bear 如果现有的编译数据库存在,则读取并将结果追加到数据库中。具有重复命令键的条目将被合并。默认情况下,生成的编译数据库保存在当前工作目录下,文件名为 compile_commands.json。

5. 最终步骤

生成编译数据库后,打开 FreeBSD src 树中的任何源文件,LSP 服务器守护进程将会在后台启动。第一次打开 src 树中的源文件时,LSP 服务器需要进行初始的后台索引,这可能会花费较长时间,因为它需要编译编译数据库中列出的所有条目。然而,语言服务器守护进程不会索引没有出现在编译数据库中的源文件,因此没有被 make 编译的源文件不会显示完整的结果。

编写 FreeBSD 问题报告

摘要

本文介绍了如何最好地构建和提交问题报告给 FreeBSD 项目。

1. 引言

作为软件用户,最令人沮丧的经历之一就是提交问题报告后,报告被草率关闭,并附上简短且毫无帮助的解释,比如“不是 bug”或“无效的 PR”。同样,作为软件开发者,最令人沮丧的经历之一是收到大量的问题报告,这些报告实际上并不是问题报告,而是支持请求,或者根本没有提供足够的信息来陈述问题和如何重现它。

本文试图概述如何写好问题报告。那么,什么是好的问题报告呢?简单来说,好的问题报告是能够迅速分析并解决的,能让用户和开发者双方都感到满意。

虽然本文的主要关注点是 FreeBSD 问题报告,但其中的大部分内容同样适用于其他软件项目。

请注意,本文是按主题组织的,而不是按时间顺序。提交问题报告之前,请阅读完整本文,而不是将其作为字面教程来使用。

2. 何时提交问题报告

问题有很多种,并不是所有问题都应提交问题报告。当然,人无完人,也会有一些时候,表面上看似程序中的 bug,实际上只是对命令语法的误解,或者配置文件中的错别字(虽然这本身可能反映了文档不足或程序处理错误的不足)。仍然有许多情况,提交问题报告显然不是正确的行动,并且只会使提交者和开发者都感到沮丧。而有些情况下,提交问题报告可能不仅仅是报告 bug,而是提交增强或新特性请求。

提交 PR 时,请考虑以下因素,尤其是关于 Ports 或其他 FreeBSD 系统之外的软件:

  • 请不要提交仅仅表明应用程序有新版本发布的问题报告。Ports 维护者会自动通过 portscout 接收到有关新版本发布的通知。欢迎提交更新 Port 到最新版本的实际补丁。

  • 对于无维护者的 Ports(MAINTAINER 是 ports@FreeBSD.org),没有补丁的 PR 不太可能被提交者采纳。如果想成为无维护者 Port 的维护者,可以提交带有请求的 PR(推荐附带补丁,但不是必须的)。

无法重现的 bug 很少能被修复。如果 bug 只发生了一次,并且你无法重现它,也没有其他人遇到这个问题,那么开发者很可能也无法重现它或弄清楚问题出在哪里。这并不意味着它没有发生,但意味着问题报告很可能无法导致 bug 修复。更糟糕的是,这些类型的 bug 很可能是由于硬盘故障或处理器过热引起的——在提交 PR 之前,你应该尽量排除这些原因。

接下来,决定向谁提交问题报告时,你需要了解 FreeBSD 所包含的软件是由几个不同的元素构成:

  • 由 FreeBSD 贡献者编写和维护的基本系统代码,如内核、C 库和设备驱动程序(分类为 kern);二进制工具(bin);手册页和文档(docs);以及网页(www)。这些领域的所有 bug 都应该报告给 FreeBSD 开发者。

  • 不在基本系统中的个别应用程序,而是 FreeBSD Ports(ports)的一部分。这些应用程序大多数不是 FreeBSD 开发者编写的,FreeBSD 提供的只是安装该应用程序的框架。因此,只有当问题被认为是 FreeBSD 特有时,才应向 FreeBSD 开发者报告;否则,应报告给软件的原作者。

然后,确认问题是否及时。如果问题已经被开发者修复,提交问题报告可能会让开发者感到烦恼。

如果问题出现在某个 Port 中,考虑向上游提交 bug。FreeBSD 项目无法修复所有软件中的 bug。

3. 准备工作

在提交问题报告之前,遵循一个好的规则是先做背景调查。也许问题已经被报告过,或者已经在邮件列表中讨论过,甚至可能已经在你正在使用的版本中修复。因此,在提交问题报告之前,你应该检查所有明显的地方。对于 FreeBSD,这意味着:

  • 可选地,使用你喜欢的搜索引擎来查找与问题相关的任何资料。你甚至可能会找到来自归档邮件列表或新闻组的内容,可能是你之前没有想到的地方。

4. 编写问题报告

现在你已经确定了问题值得提交问题报告,并且它确实是 FreeBSD 的问题,接下来就是编写实际问题报告了。在我们深入探讨生成和提交 PR 的程序机制之前,以下是一些小贴士,帮助确保你的 PR 更有效:

5. 编写良好问题报告的技巧

  • 不要留空“摘要”行。PR 会发送到一个遍布全球的邮件列表(在这个列表中,“摘要”用作 Subject: 行),也会进入一个数据库。任何后来浏览数据库的人,如果看到一个没有摘要的 PR,通常会跳过它。请记住,PR 会一直保存在数据库中,直到被关闭;一个没有摘要的 PR 很可能会被淹没在噪音中。

  • 避免使用模糊的“摘要”行。你不应该假设阅读你 PR 的人已经对你的提交有任何背景了解,所以提供越多信息越好。例如,问题应用于系统的哪个部分?你是只在安装时遇到问题,还是在运行时?举例来说,代替 Summary: portupgrade is broken,可以写成 Summary: port ports-mgmt/portupgrade coredumps on -current,这样显得信息更充实。(对于 Port 问题,特别是提供类别和 Port 名称作为“摘要”会更加有用。)

  • 如果有补丁,告诉我们。有补丁会大大加快问题的处理进度。

    • 不要使用 patch 或 patch-ready 关键字 —— 它们已经被弃用了。

  • 如果你是维护者,说明这一点。如果你是某部分源代码的维护者(例如,现有 Port 的维护者),你应该将 PR 的“类别”设置为 maintainer-update。这样处理你 PR 的提交者就不需要再次检查。

  • 具体描述问题。你提供的关于问题的信息越多,越有可能得到回应。

    • 包括你正在使用的 FreeBSD 版本(有相关字段可以填写),以及所使用的架构。如果你是从 CD-ROM 或下载的版本安装的,或者是从 Git 维护的系统上运行的(如果是,提供相关的哈希值和分支)。如果你在使用 FreeBSD-CURRENT 分支,那是别人首先会问的问题,因为修复(尤其是针对高影响问题的修复)通常会迅速提交,而 FreeBSD-CURRENT 用户需要保持更新。

    • 包括你在 make.conf、src.conf 和 src-env.conf 中指定的全局选项。由于选项的组合极为复杂,并非所有组合都完全支持。

    • 如果问题可以轻松复现,请提供有助于开发人员复现问题的信息。如果问题可以通过特定的输入进行演示,尽可能提供该输入示例,并包括实际输出和预期输出。如果数据较大或不能公开,请尝试创建一个最小的文件来重现问题,并将其附加到 PR 中。

    • 如果这是内核问题,请准备提供以下信息(默认情况下不必包含这些内容,这样会填充数据库,但如果你认为相关,可以提供以下摘录):

      • 你的内核配置(包括你安装的硬件设备)

      • 是否启用了调试选项(如 WITNESS),如果启用了,切换此选项后问题是否仍然存在

      • 任何堆栈回溯、panic 或其他控制台输出的完整文本,或者 /var/log/messages 中的条目,如果有的话

      • pciconf -l 的输出,以及与你的硬件相关的部分 dmesg 输出

      • 你是否已经阅读了 src/UPDATING,并且问题没有列在那里(有人肯定会问)

      • 你是否能够运行任何其他内核作为回退(这是为了排除硬件相关问题,例如硬盘故障和 CPU 过热,这些问题可能伪装成内核问题)

    • 如果是 Port 问题,请准备提供以下信息(默认情况下不必包含这些内容,但如果你认为相关,可以提供以下摘录):

      • 已安装的 Port

      • 任何覆盖 bsd.port.mk 中默认设置的环境变量,例如 PORTSDIR

      • 你是否阅读了 ports/UPDATING,并且问题没有列在那里(有人肯定会问)

  • 避免模糊的功能请求。像“应该有人实现某个功能”这样的 PR 不太可能得到有效处理,比起不明确的请求,更具体的请求更能获得结果。请记住,源代码对每个人都开放,因此,如果你希望某个功能被加入,最好的方式就是亲自动手实现它!同时考虑到许多此类请求更适合在 freebsd-questions 中讨论,而不是在 PR 数据库中提交。

  • 每个问题报告只提交一个问题。避免在一个报告中包含两个或更多问题,除非它们密切相关。在提交补丁时,避免将多个功能或者多个 bug 的修复集中在同一个 PR 中,除非它们紧密相关——这样 PR 通常需要更长时间才能解决。

  • 保持礼貌。几乎所有可能处理你 PR 的人都是志愿者。没有人喜欢被告知必须做某事,尤其是当他们已经出于非金钱动机在做这件事时。始终记住这一点,尤其是在开源项目中。

6. 在开始之前

最后,如果提交内容较长,建议在离线状态下准备工作,以防在提交过程中出现问题导致数据丢失。

7. 附加补丁或文件

对于涉及内核或基础工具的问题,推荐提交一个针对 FreeBSD-CURRENT(主 Git 分支)的补丁,因为所有新代码应该首先在该分支上应用和测试。经过适当或充分的测试后,代码将会合并到 FreeBSD-STABLE 分支。

如果你将补丁内嵌而非附加为文件,请注意,最常见的问题是一些电子邮件程序会将制表符渲染为空格,这将完全破坏任何打算作为 Makefile 一部分的内容。

不要使用 Content-Transfer-Encoding: quoted-printable 格式发送附件中的补丁。此格式会对字符进行转义,导致整个补丁变得无法使用。

还请注意,虽然在 PR 中包含小补丁通常是可以接受的,尤其是当它们修复了 PR 中描述的问题时;但对于较大的补丁,尤其是可能需要大量审查的新代码,应该将其放置在网页或 FTP 服务器上,并将 URL 包含在 PR 中,而不是直接附加补丁。电子邮件中的补丁往往会被破坏,补丁越大,相关方还原的难度越大。此外,将补丁发布到网页上可以让你在不重新提交整个补丁的情况下修改它。最后,大型补丁会增加数据库的大小,因为关闭的 PR 实际上不会被删除,而是被保留并标记为完成。

还需要注意的是,除非你在 PR 或补丁中明确说明,否则你提交的任何补丁都将被假定按照你修改的原始文件的相同许可条款进行授权。

8. 填写表单

注意

你使用的电子邮件地址将成为公开信息,并可能被垃圾邮件发送者获取。你应当采取垃圾邮件处理措施,或使用临时电子邮件账户。然而,请注意,如果你完全不使用有效的电子邮件账户,我们将无法就你的 PR 向你提问。

当你提交一个 bug 时,你将看到以下字段:

  • 摘要: 请简洁而准确地描述问题。摘要将用作问题报告电子邮件的主题,并会在问题报告列表和总结中使用;摘要不清晰的问题报告往往会被忽视。

  • 严重性: 选择 仅影响我、影响一些人 或 影响许多人。不要过度反应;除非确实如此,否则请避免将你的问题标记为 影响许多人。如果你夸大问题的严重性,FreeBSD 开发者不会因此而加快解决问题的速度,因为其他人也可能做了同样的事。

  • 类别: 选择合适的类别。

    首先,你需要确定问题所在的系统部分。请记住,FreeBSD 是一个完整的操作系统,安装了内核、标准库、许多外设驱动程序和大量的工具(“基本系统”)。然而,Ports 中还有成千上万的其他应用程序。你需要首先决定问题是在基本系统中,还是通过 Ports 安装的内容。

    以下是主要类别的说明:

    • 如果问题与内核、库(例如标准 C 库 libc)或基本系统中的外设驱动程序有关,通常应使用 kern 类别。(有一些例外;见下文)。一般来说,这些是手册页第 2、3 或 4 节描述的内容。

    • 如果你认为问题出在启动 (rc) 脚本,或某种其他非可执行配置文件中,正确的类别是 conf(配置)。这些内容通常在手册页第 5 节描述。

    • 如果你发现文档集(文章、书籍、手册页)或网站中的问题,正确的类别是 docs。

      注意

      如果你遇到的问题来自某个名为 www/someportname 的 Port,仍然应选择 ports 类别。

还有一些更专业的类别:

  • 如果问题本应归类为 kern,但与 USB 子系统相关,正确的类别是 usb。

  • 如果问题本应归类为 kern,但与线程库相关,正确的类别是 threads。

  • 如果问题本应归类为基本系统,但与我们遵循的标准(如 POSIX®)相关,正确的类别是 standards。

  • 如果你确信问题只会在你使用的处理器架构下发生,请选择一个架构特定的类别:通常是 i386,用于 Intel 兼容机器的 32 位模式;amd64,用于 AMD 机器的 64 位模式(这也包括运行 EMT64 模式的 Intel 兼容机器);较少见的是 arm 或 powerpc。

    注意

    这些类别经常被错误使用来处理“我不知道”的问题。与其猜测,请直接使用 misc。

示例 1. 正确使用架构特定类别

你有一台常见的 PC 机器,认为遇到与特定芯片组或特定主板相关的问题:i386 是正确的类别。

示例 2. 错误使用架构特定类别

你遇到了一个关于常见总线上的附加外设卡或某种类型硬盘驱动器的问题:在这种情况下,问题可能不仅仅与一个架构相关,应使用 kern 类别。

  • 环境: 应尽可能准确地描述你观察到问题时的环境。这包括操作系统版本、包含问题的特定程序或文件的版本,以及任何其他相关项(如系统配置、其他已安装的软件等)—简而言之,开发者需要知道的一切,以便重现问题发生的环境。

  • 描述: 你遇到问题的完整且准确的描述。尽量避免猜测问题的原因,除非你确定自己走在正确的轨道上,因为这可能会误导开发者做出错误的假设。描述应包括重现问题所需的操作步骤。如果你知道任何解决方法,请一并提供。这样不仅能帮助其他有相同问题的人绕过它,也可能帮助开发者理解问题的原因。

9. 跟进

问题报告提交后,你将通过电子邮件收到确认,其中包括分配给你的问题报告的跟踪编号以及可以用来检查其状态的 URL。幸运的话,某人可能会对你的问题产生兴趣并尝试解决,或者根据情况,解释为何这不是一个问题。任何状态更改都会自动通知你,并且你会收到任何人可能附加到问题报告审计跟踪中的评论或补丁的副本。

如果问题报告在问题解决后仍然保持打开状态,只需添加一条评论,说明问题报告可以关闭,并尽可能解释问题是如何或何时被修复的。

有时,问题报告可能会在一两周内没有得到处理,没有人分配或评论。这可能发生在问题报告积压增加或假期期间。当问题报告在几周内没有得到关注时,可以尝试找到一位特别有兴趣处理此问题的提交者。

有几种方法可以做到这一点,理想的顺序是每次尝试一个沟通渠道,之间隔几天:

请记住,这些人和维护者、用户一样,都是志愿者,因此他们可能不会立即有空帮助处理问题报告。建议保持耐心,并持续跟进,这样有朝一日找到一个提交者来处理问题报告只是时间问题。

10. 如果遇到问题

11. 进一步阅读

这是与正确撰写和处理问题报告相关的资源列表,并不完全:

vinum 卷管理器

1. 概述

无论硬盘类型如何,总是存在潜在的问题。硬盘可能太小、太慢,或者可靠性不足,无法满足系统的要求。尽管硬盘的容量越来越大,但数据存储需求也在不断增加。常常需要一个比硬盘容量更大的文件系统。为了解决这些问题,已经提出并实现了各种解决方案。

其中一种方法是使用多个有时是冗余的硬盘。除了支持各种卡和控制器用于硬件独立磁盘冗余阵列(RAID)系统外,FreeBSD 基本系统还包括 vinum 卷管理器,它是一个块设备驱动程序,实现了虚拟磁盘驱动器,并解决了这三个问题。vinum 提供比传统磁盘存储更高的灵活性、性能和可靠性,并实现了 RAID-0、RAID-1 和 RAID-5 模型,既可以单独使用,也可以组合使用。

本章概述了传统磁盘存储的潜在问题,并介绍了 vinum 卷管理器。

警告

注意

2. 访问瓶颈

现代系统经常需要高并发地访问数据。例如,大型 FTP 或 HTTP 服务器可以维护数千个并发会话,并通过多个 100 Mbit/s 的连接与外部世界通信,这远超大多数硬盘的持续传输速率。

当前的硬盘可以以最高 70 MB/s 的速度顺序传输数据,但在许多独立进程访问同一硬盘的环境下,这个速度并不重要,而且它们可能只能达到这些数值的一小部分。在这种情况下,更有意义的是从磁盘子系统的角度来看问题。关键的参数是传输给子系统带来的负载,或者传输占用参与传输的硬盘的时间。

在任何磁盘传输中,硬盘必须先定位磁头,等待第一个扇区通过读写磁头,然后执行传输。这些动作可以视为原子操作,因为中途打断它们是没有意义的。

假设一次典型的传输大约是 10 kB:当前一代高性能硬盘平均定位磁头的时间是 3.5 毫秒。最快的硬盘转速为每分钟 15,000 转,因此平均旋转延迟(半圈)是 2 毫秒。在 70 MB/s 的传输速率下,传输本身大约需要 150 微秒,这几乎可以忽略不计,相比之下,定位时间占据了绝大部分时间。在这种情况下,实际的传输速率降到了每秒 1 MB 以上,并且显然高度依赖于传输的大小。

传统且明显的解决方法是“更多的盘片”:与其使用一块大硬盘,不如使用多个较小的硬盘,总存储空间相同。每个硬盘都能独立进行定位和传输,因此有效吞吐量将接近所用硬盘的数量。

然而,实际的吞吐量提高幅度小于所用硬盘的数量。尽管每个硬盘都能够并行传输,但无法确保请求能均匀分布在各个硬盘上。不可避免地,某些硬盘的负载将高于其他硬盘。

图 1. 连接组织

另一种映射方法是将地址空间划分为更小的、大小相等的单元,并将它们顺序存储在不同的设备上。例如,前 256 个扇区存储在第一块硬盘上,接下来的 256 个扇区存储在第二块硬盘上,以此类推。直到最后一块硬盘填满,过程重复进行。这个映射方法称为 条带化(striping) 或 RAID-0。

图 2. 条带化组织

3. 数据完整性

硬盘的最终问题是它们不可靠。尽管近年来硬盘的可靠性大大提高,但硬盘仍然是服务器中最容易发生故障的核心组件。待发生故障,后果可能是灾难性的,更换故障的硬盘并恢复数据可能导致服务器停机。

解决这个问题的一种方法是 镜像(mirroring),即 RAID-1,它将数据保存在不同的物理硬件上各有一份副本。对卷的任何写入操作都会同时写入两个硬盘;读取操作可以从任何一个硬盘上获取,因此如果一个硬盘发生故障,数据仍然可以从另一个硬盘上获得。

镜像有两个问题:

  • 它需要比非冗余解决方案多一倍的磁盘存储空间。

  • 写入操作必须同时写入两个硬盘,因此它们占用了比非镜像卷多一倍的带宽。读取操作不受性能影响,甚至可能更快。

另一种解决方案是 奇偶校验(parity),它在 RAID 2、3、4 和 5 等级中得到了实现。其中,RAID-5 是最有趣的。在 vinum 中,它是条带化组织的一种变体,其中每个条带中的一个块用于存储其他块的奇偶校验块。vinum 中实现的 RAID-5 结构类似于条带化结构,只不过它通过在每个条带中包含一个奇偶校验块来实现 RAID-5。根据 RAID-5 的要求,这个奇偶校验块的位置从一个条带到下一个条带不断变化。数据块中的数字表示相对块号。

图 3. RAID-5 组织结构

与镜像相比,RAID-5 的优势在于它显著减少了存储空间的需求。读取访问与条带化组织类似,但写入访问明显更慢,约为读取性能的 25%。如果一个硬盘发生故障,阵列仍然可以在降级模式下运行,剩余可访问硬盘中的读取操作仍然正常进行,而故障硬盘的读取则会通过从其他剩余硬盘的相应块重新计算得到。

4. vinum 对象

为了解决这些问题,vinum 实现了一个四级层次结构的对象体系:

  • 最显著的对象是虚拟磁盘,称为 卷(volume)。卷本质上与 UNIX® 磁盘驱动器具有相同的属性,尽管有一些小的区别。例如,卷没有大小限制。

  • 卷由 plex 组成,每个 plex 表示一个卷的整个地址空间。这个层次结构提供了冗余。可以把 plex 看作是镜像阵列中的单个硬盘,每个硬盘都包含相同的数据。

  • 由于 vinum 存在于 UNIX® 磁盘存储框架中,因此可以使用 UNIX® 分区作为构建多磁盘 plex 的基础块。事实上,这样做太不灵活,因为 UNIX® 磁盘只能有有限数量的分区。因此,vinum 将单个 UNIX® 分区(称为 驱动器)划分为连续区域,这些区域称为 子磁盘(subdisk),并将它们作为构建 plex 的基础块。

  • 子磁盘位于 vinum 驱动器 上,当前是 UNIX® 分区。vinum 驱动器可以包含任意数量的子磁盘。除了驱动器开头的一个小区域(用于存储配置和状态信息)外,整个驱动器都可以用于数据存储。

以下部分介绍了这些对象如何提供 vinum 所需的功能。

4.1. 卷大小考虑

Plex 可以包含多个子磁盘,这些子磁盘分布在 vinum 配置中的所有驱动器上。因此,单个驱动器的大小不会限制 plex 或卷的大小。

4.2. 冗余数据存储

vinum 通过将多个 plex 附加到一个卷来实现镜像。每个 plex 表示卷中的数据。一个卷可以包含从一个到八个 plex。

虽然一个 plex 代表一个卷的完整数据,但某些表示部分可能会物理缺失,可能是有意的(通过不为 plex 的某些部分定义子磁盘)或偶然的(由于驱动器故障)。只要至少一个 plex 能提供卷完整地址范围的数据,卷就能正常工作。

4.3. 选择哪种 Plex 组织方式?

vinum 在 plex 层次上实现了连接和条带化:

  • 连接 plex 使用每个子磁盘的地址空间。连接的 plex 是最灵活的,因为它们可以包含任意数量的子磁盘,且子磁盘的长度可以不同。通过添加额外的子磁盘,可以扩展 plex。与条带化 plex 相比,它们所需的 CPU 时间较少,尽管 CPU 开销的差异不可测量。另一方面,连接的 plex 更容易出现热点,其中一个硬盘非常活跃,而其他硬盘处于空闲状态。

  • 条带化 plex 将数据条带化存储在每个子磁盘上。所有子磁盘必须具有相同的大小,且必须至少有两个子磁盘,以将其与连接 plex 区分开来。条带化 plex 的最大优势是它们减少了热点问题。通过选择最佳大小的条带(大约 256 kB),可以平衡各个组件驱动器的负载。通过添加新的子磁盘扩展 plex 是非常复杂的,vinum 没有实现这种扩展。

表 1. vinum Plex 组织方式

5. 一些示例

5.1. 配置文件

配置文件介绍了单个 vinum 对象。一个简单卷的定义可能如下所示:

这个文件介绍了四个 vinum 对象:

  • drive 行介绍了一个磁盘分区(驱动器)及其相对于底层硬件的位置。它被赋予符号名称 a。这种将符号名称与设备名称分开的方法允许磁盘在不同位置之间移动而不会产生混淆。

  • volume 行介绍了一个卷。唯一需要的属性是名称,这里是 myvol。

  • plex 行定义了一个 plex。唯一必需的参数是组织方式,这里是 concat。不需要名称,因为系统会通过添加后缀 .px 自动生成名称,其中 x 是该卷中 plex 的编号。因此,这个 plex 将被命名为 myvol.p0。

  • sd 行介绍了一个子磁盘。最小规格是存储子磁盘的驱动器名称和子磁盘的长度。无需名称,因为系统会通过添加后缀 .sx 自动分配名称,其中 x 是该 plex 中子磁盘的编号。因此,vinum 将此子磁盘命名为 myvol.p0.s0。

图 4. 简单的 vinum 卷

该图以及随后出现的图形表示了一个卷,其中包含 plex,而 plex 又包含子磁盘。在这个例子中,卷包含一个 plex,而该 plex 包含一个子磁盘。

这个特定的卷与常规磁盘分区没有特别的优势。它包含一个单一的 plex,因此没有冗余。该 plex 包含一个子磁盘,因此与常规磁盘分区在存储分配上没有区别。接下来的部分将展示更有趣的配置方法。

5.2. 增加弹性:镜像

可以通过镜像来增加卷的弹性。在布置一个镜像卷时,重要的是要确保每个 plex 的子磁盘位于不同的驱动器上,这样才能避免驱动器故障同时影响两个 plex。以下配置展示了一个镜像卷:

在这个例子中,无需再次定义驱动器 a,因为 vinum 会在其配置数据库中跟踪所有对象。处理完这个定义后,配置变成了:

图 5. 一个镜像的 vinum 卷

在这个例子中,每个 plex 包含完整的 512 MB 地址空间。如同之前的例子,每个 plex 只包含一个子磁盘。

5.3. 优化性能

前面例子中的镜像卷比未镜像的卷更能抵抗故障,但其性能较差,因为每次对卷的写入都需要同时写入到两个驱动器,占用了更大比例的磁盘带宽。性能考量需要另一种方法:不使用镜像,而是将数据条带化分布到尽可能多的磁盘驱动器上。以下配置展示了一个条带化卷,plex 被条带化到四个磁盘驱动器上:

如之前一样,无需定义已经为 vinum 所知的驱动器。处理完这个定义后,配置变成了:

图 6. 条带化的 vinum 卷

5.4. 韧性与性能

借助足够的硬件,可以构建出相较于标准 UNIX® 分区具有更高韧性和性能的卷。一个典型的配置文件可能如下所示:

第二个 plex 的子磁盘与第一个 plex 的子磁盘错开了两个驱动器。这有助于确保写入操作不会写入到相同的子磁盘,即使传输跨越了两个驱动器。

图 7. 一个镜像条带化的 vinum 卷

6. 对象命名

vinum 为 plex 和子磁盘分配默认名称,尽管可以覆盖这些名称。我们不建议覆盖默认名称,因为这样做不会带来显著的优势,且可能导致混淆。

名称可以包含任何非空白字符,但建议仅限字母、数字和下划线字符。卷、plex 和子磁盘的名称最长可达 64 个字符,驱动器的名称最长可达 32 个字符。

vinum 对象在 /dev/gvinum 层次结构中分配设备节点。上面的配置将导致 vinum 创建以下设备节点:

  • 每个卷的设备条目。这些是 vinum 使用的主要设备。上述配置会包括设备 /dev/gvinum/myvol、/dev/gvinum/mirror、/dev/gvinum/striped、/dev/gvinum/raid5 和 /dev/gvinum/raid10。

  • 所有卷都会在 /dev/gvinum/ 下获得直接条目。

  • 目录 /dev/gvinum/plex 和 /dev/gvinum/sd,分别包含每个 plex 和每个子磁盘的设备节点。

例如,考虑以下配置文件:

尽管建议不要为 plex 和子磁盘分配特定名称,但 vinum 驱动器必须命名。这使得在将驱动器移动到不同位置时,仍然可以自动识别它。驱动器名称最长可达 32 个字符。

6.1. 创建文件系统

7. 配置 vinum

7.1. 启动

vinum 将配置信息存储在磁盘切片中,形式与配置文件基本相同。当从配置数据库读取时,vinum 会识别一些在配置文件中不允许使用的关键字。例如,磁盘配置可能包含以下文本:

显而易见的区别是位置和命名信息的存在,这些信息是允许但不推荐的,以及状态信息。vinum 不存储关于驱动器的任何信息,它通过扫描配置的磁盘驱动器来查找具有 vinum 标签的分区。这使得 vinum 能够正确识别驱动器,即使它们被分配了不同的 UNIX® 驱动器 ID。

7.1.1. 自动启动

当 vinum 使用 gvinum start 启动时,vinum 会从某个 vinum 驱动器读取配置数据库。在正常情况下,每个驱动器包含配置数据库的副本,因此读取哪个驱动器并不重要。然而,在崩溃之后,vinum 必须确定哪个驱动器是最新更新的,并从该驱动器读取配置。然后,它会根据需要从逐渐较旧的驱动器更新配置。

8. 使用 vinum 作为根文件系统

对于具有完全镜像文件系统的机器,使用 vinum 作为根文件系统也是理想的。设置这种配置比镜像一个任意文件系统要复杂一些,因为:

  • 根文件系统必须在引导过程中很早就可用,因此 vinum 基础设施必须在此时已经可用。

  • 包含根文件系统的卷还包含系统引导程序和内核。这些必须使用主机系统的本地工具(如 BIOS)读取,而这些工具通常无法了解 vinum 的细节。

在以下章节中,术语“根卷”通常用来描述包含根文件系统的 vinum 卷。

8.1. 让 vinum 在启动时足够早地加载以支持根文件系统

8.2. 使基于 vinum 的根卷对引导程序可访问

当前的 FreeBSD 引导程序只有 7.5 KB 的代码,并且不能理解 vinum 的内部结构。这意味着它无法解析 vinum 配置数据,也无法识别启动卷的元素。因此,需要一些解决方法来为引导程序提供一个标准 a 分区的假象,该分区包含根文件系统。

为了使这种情况成为可能,根卷必须满足以下要求:

  • 根卷不能是条带化(stripe)或 RAID-5 类型。

  • 根卷的每个 plex 中不能包含超过一个连接子磁盘(concatenated subdisk)。

需要注意的是,使用多个 plex 每个包含一个根文件系统的副本是可行的,并且是期望的。引导程序过程将只使用一个副本来查找引导程序和所有启动文件,直到内核挂载根文件系统为止。这些 plex 中的每个单独的子磁盘需要其自己的 a 分区假象,以便相应的设备可以引导系统。严格来说,这些伪造的 a 分区不必位于每个设备中的相同偏移量,但为了避免混淆,最好按这种方式创建 vinum 卷,使结果镜像设备是对称的。

为了为每个包含根卷部分的设备设置这些 a 分区,需要执行以下步骤:

  1. 使用以下命令检查该设备的根卷子磁盘的位置、偏移量以及大小:

    vinum 的偏移量和大小是以字节为单位的。必须将这些值除以 512,以获得要用于 bsdlabel 的块号。

  2. 对参与根卷的每个设备运行以下命令:

    devname 必须是磁盘的名称,例如没有切片表的磁盘的名称 da0,或切片名称,例如 ad0s1。

    如果设备上已经存在一个 a 分区(来自预 vinum 根文件系统),则应将其重命名为其他名称,以便它仍然可访问(以防万一),但默认情况下不再用于引导系统。当前挂载的根文件系统不能被重命名,因此必须在从 "Fixit" 媒体引导时执行此操作,或者在镜像中使用两步过程,首先操作当前未引导的磁盘。

    必须将该设备上的 vinum 分区的偏移量(如果有)与根卷子磁盘的偏移量相加。所得的值将成为新 a 分区的 offset 值。这个分区的 size 值可以直接从上面的计算中获得。fstype 应该是 4.2BSD。fsize、bsize 和 cpg 的值应选择与实际文件系统相匹配,尽管在这个上下文中,它们并不是特别重要。

    这样,就会建立一个新的 a 分区,它将与该设备上的 vinum 分区重叠。只有当 vinum 分区已正确标记为 vinum 文件系统类型时,bsdlabel 才会允许这种重叠。

  3. 现在,所有包含根卷副本的设备上都有一个伪造的 a 分区。强烈建议使用以下命令验证结果:

请记住,所有包含控制信息的文件都必须相对于根文件系统,这个根文件系统在 vinum 卷中,在设置新的 vinum 根卷时,可能与当前活动的根文件系统不匹配。因此,必须特别小心处理 /etc/fstab 和 /boot/loader.conf 文件。

在下次重启时,引导程序应该能够从新的基于 vinum 的根文件系统获取相应的控制信息,并相应地进行操作。内核初始化过程结束时,在所有设备都已声明之后,显示的成功消息应该类似于:

8.3. 基于 vinum 的根文件系统示例

在设置好 vinum 根卷之后,运行 gvinum l -rv root 的输出可能如下所示:

需要注意的值是 135680,即相对于分区 /dev/da0h 的偏移量。这在 bsdlabel 中等同于 265 个 512 字节的磁盘块。类似地,根卷的大小是 245760 个 512 字节的块。包含第二个副本的 /dev/da1h 设备有对称的设置。

这些设备的 bsdlabel 可能如下所示:

在上面的示例中,整个设备专门用于 vinum,并且没有剩余的预 vinum 根分区。

8.4. 故障排除

以下是一些已知的常见问题和解决方法。

8.4.1. 系统引导加载,但系统无法启动

如果由于某种原因系统无法继续启动,可以通过在 10 秒警告时按下空格键来中断引导程序。可以通过输入 show 来检查引导程序变量 vinum.autostart,并使用 set 或 unset 进行修改。

如果 vinum 内核模块尚未列在自动加载的模块列表中,可以输入 load geom_vinum 来加载它。

加载完成后,可以输入 boot -as 继续启动过程,-as 参数请求内核询问要挂载的根文件系统(-a),并使启动过程在单用户模式下停止(-s),此时根文件系统以只读方式挂载。这样,即使只有一个 plex 的多 plex 卷被挂载,也不会导致 plex 之间的数据不一致。

在提示输入根文件系统时,可以输入任何包含有效根文件系统的设备。如果 /etc/fstab 配置正确,默认值应该类似于 ufs:/dev/gvinum/root。一个典型的替代选择可能是 ufs:da0d,它可能是包含预 vinum 根文件系统的假设分区。如果此处输入的是某个别名 a 分区,必须确保它实际引用的是 vinum 根设备的子磁盘,因为在镜像设置中,这只会挂载一个镜像根设备的一部分。如果此文件系统以后要以读写方式挂载,则需要移除其他 plex,因为这些 plex 会携带不一致的数据。

8.4.2. 只加载了主引导程序

8.4.3. 什么也没启动,引导程序崩溃

如果引导程序在 vinum 安装后被破坏,就会发生这种情况。不幸的是,vinum 会在其分区的开始处意外地留下 4 KB 的空闲空间,然后才开始写入 vinum 头信息。然而,第一阶段和第二阶段的引导程序以及 bsdlabel 需要 8 KB。因此,如果 vinum 分区从 0 偏移量开始,且该分区位于一个本应可启动的切片或磁盘中,vinum 设置将破坏引导程序。

同样,如果通过从 "Fixit" 媒体启动并使用 bsdlabel -B 重新安装引导程序来恢复上述情况,重新安装的引导程序会破坏 vinum 头,导致 vinum 无法找到其磁盘。尽管实际的 vinum 配置数据和 vinum 卷中的数据不会被破坏,并且可以通过重新输入相同的 vinum 配置数据来恢复所有数据,但这种情况很难修复。需要将整个 vinum 分区移动至少 4 KB,以避免 vinum 头和系统引导程序的冲突。

IPsec VPN

IPsec 包含以下子协议:

  • Encapsulated Security Payload (ESP):该协议通过使用对称加密算法(如 Blowfish 和 3DES)加密内容,保护 IP 包数据不受第三方干扰。

  • Authentication Header (AH):该协议通过计算加密校验和并使用安全哈希函数对 IP 包头字段进行哈希,保护 IP 包头免受第三方干扰和伪造。接着,附加一个包含哈希值的头部,允许对包中的信息进行认证。

  • IP Payload Compression Protocol (IPComp):该协议通过压缩 IP 负载以减少传输的数据量,尝试提高通信性能。

这些协议可以一起使用,也可以单独使用,具体取决于环境。

本文演示了如何在家庭网络和公司网络之间设置 IPsecVPN。

在示例场景中:

  • 两个站点都通过运行 FreeBSD 的网关连接到互联网。

  • 每个网络的网关至少有一个外部 IP 地址。在此示例中,公司 LAN 的外部 IP 地址是 172.16.5.4,家庭 LAN 的外部 IP 地址是 192.168.1.12。

  • 两个网络的内部地址可以是公共或私有 IP 地址。然而,地址空间不得重叠。在此示例中,公司 LAN 的内部 IP 地址是 10.246.38.1,家庭 LAN 的内部 IP 地址是 10.0.0.5。

1. 在 FreeBSD 上配置 VPN

使用 ifconfig gif0 验证每个网关上的设置。以下是家庭网关的输出:

以下是公司网关的输出:

正如预期的那样,双方能够从私有配置的地址发送和接收 ICMP 包。接下来,必须告诉两个网关如何路由数据包,以便正确地从每个网关后面的网络发送流量。以下命令将实现此目标:

此时,流量已经在通过 gif 隧道封装的两个网络之间流动,但没有任何加密。接下来,使用 IPSec 通过预共享密钥(PSK)加密流量。除了 IP 地址之外,/usr/local/etc/racoon/racoon.conf 在两个网关上将是相同的,并且内容类似于:

有关每个可用选项的描述,请参考 racoon.conf 的手册页。

需要配置安全策略数据库(SPD),以便 FreeBSD 和 racoon 能够加密和解密主机之间的网络流量。

可以通过类似以下的 shell 脚本在公司网关上实现这一点。该文件将在系统初始化期间使用,并应保存为 /usr/local/etc/racoon/setkey.conf。

文件配置好后,可以通过以下命令在两个网关上启动 racoon:

输出应类似于以下内容:

控制台应显示类似以下的数据。如果没有,说明存在问题,需要调试返回的数据。

注意

规则号可能需要根据当前主机配置进行修改。

最后,为了在系统初始化期间启用 VPN 支持,可以将以下行添加到 /etc/rc.conf:

原文:

本文旨在帮助读者获得足够的 LDAP 知识,从而能够配置一台 LDAP 服务器。本文还将尝试解释如何使用 与 配置客户端服务,使其与 LDAP 服务器协同工作。

LDAP 是“轻量目录访问协议”(Lightweight Directory Access Protocol)的缩写,是 X.500 目录访问协议的子集。其最新规范见于 及相关文档。它本质上是数据库,其主要用途是读取而非写入。

本文所使用的 LDAP 服务器为 。尽管本文的原理部分适用于多种服务器,但具体的管理操作是特定于 OpenLDAP 的。Ports 中提供多个版本的服务器,例如 。客户端服务器需要安装对应的 库。

这将创建一个可用于 slapd.conf 中配置项的自签名证书,其中 cert.crt 与 cacert.crt 可为同一文件。如果你计划使用多个 OpenLDAP 服务器(例如通过 slurpd 进行复制),请参阅 以生成 CA 密钥,并使用该密钥为每台服务器签发证书。

为 OpenLDAP 库安装 Port。客户端始终会使用 OpenLDAP 库,因为目前 与 只支持 OpenLDAP。

此时,你应可在客户端运行 ldapsearch -Z;其中 -Z 表示“使用 TLS”。若遇到错误,说明配置存在问题,最常见的是证书问题。可使用 的 s_client 与 s_server 工具确认证书是否配置正确并已签名。

你可以使用 slapadd 或 ldapadd 将这些条目所在的文件导入数据库。你也可以使用 工具。

客户端应该已经从 中安装了 OpenLDAP 库,但如果你要配置多台客户端机器,那么每台机器都需要安装 。

FreeBSD 需要安装两个 Port 才能对 LDAP 服务器进行认证: 和 。

通过 /usr/local/etc/ldap.conf 文件进行配置。

因此,我们需要将原先配置文件 openldap/ldap.conf 中的所有参数复制到新的 ldap.conf 中。完成此操作后,我们需要告知 要在目录服务器上查找哪些内容。

设置之后, 将在 base 所指定的整个 LDAP 目录中搜索 uid=username 这一值。如果只找到一个匹配条目,它将尝试以该用户绑定,并使用提供的密码进行验证。若绑定成功,则允许访问;否则验证失败。

该行在文件中的具体位置及第四列中所使用的选项决定了认证机制的具体行为;参见 。

Port 提供了这个功能。它使用与 相同的配置文件,安装后通常不需要额外的参数。接下来我们只需编辑 /etc/nsswitch.conf,以启用目录服务支持。只需将如下几行:

不幸的是,截至本文写作时,FreeBSD 尚不支持通过 命令更改用户密码。因此,多数管理员需要自己实现解决方案。下面提供了一些示例。注意:如果你打算自行编写密码更改脚本,有一些安全问题需要注意;请参阅 。

是一个 PAM 模块,它总是会“成功”;它的作用是为没有 home 目录的用户自动创建 home 目录。如果你拥有几十台客户端服务器和上百名用户,使用它并设置 skeleton 目录将远比为每个用户手动准备 home 目录来得容易。

是一个非常优秀的工具,它允许你用类 LDIF 的语法编辑 LDAP 条目。目录(或其中某一子集)会以你通过 EDITOR 环境变量指定的编辑器打开。这让你无需编写自定义工具,也能轻松批量修改目录内容。

支持通过 LDAP 服务器验证 SSH 密钥。如果你管理着许多服务器,并希望避免将公钥复制到所有机器上,这个功能非常有用。

以下操作步骤将直接展示过程,不会详细解释其原理——你可以参考 及相关文档以获得进一步解释。

原文:

本文已过时,无法准确概述 FreeBSD 发布工程团队当前的发布流程。本文仅为历史性文档保存。FreeBSD 发布工程团队当前使用的流程可以参阅文章 。

FreeBSD 的开发过程非常开放。FreeBSD 由来自全球成千上万人的贡献组成。FreeBSD 项目提供 Subversion ^[]^ 访问权限,允许公众访问日志信息、开发分支之间的差异(补丁)以及其他源代码管理提供的生产力提升。这对于吸引更多有才华的开发者加入 FreeBSD 项目起到了重要作用。然而,大家都同意,如果将写访问权限开放给互联网上的每个人,混乱很快就会显现出来。因此,只有大约 300 名开发者被赋予了 Subversion 仓库的写访问权限。这些 ^[]^ 通常是进行 FreeBSD 开发工作的主要人员。一个由选举产生的 ^[]^ 为项目提供一定程度的方向。

在版本发布之间,FreeBSD 项目的构建机器每周会自动生成快照,并通过 https:/download.FreeBSD.org/snapshots/ 提供下载。二进制发布快照的广泛可用性,以及我们的用户社区倾向于通过 Subversion 和 “make buildworld” ^[]^ 跟进 -STABLE 开发,有助于保持 FreeBSD-STABLE 在质量保证活动开始之前始终处于可靠的状态。

除了安装 ISO 快照外,每周还提供虚拟机镜像文件,供 VirtualBox、qemu 或其他流行的仿真软件使用。虚拟机镜像可以从 下载。

虚拟机镜像大约 150MB,通过 压缩,附加到虚拟机时包含一个 10GB 的稀疏文件系统。

在整个发布周期中,用户会不断提交错误报告和功能请求。这些问题报告通过在 提供的 Web 界面录入到 Bugzilla 数据库中。

发布工程流程的不同阶段,直至实际系统构建。

实际的构建过程。

如何由第三方扩展基本发布。

通过发布 FreeBSD 4.4 学到的一些经验教训。

开发的未来方向。

FreeBSD 在 Subversion 中的分支布局可参阅 。创建分支的第一步是确定要从哪个 stable/X 源代码修订版创建分支。

创建 releng 分支和 release 标签由 完成。

FreeBSD 开发分支
FreeBSD 3.x STABLE 分支
FreeBSD 4.x STABLE 分支
FreeBSD 5.x STABLE 分支
FreeBSD 6.x STABLE 分支
FreeBSD 7.x STABLE 分支
FreeBSD 8.x STABLE 分支
FreeBSD 9.x STABLE 分支

应更新 Sysinstall,以标注可用 Port 的数量和 Ports 所需的磁盘空间。^[^] 这些信息目前保存在 src/usr.sbin/bsdinstall/dist.c 中。

任何具有快速计算机和访问源代码库权限的人都可以构建 FreeBSD "发布"。(这应该是所有人,因为我们提供 Subversion 访问!详情请参见《》)。唯一的特殊要求是必须提供 设备。如果设备没有加载到内核中,那么在创建启动媒体阶段执行 时,内核模块应自动加载。构建发布所需的所有工具都可以从 Subversion 仓库中的 src/release 获取。这些工具旨在提供一种一致的方法来构建 FreeBSD 发布。实际上,可以仅通过一条命令来构建完整的发布,包括创建适合刻录到 CDROM 或 DVD 的 ISO 镜像,以及一个 FTP 安装目录。 完整文档介绍了用于构建发布的 src/release/generate-release.sh 脚本。generate-release.sh 是 make release 的包装器。

文档详细说明了构建 FreeBSD 发布所需的确切命令。以下命令序列可以构建 9.2.0 版本的发布:

有关发布构建基础设施的更多信息,请参见 。

是一个包含 36000 余款第三方软件包的集合,适用于 FreeBSD。Ports Management Team portmgr@FreeBSD.org 负责维护一个一致的 Ports 树,用于创建伴随官方 FreeBSD 发布的二进制包。

从 FreeBSD 4.4 开始,FreeBSD 项目决定发布之前在 BSDi/Wind River Systems/FreeBSD Mall "官方" CDROM 发行版中提供的所有四个 ISO 镜像。每个光盘必须包含一个 README.TXT 文件,解释光盘的内容,一个 CDROM.INF 文件,提供光盘的元数据,以便 验证并使用光盘内容,以及一个 filename.txt 文件,提供光盘的清单。这个 清单 可以通过以下简单命令创建:

如果要包含自定义的 FreeBSD 内核,则必须更新 和 ,以包括安装说明。相关代码位于 src/release 和 src/usr.sbin/bsdinstall 中。具体来说,需要更新 src/release/Makefile 和 dist.c、dist.h、menus.c、install.c、以及 Makefile 等文件。此外,可以选择更新 bsdinstall.8。

第二张光盘也大部分由 make release 创建。此光盘包含一个 "实时文件系统",可以从 用于故障排除 FreeBSD 安装。此光盘应为可启动,并且还应包含 CVSROOT 目录中的压缩版 CVS 仓库以及 commerce 目录中的商业软件演示。

有关 FreeBSD FTP 站点分发镜像架构的更多信息,请参阅 一文。

在更新 ftp-master 后,大多数 Tier-1 FTP 站点完成同步可能需要数小时至两天,具体取决于是否同时加载了软件包集。发布工程师必须在对外宣布 FTP 站点上新软件正式可用之前,与 进行协调。理想情况下,发布所需的软件包集合应至少在正式发布日期前四天上传。发行文件应在计划发布时间前 24 至 48 小时上传,且权限设置为禁止公众访问,以便镜像站点可以下载但公众暂时无法获取。当文件上传完成时,应发送邮件给 ,说明发布文件已就绪,并告知何时允许镜像站点对外开放访问。请务必提供带有时区的时间,例如相对于 GMT 的时间。

FreeBSD 的系统安装与配置工具 可以通过编写脚本来实现自动化安装,适用于大规模部署场景。此功能可与 Intel® PXE 配合使用,从网络引导系统启动。

FreeBSD 4.4 的发布工程正式始于 2001 年 8 月 1 日。从该日起,所有提交至 FreeBSD RELENG_4 分支的更改都必须经过发布工程团队 明确批准。x86 架构的首个候选版本于 8 月 16 日发布,之后又发布了 4 个候选版本,最终版本于 9 月 18 日发布。在最后一周,安全官员密切参与了工作,因为在先前的候选版本中发现了若干安全问题。在短短一个多月的时间内,共向发布工程团队 发送了 500 余封电子邮件。

并行化 —— 发布构建过程中的某些部分实际上是“高度可并行”的。大多数任务对 I/O 要求较高,因此使用多块高速硬盘比使用多核处理器更能有效加快 make release 过程。如果在 环境中为不同的目录层级使用不同的硬盘,那么在一块硬盘上运行 make world 的同时,可以在其他硬盘上并发进行 Ports 和 doc 树的 CVS 检出。使用 RAID 方案(无论是硬件还是软件)都能显著缩短整体构建时间。

我要感谢 Jordan Hubbard 给我机会承担 FreeBSD 4.4 发布工程中的部分职责,并感谢他多年来为 FreeBSD 发展所做的一切。当然,没有以下几位在发布工作中所做的贡献,本次发布是不可能完成的:Satoshi Asami 、Steve Price 、Bruce A. Mah 、Nik Clayton 、David O’Brien 、Kris Kennaway 、John Baldwin ,以及整个 FreeBSD 开发社区。我还要感谢 Rodney W. Grimes 、Poul-Henning Kamp 及其他在 FreeBSD 初期为发布工程工具做出贡献的人们。本文也受到了 、NetBSD 项目、以及 John Baldwin 提出的发布工程流程笔记 的影响。

原文:

有关日志记录的更多信息,请阅读 手册页面。

disklabel1
disklabel2

最后,编辑 /boot/loader.conf 文件并添加以下行,以便每次启动时加载 模块:

这通常意味着 使用日志提供者中的信息将文件系统恢复到一致状态。

根据 重新构建并重新安装你的内核。

日志可能会在有机会提交(刷新)到磁盘之前就已填满。请记住,日志的大小取决于使用负载,而不是数据提供者的大小。如果你的磁盘活动较高,你需要为日志分配更大的分区。请参阅 部分中的说明。

。

,由 的开发者 Paweł Jakub Dawidek 提供。

,由 Ivan Voras 提供。

和 的手册页。

原文链接:

NanoBSD 是由 Poul-Henning Kamp [] 开发的工具,目前由 Warner Losh [] 维护。它创建一个用于嵌入式应用的 FreeBSD 系统映像,适用于 USB 密钥、存储卡或其他大容量存储介质。

运行时一切为只读 - 即使在系统非正常关机后,拔掉电源也安全,不需要运行 。

/etc 和 /var 目录是 (malloc)磁盘。

更多详细信息,请参阅 的步骤。

NanoBSD 映像是通过一个简单的 nanobsd.sh shell 脚本构建的,该脚本位于 /usr/src/tools/tools/nanobsd 目录中。该脚本可创建映像,可以使用 工具将其复制到存储介质中。

-i:完全不构建磁盘映像。由于文件不会被创建,因此无法使用 将其复制到存储介质。

还有一些变量可以改变 NanoBSD 映像的启动方式。两个选项会传递给 来初始化磁盘映像的启动扇区:

通过 NANO_BOOTLOADER 可以选择一个引导加载程序文件。最常见的选项是 boot0sio 和 boot0,具体选择取决于设备是否有串口。最好避免提供一个不同的引导加载程序,但这是可能的。如果要这样做,最好查看 中关于启动过程的章节。

通过 NANO_BOOT0CFG,可以调整启动过程,比如选择 NanoBSD 映像实际从哪个分区启动。修改此变量的默认值之前,最好查看 页面。一个可能有用的更改是启动过程的超时设置。为此,可以将 NANO_BOOT0CFG 变量更改为 "-o packet -s 1 -m 3 -t 36"。这样启动过程大约在 2 秒后开始,因为等待 10 秒钟再启动是比较少见的需求。

/etc、/var 和 /tmp 目录在启动时作为 (malloc)磁盘分配;因此,它们的大小可以根据设备需求进行调整。NANO_RAM_ETCSIZE 变量设置 /etc 的大小;而 NANO_RAM_TMPVARSIZE 变量设置 /var 和 /tmp 目录的大小,因为 /tmp 被符号链接到 /var/tmp。默认情况下,这两个 malloc 磁盘的大小分别为 20MB。它们可以随时更改,但通常 /etc 的大小不会增长太多,因此 20MB 是一个不错的起点,而 /var 和特别是 /tmp 可能会变得更大,因此在使用时需要小心。对于内存受限的系统,可以选择较小的文件系统大小。

由于 NanoBSD 主要设计用于构建嵌入式设备的系统映像,假设使用的存储媒体将相对较小。因此,所布置的文件系统配置为具有较小的块大小(4KB)和较小的碎片大小(512B)。可以通过 NANO_NEWFS 变量修改文件系统的配置选项,但语法必须遵循 命令格式。此外,默认情况下,文件系统启用了软更新。可以查看 了解更多相关信息。

cust_comconsole - 禁用 在 VGA 设备(/dev/ttyv* 设备节点)上的使用,并启用 COM1 串口作为系统控制台。

cust_allow_ssh_root - 允许 root 用户通过 登录。

所有构建和安装编译选项都可以在 手册页中找到,但并不是所有选项都可以或应该在构建 NanoBSD 映像时使用。构建和安装选项应根据要构建的映像的需求进行定义。

2.4.1. 使用

2.4.2. 使用

2.4.3. 使用

如果远程主机没有运行 或 服务,可以尝试以下示例:

原文:

常见的 UNIX® API 定义了系统调用(syscall)作为用户空间进程向内核发出命令的一种方式。最常见的实现方式是通过使用中断或专用指令(例如,ia32 的 SYSENTER/SYSCALL 指令)。系统调用是通过一个编号来定义的。例如,在 FreeBSD 中,系统调用编号 85 是 系统调用,编号 132 是 系统调用。某些系统调用需要参数,这些参数通过多种方式从用户空间传递到内核空间(具体实现依赖于操作系统)。系统调用是同步的。

内核实例首先在系统中被处理(称为 init)。每个运行的进程都可以使用 系统调用创建它的副本。这个系统调用有一些稍微修改过的版本,但其基本语义是相同的。每个运行的进程可以通过调用 系统调用转换为另一个进程。这个系统调用也有一些修改过的版本,但所有版本的基本目的是一样的。进程通过调用 系统调用结束其生命周期。每个进程都有一个唯一的标识号,称为 PID,每个进程都有一个父进程(通过其 PID 来标识)。

FreeBSD 有个抽象概念,称为执行类加载器,它是 系统调用的一个切入点。它使用一个名为 sysentvec 的结构,描述可执行文件的 ABI。该结构包含错误号翻译表、信号翻译表、以及为系统调用提供服务的各种函数(例如堆栈修正、核心转储等)。FreeBSD 内核想要支持的每个 ABI 都必须定义这个结构,因为它会在系统调用处理代码和其他地方被使用。系统入口由陷阱处理程序处理,我们可以同时访问内核空间和用户空间。

当一个进程发出中断 0x80 时,将调用 int0x80 系统调用陷阱处理程序(在 sys/i386/i386/exception.s 中定义),该处理程序为调用 C 函数 (在 sys/i386/i386/trap.c 中定义)做准备,处理传入的陷阱帧。处理过程包括准备系统调用(取决于 sysvec 条目),确定系统调用是 32 位还是 64 位(这会改变参数的大小),然后将参数复制,包括系统调用。接下来,执行实际的系统调用函数,并处理返回代码(特殊情况包括 ERESTART 和 EJUSTRETURN 错误)。最后,调度一个 userret(),将进程切换回用户空间。实际的系统调用处理程序的参数以 struct thread *td 和 struct syscall args * 参数的形式传递,第二个参数是指向复制的参数结构的指针。

FreeBSD 操作系统遵循传统的 UNIX® 方案,其中每个进程都有一个唯一的标识号,称为 PID(进程 ID)。PID 数字的分配可以是线性分配或随机分配,范围从 0 到 PID_MAX。PID 数字的分配是通过线性搜索 PID 空间完成的。每个进程中的线程都会获得与进程相同的 PID,作为 调用的结果。

从系统调用返回由系统调用 管理,系统调用会检查进程是否有未完成的工作,然后检查是否使用了用户提供的选择器。如果发生这种情况,会应用堆栈修复,最后从堆栈恢复寄存器,进程返回到用户空间。

在 2.6 版本中,Linux® 操作系统重新定义了一些传统的 UNIX® 原语,特别是 PID、TID 和线程。PID 并不被定义为每个进程唯一的标识符,因此对于某些进程(线程), 返回相同的值。进程的唯一标识是通过 TID 提供的。这是因为 NPTL(新 POSIX® 线程库)将线程定义为普通进程(即 1:1 线程模型)。在 Linux® 2.6 中创建新进程是通过 clone 系统调用(fork 的变体)来完成的。这个 clone 系统调用定义了一组影响线程实现的克隆过程行为的标志。其语义有点模糊,因为没有单一的标志可以告诉系统调用创建一个线程。

在 NPTL 中实现 的代码定义了 clone 标志,如下所示:

UNIX® API 也是如此。大多数程序可以在很有限的系统调用集工作下生存。这些系统调用通常是最古老的那些(/、 系列、 处理、、 API),因此它们很容易仿真,因为它们的语义在今天存在的所有 UNIX® 操作系统中是共享的。

如前所述,FreeBSD 支持运行来自其他多个 UNIX® 系统的二进制文件。之所以能实现这一点,是因为 FreeBSD 提供了一个叫做执行类加载器(execution class loader)的抽象机制。该机制与 系统调用结合使用,当 准备执行一个二进制文件时,它会检查该文件的类型。

每个操作系统 ABI 必须在 FreeBSD 内核中注册,包括 FreeBSD 本身的原生操作系统 ABI。因此,当 执行一个二进制文件时,它会遍历已注册的 ABI 列表,当找到匹配的 ABI 时,它会开始使用该 ABI 描述中包含的信息(如系统调用表、errno 转换表等)。因此,每当进程调用系统调用时,它都会使用自己的一套系统调用,而不是使用全局系统调用。这为支持执行各种二进制格式提供了一种非常优雅且简单的方式。

原子操作通过一组函数实现,这些函数以原子的方式对内存操作数进行简单的算术运算,并且对外部事件(如中断、抢占等)保持原子性。原子操作仅能保证对小数据类型的原子性(在 .long. 架构的 C 数据类型量级范围内),因此它们应该在最终级代码中很少直接使用,除非是进行非常简单的操作(例如,在位图中设置标志)。事实上,单纯依赖原子操作编写错误的语义(通常被称为无锁操作)是相当常见的。FreeBSD 内核提供了一种方法,将原子操作与内存屏障结合使用。内存屏障能够保证原子操作按照指定的顺序执行,与其他内存访问之间保持一定的顺序关系。例如,如果我们需要确保原子操作在所有其他待处理写操作(指令重排缓冲区活动)完成之后才发生,就需要明确地在该原子操作之前使用内存屏障。因此,理解内存屏障在更高级别锁构建中的关键作用是很简单的(比如引用计数、互斥锁等)。关于原子操作的详细解释,请参考 。然而,值得注意的是,原子操作(以及内存屏障)理想情况下应仅用于构建前端锁(如互斥锁)。

睡眠锁允许等待的线程被重新调度并进入睡眠,直到锁的持有者释放锁并唤醒一个或多个等待者。由于睡眠锁旨在保护较大的代码路径并处理异步事件,因此它们不会进行任何形式的优先级传播。它们必须通过 接口实现。

自旋互斥锁 - 自旋 -

睡眠互斥锁 - 阻塞 -

池互斥锁 - 阻塞 -

睡眠家族 - 睡眠 - ,pause,tsleep,msleep,msleep,spin,msleep,rw,msleep,sx

条件变量 - 睡眠 -

读写锁 - 阻塞 -

sx 锁 - 睡眠 -

lockmgr - 睡眠 -

信号量 - 睡眠 -

通常,这些应该仅在特定的上下文中使用,即使它们通常可以替代锁,也应该避免使用它们,因为它们不允许使用锁调试工具(如 )来诊断简单的潜在问题。

FreeBSD 内核是为了处理中断线程而使其具有抢占性。实际上,为了避免高中断延迟,时间共享优先级线程可以被中断线程抢占(这样,它们无需等到正常调度路径的预设)。然而,抢占引入了新的竞争点,必须处理这些点。通常,为了应对抢占,最简单的做法是完全禁用抢占。临界区定义了一段代码(由 和 这两个函数边界标定),在此期间保证不会发生抢占(直到受保护的代码完全执行完毕)。这通常可以有效地替代锁,但应小心使用,以免失去抢占带来的整体优势。

例程是路径名查找和转换的中央入口点。它从起始点到终点逐点遍历路径,使用内部的查找功能。 系统调用能够处理符号链接、绝对路径和相对路径。当一个路径通过 查找时,它被输入到名称缓存中。此行为可以被抑制。这个例程在整个内核中被广泛使用,其性能非常关键。

函数尽最大努力遍历 VFS 名称缓存,并为给定(已锁定的)vnode 返回路径。这个过程不可靠,但对于大多数常见情况来说效果很好。不可靠的原因是它依赖于 VFS 缓存(它不会遍历介质结构),它不适用于硬链接等。这一例程在 Linuxulator 中的多个地方使用。

- 锁定 vnode

- 读取由 vnode 引用的目录

- 获取由 vnode 引用的文件或目录的属性

- 查找给定目录的路径

- 打开由 vnode 引用的文件

- 关闭由 vnode 引用的文件

- 减少 vnode 的使用计数并解锁它

- 减少 vnode 的使用计数

- 增加 vnode 的使用计数

如你所见,linux_fork 是在 Linuxulator 中实现的,因此其定义为 STD 类型,并且没有参数,这通过虚拟参数结构来体现。另一方面,close 只是 FreeBSD 的别名,因此没有与 Linux 参数结构相关联,并且在系统入口表中没有添加 linux_ 前缀,因为它调用的是内核中的真实 。

许多 UNIX® 派生系统实现了 系统调用,以允许各种跟踪和调试功能。该功能使得跟踪进程能够获取关于被跟踪进程的各种信息,如寄存器转储、进程地址空间中的任何内存等,并且能够像单步执行指令或在系统调用和陷阱之间进行跟踪一样跟踪进程。 还允许你设置被跟踪进程中的各种信息(如寄存器等)。 是一个 UNIX® 广泛使用的标准,在大多数 UNIX® 系统中都有实现。

在 FreeBSD 中,Linux® 仿真层实现了 功能,代码位于 linux_ptrace.c 文件中。该例程负责在 Linux® 和 FreeBSD 之间转换寄存器,并执行实际的 系统调用仿真。该系统调用是一个长的 switch 块,为每个 命令实现了 FreeBSD 对应的功能。 命令在 Linux® 和 FreeBSD 之间大多是相同的,因此通常只需要做少量修改。例如,Linux® 中的 PT_GETREGS 直接操作数据,而 FreeBSD 使用指向数据的指针,因此在执行(本地) 系统调用后,必须执行 copyout 以保持 Linux® 的语义。

Linuxulator 中的 实现存在一些已知的缺陷。使用 strace(它是 的消费方)时,曾经发生过 panic 错误。同时,PT_SYSCALL 也没有实现。

FreeBSD 中的 Linux® 仿真层支持在运行时设置仿真版本。这是通过 完成的,具体是 compat.linux.osrelease。设置该 会影响仿真层的运行时行为。当设置为 2.6.x 时,它会设置 linux_use_linux26 变量,而设置为其他值则保持未设置状态。这个变量(加上与之相关的每个 prison 变量)决定是否在代码中使用 2.6 基础设施(主要是 PID 混乱)。版本设置是系统范围的,影响所有 Linux® 进程。运行任何 Linux® 二进制文件时不应更改该 ,因为这可能会导致问题。

flags 参数告诉系统调用进程应该如何被克隆。如前所述,Linux® 可以创建共享各种资源的进程,例如两个进程可以共享文件描述符但不共享虚拟内存等。flags 参数的最后一个字节是新创建进程的退出信号。stack 参数如果非 NULL,则指示线程栈的位置;如果为 NULL,则表示我们应该进行写时复制调用进程的栈(即执行普通的 操作)。parent_tidptr 参数用于在进程足够初始化但尚未可运行时,将进程 PID(即线程 ID)拷贝到指定地址。dummy 参数是因为该系统调用在 i386 架构上的调用约定非常奇怪,直接使用寄存器而不让编译器处理,从而需要一个虚拟的系统调用。child_tidptr 参数用于在进程完成 fork 操作并退出时,拷贝 PID。

该系统调用通过设置传入的标志来执行相应的操作。例如,CLONE_VM 对应于 RFMEM(虚拟内存共享)等。唯一的细节是 CLONE_FS 和 CLONE_FILES,因为 FreeBSD 不允许单独设置这些标志,所以我们通过不设置 RFFDG(复制文件描述符表和其他文件系统信息)来伪造这种行为。由于这些标志总是一起设置,所以这不会引起任何问题。设置完标志后,进程通过内部 fork1 例程进行 fork,进程被设置为不可运行,即不会加入到运行队列中。在完成 fork 后,我们可能会将新创建的进程重新父化,以模拟 CLONE_PARENT 语义。接下来是创建仿真数据。Linux® 中的线程不会向其父进程发送信号,因此我们将退出信号设置为 0 以禁用此功能。然后设置 child_set_tid 和 child_clear_tid,以便在后续代码中启用这些功能。此时,我们将 PID 拷贝到 parent_tidptr 指定的地址。进程栈的设置通过简单地重写线程帧中的 %esp 寄存器(在 amd64 上是 %rsp)来完成。接下来是为新创建的进程设置 TLS。之后,可以模拟 的语义,最后将新创建的进程加入运行队列,并通过 clone 返回值将其 PID 拷贝到父进程中。

clone 系统调用实际上也用于模拟传统的 和 系统调用。在 2.6 内核版本中,更新后的 glibc 使用 clone 来实现 和 系统调用。

计算机科学中的线程是进程中的实体,它们可以独立于其他线程进行调度。进程中的线程共享进程范围的数据(如文件描述符等),但每个线程也有自己的栈来存储线程数据。有时需要线程特定的进程范围数据。想象一下,正在执行的线程的名称之类的东西。传统的 UNIX® 线程 API,pthread 提供了一种方法,可以通过 、 和 来创建线程局部数据的键,并使用 或 来操作这些数据。可以很容易地看出,这不是实现此功能的最方便方法。因此,许多 C/C++ 编译器的生产商引入了一种更好的方式。他们定义了一个新的修饰符关键字 thread,用于指定变量是线程特定的。还开发了访问此类变量的新方法(至少在 i386 上)。pthread 方法通常在用户空间实现为一个简单的查找表。这种解决方案的性能不太好。因此,新方法使用(在 i386 上)段寄存器来访问一个存储 TLS 区域的段,这样实际访问线程变量就像附加段寄存器到地址一样,从而通过它进行寻址。段寄存器通常是 %gs 和 %fs,它们充当段选择器。每个线程都有自己存储线程局部数据的区域,并且在每次上下文切换时都必须加载该段。此方法非常快速,几乎在整个 i386 UNIX® 世界中得到了广泛应用。FreeBSD 和 Linux® 都实现了这种方法,并且取得了非常好的效果。唯一的缺点是每次上下文切换时需要重新加载段,这可能会减慢上下文切换的速度。FreeBSD 通过只使用 1 个段描述符来避免这种开销,而 Linux® 使用了 3 个。值得注意的是,几乎没有使用多个描述符的情况(只有 Wine 似乎使用了 2 个),因此 Linux® 在上下文切换时付出了不必要的代价。

当 futex 将线程排队等待时,它会创建一个 working_proc 结构,并将该结构放入 futex 结构中的列表中,然后执行一个 来挂起线程。这个睡眠可以有超时限制。在 返回后(线程被唤醒或超时),working_proc 结构从列表中移除并销毁。所有这些操作都在 futex_sleep 函数中完成。如果线程是通过 futex_wake 被唤醒的,我们会设置 wp_new_futex,因此我们会在它上面睡眠。这样,实际的重新排队是在这个函数中完成的。

Linux® 系列的 *at 系统调用包括:linux_openat、linux_mkdirat、linux_mknodat、linux_fchownat、linux_futimesat、linux_fstatat64、linux_unlinkat、linux_renameat、linux_linkat、linux_symlinkat、linux_readlinkat、linux_fchmodat 和 linux_faccessat。这些调用都通过修改后的 例程和简单的包装层实现。

实现是通过修改 例程来完成的,给其 nameidata 结构添加了 dirfd 参数,这个参数指定路径名查找的起始点,而不是每次都使用当前工作目录。dirfd 的解析过程从文件描述符号到 vnode 的过程是在本地 *at 系统调用中完成的。当 dirfd 是 AT_FDCWD 时,nameidata 结构中的 dvp 项为 NULL,但当 dirfd 是其他数值时,我们会为这个文件描述符获取一个文件,检查它是否有效,如果有 vnode 附加到它,我们就获取这个 vnode,然后检查这个 vnode 是否是一个目录。在实际的 例程中,我们简单地将 dvp 变量替换为 dp,它决定了起始点。namei(9) 不是直接使用的,而是通过不同层级的函数调用。举个例子,openat 的调用过程如下:

我们能够运行一些常用的应用程序,比如 、 以及一些游戏。有些程序在 2.6 仿真下表现不佳,但这个问题目前正在调查中,并且希望很快能修复。唯一一个已知无法工作的主要应用程序是 Linux® Java™ 开发工具包,因为它需要 epoll 功能,而这与 Linux® 2.6 内核并不直接相关。

John Baldwin

Konstantin Belousov

Jung-uk Kim

Alexander Leidinger

Suleiman Souhlal

David Xu `

原文:

首先,假设你已经完成了 。那么,你如何确认它是否存在 ?当然,如果配置错误,你的连接无法工作,而最终正确配置后它会正常工作。 会列出它。但你能否独立确认这一点?

Ueli Maurer 的《随机比特生成器的通用统计测试》() 可以快速测量样本的熵。它使用类似压缩的算法。 是一个变种,用于测量文件的连续(约四分之一兆字节)块。

我们还需要一种捕获原始网络数据的方法。一个名为 的程序可以做到这一点,前提是你已在 中启用了 Berkeley 数据包过滤器 接口。

现在启动 。

在“安全”窗口中,运行 UNIX® 命令 ,该命令将流式输出 y 字符。运行一段时间后停止。在不安全的窗口中,重复该操作。运行一段时间后停止。

然后在捕获的数据包上运行 。你应该会看到类似以下的输出。需要注意的是,安全连接的熵值为期望值的 93%(6.7),而“正常”连接的熵值为期望值的 29%(2.1)。

大多数现代版本的 FreeBSD 在其基础源代码中就已支持 IPsec。因此,你需要在内核配置中包括 IPSEC 选项,并在重新构建并重新安装内核后,使用 命令配置 IPsec 连接。

关于在 FreeBSD 上运行 IPsec 的完整指南,请参阅 。

要使用 捕获网络数据,必须在内核配置文件中包含此项。添加此项后,请确保运行 ,并重新构建和重新安装内核。

你可以在 找到相同的代码。

原文:

镜像系统协调员可通过电子邮件与我们联系:。也可以通过 联系。

可以通过 查看当前 FTP 发行版的磁盘使用情况。

/usr/libexec/ftpd:FreeBSD 自带的 ftpd 可以使用。请务必阅读 。

:一款商业软件,供教育使用免费。

:一款注重安全的 ftpd。

:一款模块化且非常灵活的 ftpd。

:另一款注重安全的 ftpd。

:同上。

:一款“非常安全”的 ftpd。

:Apache 仍然是互联网中最广泛部署的 Web 服务器之一。FreeBSD 项目广泛使用它。

:Boa 是款单任务 HTTP 服务器。与传统 Web 服务器不同,它不会为每个传入连接创建新的进程,也不会为处理多个连接创建多个进程。尽管如此,它对于纯静态内容提供了相当好的性能。

:Cherokee 是一款非常快速、灵活且易于配置的 Web 服务器。它支持当前广泛使用的技术:FastCGI、SCGI、PHP、CGI、SSL/TLS 加密连接、虚拟主机、用户认证、动态编码和负载均衡。它还生成与 Apache 兼容的日志文件。

:lighttpd 是一款安全、快速、符合规范并且非常灵活的 Web 服务器,已针对高性能环境进行了优化。与其他 Web 服务器相比,它具有非常低的内存占用。

:nginx 是一款高性能的边缘 Web 服务器,具有低内存占用和构建现代高效 Web 基础设施所需的关键特性。包括 HTTP 服务器、HTTP 和邮件反向代理、缓存、负载均衡、压缩、请求限速、连接复用和重用、SSL 卸载以及 HTTP 媒体流。

:如果你将提供大量静态内容,你可能会发现使用 thttpd 这样的应用程序比其他服务器更高效。它还针对 FreeBSD 的优秀性能进行了优化。

镜像 FTP 区域的最佳方式是使用 rsync。你可以安装 Port 并使用 rsync 与上游主机进行同步。如 中所提到的,rsync 是一个非常高效的工具。由于 rsync 访问不是必须的,你的首选上游站点可能不允许它。你可能需要稍微搜索一下,找一个允许 rsync 访问的站点。

查阅 rsync 的文档,文档也可以在 找到,了解可以与 rsync 一起使用的各种选项。如果你同步整个模块(不同于子目录),请注意模块目录(此处为 "FreeBSD")不会被创建,因此你不能省略目标目录。此外,你可能希望设置一个脚本框架,通过 来调用此命令。

目前正在研究如何使用 实现网站镜像。

有关构建工具的更多细节,请参见 书籍。

请注意,网站已拆分为 和 docs.FreeBSD.org,并且它们之间有链接;此外,目前 HUGO_baseURL 变量无法涵盖所有链接,因此不建议镜像该网站。

由于带宽、存储和管理要求非常高,FreeBSD 项目决定不再允许公共镜像包。对于有大量机器的站点,运行一个用于 进程的缓存 HTTP 代理可能是有利的。或者,可以通过运行以下命令来获取特定的包及其依赖项:

获取包并生成仓库的元数据后,通过 HTTP 将包提供给客户端机器。有关更多信息,请参阅 的手册页,特别是 页面。

每个镜像站点至少应每天更新一次。为了防止多个运行实例同时发生,脚本中应有锁机制,以便通过 定期执行。由于几乎每个管理员的做法不同,不能提供具体的指令。一个可能的工作流程如下:

使用 将脚本添加到适当用户的 中。此用户应与运行 FTP 守护进程的用户不同,这样,如果 FTP 区域中的文件权限不是全球可读的,匿名 FTP 就无法访问这些文件。此步骤用于“暂存”版本,确保所有官方镜像站在发布日都有必要的发布文件。

此外,还存在一个镜像层次结构,通常称为 层级。主站不常被提及,但可以描述为 Tier-0。从这些主站镜像的镜像站可以被视为 Tier-1,而从 Tier-1 镜像站镜像的镜像站则是 Tier-2,以此类推。官方站点鼓励镜像站点的 层级 较低,但层级越低,对镜像站点的要求也越高,如 所述。低层级的镜像站点访问可能会受到限制,且主站点的访问肯定是受限制的。层级结构不会通过 DNS 反映,通常也没有地方文档化,除非是主站点。然而,具有较低数字(如 1-4)的官方镜像通常是 Tier-1(这只是一个大致的提示,并没有严格的规则)。

如果你没有特别的意图或要求,可以参考 中的说明。这意味着:

通常 中的介绍仍然适用。当然,你可能希望考虑到你的上游镜像站点应处于较低的层级。有关 官方 镜像的一些其他考虑因素,可以参见 部分。

如果你有充分的理由和前提条件,你可能希望并能够访问其中一个主站点。对这些站点的访问通常是受限的,并且有专门的访问政策。如果你已经是一个 官方 镜像,这肯定有助于你获得访问权限。在其他情况下,请确保你的国家确实需要另一个镜像。如果该国已有三个或更多镜像,请首先联系“区域管理员”() 或 。

无论谁帮助你成为 官方 镜像,都应帮助你获得适当的上游主机访问权限,可能是某个主站点或合适的 Tier-1 站点。如果没有,你可以发送电子邮件到 请求帮助。

ftp-master.FreeBSD.org 除了提供 FTP 外,还提供 rsync 访问。有关更多信息,请参考 。

此外,管理员应订阅 。请参见 了解如何订阅。

对于集群管理员,尤其是 Tier-1 集群管理员来说,检查 是 非常 重要的。这很重要,因为它会告诉你下一个 FreeBSD 版本的发布日期,从而为你准备迎接随之而来的流量高峰提供时间。同样重要的是,hub 管理员要尽量保持镜像站点尽可能最新(对于 Tier-1 镜像来说尤其如此)。如果 Mirror1 长时间未更新,低层级镜像站点将开始从 Mirror1 镜像旧数据,从而形成恶性循环……保持镜像站点更新!

请联系集群管理员,联系方式见 。

ftp.is.FreeBSD.org - -

ftp2.ru.FreeBSD.org - -

原文:

我们期望导师确保学员阅读 、 和 。虽然没有必要记住所有的细节,但每个提交者都需要了解这些内容,才能有效地成为社区的一部分(并尽量避免犯新手错误)。

有个不愿透露姓名的坏蛋做出了 。类似的共同导师提交也出现在 src 树中。这就意味着对吗?这就意味着错吗?似乎这是事物发展的一部分。

原文链接:

本文档记录了在远程系统的控制台不可用时,如何进行 FreeBSD 操作系统的远程安装。本文的主要思想是与 Martin Matuska () 合作的结果,Paweł Jakub Dawidek () 提供了宝贵的意见。

正如我们在 部分提到的,许多知名的服务器托管公司提供某种形式的救援系统,该系统通过他们的局域网启动,并可通过 SSH 访问。通常他们提供此类支持以帮助客户修复操作系统故障。正如本文所解释的,借助这些救援系统,实际上是可以安装 FreeBSD 的。

当网络接口驱动程序已知时,使用 conf/rc.conf 配置网络选项会更加方便。该文件的语法与 FreeBSD 标准的 文件相同。

例如,如果你知道将会使用 网络接口,可以在 conf/rc.conf 中设置以下选项:

第一步是挂载 FreeBSD 安装 CD 或安装 ISO 映像到 /cdrom。为了方便示例,本文假设你已经下载了 FreeBSD 10.1-RELEASE ISO。使用 工具挂载该 ISO 映像到 /cdrom 目录:

如果一切顺利,映像应该已经写入第一个设备的 MBR,可以重新启动机器。使用 工具观察机器是否正确启动。系统重新上线后,应该可以通过配置的密码以 root 用户通过 进行访问。

mfsBSD 已成功启动,应该可以通过 登录。接下来的部分将描述如何创建和标记切片,设置 gmirror 以实现 RAID-1,以及如何使用 sysinstall 安装一个最小的 FreeBSD 操作系统发行版。

第一步是为 FreeBSD 分配磁盘空间,也就是创建切片和分区。显然,当前正在运行的系统已完全加载到系统内存中,因此在操作硬盘时不会出现问题。可以使用 sysinstall 或 和 配合使用来完成这项任务。

接下来,使用你喜欢的工具创建切片并标记它们。虽然使用 sysinstall 更为简便,但使用标准的基于文本的 UNIX® 工具(如 和 )是一种更强大且可能更少出错的方法,本节将介绍这种方法。前者在 FreeBSD 手册的 章节中有详细的文档。正如本文引言中提到的,这篇文章将介绍如何设置一个具备 RAID-1 和 ZFS 功能的系统。我们的设置将包括一个小的 镜像 /(根)、/usr 和 /var 数据集,其余磁盘空间将分配给一个 镜像 ZFS 文件系统。请注意,ZFS 文件系统将在 FreeBSD 操作系统成功安装并启动后配置。

以下示例将描述如何创建切片和标签,如何在每个分区上初始化 ,以及如何在每个镜像分区上创建一个 UFS2 文件系统:

③ 现在,手动编辑给定磁盘的标签。参考 手册页面了解如何创建分区。创建分区 a 用于 /(根)文件系统,b 用于交换分区,d 用于 /var,e 用于 /usr,最后 f 将用于 ZFS。

⑤ 在每个分区上初始化 。 |

⑥ 请注意,-F 用于交换分区。这告诉 假设设备在电源/系统故障后处于一致状态。

完成后,启动 。从主菜单选择 Custom 安装。选择 Options 并按回车。使用箭头键将光标移至 Install Root 项目,按空格键并将其更改为 /mnt。按回车键提交更改,并按 q 退出 Options 菜单。

现在,你必须 到新安装的系统中,以完成安装。使用以下命令:

使用 工具向系统添加额外的用户。不要忘记将用户添加到 wheel 组,这样你可以在重启后获取 root 访问权限。

系统现在应该准备好进行下次启动。使用 命令重新启动系统。

最后一步是配置 并创建一些 文件系统。创建和管理 ZFS 非常简单。首先,创建一个镜像池:

就这样。如果你对 FreeBSD 上的 ZFS 更感兴趣,请参考 FreeBSD Wiki 上的 部分。

原文:

这个简单的例子展示了 alice 使用 切换到 root。

进程既是客户端也是服务器。

仲裁者是 root,因此 是 setuid root。

下面的例子展示了 eve 尝试发起到 login.example.com 的 连接,要求以 bob 身份登录,并成功登录。Bob 应该选择一个更好的密码!

客户端是 Eve 的 进程。

服务器是 login.example.com 上的 进程。

该策略适用于 sshd 服务(不一定仅限于 服务器)。

通过请求认证令牌并将其与存储在数据库中的值或从认证服务器获取的值进行比较来认证申请者。

建立账户凭证,如用户ID、组成员资格和资源限制。

验证请求的账户是否可用。

执行与会话设置相关的任务:在 utmp 和 wtmp 数据库中添加条目,启动 SSH 代理等。

执行与会话拆卸相关的任务:在 utmp 和 wtmp 数据库中添加条目,停止 SSH 代理等。

更改认证令牌,选择性地验证其是否足够难以猜测,是否未曾使用过等。

当服务器发起 PAM 事务时,PAM 库尝试加载在 调用中指定的服务的策略。该策略指定了认证请求应如何处理,并在配置文件中定义。这是 PAM 中的另一个核心概念:管理员可以通过简单地编辑文本文件来调整系统安全策略(在广义上理解)。

服务器调用 来初始化 PAM 库,并指定其服务名称和目标账户,并注册适当的会话函数。

服务器获取与事务相关的各种信息(例如申请人的用户名和客户端运行的主机名称),并使用 将其提交给 PAM。

服务器调用 来验证申请人。

服务器调用 来验证请求的账户是否可用且有效。如果密码正确但已过期, 将返回 PAM_NEW_AUTHTOK_REQD 而不是 PAM_SUCCESS。

如果前一步返回 PAM_NEW_AUTHTOK_REQD,服务器现在调用 强制客户端更改请求账户的认证令牌。

既然申请人已经正确地通过了认证,服务器调用 来建立请求账户的凭证。它能够做到这一点,因为它代表裁判行事,并持有裁判的凭证。

待正确的凭证被建立,服务器调用 来设置会话。

待服务器完成为客户端提供服务,它调用 来拆除会话。

最后,服务器调用 来通知 PAM 库它已经完成,可以释放在事务过程中分配的所有资源。

如 中所解释的,每一行 /etc/pam.conf 中包含四个或更多字段:服务名称、功能名称、控制标志、模块名称以及零个或多个模块参数。

功能是 中描述的四个功能关键字之一。

同样,控制标志是 中描述的四个关键字之一,描述如何解释模块返回的代码。Linux-PAM 支持一种替代语法,允许你为每个可能的返回码指定关联的操作,但应避免使用这种语法,因为它是非标准的,并且与 Linux-PAM 调度服务调用的方式紧密相关(与 Solaris™ 和 OpenPAM 的方式差异很大)。不出所料,OpenPAM 不支持这种语法。

当应用程序调用 时,PAM 库加载指定服务的策略,并为每个功能构建四个模块链。如果这些链中的一个或多个为空,则会用 other 服务的策略中的相应链进行替换。

第二个例外是 将 binding 和 sufficient 模块视为 required 模块。

第三个也是最后一个例外是 会运行整个链两次(一次用于初步检查,一次用于实际设置密码),在初步阶段,它将 binding 和 sufficient 模块视为 required 模块。

5.1.

模块是最简单的模块之一,它对任何请求返回 PAM_AUTH_ERR。它对于快速禁用某个服务(将其添加到每个链的顶部)或者终止 sufficient 模块链非常有用。

5.2.

模块将其参数作为 PAM_TEXT_INFO 消息传递给对话函数。它主要用于调试,但也可以用来在开始身份验证过程之前显示消息,例如 "Unauthorized access will be prosecuted"。

5.3.

模块将其第一个参数作为要执行的程序名,剩余的参数作为命令行参数传递给该程序。一个可能的应用是使用它在登录时运行一个程序来挂载用户的主目录。

5.4.

模块

5.5.

模块根据用户是否属于某个特定文件组(通常是 wheel 组,针对 )来接受或拒绝申请人。它主要用于维持 BSD 传统的 行为,但也有其他用途,例如排除某些用户组使用特定服务。

5.6.

模块允许使用固定登录名的访客登录。可以对密码施加各种要求,但默认行为是只要登录名是访客账户的名字,就允许任何密码。此模块可以轻松用于实现匿名 FTP 登录。

5.7.

模块

5.8.

模块

5.9.

模块

5.10.

模块提供了一个账户管理原语的实现,强制执行 表中指定的登录限制。

5.11.

模块在 /var/run/nologin 文件存在时拒绝非 root 用户的登录。这个文件通常由 在剩余时间少于五分钟时创建。

5.12.

模块

5.13.

模块是最简单的模块之一,它对任何请求返回 PAM_SUCCESS。它作为占位符很有用,适用于那些本应为空的服务链。

5.14.

模块

5.15.

模块

5.16.

模块仅在调用它的进程的真实用户 ID 为 0 时才返回成功。这对于非网络服务(如 或 )很有用,其中 root 应该自动有访问权限。

5.17.

模块

5.18.

模块仅在申请人的名字与目标账户的名字匹配时返回成功。它最适用于非网络服务,如 ,其中可以轻松验证申请人的身份。

5.19.

模块提供了身份验证和会话服务。身份验证服务允许拥有密码保护的 SSH 秘密密钥的用户通过输入密码来进行身份验证。会话服务启动 并将解密的密钥预加载到其中。这个特性对于本地登录特别有用,无论是在 X 窗口(使用 或其他支持 PAM 的 X 登录管理器)还是在控制台。

5.20.

模块

5.21.

模块实现了传统的 UNIX® 密码身份验证,使用 获取目标账户的密码,并将其与申请人提供的密码进行比较。它还提供账户管理服务(强制执行账户和密码到期时间)以及密码更改服务。这可能是最有用的模块,因为大多数管理员都希望至少为某些服务保留历史行为。

以下是一个使用 PAM 的最小实现示例,基于 。请注意,它使用了 OpenPAM 特有的 对话功能,该功能在 security/openpam.h 中定义。如果你希望在使用不同 PAM 库的系统上构建此应用程序,则必须提供自己的对话功能。实现一个强健的对话功能相当困难;在 中提供的实现是一个不错的起点,但不应在实际应用中使用。

以下是一个最小实现的 ,仅提供身份验证服务。它应当能够与大多数 PAM 实现一起构建和运行,但如果可用,它会利用 OpenPAM 扩展:请注意使用了 ,这大大简化了提示用户输入密码的过程。

以下是一个大大简化版的 OpenPAM 。它是完全功能的,应该能让读者大致了解对话功能的工作方式,但它对于真实世界的使用来说过于简单。即使你不使用 OpenPAM,也可以下载源代码并适应 到你的需求中;我们认为它是一个足够健壮的 tty 导向的对话功能。

. The Open Group. 1-85912-144-6. 1997年6月。

. Andrew G. Morgan. 1999-10-06。

. Sun Microsystems。

Dag-Erling Smørgrav. ThinkSec AS。

Andrew Morgan。

原文:

历史上的 BSD 系统有一个单一的启动脚本 /etc/rc。它在系统启动时由 调用,并执行所有多用户操作所需的用户空间任务:检查和挂载文件系统、设置网络、启动守护进程等。具体的任务列表在不同的系统中有所不同;管理员需要根据需要进行定制。除少数例外,/etc/rc 必须进行修改,真正的黑客对此感到满意。

后来,为了单独启动最重要的子系统,曾试图将一些 /etc/rc 的部分内容拆分出来。著名的例子是 /etc/netstart 用于启动网络。它确实允许从单用户模式访问网络,但由于其代码的一些部分需要与本质上与网络无关的操作交织在一起,因此未能很好地融入自动启动过程。这就是为什么 /etc/netstart 最终变成了 /etc/rc.network 的原因。后者不再是一个普通的脚本;它由大型、复杂的 函数组成,这些函数在系统启动的不同阶段由 /etc/rc 调用。然而,随着启动任务的多样化和复杂化,这种“准模块化”方法变得比单一的 /etc/rc 更加繁琐。

BSD rc.d 背后的基本思想是 细粒度模块化 和 代码重用。细粒度模块化 意味着每个基本的“服务”如系统守护进程或基本启动任务都有自己的 脚本,能够启动服务、停止它、重新加载它、检查其状态。特定的操作通过脚本的命令行参数来选择。/etc/rc 脚本仍然负责系统启动,但它现在只是依次调用这些小脚本,并传递 start 参数。通过用 stop 参数运行同一组脚本,执行关闭任务变得也很容易,这由 /etc/rc.shutdown 完成。请注意,这与 Unix 系统的方式非常接近,Unix 系统有一组小型的专用工具,每个工具尽可能地完成其任务。代码重用 意味着常见操作通过 函数实现,并集中在 /etc/rc.subr 中。现在,典型的脚本可能只是几行 代码。最后,rc.d 框架的一个重要部分是 ,它帮助 /etc/rc 按照它们之间的依赖关系有序地运行这些小脚本。它也可以帮助 /etc/rc.shutdown,因为关闭顺序的正确顺序与启动顺序相反。

BSD rc.d 的设计在 中有所描述,rc.d 组件在 中得到了详细的文档说明。然而,对于一个 rc.d 新手来说,如何将众多的片段和组件结合起来,创建一个风格良好的特定任务脚本,可能并不显而易见。因此,本文将采用一种不同的方法来描述 rc.d。它将展示在一些典型情况下应该使用哪些特性,以及为什么。请注意,这不是一份操作指南,因为我们的目标不是提供现成的配方,而是展示一些进入 rc.d 领域的简单入口。这篇文章也不是相关手册页的替代品。在阅读本文时,遇到需要正式和完整文档的部分,应该随时参考手册页。

理解本文有一些前提条件。首先,你应该熟悉 脚本语言,以掌握 rc.d。此外,你应该了解系统如何执行用户空间的启动和关闭任务,这在 中有所描述。

为了被 rc.d 框架正确管理,脚本需要使用 语言编写。如果你有一个服务或 Port 使用二进制控制工具或用其他语言编写的启动例程,可以将该元素安装到 /usr/sbin(用于系统)或 /usr/local/sbin(用于 Port),并从相应的 rc.d 目录中的 脚本中调用它。

如果你想了解为什么 rc.d 脚本必须用 语言编写的详细原因,请查看 /etc/rc 如何通过 run_rc_script 调用它们,然后研究 /etc/rc.subr 中 run_rc_script 的实现。

② 在 /etc/rc.subr 中定义了多个供 rc.d 脚本使用的 函数。这些函数在 中有文档说明。虽然理论上可以编写不使用 的 rc.d 脚本,但它的函数非常方便,使工作变得容易得多。所以,毫不奇怪,大家都在 rc.d 脚本中使用 ,我们也不例外。

一个 rc.d 脚本必须在调用 函数之前“source”/etc/rc.subr(通过“.”包含它),以便 可以了解这些函数。推荐的写法是首先包含 /etc/rc.subr。

③ 必须变量 name 指定了我们脚本的名称。它是 所要求的。也就是说,每个 rc.d 脚本 必须 在调用 函数之前设置 name。

现在是时候为我们的脚本选择一个唯一的名称了。我们将会在编写脚本的多个地方使用它。name 变量的内容需要与脚本名称匹配,FreeBSD 的某些部分(例如 和 rc 框架的 cpuset 功能)依赖于此。因此,文件名也不能包含可能在脚本中引起问题的字符(例如,不要使用连字符“-”等)。

当前的 rc.d 脚本风格是将分配给变量的值用双引号括起来。请记住,这仅仅是一个风格问题,可能并非总是适用。你可以安全地省略不包含 元字符的简单词语周围的引号,而在某些情况下你需要使用单引号来防止 对值的解释。程序员应能根据语言语法和风格约定来区分并合理使用两者。

④ 的主要思想是,rc.d 脚本提供处理程序或方法,以供 调用。特别是,start、stop 和 rc.d 脚本的其他参数就是通过这种方式处理的。方法是一个存储在名为 argument_cmd 的变量中的 表达式,其中 argument 对应于脚本命令行中可以指定的内容。稍后我们将看到 为标准参数提供了默认的方法。

⑤ 我们应该记住, 提供了标准参数的默认方法。因此,如果我们希望某个标准方法什么也不做,我们必须使用一个无操作的 表达式来重载该方法。

强烈建议为脚本中定义的所有函数添加 ${name} 前缀,这样它们就不会与 或其他公共包含文件中的函数冲突。

⑦ 这行调用 加载 变量。我们的脚本目前尚未使用这些变量,但仍然建议加载 ,因为可能有控制 本身的 变量。

⑧ 通常这是 rc.d 脚本中的最后一条命令。它调用 的机制,根据我们提供的变量和方法执行请求的操作。

现在让我们为我们的虚拟脚本添加一些控制选项。如你所知,rc.d 脚本通过 来进行控制。幸运的是, 隐藏了所有复杂性。下面的脚本使用 通过 来检查它是否启用,并在启动时显示一条消息。这两个任务实际上是独立的。一方面,rc.d 脚本可以只支持启用和禁用它的服务。另一方面,强制性的 rc.d 脚本可以有配置变量。尽管如此,我们会在同一个脚本中做这两件事:

② 现在 load_rc_config 在脚本中较早地被调用,在访问任何 变量之前。

在检查 rc.d 脚本时,请记住 会推迟评估函数中的表达式,直到函数被调用。因此,在脚本中很晚才调用 load_rc_config 并且仍然能够访问 变量,是没有问题的,因为方法函数是在 load_rc_config 调用之后由 run_rc_command 调用的。

③ 如果 rcvar 本身已设置,但所指示的开关变量未设置,run_rc_command 将发出警告。如果你的 rc.d 脚本是系统自带的,你应当将开关变量的默认设置添加到 /etc/defaults/rc.conf 中,并在 中进行文档说明。否则,应该由你的脚本提供开关的默认设置。后者的标准方法如本例所示。

你可以通过在脚本的参数前加上 one 或 force,使 认为开关已设置为 ON,不管其当前设置如何,例如:onestart 或 forcestop。不过,请记住,force 会有其他危险影响,稍后会讨论,而 one 只是覆盖了开关。假设 dummy_enable 为 OFF,那么下面的命令将强制执行 start 方法,尽管该设置为 OFF:

④ 现在,启动时显示的消息不再硬编码在脚本中。它由一个名为 dummy_msg 的 变量指定。这是一个简单的例子,展示了如何通过 变量来控制 rc.d 脚本。

所有仅由我们的脚本使用的 变量的名称 必须 具有相同的前缀:${name}_。例如:dummy_mode、dummy_state_file 等等。

尽管可以在内部使用更短的名称,例如仅使用 msg,但将唯一的前缀 ${name}_ 添加到脚本引入的所有全局名称,将帮助避免与 命名空间发生冲突。作为规则,基础系统中的 rc.d 脚本无需为它们的 变量提供默认值,因为默认值应该在 /etc/defaults/rc.conf 中设置。另一方面,rc.d 脚本对于 Port 则应提供像示例中那样的默认值。

我们之前提到过, 可以提供默认方法。显然,这些默认方法不能过于通用,它们适用于启动和关闭一个简单的守护进程。现在假设我们需要为一个名为 mumbled 的守护进程编写 rc.d 脚本。下面是该脚本:

① command 变量对于 是有意义的。如果它被设置, 将根据提供的默认方法来启动和关闭守护进程。特别地,默认方法会为以下命令提供支持:start、stop、restart、poll 和 status。

守护进程将通过运行 $command 并使用 $mumbled_flags 指定的命令行标志来启动。因此,默认 start 方法的所有输入数据都可以通过我们脚本中设置的变量获得。与 start 方法不同,其他方法可能需要有关已启动进程的附加信息。例如,stop 方法必须知道要终止的进程的 PID。在这种情况下, 会扫描所有进程列表,寻找名称为 procname 的进程。后者是另一个对 有意义的变量,默认情况下其值与 command 相同。换句话说,当我们设置 command 时,procname 会自动设置为相同的值。这使得我们的脚本能够终止守护进程,并首先检查它是否正在运行。

有些程序实际上是可执行脚本。系统通过启动其解释器并将脚本的名称作为命令行参数传递给它来运行此类脚本。这会反映在进程列表中,可能会让 感到困惑。如果 $command 是一个脚本,应该额外设置 command_interpreter 变量,以便 知道实际的进程名称。

对于每个 rc.d 脚本,都有一个可选的 变量,它优先于 command。该变量的名称按照以下规则构造:${name}_program,其中 name 是我们之前讨论的必需变量。例如,在此案例中,它将是 mumbled_program。是 负责让 ${name}_program 重写 command。

当然,即使未设置 command, 也允许你从 或脚本本身设置 ${name}_program。在这种情况下,${name}_program 的特殊属性就会丧失,它变成了一个普通的变量,供脚本自用。然而,不建议单独使用 ${name}_program,因为它与 command 一起使用已经成为 rc.d 脚本的惯例。

有关默认方法的更多详细信息,请参阅 。

永远不要 在 command_args 中包含以破折号开头的选项,如 -X 或 --foo。command_args 的内容会出现在最终命令行的末尾,因此它们可能会跟在 ${name}_flags 中的参数后面,而大多数命令在普通参数后不会识别这些带破折号的选项。传递额外的选项给 $command 的更好方法是将它们放在 ${name}_flags 的前面,或者修改 rc_flags 。

② 一个良好设计的守护进程应该创建一个 pidfile,这样可以更容易且更可靠地找到它的进程。设置 pidfile 变量后, 会在默认方法中使用该 pidfile。

实际上, 还会使用 pidfile 检查守护进程是否已在运行。若要跳过此检查,可以使用 faststart 参数。

③ 如果守护进程必须在启动前确保某些文件存在,可以将这些文件列在 required_files 中, 会在启动守护进程之前检查这些文件是否存在。此外,还有 required_dirs 和 required_vars 用于检查目录和环境变量,具体内容请查阅 。

信号名称应当不带 SIG 前缀,正如示例中所示。FreeBSD 版本的 可以识别 SIG 前缀,但其他操作系统版本可能不识别。

⑤⑥ 在默认方法之前或之后执行额外的任务很简单。对于脚本支持的每个命令参数,我们可以定义 argument_precmd 和 argument_postcmd,这些 命令将在相应方法的前后执行,名称也非常直观。

在方法、前后命令中,你可以放入任何有效的 表达式。虽然调用一个实现实际工作的函数是大多数情况下的好风格,但不要让风格限制你对其背后原理的理解。

reload 命令是特殊的。一方面,它在 中有预设的方法。另一方面,reload 默认是不会提供的。原因是并不是所有守护进程都使用相同的重新加载机制,有些甚至根本不需要重新加载。因此我们需要显式要求提供内建的功能,可以通过 extra_commands 来实现。

默认的 reload 方法做了什么呢?通常情况下,守护进程会在接收到某个信号后重新加载其配置——通常是 SIGHUP。因此, 会尝试通过向守护进程发送信号来重新加载它。默认信号是 SIGHUP,但可以通过 sig_reload 来进行定制。

非标准命令在启动或关闭时不会被调用。它们通常是为了系统管理员的便利而设,也可以从其他子系统调用,例如在 中通过 指定。

完整的可用命令列表可以在脚本没有参数时通过 打印的使用行中找到。例如,这是我们正在研究的脚本中的使用行:

⑬ 脚本可以根据需要调用它自己的标准或非标准命令。这看起来像是调用函数,但我们知道命令和 shell 函数并不总是相同的。例如,xyzzy 在这里不是作为函数实现的。此外,还可以有前命令和后命令,它们应该按顺序执行。因此,脚本运行自己的命令的正确方法是通过 ,如示例所示。

⑨ 有个非常方便的函数 checkyesno 是 提供的。它以变量名作为参数,只有当变量的值为 YES、TRUE、ON 或 1(不区分大小写)时,返回零退出码;否则返回非零退出码。如果变量包含其他值,即无效的值,它会打印一条警告信息。

记住,在 中,零退出码意味着真,非零退出码意味着假。

⑪ 在某些情况下,我们可能需要发出一条重要信息,并同时将其写入 syslog。这可以通过以下 提供的函数轻松实现:debug、info、warn 和 err。其中,err 函数在输出信息后会以指定的状态码退出脚本。

写好脚本之后,就需要将它整合进 rc.d。关键的一步是将脚本安装到 /etc/rc.d(用于基础系统)或 /usr/local/etc/rc.d(用于 Port)。bsd.prog.mk 和 bsd.port.mk 都提供了方便的安装钩子,通常无需担心文件的所有权和权限。系统脚本应通过 src/libexec/rc/rc.d 下的 Makefile 进行安装;Port 的脚本则可以使用 USE_RC_SUBR 来安装,具体做法见 。

我们之前提到过 。现在是时候仔细了解一下它了。简而言之,[rcorder(8)] 会读取一组文件,分析其内容,然后按依赖顺序将这些文件输出到 stdout。关键点在于将依赖信息保存在文件内部,让每个文件只描述自身。一个文件可以指定以下信息:

不出意外,[rcorder(8)] 只能处理语法类似 的文本文件。也就是说,[rcorder(8)] 所能识别的特殊行看起来像 [sh(1)] 的注释。这些特殊行的语法相当严格,以便于程序处理。详见 [rcorder(8)]。

除了使用 [rcorder(8)] 的特殊行之外,一个脚本还可以通过强制启动另一个服务来表达其依赖关系。这在所依赖服务是可选的情况下尤其有用——如果系统管理员在 中禁用了该服务,它本不会自动启动。

②③ 所以我们的脚本表明它依赖于其他脚本所提供的哪些“条件”。根据这些行,我们的脚本要求 将它排列在提供 DAEMON 和 cleanvar 的脚本之后,但在提供 LOGIN 的脚本之前。

除了每个服务对应一个条件外,还有一些元条件及其“占位符”脚本,用于确保某些操作组在其他操作组之前执行。这些元条件以 全大写字母 表示。它们的列表和用途可在 中找到。

请记住,在 REQUIRE: 行中列出某个服务名称,并不能保证该服务在我们的脚本启动时一定已运行。被要求的服务可能启动失败,或者只是被系统管理员在 中禁用了。显然, 无法跟踪此类细节, 也不会这样做。因此,我们脚本启动的应用程序应能应对任何所需服务不可用的情况。在某些情形下,我们可以像 所述那样提供帮助。

④ 正如我们在上文中所了解到的, 的关键字可用于选择或排除某些脚本。也就是说,任何 的使用者都可以通过 -k 和 -s 选项,分别指定哪些关键字属于“保留列表”或“跳过列表”。在所有待进行依赖排序的文件中, 将只挑选那些具备保留列表中的关键字(如果该列表非空),且不具备跳过列表中关键字的文件。

在 FreeBSD 中, 被 /etc/rc 和 /etc/rc.shutdown 所使用。这两个脚本定义了 FreeBSD rc.d 关键字的标准列表及其含义如下:

nojail 该服务不适用于 环境。如果在 jail 中,自动启动和关闭流程将忽略此脚本。

当系统即将关机时,/etc/rc.shutdown 会运行。它假设大多数 rc.d 脚本在那时没有任何操作。因此,/etc/rc.shutdown 会选择性地调用具有 shutdown 关键字的 rc.d 脚本,忽略其他脚本。为了更快的关机,/etc/rc.shutdown 会向其运行的脚本传递 faststop 命令,促使它们跳过初步检查,例如 pidfile 检查。由于依赖的服务应当在其前提条件之前停止,/etc/rc.shutdown 按反向依赖顺序运行脚本。如果编写一个真实的 rc.d 脚本,你应当考虑它在系统关机时是否相关。例如,如果你的脚本仅在接收到 start 命令时才执行工作,那么你不需要包括此关键字。然而,如果你的脚本管理一个服务,最好在系统进入最终关机阶段(如 中描述的)之前停止该服务。特别是,当某个服务需要较长时间或特殊操作才能干净地关闭时,应该显式地停止它。数据库引擎就是此类服务的典型例子。

如果你仍然无法避免使用 force_depend,示例展示了如何有条件地调用它。在这个示例中,我们的 mumbled 守护进程要求另一个守护进程 frotz 先启动。然而,frotz 也是可选的;而 并不知道这些细节。幸运的是,我们的脚本可以访问所有的 变量。如果 frotz_enable 为真,我们会尽力而为,依赖 rc.d 启动 frotz。否则,我们强制检查 frotz 的状态。最后,如果发现 frotz 没有运行,我们会强制执行对 frotz 的依赖。force_depend 会发出警告信息,因为它应该只在发现配置错误时才被调用。

在启动或关机时调用时,rc.d 脚本应当操作其负责的整个子系统。例如,/etc/rc.d/netif 应该启动或停止由 描述的所有网络接口。任何一项任务都可以通过单一的命令参数,如 start 或 stop 来唯一指示。在启动和关机之间,rc.d 脚本帮助管理员控制运行中的系统,这时就需要更多的灵活性和精确性。例如,管理员可能希望将一个新的网络接口的设置添加到 中,然后启动它,而不干扰现有接口的操作。下次,管理员可能需要关闭单个网络接口。为了符合命令行精神,相应的 rc.d 脚本需要一个额外的参数,即接口名称。

幸运的是, 允许将任何数量的参数传递给脚本的方法(在系统限制内)。因此,脚本本身的修改可以最小化。

那么,如何让 获得额外的命令行参数呢?它是否应该直接获取这些参数?当然不行。首先,sh(1) 函数无法访问调用者的定位参数,而 只是这些函数的集合。其次,rc.d 的良好习惯是由主脚本决定哪些参数传递给其方法。

因此, 采用的做法如下:run_rc_command 将所有参数(除了第一个)原样传递给相应的方法。第一个被省略的参数是方法本身的名称:start、stop 等。它会被 run_rc_command 移除,因此原始命令行中的 $2 将作为 $1 传递给方法,依此类推。

① 你在 start 后输入的所有参数都可以成为相应方法的位置参数。我们可以根据任务、技能和需要以任何方式使用它们。在当前的示例中,我们只是将所有参数作为一个字符串传递给 ,请注意双引号内的 $*。以下是如何调用该脚本的示例:

一个 程序员应当理解 $* 和 $@ 之间的微妙区别,因为它们表示所有位置参数。有关详细讨论,请参考一本好的 脚本手册。*在完全理解之前不要使用这些表达式,因为它们的误用会导致脚本有漏洞并不安全。

为了使脚本准备好与 一起使用,只需要插入一行配置:

严格来说,空配置并不是必需的,但它明确介绍了该脚本已准备好用于服务 Jail,并且不需要额外的 Jail 权限。因此,在这种情况下,强烈建议添加这样的空配置。最常用的选项是 "net_basic",它启用对主机 IPv4 和 IPv6 地址的使用。所有可能的选项在 中都有解释。

如果 start/stop 设置依赖于来自 rc 框架的变量(例如,在 中设置的变量),则需要通过 load_rc_config 和 run_rc_command 来处理,而不是在 precommand 中处理。

① 禁用操作需要在 load_rc_config 调用之后进行,否则 中的设置可能会覆盖它。

① 和 ② 确保将 name 变量设置为脚本文件名的 。如果文件名是 /usr/local/etc/rc.d/dummy,则 name 设置为 dummy。这样,改变 rc 脚本的文件名将自动改变 name 变量的内容。

提供了 rc.d 的概述,并详细阐述了其设计决策的理由。它对整个 rc.d 框架以及它在现代 BSD 操作系统中的位置提供了深刻的见解。

手册页 、 和 详细记录了 rc.d 组件。要充分利用 rc.d 的强大功能,必须阅读这些手册页,并在编写自己的脚本时参考它们。

/etc/rc.d 中的内容是工作中的真实示例,来自一个正在运行的系统,是最主要的学习资源。其内容简单易读,因为大多数棘手问题都隐藏在 中。然而请记住,/etc/rc.d 脚本并非由天使编写,因此它们可能存在缺陷和不理想的设计决策。现在,你可以改进它们!

subsystem

problem with the subsystem

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

port which is maintained by

problem with Bugzilla .

原文:

- 这是文档项目的一部分,虽然不包含与内核编程相关的内容,但提供了一些通用的有用信息。

- 同样是文档项目的一部分,包含了多个低级功能和程序的描述。最重要的章节是第13章,。

网站上的蓝图部分 - 包含了多篇关于内核功能的有趣文章。

手册页和 - 提供 GEOM 子系统的一般介绍。

手册页 、、、、、、 等,提供有关特定功能的文档。

手册页 - 提供有关编码风格的文档,所有提交到 FreeBSD 树的代码必须遵循这些风格。

然而,由于并非每个人都有两台或更多计算机,因此可以采取一些措施来准备一个“实时”系统进行内核代码开发。此设置也适用于在 或 虚拟机中进行开发(这是除专用开发机外的下一个最佳选择)。

请参见 。基本的内存分配与用户空间中的类似,只是略有不同。最显著的不同是,malloc() 和 free() 会接受额外的参数,详细信息可以参考手册页面。

还有另一种内存分配机制,称为 UMA(通用内存分配器)。有关详细信息,请参见 ,它主要用于快速分配由相同大小项组成的列表(例如,动态数组或结构体)。

请参见 。在很多情况下,需要维护一个事物列表。幸运的是,系统通过 C 宏提供了几种数据结构来实现这些列表。最常用的列表类型是 TAILQ,因为它最灵活。它也是内存需求最大的(它的元素是双向链接的),并且速度最慢(尽管速度差异仅在几个 CPU 指令的数量级,因此不应过于在意)。

如果数据检索速度非常重要,可以参考 和 。

这里重要的一点是,bio 是异步处理的。意味着在大多数代码中,没有类似用户空间的 和 调用,这些调用会一直等待直到请求完成。相反,当请求完成(或者出错)时,会调用开发者提供的函数作为通知。

用户调用 实用程序(或其硬链接的其他工具)

它使用 加载库,提取命令行参数和辅助函数的定义。

在命令行参数中查找命令(通常是 label),并调用辅助函数。

请参阅 手册页面,了解数据如何在 bio 结构中来回传递(特别注意 bio_parent 和 bio_children 字段,以及它们如何处理)。

调用 和 uma_zalloc(),并设置 M_WAITOK 标志。

内核线程是通过 函数创建的,它们的行为类似于用户空间线程,只是它们不能通过返回调用者来表示终止,而必须调用 。

FreeBSD 内核中的互斥锁(参见 )与它们在用户空间中的常见形式有一个区别——代码在持有互斥锁时不能进行睡眠。如果代码需要频繁睡眠, 锁可能更合适。另一方面,如果你几乎所有的工作都在一个线程中完成,那么你可能完全不需要互斥锁。

原文:

DB25 RS232-C 引脚
DB9 IBM PC 引脚
EIA 电路符号
CCITT 电路符号
常见名称
信号源
描述
"F"
QFP
(四方扁平封装) L 引脚类型
供应商
部件编号
错误(即“差异”报告)
I/O 端口
访问权限
描述

sio 驱动程序提供对基于 NS8250、NS16450、NS16550 和 NS16550A 的 EIA RS-232C(CCITT V.24)通信接口的支持。还支持几种多端口卡。有关详细技术文档,请参阅 手册页面。

如果你还没有设置自定义内核配置文件,请参考 章节,了解 FreeBSD 手册中的一般程序。以下是针对 Boca 16 板的具体配置,假设你使用的是内核名称 MYKERNEL 并使用 vi 编辑。

接下来,必须使用 /dev/MAKEDEV 脚本在 /dev 中为设备创建适当的条目。如果你正在运行支持 的 FreeBSD 5.X 内核,则此步骤可以省略。 如果你确实需要创建 /dev 条目,请作为 root 用户运行以下命令:

由 Helge Oldach 提供 ,1999 年 9 月

你需要将两个 UART 的 IRQ 驱动器解耦,以便只有在其中一个 UART 触发 IRQ 时,板卡的 IRQ 线才会变高,否则保持低电平。Joerg Wunsch 提出了一个解决方案:,即使用两个二极管(强烈推荐使用锗二极管或肖特基二极管)和一个 1 kOhm 电阻,组成一个“有线或”电路。以下是该电路的示意图,从上面的 4x3 跳线矩阵开始:

请注意,sio1 和 sio2 的 flags 设置是至关重要的;有关详细信息,请参阅 。 (通常,"flags" 属性中的 2 指的是 sio2,它包含 IRQ,而你肯定希望使用 5 的低半字节。)启用内核详细模式后,这应该会产生类似以下的输出:

如果你使用的是 在 FreeBSD 5.X 中,以下步骤不再需要。

原文:

早在“开源”一词被提出之前,软件就由松散的程序员团队开发并自由交换。自 1950 年代初期,像 和 这样的组织开发了许多计算机硬件公司与硬件捆绑的程序。在那个时代,计算机公司主要从事硬件业务;所有能降低软件成本并提供更多程序的方式,都能让硬件公司更具竞争力。

近年来,FreeBSD 所采用的所谓 实际上声明,你可以对程序或其源代码做任何事情,但你不享有任何担保,且任何作者都不承担任何责任(基本上,你无法起诉任何人)。这种新 BSD 许可证旨在鼓励产品商业化。所有 BSD 代码都可以在不受任何代码可用性或未来行为限制的情况下,出售或包含在专有产品中。

Richard Stallman(Emacs 的开发者)是麻省理工学院(MIT)的一名员工,当时他的实验室从自家开发的系统转向了专有系统。Stallman 在发现自己无法合法地对该系统进行一些小的改进时感到不满。(Stallman 的许多同事离开后,成立了两家公司,这些公司基于 MIT 开发的软件并获得 MIT 的许可;这似乎是因为对这些软件源代码的访问存在分歧)。Stallman 设计了一种替代商业软件许可证的方式,并称之为 GPL(“GNU 公共许可证”)。他还成立了一家非营利组织——(FSF),该基金会旨在开发一款完整的操作系统及其所有相关软件,且不受专有许可证的约束。这个系统被称为 GNU,意为“GNU 不是 Unix”。

“这款通用公共许可证不允许将你的程序合并到专有程序中。”

是一款复杂的许可证,以下是使用 GPL 时的一些基本规则:

请记住,GPL 要求任何与 GPL 代码静态链接的内容也必须遵守 GPL。因此,该程序的源代码必须提供给用户。然而,动态链接并不被认为是 GPL 的违反行为。将专有应用程序放到 Linux 上的压力变得越来越大。这些应用程序通常需要与系统库进行链接。这导致了 GPL 的修改版本,即 (“Library”,现已更名为“Lesser”GPL)。LGPL 允许专有代码与 GNU C 库(glibc)进行链接。你不必发布与 LGPL 库动态链接的源代码。

GPL 明确禁止撤销许可证。然而,确实发生过公司(Mattel)购买了 GPL 版权(cphack),撤销了整个版权,诉诸法院并获胜 。也就是说,他们合法地撤销了整个分发和所有基于该版权的衍生作品。这种情况是否会在更大规模和更分散的分发中发生仍然是个开放的问题;关于软件是否真的是 GPL 许可下的问题也存在一些混淆。

在另一个例子中,Red Hat 收购了 Cygnus,这是一家接管了 FSF 编译器工具开发的工程公司。Cygnus 能够做到这一点,因为他们发展了一个商业模式,在这个模式下,他们为 GNU 软件提供支持。这样他们能够雇佣大约 50 名工程师,并通过贡献大量修改来推动程序的发展方向。正如 Donald Rosenberg 所说:“使用像 GPL 这样的许可证的项目……生活在被别人通过更快地发布改进版代码来接管项目的常年威胁下。”

[1]

[2]

[4] 在 的“选择使用什么许可证?”部分

本文是原文的浓缩版本,原文可在 获取。

原文:

(其他版本也可以,但版本较新的更好。如果使用其他版本,请将 clangd12 替换为 clangdN)

(用于 llvm 的 scan-build-py 实现)

(用于 rizsotto 的 scan-build 实现)

本示例中使用的是内置插件管理器,LSP 客户端插件为 。

请参考 了解如何设置快捷键和代码补全。clangd 的官网是 ,而 ccls 的仓库链接是 。

有关编译数据库文件格式的详细信息,请参考 。

首先安装 以获取 Python 解释器。然后,从 LLVM 获取 intercept-build:

可以替代 LLVM 的 scan-build-py。LLVM 的 scan-build-py 已被合并到 rizsotto/scan-build 中。可以通过 pip install --user scan-build 来安装这个实现。intercept-build 脚本默认位于 ~/.local/bin 中。

原文:

那么,如何判断什么是 bug,什么不是 bug 呢?作为简单的经验法则,如果问题可以用问题的形式表达(通常是“如何做 X?”或“在哪里可以找到 Y?”),那么它就不是 bug。虽然这并非绝对的黑白分明,但这个规则覆盖了大多数情况。在寻找答案时,可以考虑将问题提到 。

在这两种情况下,遵循 中介绍的流程将获得最佳结果。(你也可以阅读 。)

由其他人编写并维护的基本系统代码,被导入到 FreeBSD 并加以适配。例如, 和 。这些领域的大多数 bug 应该报告给 FreeBSD 开发者,但在某些情况下,如果问题不是 FreeBSD 特有的,可能需要报告给原始作者。

如果问题出现在基本系统中,请首先阅读 的 FAQ 部分,了解该主题。如果你不熟悉该主题,了解它会很有帮助。FreeBSD 不可能修复基本系统中不属于某些较新分支的任何问题,因此,报告旧版本的 bug 可能只会导致开发者建议你升级到支持版本,以查看问题是否仍然存在。安全官团队维护着 。

FreeBSD 的 (FAQ)列表。FAQ 尝试为各种问题提供答案,例如 、 和 。

。如果你没有订阅,可以使用 查找。如果问题在列表中没有讨论过,你可以尝试发邮件并等待几天,看看是否有人发现了被忽视的内容。

接下来,搜索 (Bugzilla)。除非问题较新或较为冷门,否则很有可能已经有人报告了类似的问题。

最重要的是,查看现有的源代码文档,看看是否已解决你遇到的问题。 对于基础 FreeBSD 代码,你应该仔细阅读 /usr/src/UPDATING 中的内容,或者阅读最新版本的 。如果你是从一个版本升级到另一个版本,尤其是升级到 FreeBSD-CURRENT 分支时,这部分信息至关重要。

然而,如果问题出在作为 FreeBSD Ports 一部分安装的软件上,你应该参考 /usr/ports/UPDATING(针对个 Port)或 /usr/ports/CHANGES(影响整个 Ports 的更改)。 和 也可以通过 cgit 获取。

确保没有人已经提交了类似的 PR。虽然在上文已提到,但这里值得再强调一次。使用 中基于 Web 的搜索引擎只需几分钟时间。(当然,大家偶尔都会忘记做这一步。)

避免争议性请求。如果你的 PR 涉及到一个过去有争议的领域,最好不仅提供补丁,还要提供解释说明,阐明为什么这个补丁是“正确的解决方案”。如上所述,使用 的邮件列表归档进行详细的搜索总是好的准备工作。

类似的注意事项也适用于使用 。请小心剪切和粘贴操作,因为它们可能会更改空白字符或其他文本格式。

一般来说,我们建议使用 git format-patch 来生成一个或一系列针对基础分支(例如 origin/main)的统一 diff。这样生成的补丁将包含 Git 哈希值,并且会包括你的姓名和电子邮件地址,这样提交者就更容易应用你的补丁并正确地将你作为作者(使用 git am)。对于不希望使用 git 的小改动,你可以使用 并加上 -u 选项来创建统一的 diff,这样可以为开发者提供更多上下文,并且比其他 diff 格式更易于阅读。

如果问题与二进制程序(如 或 )有关,你需要首先确定这些程序是在基本系统中,还是通过 Ports 安装的。如果不确定,可以使用 whereis <程序名>。FreeBSD 对 Ports 的约定是将所有内容安装在 /usr/local 下,尽管这可以被系统管理员覆盖。对于这些程序,你应使用 ports 类别(即使 Port 的类别是 www;见下文)。如果程序位于 /bin、/usr/bin、/sbin 或 /usr/sbin,则它属于基本系统,你应使用 bin 类别。这些内容通常是手册页第 1 或 8 节所述的。

如果你真的不知道问题出在哪里(或解释不符合上述任何一个),请选择 misc 类别。在这样做之前,你可能希望先在 上寻求帮助。你可能会得到建议,告知你使用某个现有的类别更合适。

如果有人请求你提供额外信息,或者你记起或发现了在初始报告中未提及的内容,请提交跟进。问题没有被修复的最常见原因是缺乏与报告者的沟通。最简单的方式是使用单个 PR 的网页上的评论选项,你可以通过 访问该页面。

向 发送电子邮件,寻求对报告的评论。

加入相关的 IRC 频道。部分列表请见:。在该频道中告知问题报告,并寻求帮助。要有耐心,发帖后留在频道中,以便来自不同时区的人员有机会跟进。

找到对报告的特定问题感兴趣的提交者。如果问题出在某个特定工具、二进制文件、 Port 、文档或源文件中,检查 。找到最后几次对该文件进行了实质性更改的提交者,并尝试通过 IRC 或电子邮件联系他们。有关提交者及其电子邮件的列表可以在 文章中找到。

如果你发现了关于 bug 系统的问题,请提交 bug!有一个专门用于此目的的类别。如果你无法这样做,请联系 bug 管理员:。

:Simon G. Tatham 撰写的一篇关于撰写有效(非 FreeBSD 特定)问题报告的优秀文章。

:关于 FreeBSD 开发者如何处理问题报告的宝贵见解。

原文:

已弃用 vinum,并且在 FreeBSD 15.0 及以后的版本中不再存在。建议用户迁移到 、、、 或 。

从 FreeBSD 5 开始,vinum 被重写为适应 ,同时保留了原始的思想、术语和磁盘上的元数据。这个重写版本被称为 gvinum(即 GEOM vinum)。虽然本章使用 vinum 这个术语,但所有命令的调用应使用 gvinum。内核模块的名称已从原来的 vinum.ko 更改为 geom_vinum.ko,所有设备节点位于 /dev/gvinum 下,而不是 /dev/vinum。从 FreeBSD 6 开始,原始的 vinum 实现不再出现在代码库中。

硬盘负载的均匀性与数据在硬盘上的分布方式密切相关。以下讨论中,便于理解的方式是将磁盘存储看作一个包含大量数据扇区的空间,可以通过编号访问,就像一本书的页面一样。最明显的方法是将虚拟磁盘划分为多个连续的扇区组,每组大小与单个硬盘的大小相同,并按此方式存储,类似于将一本大书撕成若干小部分。该方法称为 连接(concatenation),其优点是硬盘不需要有任何特定的大小关系。当访问虚拟磁盘时,访问均匀分布在其地址空间时,这种方法效果良好。如果访问集中在较小的区域,效果则较差。 说明了在连接组织中,存储单元分配的顺序。

RAID 提供了各种形式的容错机制,但 RAID-0 有些误导,因为它没有提供冗余。条带化需要稍多的工作来定位数据,并且当传输跨多个硬盘时,它可能会导致额外的 I/O 负载,但它也可以提供更均匀的硬盘负载。 说明了在条带化组织中,存储单元分配的顺序。

总结了每种 plex 组织方式的优缺点。

Plex 类型
最小子磁盘数
可添加子磁盘数
必须相等的大小
应用场景

vinum 维护一个 配置数据库,介绍了系统中已知的对象。用户最初通过一个或多个配置文件使用 创建配置数据库。vinum 在每个受其控制的硬盘 设备 上存储其配置数据库的副本。该数据库在每次状态变化时更新,以便在重启时准确恢复每个 vinum 对象的状态。

在处理完该文件后, 会产生以下输出:

此输出显示了 的简要列出格式。图形化表示见于 。

以图形化方式展示了结构。

此卷在 中进行了表示。条带的深浅表示在 plex 地址空间中的位置,最浅的条带在前,最深的条带在后。

表示该卷的结构。

处理此文件后, 会在 /dev/gvinum 中创建以下结构:

卷对系统的表现与磁盘相同,唯一的例外是,vinum 不对卷进行分区,因此卷中没有分区表。这要求对一些磁盘工具进行修改,特别是 ,以防它尝试将 vinum 卷名的最后一个字母解释为分区标识符。例如,磁盘驱动器的名称可能是 /dev/ad0a 或 /dev/da2h。这些名称分别表示第一个(0)IDE 磁盘(ad)上的第一个分区(a)和第三个(2)SCSI 磁盘(da)上的第八个分区(h)。相比之下,vinum 卷可能被称为 /dev/gvinum/concat,它与分区名称没有任何关系。

要在此卷上创建文件系统,请使用 :

GENERIC 内核不包含 vinum。可以构建一个包含 vinum 的自定义内核,但不推荐这样做。启动 vinum 的标准方法是将其作为内核模块加载。因为 启动时会检查模块是否已加载,如果没有,它会自动加载模块,所以不需要使用 。

Gvinum 总是能够在内核模块加载后通过 实现自动启动。要在启动时加载 Gvinum 模块,请将 geom_vinum_load="YES" 添加到 /boot/loader.conf。

为了确保 vinum 在系统启动时能被加载,必须让 能够在启动内核之前加载 vinum 内核模块。可以通过在 /boot/loader.conf 文件中加入以下行来实现:

可以观察到,伪造的 a 分区的 size 参数与上面计算出的值相匹配,而 offset 参数是 vinum 分区 h 内的偏移量与该分区在设备或切片中的偏移量之和。这是避免 中描述的问题的典型设置。整个 a 分区完全位于包含所有 vinum 数据的 h 分区内。

如果 /boot/loader 无法加载,但主引导程序仍然加载(在启动过程开始后屏幕的左列可见一个破折号),可以尝试通过按空格键中断主引导程序。这样可以使引导程序停在 进行操作。在此阶段,可以尝试从备用分区启动,例如包含先前根文件系统的分区。

原文:

Internet Protocol Security (IPsec) 是一组协议,位于互联网协议(IP)层之上。它通过对每个通信会话的 IP 包进行认证和加密,使两个或多个主机能够以安全的方式进行通信。FreeBSD 的 IPsec 网络栈基于 实现,支持 IPv4 和 IPv6 会话。

IPsec 支持两种操作模式。第一种模式是 Transport Mode,保护两个主机之间的通信。第二种模式是 Tunnel Mode,用于建立虚拟隧道,通常称为虚拟专用网络(VPN)。有关 FreeBSD 中 IPsec 子系统的详细信息,请参考 。

首先,必须从 Ports Collection 安装 。该软件提供了多个支持配置的应用程序。

接下来的要求是创建两个 虚拟设备,用于隧道数据包并允许两个网络正确通信。作为 root 用户,在每个网关上运行以下命令:

完成后,使用 检查两个内部 IP 地址是否可以互通:

内部机器应该能够从每个网关以及网关后的机器上访问。同样,使用 来确认:

为了确保隧道正常工作,请切换到另一个控制台并使用 查看网络流量,使用以下命令。根据需要将 em0 替换为网络接口卡:

此时,两个网络应该都可用,并且看起来像是同一个网络。很可能这两个网络都受防火墙保护。为了允许流量在它们之间流动,需要在防火墙中添加规则以通过数据包。对于 防火墙,可以在防火墙配置文件中添加以下行:

对于 或 的用户,以下规则应该可以实现:

https://www.FreeBSD.org/status/
https://github.com/freebsd/freebsd-quarterly
status@FreeBSD.org
status-submissions@FreeBSD.org
freebsd-status-calls@FreeBSD.org
https://reviews.freebsd.org/project/88/
LDAP Authentication
net/nss_ldap
security/pam_ldap
RFC4510
OpenLDAP
net/openldap26-server
net/openldap26-client
LDAP 中的 OpenSSL 证书
net/openldap26-client
security/pam_ldap
net/nss_ldap
openssl(1)
sysutils/ldapvi
配置客户端
net/openldap26-client
security/pam_ldap
net/nss_ldap
security/pam_ldap
security/pam_ldap
security/pam_ldap
pam(d)
net/nss_ldap
security/pam_ldap
passwd(1)
Password Storage
security/pam_mkhomedir
sysutils/ldapvi
security/openssh-portable
openssl(1)
Legacy FreeBSD Release Engineering
FreeBSD 发布工程
1
FreeBSD 提交者
2
核心团队
3
4
https://download.freebsd.org/snapshots/VM-IMAGES/
xz(1)
https://www.freebsd.org/support/bugreports/
发布流程
发布构建
扩展性
FreeBSD 4.4 发布的经验教训
未来方向
提交者指南
发布工程团队
5
手册中的 Subversion 部分
md(4)
mdconfig(8)
release(7)
release(7)
release(7)
FreeBSD Ports Collection
bsdinstall(8)
bsdinstall(8)
release(7)
bsdinstall(8)
《Mirroring FreeBSD》
FreeBSD 镜像站点管理员
FreeBSD 镜像站点管理员
bsdinstall(8)
re@FreeBSD.org
re@FreeBSD.org
chroot(2)
asami@FreeBSD.org
steve@FreeBSD.org
bmah@FreeBSD.org
nik@FreeBSD.org
obrien@FreeBSD.org
kris@FreeBSD.org
jhb@FreeBSD.org
rgrimes@FreeBSD.org
phk@FreeBSD.org
Implementing UFS Journaling on a Desktop PC
gjournal(8)
gjournal(8)
gjournal(8)
FreeBSD 手册中的相关说明
FreeBSD 中日志记录的理解
FreeBSD 手册中的日志记录新章节
FreeBSD-CURRENT 邮件列表中的一篇帖子
gjournal(8)
pjd@FreeBSD.org
FreeBSD general questions 邮件列表中的一篇帖子
ivoras@FreeBSD.org
gjournal(8)
geom(8)
Introduction to NanoBSD
phk@FreeBSD.org
imp@FreeBSD.org
fsck(8)
md(4)
这里
dd(1)
dd(1)
boot0cfg(8)
FreeBSD Handbook
boot0cfg(8)
md(4)
newfs(8)
FreeBSD Handbook
getty(8)
sshd(8)
src.conf(5)
ftp(1)
ssh(1)
nc(1)
ftpd(8)
sshd(8)
Linux® emulation in FreeBSD
swapon(2)
mkfifo(2)
fork(2)
exec(3)
exit(2)
execve(2)
syscall(2)
getpid(2)
exit(3)
getppid(2)
pthread_create(3)
read(2)
write(2)
fork(2)
signal(3)
exit(3)
socket(2)
execve(2)
execve(2)
execve(2)
atomic(9)
sleepqueue(9)
mutex(9)
mutex(9)
mtx(pool)
sleep(9)
condvar(9)
rwlock(9)
sx(9)
lockmgr(9)
sema(9)
witness(4)
critical_enter(9)
critical_exit(9)
namei(9)
namei(9)
namei(9)
vn_fullpath(9)
vn_lock(9)
VOP_READDIR(9)
VOP_GETATTR(9)
VOP_LOOKUP(9)
VOP_OPEN(9)
VOP_CLOSE(9)
vput(9)
vrele(9)
vref(9)
close(2)
close(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
ptrace(2)
sysctl(8)
sysctl(8)
sysctl(8)
fork(2)
vfork(2)
fork(2)
vfork(2)
fork(2)
vfork(2)
pthread_key_create(3)
pthread_setspecific(3)
pthread_getspecific(3)
pthread_getspecific(3)
pthread_getspecific(3)
tsleep(9)
tsleep(9)
namei(9)
namei(9)
namei(9)
www/linux-firefox
net-im/skype
jhb@FreeBSD.org
kib@FreeBSD.org
jkim@FreeBSD.org
netchild@FreeBSD.org
ssouhlal@FreeBSD.org
davidxu@FreeBSD.org
https://tldp.org
https://www.kernel.org
Independent Verification of IPsec Functionality in FreeBSD
安装 IPsec
警告
netstat(1)
MUST
Maurer’s Universal Statistical Test (对于块大小8位)
tcpdump(1)
src/sys/i386/conf/KERNELNAME
Tcpdump
yes(1)
Maurer’s Universal Statistical Test (对于块大小8位)
setkey(8)
FreeBSD 手册
tcpdump(1)
config(8)
此链接
Mirroring FreeBSD
mirror-admin@FreeBSD.org
FreeBSD 镜像站点邮件列表
ftp://ftp.FreeBSD.org/pub/FreeBSD/dir.sizes
ftpd(8)
ftp/ncftpd
ftp/oftpd
ftp/proftpd
ftp/pure-ftpd
ftp/twoftpd
ftp/vsftpd
net/rsync
www/apache24
www/boa
www/cherokee
www/lighttpd
www/nginx
www/thttpd
net/rsync
Rsync (可选的 FTP 文件集)
http://rsync.samba.org/
cron(8)
官方基础设施
FreeBSD Documentation Project Primer for New Contributors
www.FreeBSD.org
pkg(8)
pkg(8)
pkg-repo(8)
cron(8)
crontab(1)
crontab(5)
FreeBSD 镜像要求
《好的,那我应该从哪里获取镜像?》
《我只想从某个地方镜像!》
《官方镜像》
hostmaster@CC.FreeBSD.org
FreeBSD 镜像站点邮件列表
mirror-admin@FreeBSD.org
镜像 FTP 站点
FreeBSD 镜像站点邮件列表
此链接
发布计划
https://www.FreeBSD.org/administration/#t-clusteradm
hostmaster@is.FreeBSD.org
(带宽)
(FTP 进程)
(HTTP 进程)
mirror@macomnet.ru
(带宽)
(HTTP 和 FTP 用户)
Port Mentor Guidelines
Porter’s Handbook
PR 处理指南
Committer’s Guide
第一个记录的共同导师提交
Remote Installation of the FreeBSD Operating System Without a Remote Console
mm@FreeBSD.org
pjd@FreeBSD.org
背景
rc.conf(5)
re(4)
mdconfig(8)
ping(8)
ssh(1)
ssh(1)
fdisk(8)
bsdlabel(8)
fdisk(8)
bsdlabel(8)
安装 FreeBSD
gmirror(8)
zpool(8)
gmirror(8)
bsdlabel(8)
gmirror(8)
gmirror(8)
sysinstall(8)
chroot(8)
adduser(8)
reboot(8)
zpool(8)
zfs(8)
ZFS
Pluggable Authentication Modules
su(1)
su(1)
su(1)
ssh(1)
ssh(1)
sshd(8)
sshd(8)
pam_authenticate(3)
pam_setcred(3)
pam_acct_mgmt(3)
pam_open_session(3)
pam_close_session(3)
pam_chauthtok(3)
pam_start(3)
pam_start(3)
pam_set_item(3)
pam_authenticate(3)
pam_acct_mgmt(3)
pam_acct_mgmt(3)
pam_chauthtok(3)
pam_setcred(3)
pam_open_session(3)
pam_close_session(3)
pam_end(3)
PAM 策略文件
功能和原语
链和策略
pam_start(3)
pam_setcred(3)
pam_chauthtok(3)
pam_deny(8)
pam_deny(8)
pam_echo(8)
pam_echo(8)
pam_exec(8)
pam_exec(8)
pam_ftpusers(8)
pam_ftpusers(8)
pam_group(8)
pam_group(8)
su(1)
su(1)
pam_guest(8)
pam_guest(8)
pam_krb5(8)
pam_krb5(8)
pam_ksu(8)
pam_ksu(8)
pam_lastlog(8)
pam_lastlog(8)
pam_login_access(8)
pam_login_access(8)
login.access(5)
pam_nologin(8)
pam_nologin(8)
shutdown(8)
pam_passwdqc(8)
pam_passwdqc(8)
pam_permit(8)
pam_permit(8)
pam_radius(8)
pam_radius(8)
pam_rhosts(8)
pam_rhosts(8)
pam_rootok(8)
pam_rootok(8)
su(1)
passwd(1)
pam_securetty(8)
pam_securetty(8)
pam_self(8)
pam_self(8)
su(1)
pam_ssh(8)
pam_ssh(8)
ssh-agent(1)
xdm(8)
pam_tacplus(8)
pam_tacplus(8)
pam_unix(8)
pam_unix(8)
getpwnam(3)
su(1)
openpam_ttyconv(3)
示例 PAM 对话功能
pam_unix(8)
pam_get_authtok(3)
openpam_ttyconv(3)
openpam_ttyconv(3)
X/Open 单点登录初步规范
可插拔认证模块
PAM 管理
OpenPAM主页
Linux-PAM主页
Practical rc.d scripting in BSD
init(8)
sh(1)
sh(1)
sh(1)
sh(1)
rcorder(8)
Luke Mewburn 的原始文章
相关手册页
sh(1)
rc(8)
sh(1)
sh(1)
sh(1)
sh(1)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
sh(1)
rc.subr(8)
rc.subr(8)
service jails
sh(1)
sh(1)
rc.subr(8)
rc.subr(8)
sh(1)
rc.subr(8)
rc.subr(8)
sh(1)
rc.subr(8)
rc.subr(8)
rc.conf(5)
rc.conf(5)
rc.subr(8)
rc.conf(5)
rc.subr(8)
rc.conf(5)
rc.subr(8)
rc.conf(5)
rc.subr(8)
rc.conf(5)
sh(1)
rc.conf(5)
rc.conf(5)
rc.subr(8)
rc.conf(5)
rc.conf(5)
rc.conf(5)
rc.subr(8)
rc.conf(5)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.conf(5)
rc.subr(8)
sh(1)
rc.conf(5)
rc.subr(8)
如后文所示
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
kill(1)
sh(1)
sh(1)
rc.subr(8)
rc.subr(8)
devd(8)
devd.conf(5)
rc.subr(8)
rc.subr(8)
rc.subr(8)
sh(1)
rc.subr(8)
Porter’s Handbook
rcorder(8)
sh(1)
rc.conf(5)
rcorder(8)
rc(8)
rc.conf(5)
rcorder(8)
rc(8)
下文
rcorder(8)
rcorder(8)
rcorder(8)
rcorder(8)
jail(8)
halt(8)
rcorder(8)
rc.conf(5)
rc.conf(5)
rc.conf(5)
rc.subr(8)
rc.subr(8)
rc.subr(8)
rc.subr(8)
echo(1)
sh(1)
sh(1)
Service Jails
rc.conf(5)
rc.conf(5)
rc.conf(5)
basename(1)
Luke Mewburn 的原始文章
rc(8)
rc.subr(8)
rcorder(8)
rc.subr(8)
未分配的 PR
已分配的 PR
重复的 PR
陈旧的 PR
非 Bug 的 PR
options INVARIANT_SUPPORT
options INVARIANTS
options WITNESS_SUPPORT
options WITNESS
makeoptions    DEBUG=-g
options KDB
options DDB
options KDB_TRACE
debug.debugger_on_panic=1
kern.filedelay=5
kern.dirdelay=4
kern.metadelay=3
dumpdev="/dev/ad0s4b"
dumpdir="/usr/core
hw.physmem="256M"
background_fsck="NO"
SRCS=g_journal.c
KMOD=geom_journal

.include <bsd.kmod.mk>
static MALLOC_DEFINE(M_GJOURNAL, "gjournal data", "GEOM_JOURNAL Data");
verb [-options] geomname [other]

1

-

AA

101

PG/FG

-

框架/保护接地

2

3

BA

103

TD

DTE

传输数据

3

2

BB

104

RD

DCE

接收数据

4

7

CA

105

RTS

DTE

请求发送

5

8

CB

106

CTS

DCE

清除发送

6

6

CC

107

DSR

DCE

数据集准备

7

5

AV

102

SG/GND

-

信号接地

8

1

CF

109

DCD/CD

DCE

数据载体检测

9

-

-

-

-

-

测试保留

10

-

-

-

-

-

测试保留

11

-

-

-

-

-

测试保留

12

-

CI

122

SRLSD

DCE

次级接收线路信号检测器

13

-

SCB

121

SCTS

DCE

次级清除发送

14

-

SBA

118

STD

DTE

次级传输数据

15

-

DB

114

TSET

DCE

传输信号元时序

16

-

SBB

119

SRD

DCE

次级接收数据

17

-

DD

115

RSET

DCE

接收信号元时序

18

-

-

141

LOOP

DTE

本地回环

19

-

SCA

120

SRS

DTE

次级请求发送

20

4

CD

108.2

DTR

DTE

数据终端准备

21

-

-

-

RDL

DTE

远程数字回环

22

9

CE

125

RI

DCE

振铃指示器

23

-

CH

111

DSRS

DTE

数据信号速率选择器

24

-

DA

113

TSET

DTE

传输信号元时序

25

-

-

142

-

DCE

测试模式

INS8250  -> INS8250B
  \
   \
    \-> INS8250A -> INS82C50A
             \
              \
               \-> NS16450 -> NS16C450
                        \
                         \
                          \-> NS16550 -> NS16550A -> PC16550D

"N"

DIP

(双列直插封装) 直插孔引脚类型

"V"

LPCC

(塑料芯片载体封装) J 引脚类型

National

(PC16550DV)

0

National

(NS16550AFN)

0

National

(NS16C552V)

0

TI

(TL16550AFN)

3

CMD

(16C550PE)

19

StarTech

(ST16C550J)

23

Rockwell

内部包含 16550 或仿真(RC144DPi/C3000-25)

117

Sierra

内部包含 16550 的调制解调器(SC11951/SC11351)

91

Error (6)...Timeout interrupt failed: IIR = c1  LSR = 61

+0x00

写入 (DLAB==0)

传输保持寄存器 (THR)。写入此端口的信息被视为数据字,并由 UART 传输。

+0x00

读取 (DLAB==0)

接收缓冲寄存器 (RBR)。由 UART 从串行链路接收到的任何数据字都通过读取此端口由主机访问。

+0x00

写入/读取 (DLAB==1)

除数锁存 LSB (DLL)。此值将从主输入时钟(在 IBM PC 中,主时钟为 1.8432MHz)进行除法,结果时钟将决定 UART 的波特率。此寄存器保存除数的第 0 至 7 位。

+0x01

写入/读取 (DLAB==1)

除数锁存 MSB (DLH)。此值将从主输入时钟(在 IBM PC 中,主时钟为 1.8432MHz)进行除法,结果时钟将决定 UART 的波特率。此寄存器保存除数的第 8 至 15 位。

+0x01

写入/读取 (DLAB==0)

中断使能寄存器 (IER) 8250/16450/16550 UART 将事件分类为四个类别中的一个。每个类别可以配置为在发生任何事件时生成中断。8250/16450/16550 UART 会生成一个外部中断信号,无论启用类别中有多少事件发生。由主机处理器响应中断,然后轮询启用的中断类别(通常所有类别都有中断启用),以确定中断的真正原因。 位 7 → 保留,总是 0。 位 6 → 保留,总是 0。 位 5 → 保留,总是 0。 位 4 → 保留,总是 0。 位 3 → 启用调制解调器状态中断 (EDSSI)。将此位设置为 "1" 允许 UART 在状态行之一或多个发生变化时生成中断。 位 2 → 启用接收线状态中断 (ELSI)。将此位设置为 "1" 时,UART 会在检测到传入数据中的错误(或 BREAK 信号)时生成中断。 位 1 → 启用传输保持寄存器空中断 (ETBEI)。将此位设置为 "1" 时,UART 会在 UART 有足够空间容纳一个或多个额外字符以便传输时生成中断。 位 0 → 启用接收数据可用中断 (ERBFI)。将此位设置为 "1" 时,UART 会在接收到足够的字符超过 FIFO 的触发级别,或者 FIFO 定时器到期(数据过时),或当 FIFO 被禁用时接收到一个字符时生成中断。

+0x02

写入

FIFO 控制寄存器 (FCR)(此端口在 8250 和 16450 UART 中不存在。) 位 7 → 接收触发位 #1 位 6 → 接收触发位 #0 这两个位控制在 FIFO 激活时接收器生成中断的时机。 7 6 在生成中断之前接收多少字 0 0 1 0 1 4 1 0 8 1 1 14 位 5 → 保留,总是 0。 位 4 → 保留,总是 0。 位 3 → DMA 模式选择。如果位 0 设置为 "1"(启用 FIFO),设置此位会将 -RXRDY 和 -TXRDY 信号的操作从模式 0 改为模式 1。 位 2 → 传输 FIFO 重置。当写入 "1" 时,FIFO 的内容将被丢弃。当前正在传输的任何字将完整发送。此功能在中止传输时非常有用。 位 1 → 接收 FIFO 重置。当写入 "1" 时,FIFO 的内容将被丢弃。当前在移位寄存器中正在组装的任何字将完整接收。 位 0 → 16550 FIFO 启用。当设置时,传输和接收 FIFO 都会启用。当启用或禁用 FIFO 时,保持寄存器、移位寄存器或 FIFO 中的任何内容都会丢失。

+0x02

读取

中断标识寄存器 位 7 → FIFO 启用。在 8250/16450 UART 上,此位为 0。 位 6 → FIFO 启用。在 8250/16450 UART 上,此位为 0。 位 5 → 保留,总是 0。 位 4 → 保留,总是 0。 位 3 → 中断 ID 位 #2。在 8250/16450 UART 上,此位为 0。 位 2 → 中断 ID 位 #1 位 1 → 中断 ID 位 #0。这三个位组合报告了导致当前中断的事件类别。这些类别有优先级,因此如果多个类别的事件同时发生,UART 将首先报告更重要的事件,主机必须按报告的顺序解决这些事件。所有导致当前中断的事件必须解决后,才会生成新的中断。(这是 PC 架构的限制。) 2 1 0 优先级 描述 0 1 1 第一接收错误(OE,PE,BI 或 FE) 0 1 0 第二接收数据可用 1 1 0 第二触发级别识别(接收缓冲区中的过时数据) 0 0 1 第三传输器有空间传输更多字(THRE) 0 0 0 第四调制解调器状态变化(-CTS,-DSR,-RI 或 -DCD) 位 0 → 中断待处理位。如果此位设置为 "0",则至少有一个中断待处理。

+0x03

写入/读取

行控制寄存器 (LCR) 位 7 → 除数锁存访问位 (DLAB)。当设置时,禁用对数据传输/接收寄存器 (THR/RBR) 和中断使能寄存器 (IER) 的访问。对这些端口的任何访问现在都将重定向到除数锁存寄存器。设置此位、加载除数寄存器并清除 DLAB 时,应禁用中断。 位 6 → 设置断开。当设置为 "1" 时,传输器开始传输连续间隔,直到此位设置为 "0"。这会覆盖正在传输的任何字符位。 位 5 → 固定奇偶校验。当启用奇偶校验时,设置此位会根据位 4 的值始终将奇偶校验设置为 "1" 或 "0"。 位 4 → 偶校验选择 (EPS)。当启用奇偶校验且位 5 为 "0" 时,设置此位会传输并期望偶校验。否则,使用奇校验。 位 3 → 奇偶校验使能 (PEN)。当设置为 "1" 时,在数据的最后一位和停止位之间插入一个奇偶校验位。UART 还会期望接收到的数据中存在奇偶校验位。 位 2 → 停止位数 (STB)。如果设置为 "1" 且使用 5 位数据字,则每个数据字传输并期望 1.5 个停止位。对于 6、7 和 8 位数据字,则传输并期望 2 个停止位。当此位设置为 "0" 时,每个数据字使用 1 个停止位。 位 1 → 字长选择位 #1 (WLSB1) 位 0 → 字长选择位 #0 (WLSB0) 这两位一起指定每个数据字的位数。 1 0 字长 0 0 5 数据位 0 1 6 数据位 1 0 7 数据位 1 1 8 数据位

+0x04

写入/读取

调制解调器控制寄存器 (MCR) 位 7 → 保留,总是 0。 位 6 → 保留,总是 0。 位 5 → 保留,总是 0。 位 4 → 环回使能。当设置为 "1" 时,UART 的传输器和接收器内部连接在一起,以允许诊断操作。此外,UART 调制解调器控制输出连接到 UART 调制解调器控制输入。CTS 连接到 RTS,DTR 连接到 DSR,OUT1 连接到 RI,OUT 2 连接到 DCD。 位 3 → OUT 2。一个辅助输出,主机处理器可以将其设置为高或低。在 IBM PC 串行适配器(以及大多数克隆机)中,OUT 2 用于三态(禁用)来自 8250/16450/16550 UART 的中断信号。 位 2 → OUT 1。一个辅助输出,主机处理器可以将其设置为高或低。在 IBM PC 串行适配器中未使用此输出。 位 1 → 请求发送 (RTS)。当设置为 "1" 时,UART 的 -RTS 输出为低(活动)。 位 0 → 数据终端就绪 (DTR)。当设置为 "1" 时,UART 的 -DTR 输出为低(活动)。

+0x05

写入/读取

行状态寄存器 (LSR) 位 7 → 接收 FIFO 错误。在 8250/16450 UART 上,此位为 0。当 FIFO 中的任何字节存在以下一个或多个错误条件时,此位设置为 "1":PE、FE 或 BI。 位 6 → 传输器空 (TEMT)。当设置为 "1" 时,传输 FIFO 或传输移位寄存器中没有剩余的字。传输器完全空闲。 位 5 → 传输保持寄存器空 (THRE)。当设置为 "1" 时,FIFO(或保持寄存器)现在有足够的空间传输至少一个额外的字。传输器可能仍在传输时,此位为 "1"。 位 4 → 断开中断 (BI)。接收器检测到一个断开信号。 位 3 → 帧错误 (FE)。检测到开始位,但停止位未按预期时间出现。接收到的字可能被破坏。 位 2 → 奇偶校验错误 (PE)。接收到的字的奇偶校验位不正确。 位 1 → 溢出错误 (OE)。接收到一个新字,但接收缓冲区没有空间。移位寄存器中的新到字被丢弃。在 8250/16450 UART 上,保持寄存器中的字被丢弃,新到字被放入保持寄存器。 位 0 → 数据就绪 (DR)。接收 FIFO 中有一个或多个字,主机可以读取。在此位设置之前,必须完全接收一个字,并将其从移位寄存器移动到 FIFO(或对于 8250/16450 设计,移动到保持寄存器)。

+0x06

写入/读取

调制解调器状态寄存器 (MSR) 位 7 → 数据载波检测 (DCD)。反映 UART 上 DCD 线的状态。 位 6 → 振铃指示 (RI)。反映 UART 上 RI 线的状态。 位 5 → 数据集就绪 (DSR)。反映 UART 上 DSR 线的状态。 位 4 → 清除发送 (CTS)。反映 UART 上 CTS 线的状态。 位 3 → 数据载波检测变化 (DDCD)。如果 -DCD 线自上次主机读取 MSR 后发生过状态变化,则设置为 "1"。 位 2 → 振铃指示变化 (TERI)。如果 -RI 线自上次主机读取 MSR 后经历了低到高的变化,则设置为 "1"。 位 1 → 数据集就绪变化 (DDSR)。如果 -DSR 线自上次主机读取 MSR 后发生过状态变化,则设置为 "1"。 位 0 → 清除发送变化 (DCTS)。如果 -CTS 线自上次主机读取 MSR 后发生过状态变化,则设置为 "1"。

+0x07

写入/读取

临时寄存器 (SCR)。此寄存器在 UART 中没有任何功能。主机可以将任何值写入此位置,并在之后从该位置读取。

device          sio4    at isa? port 0x100 flags 0xb05
device          sio5    at isa? port 0x108 flags 0xb05
device          sio6    at isa? port 0x110 flags 0xb05
device          sio7    at isa? port 0x118 flags 0xb05
device          sio8    at isa? port 0x120 flags 0xb05
device          sio9    at isa? port 0x128 flags 0xb05
device          sio10   at isa? port 0x130 flags 0xb05
device          sio11   at isa? port 0x138 flags 0xb05 irq 9
options COM_MULTIPORT
device sio1 at isa? port 0x100 flags 0x1005
device sio2 at isa? port 0x108 flags 0x1005
device sio3 at isa? port 0x110 flags 0x1005
device sio4 at isa? port 0x118 flags 0x1005
...
device sio15 at isa? port 0x170 flags 0x1005
device sio16 at isa? port 0x178 flags 0x1005 irq 3
flags
       0x1005
sio1 at 0x100-0x107 flags 0x1005 on isa
sio1: type 16550A (multiport)
sio2 at 0x108-0x10f flags 0x1005 on isa
sio2: type 16550A (multiport)
sio3 at 0x110-0x117 flags 0x1005 on isa
sio3: type 16550A (multiport)
sio4 at 0x118-0x11f flags 0x1005 on isa
sio4: type 16550A (multiport)
sio5 at 0x120-0x127 flags 0x1005 on isa
sio5: type 16550A (multiport)
sio6 at 0x128-0x12f flags 0x1005 on isa
sio6: type 16550A (multiport)
sio7 at 0x130-0x137 flags 0x1005 on isa
sio7: type 16550A (multiport)
sio8 at 0x138-0x13f flags 0x1005 on isa
sio8: type 16550A (multiport)
sio9 at 0x140-0x147 flags 0x1005 on isa
sio9: type 16550A (multiport)
sio10 at 0x148-0x14f flags 0x1005 on isa
sio10: type 16550A (multiport)
sio11 at 0x150-0x157 flags 0x1005 on isa
sio11: type 16550A (multiport)
sio12 at 0x158-0x15f flags 0x1005 on isa
sio12: type 16550A (multiport)
sio13 at 0x160-0x167 flags 0x1005 on isa
sio13: type 16550A (multiport)
sio14 at 0x168-0x16f flags 0x1005 on isa
sio14: type 16550A (multiport)
sio15 at 0x170-0x177 flags 0x1005 on isa
sio15: type 16550A (multiport)
sio16 at 0x178-0x17f irq 3 flags 0x1005 on isa
sio16: type 16550A (multiport master)
# dmesg | more
# cd /dev
# ./MAKEDEV tty1
# ./MAKEDEV cua1

(中间的其他条目)
# ./MAKEDEV ttyg
# ./MAKEDEV cuag
# echo at > ttyd*
o  o  o  *
Port A               |
            o  *  o  *
Port B         |
            o  *  o  o
IRQ         2  3  4  5
Diode
                +---------->|-------+
               /                    |
            o  *  o  o              |     1 kOhm
Port A                              +----|######|-------+
            o  *  o  o              |                   |
Port B          `-------------------+                 ==+==
            o  *  o  o              |                 Ground
                \                   |
                 +--------->|-------+
IRQ         2  3  4  5    Diode
# 标准的板载 COM1 端口
device          sio0    at isa? port "IO_COM1" flags 0x10
# 修补过的多功能 I/O 扩展板
options         COM_MULTIPORT
device          sio1    at isa? port "IO_COM2" flags 0x205
device          sio2    at isa? port "IO_COM3" flags 0x205 irq 3
sio0: irq maps: 0x1 0x11 0x1 0x1
sio0 at 0x3f8-0x3ff irq 4 flags 0x10 on isa
sio0: type 16550A
sio1: irq maps: 0x1 0x9 0x1 0x1
sio1 at 0x2f8-0x2ff flags 0x205 on isa
sio1: type 16550A (multiport)
sio2: irq maps: 0x1 0x9 0x1 0x1
sio2 at 0x3e8-0x3ef irq 3 flags 0x205 on isa
sio2: type 16550A (multiport master)
device cy0 at isa? irq 10 iomem 0xd4000 iosiz 0x2000
# cd /dev
# for i in 0 1 2 3 4 5 6 7;do ./MAKEDEV cuac$i ttyc$i;done
ttyc0   "/usr/libexec/getty std.38400"  unknown on insecure
ttyc1   "/usr/libexec/getty std.38400"  unknown on insecure
ttyc2   "/usr/libexec/getty std.38400"  unknown on insecure
...
ttyc7   "/usr/libexec/getty std.38400"  unknown on insecure
device si0 at isa? iomem 0xd0000 irq 11
device si0
# cd /dev
# ./MAKEDEV ttyAnn cuaAnn
ttyA01  "/usr/libexec/getty std.9600"   vt100   on insecure
# pkg install ccls
# pkg install llvm15
# mkdir -p ~/.config/nvim/pack/lsp/start
# git clone https://github.com/prabirshrestha/vim-lsp ~/.config/nvim/pack/lsp/start/vim-lsp
# mkdir -p ~/.vim/pack/lsp/start
# git clone https://github.com/prabirshrestha/vim-lsp ~/.vim/pack/lsp/start/vim-lsp
au User lsp_setup call lsp#register_server({
    \ 'name': 'ccls',
    \ 'cmd': {server_info->['ccls']},
    \ 'allowlist': ['c', 'cpp', 'objc'],
    \ 'initialization_options': {
    \     'cache': {
    \         'hierarchicalPath': v:true
    \     }
    \ }})
au User lsp_setup call lsp#register_server({
    \ 'name': 'clangd',
    \ 'cmd': {server_info->['clangd15', '--background-index', '--header-insertion=never']},
    \ 'allowlist': ['c', 'cpp', 'objc'],
    \ 'initialization_options': {},
    \ })
function! s:on_lsp_buffer_enabled() abort
    setlocal omnifunc=lsp#complete
    setlocal completeopt-=preview
    setlocal keywordprg=:LspHover

    nmap <buffer> <C-]> <plug>(lsp-definition)
    nmap <buffer> <C-W>] <plug>(lsp-peek-definition)
    nmap <buffer> <C-W><C-]> <plug>(lsp-peek-definition)
    nmap <buffer> gr <plug>(lsp-references)
    nmap <buffer> <C-n> <plug>(lsp-next-reference)
    nmap <buffer> <C-p> <plug>(lsp-previous-reference)
    nmap <buffer> gI <plug>(lsp-implementation)
    nmap <buffer> go <plug>(lsp-document-symbol)
    nmap <buffer> gS <plug>(lsp-workspace-symbol)
    nmap <buffer> ga <plug>(lsp-code-action)
    nmap <buffer> gR <plug>(lsp-rename)
    nmap <buffer> gm <plug>(lsp-signature-help)
endfunction

augroup lsp_install
    au!
    autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
[
    /* 现有配置开始 */
    ...
    /* 现有配置结束 */
    "clangd.arguments": [
        "--background-index",
        "--header-insertion=never"
    ],
    "clangd.path": "clangd12"
]
[
    /* 现有配置开始 */
    ...
    /* 现有配置结束 */
    "ccls.cache.hierarchicalPath": true
]
# git clone https://github.com/llvm/llvm-project /path/to/llvm-project
alias intercept-build='/path/to/llvm-project/clang/tools/scan-build-py/bin/intercept-build'
# intercept-build --append make buildworld buildkernel -j`sysctl -n hw.ncpu`
# bear --append -- make buildworld buildkernel -j`sysctl -n hw.ncpu`

连接式

1

是

否

大型数据存储,具有最大灵活性和适度性能

条带化

2

否

是

高性能,适用于高度并发访问

drive a device /dev/da3h
    volume myvol
      plex org concat
        sd length 512m drive a
#  gvinum -> create config1
Configuration summary
Drives:         1 (4 configured)
Volumes:        1 (4 configured)
Plexes:         1 (8 configured)
Subdisks:       1 (16 configured)

  D a                     State: up       Device /dev/da3h      Avail: 2061/2573 MB (80%)

  V myvol                 State: up       Plexes:       1 Size:      512 MB

  P myvol.p0            C State: up       Subdisks:     1 Size:      512 MB

  S myvol.p0.s0           State: up       PO:        0  B Size:      512 MB
drive b device /dev/da4h
	volume mirror
      plex org concat
        sd length 512m drive a
	  plex org concat
	    sd length 512m drive b
Drives:         2 (4 configured)
	Volumes:        2 (4 configured)
	Plexes:         3 (8 configured)
	Subdisks:       3 (16 configured)

	D a                     State: up       Device /dev/da3h       Avail: 1549/2573 MB (60%)
	D b                     State: up       Device /dev/da4h       Avail: 2061/2573 MB (80%)

    V myvol                 State: up       Plexes:       1 Size:        512 MB
    V mirror                State: up       Plexes:       2 Size:        512 MB

    P myvol.p0            C State: up       Subdisks:     1 Size:        512 MB
    P mirror.p0           C State: up       Subdisks:     1 Size:        512 MB
    P mirror.p1           C State: initializing     Subdisks:     1 Size:        512 MB

    S myvol.p0.s0           State: up       PO:        0  B Size:        512 MB
	S mirror.p0.s0          State: up       PO:        0  B Size:        512 MB
	S mirror.p1.s0          State: empty    PO:        0  B Size:        512 MB
drive c device /dev/da5h
	drive d device /dev/da6h
	volume stripe
	plex org striped 512k
	  sd length 128m drive a
	  sd length 128m drive b
	  sd length 128m drive c
	  sd length 128m drive d
Drives:         4 (4 configured)
	Volumes:        3 (4 configured)
	Plexes:         4 (8 configured)
	Subdisks:       7 (16 configured)

    D a                     State: up       Device /dev/da3h        Avail: 1421/2573 MB (55%)
    D b                     State: up       Device /dev/da4h        Avail: 1933/2573 MB (75%)
    D c                     State: up       Device /dev/da5h        Avail: 2445/2573 MB (95%)
    D d                     State: up       Device /dev/da6h        Avail: 2445/2573 MB (95%)

    V myvol                 State: up       Plexes:       1 Size:        512 MB
    V mirror                State: up       Plexes:       2 Size:        512 MB
    V striped               State: up       Plexes:       1 Size:        512 MB

    P myvol.p0            C State: up       Subdisks:     1 Size:        512 MB
    P mirror.p0           C State: up       Subdisks:     1 Size:        512 MB
    P mirror.p1           C State: initializing     Subdisks:     1 Size:        512 MB
    P striped.p1            State: up       Subdisks:     1 Size:        512 MB

    S myvol.p0.s0           State: up       PO:        0  B Size:        512 MB
    S mirror.p0.s0          State: up       PO:        0  B Size:        512 MB
    S mirror.p1.s0          State: empty    PO:        0  B Size:        512 MB
    S striped.p0.s0         State: up       PO:        0  B Size:        128 MB
    S striped.p0.s1         State: up       PO:      512 kB Size:        128 MB
    S striped.p0.s2         State: up       PO:     1024 kB Size:        128 MB
    S striped.p0.s3         State: up       PO:     1536 kB Size:        128 MB
volume raid10
      plex org striped 512k
        sd length 102480k drive a
        sd length 102480k drive b
        sd length 102480k drive c
        sd length 102480k drive d
        sd length 102480k drive e
      plex org striped 512k
        sd length 102480k drive c
        sd length 102480k drive d
        sd length 102480k drive e
        sd length 102480k drive a
        sd length 102480k drive b
drive drive1 device /dev/sd1h
	drive drive2 device /dev/sd2h
	drive drive3 device /dev/sd3h
	drive drive4 device /dev/sd4h
    volume s64 setupstate
      plex org striped 64k
        sd length 100m drive drive1
        sd length 100m drive drive2
        sd length 100m drive drive3
        sd length 100m drive drive4
drwxr-xr-x  2 root  wheel       512 Apr 13
16:46 plex
	crwxr-xr--  1 root  wheel   91,   2 Apr 13 16:46 s64
	drwxr-xr-x  2 root  wheel       512 Apr 13 16:46 sd

    /dev/vinum/plex:
    total 0
    crwxr-xr--  1 root  wheel   25, 0x10000002 Apr 13 16:46 s64.p0

    /dev/vinum/sd:
    total 0
    crwxr-xr--  1 root  wheel   91, 0x20000002 Apr 13 16:46 s64.p0.s0
    crwxr-xr--  1 root  wheel   91, 0x20100002 Apr 13 16:46 s64.p0.s1
    crwxr-xr--  1 root  wheel   91, 0x20200002 Apr 13 16:46 s64.p0.s2
    crwxr-xr--  1 root  wheel   91, 0x20300002 Apr 13 16:46 s64.p0.s3
# newfs /dev/gvinum/concat
volume myvol state up
volume bigraid state down
plex name myvol.p0 state up org concat vol myvol
plex name myvol.p1 state up org concat vol myvol
plex name myvol.p2 state init org striped 512b vol myvol
plex name bigraid.p0 state initializing org raid5 512b vol bigraid
sd name myvol.p0.s0 drive a plex myvol.p0 state up len 1048576b driveoffset 265b plexoffset 0b
sd name myvol.p0.s1 drive b plex myvol.p0 state up len 1048576b driveoffset 265b plexoffset 1048576b
sd name myvol.p1.s0 drive c plex myvol.p1 state up len 1048576b driveoffset 265b plexoffset 0b
sd name myvol.p1.s1 drive d plex myvol.p1 state up len 1048576b driveoffset 265b plexoffset 1048576b
sd name myvol.p2.s0 drive a plex myvol.p2 state init len 524288b driveoffset 1048841b plexoffset 0b
sd name myvol.p2.s1 drive b plex myvol.p2 state init len 524288b driveoffset 1048841b plexoffset 524288b
sd name myvol.p2.s2 drive c plex myvol.p2 state init len 524288b driveoffset 1048841b plexoffset 1048576b
sd name myvol.p2.s3 drive d plex myvol.p2 state init len 524288b driveoffset 1048841b plexoffset 1572864b
sd name bigraid.p0.s0 drive a plex bigraid.p0 state initializing len 4194304b driveoff set 1573129b plexoffset 0b
sd name bigraid.p0.s1 drive b plex bigraid.p0 state initializing len 4194304b driveoff set 1573129b plexoffset 4194304b
sd name bigraid.p0.s2 drive c plex bigraid.p0 state initializing len 4194304b driveoff set 1573129b plexoffset 8388608b
sd name bigraid.p0.s3 drive d plex bigraid.p0 state initializing len 4194304b driveoff set 1573129b plexoffset 12582912b
sd name bigraid.p0.s4 drive e plex bigraid.p0 state initializing len 4194304b driveoff set 1573129b plexoffset 16777216b
geom_vinum_load="YES"
# gvinum l -rv root
# bsdlabel -e devname
# fsck -n /dev/devnamea
Mounting root from ufs:/dev/gvinum/root
...
Subdisk root.p0.s0:
		Size:        125829120 bytes (120 MB)
		State: up
		Plex root.p0 at offset 0 (0  B)
		Drive disk0 (/dev/da0h) at offset 135680 (132 kB)

Subdisk root.p1.s0:
		Size:        125829120 bytes (120 MB)
		State: up
		Plex root.p1 at offset 0 (0  B)
		Drive disk1 (/dev/da1h) at offset 135680 (132 kB)
...
8 partitions:
#        size   offset    fstype   [fsize bsize bps/cpg]
  a:   245760      281    4.2BSD     2048 16384     0  # (Cyl.    0*- 15*)
  c: 71771688        0    unused        0     0        # (Cyl.    0 - 4467*)
  h: 71771672       16     vinum                       # (Cyl.    0*- 4467*)
           公司                          家
10.246.38.1/24 -- 172.16.5.4 <--> 192.168.1.12 -- 10.0.0.5/24
corp-gw# ifconfig gif0 create
corp-gw# ifconfig gif0 10.246.38.1 10.0.0.5
corp-gw# ifconfig gif0 tunnel 172.16.5.4 192.168.1.12
home-gw# ifconfig gif0 create
home-gw# ifconfig gif0 10.0.0.5 10.246.38.1
home-gw# ifconfig gif0 tunnel 192.168.1.12 172.16.5.4
gif0: flags=8051 mtu 1280
tunnel inet 172.16.5.4 --> 192.168.1.12
inet6 fe80::2e0:81ff:fe02:5881%gif0 prefixlen 64 scopeid 0x6
inet 10.246.38.1 --> 10.0.0.5 netmask 0xffffff00
gif0: flags=8051 mtu 1280
tunnel inet 192.168.1.12 --> 172.16.5.4
inet 10.0.0.5 --> 10.246.38.1 netmask 0xffffff00
inet6 fe80::250:bfff:fe3a:c1f%gif0 prefixlen 64 scopeid 0x4
home-gw# ping 10.0.0.5
PING 10.0.0.5 (10.0.0.5): 56 data bytes
64 bytes from 10.0.0.5: icmp_seq=0 ttl=64 time=42.786 ms
64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=19.255 ms
64 bytes from 10.0.0.5: icmp_seq=2 ttl=64 time=20.440 ms
64 bytes from 10.0.0.5: icmp_seq=3 ttl=64 time=21.036 ms
--- 10.0.0.5 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 19.255/25.879/42.786/9.782 ms

corp-gw# ping 10.246.38.1
PING 10.246.38.1 (10.246.38.1): 56 data bytes
64 bytes from 10.246.38.1: icmp_seq=0 ttl=64 time=28.106 ms
64 bytes from 10.246.38.1: icmp_seq=1 ttl=64 time=42.917 ms
64 bytes from 10.246.38.1: icmp_seq=2 ttl=64 time=127.525 ms
64 bytes from 10.246.38.1: icmp_seq=3 ttl=64 time=119.896 ms
64 bytes from 10.246.38.1: icmp_seq=4 ttl=64 time=154.524 ms
--- 10.246.38.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 28.106/94.594/154.524/49.814 ms
corp-gw# route add 10.0.0.0 10.0.0.5 255.255.255.0
corp-gw# route add net 10.0.0.0: gateway 10.0.0.5
home-gw# route add 10.246.38.0 10.246.38.1 255.255.255.0
home-gw# route add host 10.246.38.0: gateway 10.246.38.1
corp-gw# ping -c 3 10.0.0.8
PING 10.0.0.8 (10.0.0.8): 56 data bytes
64 bytes from 10.0.0.8: icmp_seq=0 ttl=63 time=92.391 ms
64 bytes from 10.0.0.8: icmp_seq=1 ttl=63 time=21.870 ms
64 bytes from 10.0.0.8: icmp_seq=2 ttl=63 time=198.022 ms
--- 10.0.0.8 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 21.870/101.846/198.022/74.001 ms

home-gw# ping -c 3 10.246.38.107
PING 10.246.38.1 (10.246.38.107): 56 data bytes
64 bytes from 10.246.38.107: icmp_seq=0 ttl=64 time=53.491 ms
64 bytes from 10.246.38.107: icmp_seq=1 ttl=64 time=23.395 ms
64 bytes from 10.246.38.107: icmp_seq=2 ttl=64 time=23.865 ms
--- 10.246.38.107 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 21.145/31.721/53.491/12.179 ms
path    pre_shared_key  "/usr/local/etc/racoon/psk.txt"; # 预共享密钥文件的位置
log     debug;	# 日志详细度设置:在测试和调试完成后设置为 'notify'

padding	# 选项不可更改
{
        maximum_length  20;
        randomize       off;
        strict_check    off;
        exclusive_tail  off;
}

timer	# 定时选项,根据需要更改
{
        counter         5;
        interval        20 sec;
        persend         1;
#       natt_keepalive  15 sec;
        phase1          30 sec;
        phase2          15 sec;
}

listen	# racoon 将监听的地址 [端口]
{
        isakmp          172.16.5.4 [500];
        isakmp_natt     172.16.5.4 [4500];
}

remote  192.168.1.12 [500]
{
        exchange_mode   main,aggressive;
        doi             ipsec_doi;
        situation       identity_only;
        my_identifier   address 172.16.5.4;
        peers_identifier        address 192.168.1.12;
        lifetime        time 8 hour;
        passive         off;
        proposal_check  obey;
#       nat_traversal   off;
        generate_policy off;

                        proposal {
                                encryption_algorithm    blowfish;
                                hash_algorithm          md5;
                                authentication_method   pre_shared_key;
                                lifetime time           30 sec;
                                dh_group                1;
                        }
}

sainfo  (address 10.246.38.0/24 any address 10.0.0.0/24 any)	# 地址 $network/$netmask $type 地址 $network/$netmask $type ( $type 可以是 any 或 esp)
{								# $network 必须是你要连接的两个内部网络。
        pfs_group       1;
        lifetime        time    36000 sec;
        encryption_algorithm    blowfish,3des;
        authentication_algorithm        hmac_md5,hmac_sha1;
        compression_algorithm   deflate;
}
flush;
spdflush;
# 到家用网络
spdadd 10.246.38.0/24 10.0.0.0/24 any -P out ipsec esp/tunnel/172.16.5.4-192.168.1.12/use;
spdadd 10.0.0.0/24 10.246.38.0/24 any -P in ipsec esp/tunnel/192.168.1.12-172.16.5.4/use;
# /usr/local/sbin/racoon -F -f /usr/local/etc/racoon/racoon.conf -l /var/log/racoon.log
corp-gw# /usr/local/sbin/racoon -F -f /usr/local/etc/racoon/racoon.conf
Foreground mode.
2006-01-30 01:35:47: INFO: begin Identity Protection mode.
2006-01-30 01:35:48: INFO: received Vendor ID: KAME/racoon
2006-01-30 01:35:55: INFO: received Vendor ID: KAME/racoon
2006-01-30 01:36:04: INFO: ISAKMP-SA established 172.16.5.4[500]-192.168.1.12[500] spi:623b9b3bd2492452:7deab82d54ff704a
2006-01-30 01:36:05: INFO: initiate new phase 2 negotiation: 172.16.5.4[0]192.168.1.12[0]
2006-01-30 01:36:09: INFO: IPsec-SA established: ESP/Tunnel 192.168.1.12[0]->172.16.5.4[0] spi=28496098(0x1b2d0e2)
2006-01-30 01:36:09: INFO: IPsec-SA established: ESP/Tunnel 172.16.5.4[0]->192.168.1.12[0] spi=47784998(0x2d92426)
2006-01-30 01:36:13: INFO: respond new phase 2 negotiation: 172.16.5.4[0]192.168.1.12[0]
2006-01-30 01:36:18: INFO: IPsec-SA established: ESP/Tunnel 192.168.1.12[0]->172.16.5.4[0] spi=124397467(0x76a279b)
2006-01-30 01:36:18: INFO: IPsec-SA established: ESP/Tunnel 172.16.5.4[0]->192.168.1.12[0] spi=175852902(0xa7b4d66)
corp-gw# tcpdump -i em0 host 172.16.5.4 and dst 192.168.1.12
01:47:32.021683 IP corporatenetwork.com > 192.168.1.12.privatenetwork.com: ESP(spi=0x02acbf9f,seq=0xa)
01:47:33.022442 IP corporatenetwork.com > 192.168.1.12.privatenetwork.com: ESP(spi=0x02acbf9f,seq=0xb)
01:47:34.024218 IP corporatenetwork.com > 192.168.1.12.privatenetwork.com: ESP(spi=0x02acbf9f,seq=0xc)
ipfw add 00201 allow log esp from any to any
ipfw add 00202 allow log ah from any to any
ipfw add 00203 allow log ipencap from any to any
ipfw add 00204 allow log udp from any 500 to any
pass in quick proto esp from any to any
pass in quick proto ah from any to any
pass in quick proto ipencap from any to any
pass in quick proto udp from any port = 500 to any port = 500
pass in quick on gif0 from any to any
pass out quick proto esp from any to any
pass out quick proto ah from any to any
pass out quick proto ipencap from any to any
pass out quick proto udp from any port = 500 to any port = 500
pass out quick on gif0 from any to any
ipsec_enable="YES"
ipsec_program="/usr/local/sbin/setkey"
ipsec_file="/usr/local/etc/racoon/setkey.conf" # 允许在启动时设置 spd 策略
racoon_enable="yes"
usb(4)
ipfw(4)
apache@FreeBSD.org
autotools@FreeBSD.org
doceng@FreeBSD.org
eclipse@FreeBSD.org
gecko@FreeBSD.org
gnome@FreeBSD.org
hamradio@FreeBSD.org
haskell@FreeBSD.org
java@FreeBSD.org
kde@FreeBSD.org
mono@FreeBSD.org
office@FreeBSD.org
perl@FreeBSD.org
python@FreeBSD.org
ruby@FreeBSD.org
secteam@FreeBSD.org
vbox@FreeBSD.org
x11@FreeBSD.org
web form
Writing a GEOM Class
FreeBSD 开发者手册
FreeBSD 架构手册
编写 FreeBSD 设备驱动程序
FreeBSD Diary
geom(4)
PHK 的 GEOM 幻灯片
g_bio(9)
g_event(9)
g_data(9)
g_geom(9)
g_provider(9)
g_consumer(9)
g_access(9)
style(9)
VMWare
QEmu
malloc(9)
uma(9)
queue(3)
tree(3)
hashinit(9)
read(2)
write(2)
geom(8)
dlopen(3)
geom(8)
g_bio(9)
malloc(9)
kthread_create(9)
kthread_exit(9)
mutex(9)
sx(9)
Serial and UART Tutorial
sio(4)
内核配置
devfs(5)
hmo@sep.hamburg.com
j@ida.interface-business.de
sio(4)
devfs(5)
Why you should use a BSD style license for your Open Source Project
SHARE
DECUS
新 BSD 许可证
自由软件基金会
[1]
GPL
LGPL
[2]
[3]
http://www.gnu.org/licenses/gpl.html
http://archives.cnn.com/2000/TECH/computing/03/28/cyberpatrol.mirrors/
http://www.oreilly.com/catalog/opensources/book/brian.html
http://alumni.cse.ucsc.edu/~brucem/open_source_license.htm
Use Language Servers for Development in the FreeBSD Src Tree
devel/ccls
devel/llvm12
editors/vim
editors/neovim
editors/vscode
devel/python
devel/py-pip
devel/bear
prabirshrestha/vim-lsp
https://github.com/prabirshrestha/vim-lsp/blob/master/README.md#registering-servers
https://clangd.llvm.org
https://github.com/MaskRay/ccls/
https://clang.llvm.org/docs/JSONCompilationDatabase.html#format
devel/python
rizsotto/scan-build
Writing FreeBSD Problem Reports
FreeBSD 一般问题邮件列表
Port 开发者手册
Contributing to the FreeBSD Ports Collection
clang(1)
sendmail(8)
FreeBSD 版本
支持版本列表
常见问题解答
硬件兼容性
用户应用程序
内核配置
邮件列表
可搜索的档案
FreeBSD PR 数据库
https://cgit.freebsd.org/src/tree/UPDATING
https://cgit.freebsd.org/ports/tree/UPDATING
https://cgit.freebsd.org/ports/tree/CHANGES
https://bugs.freebsd.org/bugzilla/query.cgi
https://www.FreeBSD.org/search/#mailinglists
基于网页的 PR 提交表单
diff(1)
sh(1)
mount(8)
FreeBSD 一般问题邮件列表
PR 搜索页面
相关邮件列表
https://wiki.freebsd.org/IRC/Channels
Git 仓库
FreeBSD 贡献者
bugmeister@FreeBSD.org
如何有效报告 Bug
问题报告处理指南
The vinum Volume Manager
gconcat(8)
gmirror(8)
gstripe(8)
graid(8)
zfs(8)
GEOM 架构
连接组织
条带化组织
vinum Plex 组织方式
gvinum(8)
gvinum(8)
gvinum(8)
一个简单的 vinum 卷
A 镜像 vinum 卷
条带化的 vinum 卷
镜像
gvinum(8)
newfs(8)
newfs(8)
gvinum(8)
kldload(8)
loader.conf(5)
loader(8)
Nothing Boots
第二阶段
VPN over IPsec
http://www.kame.net/
ipsec(4)
security/ipsec-tools
gif(4)
ping(8)
ping(8)
tcpdump(1)
ipfw(8)
pf(4)
ipf(8)
vinum concat
vinum striped
vinum raid5 org
vinum simple vol
vinum mirrored vol
vinum striped vol
vinum raid10 vol