FreeBSD 本月动态
作者: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> 查询。
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 认证小组主席。
最后更新于