PACKET_MMAP 및 PACKET_TX_RING을 사용한 데이터 전송 속도가 "정상"보다 느림(없음)
저는 PACKET_MMAP 소켓 옵션을 사용하여 원시 소켓을 통해 데이터를 전송할 링 버퍼를 생성하는 트래픽 생성기를 C에 작성하고 있습니다.링 버퍼는 보낼 이더넷 프레임으로 채워져 있고 그리고sendto
라고 합니다.링 버퍼의 전체 내용은 메모리와 호출에 버퍼가 있는 것보다 더 높은 성능을 제공해야 하는 소켓을 통해 전송됩니다.sendto
전송이 필요한 버퍼의 모든 프레임에 대해 반복적으로 전송합니다.
때 호출 시sendto
단일 프레임이 사용자 지정 메모리의 버퍼에서 커널 메모리의 SK 버퍼로 복사된 다음 커널은 패킷을 DMA용 NIC가 액세스하는 메모리로 복사하고 프레임을 자체 하드웨어 버퍼로 전송하도록 NIC에 신호를 보내고 전송 대기열을 지정해야 합니다.PACKET_MMAP socket 옵션을 사용할 때 매핑된 메모리는 응용 프로그램에 의해 할당되고 원시 소켓에 연결됩니다.응용 프로그램은 매핑된 버퍼에 패킷을 넣고 호출합니다.sendto
커널이 패킷을 SK에 복사해야 하는 대신 매핑된 버퍼에서 패킷을 직접 읽을 수 있습니다.또한 개별 패킷/프레임 대신 링 버퍼에서 패킷의 "블록"을 읽을 수 있습니다.따라서 성능 향상은 여러 프레임을 복사하기 위한 sys-call 하나와 NIC 하드웨어 버퍼에 저장하기 위한 각 프레임에 대한 복사 작업 하나를 줄이는 것입니다.
PACKET_MMAP를 사용하는 소켓의 성능을 "정상" 소켓(단일 패킷이 들어있는 차 버퍼)과 비교할 때 성능상의 이점은 전혀 없습니다. 이게 왜죠?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()
기능은 약간 짧은 것 같기도 하고 그래서 최소한의 성능 향상은 단순히 현재의 코드 라인이 조금 적은 것이라고 생각합니다.packet_tx
가 보기에는 두 모두 동일한 것 왜 그럴까요 제가 보기에는 두 기능 모두 성능이 거의 동일한 것 같은데, 왜 그럴까요?시스템 호출 및 복사본이 훨씬 적어야 한다는 것을 알고 있기 때문에 PACKET_MMAP가 훨씬 더 빠르지 않은 이유는 무엇입니까?
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()
:
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;
}
}
소켓 설정 기능의 유일한 차이점은 아래에 있지만, 기본적으로 SOCCET_RX_RING 또는 SOCCET_TX_RING을 설정하기 위한 요구 사항입니다.
// 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;
}
}
업데이트 1: 물리적 인터페이스에 대한 결과 PACKET_MMAP를 사용할 때 성능 차이가 나타나지 않는 한 가지 이유는 루프백 인터페이스(QDISC가 없는 경우 등)로 트래픽을 보내고 있었기 때문이라고 언급했습니다.둘 중 하나를 실행한 이후로packet_tx_mmap()
아니면packet_tx()
루틴은 10Gbps 이상의 속도를 생성할 수 있으며, 제 마음대로 사용할 수 있는 인터페이스는 10Gbps에 불과합니다. 두 개의 인터페이스를 함께 연결했는데 이 결과는 위와 거의 동일하며 두 기능 사이에 속도 차이가 거의 없음을 보여줍니다.
packet_tx()
드0로
- 나사산 1개:평균 10.77Gbps~ / 889kfps~
- 스레드 2개:평균 19.19Gbps~ / 1.58Mfps~
- 세 개의 스레드:평균 19.67Gbps~ / 1.62Mfps~ (채권이 가는 속도만큼 빠름)
packet_tx_mmap()
20G bond0결에 합니다.
- 나사산 1개:평균 11.08Gbps~ / 913kfps~
- 스레드 2개:평균 19.0Gbps~ / 1.57Mfps~
- 세 개의 스레드:평균 19.66Gbps~ / 1.62Mfps~ (채권이 가는 속도만큼 빠름)
이는 프레임 크기가 1514바이트(위의 원래 루프백 테스트와 동일하게 유지하기 위한 것입니다.
위의 모든 테스트에서 소프트 IRQ의 수는 거의 동일했습니다(이 스크립트를 사용하여 측정).한 개의 스레드를 실행할 경우packet_tx()
CPU 코어에 초당 약 40k의 인터럽트가 발생했습니다.2, 3개의 스레드가 2, 3 코어에서 각각 40k씩 실행됩니다.사용시 결과packet_tx_mmap()
같은 곳에 하나의 코어에 약 40k .하나의 CPU 코어에 하나의 스레드에 대한 약 40k 소프트 IRQ.2개 및 3개의 스레드를 실행할 경우 코어당 40k.
업데이트 2: 전체 소스 코드
지금 전체 소스 코드를 업로드했습니다. 아직 이 응용 프로그램을 작성 중이므로 많은 결함이 있을 수 있지만 이 질문의 범위를 벗어납니다. https://github.com/jwbensley/EtherateMT
리눅스 커널에 대한 많은 인터페이스는 잘 문서화되어 있지 않습니다.또는 문서화가 잘 되어 있는 것처럼 보이더라도 상당히 복잡할 수 있으며 인터페이스의 기능적 특성이나 심지어 더 어려운 비기능적 특성이 무엇인지 이해하기 어려울 수 있습니다.
이러한 이유로 커널 API에 대한 확실한 이해를 원하거나 커널 API를 사용하여 고성능 애플리케이션을 만들어야 하는 사람들에게 제 조언은 성공적으로 커널 코드를 사용할 수 있어야 합니다.
이 경우 질문자는 커널에 공유 메모리 인터페이스(packet mmap)를 통해 원시 프레임을 전송하는 성능 특성을 파악하고자 합니다.
리눅스 설명서는 여기 있습니다."how to"에 대한 오래된 링크를 가지고 있습니다. 이제 여기에서 찾을 수 있으며 다음과 같은 복사본이 포함되어 있습니다.packet_mmap.c
(여기에 약간 다른 버전이 있습니다.
이 문서는 주로 패킷 mmap을 사용하기 위한 전형적인 사용 사례인 읽기를 목표로 하고 있습니다. 인터페이스에서 원시 프레임을 효율적으로 읽기. 예를 들어, 손실이 거의 없거나 전혀 없는 고속 인터페이스에서 패킷 캡처를 효율적으로 얻기 위해서입니다.
그러나 OP는 훨씬 덜 일반적인 사용 사례인 고성능 쓰기에 관심이 있지만 OP가 원하는 것처럼 보이는 트래픽 발생기/시뮬레이터에 잠재적으로 유용합니다.감사하게도, "방법"은 프레임을 쓰는 것입니다.
그러나 실제로 어떻게 작동하는지에 대한 정보는 거의 제공되지 않으며 패킷 mmap을 사용하는 것이 왜 패킷 mmap을 사용하지 않고 한 번에 하나의 프레임을 전송하는 것보다 빠르지 않은 것 같으냐는 OP의 질문에 답하는 데 도움이 되는 것은 없습니다.
다행히 커널 소스가 오픈 소스이고 색인이 잘 되어 있기 때문에 질문에 대한 답을 얻을 수 있도록 소스로 전환할 수 있습니다.
관련 커널 코드를 찾기 위해 검색할 수 있는 키워드가 몇 가지 있지만,PACKET_TX_RING
는 이 기능에 고유한 소켓 옵션으로 눈에 띕니다.인터웹에서 "PACKET_TX_RING linux cross reference"를 검색하면 다음과 같은 소수의 참조가 표시됩니다.af_packet.c
, 약간의 검사로 모든 것을 실행하는 것으로 보입니다.AF_PACKET
패킷 mmap을 포함한 기능.
훑어보기af_packet.c
, 패킷 mmap으로 전송하는 작업의 핵심은 다음과 같습니다.tpacket_snd()
이 맞습니까 그런데 이게 맞는 건가요?이것이 우리가 생각하는 것과 관련이 있는지 어떻게 알 수 있을까요?
이와 같은 정보를 커널에서 가져올 수 있는 매우 강력한 도구가 SystemTap입니다. (이를 사용하려면 커널에 디버깅 심볼을 설치해야 합니다.우연히 Ubuntu를 사용하게 되었는데, 이것이 Ubuntu에서 SystemTap을 작동시키는 방법입니다.)
SystemTap이 작동하면 SystemTap을 다음과 함께 사용할 수 있습니다.packet_mmap.c
확인해 보다tpacket_snd()
커널 함수에 프로브를 설치하여 호출하기도 합니다.tpacket_snd
, 그 다음엔 달리죠packet_mmap
공유 TX 링을 통해 프레임을 전송하려면:
$ sudo stap -e 'probe kernel.function("tpacket_snd") { printf("W00T!\n"); }' &
[1] 19961
$ sudo ./packet_mmap -c 1 eth0
[...]
STARTING TEST:
data offset = 32 bytes
start fill() thread
send 1 packets (+150 bytes)
end of task fill()
Loop until queue empty (0)
END (number of error:0)
W00T!
W00T!
W00T! .tpacket_snd
실제로 호출되고 있습니다.하지만 우리의 승리는 오래가지 못할 것입니다.재고 커널 빌드에서 더 많은 정보를 얻기 위해 계속 노력하면 SystemTap에서 검사하려는 변수를 찾을 수 없다고 불평하고 함수 인수는 다음과 같은 값으로 출력됩니다.?
아니면ERROR
. 이것은 커널이 최적화된 상태로 컴파일되기 때문입니다. 그리고 모든 기능은AF_PACKET
단일 번역 단위로 정의됩니다.af_packet.c
; 많은 함수들이 컴파일러에 의해 인라인 처리되어 사실상 로컬 변수와 인수를 잃습니다.
더 많은 정보를 캐내기 위해서는af_packet.c
, 우리는 다음과 같이 커널 버전을 만들어야 할 것입니다.af_packet.c
최적화 없이 구축됩니다.여기서 가이드를 좀 찾으세요.기다리고 있을게요.
좋아요, 너무 어렵지 않아서 SystemTap에서 좋은 정보를 많이 얻을 수 있는 커널을 성공적으로 부팅할 수 있기를 바랍니다.이 커널 버전은 패킷 mmap이 어떻게 작동하는지 알아내는 데 도움이 된다는 것을 명심하세요.이 커널에서 직접적인 성능 정보를 얻을 수 없습니다.af_packet.c
최적화되지 않은 상태로 구축되었습니다.만약 우리가 최적화된 버전이 어떻게 작동할지에 대한 정보를 얻어야 한다는 것이 밝혀지면, 우리는 다음과 같이 또 다른 커널을 구축할 수 있습니다.af_packet.c
최적화 기능으로 컴파일되지만, SystemTap에서 볼 수 있도록 최적화되지 않은 변수를 통해 정보를 노출하는 일부 계측 코드가 추가됩니다.
그럼 정보를 얻기 위해 사용해 보겠습니다.다음 항목을 살펴봅니다.
# This is specific to net/packet/af_packet.c 3.13.0-116
function print_ts() {
ts = gettimeofday_us();
printf("[%10d.%06d] ", ts/1000000, ts%1000000);
}
# 325 static void __packet_set_status(struct packet_sock *po, void *frame, int status)
# 326 {
# 327 union tpacket_uhdr h;
# 328
# 329 h.raw = frame;
# 330 switch (po->tp_version) {
# 331 case TPACKET_V1:
# 332 h.h1->tp_status = status;
# 333 flush_dcache_page(pgv_to_page(&h.h1->tp_status));
# 334 break;
# 335 case TPACKET_V2:
# 336 h.h2->tp_status = status;
# 337 flush_dcache_page(pgv_to_page(&h.h2->tp_status));
# 338 break;
# 339 case TPACKET_V3:
# 340 default:
# 341 WARN(1, "TPACKET version not supported.\n");
# 342 BUG();
# 343 }
# 344
# 345 smp_wmb();
# 346 }
probe kernel.statement("__packet_set_status@net/packet/af_packet.c:334") {
print_ts();
printf("SET(V1): %d (0x%.16x)\n", $status, $frame);
}
probe kernel.statement("__packet_set_status@net/packet/af_packet.c:338") {
print_ts();
printf("SET(V2): %d\n", $status);
}
# 348 static int __packet_get_status(struct packet_sock *po, void *frame)
# 349 {
# 350 union tpacket_uhdr h;
# 351
# 352 smp_rmb();
# 353
# 354 h.raw = frame;
# 355 switch (po->tp_version) {
# 356 case TPACKET_V1:
# 357 flush_dcache_page(pgv_to_page(&h.h1->tp_status));
# 358 return h.h1->tp_status;
# 359 case TPACKET_V2:
# 360 flush_dcache_page(pgv_to_page(&h.h2->tp_status));
# 361 return h.h2->tp_status;
# 362 case TPACKET_V3:
# 363 default:
# 364 WARN(1, "TPACKET version not supported.\n");
# 365 BUG();
# 366 return 0;
# 367 }
# 368 }
probe kernel.statement("__packet_get_status@net/packet/af_packet.c:358") {
print_ts();
printf("GET(V1): %d (0x%.16x)\n", $h->h1->tp_status, $frame);
}
probe kernel.statement("__packet_get_status@net/packet/af_packet.c:361") {
print_ts();
printf("GET(V2): %d\n", $h->h2->tp_status);
}
# 2088 static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
# 2089 {
# [...]
# 2136 do {
# 2137 ph = packet_current_frame(po, &po->tx_ring,
# 2138 TP_STATUS_SEND_REQUEST);
# 2139
# 2140 if (unlikely(ph == NULL)) {
# 2141 schedule();
# 2142 continue;
# 2143 }
# 2144
# 2145 status = TP_STATUS_SEND_REQUEST;
# 2146 hlen = LL_RESERVED_SPACE(dev);
# 2147 tlen = dev->needed_tailroom;
# 2148 skb = sock_alloc_send_skb(&po->sk,
# 2149 hlen + tlen + sizeof(struct sockaddr_ll),
# 2150 0, &err);
# 2151
# 2152 if (unlikely(skb == NULL))
# 2153 goto out_status;
# 2154
# 2155 tp_len = tpacket_fill_skb(po, skb, ph, dev, size_max, proto,
# 2156 addr, hlen);
# [...]
# 2176 skb->destructor = tpacket_destruct_skb;
# 2177 __packet_set_status(po, ph, TP_STATUS_SENDING);
# 2178 atomic_inc(&po->tx_ring.pending);
# 2179
# 2180 status = TP_STATUS_SEND_REQUEST;
# 2181 err = dev_queue_xmit(skb);
# 2182 if (unlikely(err > 0)) {
# [...]
# 2195 }
# 2196 packet_increment_head(&po->tx_ring);
# 2197 len_sum += tp_len;
# 2198 } while (likely((ph != NULL) ||
# 2199 ((!(msg->msg_flags & MSG_DONTWAIT)) &&
# 2200 (atomic_read(&po->tx_ring.pending))))
# 2201 );
# 2202
# [...]
# 2213 return err;
# 2214 }
probe kernel.function("tpacket_snd") {
print_ts();
printf("tpacket_snd: args(%s)\n", $$parms);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2140") {
print_ts();
printf("tpacket_snd:2140: current frame ph = 0x%.16x\n", $ph);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2141") {
print_ts();
printf("tpacket_snd:2141: (ph==NULL) --> schedule()\n");
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2142") {
print_ts();
printf("tpacket_snd:2142: flags 0x%x, pending %d\n",
$msg->msg_flags, $po->tx_ring->pending->counter);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2197") {
print_ts();
printf("tpacket_snd:2197: flags 0x%x, pending %d\n",
$msg->msg_flags, $po->tx_ring->pending->counter);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2213") {
print_ts();
printf("tpacket_snd: return(%d)\n", $err);
}
# 1946 static void tpacket_destruct_skb(struct sk_buff *skb)
# 1947 {
# 1948 struct packet_sock *po = pkt_sk(skb->sk);
# 1949 void *ph;
# 1950
# 1951 if (likely(po->tx_ring.pg_vec)) {
# 1952 __u32 ts;
# 1953
# 1954 ph = skb_shinfo(skb)->destructor_arg;
# 1955 BUG_ON(atomic_read(&po->tx_ring.pending) == 0);
# 1956 atomic_dec(&po->tx_ring.pending);
# 1957
# 1958 ts = __packet_set_timestamp(po, ph, skb);
# 1959 __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts);
# 1960 }
# 1961
# 1962 sock_wfree(skb);
# 1963 }
probe kernel.statement("tpacket_destruct_skb@net/packet/af_packet.c:1959") {
print_ts();
printf("tpacket_destruct_skb:1959: ph = 0x%.16x, ts = 0x%x, pending %d\n",
$ph, $ts, $po->tx_ring->pending->counter);
}
함수를 정의합니다(print_ts
마이크로초 해상도로 unix 에포크타임 출력) 및 다수의 프로브.
먼저 tx_ring에 있는 패킷의 상태가 설정되거나 읽혀질 때 정보를 출력하도록 프로브를 정의합니다.다음으로 호출 및 반환을 위한 탐색기를 정의합니다.tpacket_snd
그리고 그 안에 있는 지점들에서do {...} while (...)
tx_ring의 패킷을 루프 처리합니다.마지막으로 skb destructor에 프로브를 추가합니다.
시스템Tap 스크립트를 시작할 수 있습니다.sudo stap status.stp
. 그다음달리기sudo packet_mmap -c 2 <interface>
인터페이스를 통해 2개의 프레임을 전송합니다.SystemTap 스크립트에서 얻은 출력은 다음과 같습니다.
[1492581245.839850] tpacket_snd: args(po=0xffff88016720ee38 msg=0x14)
[1492581245.839865] GET(V1): 1 (0xffff880241202000)
[1492581245.839873] tpacket_snd:2140: current frame ph = 0xffff880241202000
[1492581245.839887] SET(V1): 2 (0xffff880241202000)
[1492581245.839918] tpacket_snd:2197: flags 0x40, pending 1
[1492581245.839923] GET(V1): 1 (0xffff88013499c000)
[1492581245.839929] tpacket_snd:2140: current frame ph = 0xffff88013499c000
[1492581245.839935] SET(V1): 2 (0xffff88013499c000)
[1492581245.839946] tpacket_snd:2197: flags 0x40, pending 2
[1492581245.839951] GET(V1): 0 (0xffff88013499e000)
[1492581245.839957] tpacket_snd:2140: current frame ph = 0x0000000000000000
[1492581245.839961] tpacket_snd:2141: (ph==NULL) --> schedule()
[1492581245.839977] tpacket_snd:2142: flags 0x40, pending 2
[1492581245.839984] tpacket_snd: return(300)
[1492581245.840077] tpacket_snd: args(po=0x0 msg=0x14)
[1492581245.840089] GET(V1): 0 (0xffff88013499e000)
[1492581245.840098] tpacket_snd:2140: current frame ph = 0x0000000000000000
[1492581245.840093] tpacket_destruct_skb:1959: ph = 0xffff880241202000, ts = 0x0, pending 1
[1492581245.840102] tpacket_snd:2141: (ph==NULL) --> schedule()
[1492581245.840104] SET(V1): 0 (0xffff880241202000)
[1492581245.840112] tpacket_snd:2142: flags 0x40, pending 1
[1492581245.840116] tpacket_destruct_skb:1959: ph = 0xffff88013499c000, ts = 0x0, pending 0
[1492581245.840119] tpacket_snd: return(0)
[1492581245.840123] SET(V1): 0 (0xffff88013499c000)
다음은 네트워크 캡처입니다.
SystemTap 출력에는 유용한 정보가 많이 있습니다.우리는 볼 수 있습니다.tpacket_snd
링에서 첫 번째 프레임의 상태(TP_STATUS_SEND_REQUEST
1)를 얻은 후 다음으로 설정합니다.TP_STATUS_SENDING
(2) 두번째 것도 마찬가지입니다.다음 프레임에 상태가 있습니다.TP_STATUS_AVAILABLE
(0), 이것은 송신 요청이 아니기 때문에 호출합니다.schedule()
양보하고, 고리를 이어가는 것입니다.더이상 보낼 프레임이 없기 때문에 (ph==NULL
및 요청 차단 ()msg->msg_flags ==
MSG_DONTWAIT
) 더do {...} while (...)
루프가 종료되고,tpacket_snd
돌아온다300
, 전송을 위해 대기 중인 바이트 수입니다.
다음 분.packet_mmap
부름sendto
다시("대기열 비울 때까지 루프" 코드를 통해), 그러나 tx 링으로 보낼 데이터가 더 이상 없고, 비blocking이 요청되므로 대기열에 있는 데이터가 없으므로 즉시 0을 반환합니다.상태를 확인한 프레임은 이전 통화에서 마지막으로 확인한 프레임과 동일합니다. --- tx ring의 첫 번째 프레임으로 시작하지 않고 확인했습니다.head
(유저랜드에서는 사용할 수 없음).
비동기적으로, 첫번째 프레임에서, 프레임의 상태를 다음으로 설정하면서, 디스트럭터가 호출됩니다.TP_STATUS_AVAILABLE
그리고 보류 중인 카운트를 줄이고 두 번째 프레임을 사용합니다.비차단이 요청되지 않은 경우, 테스트 종료 시do {...} while (...)
loop은 보류 중인 모든 패킷이 NIC로 전송될 때까지 대기한 후(산란 데이터를 지원한다고 가정) 반환됩니다.러닝으로 보실 수 있습니다.packet_mmap
와 함께-t
차단 I/O를 사용하는 "스레드" 옵션("큐가 비울 때까지 루프").
몇 가지 주의할 점이 있습니다.첫째, SystemTap 출력의 타임스탬프가 증가하지 않습니다. SystemTap 출력에서 시간 순서를 유추하는 것은 안전하지 않습니다.둘째, (로컬로 수행되는) 네트워크 캡처의 타임스탬프가 서로 다르다는 점에 유의하십시오.FWIW, 인터페이스는 저렴한 타워 컴퓨터에서 저렴한 1G입니다.
그래서 이 시점에서, 우리는 그들이 어떤 방식으로af_packet
공유 tx 링을 처리하고 있습니다.다음은 tx 링의 프레임이 네트워크 인터페이스로 이동하는 방법입니다.Linux 네트워킹 커널의 제어 흐름에 대한 개요에 대한 이 섹션(계층 2 전송이 처리되는 방법)을 검토하는 것이 도움이 될 수 있습니다.
좋아요, 계층 2 전송이 어떻게 처리되는지 기본적으로 이해하고 있다면 이 패킷 mmap 인터페이스는 거대한 소방 호스가 되어야 할 것 같습니다. 패킷과 함께 공유 tx 링을 로드하고, 호출합니다.sendto()
와 함께MSG_DONTWAIT
,그리고 나서.tpacket_snd
tx 대기열을 통해 skb를 생성하고 qdisc에 대기열을 만듭니다.동시에 skb는 qdisc에서 큐가 해제되고 하드웨어 tx 링으로 전송됩니다.skb는 복사가 아닌 tx 링의 데이터를 참조할 수 있도록 비선형이어야 하며, 좋은 최신 NIC는 분산된 데이터를 처리하고 tx 링의 데이터도 참조할 수 있어야 합니다.물론, 이 가정들 중 어떤 것도 틀릴 수 있으므로, 이 소방 호스로 qdisc에 많은 상처를 입히도록 합시다.
하지만 첫째, qdisc가 어떻게 작동하는지에 대해 일반적으로 이해되지 않는 사실입니다.이들은 제한된 양의 데이터(일반적으로 프레임 수로 계산되지만 경우에 따라서는 바이트로 측정될 수도 있음)를 보유하며, 프레임을 전체 qdisc로 대기하려고 하면 일반적으로 프레임이 삭제됩니다(대기자가 수행하기로 결정한 작업에 따라).그래서 저의 원래 가설은 OP가 패킷 mmap을 사용하여 프레임을 qdisc로 너무 빨리 발사하여 많은 부분이 떨어졌다는 것이었습니다.그러나 그 생각을 너무 빨리 하지 마세요. 그것은 당신을 어떤 방향으로 이끌기 때문에, 그러나 항상 열린 마음을 가지세요.무슨 일이 일어나는지 한번 알아보겠습니다.
이를 시도해 볼 때 첫 번째 문제는 기본 qdisc입니다.pfifo_fast
통계를 보관하지 않습니다.그럼 qdisc로 대체해 보겠습니다.pfifo
그럼 되는 거지.기본적으로pfifo
대기열을 다음으로 제한합니다.TXQUEUELEN
프레임(일반적으로 기본값은 1000)입니다.하지만 압도적인 qdisc를 시연하고 싶으므로 명시적으로 50으로 설정해 보겠습니다.
$ sudo tc qdisc add dev eth0 root pfifo limit 50
$ tc -s -d qdisc show dev eth0
qdisc pfifo 8004: root refcnt 2 limit 50p
Sent 42 bytes 1 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
프레임 처리에 걸리는 시간도 측정해 보겠습니다.tpacket_snd
시스템Tap 스크립트를 사용하여call-return.stp
:
# This is specific to net/packet/af_packet.c 3.13.0-116
function print_ts() {
ts = gettimeofday_us();
printf("[%10d.%06d] ", ts/1000000, ts%1000000);
}
# 2088 static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
# 2089 {
# [...]
# 2213 return err;
# 2214 }
probe kernel.function("tpacket_snd") {
print_ts();
printf("tpacket_snd: args(%s)\n", $$parms);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2213") {
print_ts();
printf("tpacket_snd: return(%d)\n", $err);
}
시스템Tap 스크립트 시작하기sudo stap call-return.stp
그런 다음 8096 1500바이트 프레임을 빈약한 50프레임 용량의 qdisc로 발사합니다.
$ sudo ./packet_mmap -c 8096 -s 1500 eth0
[...]
STARTING TEST:
data offset = 32 bytes
start fill() thread
send 8096 packets (+12144000 bytes)
end of task fill()
Loop until queue empty (0)
END (number of error:0)
그럼 qdisc에 의해 몇 개의 패킷이 삭제되었는지 확인해 보겠습니다.
$ tc -s -d qdisc show dev eth0
qdisc pfifo 8004: root refcnt 2 limit 50p
Sent 25755333 bytes 8606 pkt (dropped 1, overlimits 0 requeues 265)
backlog 0b 0p requeues 265
WAT? 8096 프레임 중 하나를 50 프레임 qdisc에 버렸다고요?SystemTap 출력을 확인해 보겠습니다.
[1492603552.938414] tpacket_snd: args(po=0xffff8801673ba338 msg=0x14)
[1492603553.036601] tpacket_snd: return(12144000)
[1492603553.036706] tpacket_snd: args(po=0x0 msg=0x14)
[1492603553.036716] tpacket_snd: return(0)
WAT? 8096 프레임을 처리하는 데 거의 100ms가 걸렸습니다tpacket_snd
? 1 gigabit/s ~= 97ms의 1500바이트/프레임에서 8096 프레임을 전송하는 데 실제로 얼마나 걸리는지 확인해 보겠습니다. WAT?뭔가 막히는 냄새가 납니다.
좀 더 자세히 알아보겠습니다.tpacket_snd
신음소리:
skb = sock_alloc_send_skb(&po->sk,
hlen + tlen + sizeof(struct sockaddr_ll),
0, &err);
.0
악의가 없어 보이지만, 사실 그것이 노블록의 주장입니다.그럴 것 같네요.msg->msg_flags & MSG_DONTWAIT
(이것이 4.1에서 수정된 것으로 드러났습니다.)여기서 일어나고 있는 일은 qdisc의 크기가 유일한 제한 자원이 아니라는 것입니다.skb에 대한 공간 할당이 소켓의 sndbuf 제한 크기를 초과하는 경우 이 호출은 skb가 해제될 때까지 기다리거나 반환되도록 차단됩니다.-EAGAIN
blocking이 아닌 발신자에게 전송합니다.V4.1의 수정 프로그램에서 비차단 요청이 있으면 0이 아닌 경우 기록된 바이트 수를 반환합니다.-EAGAIN
전화를 건 사람에게, 거의 누군가가 당신이 이것을 사용하는 방법을 알아내는 것을 원하지 않는 것처럼 보입니다(예를 들어, 당신은 tx 링에 80MB의 데이터를 채웁니다, 다음으로 전화를 보냅니다).MSG_DONTWAIT
, 그리고 당신은 당신이 150KB를 보낸 결과를 돌려받습니다.EWOULDBLOCK
).
따라서 4.1 이전 커널을 실행하고 있다면(OP가 4.1을 실행하고 있으며 이 버그의 영향을 받지 않는다고 생각합니다) 패치를 적용해야 합니다.af_packet.c
새로운 커널을 구축하거나 커널 4.1 또는 그 이상으로 업그레이드할 수 있습니다.
지금 사용하고 있는 기계가 3.13을 실행하고 있기 때문에 커널의 패치 버전을 부팅했습니다.sndbuf가 가득 차면 차단하지는 않겠지만, 우리는 여전히 다음과 함께 돌아올 것입니다.-EAGAIN
. 제가 몇 가지 변경을 했습니다.packet_mmap.c
sndbuff의 기본 크기를 늘리고 사용하려면SO_SNDBUFFORCE
필요한 경우 소켓당 시스템 최대값을 재정의합니다(각 프레임에 대해 약 750바이트 + 프레임 크기가 필요한 것으로 보임).저는 또한 몇 가지를 추가했습니다.call-return.stp
sndbuf록()를 하려면 다음과 같이 하십시오.sk_sndbuf
(), ()sk_wmem_alloc
), 에 의해 반환된 오류sock_alloc_send_skb
그리고 모든 오류가 발생했을 때dev_queue_xmit
skb를 qdisc에 합니다.새 버전은 다음과 같습니다.
# This is specific to net/packet/af_packet.c 3.13.0-116
function print_ts() {
ts = gettimeofday_us();
printf("[%10d.%06d] ", ts/1000000, ts%1000000);
}
# 2088 static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
# 2089 {
# [...]
# 2133 if (size_max > dev->mtu + reserve + VLAN_HLEN)
# 2134 size_max = dev->mtu + reserve + VLAN_HLEN;
# 2135
# 2136 do {
# [...]
# 2148 skb = sock_alloc_send_skb(&po->sk,
# 2149 hlen + tlen + sizeof(struct sockaddr_ll),
# 2150 msg->msg_flags & MSG_DONTWAIT, &err);
# 2151
# 2152 if (unlikely(skb == NULL))
# 2153 goto out_status;
# [...]
# 2181 err = dev_queue_xmit(skb);
# 2182 if (unlikely(err > 0)) {
# 2183 err = net_xmit_errno(err);
# 2184 if (err && __packet_get_status(po, ph) ==
# 2185 TP_STATUS_AVAILABLE) {
# 2186 /* skb was destructed already */
# 2187 skb = NULL;
# 2188 goto out_status;
# 2189 }
# 2190 /*
# 2191 * skb was dropped but not destructed yet;
# 2192 * let's treat it like congestion or err < 0
# 2193 */
# 2194 err = 0;
# 2195 }
# 2196 packet_increment_head(&po->tx_ring);
# 2197 len_sum += tp_len;
# 2198 } while (likely((ph != NULL) ||
# 2199 ((!(msg->msg_flags & MSG_DONTWAIT)) &&
# 2200 (atomic_read(&po->tx_ring.pending))))
# 2201 );
# [...]
# 2213 return err;
# 2214 }
probe kernel.function("tpacket_snd") {
print_ts();
printf("tpacket_snd: args(%s)\n", $$parms);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2133") {
print_ts();
printf("tpacket_snd:2133: sk_sndbuf = %d sk_wmem_alloc = %d\n",
$po->sk->sk_sndbuf, $po->sk->sk_wmem_alloc->counter);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2153") {
print_ts();
printf("tpacket_snd:2153: sock_alloc_send_skb err = %d, sk_sndbuf = %d sk_wmem_alloc = %d\n",
$err, $po->sk->sk_sndbuf, $po->sk->sk_wmem_alloc->counter);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2182") {
if ($err != 0) {
print_ts();
printf("tpacket_snd:2182: dev_queue_xmit err = %d\n", $err);
}
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2187") {
print_ts();
printf("tpacket_snd:2187: destructed: net_xmit_errno = %d\n", $err);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2194") {
print_ts();
printf("tpacket_snd:2194: *NOT* destructed: net_xmit_errno = %d\n", $err);
}
probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2213") {
print_ts();
printf("tpacket_snd: return(%d) sk_sndbuf = %d sk_wmem_alloc = %d\n",
$err, $po->sk->sk_sndbuf, $po->sk->sk_wmem_alloc->counter);
}
다시 시도해 보겠습니다.
$ sudo tc qdisc add dev eth0 root pfifo limit 50
$ tc -s -d qdisc show dev eth0
qdisc pfifo 8001: root refcnt 2 limit 50p
Sent 2154 bytes 21 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
$ sudo ./packet_mmap -c 200 -s 1500 eth0
[...]
c_sndbuf_sz: 1228800
[...]
STARTING TEST:
data offset = 32 bytes
send buff size = 1228800
got buff size = 425984
buff size smaller than desired, trying to force...
got buff size = 2457600
start fill() thread
send: No buffer space available
end of task fill()
send: No buffer space available
Loop until queue empty (-1)
[repeated another 17 times]
send 3 packets (+4500 bytes)
Loop until queue empty (4500)
Loop until queue empty (0)
END (number of error:0)
$ tc -s -d qdisc show dev eth0
qdisc pfifo 8001: root refcnt 2 limit 50p
Sent 452850 bytes 335 pkt (dropped 19, overlimits 0 requeues 3)
backlog 0b 0p requeues 3
다음은 SystemTap 출력입니다.
[1492759330.907151] tpacket_snd: args(po=0xffff880393246c38 msg=0x14)
[1492759330.907162] tpacket_snd:2133: sk_sndbuf = 2457600 sk_wmem_alloc = 1
[1492759330.907491] tpacket_snd:2182: dev_queue_xmit err = 1
[1492759330.907494] tpacket_snd:2187: destructed: net_xmit_errno = -105
[1492759330.907500] tpacket_snd: return(-105) sk_sndbuf = 2457600 sk_wmem_alloc = 218639
[1492759330.907646] tpacket_snd: args(po=0x0 msg=0x14)
[1492759330.907653] tpacket_snd:2133: sk_sndbuf = 2457600 sk_wmem_alloc = 189337
[1492759330.907688] tpacket_snd:2182: dev_queue_xmit err = 1
[1492759330.907691] tpacket_snd:2187: destructed: net_xmit_errno = -105
[1492759330.907694] tpacket_snd: return(-105) sk_sndbuf = 2457600 sk_wmem_alloc = 189337
[repeated 17 times]
[1492759330.908541] tpacket_snd: args(po=0x0 msg=0x14)
[1492759330.908543] tpacket_snd:2133: sk_sndbuf = 2457600 sk_wmem_alloc = 189337
[1492759330.908554] tpacket_snd: return(4500) sk_sndbuf = 2457600 sk_wmem_alloc = 196099
[1492759330.908570] tpacket_snd: args(po=0x0 msg=0x14)
[1492759330.908572] tpacket_snd:2133: sk_sndbuf = 2457600 sk_wmem_alloc = 196099
[1492759330.908576] tpacket_snd: return(0) sk_sndbuf = 2457600 sk_wmem_alloc = 196099
하고 있습니다. 찰 그 에서 합니다 됩니다. sndbuf 제한을 초과하는 버그를 수정하여 sndbuf 제한이 되지 않도록 조정했습니다. 이제 tx ring의 프레임이 꽉 찰 때까지 qdisc에 대기되어 있으며, 그 시점에서 반환됩니다.ENOBUFS
.
다음 문제는 qdisc에 효율적으로 퍼블리싱하여 인터페이스를 계속 사용할 수 있도록 하는 방법입니다.다음의 구현에 유의하십시오.packet_poll
우리가 qdisc를 채우고 돌아오는 경우에는 쓸모가 없습니다.ENOBUFS
, 왜냐하면 그것은 단지 머리가 그런 것인지를 묻기 때문입니다.TP_STATUS_AVAILABLE
, 이 경우에 남아있을 것입니다.TP_STATUS_SEND_REQUEST
다시 전화가 올 때까지sendto
프레임을 qdisc에 대기열에 넣습니다.(packet_mmap.c에서 업데이트된) 간단한 편법은 다음이 아닌 다른 오류가 발생하거나 성공할 때까지 전송을 반복하는 것입니다.ENOBUFS
아니면EAGAIN
.
어쨌든, NIC의 부족을 효율적으로 방지할 수 있는 완벽한 솔루션이 없더라도 지금은 OPs 질문에 답할 수 있을 만큼 충분히 알고 있습니다.
우리가 배운 바에 의하면, OP calls가 blocking mode에서 tx ring과 함께 to를 보낼 때,tpacket_snd
으로 약 skbs를 qdisc에 된 프레임 데이터가 sndbuf음를 213K다까지) skbs로 qdisc약을 또한 공유 tx 링에서 참조된 프레임 데이터가 차단될 때(계속 유지되는 동안) 이 쪽으로 카운트된다는 것을 발견했습니다.pg_vec_lock
더 많은 는 다시 차단할 입니다.) skb가 프리업됨에 따라 더 많은 프레임들이 대기하고, 아마도 sndbuf가 다시 초과되어 다시 차단할 것입니다.결국 모든 데이터가 qdisc에 큐잉되겠지만tpacket_snd
모든 프레임이 전송될 때까지 계속 차단됩니다(드라이버 링의 skb가 tx 링의 프레임을 참조하므로 NIC가 수신할 때까지 사용 가능한 것으로 표시할 수 없음).pg_vec_lock
이 이 시점에서 NIC가 부족해지고 다른 소켓 기록기가 잠금으로 차단됩니다.
반면 OP가 패킷을 한 번에 퍼블리싱할 때는 다음과 같이 처리됩니다.packet_snd
sndbuf에 자리가 없을 경우 차단한 다음 프레임을 qdisc에 대기시키고 즉시 돌아갑니다.프레임이 전송될 때까지 기다리지 않습니다.qdisc가 배출되는 동안 추가 프레임이 대기열에 있을 수 있습니다.게시자가 계속 유지할 수 있다면 NIC가 절대로 부족해지지 않습니다.
또한 op는 전송할 때마다 tx 링에 복사하여 tx 링을 사용하지 않을 때 고정 프레임 버퍼를 통과시키는 것과 비교합니다.이 방식으로 복사하지 않으면 속도가 향상되지 않습니다(tx 링 사용의 유일한 이점은 아니지만).
언급URL : https://stackoverflow.com/questions/43193889/sending-data-with-packet-mmap-and-packet-tx-ring-is-slower-than-normal-withou
'itsource' 카테고리의 다른 글
왜 탈참조를 가리켜 탈참조라고 합니까? (0) | 2023.10.29 |
---|---|
jQuery .hasClass() vs.is() (0) | 2023.10.29 |
도커 파일을 사용하여 로컬 wp-content 파일을 Wordpress 컨테이너에 복사하는 방법 (0) | 2023.10.29 |
Class current_page_item 추가 중 (0) | 2023.10.29 |
ASP에서 NuGet 패키지 위치는 어디입니까?NET Core? (0) | 2023.10.29 |