> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20151112-qian-yi-jail-guan-li/this-month-in-freebsd.md).

# FreeBSD 本月动态

* 原文：[This Month in FreeBSD](https://freebsdfoundation.org/our-work/journal/browser-based-edition/migrating-jail-management-from-warden-to-iocage/)
* 作者：**Dru Lavigne**

在未来几个月里，我们将更深入地关注即将进入 2016 年版本的一些新特性及其背后的开发者。本月，我有机会采访了 EDWARD TOMASZ NAPIERALA，听他讲述自己从 FreeBSD 用户、到 Ports committer、到编程之夏学生、再到 src committer 的历程。他还谈到了 FreeBSD root 重挂载（root remount）特性的开发。你可以在他的 FreeBSD wiki 页面（<https://wiki.freebsd.org/EdwardTomaszNapierala>）上了解更多关于他过往项目的信息。

**问**：请简单介绍一下你自己。你是如何开始接触 FreeBSD 的？在 FreeBSD 项目中担任什么角色？

**答**：我是物理专业出身，但实际工作一直与软件相关。高中时我决定尝试 FreeBSD，那时 Linux 第 N 次把我的文件搞丢了（哦，2.3.X 开发内核的美好年代）。很长一段时间里我只是用户和偶尔的系统管理员。之后我加入了 Ports，并最终拿到了 Ports 的 commit 权限。2008 年我决定参加谷歌 Summer of Code（GSoC），从事 NFSv4 ACL 的工作。不久后我获得了 FreeBSD src 的 commit 权限，并一直使用至今。

**问**：你目前在做 root 重挂载项目。root 重挂载能带来什么好处？用户何时会用到这一特性？

**答**：它让你能以临时文件系统引导——例如由 loader(8) 预载入的内存盘镜像——然后再用真正的文件系统把它替换掉。典型的例子是 iSCSI 引导，这存在先有鸡还是先有蛋的问题：建立 iSCSI 会话需要运行 iscsid(8)，因此需要先挂载 rootfs，因为 rootfs 里才有 iscsid。借助 reroot，你提供一个含有必要二进制文件（如 **/rescue**）的内存盘和一个用于建立 iSCSI 会话的脚本，并在真正的根设备可访问后调用 `reboot -r`。内核会切换根设备，init(8) 继续执行通常的启动脚本。

你可以把它看作 FreeBSD 中类似于 Linux 上 pivot\_root() 和 initrd 的对应物。注意，你也可以把 root 重挂载当作快速重置用户态的方式，这在试验启动脚本和配置改动时很有用。

root 重挂载支持已合入 11-CURRENT，并将合并到 **stable/10**。reroot 支持计划纳入 FreeBSD 10.3，该版本目前定于 2016 年 3 月发布。

**问**：从开发者的角度看，实现 root 重挂载特性有多难？

**答**：这是一场颇不寻常的历险。整体思路虽简单，但要尝试好几种方案，过程中还顺手修了一些不相关的问题。

我没有采用 pivot\_root() 方案，因为这是对管理员不友好的接口。它在 devfs 上也有问题：执行 pivot\_root() 之后你无法卸载旧的 **/dev**，因为伪终端和已挂载磁盘设备的设备节点都还开着。于是我决定改走另一条路——重复系统引导的最后阶段，即从挂载根文件系统那一刻开始的流程。

第一次尝试有点天真：写了调用 vfs\_unmountall() 的函数，它卸载所有文件系统，同时也把 init(8) 杀掉了，因为进程无法在包含其可执行文件的文件系统被强制卸载后幸存。然后我重复了启动时通常执行的步骤，比如挂载 **/** 和 **/dev** 并启动 init。我修改了 reboot(2) 系统调用和 reboot(8) 工具来调用该函数。我还需要一种办法让新的 init(8) 拥有 PID 1。

这次实验只花了几小时，但结果发现了一个 bug：在强制卸载包含运行中可执行文件的文件系统后，会在虚拟内存管理代码深处引发 panic。这远远超出我的专业范围，于是我报告了这个 bug，并写了先把所有进程都杀掉的变通方案。结果勉强能用，但之后系统会变得……很慢。原来向某个特定内核线程发送信号会导致它无限消耗 CPU 周期。我报告了这些问题，由对该部分更熟悉的人修复了。

但冒险远未结束。我当初没预见到——事后看本该预见——init 进程是特殊的，它会收养孤儿进程。正因如此，内核对它的处理与其他进程不同。而那种处理（比如把孤儿僵尸进程从旧的 PID 1 重新挂到新的 PID 1）需要在 reroot 代码中重复一遍。而那段代码相当复杂。

另一个问题是 reroot 之后残留了 **/dev** 挂载。原来是 devfs 忽略了强制卸载。这部分代码我还算熟悉，通过修复 GEOM 框架中的几个问题让它能工作。然后我意识到这其实并没有让我离目标更近：你不能在 rootfs 之前强制卸载 devfs，因为那就像不卸载就拔掉一块硬盘，会留下脏文件系统。所以强制卸载 devfs 的真正修法，是调整 vfs\_unmountall() 中的代码，确保它不会在卸载 rootfs 之前就尝试卸载 devfs。

经过这些修复，代码终于正确工作了，但既难看又不好维护。而 init 部分调试起来简直像炼狱，因为 devfs 强制卸载会撤销终端，于是连 printf 这种最简单的调试方式都用不了。而 ddb(4) 内核调试器也帮不上忙，它无法调试用户态进程。

第二次尝试保留了那些有效的想法：先卸载一切，再用内核里已有的、用于启动时挂载 rootfs 的代码挂载新的 rootfs。这次替换掉了糟糕的部分：即重启 init。当初 init 之所以被杀，是因为当你卸载一个包含运行进程可执行文件的文件系统时，该进程多半会死掉。进程可能正在从二进制文件中读取含有机器码的某个页面，而文件系统一旦不在就无从读起。这种情况下 mlockall(2) 也无济于事。

那么我能做的是让 init(8) 在卸载 rootfs 之前把自己复制到一个安全的地方，然后 exec(2) 那个副本。记住，exec 不会创建新进程，它只是替换正在运行的那个，因此新的 init 会从新的可执行文件运行，却仍保留 PID 1。整个过程结束后，它会再次执行目标 rootfs 中的 **/sbin/init**。

与第一个原型相比的另一处变化是，不再卸载 **/dev**，而是保留它，把它从旧 rootfs 中的旧挂载点移到新的 rootfs。虽然把这种功能暴露给用户态未必是最佳选择，但既有 root 挂载代码已经在这么用了。

如今它的工作方式是：reboot(8) 工具向 init(8) 发送一个信号，init 随后把 tmpfs 挂载到 **/dev/reroot**，把 init(8) 可执行文件复制到那里，执行它，请求内核切换文件系统，执行新的 **/sbin/init** 并清理。之后 init 像往常一样启动，运行它的 rc 脚本。

**问**：你在 FreeBSD 项目中还做过哪些类型的开发工作？

**答**：我先从 Ports 入手，既修复既有 Port——当时因为 GCC 升级，数以百计的 Port 需要修构建——也添加新的 Port。之后我转向 src，实现了 rctl(8)、活动文件系统扩容、原生 iSCSI 协议栈、autofs(5)，最后是 root 重挂载。我也修过不少 bug，从 ZFS 到 iwn(4) WiFi 驱动。

**问**：除了曾是 Summer of Code 学生，你还作为 FreeBSD 导师参与过 Summer of Code 项目。你对 Summer of Code 项目在新开发者和开源项目中的作用怎么看？

**答**：这是参与项目的绝佳机会。有好几位 FreeBSD committer 都是从参加 GSoC 起步的，我也在其中。这就像在 FreeBSD 做一次虚拟实习。

作为潜在的学生，有一点要记住：要提前几个月就参与进来。与你所选兴趣领域的相关人员取得联系。如果你判断不出该找谁，尽管问一些开发者，大家通常能轻松把你指向对的人。最后，尽量清楚地理解你的项目应当如何运作。做到了这一点，被录取就很容易。

> “这就像在 FreeBSD 做一次虚拟实习。”
>
> ——EDWARD TOMASZ NAPIERALA

**问**：参与 FreeBSD 项目是否推进了你的职业发展？如果是，如何推进的？

**答**：当然！实际上，我职业生涯的大部分都与 FreeBSD 密切相关。第一次 GSoC 之后我就收到了第一份与 FreeBSD 相关的工作邀约，来自 Wheel Systems，一家认证系统和基于 FreeBSD 的安全设备厂商。几年后，我决定暂时离开商业工作，在 FreeBSD 基金会的资助下以开发 FreeBSD 为生。

## 活动日历

以下 BSD 相关会议将于 2016 年 1 月举行。关于这些活动、本地用户组会议的更多信息，可在 <[www.bsdevents.org](http://www.bsdevents.org)> 查询。

**SCALE** • 1 月 21 日至 24 日 • 美国加利福尼亚州帕萨迪纳

<http://www.socallinuxexpo.org/scale/14x> • 第 14 届南加州 Linux 博览会将再次提供若干与 FreeBSD 相关的演讲、expo 区的 FreeBSD 展位，并有机会参加 BSDA 认证考试。本活动需缴纳少量费用注册参加。

**FOSDEM** • 1 月 30 日至 31 日 • 比利时布鲁塞尔

<https://fosdem.org/2016/> • FOSDEM 是一场免费活动，为开源社区提供相聚、分享想法、协作的场所。这场年度盛会每年吸引超过 5,000 名参与者。今年的活动设有 BSD 开发者分场、expo 区的 FreeBSD 展位，并有机会参加 BSDA 认证考试。

***

**Dru Lavigne** 是 FreeBSD 基金会董事，BSD 认证小组主席。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20151112-qian-yi-jail-guan-li/this-month-in-freebsd.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
