作者: Justin Hibbits
如某些人所知,瞻博(Juniper)使用自己定制的网络栈,该网络栈基于 FreeBSD 开发的长期分支,因此在表面上它看起来与当前的 FreeBSD 网络栈相似,但实际上存在诸多差异。当前 FreeBSD 网络栈中的某些状态在 Junos 中并不存在,反之亦然。
Junos 非常庞大。同步 FreeBSD 是一项艰巨的任务。为简化这个过程,Juniper 将 FreeBSD 组件拆分成了独立的仓库,将瞻博的增强功能保留在独立的仓库中。这就带来了一个难题——我们如何在 FreeBSD 中保留驱动程序,而将网络栈放在其他地方?作为分拆 FreeBSD 项目的一部分,提出了原始的 DrvAPI。通过这个 API,驱动程序可以存在于 FreeBSD 仓库中,同时把 Junos 网络栈保持为独立部分。
但什么是网络栈?我们在哪里划定网络栈与其他部分的界限?最初的方法是“sys/ 目录下所有以 net
开头的目录”,这种方法有效。然而,最近添加了组件 netlink
,从概念上来说,它并不属于网络栈的一部分,所以这一方法被抛弃了。现在,网络栈有 net
、net80211
、netgraph
、netinet
、netinet6
和 netpfil
。保持网络栈的细节只关乎网络栈本身,也让核心内核的细节得以遮蔽。某些内核的其他部分也需要做出调整,以适应 IfAPI,包括 NFS 无根(boot)和 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 和其他嵌入式架构的工作。目前他在瞻博 Networks 工作,负责所有与 FreeBSD 内核相关的事务,并继续他对底层开发和异构架构的热情。