# 【NO.27】Netmap:一个用于快速数据包I/O的新框架

## 0. 摘要

许多应用程序(路由器、流量监视器、防火墙等)即使在非常快的链路上也需要以线路速率发送和接收数据包。在本文中，我们提出了一种新的框架netmap，它使普通操作系统能够每秒处理通过1..10的数百万个数据包传输速率为Gbit/s的链路，无需定制硬件或更改应用程序。

在构建netmap时，我们确定并成功地减少或消除了三个主要的包处理成本:每个包动态内存分配，通过预分配资源来消除;系统调用开销，在大批量上分摊;还有内存副本，通过在内核和用户空间之间共享缓冲区和元数据来消除，同时仍然保护对设备寄存器和其他内核内存区域的访问。另外，这些技术中的一些在过去已经被使用过。我们提议的优点不仅在于我们的性能超过了大多数以前的工作，而且还在于我们提供了一个与现有操作系统原语紧密集成的体系结构，不依赖于特定的硬件，并且易于使用和维护。

netmap已经在FreeBSD和Linux中实现了多个1 Gbit/s和10 Gbit/s网卡。在我们的原型中，运行在900 MHz的单个核心可以发送或接收14.88 Mpps (10gbit /s链路上的峰值包速率)。这比传统的api快20多倍。使用运行在netmap之上的libpcap仿真库，在用户空间的Click和其他包转发应用程序上也可以实现较大的加速(5倍或更多)。

> 本文为翻译论文，原论文链接：**[《netmap a novel framework for fast packet IO》](https://link.zhihu.com/?target=https%3A//github.com/0voice/computer_expert_paper/blob/main/网络编程那些事儿/《netmap a novel framework for fast packet IO》.pdf)**
> 更多计算机学术论文阅读：**[1000+份计算机paper，卡耐基梅隆大学，芝加哥大学，facebook，google，微软，twitter等大牛一作，持续更新中…](https://link.zhihu.com/?target=https%3A//github.com/0voice/computer_expert_paper)**

## 1. 简介

通用操作系统提供了丰富而灵活的运行环境，其中包括许多包处理和网络监控和测试任务。

这些应用程序所需的高速率原始数据包I/O不是通用操作系统的预期目标。

原始套接字、伯克利包过滤[14](https://linuxcpp.0voice.com/BPF)、AF套接字家族以及相应的api已经被用于构建各种网络监视器、流量生成器和通用路由系统

然而，对于1..10的每秒数百万包(pps)来说，性能是不够的Gbit / s链接。为了寻求更好的性能，一些系统(见第3节)要么完全运行在内核中，要么通过将NIC的数据结构暴露给用户空间应用程序而绕过设备驱动程序和整个网络堆栈。尽管它们可能很有效，但许多这些方法依赖于特定的硬件特性，对硬件提供不受保护的访问，或者与现有的OS原语集成得很差。

本文中提出的netmap框架结合并扩展了过去提出的一些想法，试图解决它们的缺点。除了极大的速度提升，netmap不依赖于特定的硬件1，已经完全集成在FreeBSD和Linux中，并且通过兼容性库支持未经修改的libpcap客户端。

评估我们的框架的一个指标是性能:在我们的实现中，在连接线和用户空间应用程序之间移动一个包的平摊成本不到70个CPU时钟周期，这至少比标准api快一个数量级。换句话说，运行在900 MHz的单个核心可以在10gbit /s链路上实现14.88 Mpps。运行在150mhz的同一核心远远超过1gbit /s链路的容量。

其他同样重要的指标是操作的安全性和易用性。Netmap客户机不可能使系统崩溃，因为设备寄存器和关键内核内存区域没有暴露给客户机，而且他们不能在内核中注入虚假的内存指针(这些通常是基于共享内存的其他方案的漏洞)。同时，netmap使用了非常简单的数据模型，非常适合零拷贝包转发;支持多队列适配器;并使用标准的系统调用(select()/poll())来进行事件通知。所有这些都使得将现有应用程序移植到新的机制和编写有效使用netmap API的新应用程序非常容易。

在本文中，我们将重点介绍netmap的体系结构和特性，以及它的核心性能。在Infocom的一篇相关论文[19]中，我们讨论了一个不同的问题:应用程序如何充分利用快速I/O子系统，比如netmap [19]表明，应用程序本身可能会出现显著的性能瓶颈，尽管在某些情况下，我们可以消除它们并充分利用新的基础设施。

在本文的其余部分，第2节给出了当前网络堆栈体系结构和性能的一些背景。第3节介绍了相关的工作，说明了netmap集成和扩展的一些技术。第4节详细介绍了netmap。性能数据将在第5节中介绍。最后，第6节讨论了未决问题和我们未来的工作计划。

## 2. 背景

人们一直对使用通用硬件和操作系统来运行软件交换机[15]、路由器[6,4,5]、防火墙、流量监视器、入侵检测系统或流量生成器等应用程序感兴趣。在提供方便的开发和运行时环境的同时，这样的操作系统通常不提供高效的机制来以高分组速率访问原始分组数据。本节介绍了通用操作系统中网络栈的组织结构，以及各个阶段的处理成本。

### **2.1 网卡数据结构和操作**

网络适配器(nic)通常通过缓冲区描述符的循环队列(环)管理进出数据包，如图1所示。环中的每个槽都包含缓冲区的长度和物理地址。NIC中的cpu可访问寄存器表示可用于传输或接收的环的部分。

在接收时，传入的数据包被存储在下一个可用的缓冲区(可能分成多个片段)，长度/状态信息被写回槽，以表明新数据的可用性。中断通知CPU这些事件。在传输端，NIC期望OS用要发送的数据填充缓冲区。发送新数据包的请求是通过写入网卡的寄存器发出的，相应地，网卡开始发送标记为TX环可用的数据包。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204253_80779.jpg)

图1:典型网卡的数据结构及其与操作系统数据结构的关系。

在高包速率下，中断处理可能代价昂贵，并可能导致所谓的“接收活锁”[16]，或在特定负载下无法执行任何有用的工作。轮询设备驱动程序[10,16,18]和最近网卡中实现的硬件中断缓解解决了这个问题。

一些高速网卡支持多个发送和接收环。这有助于将负载分散到多个CPU核上，减轻对网卡流量的过滤，并帮助解耦共享相同硬件的虚拟机。

### **2.2 内核和用户api**

操作系统维护NIC数据结构的影子副本。缓冲区被链接到os特定的，设备独立的容器(mbufs[22]或等价结构，如sk buffs和NdisPackets)。这些容器包括关于每个包的大量元数据:大小、源或目标接口、属性和标志，这些属性和标志指示NIC和操作系统应该如何处理缓冲区。

驱动程序/操作系统:设备驱动程序和操作系统之间的软件接口通常假定数据包在两个方向上可以被分割成任意数量的片段;设备驱动程序和主机堆栈都必须准备好处理碎片。同样的API也期望子系统可以保留数据包以进行延迟处理，因此缓冲区和元数据不能在函数调用期间简单地通过引用传递，而是必须复制或引用计数。这种灵活性在运行时带来了巨大的开销。

这些API合同在20-30年前被设计出来的时候可能是合适的，但对于今天的系统来说太昂贵了。通过缓冲区链分配、管理和导航的成本常常超过线性化内容的成本，即使生产者确实生成了分段包(例如TCP在从套接字缓冲区中添加数据头时)。

原始数据包I/O:为用户程序读写原始数据包的标准api需要至少一个内存副本来在内核和用户空间之间移动数据和元数据，每个数据包(或者，在最好的情况下，每个批数据包)需要一个系统调用。典型的方法包括打开套接字或Berkeley Packet Filter[14]设备，并使用send()/recv()或专用的ioctl()函数通过它执行I/O。

### **2.3 案例研究:FreeBSD sendto()**

为了评估处理数据包所花费的时间，我们在FreeBSD2中插入了sendto()系统调用，这样我们就可以在不同的深度强制系统调用的早期返回，并估算在网络堆栈的不同层所花费的时间。图2显示了测试程序在绑定的UDP套接字上循环sendto()时的结果。在表中,“time”是返回点在行中列出的函数开始处时每个包的平均时间;“delta”是相邻行之间的差值，表示在处理链的每个阶段所花费的时间。例如，用户空间代码每次迭代需要8 ns，进入内核需要额外的96 ns，以此类推。

正如我们所看到的，我们发现堆栈中所有级别的几个函数消耗了总执行时间的很大一部分。任何网络I/O(无论是通过TCP或原始套接字，还是BPF写入器)都必须经过几个昂贵的层。当然，我们不能避免系统调用;初始的mbuf构造/数据拷贝是昂贵的，路由和报头设置也是昂贵的，(令人惊讶的是)MAC报头设置。最后，将mbufs和元数据转换成NIC格式需要很长时间。本地优化(例如缓存路由和报头，而不是每次都重新构建它们)可以提供适度的改进，但我们需要在所有层进行彻底的改变，以获得10 Gbit/s接口的线速率所需的10倍加速。

我们在本文中展示的是，如果我们采取这样一种激进的方法，同时仍然通过系统调用对用户提供的数据进行安全检查，并提供兼容libpcap的API，那么我们的速度会有多快。

## 3. 相关(和不相关)工作

在这一点上，介绍一些文献中提出的或在商业系统中使用的技术来提高包处理速度是很有用的。这将有助于理解它们的优点和局限性，并展示我们的框架如何使用它们。

Socket api: Berkeley Packet Filter，简称BPF[14]，是最流行的直接访问数据包数据的系统之一。BPF进入网络设备驱动程序的数据路径，并将每个发送或接收的数据包的副本发送到一个文件描述符，用户空间进程可以从中读取或写入。Linux通过AF包套接字家族有类似的机制。BPF可以与系统之间的常规流量共存，尽管通常BPF客户端将卡置于混杂模式，导致大量流量被发送到主机堆栈(并立即丢弃)。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204253_27314.jpg)

图2:sendto()在最近的FreeBSD HEAD 64位i7-870上的路径和执行时间(2.93 GHz +Turboboost, Intel 10gbit NIC和ixgbe驱动。由单个进程发出sendto()调用完成的度量。值有5%的偏差，并且在多次5秒测试中取平均值。

**包过滤钩子:**Netgraph (FreeBSD)， Netfilter (Linux)和Ndis Miniport驱动(Windows)是在内核中使用的机制，当包复制是不必要的，而应用程序(例如防火墙)必须被插入到包处理链。这些钩子拦截来自/到驱动程序的流量，并将其传递给处理模块，而不需要额外的数据副本。包过滤钩子依赖于基于包表示的标准mbuf/sk buff。

**直接缓冲区访问:**删除内核-用户过渡中涉及的数据副本的一种简单方法是直接在内核中运行应用程序代码。内核模式单击[10]支持这种方式[4]。Click允许通过模块组成轻松构建包处理链，其中一些模块支持对NIC的快速访问(即使它们保留了基于sk buffo的包表示)。

然而，内核环境是非常有限和脆弱的，因此更好的选择是将包缓冲区暴露给用户空间。例如PF RING[2]和Linux PACKET MMAP，它们将包含多个预分配的包缓冲区的共享内存区域导出到用户空间客户端。内核负责在sk buffs和共享缓冲区之间复制数据，因此不需要自定义设备驱动程序。这将系统调用成本分摊到多个包上，但保留了数据拷贝和sk buff管理开销。可能(但我们没有详细的文档)这也是“Windows注册I/O API”(里约热内卢)[20]的工作方式。

更好的性能可以通过在用户空间中运行完整的堆栈(一直到网卡访问)来实现。这需要自定义设备驱动程序，并带来一些风险，因为NIC的DMA引擎可以写入任意内存地址(除非受到IOMMUs等硬件机制的限制)，因此行为不正常的客户机可能会在系统的任何地方垃圾数据。这类例子包括UIO-IXGBE[11]、PF RING-DNA[3]和商业解决方案，包括英特尔的DPDK[8]和SolarFlare的OpenOnload[21]。

Van Jacobson的NetChannels[9]和我们的工作有一些相似的特性，至少在用于加速性能的技术上:删除sk bufs，避免在中断处理程序中进行包处理，以及将缓冲区映射到合适的库实现整个协议处理的用户空间。唯一可用的文档able[9]显示了有趣的加速，尽管后来在Linux中实现相同想法的尝试(参见[13])被认为不令人满意，可能是因为试图保持与现有内核网络体系结构100%兼容引入了额外的约束。PacketShader [5] I/O引擎(PSIOE)是另一个与我们的提议密切相关的，特别是在每个的性能方面。PSIOE使用自定义设备驱动程序，该驱动程序re将基于sk buff的API与一个更简单的API放在一起，使用预分配的缓冲区。自定义ioctl()用于将内核与用户空间应用程序同步，并且mul多个包通过内核和应用程序之间共享的内存ory区域上下传递。内核负责在共享内存和包缓冲区之间复制包数据。与netmap不同，PSIOE只支持一个特定的NIC，不支持port select()/poll()，需要修改应用程序以允许它们使用新的API。

**硬件解决方案:**一些硬件被专门设计来支持高速数据包捕获，或者可能的数据包生成，以及一些特殊功能，如时间戳、过滤、转发。通常这些卡带有定制的设备驱动程序和用户库来访问硬件。例如，DAG[1,7]卡是基于fpga的设备，用于线速率包捕获和精确的时间戳，使用快速板载内存用于捕获缓冲区(在引入它们时，典型的I/O总线无法维持1和10gbit /s的线速率)。NetFPGA[12]是基于fpga的卡的另一个例子，卡的固件可以通过编程直接在NIC中实现特定功能，从CPU中卸载一些工作。

### **3.1 不相关的工作**

在高速网络中，很多商业利益都转向了TCP加速和硬件虚拟化，因此阐明netmap在这方面的地位是很重要的。Netmap是一个框架，用于降低在硬件和主机堆栈之间移动流量的成本。 与TCP加速相关的流行硬件特性，如硬件校验和甚至加密、Tx分段卸载、大型接收卸载，与我们的建议完全正交:它们减少了主机堆栈中的一些处理，但不处理与设备的通信。与虚拟化相关的特性也同样正交，例如支持多个硬件队列，以及将流量分配给特定队列(VMDq)和/或将队列分配给特定虚拟机(VMDc, SR-IOV)的能力。我们希望在虚拟机中运行netmap，尽管探索netmap中使用的思想如何在管理程序中用于帮助虚拟化网络硬件可能是值得的(但不是本文的重点)。

## 4. Netmap

之前的调查显示，大多数相关的建议已经确定并试图消除以下数据包处理中的高成本操作:数据复制、元数据管理和系统调用开销。我们的框架叫做netmap，它是一个系统，可以让用户空间应用程序非常快速地访问网络数据包，在接收端和发送端，包括来自/到主机堆栈的。效率不会以牺牲操作的安全性为代价:潜在的危险行为，如编程的网卡是由操作系统验证，这也加强了内存保护。另外，netmap的一个独特特性是试图设计和实现一个使用简单、与现有操作系统机制紧密集成、不绑定特定设备或硬件特性的API。

Netmap通过以下几种技术实现高性能:

- 一种轻量级元数据表示，它紧凑、易于使用，并且隐藏了特定于设备的特性。此外，该表示支持在每个系统调用中处理大量数据包，从而摊销其成本;
- 线性的、固定大小的包缓冲区在设备打开时预先分配，从而节省每个包分配和释放的成本;
- 通过授予应用程序对数据包缓冲区的直接、受保护的访问权来消除数据复制成本。同样的机制也支持接口之间的零拷贝传输数据包;
- 支持有用的硬件特性(例如多个硬件队列)。

总的来说，我们使用系统的每个部分来完成最适合的任务:网卡用于在网络和内存之间快速移动数据，操作系统用于加强保护并提供同步支持。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212122040232603928.png)

图3:在netmap模式下，网卡环与主机网络堆栈断开连接，并通过netmap API交换数据包。另外两个netmap环让应用程序与主机堆栈通信

在非常高的级别上，当程序请求将接口置于netmap模式时，NIC将部分地从主机协议堆栈断开连接(见图3)。通过在共享内存中实现的缓冲区(netmap环)的循环队列，该程序获得了与NIC和(单独)与主机堆栈交换数据包的能力。传统的OS原语，如select()/poll()用于同步。除了数据路径中的断开连接之外，操作系统并不知道更改，因此它仍然像在常规操作期间一样继续使用和管理接口。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204254_46624.jpg)

图4:netmap导出的共享内存区域的用户视图

### **4.1 数据结构**

netmap体系结构中的关键组件是图4所示的数据结构。它们的设计目的是提供以下特性:1)减少/摊销每个数据包的开销;2)接口间高效转发;3)网卡与主机栈之间的高效通信;4)支持多队列适配器和多核心系统。

netmap通过将三种用户可见的对象关联到每个接口来支持这些特性，如图4所示:包缓冲区、netmap环和netmap if描述符。系统中所有启用netmap的接口的所有对象都驻留在同一个内存区域中，由内核在一个非分页区域中分配，并由所有用户进程共享。使用单个区域可以方便地支持接口之间的零复制转发，但是修改代码使不同的接口或接口组使用单独的内存区域，从而在客户机之间获得更好的隔离是很简单的。

由于共享内存是由不同虚拟地址空间中的进程和内核线程映射的，因此包含在该区域中的任何内存引用都必须使用相对地址，这样指针就可以以位置无关的方式计算。这个问题的解决方案是将引用作为父数据结构和子数据结构之间的偏移量来实现。

包缓冲区有固定的大小(当前实现为2kbytes)，由网卡和用户进程共享。每个缓冲区都由唯一的索引标识，用户进程或内核可以很容易地将其转换为虚拟地址，并将其转换为NIC的DMA引擎所使用的物理地址。当接口进入netmap模式时，所有netmap环的缓冲区都被预先分配，这样在网络I/O期间就不需要分配它们了。描述缓冲区的元数据(索引、数据长度、一些标志)存储在下面描述的netmap环的插槽中。每个缓冲区被一个netmap环和相应的硬件环引用。

netmap环是由NIC实现的与设备无关的循环队列的副本，包括:

- 环的尺寸，环内槽的数量;
- 当前在环中的读或写位置;
- avail，可用缓冲区的数量(在RX环中接收的数据包，在TX环中空槽);
- Buf ofs，环与(固定大小的)包缓冲区数组开始之间的偏移量;
- Slots[]，一个包含环大小条目的数组。每个槽包含相应的数据包缓冲区的索引、数据包的长度和一些用于请求缓冲区上的特殊操作的标志。

最后，一个netmap if包含描述接口的只读信息，例如环的数量和一个数组，该数组包含netmap if和每个关联到接口的netmap环之间的内存偏移量(同样，偏移量用于使寻址位置独立)。

#### **4.1.1 数据所有权和访问规则**

netmap数据结构在内核和用户空间之间共享，但是不同数据区域的所有权定义得很好，因此不存在竞争。特别是，netmap环总是由用户空间应用程序拥有，除非在执行系统调用期间，内核代码仍然在用户进程的上下文中更新它。中断处理程序和其他内核线程永远不会触及netmap环。

当前和当前+ available -1之间的包缓冲区是由用户空间应用程序拥有的，而其余的缓冲区是由内核拥有的(实际上，只有NIC访问这些缓冲区)。这两个区域之间的边界在系统调用期间更新。

### **4.2 netmap API**

程序通过打开专用设备/dev/netmap并发出一个

```
ioctl(.., NIOCREG, arg) 
```

在文件描述符上。参数包含接口名称，并可选地指示我们想通过这个文件描述符控制哪个环(参见第4.2.2节)。成功,该函数返回所有数据结构所在的共享内存区域的大小，如果在该区域内，则返回netmap的偏移量。文件描述符上的后续mmap()使进程的地址空间中可以访问内存。

一旦文件描述符绑定到一个接口及其环上，另外两个ioctl()就支持数据包的传输和接收。特别是，传输要求程序填充TX环中的缓冲区，从槽cur(包长度写入槽的len字段)开始，然后发出一个

```
ioctl(.., NIOCTXSYNC) 
```

告诉操作系统要发送的新数据包。这个系统调用将信息传递给内核，并在返回时更新netmap环中的avail字段，报告由于之前的传输完成而变得可用的插槽。

在接收端，程序应该首先发出一个

```
ioctl(.., NIOCTXSYNC) 
```

询问OS有多少数据包可供读取;然后它们的长度和有效负载就可以通过netmap环中的槽(从cur开始)立即获得。

两个NIOC*SYNC ioctl()都是非阻塞的，不涉及数据复制(除了同步的插槽在netmap和硬件环)，并可以处理多个包一次。这些特性对于将每个包的开销减少到非常小的值非常重要。这些系统调用的内核部分做了以下工作:

- 验证cur/avail字段和涉及的槽的内容(长度和缓冲区索引，在netmap和硬件环);
- 同步netmap和硬件环之间的槽的内容，并向NIC发出命令通告新的数据包发送或新可用的接收缓冲区;
- 更新netmap环中的avail字段。

内核中的工作量很小，所执行的检查确保共享数据结构中用户提供的数据不会导致系统崩溃。

#### **4.2.1** 阻塞原语

阻塞I/O通过select()和poll()系统调用得到支持。Netmap文件描述符可以传递给这些函数，并在可用> 0时报告为就绪(唤醒调用者)。在从select()/poll()返回之前，系统更新环的状态，这与NIOC*SYNC ioctls中的状态相同。这样，在事件循环上旋转的应用程序每次迭代只需要一次系统调用。

#### **4.2.2 多队列接口**

对于具有多个环对的卡片，文件描述符(以及相关的ioctl()和poll())可以配置为两种模式之一，通过NIOCREG ioctl()参数中的环id字段进行选择。在默认模式下，文件描述符控制所有环，导致内核检查其中任何一个环上的可用缓冲区。在替代模式中，文件描述符与单个TX/RX环对相关联。通过这种方式，多个线程/进程可以创建单独的文件描述符，将它们绑定到不同的环对并可在卡上独立操作，不受干扰，不需要同步。将线程绑定到特定的核心只需要一个标准的OS系统调用setaffinity()，而不需要任何新的机制。

#### **4.2.3 使用示例**

下面的示例(第5节中使用的包生成器的核心)展示了netmap API的简单使用。除了一些用于在共享内存区域中导航数据结构的宏之外，netmap客户端不需要任何库来使用系统，并且代码非常紧凑和可读

```
fds.fd = open("/dev/netmap", O_RDWR);strcpy(nmr.nm_name, "ix0");ioctl(fds.fd, NIOCREG, &nmr);p = mmap(0, nmr.memsize, fds.fd);nifp = NETMAP_IF(p, nmr.offset);fds.events = POLLOUT;for (;;) {  poll(fds, 1, -1);  for (r = 0; r < nmr.num_queues; r++) {    ring = NETMAP_TXRING(nifp, r);    while (ring->avail-- > 0) {      i = ring->cur;      buf = NETMAP_BUF(ring, ring->slot[i].buf_index);      ... store the payload into buf ...      ring->slot[i].len = ... // set packet length      ring->cur = NETMAP_NEXT(ring, i);      }   }}
```

### **4.3 与主机堆栈通信**

即使在netmap模式下，操作系统中的网络堆栈仍然负责控制接口(通过ifconfig和其他功能)，并将生成(并期望)到接口/从接口的流量。此流量通过另外一对netmap环来处理，可以通过NIOCREG调用将其绑定到一个netmap文件描述符

其中一个环上的NIOCTXSYNC将缓冲区封装到mbufs中，然后将它们传递给主机堆栈，就像它们来自物理接口一样。相反，来自主机堆栈的数据包将排队进入“主机堆栈”netmap环，并在随后的niocrxsync上对netmap客户端可用。

netmap客户端负责确保数据包在连接到主机堆栈的环和那些连接到NIC的环之间正确地传递。实现这个特性很简单，甚至可以使用第4.5节中所示的零复制技术。这也是实现防火墙、流量整形器和NAT盒等功能的理想机会，这些功能通常附加在包过滤钩子上。

### **4.4 安全考虑**

内核和能够打开/dev/netmap的多个用户进程之间的内存共享提出了这样一个问题:在框架的使用中存在什么安全隐患。

使用netmap的进程，即使行为不正常，也不会导致内核崩溃，这与许多其他高性能数据包I/O系统(例如UIO-IXGBE, PF RING-DNA, in-kernel Click)不同。事实上，共享内存区域并不包含关键的内核内存区域，并且缓冲区索引和长度在使用之前总是由内核进行验证。

一个行为不当的进程可以破坏其他人的网络地图环或数据包缓冲区。解决这个问题的简单方法是为每个环实现一个单独的内存区域，这样客户端就不会干扰了。在硬件多队列的情况下，这很简单，或者可以在软件中简单地模拟，而不需要数据副本。这些解决方案将在今后的工作中进行探讨。

### **4.5 零拷贝数据包转发**

由于在同一个内存区域中有所有接口的所有缓冲区，所以接口之间的零拷贝包转发只需要在入接口的接收槽和出接口的发送槽之间交换缓冲区索引，并相应地更新长度和标志字段:

```
src = &src_nifp->slot[i]; /* locate src and dst slots */dst = &dst_nifp->slot[j];/* swap the buffers */tmp = dst->buf_index;dst->buf_index = src->buf_index;src->buf_index = tmp;/* update length and flags */dst->len = src->len;/* tell kernel to update addresses in the NIC rings */dst->flags = src->flags = BUF_CHANGED;
```

交换器在输出接口上对数据包进行排队，同时用一个空缓冲区填充输入环，而不需要涉及内存分配器。

### **4.6 libpcap兼容性**

如果没有应用程序使用API，那么API就没有什么价值，而部署新API的一个重大障碍是需要对现有代码进行调整以适应它们。

遵循一种解决兼容性问题的通用方法，我们在netmap上写的第一件事是一个小库，它将libpcap调用映射到netmap调用。由于netmap使用了标准的同步原语，这个任务被大大简化了，所以我们只需要将读/写函数(pcap dispatch()/pcap inject())映射到等效的netmap调用中——总共大约20行代码。

4.7 实现

在网络地图的设计和开发中，为了保证系统的可维护性和性能，做了大量的工作。FreeBSD中包含的当前版本包含了大约2000行用于系统调用(ioctl, select/poll)和驱动程序支持的代码。不需要用户空间库:一个小的C头文件(200行)定义了netmap客户端使用的所有结构、原型和宏。我们最近完成了一个Linux版本，它使用相同的代码加上一个小的包装器将某些FreeBSD内核函数映射到它们的Linux对等体中。

保持设备驱动程序修改小(必须,如果我们想要新的硬件上实现API),大多数通用代码实现的功能,并且每个司机只需要实现两个函数NIOC *同步程序的核心,一个戒指在netmap模式初始化函数,和一个函数导出设备驱动锁到公共代码。这将单个驱动程序的更改(主要是机械的)减少到每个大约500行，(一个典型的设备驱动程序有4k ..10k行代码)。netmap目前支持Intel 10gbit /s适配器(ixgbe驱动程序)和各种1gbit /s适配器(Intel、RealTek、nvidia)。

在netmap体系结构中，设备驱动程序在执行系统调用期间，在用户空间进程的上下文中完成它们的大部分工作(归结为同步网卡和netmap环)。这改进了缓存局域性，简化了资源管理(例如将进程绑定到特定的核心)，并使系统更加可控和健壮，因为我们不需要担心在不可中断的上下文中执行太多代码。我们通常修改网卡驱动程序，使中断服务程序除了唤醒任何休眠的进程之外不工作。这意味着中断缓解延迟将直接传递给用户进程。

一些微不足道的优化在性能方面也有巨大的回报。例如，如果使用avail > 0调用系统调用，我们不会回收传输的缓冲区或寻找更多的传入数据包。这有助于不必要地对每个包调用系统调用的应用程序。另外两种优化(即使没有指定POLLOUT，也会输出任何排队等待传输的数据包; 并且在poll()返回之前更新netmap环内的时间戳)将典型事件循环每次迭代中的系统调用数量从3个减少到1个——对于某些应用程序来说，这再次是一个显著的性能增强。

到目前为止，我们还没有尝试过与使用预取指令或数据放置相关的优化来改善缓存行为

## **5. 技术性能分析**

我们将通过分析简单I/O函数的行为来讨论框架的性能，然后研究运行在netmap上的更复杂的应用程序。在给出我们的结果之前，详细定义测试条件是很重要的。

### **5.1 性能标准**

数据包的处理涉及多个子系统:CPU管道，缓存，内存和I/O总线。有趣的应用程序是CPU绑定的，所以我们将关注CPU成本的度量。具体来说，我们将测量在应用程序和网卡之间移动数据包所执行的工作(系统成本)。这正是netmap或其他数据包i /O api负责的任务。我们可以将这些成本分成两部分:

i) 每字节成本是CPU周期从/到网卡的缓冲区移动数据(为读或写数据包)。在某些情况下，该组件可以等于零:例如，netmap将网卡缓冲区导出到应用程序，因此它没有每字节的系统成本。其他API,比如套接字API,征收数据复制到移动交通从/到用户空间,这有一个每字节CPU成本,考虑到内存总线的宽度和CPU和内存总线时钟之间的比例,可以在0.25到2时钟周期/字节的范围。

ii) 每个包的开销有多个来源。CPU至少必须为每个数据包更新一个网卡环插槽。此外，根据软件体系结构的不同，每个包可能需要额外的工作，如内存分配、系统调用、编程NIC的寄存器、更新统计数据等等。在某些情况下，第二个集合中的部分操作可以被删除或分摊到多个包上。

在大多数情况下(netmap当然是这样)，每个包的开销是主要的组成部分，就系统负载而言，最具挑战性的情况是由尽可能小的包遍历链路。因此，我们使用64字节数据包(60+4 CRC)运行我们的大多数测试。

当然，为了运行系统并测量其性能，我们需要运行一些测试代码，但我们希望它尽可能简单，以减少对测量的干扰。我们的初始化应用程序的成本几乎可以忽略不计:包生成器流预生成的包，包接收器只计算传入的包。

### **5.2 测试设备**

我们在配备了2.93 GHz的i7-870 4核CPU(带涡轮增压的3.2 GHz)、1.33 GHz的内存和基于Intel 82599 NIC的双端口10gbit /s卡的系统上运行了我们的大部分实验。本文中报道的数字指的是2012年4月FreeBSD HEAD/amd64中的netmap版本。在两个类似的系统上使用直接连接的卡进行了实验。结果是高度可重复性的(在2%以内或更少)，所以我们不报告表格和图表中的置信区间。

netmap非常高效，因此即使在最大包速率下，它也会使10gbit /s接口饱和，我们需要以较低的时钟速度运行系统，以确定性能限制和代码更改的影响。我们的系统可以从一组离散的值中获得不同频率的时钟。名义上，它们大多数是150 MHz的倍数，但我们不知道时钟速度有多精确，也不知道CPU和内存/总线时钟速度之间的关系。

传输速度(以数据包每秒为单位)已经用一个类似于4.2.3节中的数据包生成器来测量。可以在运行时配置包大小，以及用于发送/接收流量的队列和线程/核的数量。数据包是预先准备好的，这样我们就可以以接近于零的每字节成本运行测试。测试程序围绕poll()循环，每轮最多发送B个包(批处理大小)。在接收端，我们使用类似的程序，只不过这次我们轮询读事件，并且只对包进行计数。

### **5.3 传输速度与时钟速率**

作为第一个实验，我们使用可变时钟速度和核数运行生成器，使用大量的批处理，这样系统调用成本几乎可以忽略不计。通过降低时钟频率，我们可以确定系统受CPU限制的点，并估计每个包所花费的(平摊)周期数。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204255_79445.jpg)

图5:与pktgen (linux上专用的内核内生成器，峰值约为4 Mpps)和netsend相比，64字节包、可变时钟速率和内核数量的Netmap传输性能(FreeBSD用户空间，峰值为1.05 Mpps)。

图5显示了使用1..4核和相同数量的环，64字节数据包。吞吐量与时钟速度相当匹配，在1核的情况下达到接近900 MHz的最大线路速率。这对应于60-65 cycles/packet，这个值与我们的期望是合理的。实际上，在这个特定的测试中，每个包的工作仅限于验证netmap环中插槽的内容并更新NIC环中相应的插槽。缓存未命中的代价(确实存在，特别是在NIC环上)被摊销在所有符合缓存线的描述符中，而其他的代价(比如读/写NIC的寄存器)被摊销在整个批处理中。

一旦系统达到行速率，增加时钟速度会减少总的CPU使用，因为生成器会休眠，直到NIC的中断报告新缓冲区的可用性。这种现象不是线性的，它取决于中断缓解间隔的持续时间。对于一个核心，我们在900 MHz时测量了100%的CPU负载，在1.2 GHz时测量了80%，在全速时测量了55%。

多核的扩展是相当不错的，但数字不是特别有趣，因为在这种类型的实验中没有重要的争用点，而且我们只有少量的操作点(1..4核，150,300,450mhz)到达链路饱和。

仅供参考，图5还报告了两个数据包生成器的最大吞吐量，它们代表了使用标准api可以实现的性能。底部的一行表示netsend，一个运行在原始套接字之上的FreeBSD用户空间应用程序。在最高时钟速度下，netsend峰值为1.05 Mpps。图2详细说明了950 ns/pkt是如何使用的。

图中的另一条线是pktgen, Linux中可用的内核包生成器，在最高时钟速度下可以达到4 Mpps，在1.2 GHz(我们可以在Linux中设置的最低速度)下达到2 Mpps。在这里，我们没有详细描述时间是如何花费的，但是设备驱动程序和应用程序架构的相似性表明，大部分成本都在设备驱动程序本身。

接收的速度和时钟结果与发送的相似。netmap可以在900mhz的1核上实现行速率，至少对于64字节的包大小的倍数。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204255_93413.jpg)

图6:不同包大小的实际发送和接收速度(不包括以太网CRC)。上面的曲线是发送速率，下面的曲线是接收速率。看到第5.4节解释

### **5.4 速度与包大小**

前面的实验使用了最小大小的数据包，就每个数据包开销而言，这是最关键的情况。64字节包与系统中不同路径上的总线宽度匹配得非常好，这有助于提高系统的性能。然后我们检查了数据包大小的变化是否对系统的性能有影响，无论是在发送端还是接收端。

可变数据包大小的传输速度显示了预期的1/size行为，如图6中的上曲线所示。相反，接收端显示了一些令人惊讶的情况，如图6中底部曲线所示。最大速率(与CPU速度无关)仅在包大小为64倍时才能实现(或者足够大，从而使总数据速率较低)。在其他尺寸下，接收性能下降(例如，在英特尔cpu上，在65到127字节之间，平均约7.5 Mpps;在AMD cpu上，这个值略高)。调查表明，网卡和/或I/O桥为不是完整缓存线的写发出读-修改-写周期。改变操作模式，从接收的数据包中移除CRC，将“甜蜜点”移动4字节(即64+4,128+4等实现行率，其他没有)。我们发现，在一些网卡(包括1gbit /s的网卡)中，在某些包大小和发送或接收模式中无法实现线速率。

### **5.5 传输速度与批大小**

批量操作提高了系统的吞吐量，因为它摊平了系统调用和其他潜在昂贵操作的成本，比如访问NIC的寄存器。但并不是所有的应用程序都有这种特权，在某些情况下，它们被迫在每几个包上发出一个系统调用的情况下操作。然后，我们使用不同的批处理大小和最小大小的数据包(64字节，包括以太网CRC)运行了另一组实验，试图确定批处理大小如何影响吞吐量。在这个特定的测试中，我们只使用了一个核心和可变数量的队列。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204256_30346.jpg)

图7:1核2.93 GHz的发射性能64字节数据包，不同的批处理大小

结果如图7所示:吞吐量从约2.45 Mpps (408 ns/pkt)开始，batch size为1，并随着batch size快速增长，达到line rate (14.88 Mpps)， batch为8 packets。没有调用特定于netmap的轮询处理程序(netmap poll()和ixgbe txsync())的标准FreeBSD poll()的开销大约是250 ns，所以如果我们想要达到10gbit /s的线路速率和更快的接口，那么每次调用处理多个数据包是绝对必要的。

### **5.6 报文转发性能**

到目前为止，我们已经测量了在线路和应用程序之间移动数据包的成本。这包括操作系统开销，但不包括任何显著的应用程序成本，以及任何数据接触操作。然后，度量netmap API在被cpu密集型任务使用时的好处是很有趣的。分组转发是分组处理系统的主要应用之一，也是我们的框架的一个很好的测试用例。事实上，它涉及同时接收和传输(因此可能导致内存和总线争用)，可能涉及一些数据包处理，这会消耗CPU周期，并导致管道停滞和缓存冲突。与目前使用的简单应用程序相比，所有这些现象可能会降低使用快速数据包I/O机制的好处。

然后，我们研究了一些包转发应用程序在使用新API时的行为，直接使用或通过第4.6节中描述的libpcap兼容库。测试用例如下:

- netmap-fwd，一个简单的应用程序，使用第4.5节所示的零复制技术在接口之间转发数据包;
- Netmap-fwd + pcap，如上所述，但使用libpcap仿真代替零拷贝代码;
- 这个实验使用了系统的libpcap和netmap，并在netmap上使用了libpcap仿真库;
- click-EtherSwitch，如上所述，但将两个队列替换为一个EtherSwitch元素;
- openvswitch，使用用户空间转发的openvswitch软件，既使用系统的libpcap，也在netmap的顶部;
- bsd-bridge，内核中的FreeBSD桥接，使用基于mbuf的设备驱动程序。

图8报告了测量的性能。所有实验都是在具有两个10gbit /s接口的单个核心上运行的，除了第一种情况，即链路饱和仅为1.733 GHz。

从这个实验中我们可以得出一些有趣的观察结果:

- 本机网络地图转发，无数据触摸操作，容易达到线路速率。这很有趣，因为这意味着即使是单个核也可以实现全速率双向操作;
- libpcap仿真库给前面的情况增加了很大的开销(在完整时钟下为7.5 Mpps，而在1.733 GHz下为14.88 Mpps，这意味着每个包大约有80-100 ns的差异)。我们还没有研究是否/如何改进这一点(例如，使用预取);
- 使用基于netmap的libpcap模拟替换系统的libpcap, OpenvSwitch和Click将加速4到8倍，尽管pcap inject()确实使用了数据副本。这也是一个重要的结果，因为它意味着现实生活中的应用程序可以从我们的API中获益。

### **5.7 讨论**

存在巨大的性能提升那些呈现在图5和图8中,这表明netmap 4 - 40倍类似的应用程序使用标准的api,有人可能会怀疑我)有多公平比较,和2)的贡献是什么各种机制来提高性能。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204257_31149.jpg)

图8:使用各种软件配置转发测试硬件的性能

netmap比使用标准api的类似应用程序快4到40倍，人们可能想知道i)这种比较有多公平，ii)各种机制对性能改进的贡献是什么。

第一个问题的答案是，这种比较确实是公平的。图5中的所有流量生成器都执行完全相同的操作，并且每个生成器都试图以最有效的方式执行，只受其使用的底层api的限制。答案在图8中更加明显，在许多情况下，我们只是在两个不同的libpcap实现之上使用相同的未经修改的二进制文件。

在不同配置下测量的结果还让我们回答第二个问题——评估不同优化对netmap性能的影响。

如第5.6节所示，数据拷贝的开销比较大，但是它们不能阻止显著的加速(比如在libpcap+netmap之上实现7.5 Mpps转发数据包)。

每个包的系统调用当然起着重要的作用，netsend和pktgen之间的差异(尽管在不同的平台上)，或者使用小批量时包生成器的低性能都证明了这一点。

最后，一个关于基于skbuf/mbuf API成本的有趣观察来自pktgen(大约花费250 ns/pkt)和基于netmap的包生成器的比较，后者每个包只花费20-30 ns，用于编程NIC。这两个应用程序本质上的区别只是包缓冲区的管理方式不同，因为系统调用和内存副本的摊销成本在这两种情况下都可以忽略不计。5.8应用程序移植我们最后简要讨论了在将现有应用程序适应netmap时遇到的问题。我们的libpcap模拟库是标准模拟库的一种替代，但是应用程序中的其他性能瓶颈可能会阻止我们提供的更快的I/O子系统的利用。情况就是这样。

### **5.8 应用程序移植**

最后，我们简要讨论了在将现有应用程序适应netmap时遇到的问题。我们的libpcap模拟库是标准模拟库的一种替代，但是应用程序中的其他性能瓶颈可能会阻止我们提供的更快的I/O子系统的利用。这正是我们在两个应用程序中遇到的情况，OpenvSwitch和Click(详细信息在[19]中描述)。

在OpenvSwitch的例子中，原始代码(带有userspace/libpcap转发模块)有一个非常昂贵的事件循环，只能执行少于70 Kpps的任务。用基于netmap的版本替换本机libpcap几乎没有任何可测量的改进。在重新构造事件循环并将系统拆分为两个进程之后，本机性能提高到了780 Kpps，而基于netmap的libpcap进一步将转发性能提高到了近3 Mpps。

在Click的例子中，罪魁祸首是c++分配器，它比管理固定大小的包缓冲区的私有池要昂贵得多。在netmap上运行时，替换内存分配器将转发性能从1.3 Mpps提高到3.95 Mpps，在标准libpcap上运行时从0.40 Mpps提高到0.495 Mpps。点击用户空间现在实际上比内核版本要快，原因是昂贵的设备驱动程序和在2.3节中讨论的sk buff管理开销。

## **6. 结论和未来工作**

我们已经介绍了netmap，这是一个为用户空间应用程序提供一个非常快的通道来与网络适配器交换原始数据包的框架。netmap不依赖于特殊的硬件特性，它的设计对网卡的功能做了非常合理的假设。我们的测量表明，netmap可以给使用低/级数据包I/O(数据包捕获和生成工具，软件路由器，防火墙)的广泛应用程序带来巨大的性能改进。FreeBSD和Linux版本的存在，有限的操作系统依赖，以及libpcap仿真库的可用性，使我们有信心netmap可以成为一个有用的和流行的工具，用于开发低水平，高性能的网络软件。

一个有趣的开放式问题和未来工作的主题是，netmap体系结构的特性如何被主机传输/网络堆栈利用，以及它们如何帮助在虚拟化平台中构建高效的网络支持。

关于这项工作的更多信息，包括源代码和未来发展的更新，可以在该项目的页面[17]上找到。

## 7. 参考文献

[1] The dag project. Tech. rep., University of Waikato, 2001.

[2] DERI, L. Improving passive packet capture: Beyond device polling. In SANE 2004, Amsterdam.

[3] DERI, L. ncap: Wire-speed packet capture and transmission. In Workshop on End-to-End Monitoring Techniques and Services (2005), IEEE, pp. 47–55

[4] DOBRESCU, M., EGI, N., ARGYRAKI, K., CHUN, B., FALL, K., IANNACCONE, G., KNIES, A., MANESH, M., AND RATNASAMY, S. Routebricks: Exploiting parallelism to scale software routers. In ACM SOSP (2009), pp. 15–28.

[5] HAN, S., JANG, K., PARK, K., AND MOON, S. Packetshader: a gpu-accelerated software router. ACM SIGCOMM Computer Communication Review 40, 4 (2010), 195–206.

[6] HANDLEY, M., HODSON, O., AND KOHLER, E. Xorp: An open platform for network research. ACM SIGCOMM Computer Communication Review 33, 1 (2003), 53–57.

[7] HEYDE, A., AND STEWART, L. Using the Endace DAG 3.7 GF card with FreeBSD 7.0. CAIA, Tech. Rep. 080507A, May 2008.[Online]. Available: [http://caia](https://link.zhihu.com/?target=http%3A//caia). swin. edu. au/reports/080507A/CAIA-TR-080507A. pdf (2008).

[8] INTEL. Intel data plane development kit. [http://edc.intel.com/Link.aspx?id=5378](https://link.zhihu.com/?target=http%3A//edc.intel.com/Link.aspx%3Fid%3D5378) (2012).

[9] JACOBSON, V., AND FELDERMAN, B. Speeding up networking. Linux Conference Au, [http://www.lemis.com/grog/Documentation/vj/lca06vj.pdf](https://link.zhihu.com/?target=http%3A//www.lemis.com/grog/Documentation/vj/lca06vj.pdf) .

[10] KOHLER, E., MORRIS, R., CHEN, B., JANNOTTI, J., AND KAASHOEK, M. The click modular router. ACM Transactions on Computer Systems (TOCS) 18, 3 (2000), 263–297.

[11] KRASNYANSKY, M. Uio-ixgbe. Qualcomm, [https://opensource.qualcomm.com/wiki/UIO-IXGBE](https://link.zhihu.com/?target=https%3A//opensource.qualcomm.com/wiki/UIO-IXGBE).

[12] LOCKWOOD, J. W., MCKEOWN, N., WATSON, G., ET AL. Netfpga–an open platform for gigabit-rate network switching and routing. IEEE Conf. on Microelectronics Systems Education (2007).

[13] [http://LWN.NET](https://link.zhihu.com/?target=http%3A//LWN.NET) ARTICLE 192767. Reconsidering network channels. [http://lwn.net/Articles/192767/](https://link.zhihu.com/?target=http%3A//lwn.net/Articles/192767/).

[14] MCCANNE, S., AND JACOBSON, V. The bsd packet filter: A new architecture for user-level packet capture. In USENIX Winter Conference (1993), USENIX Association.

[15] MCKEOWN, N., ANDERSON, T., BALAKRISHNAN, H., PARULKAR, G., PETERSON, L., REXFORD, J., SHENKER, S., AND TURNER, J. Openflow: enabling innovation in campus networks. ACM SIGCOMM Computer Communication Review 38 (March 2008), 69–74.

[16] MOGUL, J., AND RAMAKRISHNAN, K. Eliminating receive livelock in an interrupt-driven kernel. ACM Transactions on Computer Systems (TOCS) 15, 3 (1997), 217–252.

[17] RIZZO, L. Netmap home page. Universit`a di Pisa, [http://info.iet.unipi.it/](https://link.zhihu.com/?target=http%3A//info.iet.unipi.it/)∼luigi/netmap/.

[18] RIZZO, L. Polling versus interrupts in network device drivers. BSDConEurope 2001 (2001).

[19] RIZZO, L., CARBONE, M., AND CATALLI, G. Transparent acceleration of software packet forwarding using netmap. INFOCOM’12, Orlando, FL, March 2012, [http://info.iet.unipi.it/](https://link.zhihu.com/?target=http%3A//info.iet.unipi.it/)∼luigi/netmap/.

[20] SCHUTZ, B., BRIGGS, E., AND ERTUGAY, O. New techniques to develop low-latency network apps. [http://channel9.msdn.com/Events/BUILD/BUILD2011/SAC593T](https://link.zhihu.com/?target=http%3A//channel9.msdn.com/Events/BUILD/BUILD2011/SAC593T).

[21] SOLARFLARE. Openonload. [http://www.openonload.org/](https://link.zhihu.com/?target=http%3A//www.openonload.org/) (2008).

[22] STEVENS, W., AND WRIGHT, G. TCP/IP illustrated (vol. 2): the implementation. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, USA, 1995

原文链接：https://zhuanlan.zhihu.com/p/391083793
原文作者：零声Github整理库