FreeBSD 接口 API(IfAPI)
作者:Justin Hibbits

如某些人所知,瞻博(Juniper)使用自己定制的网络栈,该网络栈基于 FreeBSD——很久以前分叉而来,因此它仅在表面上与当前的 FreeBSD 网络栈相似。当前 FreeBSD 网络栈中的某些状态在 Junos 中并不存在,反之亦然。
为什么以及如何实现
Junos 非常庞大。更新 FreeBSD 是一项艰巨的任务。为简化此过程,Juniper 将 FreeBSD 组件拆分到独立仓库中,将其增强功能保留在另一个独立仓库中。这就带来了一个难题——如何在 FreeBSD 中保留驱动程序,而将网络栈放在其他地方?作为拆分 FreeBSD 项目的一部分,原始的 DrvAPI 诞生了。通过它,驱动程序可以存在于 FreeBSD 仓库中,同时保持 Junos 网络栈独立。
但什么是网络栈?在哪里划定网络栈与其余部分的界限?最初的方法是“所有 sys/ 目录下以 net 开头的目录”,这种方法行之有效。然而,最近添加了 netlink 组件,从概念上讲它不属于网络栈,因此 netlink 被排除在外。现在,网络栈由 net、net80211、netgraph、netinet、netinet6 和 netpfil 组成。将细节仅保留在网络栈中,也使核心内核无需了解这些细节。内核的其他部分也需要调整,以适应 IfAPI,包括 NFS 无根(启动)和 mbuf 处理。
设计
当前 IfAPI 的设计主要是访问器和迭代器。这种方法被选为转换驱动程序并隐藏 struct ifnet 的最便捷方式,尽管它并非最佳方式。转换过程主要是机械式的,Juniper 提供了一个 shell 脚本 tools/ifnet/convert_ifapi.sh 来处理大部分转换工作。显然,这可能会漏掉一些转换,例如当 ifp 是另一个结构体的成员,或者命名为其他形式(如 foo_ifp)时,但它能处理大多数情况。
至于迭代器,最初的实现基于 Gleb Smirnoff 的 if_foreach_lladdr(),在迭代给定类型时使用回调函数。这被应用于 if_addr 和 if_t,在迭代接口时只会遍历当前的 VNET。最近,增加了一种新的迭代器 API,能使用更传统的循环进行迭代;要求是你必须调用 if_iter_finish()/ifa_iter_finish() 来清理迭代所设置的任何状态,包括释放实现可能分配的任何内存(FreeBSD 网络栈未做此处理,但其他栈可以做到)。
好处
将设备驱动程序与网络栈细节解耦,不仅对瞻博的源代码管理有益,还带来了一些额外的好处。通过稳定的 ABI,单个设备驱动程序能与多个不同的网络栈一起使用。例如,在数据中心的所有计算机上,在启动时,某款镜像能选择不同的网络栈,依据执行配置选择一款高性能的有限网络栈用于某些设备,而为其他设备使用完整的网络栈,所有设备仍使用相同的网络驱动程序。
另一个较小的好处是,驱动程序更改和网络栈更改可以同时发生,而不会互相影响。在 IfAPI 出现之前,对 struct ifnet 的任何更改都需要重新构建所有设备驱动程序。今后,由于 struct ifnet 是完全私有的,任何对该结构的更改只需要重新构建那些直接引用它的文件,从而缩短了调试周期。
接下来会做什么?
IfAPI 只是第一步,仍然有很多工作要做,以便真正地抽象化网络栈。Gleb Smirnoff 提出了使用 KOBJ 接口来允许更可插拔的网络栈,并完全解耦网络栈与其他部分。这甚至可以允许在运行时替换网络栈(kldunload/kldload)。进一步来说,我们甚至可以允许多个网络栈,并将不同的设备分配到不同的网络栈中。如此一来,甚至可能实现动态地在网络栈之间移动接口。
结论
IfAPI 只是解耦网络驱动程序与网络栈内部实现的第一阶段。随着进一步的工作,多个网络栈可以同时使用——甚至是可重新加载的网络栈。
Justin Hibbits 于 2011 年因其对 PowerPC 的执着被愚蠢地授予了 FreeBSD 提交权限。从那时起,他主要专注于 PowerPC 和其他嵌入式架构的工作。目前他在瞻博网络工作,负责所有与 FreeBSD 内核相关的事务,并继续他对底层开发和异构架构的热情。
最后更新于