如何提升AM335x平台Linux原始套接字吞吐量至4000帧/秒
我在AM335x平台上折腾过不少raw socket的性能优化,针对你的场景——固定ethertype数据包接收+双副本发送,结合4.14内核的特性,给你几个实战性的优化方向,组合起来应该能轻松把吞吐量拉到4000帧/秒以上:
1. 用PACKET_MMAP替代传统recvfrom()/sendto(),消除内核-用户态拷贝
这是提升raw socket性能的核心!传统的recvfrom()会把内核缓冲区的数据包拷贝到用户态,sendto()又会把用户态数据拷贝回内核,高频率下拷贝开销极大。PACKET_MMAP可以直接把内核的环形缓冲区映射到用户态,让你直接读写内核缓冲区,完全避免拷贝。
接收端配置示例:
int sock_fd = socket(AF_PACKET, SOCK_RAW, htons(YOUR_ETHERTYPE)); struct tpacket_req rx_req; memset(&rx_req, 0, sizeof(rx_req)); // 配置环形缓冲区参数,根据你的数据包大小调整 rx_req.tp_block_size = 4096; // 每个块的大小,建议用页大小 rx_req.tp_block_nr = 128; // 块数量,越大缓存越多但占用内存也多 rx_req.tp_frame_size = TPACKET_ALIGN(ETH_FRAME_LEN + ETH_FCS_LEN + sizeof(struct tpacket_hdr)); rx_req.tp_frame_nr = (rx_req.tp_block_size * rx_req.tp_block_nr) / rx_req.tp_frame_size; // 设置接收环形缓冲区 setsockopt(sock_fd, SOL_PACKET, PACKET_RX_RING, &rx_req, sizeof(rx_req)); // 映射内核缓冲区到用户态 void *rx_ring = mmap(NULL, rx_req.tp_block_size * rx_req.tp_block_nr, PROT_READ | PROT_WRITE, MAP_SHARED, sock_fd, 0);
发送端:用sendmmsg()批量发送减少系统调用
你现在每次发两个包要调用两次sendto(),两次系统调用的上下文切换开销非常大。用sendmmsg()可以一次系统调用发送多个数据包,把两次调用合并成一次:
struct mmsghdr msg_vec[2]; struct iovec iov[2]; struct sockaddr_ll dest_addr; // 填充第一个包的信息 msg_vec[0].msg_hdr.msg_name = &dest_addr; msg_vec[0].msg_hdr.msg_namelen = sizeof(dest_addr); msg_vec[0].msg_hdr.msg_iov = &iov[0]; msg_vec[0].msg_hdr.msg_iovlen = 1; iov[0].iov_base = packet1_buf; iov[0].iov_len = packet1_len; // 填充第二个包的信息(和第一个类似) msg_vec[1].msg_hdr.msg_name = &dest_addr; msg_vec[1].msg_hdr.msg_namelen = sizeof(dest_addr); msg_vec[1].msg_hdr.msg_iov = &iov[1]; msg_vec[1].msg_hdr.msg_iovlen = 1; iov[1].iov_base = packet2_buf; iov[1].iov_len = packet2_len; // 批量发送两个包 sendmmsg(send_sock_fd, msg_vec, 2, 0);
2. 内核参数调优,适配AM335x的硬件特性
AM335x的CPSW网卡本身性能不差,但默认内核参数可能限制了吞吐量,修改以下参数:
# 增大网络缓冲区大小,避免丢包 sysctl -w net.core.rmem_max=26214400 # 25MB sysctl -w net.core.wmem_max=26214400 sysctl -w net.core.rmem_default=1048576 sysctl -w net.core.wmem_default=1048576 # 增大网卡接收队列的最大长度,避免数据包在网卡层被丢弃 sysctl -w net.core.netdev_max_backlog=10000 # 开启网卡校验和卸载,让硬件处理校验和,减轻CPU负担 ethtool -K eth0 rx-checksumming on ethtool -K eth0 tx-checksumming on
另外,调整网卡的中断聚合(Interrupt Coalescing),减少中断次数——AM335x的CPU资源有限,频繁的网卡中断会打满CPU:
# 设置接收端攒够32个包或10微秒再触发中断,根据实际情况调整 ethtool -C eth0 rx-usecs 10 rx-frames 32
3. 进程/线程绑定,避免上下文切换
AM335x是Cortex-A8架构,单核或双核版本都有。把你的数据包处理线程绑定到固定的CPU核心上,避免内核调度带来的上下文切换开销:
#include <pthread.h> #include <sched.h> void bind_to_core(int core_id) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) { perror("pthread_setaffinity_np failed"); } } // 在处理线程的入口调用 bind_to_core(0); // 如果是双核,可以把接收和发送线程分别绑定到0和1核
4. 内存分配优化,避免动态malloc/free
生成数据包副本时,不要每次都用malloc()/free()——嵌入式系统中动态内存分配的开销极大,高频率下会成为瓶颈。提前用内存池分配一批固定大小的缓冲区,循环使用:
#define BUFFER_COUNT 64 #define PACKET_SIZE (YOUR_PACKET_LEN + TAIL_LEN) // 你的数据包大小+尾部长度 char *buffer_pool[BUFFER_COUNT]; int pool_idx = 0; // 初始化内存池 void init_pool() { for (int i = 0; i < BUFFER_COUNT; i++) { buffer_pool[i] = malloc(PACKET_SIZE); if (!buffer_pool[i]) { perror("malloc failed"); exit(1); } } } // 从池中取缓冲区 char *get_buffer() { char *buf = buffer_pool[pool_idx]; pool_idx = (pool_idx + 1) % BUFFER_COUNT; return buf; }
5. 编译与代码逻辑优化
- 开启编译器优化:编译时加上
-O2或-O3,同时添加ARM架构相关的优化选项:-march=armv7-a -mfpu=neon,让编译器生成更高效的ARM指令(比如用NEON优化memcpy操作)。 - 减少冗余操作:生成副本时,只拷贝必要的部分,比如头部可以复用指针,只拷贝需要修改的尾部;如果尾部是固定内容,可以提前预存,避免重复生成。
总结
以上优化组合起来,完全可以让你的吞吐量突破4000帧/秒——AM335x的CPSW网卡在100Mbps带宽下,理论上能处理约8000帧/秒(按1500字节包计算),4000帧的目标是很容易达到的。建议先从PACKET_MMAP和sendmmsg入手,这两个优化带来的性能提升最明显,再逐步调整其他参数。
内容的提问来源于stack exchange,提问作者Sergey1101

