我正在使用PACKET_MMAP套接字选项在C中编写流量生成器,以创建环形缓冲区以通过原始套接字发送数据。环形缓冲区填充了要发送的以太网帧,并调用sendto
。环形缓冲区的全部内容通过套接字发送,这应该比内存中的缓冲区提供更高的性能,并且对需要发送的缓冲区中的每一帧重复调用sendto
。
当不使用PACKET_MMAP时,在调用sendto
时,将单个帧从用户区内存中的缓冲区复制到内核内存中的SK buf,然后内核必须将该数据包复制到NIC访问的内存中对于DMA并发信号通知NIC将帧DMA映射到它自己的硬件缓冲区并对其进行排队以进行传输。当使用PACKET_MMAP套接字选项时,mmapped内存由应用程序分配并链接到原始套接字。应用程序将数据包放入mmapped缓冲区,调用sendto
而不是内核必须将数据包复制到SK buf中,它可以直接从mmapped缓冲区中读取它们。还有"块"可以从环形缓冲区而不是单独的分组/帧中读取分组。因此,性能提升是一个系统调用,用于复制多个帧,每个帧复制操作少一个,以使其进入NIC硬件缓冲区。
当我将使用PACKET_MMAP的套接字的性能与“普通”套接字(其中包含单个数据包的char缓冲区)进行比较时,根本没有性能优势。为什么会这样?在Tx模式下使用PACKET_MMAP时,每个环形块只能放入一个帧(而不是像Rx模式那样每个环形块有多个帧)但是我创建了256个块,所以我们应该发送256个单个sendto
电话中的帧权限?
使用PACKET_MMAP进行效果,main()
调用packet_tx_mmap()
:
bensley@ubuntu-laptop:~/C/etherate10+$ sudo taskset -c 1 ./etherate_mt -I 1
Using inteface lo (1)
Running in Tx mode
1. Rx Gbps 0.00 (0) pps 0 Tx Gbps 17.65 (2206128128) pps 1457152
2. Rx Gbps 0.00 (0) pps 0 Tx Gbps 19.08 (2385579520) pps 1575680
3. Rx Gbps 0.00 (0) pps 0 Tx Gbps 19.28 (2409609728) pps 1591552
4. Rx Gbps 0.00 (0) pps 0 Tx Gbps 19.31 (2414260736) pps 1594624
5. Rx Gbps 0.00 (0) pps 0 Tx Gbps 19.30 (2411935232) pps 1593088
没有PACKET_MMAP的效果,main()
调用packet_tx()
:
bensley@ubuntu-laptop:~/C/etherate10+$ sudo taskset -c 1 ./etherate_mt -I 1
Using inteface lo (1)
Running in Tx mode
1. Rx Gbps 0.00 (0) pps 0 Tx Gbps 18.44 (2305001412) pps 1522458
2. Rx Gbps 0.00 (0) pps 0 Tx Gbps 20.30 (2537520018) pps 1676037
3. Rx Gbps 0.00 (0) pps 0 Tx Gbps 20.29 (2535744096) pps 1674864
4. Rx Gbps 0.00 (0) pps 0 Tx Gbps 20.26 (2533014354) pps 1673061
5. Rx Gbps 0.00 (0) pps 0 Tx Gbps 20.32 (2539476106) pps 1677329
packet_tx()
函数稍微快于packet_tx_mmap()
函数,但它也略短,所以我认为最小的性能提升只是{{1 }}。所以在我看来,这两个功能几乎具有相同的性能,为什么呢?为什么PACKET_MMAP不会更快,因为据我所知,系统调用和副本应该少得多?
packet_tx
请注意,以下功能会调用void *packet_tx_mmap(void* thd_opt_p) {
struct thd_opt *thd_opt = thd_opt_p;
int32_t sock_fd = setup_socket_mmap(thd_opt_p);
if (sock_fd == EXIT_FAILURE) exit(EXIT_FAILURE);
struct tpacket2_hdr *hdr;
uint8_t *data;
int32_t send_ret = 0;
uint16_t i;
while(1) {
for (i = 0; i < thd_opt->tpacket_req.tp_frame_nr; i += 1) {
hdr = (void*)(thd_opt->mmap_buf + (thd_opt->tpacket_req.tp_frame_size * i));
data = (uint8_t*)(hdr + TPACKET_ALIGN(TPACKET2_HDRLEN));
memcpy(data, thd_opt->tx_buffer, thd_opt->frame_size);
hdr->tp_len = thd_opt->frame_size;
hdr->tp_status = TP_STATUS_SEND_REQUEST;
}
send_ret = sendto(sock_fd, NULL, 0, 0, NULL, 0);
if (send_ret == -1) {
perror("sendto error");
exit(EXIT_FAILURE);
}
thd_opt->tx_pkts += thd_opt->tpacket_req.tp_frame_nr;
thd_opt->tx_bytes += send_ret;
}
return NULL;
}
而不是setup_socket()
:
setup_socket_mmap()
套接字设置功能的唯一区别是粘贴在下面,但基本上是设置SOCKET_RX_RING或SOCKET_TX_RING的要求:
void *packet_tx(void* thd_opt_p) {
struct thd_opt *thd_opt = thd_opt_p;
int32_t sock_fd = setup_socket(thd_opt_p);
if (sock_fd == EXIT_FAILURE) {
printf("Can't create socket!\n");
exit(EXIT_FAILURE);
}
while(1) {
thd_opt->tx_bytes += sendto(sock_fd, thd_opt->tx_buffer,
thd_opt->frame_size, 0,
(struct sockaddr*)&thd_opt->bind_addr,
sizeof(thd_opt->bind_addr));
thd_opt->tx_pkts += 1;
}
}
更新1:针对物理接口的结果
有人提到我使用PACKET_MMAP时可能没有看到性能差异的一个原因是因为我正在向loopback接口发送流量(一方面,它没有QDISC)。由于运行// Set the TPACKET version, v2 for Tx and v3 for Rx
// (v2 supports packet level send(), v3 supports block level read())
int32_t sock_pkt_ver = -1;
if(thd_opt->sk_mode == SKT_TX) {
static const int32_t sock_ver = TPACKET_V2;
sock_pkt_ver = setsockopt(sock_fd, SOL_PACKET, PACKET_VERSION, &sock_ver, sizeof(sock_ver));
} else {
static const int32_t sock_ver = TPACKET_V3;
sock_pkt_ver = setsockopt(sock_fd, SOL_PACKET, PACKET_VERSION, &sock_ver, sizeof(sock_ver));
}
if (sock_pkt_ver < 0) {
perror("Can't set socket packet version");
return EXIT_FAILURE;
}
memset(&thd_opt->tpacket_req, 0, sizeof(struct tpacket_req));
memset(&thd_opt->tpacket_req3, 0, sizeof(struct tpacket_req3));
//thd_opt->block_sz = 4096; // These are set else where
//thd_opt->block_nr = 256;
//thd_opt->block_frame_sz = 4096;
int32_t sock_mmap_ring = -1;
if (thd_opt->sk_mode == SKT_TX) {
thd_opt->tpacket_req.tp_block_size = thd_opt->block_sz;
thd_opt->tpacket_req.tp_frame_size = thd_opt->block_sz;
thd_opt->tpacket_req.tp_block_nr = thd_opt->block_nr;
// Allocate per-frame blocks in Tx mode (TPACKET_V2)
thd_opt->tpacket_req.tp_frame_nr = thd_opt->block_nr;
sock_mmap_ring = setsockopt(sock_fd, SOL_PACKET , PACKET_TX_RING , (void*)&thd_opt->tpacket_req , sizeof(struct tpacket_req));
} else {
thd_opt->tpacket_req3.tp_block_size = thd_opt->block_sz;
thd_opt->tpacket_req3.tp_frame_size = thd_opt->block_frame_sz;
thd_opt->tpacket_req3.tp_block_nr = thd_opt->block_nr;
thd_opt->tpacket_req3.tp_frame_nr = (thd_opt->block_sz * thd_opt->block_nr) / thd_opt->block_frame_sz;
thd_opt->tpacket_req3.tp_retire_blk_tov = 1;
thd_opt->tpacket_req3.tp_feature_req_word = 0;
sock_mmap_ring = setsockopt(sock_fd, SOL_PACKET , PACKET_RX_RING , (void*)&thd_opt->tpacket_req3 , sizeof(thd_opt->tpacket_req3));
}
if (sock_mmap_ring == -1) {
perror("Can't enable Tx/Rx ring for socket");
return EXIT_FAILURE;
}
thd_opt->mmap_buf = NULL;
thd_opt->rd = NULL;
if (thd_opt->sk_mode == SKT_TX) {
thd_opt->mmap_buf = mmap(NULL, (thd_opt->block_sz * thd_opt->block_nr), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock_fd, 0);
if (thd_opt->mmap_buf == MAP_FAILED) {
perror("mmap failed");
return EXIT_FAILURE;
}
} else {
thd_opt->mmap_buf = mmap(NULL, (thd_opt->block_sz * thd_opt->block_nr), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock_fd, 0);
if (thd_opt->mmap_buf == MAP_FAILED) {
perror("mmap failed");
return EXIT_FAILURE;
}
// Per bock rings in Rx mode (TPACKET_V3)
thd_opt->rd = (struct iovec*)calloc(thd_opt->tpacket_req3.tp_block_nr * sizeof(struct iovec), 1);
for (uint16_t i = 0; i < thd_opt->tpacket_req3.tp_block_nr; ++i) {
thd_opt->rd[i].iov_base = thd_opt->mmap_buf + (i * thd_opt->tpacket_req3.tp_block_size);
thd_opt->rd[i].iov_len = thd_opt->tpacket_req3.tp_block_size;
}
}
或packet_tx_mmap()
例程中的任何一个都可以生成超过10Gbps的速度而且我只有10Gbps接口供我使用,我将两个绑定在一起,这些是结果,显示与上面几乎相同,这两个函数之间的速度差异很小:
packet_tx()
到20G bond0
packet_tx()
到20G bond0:
这是帧大小为1514字节(保持与上面的原始环回测试相同)。
在所有上述测试中,软IRQ的数量大致相同(使用this script测量)。当一个线程运行packet_tx_mmap()
时,CPU核心上每秒大约有40k个中断。有2个和3个线程分别在2和3个核心上运行40k。使用packet_tx()
时的结果相同。大约40k个软IRQ,用于一个CPU内核上的单个线程。运行2和3个线程时,每个核心40k。
更新2:完整源代码
我现在已经上传了完整的源代码,我还在编写这个应用程序,所以它可能有很多缺陷,但它们超出了这个问题的范围:https://github.com/jwbensley/EtherateMT