<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TCP/IP on JasperSao的博客</title><link>https://jasperstonnne.github.io/MyBlog/tags/tcp/ip/</link><description>Recent content in TCP/IP on JasperSao的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 30 Aug 2026 08:12:59 +0000</lastBuildDate><atom:link href="https://jasperstonnne.github.io/MyBlog/tags/tcp/ip/index.xml" rel="self" type="application/rss+xml"/><item><title>DPDK 用户态协议栈设计与实现</title><link>https://jasperstonnne.github.io/MyBlog/p/dpdk-userspace-network-stack/</link><pubDate>Sat, 15 Aug 2026 00:00:00 +0800</pubDate><guid>https://jasperstonnne.github.io/MyBlog/p/dpdk-userspace-network-stack/</guid><description>&lt;img src="https://jasperstonnne.github.io/MyBlog/p/dpdk-userspace-network-stack/cover.svg" alt="Featured image of post DPDK 用户态协议栈设计与实现" /&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;学习目标：理解网卡、网卡驱动、TCP/IP 协议栈、POSIX Socket API 与应用程序之间的关系；搭建 DPDK 环境；使用 DPDK 收取以太网帧；逐步解析 Ethernet、IPv4、UDP、TCP；最终理解用户态协议栈需要补齐哪些能力。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一先建立完整的网络数据路径"&gt;一、先建立完整的网络数据路径&#10;&lt;/h2&gt;&lt;p&gt;可以把网络程序拆成五个逻辑模块：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 网卡 NIC&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↕&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 网卡驱动 Driver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↕&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. TCP/IP 协议栈&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↕&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. POSIX Socket API：socket / bind / listen / accept / recv / send&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↕&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. Application：Redis / Nginx / 自己编写的服务器&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接收数据时，数据总体从左向右、从上向下流动；发送数据时方向相反。&lt;/p&gt;&#10;&lt;p&gt;不过，必须区分下面两条不同的数据路径。&lt;/p&gt;&#10;&lt;h3 id="1-linux-内核协议栈路径"&gt;1. Linux 内核协议栈路径&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;物理网络&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 网卡&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Linux 内核网卡驱动&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → sk_buff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 内核 Ethernet/IP/TCP/UDP 协议栈&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Socket 接收缓冲区&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → recv()/read()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 应用程序&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;应用程序使用的是 Linux 已经实现好的协议栈。Redis、Nginx 默认走的就是这条路径。&lt;/p&gt;&#10;&lt;h3 id="2-dpdk-用户态路径"&gt;2. DPDK 用户态路径&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;物理网络&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 网卡&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → VFIO/UIO + DPDK PMD 用户态驱动&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → RX Descriptor Ring&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → rte_mbuf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 自己实现或引入的用户态协议栈&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 自己实现的 Socket/POSIX 兼容层&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 应用程序&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;网卡绑定给 DPDK 使用的驱动后，该端口的普通数据收发路径通常绕过 Linux 内核网卡驱动和内核 TCP/IP 协议栈。DPDK 负责高速收发原始数据包，但 &lt;strong&gt;DPDK 本身不是 TCP/IP 协议栈&lt;/strong&gt;。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;重要纠错：不能把 DPDK 主路径理解成“网卡 → DPDK → 内核驱动 → 内核 TCP/IP”。正常的 DPDK 用户态收包路径绕过内核协议栈。只有主动使用 KNI、TAP、virtio-user 等机制时，才会把选定的数据包重新送入内核。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="二网卡是什么"&gt;二、网卡是什么&#10;&lt;/h2&gt;&lt;h3 id="1-网卡的基本职责"&gt;1. 网卡的基本职责&#10;&lt;/h3&gt;&lt;p&gt;网卡（NIC，Network Interface Card）连接计算机和网络介质，主要完成：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;接收和发送物理信号；&lt;/li&gt;&#10;&lt;li&gt;完成物理层编码、解码、时钟恢复等工作；&lt;/li&gt;&#10;&lt;li&gt;识别和生成以太网帧；&lt;/li&gt;&#10;&lt;li&gt;校验帧的 FCS；&lt;/li&gt;&#10;&lt;li&gt;按配置进行 MAC 地址过滤、VLAN 处理；&lt;/li&gt;&#10;&lt;li&gt;通过 DMA 在网卡和主机内存之间搬运数据；&lt;/li&gt;&#10;&lt;li&gt;一些网卡还能完成 checksum、TSO、LRO、RSS 等硬件卸载。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;“网卡把模拟信号通过 AD/DA 转换为 0 和 1”可以帮助入门理解，但并不完全准确。现代以太网 PHY 会进行复杂的线路编码、调制、均衡和信号恢复，不能简单等同于普通 ADC/DAC。&lt;/p&gt;&#10;&lt;h3 id="2-网卡属于哪一层"&gt;2. 网卡属于哪一层&#10;&lt;/h3&gt;&lt;p&gt;网卡不是完全独立于分层模型之外。通常可以这样理解：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;PHY 部分主要工作在物理层；&lt;/li&gt;&#10;&lt;li&gt;MAC 部分主要工作在数据链路层；&lt;/li&gt;&#10;&lt;li&gt;checksum、TSO、RSS 等卸载能力会辅助更高层处理。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;因此，说网卡“主要横跨物理层和数据链路层”更准确。&lt;/p&gt;&#10;&lt;h3 id="3-dma网卡如何把数据交给内存"&gt;3. DMA：网卡如何把数据交给内存&#10;&lt;/h3&gt;&lt;p&gt;网卡不会要求 CPU 一个字节一个字节地读取数据。典型过程如下：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;驱动在内存中准备一组 RX 描述符和数据缓冲区；&lt;/li&gt;&#10;&lt;li&gt;驱动把这些内存地址告诉网卡；&lt;/li&gt;&#10;&lt;li&gt;网卡收到数据后，通过 DMA 直接把数据写入内存缓冲区；&lt;/li&gt;&#10;&lt;li&gt;网卡更新 RX 描述符，标记该数据包已经完成；&lt;/li&gt;&#10;&lt;li&gt;CPU 再处理描述符所指向的数据。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;这里的 RX 描述符通常按环形结构组织，所以常称为 RX Ring 或 Descriptor Ring。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="三网卡驱动做什么"&gt;三、网卡驱动做什么&#10;&lt;/h2&gt;&lt;p&gt;网卡驱动是操作系统或 DPDK 与具体网卡硬件之间的适配层。它通常负责：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;根据 PCI Vendor ID / Device ID 识别硬件；&lt;/li&gt;&#10;&lt;li&gt;初始化网卡寄存器；&lt;/li&gt;&#10;&lt;li&gt;配置 MAC、MTU、收发队列和硬件卸载；&lt;/li&gt;&#10;&lt;li&gt;分配 RX/TX Descriptor Ring；&lt;/li&gt;&#10;&lt;li&gt;建立 DMA 映射；&lt;/li&gt;&#10;&lt;li&gt;启停网卡；&lt;/li&gt;&#10;&lt;li&gt;处理链路状态和错误；&lt;/li&gt;&#10;&lt;li&gt;在中断模式或轮询模式下回收、提交数据包。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Linux 内核中有统一的网络设备框架，e1000、ixgbe、i40e、mlx5、vmxnet3 等具体驱动在这个框架下实现。DPDK 则提供 PMD（Poll Mode Driver），让用户态程序通过轮询直接与网卡队列交互。&lt;/p&gt;&#10;&lt;h3 id="1-中断模式与轮询模式"&gt;1. 中断模式与轮询模式&#10;&lt;/h3&gt;&lt;p&gt;传统内核路径通常结合中断和 NAPI 轮询：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;流量较低时，网卡通过中断通知 CPU；&lt;/li&gt;&#10;&lt;li&gt;流量较高时，NAPI 暂时关闭频繁中断，改用批量轮询；&lt;/li&gt;&#10;&lt;li&gt;这样可以避免“中断风暴”。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;DPDK 的典型方式是 Poll Mode：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;rte_eth_rx_burst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queue_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;线程不断轮询指定的端口和队列，避免中断、调度和上下文切换开销。代价是即使没有数据，轮询线程也可能持续占用一个 CPU 核。&lt;/p&gt;&#10;&lt;h3 id="2-多队列网卡"&gt;2. 多队列网卡&#10;&lt;/h3&gt;&lt;p&gt;多队列网卡拥有多个 RX/TX 队列。其价值在于：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;不同队列可以由不同 CPU 核或线程独立处理；&lt;/li&gt;&#10;&lt;li&gt;减少多个核心竞争同一个队列锁；&lt;/li&gt;&#10;&lt;li&gt;利用 RSS 把不同网络流分散到多个 RX 队列；&lt;/li&gt;&#10;&lt;li&gt;提升并行处理能力和总体吞吐量。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;常见绑定关系是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RX Queue 0 → lcore 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RX Queue 1 → lcore 2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RX Queue 2 → lcore 3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RX Queue 3 → lcore 4&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;RSS 通常根据源/目的 IP、源/目的端口、协议号计算哈希，将同一个流稳定地送到同一队列，避免同一 TCP 流的数据包在多个核心间乱序处理。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;重要纠错：多队列能显著提高并行性，但“不是多队列网卡就一定无法绑定 DPDK”并不准确。单队列设备也可能被支持，只是扩展能力差。能否绑定和运行取决于设备、PMD、IOMMU、VFIO/UIO 以及虚拟化环境等条件。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="3-虚拟机中的-vmxnet3"&gt;3. 虚拟机中的 vmxnet3&#10;&lt;/h3&gt;&lt;p&gt;VMware 中可以在 &lt;code&gt;.vmx&lt;/code&gt; 文件中指定：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ethernet0.virtualDev = &amp;#34;vmxnet3&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ethernet0.wakeOnPcktRcv = &amp;#34;TRUE&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;vmxnet3&lt;/code&gt; 是 VMware 的半虚拟化高性能网卡，通常比模拟的 e1000 拥有更好的性能和多队列能力。修改配置前应关闭虚拟机，并确认实际使用的 &lt;code&gt;ethernet0&lt;/code&gt;、&lt;code&gt;ethernet1&lt;/code&gt; 与目标 MAC 地址相匹配。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="四sk_buffrte_mbuf-与-ring"&gt;四、sk_buff、rte_mbuf 与 Ring&#10;&lt;/h2&gt;&lt;h3 id="1-linux-的-sk_buff"&gt;1. Linux 的 sk_buff&#10;&lt;/h3&gt;&lt;p&gt;Linux 内核用 &lt;code&gt;struct sk_buff&lt;/code&gt; 描述网络包。它不是简单地把所有协议头复制一遍，而是保存：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;数据缓冲区的位置；&lt;/li&gt;&#10;&lt;li&gt;当前数据起点和末尾；&lt;/li&gt;&#10;&lt;li&gt;MAC、网络层和传输层头的位置；&lt;/li&gt;&#10;&lt;li&gt;数据包长度；&lt;/li&gt;&#10;&lt;li&gt;所属设备；&lt;/li&gt;&#10;&lt;li&gt;checksum、VLAN、路由等元数据；&lt;/li&gt;&#10;&lt;li&gt;链表等组织信息。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;协议层之间传递的通常是 &lt;code&gt;sk_buff&lt;/code&gt; 指针。各层通过移动或记录头部位置来解析数据，而不是每经过一层就复制整个包。&lt;/p&gt;&#10;&lt;h3 id="2-海量-sk_buff-如何组织"&gt;2. 海量 sk_buff 如何组织&#10;&lt;/h3&gt;&lt;p&gt;“每包一个 &lt;code&gt;sk_buff&lt;/code&gt;，海量包如何组织”不能只用一种数据结构回答。不同阶段使用不同结构：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;网卡收发描述符：环形队列 Ring；&lt;/li&gt;&#10;&lt;li&gt;NAPI 待处理对象：调度列表；&lt;/li&gt;&#10;&lt;li&gt;Socket 接收队列：链表/队列形式的 skb queue；&lt;/li&gt;&#10;&lt;li&gt;TCP 乱序数据：红黑树等结构；&lt;/li&gt;&#10;&lt;li&gt;TCP 重传数据：按发送顺序管理的队列/树；&lt;/li&gt;&#10;&lt;li&gt;内存对象分配：slab/slub 缓存。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;所以，常用答案是“以 Ring 和各种队列为主，根据查找、排序、重传等需求再使用树结构”。&lt;/p&gt;&#10;&lt;h3 id="3-dpdk-的-rte_mbuf"&gt;3. DPDK 的 rte_mbuf&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;rte_mbuf&lt;/code&gt; 与 &lt;code&gt;sk_buff&lt;/code&gt; 的定位相似：都用来描述一个数据包及其元数据。DPDK 通常从预先创建的 mempool 中分配 mbuf，以减少频繁动态分配的开销。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mempool&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── rte_mbuf + data buffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── rte_mbuf + data buffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── rte_mbuf + data buffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;网卡通过 DMA 把数据写入 mbuf 对应的数据缓冲区。&lt;code&gt;rte_eth_rx_burst()&lt;/code&gt; 返回的是已经装有数据的 mbuf 指针数组，因此 CPU 不需要再把整个数据包从内核复制到用户态。&lt;/p&gt;&#10;&lt;p&gt;这通常称为零拷贝或少拷贝设计，但严格来说仍然可能发生 DMA、缓存行传输、跨 NUMA 访问以及应用层复制，不能把“零拷贝”理解成整个系统完全没有任何数据移动。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="五协议栈是什么"&gt;五、协议栈是什么&#10;&lt;/h2&gt;&lt;p&gt;协议栈是一组按层协作的协议实现。接收一个 UDP/IPv4 以太网帧时，可依次解析：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Ethernet Header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → IPv4 Header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → UDP Header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Application Payload&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接收 TCP 时则是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Ethernet Header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → IPv4 Header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → TCP Header&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → TCP Segment Payload&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;解析协议头只是用户态协议栈的第一步。一个真正可用的协议栈还需要实现 ARP、路由、校验和、分片重组、TCP 状态机、重传、拥塞控制、Socket 缓冲区等功能。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="六以太网帧解析"&gt;六、以太网帧解析&#10;&lt;/h2&gt;&lt;p&gt;不考虑 VLAN 标签时，Ethernet II 头部固定为 14 字节：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0 5 6 11 12 13&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+----------------+----------------+--------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 目的 MAC 6字节 | 源 MAC 6字节 | 类型 2 |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+----------------+----------------+--------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;常见 EtherType：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;0x0800&lt;/code&gt;：IPv4；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;0x0806&lt;/code&gt;：ARP；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;0x86DD&lt;/code&gt;：IPv6；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;0x8100&lt;/code&gt;：802.1Q VLAN。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;DPDK 中对应：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ethhdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_pktmbuf_mtod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;rte_pktmbuf_mtod()&lt;/code&gt; 的作用是把 mbuf 中的数据起始地址转换成指定类型的指针。它并没有复制以太网头，只是在同一段内存上按 &lt;code&gt;struct rte_ether_hdr&lt;/code&gt; 的布局解释字节。&lt;/p&gt;&#10;&lt;p&gt;判断是否为 IPv4：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ethhdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ether_type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;rte_cpu_to_be_16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTE_ETHER_TYPE_IPV4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;网络协议字段通常采用大端字节序，所以要进行主机字节序与网络字节序转换。&lt;/p&gt;&#10;&lt;h3 id="vlan-注意事项"&gt;VLAN 注意事项&#10;&lt;/h3&gt;&lt;p&gt;代码直接把以太网头后的数据当作 IPv4，只适用于没有 VLAN 标签的帧。若 EtherType 为 &lt;code&gt;0x8100&lt;/code&gt;，还要解析 4 字节 VLAN Header，然后才能找到真正的上层 EtherType。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="七ipv4-解析"&gt;七、IPv4 解析&#10;&lt;/h2&gt;&lt;p&gt;IPv4 基础头部最少 20 字节，结构包括：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------+---------+-------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|Version | IHL | DSCP/ECN | Length |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------+---------+-------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Identification | Flags/Fragment Offset|&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| TTL | Protocol | Header Checksum |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Source IPv4 Address |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Destination IPv4 Address |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Options（可选，IHL &amp;gt; 5 时存在） |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键字段：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Version：IPv4 时为 4；&lt;/li&gt;&#10;&lt;li&gt;IHL：头部长度，以 4 字节为单位；最小值为 5，即 20 字节；&lt;/li&gt;&#10;&lt;li&gt;Total Length：整个 IPv4 数据报长度；&lt;/li&gt;&#10;&lt;li&gt;Fragment Offset / Flags：分片相关；&lt;/li&gt;&#10;&lt;li&gt;TTL：每经过一个路由器通常减 1；&lt;/li&gt;&#10;&lt;li&gt;Protocol：上层协议，UDP=17，TCP=6，ICMP=1；&lt;/li&gt;&#10;&lt;li&gt;Header Checksum：只校验 IPv4 头；&lt;/li&gt;&#10;&lt;li&gt;Source/Destination Address：源和目的 IPv4 地址。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;原代码通过固定的 14 字节 Ethernet Header 偏移找到 IPv4 头：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ipv4_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rte_pktmbuf_mtod_offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ipv4_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;判断 UDP：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next_proto_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_UDP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="ipv4-解析必须检查的内容"&gt;IPv4 解析必须检查的内容&#10;&lt;/h3&gt;&lt;p&gt;健壮的实现至少应检查：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;mbuf 中是否有完整的 Ethernet 和 IPv4 基础头；&lt;/li&gt;&#10;&lt;li&gt;IPv4 Version 是否为 4；&lt;/li&gt;&#10;&lt;li&gt;IHL 是否至少为 5；&lt;/li&gt;&#10;&lt;li&gt;包长度是否覆盖 &lt;code&gt;IHL * 4&lt;/code&gt;；&lt;/li&gt;&#10;&lt;li&gt;Total Length 是否合理；&lt;/li&gt;&#10;&lt;li&gt;IPv4 Header Checksum 是否正确，或确认硬件已校验；&lt;/li&gt;&#10;&lt;li&gt;是否为分片；&lt;/li&gt;&#10;&lt;li&gt;再根据 Protocol 分发给 ICMP、UDP 或 TCP。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;原代码使用 &lt;code&gt;(iphdr + 1)&lt;/code&gt; 寻找 UDP 头，相当于默认 IPv4 头固定为 20 字节。若 IPv4 带 Options，IHL 会大于 5，UDP 头的真实起点应为 &lt;code&gt;IP Header 起点 + IHL * 4&lt;/code&gt;。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="八udp-解析"&gt;八、UDP 解析&#10;&lt;/h2&gt;&lt;p&gt;UDP 头固定为 8 字节：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------+-------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Source Port | Destination Port |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------+-------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| UDP Length | UDP Checksum |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------+-------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Payload ... |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键字段：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Source Port：源端口；&lt;/li&gt;&#10;&lt;li&gt;Destination Port：目的端口；&lt;/li&gt;&#10;&lt;li&gt;Length：UDP 头加 Payload 的总长度，最小为 8；&lt;/li&gt;&#10;&lt;li&gt;Checksum：覆盖伪首部、UDP 头和 UDP 数据。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;原代码：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_udp_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;udphdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_udp_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;udp:%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;udphdr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一行把 IPv4 基础头后的地址解释为 UDP Header；第二行把 UDP Header 后的数据解释为 C 字符串并打印。&lt;/p&gt;&#10;&lt;h3 id="为什么-s-只适合当前演示"&gt;为什么 &lt;code&gt;%s&lt;/code&gt; 只适合当前演示&#10;&lt;/h3&gt;&lt;p&gt;网络 Payload 不保证以 &lt;code&gt;\0&lt;/code&gt; 结尾，而 &lt;code&gt;%s&lt;/code&gt; 会持续读取，直到遇到 &lt;code&gt;\0&lt;/code&gt;。因此实际代码必须：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;根据 UDP Length 计算 Payload 长度；&lt;/li&gt;&#10;&lt;li&gt;确认长度没有超过 mbuf/IPv4 数据长度；&lt;/li&gt;&#10;&lt;li&gt;使用带长度的输出方式，例如 &lt;code&gt;%.*s&lt;/code&gt;；&lt;/li&gt;&#10;&lt;li&gt;对二进制协议应使用十六进制打印，而不是字符串打印。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;当前 Packet Sender 发送的是短 ASCII 文本，并且内存后面恰好出现零字节，所以演示中可能正常输出，但这不是安全的通用处理方法。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="九tcp-解析"&gt;九、TCP 解析&#10;&lt;/h2&gt;&lt;p&gt;TCP 头最少为 20 字节，可带 Options，最长可达 60 字节。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------+-------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Source Port | Destination Port |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Sequence Number |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Acknowledgment Number |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------+----------+---------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|Offset| Flags | Window |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------+---------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Checksum | Urgent Pointer |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Options（可选） |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Payload ... |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键字段：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sequence Number：本段第一个字节的序号；&lt;/li&gt;&#10;&lt;li&gt;Acknowledgment Number：期望收到的下一个序号；&lt;/li&gt;&#10;&lt;li&gt;Data Offset：TCP Header 长度，以 4 字节为单位；&lt;/li&gt;&#10;&lt;li&gt;Flags：SYN、ACK、FIN、RST、PSH、URG 等；&lt;/li&gt;&#10;&lt;li&gt;Window：接收窗口，参与流量控制；&lt;/li&gt;&#10;&lt;li&gt;Checksum：覆盖 IPv4/IPv6 伪首部、TCP 头和 TCP 数据。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;只把 TCP Header 拆出来还不能叫“实现 TCP”。至少还需要：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;四元组查找连接：源 IP、源端口、目的 IP、目的端口；&lt;/li&gt;&#10;&lt;li&gt;TCP 状态机；&lt;/li&gt;&#10;&lt;li&gt;三次握手和四次挥手；&lt;/li&gt;&#10;&lt;li&gt;序列号和 ACK；&lt;/li&gt;&#10;&lt;li&gt;乱序重组；&lt;/li&gt;&#10;&lt;li&gt;超时重传；&lt;/li&gt;&#10;&lt;li&gt;RTT/RTO 估计；&lt;/li&gt;&#10;&lt;li&gt;滑动窗口和流量控制；&lt;/li&gt;&#10;&lt;li&gt;拥塞控制；&lt;/li&gt;&#10;&lt;li&gt;TIME_WAIT 等定时器管理。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="tcp-状态机简图"&gt;TCP 状态机简图&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;服务器：CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;客户端：CLOSED → SYN_SENT → ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;主动关闭的一方：ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;被动关闭的一方：ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="十posix-socket-api-与用户态协议栈"&gt;十、POSIX Socket API 与用户态协议栈&#10;&lt;/h2&gt;&lt;p&gt;POSIX API 是应用程序看到的接口：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;socket()&lt;/code&gt;：创建 Socket；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;bind()&lt;/code&gt;：绑定本地 IP 和端口；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;listen()&lt;/code&gt;：把 TCP Socket 设为监听状态；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;accept()&lt;/code&gt;：取出一个已经建立的 TCP 连接；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;connect()&lt;/code&gt;：主动发起 TCP 连接；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;recv()/read()&lt;/code&gt;：读取接收数据；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;send()/write()&lt;/code&gt;：发送数据；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;close()&lt;/code&gt;：关闭 Socket。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="1-内核中的关系"&gt;1. 内核中的关系&#10;&lt;/h3&gt;&lt;p&gt;在 Linux 内核协议栈中，文件描述符 &lt;code&gt;fd&lt;/code&gt; 会关联到内核 Socket 对象。TCP 连接还对应一个传输控制块，可广义称为 TCB，其中保存：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;四元组；&lt;/li&gt;&#10;&lt;li&gt;TCP 状态；&lt;/li&gt;&#10;&lt;li&gt;序列号；&lt;/li&gt;&#10;&lt;li&gt;收发窗口；&lt;/li&gt;&#10;&lt;li&gt;重传队列；&lt;/li&gt;&#10;&lt;li&gt;接收队列；&lt;/li&gt;&#10;&lt;li&gt;定时器等。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;code&gt;recv(fd, ...)&lt;/code&gt; 不是简单地“直接读取 TCB 某个位置”，而是通过 fd 找到 Socket，再从该 Socket 的接收缓冲区读取已经按序整理好的字节。如果没有数据，调用可能阻塞，或者在非阻塞模式下返回 &lt;code&gt;EAGAIN&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h3 id="2-用户态协议栈怎样提供-posix-api"&gt;2. 用户态协议栈怎样提供 POSIX API&#10;&lt;/h3&gt;&lt;p&gt;如果应用完全绕过内核协议栈，Linux 原生 &lt;code&gt;recv()&lt;/code&gt; 不会自动读取 DPDK mbuf。需要增加适配层，常见方法包括：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;修改应用，让它调用用户态协议栈自己的 API；&lt;/li&gt;&#10;&lt;li&gt;使用库替换或 LD_PRELOAD 截获 Socket 调用；&lt;/li&gt;&#10;&lt;li&gt;提供兼容 POSIX 的用户态 Socket 库；&lt;/li&gt;&#10;&lt;li&gt;通过事件通知机制实现类似 epoll 的接口。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;用户态需要自己维护：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fd/table&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → socket object&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → UDP endpoint 或 TCP TCB&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → receive queue / send queue&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于 UDP，&lt;code&gt;recvfrom()&lt;/code&gt; 通常从对应端口的报文队列取出一个 Datagram；对于 TCP，&lt;code&gt;recv()&lt;/code&gt; 从已经重排并确认连续的字节流中读取数据。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十一dpdk-是什么以及为什么快"&gt;十一、DPDK 是什么，以及为什么快&#10;&lt;/h2&gt;&lt;p&gt;DPDK（Data Plane Development Kit）是一组用于高速数据包处理的库、驱动和工具。它的核心思想包括：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;用户态 PMD 轮询网卡；&lt;/li&gt;&#10;&lt;li&gt;批量收发 Burst；&lt;/li&gt;&#10;&lt;li&gt;预分配 mempool/mbuf；&lt;/li&gt;&#10;&lt;li&gt;Hugepage；&lt;/li&gt;&#10;&lt;li&gt;CPU 亲和性和独占核心；&lt;/li&gt;&#10;&lt;li&gt;NUMA 感知；&lt;/li&gt;&#10;&lt;li&gt;无锁 Ring；&lt;/li&gt;&#10;&lt;li&gt;硬件多队列与 RSS；&lt;/li&gt;&#10;&lt;li&gt;Prefetch 和缓存友好布局；&lt;/li&gt;&#10;&lt;li&gt;减少中断、系统调用、上下文切换和数据复制。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="dpdk-优化的核心指标"&gt;DPDK 优化的核心指标&#10;&lt;/h3&gt;&lt;p&gt;DPDK 不只是“处理大包、提高吞吐量”。更准确地说，它常用于提高：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;每秒处理的数据包数 PPS，尤其是大量小包；&lt;/li&gt;&#10;&lt;li&gt;总吞吐量；&lt;/li&gt;&#10;&lt;li&gt;延迟的可预测性和尾延迟；&lt;/li&gt;&#10;&lt;li&gt;多核心扩展能力。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;是否能提升 Redis/Nginx 性能，取决于瓶颈：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;如果瓶颈在业务逻辑、锁、存储或单线程执行，只有 DPDK 不会神奇地提高 QPS；&lt;/li&gt;&#10;&lt;li&gt;如果瓶颈在内核网络路径、软中断、系统调用或数据复制，用户态网络方案可能改善 QPS 或延迟；&lt;/li&gt;&#10;&lt;li&gt;把现有 Redis/Nginx 接到 DPDK 上需要完整协议栈和兼容层，并非简单绑定网卡即可。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="常见使用场景"&gt;常见使用场景&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;防火墙、ACL、DPI；&lt;/li&gt;&#10;&lt;li&gt;路由器、交换机、网关；&lt;/li&gt;&#10;&lt;li&gt;负载均衡器；&lt;/li&gt;&#10;&lt;li&gt;5G/电信数据面；&lt;/li&gt;&#10;&lt;li&gt;虚拟交换机；&lt;/li&gt;&#10;&lt;li&gt;流媒体或报文转发；&lt;/li&gt;&#10;&lt;li&gt;流量采集与监控；&lt;/li&gt;&#10;&lt;li&gt;高性能存储网络的数据面。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;数据备份和 RDMA 可能使用类似的大页、DMA、零拷贝、用户态驱动思想，但 DPDK 与 RDMA 是不同的技术体系，不能简单说“底层一定用 DPDK，应用层用 RDMA”。&lt;/p&gt;&#10;&lt;p&gt;可研究的用户态协议栈/数据面项目包括 mTCP、lwIP、F-Stack、VPP、Seastar networking、基于 BSD 协议栈移植的实现等。项目是否仍活跃、DPDK 版本是否兼容，需要按实际版本确认。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十二vfiouiopcikni-与-numa"&gt;十二、VFIO、UIO、PCI、KNI 与 NUMA&#10;&lt;/h2&gt;&lt;h3 id="1-pci-地址"&gt;1. PCI 地址&#10;&lt;/h3&gt;&lt;p&gt;网卡通常是 PCIe 设备。可以使用：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lspci&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lspci -nn&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lspci -k&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PCI 地址形如：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0000:1a:00.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;含义是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;domain:bus:device.function&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-vfio-与-uio"&gt;2. VFIO 与 UIO&#10;&lt;/h3&gt;&lt;p&gt;它们帮助用户态程序访问 PCI 设备资源：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;UIO：较简单的通用用户态 I/O 框架；&lt;/li&gt;&#10;&lt;li&gt;VFIO：通常配合 IOMMU，隔离性和安全性更好，现代环境一般优先使用；&lt;/li&gt;&#10;&lt;li&gt;DPDK PMD：真正理解并操作具体网卡寄存器和队列的用户态驱动。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;不能简单理解为 VFIO/UIO“截获 PCI 地址”；更准确地说，它们把设备资源安全地映射或暴露给用户态驱动，并管理中断、DMA/IOMMU 等能力。&lt;/p&gt;&#10;&lt;h3 id="3-kni"&gt;3. KNI&#10;&lt;/h3&gt;&lt;p&gt;KNI 曾用于在 DPDK 应用与 Linux 内核网络栈之间交换数据包。例如，把控制面流量送回内核处理。但它不是 DPDK 收包必须经过的组件，而且在较新的 DPDK 方案中通常会考虑 TAP、virtio-user 等替代路径。&lt;/p&gt;&#10;&lt;h3 id="4-numa"&gt;4. NUMA&#10;&lt;/h3&gt;&lt;p&gt;NUMA 是 Non-Uniform Memory Access，即非一致内存访问。多路 CPU 服务器中，每个 CPU Socket 通常拥有本地内存：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CPU Socket 0 ↔ Local Memory 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CPU Socket 1 ↔ Local Memory 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CPU 访问本地内存比跨 Socket 访问远端内存更快。因此应尽量做到：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;网卡所在 NUMA Node；&lt;/li&gt;&#10;&lt;li&gt;轮询该网卡的 CPU 核；&lt;/li&gt;&#10;&lt;li&gt;mbuf mempool 所在内存；&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;三者位于同一个 NUMA Node。&lt;/p&gt;&#10;&lt;p&gt;代码中的 &lt;code&gt;rte_eth_dev_socket_id()&lt;/code&gt; 用于查询设备所在 NUMA Socket，&lt;code&gt;rte_socket_id()&lt;/code&gt; 返回当前 lcore 所在 Socket。二者不一定总是相同。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十三hugepage-大页"&gt;十三、Hugepage 大页&#10;&lt;/h2&gt;&lt;p&gt;普通页通常为 4 KiB。Hugepage 常见规格为：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;2 MiB；&lt;/li&gt;&#10;&lt;li&gt;1 GiB。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;DPDK 使用大页主要是为了：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;减少页表项数量；&lt;/li&gt;&#10;&lt;li&gt;降低 TLB Miss；&lt;/li&gt;&#10;&lt;li&gt;便于大块、稳定、可用于 DMA 的内存管理；&lt;/li&gt;&#10;&lt;li&gt;减少运行时内存分配的不确定性。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="1-示例-grub-配置"&gt;1. 示例 GRUB 配置&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GRUB_CMDLINE_LINUX_DEFAULT=&amp;#34;default_hugepagesz=2M hugepagesz=2M hugepages=1024&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这表示默认大页为 2 MiB，共预留 1024 页，即约 2 GiB 内存。虚拟机内存有限时，需要根据虚拟机总内存调整，不能机械照抄。&lt;/p&gt;&#10;&lt;p&gt;通常修改后还需要更新 GRUB 并重启，例如 Ubuntu/Debian：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo update-grub&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo reboot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;重启后验证：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -i huge /proc/meminfo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;具体命令因发行版和 DPDK 版本而异。现代 DPDK 可能自动处理 hugetlbfs，也可以手动挂载。1 GiB 大页还依赖 CPU 支持，并通常需要在启动参数中预留。&lt;/p&gt;&#10;&lt;h3 id="2-hugepage-不直接提升网卡物理转换速度"&gt;2. Hugepage 不直接提升网卡物理转换速度&#10;&lt;/h3&gt;&lt;p&gt;Hugepage 优化的是主机内存管理、TLB 和 DMA 缓冲区访问，并不会让网卡的光电信号转换本身更快。它提升的是数据包在 CPU/内存数据面中的处理效率和稳定性。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十四vmware-三种网络模式"&gt;十四、VMware 三种网络模式&#10;&lt;/h2&gt;&lt;h3 id="1-bridged桥接"&gt;1. Bridged（桥接）&#10;&lt;/h3&gt;&lt;p&gt;虚拟机像局域网中的独立主机：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;物理局域网&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── Windows/macOS 宿主机：172.26.185.5&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── 虚拟机：172.26.185.250&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;虚拟机通常与宿主机处于同一二层网络，可从局域网 DHCP 获取地址，或配置同网段静态地址。桥接绑定到某个宿主机物理接口时，该接口断开可能影响虚拟机网络。&lt;/p&gt;&#10;&lt;p&gt;“复制物理网络连接状态”之类的选项，通常让虚拟网卡随宿主机物理接口的连接/断开状态变化，适合笔记本在 Wi-Fi、有线网络之间切换的场景。不同 VMware 产品中的名称和细节会略有不同。&lt;/p&gt;&#10;&lt;h3 id="2-nat"&gt;2. NAT&#10;&lt;/h3&gt;&lt;p&gt;VMware 在宿主机上创建虚拟子网，虚拟机通过宿主机做 NAT 访问外网：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;虚拟机 → VMware 虚拟网关/NAT → 宿主机 → 外网&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;外部设备通常不能直接主动访问虚拟机，除非做端口映射或额外配置。&lt;/p&gt;&#10;&lt;h3 id="3-host-only仅主机"&gt;3. Host-only（仅主机）&#10;&lt;/h3&gt;&lt;p&gt;虚拟机主要与宿主机及同一 Host-only 网络中的虚拟机通信，默认不能直接访问外网。它适合隔离实验环境。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;“Host-only 一定可以 ping 通公网”是不对的；默认情况下它不能上网，除非宿主机另行开启路由/NAT。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十五macos-packet-sender-测试链路"&gt;十五、macOS Packet Sender 测试链路&#10;&lt;/h2&gt;&lt;p&gt;已知 DPDK 端口信息：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MAC：00:0c:29:aa:5b:f0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PCI：0000:1a:00.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PMD：net_vmxnet3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Link：up&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;宿主机：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;IP：172.26.185.5&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Mask：255.255.0.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Interface：en0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ustack 测试地址：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;172.26.185.250&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="1-为什么需要静态-arp"&gt;1. 为什么需要静态 ARP&#10;&lt;/h3&gt;&lt;p&gt;应用把 UDP 发往 &lt;code&gt;172.26.185.250:8888&lt;/code&gt; 时，操作系统需要构造以太网帧。因为目标 IP 在同一子网，macOS 必须知道目标 IP 对应的 MAC 地址。&lt;/p&gt;&#10;&lt;p&gt;正常情况是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;macOS 广播 ARP Request：谁是 172.26.185.250？&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ustack 单播 ARP Reply：172.26.185.250 是 00:0c:29:aa:5b:f0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当前 ustack 没有实现 ARP Reply，所以手工建立：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo arp -s 172.26.185.250 00:0c:29:aa:5b:f0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo arp -s 172.26.185.250 00:0c:29:aa:5b:f0 ifscope en0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;验证：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;arp -n 172.26.185.250&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-packet-sender-配置"&gt;2. Packet Sender 配置&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Address：172.26.185.250&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Port：8888&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Protocol：UDP&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ASCII：hello dpdk&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;完整路径：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Packet Sender&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → macOS UDP/IP 处理&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 静态 ARP 表查到目标 MAC&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → macOS en0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → VMware Fusion 桥接&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → vmxnet3 虚拟网卡&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → DPDK port 0 / RX queue 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → rte_eth_rx_burst()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Ethernet / IPv4 / UDP 解析&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 打印 hello dpdk&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看到：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;udp:hello dpdk&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;说明至少以下环节已经连通：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;macOS 到 Fusion 桥接网络；&lt;/li&gt;&#10;&lt;li&gt;目标 MAC 投递；&lt;/li&gt;&#10;&lt;li&gt;vmxnet3 链路；&lt;/li&gt;&#10;&lt;li&gt;DPDK 端口初始化；&lt;/li&gt;&#10;&lt;li&gt;RX Queue 0 收包；&lt;/li&gt;&#10;&lt;li&gt;当前测试报文的 Ethernet/IPv4/UDP 基础解析。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;但它还不能单独证明 checksum、分片、VLAN、IP Options、异常长度处理等已经正确实现。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十六dpdk-环境搭建思路"&gt;十六、DPDK 环境搭建思路&#10;&lt;/h2&gt;&lt;p&gt;老版本 DPDK 常使用 &lt;code&gt;dpdk-setup.sh&lt;/code&gt;、&lt;code&gt;RTE_SDK&lt;/code&gt; 和 &lt;code&gt;RTE_TARGET&lt;/code&gt;。典型含义是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RTE_SDK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/.../dpdk&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RTE_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;RTE_SDK&lt;/code&gt;：DPDK 源码/SDK 根目录；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;RTE_TARGET&lt;/code&gt;：旧版构建系统生成的目标目录名称。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;code&gt;native&lt;/code&gt; 一般表示按当前机器架构优化；&lt;code&gt;linuxapp&lt;/code&gt; 是旧版目标名称的一部分；具体选项需要以所使用的旧版 DPDK 文档和脚本为准。&lt;/p&gt;&#10;&lt;p&gt;现代 DPDK 已主要使用 Meson + Ninja 构建，不再依赖旧式 &lt;code&gt;make config&lt;/code&gt;、&lt;code&gt;RTE_TARGET&lt;/code&gt; 流程：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;meson setup build&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ninja -C build&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因此必须先确认教程使用的 DPDK 版本。不要把老教程里的菜单选项直接套到新版本。&lt;/p&gt;&#10;&lt;h3 id="通用搭建检查表"&gt;通用搭建检查表&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;确认 CPU 架构、Linux 发行版和 DPDK 版本；&lt;/li&gt;&#10;&lt;li&gt;确认 vmxnet3/物理网卡被当前 DPDK PMD 支持；&lt;/li&gt;&#10;&lt;li&gt;配置 Hugepage；&lt;/li&gt;&#10;&lt;li&gt;若使用 VFIO，确认 IOMMU/VFIO 条件；&lt;/li&gt;&#10;&lt;li&gt;查看 PCI 地址和当前驱动；&lt;/li&gt;&#10;&lt;li&gt;将实验网卡绑定到合适驱动；&lt;/li&gt;&#10;&lt;li&gt;保留管理网卡，避免把 SSH 所依赖的唯一网卡绑定走；&lt;/li&gt;&#10;&lt;li&gt;用 testpmd 检查端口、队列、Link 和收包计数；&lt;/li&gt;&#10;&lt;li&gt;再运行自己的程序。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;安全提示：绑定网卡前一定确认 PCI 地址。将正在承载 SSH 或默认路由的网卡绑定给 DPDK，会立即让普通 Linux 网络连接中断。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十七原始代码保持不变"&gt;十七、原始代码（保持不变）&#10;&lt;/h2&gt;&lt;p&gt;下面代码完全按原内容保留，没有修改：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;rte_eal.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;rte_ethdev.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c1"&gt;//网卡绑定id从0开始 发数据和收数据知道id就好&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define NUM_MBUFS 4096&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define BURST_SIZE 128&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_eth_conf&lt;/span&gt; &lt;span class="n"&gt;port_conf_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rxmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{.&lt;/span&gt;&lt;span class="n"&gt;max_rx_pkt_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RTE_ETHER_MAX_LEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;ustack_init_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_mempool&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;nb_sys_ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_dev_count_avail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb_sys_ports&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;No Supported eth found/n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//struct rte_eth_dev_info dev_info;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//rte_eth_dev_info_get(global_portid，&amp;amp;dev_info);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//绑定的网卡&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//现在有八个 队列 弄1 2 4 8 &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num_rx_queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num_tx_queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_eth_dev_configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;num_rx_queues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;num_tx_queues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;port_conf_default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_rx_queue_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_dev_socket_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//在0号网卡的0号队列开辟了RX队列 准备了128个mbuffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34; Could not setup RX queue/n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_dev_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="c1"&gt;//启动&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Could not start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[]){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rte_eal_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="c1"&gt;//初始化 检查网卡信息 配置 是否绑定&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error with EAL init&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_mempool&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rte_pktmbuf_pool_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mbuf pool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;NUM_MBUFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;RTE_MBUF_DEFAULT_BUF_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;rte_socket_id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//接收数据放在mbuff&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//mbuffer分配在内存上的 rte_socket_id 就是当前使用哪块内存卡槽ID分配的&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Could not create mbuf pool&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;ustack_init_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//接收数据&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_mbuf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;num_recvd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rte_eth_rx_burst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_recvd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error receiving from eth&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;num_recvd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ethhdr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;rte_pktmbuf_mtod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ethhdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ether_type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;rte_cpu_to_be_16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTE_ETHER_TYPE_IPV4&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ipv4_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;rte_pktmbuf_mtod_offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ipv4_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next_proto_id&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;IPPROTO_UDP&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="c1"&gt;//两个以上都转&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_udp_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;udphdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_udp_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;udp:%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;udphdr&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello dpdk&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="十八代码逐行对照理解"&gt;十八、代码逐行对照理解&#10;&lt;/h2&gt;&lt;h3 id="1-头文件"&gt;1. 头文件&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;引入标准输入输出声明，本程序用它调用 &lt;code&gt;printf()&lt;/code&gt;。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;rte_eal.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;引入 DPDK EAL 接口。EAL 是 Environment Abstraction Layer，负责 Hugepage、lcore、内存、PCI 设备等基础环境初始化，也声明了 &lt;code&gt;rte_eal_init()&lt;/code&gt;、&lt;code&gt;rte_exit()&lt;/code&gt; 等接口。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;rte_ethdev.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;引入以太网设备抽象接口，包括端口配置、RX Queue 设置、设备启动和 Burst 收包。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;提供网络字节序、IP 地址转换等常用声明。当前代码中没有直接调用 &lt;code&gt;htons()&lt;/code&gt;、&lt;code&gt;ntohs()&lt;/code&gt; 等函数，但 &lt;code&gt;IPPROTO_UDP&lt;/code&gt; 等定义可能通过相关系统头可见。实际项目通常还会显式包含 DPDK Ethernet/IP/UDP 头文件，具体取决于 DPDK 版本的包含关系。&lt;/p&gt;&#10;&lt;h3 id="2-全局端口与常量"&gt;2. 全局端口与常量&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;选择 DPDK 逻辑端口 0。这里的 port id 是 DPDK 枚举后的端口编号，不等于 PCI 地址，也不一定等于 Linux 的 &lt;code&gt;eth0&lt;/code&gt;。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define NUM_MBUFS 4096&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;准备在 mempool 中创建 4096 个 mbuf。它们不仅供 128 个 RX 描述符使用，还要留给收包后尚未释放、缓存以及其他内部需求。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define BURST_SIZE 128&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每次调用 &lt;code&gt;rte_eth_rx_burst()&lt;/code&gt;，最多希望取回 128 个包。实际返回值可以是 0 到 128。&lt;/p&gt;&#10;&lt;h3 id="3-默认端口配置"&gt;3. 默认端口配置&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_eth_conf&lt;/span&gt; &lt;span class="n"&gt;port_conf_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rxmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{.&lt;/span&gt;&lt;span class="n"&gt;max_rx_pkt_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RTE_ETHER_MAX_LEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建端口配置结构，把最大接收包长设为标准以太网最大长度对应的 DPDK 常量。&lt;/p&gt;&#10;&lt;p&gt;注意：DPDK 的结构体字段会随版本变化。某些新版本中 &lt;code&gt;max_rx_pkt_len&lt;/code&gt; 的位置或用法已变化。如果发生编译错误，应首先对照当前版本 API，而不是认为网络原理有问题。&lt;/p&gt;&#10;&lt;h3 id="4-端口初始化函数"&gt;4. 端口初始化函数&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;ustack_init_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_mempool&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;定义只在本源文件使用的函数，参数是已经创建好的 mbuf 内存池。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;nb_sys_ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_dev_count_avail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查询当前 EAL 环境中可用的 DPDK Ethernet 设备数量。&lt;code&gt;nb&lt;/code&gt; 是 number 的常用缩写。它不是固定表示“绑定网口数量”，更准确地说是当前可用的 ethdev 数量。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb_sys_ports&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;No Supported eth found/n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果没有设备，打印错误并退出。字符串中的 &lt;code&gt;/n&lt;/code&gt; 是普通字符，不是换行符；真正的换行符写法是 &lt;code&gt;\n&lt;/code&gt;。本笔记遵守要求，不修改原代码，只指出区别。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//struct rte_eth_dev_info dev_info;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//rte_eth_dev_info_get(global_portid，&amp;amp;dev_info);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这两行被注释，不会执行。设计意图是读取端口能力，比如最大队列数、RSS、offload 能力。第二行参数之间使用了中文逗号 &lt;code&gt;，&lt;/code&gt;，若取消注释需要注意它不能作为 C 语言逗号使用。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num_rx_queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num_tx_queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置 1 个接收队列、0 个发送队列。此程序只收不发，所以 TX Queue 设为 0。若以后要回复 ARP、UDP 或 TCP，就必须配置 TX Queue。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;rte_eth_dev_configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;num_rx_queues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;num_tx_queues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;port_conf_default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置端口的 RX/TX 队列数量和总体参数。函数有返回值，当前代码没有检查；健壮程序应检查是否小于 0。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_rx_queue_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_eth_dev_socket_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置 RX Queue：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;global_portid&lt;/code&gt;：端口 0；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;0&lt;/code&gt;：RX Queue 0；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;128&lt;/code&gt;：描述符数量；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;rte_eth_dev_socket_id(...)&lt;/code&gt;：在网卡所在 NUMA Socket 分配队列资源；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;NULL&lt;/code&gt;：使用默认 RX Queue 配置；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mbuf_pool&lt;/code&gt;：网卡收到的数据放入这个池提供的 mbuf。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;注释“准备了 128 个 mbuffer”是便于理解的近似说法。准确说是请求建立 128 个 RX descriptors；描述符与 mbuf 的补充、占用和回收由 PMD 管理，mempool 总大小仍为 4096。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34; Could not setup RX queue/n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置失败就退出。同样，&lt;code&gt;/n&lt;/code&gt; 不会产生换行。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rte_eth_dev_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Could not start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;启动端口。配置端口和队列只是准备工作，start 后网卡才进入正式收发状态。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;表示初始化成功。&lt;/p&gt;&#10;&lt;h3 id="5-main-与-eal-初始化"&gt;5. main 与 EAL 初始化&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[]){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;程序入口。DPDK 会先解析命令行中的 EAL 参数，应用自己的参数通常放在 &lt;code&gt;--&lt;/code&gt; 后面并根据 &lt;code&gt;rte_eal_init()&lt;/code&gt; 的返回值调整。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rte_eal_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error with EAL init&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;初始化 DPDK 运行环境，涉及：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;解析 EAL 参数；&lt;/li&gt;&#10;&lt;li&gt;发现 CPU lcore；&lt;/li&gt;&#10;&lt;li&gt;初始化 Hugepage 内存；&lt;/li&gt;&#10;&lt;li&gt;初始化内存管理；&lt;/li&gt;&#10;&lt;li&gt;探测并初始化设备和总线；&lt;/li&gt;&#10;&lt;li&gt;建立主从 lcore 等运行环境。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;它不等于“自动保证所有网卡已经正确绑定”。绑定通常要在运行程序之前完成，EAL 负责发现当前可使用的设备。&lt;/p&gt;&#10;&lt;h3 id="6-创建-mbuf-pool"&gt;6. 创建 mbuf pool&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_mempool&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_pktmbuf_pool_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mbuf pool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;NUM_MBUFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;RTE_MBUF_DEFAULT_BUF_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;rte_socket_id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;参数逐个理解：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;&amp;quot;mbuf pool&amp;quot;&lt;/code&gt;：内存池名称；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;NUM_MBUFS&lt;/code&gt;：4096 个 mbuf；&lt;/li&gt;&#10;&lt;li&gt;第一个 &lt;code&gt;0&lt;/code&gt;：每个 lcore 的 cache 数量为 0；&lt;/li&gt;&#10;&lt;li&gt;第二个 &lt;code&gt;0&lt;/code&gt;：每个对象额外私有区域大小为 0；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;RTE_MBUF_DEFAULT_BUF_SIZE&lt;/code&gt;：每个 mbuf 默认数据缓冲区大小；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;rte_socket_id()&lt;/code&gt;：在当前运行核心所在 NUMA Socket 分配。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这里 &lt;code&gt;rte_socket_id()&lt;/code&gt; 指 NUMA 节点编号，不是物理“内存卡槽 ID”。服务器硬件的 DIMM 插槽和 NUMA Node 不是同一个概念。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Could not create mbuf pool&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建失败立即退出。可能原因包括 Hugepage 不足、名称冲突、socket 内存不够或参数不合法。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;ustack_init_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbuf_pool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;用刚创建的内存池配置并启动端口。&lt;/p&gt;&#10;&lt;h3 id="7-轮询收包"&gt;7. 轮询收包&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;进入无限循环。这是 PMD 轮询模型的核心，线程会持续询问 RX Queue 是否有包。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_mbuf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建一个长度为 128 的指针数组。数组保存 &lt;code&gt;rte_mbuf *&lt;/code&gt;，并不在栈上创建 128 个完整 mbuf。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;num_recvd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_eth_rx_burst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_portid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从端口 0、队列 0 最多取 128 个已经收到的包：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;返回 0：当前没有包；&lt;/li&gt;&#10;&lt;li&gt;返回 1～128：实际取到的包数；&lt;/li&gt;&#10;&lt;li&gt;mbuf 指针写入 &lt;code&gt;mbufs[0 ... num_recvd-1]&lt;/code&gt;。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;code&gt;rte_eth_rx_burst()&lt;/code&gt; 通常通过检查 RX descriptors 批量完成工作，不是每次都触发系统调用。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_recvd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;BURST_SIZE&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error receiving from eth&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;按 API 契约，返回值不会大于传入的 &lt;code&gt;BURST_SIZE&lt;/code&gt;，所以这个判断正常情况下不会成立。它也不能检测一般收包错误，因为该 API 的返回值语义主要是“本次收到多少包”。&lt;/p&gt;&#10;&lt;h3 id="8-遍历每一个包"&gt;8. 遍历每一个包&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;num_recvd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;依次处理本轮收到的所有 mbuf。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ethhdr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;rte_pktmbuf_mtod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;取得包数据起点，把它解释成 Ethernet Header。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ethhdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ether_type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;rte_cpu_to_be_16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTE_ETHER_TYPE_IPV4&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果 EtherType 不是 IPv4，就跳过后续解析。ARP、IPv6、VLAN 等都会走 &lt;code&gt;continue&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;这里有一个重要的生命周期问题：跳过不等于释放。当前程序没有调用 &lt;code&gt;rte_pktmbuf_free(mbufs[i])&lt;/code&gt;，因此收到的 mbuf 不会归还 mempool。运行一段时间后，RX Queue 可能因为拿不到空闲 mbuf 而停止继续收包。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ipv4_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;rte_pktmbuf_mtod_offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;mbufs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ipv4_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_ether_hdr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从数据起点向后移动一个 Ethernet Header 的长度，即 14 字节，再把地址解释成 IPv4 Header。&lt;/p&gt;&#10;&lt;p&gt;当前代码在解引用前没有检查包的实际长度；遇到截断帧或异常帧时可能越界读取。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next_proto_id&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;IPPROTO_UDP&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;检查 IPv4 Protocol 字段是否等于 UDP 的协议号 17。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_udp_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;udphdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rte_udp_hdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;iphdr&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;iphdr + 1&lt;/code&gt; 会跳过一个 &lt;code&gt;struct rte_ipv4_hdr&lt;/code&gt;，也就是按 20 字节基础 IPv4 头定位 UDP Header。它没有考虑 IHL 大于 5的 IPv4 Options。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;udp:%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;udphdr&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;udphdr + 1&lt;/code&gt; 跳过 8 字节 UDP Header，将后续 Payload 当作字符串打印。它没有依据 UDP Length 限制读取长度，也没有保证 Payload 以 &lt;code&gt;\0&lt;/code&gt; 结尾，因此只适合受控的入门测试。&lt;/p&gt;&#10;&lt;h3 id="9-循环之后的代码"&gt;9. 循环之后的代码&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello dpdk&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于前面是无限循环，并且没有 &lt;code&gt;break&lt;/code&gt;，正常情况下这一行不可达。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十九今天内容的核心总结"&gt;十九、今天内容的核心总结&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;网卡把网络介质上的信号变成帧，并通过 DMA 把数据放入主机内存；它主要涉及物理层和数据链路层。&lt;/li&gt;&#10;&lt;li&gt;驱动负责初始化网卡、配置队列和 DMA，并让软件能够操作具体硬件。&lt;/li&gt;&#10;&lt;li&gt;Linux 使用 &lt;code&gt;sk_buff&lt;/code&gt; 描述数据包；DPDK 使用 &lt;code&gt;rte_mbuf&lt;/code&gt;，两者都不只是数据本身，还包含元数据。&lt;/li&gt;&#10;&lt;li&gt;多队列让多个 CPU 核并行处理流量，RSS 负责把流分配到不同队列；多队列很重要，但不是所有 DPDK 绑定的绝对前提。&lt;/li&gt;&#10;&lt;li&gt;DPDK 用用户态轮询、Burst、Hugepage、mempool、多队列和 NUMA 优化高速包处理。&lt;/li&gt;&#10;&lt;li&gt;网卡绑定给 DPDK 后，常规数据路径绕过内核 TCP/IP；因此要么引入现有用户态协议栈，要么自己实现。&lt;/li&gt;&#10;&lt;li&gt;Ethernet 普通头为 14 字节，IPv4 最少 20 字节，UDP 固定 8 字节，TCP 最少 20 字节。&lt;/li&gt;&#10;&lt;li&gt;“能找到协议头并打印 Payload”只是解析器原型，不等于完整协议栈。&lt;/li&gt;&#10;&lt;li&gt;完整 UDP 还需要 ARP、IP、校验和、端口分发、队列和 API；完整 TCP 还需要状态机、序列号、重传、窗口、定时器等。&lt;/li&gt;&#10;&lt;li&gt;POSIX &lt;code&gt;recv()&lt;/code&gt; 读取的是 Socket 接收缓冲区；用户态协议栈必须自己建立 fd、Socket、TCB 和收发队列之间的关系。&lt;/li&gt;&#10;&lt;li&gt;当前测试通过静态 ARP 绕过了尚未实现的 ARP Reply，成功证明了 macOS、Fusion 桥接、vmxnet3、DPDK RX 和基础 UDP 解析链路已打通。&lt;/li&gt;&#10;&lt;li&gt;下一步最重要的不是立即写 TCP，而是先补齐 mbuf 释放、长度校验、ARP Reply、TX 和 UDP Echo。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="二十版本说明与官方参考"&gt;二十、版本说明与官方参考&#10;&lt;/h2&gt;&lt;p&gt;你的学习材料明显包含旧版 DPDK 构建流程，因此本笔记保留了 &lt;code&gt;dpdk-setup.sh&lt;/code&gt;、&lt;code&gt;RTE_SDK&lt;/code&gt;、&lt;code&gt;RTE_TARGET&lt;/code&gt; 的历史解释，同时把现代版本单独说明。实际执行命令时，必须先运行 &lt;code&gt;dpdk-testpmd -v&lt;/code&gt;、查看源码目录中的 release 信息或构建文件，确认本机 DPDK 版本。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.dpdk.org/guides/linux_gsg/" target="_blank" rel="noopener"&#10; &gt;DPDK Linux Getting Started Guide&lt;/a&gt;：现代 Linux 环境、编译、驱动和运行入口；&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.dpdk.org/guides/prog_guide/ethdev/ethdev.html" target="_blank" rel="noopener"&#10; &gt;DPDK Poll Mode Driver&lt;/a&gt;：PMD 绕过传统内核网络栈、直接轮询 RX/TX Descriptor 的官方说明；&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.dpdk.org/guides/linux_gsg/linux_drivers.html" target="_blank" rel="noopener"&#10; &gt;DPDK Linux Drivers&lt;/a&gt;：VFIO、UIO、设备绑定和安全性说明；&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.dpdk.org/guides/nics/vmxnet3.html" target="_blank" rel="noopener"&#10; &gt;DPDK VMXNET3 PMD&lt;/a&gt;：vmxnet3 收发环、多队列/RSS、功能与限制；&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.dpdk.org/guides/tools/hugepages.html" target="_blank" rel="noopener"&#10; &gt;DPDK Hugepage Tool&lt;/a&gt;：查看、预留和挂载大页；&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.dpdk.org/guides-22.11/prog_guide/kernel_nic_interface.html" target="_blank" rel="noopener"&#10; &gt;DPDK 22.11 KNI 文档&lt;/a&gt;：KNI 的用途、弃用状态及 virtio-user 替代说明。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>