Linux高速网卡驱动设计随笔

网络系统是Linux中设计最复杂,功能最强大的子系统。本篇文章要陈述的并非正统的Linux网络系统,而是仅仅聚焦在链路层上,也就是所谓的网卡驱动。

        TCP/IP协议族,每层都分配了不同的功能,针对不同的协议和应用场景设计,L2负责将网络层的报文发送到物理介质上。网卡驱动是构成L2的主要部分。

  Linux内核本身提供了针对网络设备的驱动框架开发框架netops,内核里也集成了大量主流网卡厂商的驱动程序。但这类标准框架下开发出来的驱动程序,虽然可以很好得契合Linux网络子系统设计,但在某些场景的应用下,则显得力不从心。核心问题是,性能!

        笔者所在的行业,存在大量数据面或转发面的设计,在数据面或转发面系统中,对需要支持的协议栈要求并不高,UDP + IP的组合基本足够了,甚至可以更简单。而对网卡转发性能的要求则非常高,要求达到linerate!在当前,行业内10G网卡早就是标配了,40G甚至100G的也不缺乏。从长期的测试数据来看,基于传统Linux内核态驱动的网络设备很难满足性能上的需求。先来看看内核态驱动的瓶颈所在,不一一给出理论上的分析了,直接给出结论。

        1) 存在内存拷贝。从内核态mbuf到用户态之间,有一次内存拷贝

        2)动态buffer管理。内存buffer动态申请释放

       3)内核态用户态切换存在上下文切换的时延

       4)中断驱动收包的方式,会进一步增大时延

       5)庞大复杂的内核协议栈处理


        针对上述问题,笔者总结两类解决方案,并试着陈述其设计概要。

       第一类为用户态驱动方式,设计概要如下:

       1)将外设IO映射到用户态,让用户态程序可直接访问外设。 这样就避免了内核协议栈的干扰,以及内核态用户态间的内存拷贝开销

       2)改中断方式收包围查询式收包。当然也可权衡采用一次中断多次查询的方式,在性能和cpu占用率间作出一定的平衡

       3)预分配buffer。在用户态实现一个buffer cache池,用于填充到硬件。如果硬件支持自释放buffer,此处的设计会更简单

       4)线程绑定到特定核,避免线程调度带来的时延以及开销。

        用户态驱动的性能相比内核态驱动可以提升一大截,基本可满足10G甚至更高的要求。但这类方案也存在一定的弊端:

        1)用户态需要实现一套简单的协议栈,某种程度上会加大工作量  

        2)系统健壮性及安全性有一定的降低。用户可直接访问并控制外设。系统入侵成本太低,因此并不适合一些开放式的应用场景。


        第二类为基于内核态驱动的优化改造,比较典型的为pf_ring及netmap方案。此处不展开将这两个开源项目的细节,只从设计思想进行概括。以netmap为例

        1)实现静态预留buffer,避免动态申请释放buffer内存

        2)采用查询式收报(由用户态发起查询,内核态配合完成)

        3)采用批处理的方式,一次查询完成多个收包处理,分摊查询所带来系统调用的开销。如系统调用开销为100ms,若一次收10个包,则分摊到每个包的开销为10ms。

        4)实现用户态和内核态的贡献内存,作为buffer。这样只需要在内核态跟用户态间传递指针或偏移量即可,无须拷贝内存。

        同时用户态驱动所实现的线程绑定技术也需要应用到这里来。这类方案比用户态驱动的优势在于:

        1)用户态无法访问外设,增加了安全性

        2)性能远高于传统内核态驱动,略低于用户态驱动,但可满足线速的需求

       3)某些时候可借用内核的协议栈。可在性能和功能完整性间比较灵活地切换。

        

        关于netmap的实现,后续有时间的话打算写个专题。此处不再展开

你可能感兴趣的:(Unix/Linux)