DPDK
DPDK(Data Plane Development Kit) 고성능 패킷 처리 구조를 심층 분석합니다. EAL 초기화와 hugepage/NUMA 메모리 모델, IOVA(VA/PA), PMD poll-mode 루프, rte_mbuf/ring/graph/mempool 데이터 경로, VFIO/UIO 및 bifurcated PMD, AF_XDP와 representor/rte_flow/hairpin, dmadev, OVS-DPDK·VPP·testpmd 운영 시나리오, trace·metrics·power 관리와 하드닝·트러블슈팅까지 실무 중심으로 다룹니다.
핵심 요약
- 패킷 수명주기 — ingress, 처리, egress 경로를 연결합니다.
- 큐/버퍼 모델 — sk_buff와 큐 지점의 역할을 분리합니다.
- 정책/데이터 분리 — 제어 평면과 데이터 평면을 구분합니다.
- 성능 지표 — PPS, 지연, 드롭 원인을 함께 분석합니다.
- 오프로딩 경계 — NIC/XDP/DPDK 경계를 명확히 유지합니다.
단계별 이해
- 경로 고정
문제가 발생한 ingress/egress 지점을 먼저 특정합니다. - 큐 관찰
백로그와 드롭 위치를 계측합니다. - 정책 반영 확인
라우팅/필터 변경이 데이터 경로에 반영됐는지 봅니다. - 부하 검증
실제 트래픽 패턴에서 재현성을 확인합니다.
DPDK는 Intel이 주도하여 개발한 고성능 패킷 처리 프레임워크로, 커널 네트워크 스택을 완전히 바이패스하여 유저 공간에서 NIC를 직접 제어합니다. 표준 커널 드라이버가 인터럽트 기반으로 패킷을 처리하는 반면, DPDK는 폴링(polling) 기반 PMD(Poll Mode Driver)를 사용하여 컨텍스트 스위칭과 인터럽트 오버헤드를 제거합니다. 10~400GbE 환경에서 수십~수백 Mpps(백만 패킷/초)의 처리량을 단일 서버에서 달성할 수 있으며, 통신사(NFV), 클라우드(가상 스위칭), 금융(초저지연 트레이딩), CDN, 보안 장비 등에서 핵심 기술로 사용됩니다.
VFIO, UIO, hugepages, IOMMU 등
여러 서브시스템에 의존하며, 최근에는 커널의 AF_XDP 소켓을 PMD 백엔드로 사용하는
하이브리드 접근도 지원합니다. 커널 개발자가 DPDK의 동작 원리를 이해하면
드라이버 최적화, VFIO/IOMMU 튜닝, AF_XDP 개발 등에서 큰 도움이 됩니다.
DPDK 아키텍처 개요
| 계층 | 구성 요소 | 역할 |
|---|---|---|
| Application | l2fwd, l3fwd, OVS-DPDK, VPP, Pktgen | 패킷 처리 로직 구현 |
| Core Libraries | mbuf, Ring, Mempool, Hash, LPM, ACL | 고성능 데이터 구조 및 알고리즘 |
| EAL | Environment Abstraction Layer | hugepage, CPU 코어, PCI 디바이스, 타이머, 로깅 추상화 |
| PMD | Poll Mode Driver (ixgbe, i40e, mlx5, virtio, af_xdp...) | NIC별 RX/TX 드라이버 (유저 공간) |
| Kernel | VFIO, UIO, hugepages, IOMMU | 디바이스 접근 및 메모리 관리 지원 |
| Hardware | NIC, DMA, PCIe BAR | 물리적 패킷 송수신, DMA 전송 |
커널 네트워크 스택 vs DPDK 비교
| 항목 | 커널 네트워크 스택 | DPDK |
|---|---|---|
| 실행 공간 | 커널 공간 | 유저 공간 |
| 패킷 수신 모델 | 인터럽트 → NAPI 폴링 (하이브리드) | 100% 폴링 (busy-wait) |
| 패킷 버퍼 | sk_buff (동적 할당) | rte_mbuf (사전 할당 Mempool) |
| 프로토콜 스택 | 완전한 L2~L7 (TCP/IP, Netfilter, conntrack...) | 없음 (앱이 직접 구현 또는 별도 라이브러리) |
| CPU 사용 | 필요 시만 사용 (이벤트 기반) | 전용 코어 상시 100% 사용 (busy-poll) |
| 지연 시간 | 수~수십 μs | 수백 ns ~ 수 μs |
| 처리량 (64B pkt) | ~1-5 Mpps/core | ~14.88 Mpps/core (10GbE wire rate) |
| 메모리 관리 | SLAB/SLUB + per-CPU 캐시 | Hugepage 기반 Mempool |
| 보안/격리 | 네임스페이스, Netfilter, SELinux 등 | IOMMU/VFIO로 DMA 격리만 |
| 도구/디버깅 | tcpdump, ss, ip, nftables, eBPF | dpdk-proc-info, dpdk-pdump, DPDK telemetry |
| 적용 분야 | 범용 서버/데스크탑/IoT | NFV, SDN 스위칭, 패킷 브로커, DPI, 로드밸런서 |
iptables, tc, conntrack, TCP/IP 프로토콜 처리 등
커널이 제공하는 모든 기능을 사용할 수 없습니다. 이런 기능이 필요하면 앱에서 직접 구현하거나
VPP/FD.io 같은 유저 공간 네트워크 스택을 사용해야 합니다. 또한 전용 CPU 코어를 상시 폴링에 사용하므로
코어 수가 제한된 환경에서는 비효율적입니다.
EAL (Environment Abstraction Layer)
EAL은 DPDK의 초기화 계층으로, 애플리케이션이 하드웨어와 OS 세부 사항에 독립적으로 동작하도록 추상화합니다.
rte_eal_init() 호출 시 수행되는 핵심 작업:
/* DPDK 애플리케이션 기본 구조 */
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
int main(int argc, char *argv[])
{
/* EAL 초기화: hugepage 매핑, 코어 설정, PCI 스캔 */
int ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_exit(EXIT_FAILURE, "EAL init failed\\n");
/* Mempool 생성 (패킷 버퍼 풀) */
struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create(
"MBUF_POOL",
8192, /* 풀 크기 (mbuf 개수) */
256, /* per-core 캐시 크기 */
0, /* priv_size */
RTE_MBUF_DEFAULT_BUF_SIZE, /* 데이터룸 크기 */
rte_socket_id() /* NUMA 소켓 */
);
/* 포트 설정 및 시작 */
struct rte_eth_conf port_conf = { 0 };
rte_eth_dev_configure(port_id, nb_rx_q, nb_tx_q, &port_conf);
rte_eth_rx_queue_setup(port_id, 0, 1024, rte_eth_dev_socket_id(port_id),
NULL, mbuf_pool);
rte_eth_tx_queue_setup(port_id, 0, 1024, rte_eth_dev_socket_id(port_id),
NULL);
rte_eth_dev_start(port_id);
/* 메인 패킷 처리 루프 (busy-poll) */
while (!force_quit) {
struct rte_mbuf *bufs[32];
uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, bufs, 32);
if (nb_rx == 0)
continue;
/* 패킷 처리 ... */
process_packets(bufs, nb_rx);
uint16_t nb_tx = rte_eth_tx_burst(port_id, 0, bufs, nb_rx);
for (uint16_t i = nb_tx; i < nb_rx; i++)
rte_pktmbuf_free(bufs[i]); /* 전송 실패 mbuf 반환 */
}
rte_eal_cleanup();
return 0;
}
-l 0-3— 사용할 논리 코어 목록. DPDK 워커 스레드가 이 코어에 1:1 매핑-n 4— 메모리 채널 수 (NUMA interleaving 최적화)--socket-mem 1024,1024— NUMA 노드별 hugepage 할당량 (MB)-a 0000:03:00.0— 허용할 PCI 디바이스 (allowlist)--file-prefix dpdk1— 여러 DPDK 프로세스 공존 시 hugepage 파일 구분--proc-type secondary— 멀티프로세스 모드 (primary/secondary)--no-huge— hugepage 없이 실행 (개발/테스트용, 성능 저하)
PMD (Poll Mode Driver)
PMD는 DPDK의 핵심으로, 커널 드라이버 없이 유저 공간에서 NIC를 직접 제어합니다.
NIC의 PCIe BAR를 mmap()하여 하드웨어 레지스터에 직접 접근하고,
DMA 디스크립터 링(descriptor ring)을 유저 공간 메모리에 배치합니다.
전통적인 네트워크 스택은 NIC가 패킷을 수신하면 커널이 Interrupt를 발생시켜 처리합니다. 하지만 고속 패킷 처리에서 Interrupt 기반 모델은:
- Interrupt 오버헤드: 초당 수백만 개의 Interrupt가 발생하여 CPU 부하 증가
- 컨텍스트 스위치: 커널-유저 공간 전환 비용 발생
- 복사 오버헤드: 패킷이 커널 버퍼 → 유저 버퍼로 복사
PMD는 이 문제를 해결하기 위해 폴링(Polling) 방식으로 동작합니다. 애플리케이션이 직접 NIC를 폴링하여 패킷을 수신하므로 오버헤드가 없습니다.
RX (수신) 동작 원리
PMD의 RX 동작은 생산자-소비자(Producer-Consumer) 패턴을 따릅니다:
- 초기 상태: 빈 Descriptor
PMD는 초기화 시에 mbuf 메모리 풀(mempool)에서 Descriptor마다 빈 mbuf를 할당하여 Descriptor Ring에 채워둡니다. 이 mbuf는 패킷 데이터를 저장할 수 있는 버퍼입니다. - NIC의 DMA 동작
NIC가 네트워크 케이블에서 패킷을 수신하면, Descriptor에 있는 mbuf 주소로 DMA를 통해 패킷 데이터를 직접 메모리에 기록합니다. 이때 Descriptor의 DD(Descriptor Done) 비트를 1로 설정합니다. - PMD의 폴링
애플리케이션이rte_eth_rx_burst()를 호출하면, PMD는 Descriptor Ring을 순회하며 각 Descriptor의 DD 비트를 확인합니다. - 패킷 처리
DD=1인 Descriptor(패킷이 도착한)를 찾으면:- Descriptor에서 패킷 메타데이터(길이, RSS hash, VLAN 등)를 추출
- mbuf에 패킷 데이터가 있으므로, 이를 mbuf 포인터 배열에 추가
- Descriptor 재사용
사용된 Descriptor에 새로운 mbuf를 할당하여 채워 넣고, Tail Register를 갱신하여 NIC에게 "이 Descriptor를 사용 가능"함을 알려줍니다.
이 과정이 매번 반복되며, PMD는 Interrupt 없이도 패킷을 지속적으로 수신할 수 있습니다.
/* PMD의 RX burst 내부 (간략화) — ixgbe_recv_pkts() 기반 */
static uint16_t
pmd_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
{
struct pmd_rx_queue *rxq = rx_queue;
volatile union rx_desc *rxdp; /* 하드웨어 descriptor 포인터 */
uint16_t nb_rx = 0;
while (nb_rx < nb_pkts) {
rxdp = &rxq->rx_ring[rxq->rx_tail];
/* DD 비트 확인: NIC가 DMA를 완료했는가? */
if (!(rxdp->wb.status & RX_DESC_DD))
break; /* 더 이상 수신된 패킷 없음 */
/* mbuf 회수 및 메타데이터 설정 */
struct rte_mbuf *mb = rxq->sw_ring[rxq->rx_tail];
mb->pkt_len = rxdp->wb.pkt_len;
mb->data_len = rxdp->wb.pkt_len;
mb->hash.rss = rxdp->wb.rss_hash;
mb->vlan_tci = rxdp->wb.vlan_tag;
rx_pkts[nb_rx++] = mb;
/* 새 mbuf를 할당하여 descriptor에 채움 (NIC에 반환) */
struct rte_mbuf *nmb = rte_mbuf_raw_alloc(rxq->mb_pool);
rxdp->read.pkt_addr = rte_mbuf_data_iova(nmb);
rxq->sw_ring[rxq->rx_tail] = nmb;
rxq->rx_tail = (rxq->rx_tail + 1) & rxq->rx_tail_mask;
}
/* tail 레지스터 갱신 → NIC에 새 descriptor 사용 가능 알림 */
if (nb_rx)
rte_write32(rxq->rx_tail, rxq->tail_ptr);
return nb_rx;
}
/* PMD의 TX burst 내부 (간략화) */
static uint16_t
pmd_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
{
struct pmd_tx_queue *txq = tx_queue;
uint16_t nb_tx = 0;
/* 이전에 전송 완료된 descriptor 정리 (mbuf 반환) */
tx_free_bufs(txq);
while (nb_tx < nb_pkts) {
volatile union tx_desc *txdp = &txq->tx_ring[txq->tx_tail];
struct rte_mbuf *mb = tx_pkts[nb_tx];
/* TX descriptor에 mbuf의 DMA 주소와 길이 설정 */
txdp->read.buffer_addr = rte_mbuf_data_iova(mb);
txdp->read.cmd_type_len = mb->data_len | TX_DESC_EOP | TX_DESC_RS;
txq->sw_ring[txq->tx_tail] = mb;
txq->tx_tail = (txq->tx_tail + 1) & txq->tx_tail_mask;
nb_tx++;
}
/* tail 레지스터 갱신 → NIC가 전송 시작 */
rte_wmb(); /* 메모리 배리어: descriptor 쓰기 완료 보장 */
rte_write32(txq->tx_tail, txq->tail_ptr);
return nb_tx;
}
TX (송신) 동작 원리
TX 동작은 RX의 역순으로 진행됩니다:
- 애플리케이션의 패킷 준비
애플리케이션이 전송할 패킷이 담긴 mbuf 배열을 준비합니다. - Descriptor 채우기
PMD는 각 mbuf의 DMA 주소와 길이를 TX Descriptor에 기록합니다. 명령어 필드에 패킷 끝(EOP), Report Status(RS) 플래그를 설정합니다. - Tail Register 갱신
모든 Descriptor를 채운 후, 메모리 배리어를 수행하고 Tail Register에 새 위치를 기록합니다. 이를 통해 NIC에게 "이제 전송해도 좋다"고 알려줍니다. - NIC의 DMA 및 전송
NIC가 Descriptor를 읽어 mbuf에서 데이터를 DMA로 읽어 네트워크 케이블로 전송합니다. - Descriptor 회수
전송이 완료되면 NIC가 Descriptor의 DD 비트를 설정하고, PMD가 다음 번 호출 시 사용된 mbuf를 회수하여 Mempool에 반환합니다.
rte_wmb()는 Descriptor 쓰기가 실제 메모리에 완료된 후 Tail Register를 갱신하도록 보장합니다.
이를 통해 NIC가 Descriptor를 읽을 때 데이터가 아직 메모리에 쓰여지지 않은 추측적 읽기를 방지합니다.| PMD 종류 | 백엔드 | 대표 드라이버 | 특징 |
|---|---|---|---|
| Physical PMD | VFIO / UIO | ixgbe, i40e, ice, mlx5, bnxt, ena | 실제 NIC 직접 제어, 최고 성능 |
| Virtual PMD | virtio | virtio-net, vmxnet3, avf | VM 내부에서 가상 NIC 접근 |
| AF_XDP PMD | 커널 AF_XDP 소켓 | af_xdp | 커널 기능 유지하면서 고성능, VFIO 불필요 |
| SW PMD | libpcap / TAP | pcap, net_tap | 개발/테스트용, 커널 NIC에 연결 |
| Crypto PMD | QAT / AESNI / SW | qat, aesni_mb, openssl | 암호화 가속기 접근 |
| Compress PMD | QAT / zlib | qat_comp, zlib | 압축 가속기 |
| Event PMD | 하드웨어 이벤트 스케줄러 | dlb2, sw_event | 이벤트 기반 패킷 분배 |
Rx/Tx 오프로드 플래그
DPDK PMD는 NIC 하드웨어의 오프로드 기능을 ol_flags와 tx_offload 필드로 노출합니다.
올바른 오프로드 설정은 CPU 부하를 NIC으로 이전하여 처리량을 크게 향상시킵니다.
| Rx 오프로드 | 플래그 | 동작 |
|---|---|---|
| IP Checksum 검증 | RTE_ETH_RX_OFFLOAD_IPV4_CKSUM | NIC가 IPv4 헤더 체크섬을 검증, 결과를 ol_flags에 기록 |
| TCP/UDP Checksum 검증 | RTE_ETH_RX_OFFLOAD_TCP_CKSUM | NIC가 L4 체크섬 검증 |
| VLAN 스트리핑 | RTE_ETH_RX_OFFLOAD_VLAN_STRIP | NIC가 VLAN 태그를 제거하고 vlan_tci에 저장 |
| RSS | RTE_ETH_RX_OFFLOAD_RSS_HASH | NIC가 패킷 해시 계산, hash.rss에 저장 |
| Scatter (SG) | RTE_ETH_RX_OFFLOAD_SCATTER | Jumbo Frame을 다중 mbuf에 분산 수신 |
| LRO | RTE_ETH_RX_OFFLOAD_TCP_LRO | NIC가 TCP 세그먼트를 병합 (GRO의 하드웨어 버전) |
| Timestamp | RTE_ETH_RX_OFFLOAD_TIMESTAMP | NIC가 수신 타임스탬프 기록 (PTP/IEEE 1588) |
| Tx 오프로드 | 플래그 | 동작 |
|---|---|---|
| IP Checksum 계산 | RTE_ETH_TX_OFFLOAD_IPV4_CKSUM | NIC가 송신 시 IPv4 체크섬 계산 (앱은 0으로 설정) |
| TCP/UDP Checksum | RTE_ETH_TX_OFFLOAD_TCP_CKSUM | NIC가 L4 체크섬 계산 |
| TSO (TCP Segmentation) | RTE_ETH_TX_OFFLOAD_TCP_TSO | NIC가 대형 TCP 세그먼트를 MSS 단위로 분할 |
| VLAN 삽입 | RTE_ETH_TX_OFFLOAD_VLAN_INSERT | NIC가 VLAN 태그 삽입 |
| Multi-segment | RTE_ETH_TX_OFFLOAD_MULTI_SEGS | Scatter-Gather 송신 (다중 mbuf 체인) |
| VXLAN/GRE TSO | RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO | 터널 내부 TCP에 대한 TSO |
/* Tx 오프로드 설정 예시: TSO + Checksum */
struct rte_eth_conf port_conf = {
.txmode = {
.offloads = RTE_ETH_TX_OFFLOAD_IPV4_CKSUM
| RTE_ETH_TX_OFFLOAD_TCP_CKSUM
| RTE_ETH_TX_OFFLOAD_TCP_TSO,
},
};
/* 패킷 송신 시 mbuf 오프로드 필드 설정 */
pkt->ol_flags = RTE_MBUF_F_TX_IPV4 | RTE_MBUF_F_TX_IP_CKSUM
| RTE_MBUF_F_TX_TCP_CKSUM | RTE_MBUF_F_TX_TCP_SEG;
pkt->l2_len = sizeof(struct rte_ether_hdr); /* 14 */
pkt->l3_len = sizeof(struct rte_ipv4_hdr); /* 20 */
pkt->l4_len = sizeof(struct rte_tcp_hdr); /* 20 (옵션 없음) */
pkt->tso_segsz = 1460; /* MSS */
/* Checksum: pseudo-header checksum을 앱이 계산, 나머지는 NIC */
rte_ipv4_phdr_cksum(ip_hdr, pkt->ol_flags);
/* NIC capability 조회 */
struct rte_eth_dev_info dev_info;
rte_eth_dev_info_get(port_id, &dev_info);
printf("RX offloads: 0x%lx\\n", dev_info.rx_offload_capa);
printf("TX offloads: 0x%lx\\n", dev_info.tx_offload_capa);
l2_len/l3_len이 틀리면 NIC가 잘못된 위치에 체크섬을 계산합니다.
testpmd의 csum fwd 모드로 오프로드 조합을 먼저 검증하세요.
벡터화 PMD와 스칼라 PMD
DPDK PMD는 동일 NIC에 대해 여러 코드 경로를 제공합니다. 벡터화(vectorized) PMD는 SSE/AVX2/AVX-512/NEON SIMD 명령어를 사용하여 한 번에 여러 descriptor를 처리하므로 스칼라 경로 대비 1.5~3배의 처리량을 달성합니다.
| PMD 경로 | 특징 | 활성화 조건 |
|---|---|---|
| 벡터 (AVX-512) | 16개 descriptor 동시 처리, 최고 성능 | AVX-512 CPU + 단순 오프로드 + 단일 세그먼트 |
| 벡터 (AVX2) | 8개 descriptor 동시 처리 | AVX2 CPU + 제한된 오프로드 |
| 벡터 (SSE/NEON) | 4개 descriptor 동시 처리 | 기본 x86-64 / ARM64 |
| 스칼라 (simple) | 1개씩 처리, 단순 오프로드만 | 벡터 비활성 시 fallback |
| 스칼라 (full) | 1개씩 처리, 모든 오프로드 지원 | TSO, Scatter, Tunnel 등 복잡한 오프로드 |
RTE_ETH_RX_OFFLOAD_SCATTER활성화 (Jumbo Frame)- 복잡한 Tx 오프로드 (TSO, 터널 체크섬)
- VLAN 스트리핑/삽입 (일부 PMD)
rte_eth_dev_info_get()의rx_burst_mode/tx_burst_mode로 현재 경로 확인 가능testpmd에서show port 0 rx_offload capabilities로 벡터 호환 오프로드 확인
인터럽트 모드 (하이브리드 폴링)
DPDK는 100% busy-poll만 지원하는 것이 아닙니다. Rx 인터럽트 모드를 사용하면
빈 폴링 시 epoll_wait()로 블로킹하여 CPU를 절약하고,
패킷이 도착하면 VFIO eventfd 인터럽트로 깨어나 폴링을 재개합니다.
/* 인터럽트 모드: 빈 폴링 시 epoll 대기 */
while (!force_quit) {
uint16_t nb_rx = rte_eth_rx_burst(port_id, queue_id, pkts, 32);
if (nb_rx == 0) {
/* 패킷 없음 → 인터럽트 활성화 후 대기 */
rte_eth_dev_rx_intr_enable(port_id, queue_id);
struct rte_epoll_event ev;
rte_epoll_wait(epfd, &ev, 1, timeout_ms);
rte_eth_dev_rx_intr_disable(port_id, queue_id);
continue;
}
/* 패킷 있음 → 일반 폴링 처리 */
process_packets(pkts, nb_rx);
}
| 모드 | CPU 사용 | 지연 | 적합한 경우 |
|---|---|---|---|
| Pure polling | 100% (항상) | 최소 (~수백 ns) | 고 트래픽, 지연 민감 (NFV, 금융) |
| Interrupt + polling | 부하 비례 | 첫 패킷 ~수 μs (인터럽트 복귀) | 저 트래픽, 전력 민감, 코어 부족 |
| Empty poll power | 적응형 | 설정에 따라 가변 | 가변 부하, 전력 관리 필요 |
l3fwd-power는 인터럽트 모드와 empty poll 전력 관리를 함께 시연합니다.
이 예제를 분석하면 busy-poll과 인터럽트 하이브리드의 실제 구현 패턴을 이해할 수 있습니다.
rte_mbuf 구조체
rte_mbuf는 DPDK의 패킷 버퍼로, 커널의 sk_buff에 대응하지만 설계 철학이 다릅니다.
sk_buff는 동적으로 할당되며 메타데이터가 풍부한 반면, rte_mbuf는 Mempool에서
사전 할당되어 고정 크기의 경량 구조체입니다.
/* rte_mbuf 핵심 필드 (lib/mbuf/rte_mbuf_core.h) */
struct rte_mbuf {
/* 캐시라인 0 (핫 필드) */
void *buf_addr; /* 가상 주소: headroom + 데이터 시작 */
rte_iova_t buf_iova; /* IO 가상 주소 (DMA용, IOVA) */
union {
struct {
uint32_t pkt_len; /* 전체 패킷 길이 (체인 포함) */
uint16_t data_len; /* 이 세그먼트의 데이터 길이 */
uint16_t vlan_tci; /* VLAN Tag */
};
};
uint64_t ol_flags; /* Offload 플래그 (checksum, TSO, VLAN...) */
union {
uint32_t rss; /* RSS 해시값 */
struct {
uint16_t hash;
uint16_t id;
} fdir; /* Flow Director 필터 ID */
} hash;
uint16_t data_off; /* buf_addr부터 데이터 시작까지 오프셋 (headroom) */
uint16_t refcnt; /* 참조 카운트 (공유 mbuf) */
uint16_t nb_segs; /* 체인된 세그먼트 수 */
uint16_t port; /* 입력 포트 ID */
/* 캐시라인 1 (TX offload) */
uint64_t tx_offload; /* L2/L3/L4 len, TSO segsz 등 (비트필드) */
struct rte_mempool *pool; /* 소속 Mempool (반환 시 사용) */
struct rte_mbuf *next; /* 다음 세그먼트 (체인) */
/* 메모리 배치: rte_mbuf 구조체 + headroom + packet data + tailroom */
/* 자세한 배치는 아래 SVG 참고 */
};
| 비교 항목 | sk_buff (커널) | rte_mbuf (DPDK) |
|---|---|---|
| 크기 | ~240 바이트 (가변) | 2 캐시라인 (128 바이트) 고정 |
| 할당 | __alloc_skb() → SLAB | Mempool에서 사전 할당 (rte_pktmbuf_alloc) |
| 해제 | kfree_skb() / consume_skb() | rte_pktmbuf_free() → Mempool 반환 |
| DMA 주소 | dma_map_single() 필요 | buf_iova 필드에 사전 매핑 |
| 메타데이터 | 풍부 (sk, dst, nf_bridge, tc...) | 최소한 (앱이 필요 시 priv_size로 확장) |
| 체인 | frag_list, skb_shinfo | next 포인터로 세그먼트 체인 |
| 복제 | skb_clone() (데이터 공유) | rte_pktmbuf_clone() (refcnt 증가) |
멀티 세그먼트 mbuf (Scatter-Gather)
단일 mbuf의 데이터 영역(기본 2048B)보다 큰 패킷(Jumbo Frame, 최대 9KB)은
여러 mbuf를 next 포인터로 체이닝하여 표현합니다.
이를 Scatter-Gather라고 하며, NIC의 SG DMA 기능과 연동됩니다.
/* 멀티 세그먼트 mbuf 순회 */
struct rte_mbuf *seg = pkt;
uint32_t total_len = 0;
while (seg != NULL) {
uint8_t *data = rte_pktmbuf_mtod(seg, uint8_t *);
process_segment(data, seg->data_len);
total_len += seg->data_len;
seg = seg->next;
}
/* total_len == pkt->pkt_len 이어야 정상 */
/* 멀티 세그먼트를 단일 세그먼트로 선형화 (필요 시) */
if (rte_pktmbuf_linearize(pkt) != 0)
rte_pktmbuf_free(pkt); /* 선형화 실패 (headroom 부족) */
rte_pktmbuf_linearize()보다는 세그먼트를 직접 순회하는 것이 더 효율적입니다.
Ring 라이브러리
DPDK rte_ring은 고정 크기, FIFO, lock-free 큐로,
코어 간 패킷 전달, 멀티프로세스 IPC, 태스크 분배 등에 사용됩니다.
CAS(Compare-And-Swap) 연산 기반으로 락 없이 다수 생산자/소비자를 지원합니다.
/* Ring 기본 사용 */
struct rte_ring *ring = rte_ring_create(
"MY_RING",
1024, /* 크기 (2의 거듭제곱이어야 함) */
rte_socket_id(), /* NUMA 소켓 */
RING_F_SP_ENQ | RING_F_SC_DEQ /* 단일 생산자/소비자 (더 빠름) */
);
/* 생산자: 패킷을 Ring에 삽입 */
struct rte_mbuf *pkts[32];
unsigned nb = rte_eth_rx_burst(port, 0, pkts, 32);
unsigned sent = rte_ring_enqueue_burst(ring, (void **)pkts, nb, NULL);
/* 소비자: Ring에서 패킷 추출 (다른 코어) */
struct rte_mbuf *dequeued[32];
unsigned got = rte_ring_dequeue_burst(ring, (void **)dequeued, 32, NULL);
RING_F_SP_ENQ | RING_F_SC_DEQ— 1:1 코어 매핑 시 최적 (파이프라인 모델)RING_F_MP_HTS_ENQ | RING_F_MC_HTS_DEQ— head/tail sync 방식. 기존 MP/MC보다 공정하고 순서 보장 강화- Ring 크기는 반드시 2의 거듭제곱이어야 합니다 (마스크 연산 최적화)
Mempool과 메모리 관리
rte_mempool은 고정 크기 객체의 풀로, DPDK에서 mbuf 할당/해제의 핵심입니다.
Hugepage 위에 구축되며, per-core 캐시로 코어 간 경합을 최소화합니다.
/* Mempool 생성과 NUMA 인식 */
struct rte_mempool *pool;
/* 기본: 패킷 mbuf 풀 */
pool = rte_pktmbuf_pool_create(
"PKT_POOL",
65535, /* 총 mbuf 개수 (2^n - 1 권장) */
512, /* per-core 캐시 크기 (0이면 캐시 비활성) */
0, /* priv_size: mbuf당 추가 메타데이터 크기 */
RTE_MBUF_DEFAULT_BUF_SIZE, /* 2048 + 128(headroom) = 2176 */
rte_socket_id() /* NUMA 노드 */
);
/* 고급: 외부 메모리로 Mempool 생성 (huge 1GB) */
struct rte_pktmbuf_extmem ext_mem = {
.buf_ptr = mmap_addr, /* 사전 매핑된 hugepage 주소 */
.buf_iova = iova_addr, /* IOMMU 매핑된 DMA 주소 */
.buf_len = 1ULL << 30, /* 1GB */
.elt_size = 2176, /* mbuf + data 크기 */
};
pool = rte_pktmbuf_pool_create_extbuf("EXT_POOL", 65535, 512,
0, 2176, rte_socket_id(),
&ext_mem, 1);
- TLB 효율 — 64K개 mbuf × 2KB = 128MB. 4KB 페이지면 32,768 TLB 엔트리 필요하지만, 2MB hugepage면 64개, 1GB hugepage면 1개
- 연속 물리 메모리 — NIC DMA가 물리적으로 연속된 영역을 필요로 함. Hugepage는 2MB/1GB 단위로 물리 연속 보장
- IOVA 변환 단순화 — 큰 페이지 단위로 IOMMU 매핑하므로 IOTLB 미스 최소화
- 페이지 폴트 제거 — mmap 시 MAP_POPULATE로 즉시 물리 페이지 할당. 런타임 페이지 폴트 없음
DPDK 커널 인터페이스: VFIO vs UIO
DPDK가 유저 공간에서 NIC를 제어하려면 커널의 도움이 필요합니다. PCIe BAR 메모리를 유저 공간에 매핑하고, DMA를 위한 물리/IO 주소 변환을 제공하는 두 가지 메커니즘:
| 항목 | VFIO (vfio-pci) | UIO (igb_uio / uio_pci_generic) |
|---|---|---|
| IOMMU | 필수 (DMA 격리) | 불필요 (DMA 격리 없음) |
| 보안 | 안전 (IOMMU가 DMA 범위 제한) | 위험 (디바이스가 모든 물리 메모리에 접근 가능) |
| 인터럽트 | MSI/MSI-X eventfd (전체 지원) | 제한적 (INTx만 또는 커널 패치 필요) |
| 비특권 실행 | 가능 (/dev/vfio 권한 설정) | root 필수 |
| IOMMU 그룹 | 그룹 내 모든 디바이스를 vfio에 바인딩해야 함 | 개별 디바이스만 바인딩 |
| VM 패스스루 | KVM/QEMU와 통합 지원 | 미지원 |
| 커널 모듈 | vfio-pci (in-tree) | igb_uio (DPDK 동봉), uio_pci_generic (in-tree) |
| 권장 여부 | 권장 (기본) | 레거시 (IOMMU 없는 환경에서만) |
# ━━━ VFIO 기반 DPDK 디바이스 바인딩 ━━━
# 1. IOMMU 활성화 확인
dmesg | grep -i iommu
# "DMAR: IOMMU enabled" 또는 "AMD-Vi: IOMMU performance counters supported"
# 2. vfio-pci 모듈 로드
modprobe vfio-pci
# 3. NIC 상태 확인
dpdk-devbind --status
# Network devices using kernel driver
# 0000:03:00.0 'Ethernet Controller X710' if=ens3f0 drv=i40e
# 0000:03:00.1 'Ethernet Controller X710' if=ens3f1 drv=i40e
# 4. 커널 드라이버에서 언바인딩 → VFIO에 바인딩
ip link set ens3f0 down
dpdk-devbind --bind=vfio-pci 0000:03:00.0
# 5. 바인딩 확인
dpdk-devbind --status
# Network devices using DPDK-compatible driver
# 0000:03:00.0 'Ethernet Controller X710' drv=vfio-pci
# ━━━ Hugepage 설정 ━━━
# 2MB hugepage 1024개 = 2GB
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# NUMA 노드별 설정 (듀얼 소켓)
echo 512 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 512 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# 1GB hugepage (부팅 커맨드라인에서만 예약 가능)
# GRUB: default_hugepagesz=1G hugepagesz=1G hugepages=4
# hugetlbfs 마운트
mount -t hugetlbfs nodev /dev/hugepages
# ━━━ UIO 기반 (레거시, IOMMU 없는 환경) ━━━
modprobe uio_pci_generic
dpdk-devbind --bind=uio_pci_generic 0000:03:00.0
# ⚠ DMA 격리 없음 — 프로덕션에서 비권장
/* 커널 VFIO-PCI 드라이버가 DPDK에 제공하는 인터페이스 */
/* 1. Container FD — IOMMU 도메인 (DMA 매핑 관리) */
container_fd = open("/dev/vfio/vfio", O_RDWR);
ioctl(container_fd, VFIO_SET_IOMMU, VFIO_TYPE1v2_IOMMU);
/* 2. Group FD — IOMMU 그룹 */
group_fd = open("/dev/vfio/15", O_RDWR);
ioctl(group_fd, VFIO_GROUP_SET_CONTAINER, &container_fd);
/* 3. Device FD — 특정 PCI 디바이스 */
device_fd = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD, "0000:03:00.0");
/* 4. BAR 매핑 — NIC 레지스터에 직접 접근 */
struct vfio_region_info reg = { .argsz = sizeof(reg), .index = 0 };
ioctl(device_fd, VFIO_DEVICE_GET_REGION_INFO, ®);
bar0 = mmap(NULL, reg.size, PROT_READ | PROT_WRITE, MAP_SHARED,
device_fd, reg.offset);
/* 이제 bar0[offset]으로 NIC 레지스터 직접 read/write 가능 */
/* 5. DMA 매핑 — hugepage를 IOMMU에 등록 */
struct vfio_iommu_type1_dma_map dma = {
.argsz = sizeof(dma),
.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
.vaddr = (uint64_t)hugepage_vaddr,
.iova = (uint64_t)hugepage_vaddr, /* VA=IOVA 모드 */
.size = hugepage_size,
};
ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma);
/* NIC DMA가 이 hugepage 영역에 접근 가능 */
IOVA 모드: VA vs PA
현재 DPDK EAL은 Linux에서 버스가 요구하는 모드, 물리 주소 접근 가능 여부, IOMMU 그룹 존재 여부를 함께 보고 IOVA 모드를 고릅니다. 공식 Programmer's Guide는 대부분의 경우 RTE_IOVA_VA를 기본 선호 모드로 설명합니다. VFIO + IOMMU가 있으면 VA와 IOVA를 같게 둘 수 있어 초기화와 DMA 매핑이 단순해지기 때문입니다.
| 항목 | RTE_IOVA_VA | RTE_IOVA_PA |
|---|---|---|
| 기본 선호도 | 대부분의 Linux + VFIO 환경에서 권장 | 버스/드라이버가 PA를 요구할 때만 사용 |
| 물리 주소 접근 | 불필요 | 필요할 수 있음 |
| 권한 요구 | 낮음 | /proc/self/pagemap 접근 권한이 문제 되기 쉬움 |
| mempool/메모리 부팅 시간 | 유리 | IOVA 연속 메모리 탐색 비용이 커질 수 있음 |
| 대표 사용처 | VFIO, 멀티프로세스, 일반적인 최신 NIC | 레거시 UIO, 일부 특수 PMD/플랫폼 |
--iova-mode=va 또는 --iova-mode=pa를 명시해 EAL 결정 로직을 고정하는 편이 좋습니다.
특히 cannot open /proc/self/pagemap 계열 오류는 "DPDK 전체 문제"가 아니라 PA 모드 전제와 권한 문제인 경우가 많습니다.
비특권 실행과 EAL 메모리 옵션
최신 Linux GSG는 비특권 DPDK 실행을 공식적으로 다룹니다. 핵심은 hugepage 예약은 root가 먼저 수행하고, 그 뒤에는 hugetlbfs 디렉터리와 /dev/vfio/* 권한을 사용자나 전용 그룹에 넘겨주는 방식입니다. 멀티프로세스가 필요 없다면 --in-memory를 사용해 hugetlbfs 파일 접근 자체를 생략할 수도 있습니다.
# 1. root가 hugepage 예약
sudo dpdk-hugepages.py -p 1G --setup 2G
# 2. 사용자 전용 hugepage 마운트 생성
export HUGEDIR=$HOME/huge-1G
mkdir -p "$HUGEDIR"
sudo dpdk-hugepages.py --mount --directory "$HUGEDIR" \
--user "$(id -u)" --group "$(id -g)"
# 3. VFIO 디바이스를 일반 사용자에게 넘김
sudo dpdk-devbind --bind=vfio-pci --uid "$(id -u)" --gid "$(id -g)" \
0000:03:00.0
# 4. 필요 리소스 한도 조정 (/etc/security/limits.d/dpdk.conf)
* soft memlock unlimited
* hard memlock unlimited
* soft nofile 1048576
* hard nofile 1048576
# 5. 비특권 testpmd 실행
dpdk-testpmd -l 2-5 -n 4 \
--huge-dir "$HUGEDIR" \
--iova-mode=va \
-a 0000:03:00.0 \
-- -i --rxq=2 --txq=2 --forward-mode=io
| EAL 옵션 | 의미 | 주의점 |
|---|---|---|
--in-memory | hugetlbfs 파일 대신 익명 매핑 사용 | 멀티프로세스에는 부적합 |
--single-file-segments | 페이지별 파일 대신 memseg list당 파일 수 축소 | vhost-user처럼 FD 수가 민감한 환경에 유리 |
--legacy-mem | 시작 시 모든 hugepage를 미리 잡아 연속 IOVA 확보 | 메모리 사용량이 커지고 동적 해제가 어려움 |
--match-allocations | 할당과 해제를 더 엄격히 대응시킴 | --legacy-mem, --no-huge와 같이 쓰지 않음 |
--huge-unlink | hugetlbfs에 남는 backing file 오염을 줄임 | 멀티프로세스를 사실상 포기하는 쪽에 가깝습니다 |
cap_dac_read_search, cap_ipc_lock, cap_sys_admin가 필요할 수 있다고 설명합니다.
따라서 가능하면 vfio-pci + RTE_IOVA_VA 조합으로 문제를 단순화하는 것이 낫습니다.
bifurcated PMD와 Linux control plane
모든 DPDK 포트가 VFIO/UIO로 디바이스 전체를 탈취하는 것은 아닙니다. 최신 DPDK 문서는 일부 NIC가 bifurcated driver 모델을 제공한다고 설명합니다. 이 모델에서는 커널 드라이버가 netdev와 control plane을 유지하고, DPDK PMD는 데이터 경로를 분담합니다. 대표적인 예가 mlx5 계열입니다.
| 모델 | 커널 netdev | 장점 | 주의점 |
|---|---|---|---|
| Full device ownership | 없음 또는 비활성 | 가장 단순한 성능 모델, 디바이스를 애플리케이션이 독점 | 커널 도구와 공존이 어렵고 VFIO/IOMMU 구성이 필수 |
| Bifurcated PMD | 유지 | Linux control plane, representor, switchdev/TC와 조합 가능 | 드라이버별 제약이 크고 디버깅이 복합적 |
패킷 처리 파이프라인 모델
DPDK 애플리케이션은 두 가지 기본 패킷 처리 모델을 사용합니다:
| 코어 | 단계 | 역할 |
|---|---|---|
| Core 0 | RX burst | 수신 패킷을 가져와 다음 단계로 전달 |
| Core 1 | DPI/파싱 | L3~L7 분류 및 메타데이터 생성 |
| Core 2 | NAT/암호화 | 정책 적용/변환 처리 |
| Core 3 | TX burst | 처리 완료 패킷 송신 |
단계 간 전달은 rte_ring으로 수행합니다. 장점은 단계별 독립 확장, 단점은 Ring 통과 지연과 NUMA 크로싱 비용입니다.
/* Run-to-Completion 모델 구현 예시 (RSS 기반 L2 포워딩) */
/* 포트 설정: 코어 수만큼 RX/TX 큐 생성 */
struct rte_eth_conf port_conf = {
.rxmode = {
.mq_mode = RTE_ETH_MQ_RX_RSS, /* RSS 활성화 */
},
.rx_adv_conf = {
.rss_conf = {
.rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP,
},
},
};
rte_eth_dev_configure(port_id, nb_cores, nb_cores, &port_conf);
/* 각 코어에 RX/TX 큐 1개씩 할당 */
for (int q = 0; q < nb_cores; q++) {
rte_eth_rx_queue_setup(port_id, q, 1024, socket_id, NULL, mbuf_pool);
rte_eth_tx_queue_setup(port_id, q, 1024, socket_id, NULL);
}
/* 워커 함수: 각 코어가 독립적으로 실행 */
static int
worker_main(void *arg)
{
unsigned core_id = rte_lcore_id();
unsigned queue_id = core_to_queue[core_id];
while (!force_quit) {
struct rte_mbuf *pkts[32];
uint16_t nb_rx = rte_eth_rx_burst(port_id, queue_id, pkts, 32);
for (uint16_t i = 0; i < nb_rx; i++) {
struct rte_ether_hdr *eth = rte_pktmbuf_mtod(pkts[i],
struct rte_ether_hdr *);
/* MAC 주소 스왑 (L2 포워딩) */
struct rte_ether_addr tmp = eth->dst_addr;
eth->dst_addr = eth->src_addr;
eth->src_addr = tmp;
}
uint16_t nb_tx = rte_eth_tx_burst(port_id, queue_id, pkts, nb_rx);
for (uint16_t i = nb_tx; i < nb_rx; i++)
rte_pktmbuf_free(pkts[i]);
}
return 0;
}
/* 모든 워커 코어에서 실행 시작 */
rte_eal_mp_remote_launch(worker_main, NULL, CALL_MAIN);
Graph 라이브러리 — 노드 기반 패킷 실행기
rte_graph는 DPDK의 패킷 처리 로직을 "노드 그래프"로 표현하는 런타임입니다. 예전의 수동 while (rx_burst → process → tx_burst) 루프를 대체한다기보다, 그 루프를 재사용 가능한 노드 단위로 정규화해 주는 계층이라고 보는 편이 정확합니다. 최신 Programmer's Guide는 Graph가 run-to-completion(RTC)와 dispatch 두 실행 모델을 제공하고, 통계/멀티프로세스/그래프 export까지 지원한다고 설명합니다.
/* Graph 생성 흐름의 핵심만 남긴 예시 */
struct rte_graph_param graph_conf = {
.socket_id = rte_socket_id(),
.nb_node_patterns = 1,
.node_patterns = (const char *[]){ "*" },
};
rte_graph_t graph_id = rte_graph_create("graph0", &graph_conf);
struct rte_graph *graph = rte_graph_lookup("graph0");
/* 메인 루프는 rte_graph_walk() 한 줄로 단순화 */
while (!force_quit)
rte_graph_walk(graph);
| 항목 | 수동 RTC 루프 | Graph | Eventdev |
|---|---|---|---|
| 구현 단위 | 직접 작성한 while 루프 | 노드와 에지 | 이벤트 큐와 스케줄러 |
| 장점 | 가장 단순, 오버헤드 최소 | 구조화, 재사용, export/통계 | 유연한 워커 스케줄링, 플로우 순서 제어 |
| 적합한 경우 | 짧은 포워딩 경로 | 서비스 체인, 공통 노드 재사용 | 부하 편차가 큰 멀티스테이지 파이프라인 |
| 주의점 | 코드가 커지면 유지보수 어려움 | 노드 설계와 디버깅 규율 필요 | 가장 복잡, 디바이스 지원 차이 큼 |
AF_XDP PMD — 커널 통합 고성능 경로
AF_XDP는 DPDK의 완전한 커널 바이패스와 커널 네트워크 스택 사이의 중간 지점입니다.
커널의 XDP 훅에서 패킷을 유저 공간으로 제로카피 전달하므로, VFIO 바인딩 없이 커널 드라이버를 유지하면서
고성능 패킷 처리가 가능합니다.
| 비교 항목 | DPDK (VFIO PMD) | DPDK (AF_XDP PMD) | 커널 XDP |
|---|---|---|---|
| 커널 바이패스 | 완전 바이패스 | 부분 바이패스 (XDP 훅까지 커널) | 커널 내 처리 |
| NIC 드라이버 | DPDK PMD (유저 공간) | 커널 NIC 드라이버 유지 | 커널 NIC 드라이버 |
| 커널 기능 | 사용 불가 (tc, iptables, tcpdump...) | 부분 사용 가능 (같은 NIC의 다른 큐) | 모두 사용 가능 |
| 성능 | 최고 (~14.88 Mpps/core @10G) | 높음 (~10 Mpps/core @10G) | 높음 (~24 Mpps @XDP_TX) |
| IOMMU | 필요 (VFIO) | 불필요 | 불필요 |
| 특권 | CAP_SYS_RAWIO | CAP_NET_RAW / CAP_BPF | CAP_BPF |
| NIC 공유 | 불가 (DPDK 전용) | 가능 (큐별 분리) | 가능 |
| 설정 복잡도 | VFIO 바인딩, hugepage | XDP 프로그램 로드, UMEM 설정 | XDP 프로그램만 |
# ━━━ AF_XDP PMD로 DPDK 실행 ━━━
# NIC은 커널 드라이버에 그대로 유지 (VFIO 바인딩 불필요!)
ip link show ens3f0
# ens3f0: ... state UP ... driver: i40e
# AF_XDP PMD로 DPDK 앱 실행
dpdk-l2fwd -l 0-3 -n 4 \
--vdev net_af_xdp0,iface=ens3f0,start_queue=0,queue_count=4 \
-- -p 0x1
# 제로카피 모드 (NIC 드라이버가 지원하는 경우)
# i40e, ice, mlx5 등이 AF_XDP 제로카피 지원
dpdk-l2fwd -l 0-3 -n 4 \
--vdev net_af_xdp0,iface=ens3f0,start_queue=0,queue_count=4 \
-- -p 0x1
# 제로카피는 NIC 드라이버가 자동 감지하여 활성화
rte_flow, representor, embedded switch
현대 NIC와 DPU는 단순한 RX/TX 큐 이상의 기능을 제공합니다. PF/VF/SF 앞단의 embedded switch(eSwitch)를 하드웨어가 직접 처리하고, DPDK는 representor 포트와 rte_flow transfer 규칙으로 이 내부 스위치를 제어합니다. 이 개념을 이해해야 OVS hardware offload, SR-IOV, SmartNIC, switchdev 구성이 한 그림으로 연결됩니다.
| 개념 | 의미 | 운영 포인트 |
|---|---|---|
| PF | Physical Function. NIC의 기본 함수 | uplink 제어, VF/SF 관리, representor의 기준점 |
| VF | SR-IOV 가상 함수 | VM/컨테이너에 직결되기 쉽고 representor와 1:1 대응 |
| SF | SubFunction | 일부 최신 NIC/DPU가 제공하는 더 세밀한 함수 단위 |
| Representor | eSwitch 내부 포트를 호스트에서 표현한 가상 포트 | control plane, 통계, 미러링, 정책 투입 지점 |
transfer 규칙 | wire ↔ VF/SF ↔ representor 사이 하드웨어 스위치 도메인 규칙 | 일반 ingress/egress 큐 규칙과 구분해서 생각해야 함 |
| Bifurcated PMD | 커널 netdev와 DPDK가 기능을 분담 | 대표적으로 mlx5 계열에서 중요 |
# representor 포트를 함께 열어 eSwitch를 제어하는 예시
dpdk-testpmd -l 2-7 -n 4 \
-a 0000:03:00.0,representor=vf[0-3] \
-- -i
# rte_flow 관점에서는 attr.transfer = 1 이면
# 큐가 아니라 eSwitch 도메인에 규칙을 넣겠다는 뜻입니다.
/* eSwitch transfer 규칙의 핵심만 남긴 예시 */
struct rte_flow_attr attr = {
.ingress = 1,
.transfer = 1, /* 큐가 아닌 eSwitch 도메인 */
};
/* 패턴/액션은 NIC별 지원 범위가 다르므로
* 반드시 rte_flow_validate()와 rte_eth_dev_info_get()로 먼저 확인 */
rte_flow_validate(port_id, &attr, pattern, actions, &error);
rte_flow 패턴·액션 카탈로그
rte_flow는 NIC 하드웨어에 패킷 분류/액션 규칙을 설치하는 통합 API입니다.
패턴(pattern)으로 매칭 조건을 정의하고, 액션(action)으로 매칭된 패킷의 처리를 지정합니다.
NIC별 지원 범위가 다르므로 반드시 rte_flow_validate()로 검증해야 합니다.
| 패턴 항목 | 매칭 대상 | 사용 예 |
|---|---|---|
RTE_FLOW_ITEM_TYPE_ETH | 이더넷 헤더 (src/dst MAC, EtherType) | 특정 MAC 주소 필터링 |
RTE_FLOW_ITEM_TYPE_VLAN | VLAN 태그 (VID, PCP, DEI) | VLAN별 큐 분배 |
RTE_FLOW_ITEM_TYPE_IPV4 | IPv4 헤더 (src/dst IP, protocol, TTL) | 서브넷별 라우팅 |
RTE_FLOW_ITEM_TYPE_IPV6 | IPv6 헤더 (src/dst IP, next_hdr, flow_label) | IPv6 플로우 레이블 분류 |
RTE_FLOW_ITEM_TYPE_TCP | TCP 헤더 (src/dst port, flags) | 특정 포트 트래픽 분리 |
RTE_FLOW_ITEM_TYPE_UDP | UDP 헤더 (src/dst port) | DNS/VXLAN 트래픽 식별 |
RTE_FLOW_ITEM_TYPE_VXLAN | VXLAN 헤더 (VNI) | 오버레이 네트워크 분류 |
RTE_FLOW_ITEM_TYPE_GRE | GRE 헤더 (protocol, key) | 터널 트래픽 분류 |
RTE_FLOW_ITEM_TYPE_GENEVE | Geneve 헤더 (VNI, options) | OVN/Geneve 오버레이 |
RTE_FLOW_ITEM_TYPE_MARK | 이전 규칙이 설정한 마크 | 다단계 필터링 |
RTE_FLOW_ITEM_TYPE_META | 메타데이터 (드라이버 정의) | eSwitch/representor 간 상태 전달 |
RTE_FLOW_ITEM_TYPE_CONNTRACK | CT 상태 (established, new, invalid) | 상태 기반 방화벽 |
RTE_FLOW_ITEM_TYPE_PORT_REPRESENTOR | representor 포트 | eSwitch 규칙에서 소스/대상 지정 |
| 액션 | 동작 | 사용 예 |
|---|---|---|
RTE_FLOW_ACTION_TYPE_QUEUE | 특정 RX 큐로 전달 | 플로우별 코어 고정 |
RTE_FLOW_ACTION_TYPE_RSS | RSS로 큐 그룹에 분배 | 서비스별 RSS 분리 |
RTE_FLOW_ACTION_TYPE_DROP | 패킷 드롭 | DDoS 필터링, ACL |
RTE_FLOW_ACTION_TYPE_MARK | mbuf에 마크 값 설정 | 소프트웨어 후처리 힌트 |
RTE_FLOW_ACTION_TYPE_COUNT | 매칭 패킷/바이트 카운터 | 통계, 과금 |
RTE_FLOW_ACTION_TYPE_SET_MAC_SRC/DST | MAC 주소 변경 | L2 NAT |
RTE_FLOW_ACTION_TYPE_SET_IPV4_SRC/DST | IP 주소 변경 | NAT, 로드밸런싱 |
RTE_FLOW_ACTION_TYPE_SET_TP_SRC/DST | TCP/UDP 포트 변경 | NAPT |
RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP/DECAP | VXLAN 캡슐화/해제 | 오버레이 네트워크 |
RTE_FLOW_ACTION_TYPE_PORT_ID | 다른 포트(representor)로 전달 | eSwitch 내부 포워딩 |
RTE_FLOW_ACTION_TYPE_JUMP | 다른 flow 테이블로 이동 | 다단계 파이프라인 |
RTE_FLOW_ACTION_TYPE_METER | 트래픽 미터링 (srTCM/trTCM) | QoS, 과금 |
RTE_FLOW_ACTION_TYPE_AGE | 타임아웃 기반 규칙 만료 | 동적 플로우 관리 |
RTE_FLOW_ACTION_TYPE_CONNTRACK | CT 상태 추적/업데이트 | 하드웨어 상태 방화벽 |
RTE_FLOW_ACTION_TYPE_SAMPLE | 패킷 샘플링 (미러링) | 모니터링, 디버깅 |
/* rte_flow 규칙 생성 — IPv4 TCP 포트 80을 Queue 2로 전달 */
struct rte_flow_attr attr = { .ingress = 1 };
/* 패턴: ETH / IPv4 / TCP dst_port=80 */
struct rte_flow_item_eth eth_spec = { 0 };
struct rte_flow_item_ipv4 ipv4_spec = { 0 };
struct rte_flow_item_tcp tcp_spec = {
.hdr.dst_port = rte_cpu_to_be_16(80),
};
struct rte_flow_item_tcp tcp_mask = {
.hdr.dst_port = 0xFFFF,
};
struct rte_flow_item pattern[] = {
{ .type = RTE_FLOW_ITEM_TYPE_ETH, .spec = ð_spec },
{ .type = RTE_FLOW_ITEM_TYPE_IPV4, .spec = &ipv4_spec },
{ .type = RTE_FLOW_ITEM_TYPE_TCP, .spec = &tcp_spec, .mask = &tcp_mask },
{ .type = RTE_FLOW_ITEM_TYPE_END },
};
/* 액션: Queue 2 + 카운터 */
struct rte_flow_action_queue queue = { .index = 2 };
struct rte_flow_action_count count = { 0 };
struct rte_flow_action actions[] = {
{ .type = RTE_FLOW_ACTION_TYPE_COUNT, .conf = &count },
{ .type = RTE_FLOW_ACTION_TYPE_QUEUE, .conf = &queue },
{ .type = RTE_FLOW_ACTION_TYPE_END },
};
/* 검증 후 생성 */
struct rte_flow_error error;
if (rte_flow_validate(port_id, &attr, pattern, actions, &error) == 0) {
struct rte_flow *flow = rte_flow_create(port_id, &attr,
pattern, actions, &error);
} else {
printf("Flow validate failed: %s\\n", error.message);
}
Connection Tracking (CT) 오프로드
DPDK 22.x 이후 rte_flow는 하드웨어 기반 Connection Tracking을 지원합니다.
커널의 conntrack처럼 TCP/UDP 연결 상태(NEW, ESTABLISHED, RELATED, INVALID)를 NIC eSwitch 하드웨어가 추적하고,
rte_flow 규칙에서 상태를 기반으로 패킷을 분류/드롭합니다. OVS hardware offload의 stateful firewall 지원에 핵심입니다.
| CT 상태 | 의미 | 전형적 액션 |
|---|---|---|
| NEW | 새 연결의 첫 패킷 (SYN) | CT 테이블에 등록, 정책 검사 후 허용/드롭 |
| ESTABLISHED | 양방향 패킷이 관찰된 연결 | 빠른 경로로 직접 전달 (하드웨어 오프로드) |
| RELATED | 기존 연결과 관련된 패킷 (FTP DATA 등) | 허용 |
| INVALID | 추적할 수 없는 패킷 | 드롭 |
/* CT 오프로드: ESTABLISHED 연결은 하드웨어에서 직접 전달 */
struct rte_flow_item_conntrack ct_item = {
.flags = RTE_FLOW_CONNTRACK_FLAG_ESTABLISHED,
};
struct rte_flow_item ct_pattern[] = {
{ .type = RTE_FLOW_ITEM_TYPE_ETH },
{ .type = RTE_FLOW_ITEM_TYPE_IPV4 },
{ .type = RTE_FLOW_ITEM_TYPE_TCP },
{ .type = RTE_FLOW_ITEM_TYPE_CONNTRACK, .spec = &ct_item },
{ .type = RTE_FLOW_ITEM_TYPE_END },
};
/* ESTABLISHED → representor로 직접 전달 (CPU 바이패스) */
struct rte_flow_action ct_actions[] = {
{ .type = RTE_FLOW_ACTION_TYPE_PORT_ID, .conf = &repr_port },
{ .type = RTE_FLOW_ACTION_TYPE_END },
};
tc-ct 오프로드를 통해 커널 conntrack 하드웨어 오프로드를 지원하고,
DPDK 측에서는 rte_flow CT 액션이 같은 NIC eSwitch 하드웨어를 사용합니다. 두 경로 모두 SmartNIC(mlx5, bnxt 등)의
eSwitch CT 테이블을 활용하므로, NIC 펌웨어의 CT 테이블 크기가 실질적 한계입니다.
Flow isolated mode
isolated mode는 "명시적으로 설치한 rte_flow 규칙만 포트에 들어오게 하겠다"는 선언입니다. 이 모드를 켜면 PMD의 기본 RSS/기본 큐 처리 경로를 차단하고, 포트 전체를 flow rule 기반 제어면으로 바꾸게 됩니다. 하드웨어 오프로드 어플라이언스나 엄격한 서비스 체인에서 특히 유용합니다.
testpmd> flow isolate 0 true
testpmd> flow create 0 ingress pattern eth / ipv4 / tcp / end \
actions queue index 0 / end
testpmd> flow list 0
| 장점 | 주의점 |
|---|---|
| 기본 포트 동작을 제거해 "무엇이 패킷을 받는지"를 명시적으로 통제 | 드라이버별로 되돌림 가능 여부와 지원 범위가 다릅니다. |
| 오프로드 디버깅 시 소프트웨어 fallback을 숨겨 문제를 더 빨리 드러냄 | rule 누락 시 패킷이 조용히 사라질 수 있으므로 초기 bring-up 때는 신중해야 합니다. |
| 보안 관점에서 default queue를 닫고 allowlist 규칙만 남기기 쉬움 | 테스트 도구 없는 운영 전환은 위험합니다. |
Hairpin queue와 intra-device 포워딩
hairpin queue는 패킷을 호스트 메모리까지 올리지 않고 NIC 내부에서 다른 RX/TX 큐나 포트로 되돌리는 기능입니다. OVS hardware offload, representor 기반 서비스 체인, 고속 TAP 없는 VM 브리징, 장치 내부 loopback 테스트에서 중요합니다. 최신 testpmd 문서도 hairpin 관련 명령과 포트 프록시 조회 기능을 별도로 다룹니다.
/* Hairpin capability 조회 후 큐 구성하는 패턴 */
struct rte_eth_hairpin_cap cap;
rte_eth_dev_hairpin_capability_get(port_id, &cap);
struct rte_eth_hairpin_conf hp = {
.peer_count = 1,
.manual_bind = 1,
.tx_explicit = 1,
};
rte_eth_rx_hairpin_queue_setup(port_id, rxq_id, 1024, &hp);
rte_eth_tx_hairpin_queue_setup(port_id, txq_id, 1024, &hp);
| 적합한 경우 | 이유 |
|---|---|
| Representor 기반 서비스 체인 | VM/VF/SF 사이 트래픽을 NIC 내부에서 우회시켜 CPU 부하를 줄입니다. |
| Port-to-port 브리지 | 동일 디바이스 안의 포트를 초저지연으로 연결할 수 있습니다. |
| 하드웨어 오프로드 검증 | flow rule이 실제 eSwitch 경로를 타는지 빠르게 확인할 수 있습니다. |
Virtio/Vhost-user 아키텍처
DPDK 환경에서 VM과의 패킷 교환은 vhost-user 프로토콜을 통해 이루어집니다. QEMU 게스트의 virtio-net 프론트엔드와 호스트의 DPDK vhost 백엔드가 공유 메모리(hugepage) 위의 virtqueue를 통해 제로카피로 패킷을 전달합니다. 이 구조를 이해해야 OVS-DPDK, Snabbswitch, VPP 등 가상 스위칭의 성능 특성을 설명할 수 있습니다.
| 항목 | vhost-user | vhost-net (커널) | SR-IOV 패스스루 |
|---|---|---|---|
| 호스트 백엔드 | DPDK 유저 공간 | 커널 vhost 모듈 | VF 직접 할당 |
| 스위칭 | OVS-DPDK / VPP | 커널 브릿지 / OVS | NIC eSwitch |
| 성능 (64B) | ~8 Mpps/core | ~2 Mpps/core | ~14 Mpps (wire) |
| 유연성 | 높음 (소프트웨어 스위칭) | 중간 | 낮음 (고정 기능) |
| 라이브 마이그레이션 | 지원 (dirty page tracking) | 지원 | 제한적 |
| 멀티큐 | MQ virtio 지원 | 지원 | VF 큐 수에 의존 |
| 제어 채널 | UNIX 소켓 | ioctl | 없음 (직접 VF) |
# ━━━ QEMU vhost-user 설정 ━━━
# 1. vhost-user 소켓 경로 (OVS-DPDK가 생성)
# /tmp/vhost-user0
# 2. QEMU 실행 (vhost-user 백엔드 연결)
qemu-system-x86_64 \
-m 4G -smp 4 \
-object memory-backend-file,id=mem,size=4G,mem-path=/dev/hugepages,share=on \
-numa node,memdev=mem \
-chardev socket,id=char0,path=/tmp/vhost-user0,server=off \
-netdev vhost-user,id=net0,chardev=char0,vhostforce=on,queues=4 \
-device virtio-net-pci,netdev=net0,mq=on,vectors=10 \
...
# 핵심 옵션 설명:
# share=on — hugepage를 호스트와 게스트가 공유 (필수)
# vhostforce=on — vhost-user 강제 사용
# queues=4 — 멀티큐 virtio (vCPU 수에 맞춤)
# vectors=10 — MSI-X 벡터 수 (2*queues + 2)
OVS-DPDK (Open vSwitch + DPDK)
OVS-DPDK는 Open vSwitch의 데이터플레인을 DPDK로 교체하여 가상 스위칭 성능을 극대화한 구성입니다. 클라우드/NFV 환경에서 VM/컨테이너 간 네트워크 트래픽을 유저 공간에서 처리하여, 기존 커널 OVS 대비 5~10배의 처리량을 달성합니다.
DPDK 25.11.0 지원을 명시합니다.
OVS-DPDK는 커널 모듈보다 OVS 사용자 공간 바이너리와 DPDK ABI 조합에 더 민감하므로, 배포판 기본 패키지와 자체 빌드를 섞기 전에 지원 조합을 먼저 고정해야 합니다.
# ━━━ OVS-DPDK 설정 예시 ━━━
# 1. OVS에 DPDK 초기화 파라미터 설정
ovs-vsctl --no-wait set Open_vSwitch . \
other_config:dpdk-init=true \
other_config:dpdk-socket-mem="1024,1024" \
other_config:dpdk-lcore-mask=0x3 \
other_config:dpdk-hugepage-dir="/dev/hugepages"
# 2. OVS 데몬 재시작
systemctl restart openvswitch-switch
# 3. DPDK 브릿지 생성
ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev
# 4. DPDK 물리 포트 추가 (VFIO 바인딩된 NIC)
ovs-vsctl add-port br0 dpdk-p0 -- set Interface dpdk-p0 \
type=dpdk options:dpdk-devargs=0000:03:00.0
# 5. vhost-user 포트 추가 (VM 연결용)
ovs-vsctl add-port br0 vhost-user0 -- set Interface vhost-user0 \
type=dpdkvhostuserclient \
options:vhost-server-path="/tmp/vhost-user0"
# 6. PMD 스레드 CPU 할당 (NUMA 인식)
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=0x3C
# 7. 플로우 설정 (OpenFlow)
ovs-ofctl add-flow br0 "in_port=dpdk-p0,actions=output:vhost-user0"
ovs-ofctl add-flow br0 "in_port=vhost-user0,actions=output:dpdk-p0"
# ━━━ 성능 확인 ━━━
ovs-appctl dpif-netdev/pmd-stats-show # PMD 스레드 통계
ovs-appctl dpif-netdev/pmd-rxq-show # RX 큐 매핑
ovs-appctl dpctl/dump-flows # 데이터패스 플로우
ovs-appctl coverage/show # 내부 이벤트 카운터
DPDK 성능 튜닝
| 튜닝 항목 | 설정 | 효과 |
|---|---|---|
| CPU 격리 | isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15 |
DPDK 코어에서 커널 스케줄러/RCU/타이머 인터럽트 제거. jitter 최소화 |
| 1GB Hugepage | hugepagesz=1G hugepages=8 |
2MB 대비 TLB 미스 99% 감소. IOTLB 효율도 향상 |
| NUMA 정렬 | NIC과 같은 NUMA 노드의 코어/메모리 사용 | 크로스-NUMA 접근 시 40~100ns 추가 지연 방지 |
| RX/TX burst 크기 | 32~64 (2의 거듭제곱) | 벡터화 PMD 경로 활성화. prefetch 효율 극대화 |
| Descriptor ring 크기 | 2048~4096 | 마이크로버스트 흡수. 너무 크면 캐시 효율 저하 |
| Mempool 캐시 | per-core 캐시 256~512 | 공용 Ring 접근 빈도 감소. 캐시 > burst_size × 1.5 권장 |
| 하이퍼스레딩 | 비활성화 또는 sibling 회피 | HT sibling이 같은 L1/L2 캐시를 공유하여 캐시 오염 |
| 전력 관리 | intel_pstate=disable processor.max_cstate=1 |
C-state 전환 지연 제거 (C6→C0 복귀에 ~100μs) |
| IRQ 밸런싱 | irqbalance 중지, DPDK 코어에서 IRQ 배제 |
폴링 코어에 인터럽트 간섭 제거 |
| PCIe MMIO 최적화 | Write-Combining 활성화 (RTE_ETH_TX_OFFLOAD_WC) |
TX tail 레지스터 쓰기를 배치하여 PCIe 트랜잭션 감소 |
# ━━━ 시스템 레벨 DPDK 성능 튜닝 ━━━
# CPU 격리 (GRUB 커맨드라인)
# GRUB_CMDLINE_LINUX="isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15 \
# intel_pstate=disable processor.max_cstate=1 intel_idle.max_cstate=0 \
# default_hugepagesz=1G hugepagesz=1G hugepages=8 \
# iommu=pt intel_iommu=on"
# NUMA 토폴로지 확인 — NIC이 어느 NUMA 노드에 있는지
cat /sys/bus/pci/devices/0000:03:00.0/numa_node
# 0 → NUMA 노드 0의 코어와 메모리를 사용해야 함
# 해당 NUMA 노드의 코어 확인
lscpu | grep "NUMA node0"
# NUMA node0 CPU(s): 0-7,16-23
# irqbalance 비활성화 + DPDK 코어에서 IRQ 배제
systemctl stop irqbalance
# /proc/irq/default_smp_affinity에서 DPDK 코어 제외
echo 000F > /proc/irq/default_smp_affinity # 코어 0-3만 IRQ 허용
# CPU 주파수 고정 (Turbo Boost는 유지, P-state 전환 최소화)
cpupower -c 4-15 frequency-set -g performance
# 코어별 C-state 확인
turbostat --quiet --show Core,CPU,Busy%,Bzy_MHz,IRQ,C1%,C6% sleep 1
QoS/Traffic Management (rte_sched)
rte_sched는 DPDK의 계층적 QoS 스케줄러로, 통신사 NFV와 엔터프라이즈 네트워크에서
가입자/서비스별 대역폭 보장과 트래픽 셰이핑을 유저 공간에서 구현합니다.
커널의 tc/HTB/TBF에 대응하지만, DPDK 데이터 경로 내에서
패킷 단위로 동작하므로 수십 Mpps 수준의 처리량에서도 정밀한 QoS를 유지할 수 있습니다.
| 계층 | 스케줄링 알고리즘 | 설정 예 |
|---|---|---|
| Port | Token Bucket (전체 출력 rate) | 10 Gbps line rate |
| Subport | Token Bucket + TC간 WFQ | 고객 A: 2 Gbps 보장 |
| Pipe | Token Bucket (가입자 셰이핑) | 가입자당 100 Mbps |
| TC | Strict Priority (높은 TC 우선) | TC0=BE, TC1=AF, TC2=EF(VoIP) |
| Queue | WRR (가중치 라운드 로빈) | Q0: weight=4, Q1: weight=2 |
/* rte_sched QoS 스케줄러 설정 (간략화) */
struct rte_sched_subport_params subport_params = {
.tb_rate = 1250000000, /* 10 Gbps in bytes/s */
.tb_size = 1000000, /* 토큰 버킷 크기 */
.tc_rate = {
[0] = 500000000, /* TC0: 4 Gbps (Best Effort) */
[1] = 375000000, /* TC1: 3 Gbps (Assured Forwarding) */
[2] = 250000000, /* TC2: 2 Gbps (Expedited Forwarding) */
[3] = 125000000, /* TC3: 1 Gbps */
},
.tc_period = 10, /* TC 갱신 주기 (ms) */
};
struct rte_sched_pipe_params pipe_profiles[] = {
{ /* 프로필 0: 일반 가입자 100 Mbps */
.tb_rate = 12500000, /* 100 Mbps */
.tb_size = 100000,
.tc_rate = { 6250000, 3125000, 3125000, 0 },
.tc_period = 40,
.wrr_weights = { 1, 1, 1, 1 },
},
};
/* 스케줄러 생성 및 패킷 큐잉/디큐잉 */
struct rte_sched_port *sched = rte_sched_port_config(&port_params);
rte_sched_subport_config(sched, 0, &subport_params, 0);
rte_sched_pipe_config(sched, 0, pipe_id, 0);
/* 데이터 경로에서: classify → enqueue → dequeue */
rte_sched_port_pkt_write(sched, pkts, nb_pkts, subport, pipe, tc, queue);
uint16_t nb_enq = rte_sched_port_enqueue(sched, pkts, nb_pkts);
uint16_t nb_deq = rte_sched_port_dequeue(sched, out_pkts, 32);
rte_eth_tx_burst(port_id, 0, out_pkts, nb_deq);
tc/HTB는 커널 공간에서 sk_buff를 다루고 Qdisc 프레임워크를 사용합니다.
DPDK rte_sched는 유저 공간에서 rte_mbuf를 직접 큐잉하므로 컨텍스트 스위칭 없이 동작하지만,
커널의 nftables/conntrack 기반 QoS 정책과는 통합되지 않습니다.
Eventdev — 이벤트 기반 패킷 스케줄링
rte_eventdev는 DPDK의 이벤트 기반 프로그래밍 모델로,
하드웨어 이벤트 스케줄러(Intel DLB2 등)나 소프트웨어 구현을 통해
패킷을 워커 코어에 동적으로 분배합니다. Run-to-Completion과 Pipeline 모델의 장점을 결합합니다.
Eventdev 동작 모델 요약
- RX Adapter가 이벤트를 생성해 Scheduler Queue(Atomic/Ordered/Parallel)에 분배
- Worker(Core 4~6)가 큐에서 이벤트를 처리
- TX Adapter가 처리 완료 이벤트를 송신 큐로 전달
큐 타입:
- Atomic: 같은 플로우는 동일 워커로 전달 (순서 보장)
- Ordered: 병렬 처리 후 출력 시 순서 재정렬
- Parallel: 순서 무관, 최대 처리량
장점:
- 트래픽 부하에 따라 워커 수를 유연하게 조정
- Atomic 큐로 플로우별 락 없는 처리 보장
- 하드웨어 스케줄러(DLB2)는 CPU 오버헤드 없이 이벤트 분배
Cryptodev — 암호화 가속 프레임워크
rte_cryptodev는 DPDK의 암호화 추상화 계층으로, 하드웨어 가속기(Intel QAT, Marvell OCTEON, ARM CE)와
소프트웨어 구현(AESNI, OpenSSL, ARMv8 CE)을 동일한 API로 접근합니다.
IPSec VPN, TLS 프록시, MACsec, 디스크 암호화 가속 등에서 DPDK 데이터 경로와 통합되어
패킷 단위 암호 연산을 인라인 또는 lookaside 방식으로 처리합니다.
/* Cryptodev 세션 생성 및 암호화 요청 (간략화) */
/* 1. Crypto 디바이스 설정 */
struct rte_cryptodev_config conf = {
.nb_queue_pairs = 1,
.socket_id = rte_socket_id(),
};
rte_cryptodev_configure(cdev_id, &conf);
/* 2. 세션 풀 생성 */
struct rte_mempool *sess_pool = rte_cryptodev_sym_session_pool_create(
"SESS_POOL", 256, 0, 0, 0, rte_socket_id());
/* 3. 대칭 암호화 세션: AES-128-CBC + HMAC-SHA256 */
struct rte_crypto_sym_xform cipher_xform = {
.type = RTE_CRYPTO_SYM_XFORM_CIPHER,
.cipher = {
.op = RTE_CRYPTO_CIPHER_OP_ENCRYPT,
.algo = RTE_CRYPTO_CIPHER_AES_CBC,
.key = { .data = key, .length = 16 },
.iv = { .offset = IV_OFFSET, .length = 16 },
},
.next = &auth_xform, /* 인증 체이닝 */
};
void *session = rte_cryptodev_sym_session_create(cdev_id,
&cipher_xform, sess_pool);
/* 4. 암호화 요청 (enqueue/dequeue) */
struct rte_crypto_op *ops[32];
rte_crypto_op_bulk_alloc(op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC,
ops, 32);
for (int i = 0; i < 32; i++) {
ops[i]->sym->m_src = pkts[i]; /* mbuf 연결 */
rte_crypto_op_attach_sym_session(ops[i], session);
}
uint16_t enq = rte_cryptodev_enqueue_burst(cdev_id, 0, ops, 32);
/* ... 다른 작업 수행 ... */
uint16_t deq = rte_cryptodev_dequeue_burst(cdev_id, 0, ops, 32);
| Crypto PMD | 백엔드 | 특징 |
|---|---|---|
| QAT | Intel QuickAssist | 하드웨어 가속, 대칭/비대칭/해시, 압축도 지원 |
| AESNI-MB | Intel AES-NI 명령어 | 소프트웨어, x86 AES-NI/AVX2/AVX-512 활용 |
| OpenSSL | libcrypto | 소프트웨어, 가장 넓은 알고리즘 지원 |
| ARMv8 CE | ARM Crypto Extension | ARM 서버 네이티브 가속 |
| Scheduler | 멀티 PMD 번들 | 여러 crypto PMD를 묶어 부하 분산 |
| Null | 무연산 | 성능 베이스라인 측정용 |
| 모델 | 작동 방식 | 적합한 경우 |
|---|---|---|
| Lookaside Protocol | 전체 프로토콜(ESP 등)을 crypto 장치가 처리 | IPSec 게이트웨이, 하드웨어가 프로토콜 인식 |
| Lookaside None | 앱이 프로토콜 처리, crypto 장치는 알고리즘만 | 커스텀 프로토콜, 세밀한 제어 필요 시 |
| Inline Crypto | NIC RX/TX 경로에서 하드웨어 암복호화 | 와이어 스피드 IPSec, MACsec |
| Inline Protocol | NIC이 전체 IPSec/MACsec 프로토콜 처리 | 최저 지연 IPSec 어플라이언스 |
examples/ipsec-secgw는 DPDK 기반 IPSec 게이트웨이 레퍼런스 구현으로,
Lookaside/Inline 모델을 모두 지원합니다. VPP의 ipsec 노드와 함께 프로덕션 IPSec 구현의 두 가지 주요 선택지입니다.
DPDK 멀티프로세스 모드
DPDK는 primary-secondary 프로세스 모델을 지원합니다. Primary 프로세스가 EAL 초기화, 포트 설정, Mempool 생성을 수행하고, Secondary 프로세스가 공유 메모리(hugepage)를 통해 동일한 자원에 접근합니다.
# 멀티프로세스 실행 예시
# Primary 프로세스 시작
dpdk-l2fwd -l 0-3 -n 4 --proc-type primary -- -p 0x3
# Secondary 프로세스 (패킷 덤프)
dpdk-pdump -l 4 --proc-type secondary -- \
--pdump 'port=0,queue=*,rx-dev=/tmp/rx.pcap'
# Secondary 프로세스 (통계 모니터링)
dpdk-proc-info --proc-type secondary -- --stats
컨테이너·Kubernetes 통합
DPDK를 컨테이너화하면 배포·확장·롤백이 용이하지만, hugepage 마운트, VFIO 디바이스 노출, CPU 격리, NUMA 인식 스케줄링 등 추가 설정이 필요합니다. Kubernetes 환경에서는 SR-IOV Device Plugin과 Multus CNI가 DPDK 워크로드에 VF를 동적 할당하는 표준 패턴입니다.
# Kubernetes DPDK Pod 스펙 예시
apiVersion: v1
kind: Pod
metadata:
name: dpdk-testpmd
annotations:
k8s.v1.cni.cncf.io/networks: sriov-dpdk-net
spec:
containers:
- name: testpmd
image: dpdk-app:24.11
command: ["dpdk-testpmd"]
args:
- "-l"
- "2-5"
- "-n"
- "4"
- "--iova-mode=va"
- "--in-memory"
- "--"
- "-i"
- "--rxq=2"
- "--txq=2"
resources:
requests:
cpu: "4"
memory: "4Gi"
hugepages-1Gi: "2Gi"
intel.com/sriov_dpdk: "1"
limits:
cpu: "4"
memory: "4Gi"
hugepages-1Gi: "2Gi"
intel.com/sriov_dpdk: "1"
securityContext:
capabilities:
add: ["IPC_LOCK", "SYS_RAWIO"]
volumeMounts:
- name: hugepages
mountPath: /dev/hugepages
volumes:
- name: hugepages
emptyDir:
medium: HugePages-1Gi
| 컨테이너 환경 항목 | 설정 | 이유 |
|---|---|---|
| Hugepage | emptyDir.medium: HugePages-1Gi | DPDK는 hugetlbfs 필수, Pod 내 /dev/hugepages에 마운트 |
| CPU Manager | cpuManagerPolicy: static | Guaranteed QoS Pod에 전용 코어 할당 (busy-poll 필수) |
| Topology Manager | single-numa-node | NIC VF, CPU, 메모리를 같은 NUMA 노드에 배치 |
| SR-IOV Device Plugin | ConfigMap으로 VF 풀 정의 | VF를 VFIO 바인딩하고 리소스로 어드버타이징 |
| Multus CNI | NetworkAttachmentDefinition | 기본 CNI(Calico 등) + DPDK VF 다중 네트워크 |
| securityContext | IPC_LOCK, SYS_RAWIO | hugepage mlock과 VFIO 접근에 필요 (privileged 불필요) |
| Docker 직접 실행 | --device /dev/vfio/N -v /dev/hugepages | K8s 없는 Docker 환경 최소 설정 |
# ━━━ Docker 직접 실행 예시 ━━━
# SR-IOV VF 생성
echo 4 > /sys/class/net/ens3f0/device/sriov_numvfs
# VF를 vfio-pci에 바인딩
dpdk-devbind --bind=vfio-pci 0000:03:02.0
# VFIO 그룹 번호 확인
ls -la /sys/bus/pci/devices/0000:03:02.0/iommu_group
# → /sys/kernel/iommu_groups/42
# Docker 실행 (privileged 없이!)
docker run --rm -it \
--device /dev/vfio/42 \
--device /dev/vfio/vfio \
-v /dev/hugepages:/dev/hugepages \
--ulimit memlock=-1:-1 \
--cpuset-cpus=4-7 \
dpdk-app:24.11 \
dpdk-testpmd -l 4-7 -n 4 -a 0000:03:02.0 -- -i
--privileged플래그는 가능한 사용하지 말고, 필요한 capability만 부여- 멀티프로세스 모드(
--proc-type secondary)는 같은 hugepage 네임스페이스를 공유해야 하므로 컨테이너 간 사용이 복잡 --in-memory옵션은 hugetlbfs 파일을 만들지 않아 단일 프로세스 컨테이너에 적합- NUMA 토폴로지가 호스트와 일치하는지 반드시 확인 (VM 위 컨테이너는 가짜 NUMA일 수 있음)
DPDK 디버깅과 모니터링
| 도구 | 용도 | 사용법 |
|---|---|---|
dpdk-proc-info |
포트 통계, 큐 통계, 메모리 사용량 | dpdk-proc-info -- --stats --xstats |
dpdk-pdump |
패킷 캡처 (pcap 형식) | dpdk-pdump -- --pdump 'port=0,...' |
dpdk-dumpcap |
Wireshark 친화적 캡처 (pcapng) | dpdk-dumpcap -i <iface> -w trace.pcapng |
dpdk-telemetry.py |
JSON 기반 텔레메트리 API | UNIX 소켓으로 런타임 통계 조회 |
dpdk-devbind |
PCI 디바이스 바인딩 관리 | dpdk-devbind --status |
dpdk-testpmd |
포트 기능 테스트, 성능 벤치마크 | dpdk-testpmd -- -i --forward-mode=io |
rte_eth_stats_get() |
프로그래밍 방식 통계 수집 | RX/TX 패킷/바이트/에러 카운터 |
# ━━━ DPDK 디버깅/모니터링 명령 모음 ━━━
# 포트 통계 (기본 + 확장)
dpdk-proc-info -- --stats
dpdk-proc-info -- --xstats # NIC별 상세 카운터 (rx_good_bytes, tx_errors, ...)
# 실시간 패킷 캡처 (secondary 프로세스)
dpdk-pdump -l 8 --proc-type secondary -- \
--pdump 'port=0,queue=*,rx-dev=/tmp/capture.pcap'
# 캡처 파일을 tcpdump/Wireshark로 분석
tcpdump -r /tmp/capture.pcap
# dumpcap 스타일 캡처 (pcapng)
dpdk-dumpcap --list-interfaces
dpdk-dumpcap -i "0000:03:00.0" -c 1000 -w /tmp/trace.pcapng
# 텔레메트리 API (DPDK 20.05+)
# UNIX 소켓 경로: /var/run/dpdk/rte/dpdk_telemetry.vX
dpdk-telemetry.py
# 대화형 쿼리:
# --> /ethdev/stats,0
# --> /ethdev/xstats,0
# --> /eal/params
# --> /mempool/list
# testpmd로 성능 측정
dpdk-testpmd -l 0-3 -n 4 -a 0000:03:00.0 -- \
-i \
--forward-mode=io \
--rxq=4 --txq=4 \
--nb-cores=3 \
--burst=32
# testpmd 내부 명령:
# testpmd> start
# testpmd> show port stats all
# testpmd> show fwd stats all
# testpmd> show port xstats 0
# testpmd> stop
# Mempool 상태 확인
dpdk-proc-info -- --mempool=MBUF_POOL
# EAL 로그 레벨 (런타임 조정)
# --log-level=lib.eal:8 (DEBUG)
# --log-level=pmd.net.mlx5:7 (INFO)
/* 프로그래밍 방식 통계 수집 */
struct rte_eth_stats stats;
rte_eth_stats_get(port_id, &stats);
printf("Port %u: RX %lu pkts (%lu bytes) TX %lu pkts (%lu bytes)\\n",
port_id,
stats.ipackets, stats.ibytes,
stats.opackets, stats.obytes);
printf(" RX errors: %lu TX errors: %lu RX no-mbuf: %lu\\n",
stats.ierrors, stats.oerrors, stats.rx_nombuf);
/* 확장 통계 (xstats): NIC별 상세 카운터 */
int len = rte_eth_xstats_get(port_id, NULL, 0);
struct rte_eth_xstat *xstats = malloc(len * sizeof(*xstats));
struct rte_eth_xstat_name *names = malloc(len * sizeof(*names));
rte_eth_xstats_get(port_id, xstats, len);
rte_eth_xstats_get_names(port_id, names, len);
for (int i = 0; i < len; i++)
printf(" %s: %lu\\n", names[i].name, xstats[i].value);
추가 런타임 서비스: DMA, 전력, 메트릭, 추적
DPDK는 ethdev와 mempool만으로 끝나지 않습니다. 최신 Programmer's Guide는 DMA 엔진 추상화(dmadev), 유휴 폴링 완화용 전력 관리, 메트릭 레지스트리, 저오버헤드 trace 라이브러리를 별도 서브시스템으로 제공합니다. 패킷 처리 코드가 어느 정도 안정되면, 실제 운영 품질은 이 주변 서비스에서 갈리는 경우가 많습니다.
dmadev — 복사/메모리 이동 오프로딩
rte_dmadev는 패킷 그 자체를 처리하는 장치가 아니라 메모리 이동 작업을 오프로딩하는 계층입니다. 공식 문서는 submission/completion ring 기반 API와 ring-less API를 함께 설명합니다. 패킷 payload 복사, 배치 memcpy, 압축/암호화 전 사전 정렬, 소프트웨어 파이프라인의 copy stage 분리에 유용합니다.
/* dmadev로 메모리 복사 enqueue */
uint16_t vchan = 0;
uint16_t job = rte_dma_copy(dev_id, vchan,
src_iova, dst_iova, copy_len,
0);
rte_dma_submit(dev_id, vchan);
/* 완료 수거 */
uint16_t done = rte_dma_completed(dev_id, vchan,
32, last_idx, NULL, NULL);
| 패턴 | 언제 쓰나 | 주의점 |
|---|---|---|
| ring-based API | 배치 큐잉, 명확한 completion 수거가 필요할 때 | 큐 깊이와 completion polling 지연을 같이 튜닝해야 합니다. |
| ring-less API | 드라이버가 더 직접적인 제출 경로를 제공할 때 | 장치별 지원 범위 차이가 큽니다. |
| vchan | 하나의 DMA 장치를 논리 채널로 분리할 때 | ethdev queue와 일대일 대응을 강제하지 말고 작업 특성에 맞춰 배치해야 합니다. |
유휴 폴링과 전력 관리
DPDK는 본질적으로 busy-poll 프레임워크이지만, 최신 power management 문서는 empty poll 기반 절전, monitor/pause instruction, CPU frequency scaling 연계를 별도로 제공합니다. 즉 "DPDK는 항상 100% CPU를 태운다"는 단순화는 반만 맞습니다. 낮은 부하 구간에서 지연 허용치가 있다면, empty poll 횟수를 기준으로 절전과 지연을 절충할 수 있습니다.
| 전략 | 장점 | 대가 |
|---|---|---|
| 순수 busy-poll | 지연 최저, 가장 단순 | 전력 사용량 최고 |
| empty poll + pause | 낮은 부하에서 전력 절감 | tail latency가 약간 흔들릴 수 있음 |
| frequency scaling 연동 | 장시간 저부하 서비스에서 유리 | 복귀 지연이 workload 민감 |
Metrics와 Trace
Metrics 라이브러리는 이름이 등록된 메트릭 집합에 값을 채워 넣는 모델이고, Trace 라이브러리는 저오버헤드 순환 버퍼에 이벤트를 남긴 뒤 파일로 내보내는 모델입니다. 관측 대상을 "지속적 수치"와 "순간 사건"으로 분리하면 설계가 단순해집니다.
/* Metrics 등록 예시 */
int key = rte_metrics_reg_name("rx_missed_errors");
uint64_t values[1] = { rx_missed };
rte_metrics_update_values(RTE_METRICS_GLOBAL, &key, values, 1);
/* Trace는 데이터 평면 이벤트를 남기고, 필요 시 파일로 덤프 */
rte_trace_point_emit_u16(port_id);
rte_trace_save();
| 도구 | 용도 | 팁 |
|---|---|---|
| Metrics | 지속적 수치, 외부 모니터링 수집 | 포트 xstats와 중복되지 않게 "서비스 관점 지표"를 따로 두면 좋습니다. |
| Trace | 희귀 사건, 상태 전이, 에러 경로 분석 | overwrite/discard 정책을 정하고, 장애 재현 시점에만 활성화하는 편이 안전합니다. |
| Telemetry | 런타임 조회 인터페이스 | Metrics/Trace/ethdev/mempool 상태를 한 소켓으로 묶어 조회할 수 있습니다. |
testpmd 기반 재현 실습
testpmd는 DPDK를 배울 때 가장 중요한 도구입니다. 포트 초기화, 큐 배치, 오프로드 플래그, xstats, flow rule, representor, hairpin을 하나씩 검증할 수 있기 때문입니다. 실무에서는 "내 애플리케이션이 이상하다"라고 보기 전에 먼저 testpmd로 하드웨어와 EAL 조합이 정상인지 확인해야 합니다.
랩 환경 준비
# 1. hugepage 준비
sudo dpdk-hugepages.py -p 1G --setup 2G
dpdk-hugepages.py --show
# 2. 장치 바인딩 상태 확인
dpdk-devbind --status
# 3. VFIO 바인딩
sudo modprobe vfio-pci
sudo ip link set ens3f0 down
sudo dpdk-devbind --bind=vfio-pci 0000:03:00.0
# 4. interactive testpmd 시작
dpdk-testpmd -l 2-5 -n 4 \
--iova-mode=va \
-a 0000:03:00.0 \
-- -i \
--nb-cores=2 \
--rxq=2 --txq=2 \
--burst=32 \
--port-topology=chained
세션 중 확인 명령
testpmd> show port info all
testpmd> show port stats all
testpmd> show rxq info 0 0
testpmd> show txq info 0 0
testpmd> set fwd macswap
testpmd> set burst 64
testpmd> start
testpmd> stop
testpmd> show fwd stats all
testpmd> show port xstats 0
testpmd> clear port stats all
testpmd> dump mempool
testpmd> quit
패킷 캡처와 관찰 포인트
| 관찰 대상 | 확인 도구 | 정상 신호 |
|---|---|---|
| 링크/속도/오프로드 | show port info all | 링크 업, MTU, RSS/offload capability가 기대치와 일치 |
| 큐 배치 | show rxq info, show txq info | NUMA 노드와 큐 수가 워커 수에 맞음 |
| xstats | show port xstats 0 | rx_discards, rx_missed_errors, rx_nombuf가 낮음 |
| 패킷 캡처 | dpdk-dumpcap 또는 dpdk-pdump | 테스트 프레임이 기대한 queue/port로 보임 |
dpdk-dumpcap 제약: 공식 도구 문서는 dpdk-dumpcap이 primary 애플리케이션과 같은 DPDK 버전을 써야 하고,
primary 쪽에서 패킷 캡처 프레임워크가 준비되어 있어야 한다고 설명합니다. 바로 캡처가 되지 않으면 NIC 문제보다 버전/프레임워크 전제부터 확인하세요.
io 모드로 하드웨어 RX/TX만 먼저 확인하고, 그다음 macswap, flow, representor, hairpin처럼 상태 공간을 넓혀 가는 편이 디버깅 비용이 낮습니다.
VPP (FD.io) — 유저 공간 네트워크 스택
VPP(Vector Packet Processing)는 Cisco가 개발한 고성능 유저 공간 네트워크 스택으로, DPDK PMD 위에서 동작하며 L2~L4 스위칭/라우팅, NAT, IPSec, ACL 등 커널 네트워크 스택의 기능을 유저 공간에서 구현합니다.
| 비교 항목 | OVS-DPDK | VPP/FD.io |
|---|---|---|
| 주요 용도 | L2 가상 스위칭 (OpenFlow) | L2~L4 라우팅/NAT/IPSec |
| 패킷 처리 | 플로우 테이블 매칭 | 벡터 그래프 (노드 체인) |
| 처리 모델 | 플로우 캐시 + upcall | 벡터화: 같은 노드를 256패킷 배치로 처리 |
| I-cache 효율 | 보통 (플로우별 분기) | 높음 (동일 코드를 벡터 크기만큼 반복) |
| 기능 | L2 스위칭 특화 | L2~L4, NAT44/NAT64, SRv6, IPSec, MPLS, VXLAN 등 |
| 설정 | OpenFlow / OVN | CLI / API (VPP API, NETCONF/YANG) |
| 성능 (64B) | ~10 Mpps/core | ~15 Mpps/core (벡터화 효과) |
DPDK 관련 커널 소스 구조
DPDK 자체는 유저 공간 라이브러리이지만, 커널 측에서 DPDK 동작을 지원하는 핵심 구성 요소:
| 커널 경로 | 역할 | DPDK 관련성 |
|---|---|---|
drivers/vfio/ | VFIO 프레임워크 | PCIe 디바이스를 유저 공간에 안전하게 노출 |
drivers/uio/ | UIO 프레임워크 | 레거시 디바이스 접근 (IOMMU 없는 환경) |
net/xdp/ | AF_XDP 소켓 | 커널 기반 제로카피 패킷 전달 |
mm/hugetlb.c | Hugepage 관리 | DPDK 메모리 관리의 기반 |
drivers/iommu/ | IOMMU (VT-d/AMD-Vi) | VFIO DMA 격리, IOVA 매핑 |
kernel/irq/ | IRQ 관리 | MSI/MSI-X eventfd (VFIO 인터럽트) |
drivers/net/ | NIC 커널 드라이버 | AF_XDP PMD가 커널 드라이버의 XDP 지원에 의존 |
# DPDK 관련 커널 설정 (CONFIG_*)
# VFIO (권장)
CONFIG_VFIO=m
CONFIG_VFIO_PCI=m
CONFIG_VFIO_IOMMU_TYPE1=m
CONFIG_VFIO_NOIOMMU=y # no-IOMMU 모드 (테스트용)
# UIO (레거시)
CONFIG_UIO=m
CONFIG_UIO_PCI_GENERIC=m
# IOMMU
CONFIG_IOMMU_SUPPORT=y
CONFIG_INTEL_IOMMU=y # Intel VT-d
CONFIG_AMD_IOMMU=y # AMD-Vi
CONFIG_IOMMU_DEFAULT_DMA_LAZY=y # IOVA 지연 해제 (성능)
# Hugepage
CONFIG_HUGETLBFS=y
CONFIG_HUGETLB_PAGE=y
CONFIG_TRANSPARENT_HUGEPAGE=y # THP (DPDK는 명시적 hugetlbfs 선호)
# AF_XDP
CONFIG_XDP_SOCKETS=y
CONFIG_BPF_SYSCALL=y
CONFIG_NET_CLS_BPF=m
# NUMA
CONFIG_NUMA=y
CONFIG_NUMA_BALANCING=y
DPDK 빌드 시스템과 버전 변천
DPDK는 22.11 LTS 이후 Meson + Ninja를 유일한 공식 빌드 시스템으로 사용합니다. 이전의 make 기반 빌드는 21.11에서 완전히 제거되었습니다. ABI 안정성 정책은 LTS 릴리스 간에 유지되며, 연 1회 LTS와 분기별 안정 릴리스로 운영됩니다.
# ━━━ DPDK Meson 빌드 (권장) ━━━
# 의존성 설치 (Debian/Ubuntu)
apt install build-essential meson ninja-build python3-pyelftools \
libnuma-dev libpcap-dev pkg-config
# 소스 다운로드 및 빌드
tar xf dpdk-24.11.tar.xz && cd dpdk-24.11
meson setup build
# 또는 옵션 지정:
meson setup build \
-Dplatform=native \
-Dexamples=l2fwd,l3fwd,testpmd \
-Dmax_ethports=32 \
-Ddisable_drivers=net/bnxt,net/enic \
-Ddefault_library=shared
ninja -C build
ninja -C build install
ldconfig
# pkg-config로 DPDK 연동 확인
pkg-config --cflags --libs libdpdk
# ━━━ 애플리케이션 빌드 (Meson 연동) ━━━
# meson.build 예시:
# project('my_app', 'c')
# dpdk_dep = dependency('libdpdk')
# executable('my_app', 'main.c', dependencies: dpdk_dep)
| Meson 옵션 | 기본값 | 설명 |
|---|---|---|
-Dplatform | native | native(현재 CPU 최적화), generic(이식성), x86-64-v3(AVX2) 등 |
-Dmax_ethports | 32 | 최대 이더넷 포트 수 (구조체 크기에 영향) |
-Dmax_lcores | 128 | 최대 논리 코어 수 |
-Ddefault_library | shared | shared/static — 정적 링크는 단일 바이너리 배포에 유리 |
-Ddisable_drivers | 없음 | 불필요 PMD 제외 → 빌드 시간·바이너리 크기 감소 |
-Denable_kmods | false | igb_uio 등 커널 모듈 빌드 (deprecated) |
-Dtests | true | 유닛 테스트 빌드 (CI 환경) |
코어 빌드 옵션 상세
Meson 빌드 시스템에서 -D옵션=값 형태로 지정하는 핵심 빌드 옵션의 전체 목록입니다. meson configure build 명령으로 현재 설정을 확인할 수 있습니다.
| 옵션 | 기본값 | 허용 값 | 설명 |
|---|---|---|---|
-Dplatform | native | native, generic, x86-64-v2~v4 | CPU 최적화 수준. native는 빌드 호스트 CPU에 맞춰 최적화 |
-Dmachine | auto | GCC -march 값 | platform이 native일 때 세부 아키텍처 지정 (예: icelake-server) |
-Dcpu_instruction_set | auto | auto, generic, native | ARM 전용: NEON/SVE/SVE2 명령어 세트 선택 |
-Dbuildtype | release | release, debug, debugoptimized, plain | Meson 내장 옵션. debug는 -O0 -g, release는 -O3 |
-Doptimization | 3 | 0~3, s | buildtype 설정을 재정의하는 세부 최적화 수준 |
-Ddebug | true | true/false | 디버그 심볼 포함 여부 (-g 플래그) |
-Dwerror | false | true/false | 경고를 에러로 처리 (-Werror). CI에서 권장 |
-Ddeveloper_mode | auto | auto, enabled, disabled | Git 트리에서 자동 활성화. 추가 경고·werror·디버그 체크 포함 |
-Dcheck_includes | false | true/false | 각 공개 헤더의 독립 컴파일 가능성 검증 |
-Db_lto | false | true/false | LTO (Link-Time Optimization). 바이너리 크기 감소·성능 향상 |
-Db_pgo | off | off, generate, use | PGO (Profile-Guided Optimization). 2단계 빌드 필요 |
-Dprefix | /usr/local | 경로 | 설치 경로 접두사 |
-Dlibdir | lib | 경로 | 라이브러리 설치 디렉터리 (일부 배포판은 lib64) |
platform이 native 또는 generic이면 DPDK가 자동으로 적절한 -march 값을 선택합니다. machine을 명시하면 platform의 자동 감지를 재정의합니다. 크로스 컴파일 시에는 machine이 반드시 필요합니다.
드라이버·라이브러리 선택 옵션
DPDK는 200개 이상의 PMD(Poll Mode Driver)를 포함하며, 불필요한 드라이버를 제외하면 빌드 시간과 바이너리 크기를 크게 줄일 수 있습니다.
| 옵션 | 기본값 | 설명 |
|---|---|---|
-Ddisable_drivers | 없음 | 제외할 드라이버 (쉼표 구분). 와일드카드 지원: net/i* |
-Denable_drivers | 없음 (전체) | 포함할 드라이버만 지정 (화이트리스트 방식) |
-Ddisable_libs | 없음 | 제외할 라이브러리 (쉼표 구분). 의존성 있는 드라이버도 함께 제외 |
-Denable_kmods | false | igb_uio 등 커널 모듈 빌드. DPDK 20.11+에서 deprecated |
-Dkernel_dir | 자동 감지 | 커널 헤더 경로 (kmod 빌드 또는 KNI에 필요) |
-Ddisable_apps | 없음 | 제외할 애플리케이션 (예: test, proc-info) |
-Denable_apps | 없음 (전체) | 포함할 애플리케이션만 지정 |
# ━━━ 최소 드라이버 빌드 (mlx5 + virtio만) ━━━
meson setup build \
-Denable_drivers=net/mlx5,net/virtio,common/mlx5 \
-Ddisable_libs=pipeline,table,port,fib,rib,reorder \
-Ddisable_apps=test \
-Dplatform=generic \
-Ddefault_library=static \
-Db_lto=true
enable_drivers와 disable_drivers를 동시에 지정하면 enable_drivers가 우선합니다. enable_drivers에 나열된 드라이버만 빌드 후보가 되고, 그중 disable_drivers에 해당하는 것이 제외됩니다. enable_drivers만 사용하는 것이 직관적입니다.
크기 제한 및 리소스 상수
DPDK의 내부 자료 구조 크기를 컴파일 타임에 결정하는 상수입니다. 기본값은 범용 서버를 기준으로 설정되어 있으며, 임베디드 환경이나 대규모 NIC 환경에서는 조정이 필요합니다.
| 옵션 / 매크로 | 기본값 | 영향 |
|---|---|---|
-Dmax_ethports | 32 | 최대 이더넷 포트 수. 포트별 배열 크기 결정 |
-Dmax_lcores | 128 | 최대 논리 코어 수. RTE_MAX_LCORE에 매핑 |
-Dmax_numa_nodes | 4 | 최대 NUMA 노드 수. 메모리 풀 분할에 영향 |
RTE_MAX_ETHPORTS | max_ethports | rte_ethdev 배열 크기. SmartNIC VF가 많으면 증가 필요 |
RTE_MAX_LCORE | max_lcores | per-lcore 변수 배열 크기. 대형 NUMA 서버는 256+ 필요 |
RTE_MAX_MEMSEG_LISTS | 64 | hugepage 메모리 세그먼트 목록 수. 다중 hugepage 크기 시 증가 |
RTE_MAX_MEM_MB | 524288 | 최대 hugepage 메모리 (MB). 512GB 기본, TB급 서버는 증가 필요 |
RTE_MAX_QUEUES_PER_PORT | 1024 | 포트당 최대 큐 수. RSS 해시 분산 범위에 영향 |
RTE_MAX_MEM_MB는 rte_eal_init() 시 실제 hugepage 할당량과는 무관한 상한값입니다.
기능 토글 옵션
빌드에 포함할 기능을 세밀하게 제어하는 옵션입니다.
| 옵션 | 기본값 | 설명 |
|---|---|---|
-Dtests | true | 유닛 테스트 빌드. CI에서는 true, 프로덕션 빌드에서는 false |
-Dexamples | 빈 문자열 | 빌드할 예제 (쉼표 구분). all로 전체 빌드 가능 |
-Denable_apps | 전체 | 빌드할 앱 선택 (test-pmd, proc-info, pdump 등) |
-Denable_docs | false | API 문서(Doxygen) 및 가이드(Sphinx) 빌드 |
-Denable_trace_fp | false | fast-path trace point 활성화. 성능 오버헤드 미미 |
-Dmbuf_refcnt_atomic | true | mbuf 참조 카운트 원자적 연산. 단일 스레드면 false로 성능 향상 |
-Dper_library_versions | true | 라이브러리별 개별 SO 버전 관리. false면 단일 ABI 버전 |
-Denable_driver_sdk | false | 드라이버 개발용 내부 헤더 설치. out-of-tree PMD 개발 시 필요 |
-Dlog_default_level | info | 기본 로그 수준: emergency~debug (8단계) |
-Duse_hpet | false | HPET 타이머 사용. TSC가 정확한 현대 CPU에서는 불필요 |
크로스 컴파일 설정
DPDK는 Meson의 크로스 컴파일 파일(cross-file)을 사용하여 ARM, RISC-V 등 다양한 아키텍처용 빌드를 지원합니다.
| 크로스파일 항목 | 값 예시 | 설명 |
|---|---|---|
[binaries] c | aarch64-linux-gnu-gcc | 크로스 C 컴파일러 경로 |
[binaries] ar | aarch64-linux-gnu-ar | 크로스 아카이버 |
[binaries] strip | aarch64-linux-gnu-strip | 심볼 제거 도구 |
[binaries] pkgconfig | aarch64-linux-gnu-pkg-config | 크로스 pkg-config |
[host_machine] cpu_family | aarch64 | 타겟 CPU 패밀리 |
# ━━━ ARM64 크로스 컴파일 파일 (aarch64_cross.txt) ━━━
[binaries]
c = 'aarch64-linux-gnu-gcc'
cpp = 'aarch64-linux-gnu-g++'
ar = 'aarch64-linux-gnu-ar'
strip = 'aarch64-linux-gnu-strip'
pkgconfig = 'aarch64-linux-gnu-pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'armv8-a'
endian = 'little'
[properties]
platform = 'generic'
cpu_instruction_set = 'generic'
# ━━━ 크로스 컴파일 빌드 명령 ━━━
# ARM64 크로스 컴파일 (BlueField DPU 등)
meson setup build-arm64 \
--cross-file config/arm/arm64_bluefield_platform \
-Dplatform=generic \
-Denable_drivers=net/mlx5,common/mlx5,regex/mlx5,vdpa/mlx5 \
-Ddefault_library=static
ninja -C build-arm64
DESTDIR=/opt/dpdk-arm64 ninja -C build-arm64 install
# RISC-V 크로스 컴파일 (실험적, DPDK 23.11+)
meson setup build-riscv \
--cross-file config/riscv/riscv64_linux_gcc \
-Dplatform=generic
ninja -C build-riscv
apt install libnuma-dev:arm64 libpcap-dev:arm64로 설치합니다. sysroot를 별도로 구성할 경우 [properties] 섹션에 sys_root를 지정합니다.
make → Meson 전환 매핑
DPDK 21.11에서 make 빌드가 제거되었습니다. 기존 CONFIG_RTE_* 환경 변수와 대응하는 Meson 옵션 매핑입니다.
| make (CONFIG_RTE_*) | Meson (-D) | 비고 |
|---|---|---|
CONFIG_RTE_MACHINE | -Dmachine | GCC -march 값 |
CONFIG_RTE_ARCH | 자동 감지 | Meson이 호스트/크로스파일에서 결정 |
CONFIG_RTE_MAX_ETHPORTS | -Dmax_ethports | 동일 기능 |
CONFIG_RTE_MAX_LCORE | -Dmax_lcores | 동일 기능 |
CONFIG_RTE_MAX_NUMA_NODES | -Dmax_numa_nodes | 동일 기능 |
CONFIG_RTE_LIBRTE_PMD_* | -Denable_drivers / -Ddisable_drivers | 개별 옵션 → 통합 리스트 |
CONFIG_RTE_BUILD_SHARED_LIB | -Ddefault_library=shared | Meson 내장 옵션 |
CONFIG_RTE_EAL_IGB_UIO | -Denable_kmods | deprecated |
CONFIG_RTE_LIBRTE_VHOST | 자동 (libc 감지) | vhost-user 빌드 조건 자동 판단 |
CONFIG_RTE_APP_TEST | -Dtests | 테스트 빌드 토글 |
CONFIG_RTE_BUILD_EXAMPLES | -Dexamples | 쉼표 구분 리스트 또는 all |
CONFIG_RTE_EXEC_ENV_LINUX | 자동 감지 | 실행 환경(Linux/FreeBSD) 자동 판단 |
CONFIG_RTE_ENABLE_STDATOMIC | -Denable_stdatomic | DPDK 23.11+: C11 stdatomic 사용 |
CONFIG_RTE_USE_HPET | -Duse_hpet | HPET 타이머 |
T= (target) | --cross-file | 크로스 컴파일 대상 지정 |
O= (output dir) | meson setup <dir> | 빌드 디렉터리 지정 |
EXTRA_CFLAGS | -Dc_args | 추가 C 플래그 (Meson 내장) |
.config 파일의 CONFIG_RTE_* 항목을 수동으로 위 매핑표에 따라 -D 옵션으로 변환해야 합니다. meson_options.txt 파일에서 현재 버전의 모든 옵션을 확인할 수 있습니다.
실전 빌드 레시피
환경별 최적화된 빌드 명령 모음입니다.
# ━━━ 1. 최소 CI 빌드 (빠른 검증) ━━━
meson setup build-ci \
-Dbuildtype=debugoptimized \
-Dwerror=true \
-Dtests=true \
-Dexamples=l2fwd,l3fwd \
-Ddisable_drivers=raw/*,crypto/*,compress/*,regex/*,ml/* \
-Dcheck_includes=true
ninja -C build-ci
meson test -C build-ci --suite fast-tests
# ━━━ 2. 프로덕션 mlx5 빌드 (최대 성능) ━━━
meson setup build-prod \
-Dbuildtype=release \
-Dplatform=native \
-Denable_drivers=net/mlx5,common/mlx5,regex/mlx5 \
-Ddefault_library=static \
-Db_lto=true \
-Dmax_ethports=16 \
-Dmax_lcores=64 \
-Dtests=false \
-Denable_trace_fp=true \
-Dmbuf_refcnt_atomic=false \
-Dc_args='-march=icelake-server -mtune=icelake-server'
ninja -C build-prod
sudo ninja -C build-prod install && sudo ldconfig
# ━━━ 3. 디버그 빌드 (문제 추적) ━━━
meson setup build-debug \
-Dbuildtype=debug \
-Doptimization=0 \
-Ddebug=true \
-Dtests=true \
-Dexamples=all \
-Dlog_default_level=debug \
-Denable_trace_fp=true \
-Dc_args='-fsanitize=address,undefined' \
-Dc_link_args='-fsanitize=address,undefined'
ninja -C build-debug
# ━━━ 4. 문서 빌드 (Doxygen + Sphinx) ━━━
apt install doxygen python3-sphinx python3-sphinx-rtd-theme
meson setup build-doc \
-Denable_docs=true \
-Dtests=false \
-Dexamples=
ninja -C build-doc doc
# ━━━ 5. ARM64 DPU 빌드 (NVIDIA BlueField-3) ━━━
meson setup build-bf3 \
--cross-file config/arm/arm64_bluefield_platform \
-Dplatform=generic \
-Denable_drivers=net/mlx5,common/mlx5,regex/mlx5,vdpa/mlx5,compress/mlx5 \
-Ddefault_library=static \
-Db_lto=true \
-Dmax_ethports=8 \
-Dtests=false \
-Dprefix=/opt/dpdk
ninja -C build-bf3
DESTDIR=/mnt/bf3-rootfs ninja -C build-bf3 install
DPDK 주요 버전 변천
| 버전 | 연도 | 주요 변경 |
|---|---|---|
| 1.x~2.x | 2013~2015 | Intel 초기 릴리스, make 빌드, ixgbe/i40e PMD, 기본 mbuf/ring/mempool |
| 16.04~16.11 | 2016 | 연월 버전 체계 도입, rte_flow API 초안, Eventdev 프레임워크 |
| 17.11 LTS | 2017 | 첫 LTS, Cryptodev 성숙, Flow API 정식화, AF_XDP 실험 |
| 18.11 LTS | 2018 | ABI 안정성 정책 도입, Meson 빌드 추가, mlx5 bifurcated PMD |
| 19.11 LTS | 2019 | AF_XDP PMD 정식, Graph 라이브러리, Telemetry v2, RCU 라이브러리 |
| 20.11 LTS | 2020 | API 대정비 (rte_eth → rte_ethdev), dmadev 초안, IOAT 드라이버 |
| 21.11 LTS | 2021 | make 빌드 제거, Meson 전용, dmadev 정식, GPU 디바이스, Trace 라이브러리 |
| 22.11 LTS | 2022 | ABI 23.x 시리즈, Graph dispatch 모델, flow aging, CT offload 강화 |
| 23.11 LTS | 2023 | vhost DMA offload, rte_flow async, ethdev 텔레메트리 확장 |
| 24.11 LTS | 2024 | ABI 25.x 시리즈, ARM SVE2 최적화, GPUdev DMA, power intrinsics 강화 |
버전별 옵션 변경 이력
DPDK 버전 업그레이드 시 빌드 스크립트를 수정해야 하는 옵션 변경 사항입니다.
| 버전 | 추가된 옵션 | 변경된 옵션 | 제거/Deprecated |
|---|---|---|---|
| 18.11 | Meson 빌드 도입 (make과 병행) | — | — |
| 19.11 | enable_trace_fp | max_lcores 기본값 128→128 유지 | — |
| 20.11 | disable_apps, enable_apps | disable_drivers 와일드카드 지원 | enable_kmods deprecated |
| 21.11 | platform (기존 machine 대체) | machine은 platform 하위로 이동 | make 빌드 완전 제거 |
| 22.11 | enable_driver_sdk, log_default_level | max_ethports 기본값 32 유지 | KNI 라이브러리 deprecated |
| 23.11 | enable_stdatomic, RISC-V 크로스파일 | cpu_instruction_set ARM 확장 | igb_uio 소스 제거 |
| 24.03 | x86-64-v2~v4 platform 값 | developer_mode 동작 세분화 | — |
| 24.11 | ARM SVE2 cpu_instruction_set | max_numa_nodes 기본값 4→4 유지 | use_hpet deprecated 예고 |
diff <(cd dpdk-23.11 && meson configure build) <(cd dpdk-24.11 && meson configure build) 명령 또는 meson_options.txt 파일의 Git diff를 확인합니다. 릴리스 노트의 "Build System" 섹션도 참고하세요.
타 바이패스 프레임워크 비교
DPDK는 유일한 커널 바이패스 기술이 아닙니다. 각 프레임워크는 서로 다른 설계 철학과 트레이드오프를 가집니다.
| 항목 | DPDK | XDP (eBPF) | AF_XDP | Netmap | io_uring (net) |
|---|---|---|---|---|---|
| 실행 위치 | 유저 공간 | 커널 (NIC 드라이버 훅) | 유저 공간 (커널 XDP 경유) | 유저 공간 (커널 모듈) | 커널 (비동기 syscall) |
| NIC 점유 | 전용 (VFIO/UIO) | 공유 (커널 드라이버) | 큐별 분리 가능 | 전용/공유 혼합 | 공유 (커널 스택) |
| 처리량 (64B) | ~14.88 Mpps/core | ~24 Mpps (XDP_TX) | ~10 Mpps/core | ~12 Mpps/core | 아직 미성숙 |
| 지연 | 수백 ns | 수백 ns | ~1 μs | ~1 μs | 수 μs |
| 커널 기능 | 사용 불가 | 모두 가능 | 부분 가능 | 제한적 | 모두 가능 |
| 프로토콜 스택 | 없음 (VPP 등 별도) | 커널 TCP/IP | 없음/커널 혼합 | 없음 | 커널 TCP/IP |
| NIC 지원 범위 | 최대 (100+ PMD) | 드라이버 XDP 지원 필요 | AF_XDP 지원 NIC | 제한적 | 모든 NIC |
| 에코시스템 | OVS, VPP, SPDK, 상업 앱 | Cilium, Katran, bpfilter | DPDK AF_XDP PMD | 학술/소규모 | 범용 서버 |
| 학습 곡선 | 높음 | 중간 (eBPF) | 중간~높음 | 중간 | 낮음 |
| 적합 분야 | NFV, 통신, 전용 어플라이언스 | DDoS 방어, 로드밸런서, 커널 내 필터링 | 커널 기능 + 고성능 혼합 | 연구/교육 | 범용 서버 I/O 최적화 |
- 커널 기능(iptables, tc, routing)이 필요하다면 → XDP 또는 AF_XDP
- NIC을 다른 프로세스와 공유해야 한다면 → AF_XDP 또는 XDP
- 최대 처리량, 최소 지연이 절대 우선이라면 → DPDK
- 이미 VPP/OVS-DPDK 에코시스템을 사용 중이라면 → DPDK 유지
- L7 애플리케이션(웹 서버, DB) 최적화라면 → io_uring
DPDK 구동 시 NVMe 성능 저하 분석
DPDK와 NVMe 드라이버는 직접 코드 경로를 공유하지 않지만, PCIe 버스·CPU 코어·메모리 계층·커널 인프라 수준에서 다수의 간접 간섭 경로가 존재합니다. DPDK 애플리케이션을 기동한 뒤 NVMe SSD의 IOPS·지연이 갑자기 악화되는 현상은 대부분 아래 17가지 원인 중 하나(또는 복합)에 해당합니다.
1. PCIe 버스 대역폭 경합
DPDK 10 GbE 라인레이트(~14.88 Mpps, 64B 프레임)는 PCIe 3.0 x8 대역폭의 상당 부분을 소비합니다. NVMe가 같은 Root Complex 하위 Switch에 연결되면 TLP(Transaction Layer Packet) 수준에서 중재 경합이 발생합니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| 업스트림 대역폭 포화 | NIC과 NVMe가 동일 PCIe Switch 하위 → 업스트림 링크 대역폭을 공유 | NVMe 대역폭 30-50% 하락, avgqu-sz 증가 |
lspci -tv로 토폴로지 확인perf stat -e uncore_iio |
NVMe를 별도 PCIe Switch / CPU 직결 슬롯으로 이동 |
| TLP Credit 고갈 | DPDK PMD가 DMA descriptor를 대량 발급 → Posted/Non-Posted Credit 소진 → NVMe Completion 지연 | NVMe p99 지연 스파이크 (정상 대비 5~10×) | setpci로 Link Status 확인PCIe AER 로그 |
NIC tx_burst 크기 제한, PCIe MPS/MRRS 튜닝 |
| PCIe Ordering Rule | x86 PCIe는 Posted Write 순서 보장 필요 → NIC DMA와 NVMe DMA 간 Ordering Barrier 발생 | 단일 Switch에서만 NVMe 쓰기 지연 증가 | 분리 배치 후 지연 비교 A/B 테스트 | Relaxed Ordering 활성화 (setpci DEVCTL2) |
devargs에 mprq_en=1과 함께 설정하면 PCIe Ordering 부하를 줄일 수 있지만, 일부 플랫폼에서는 데이터 무결성 문제가 보고되었으므로 반드시 검증 후 적용하세요.
2. CPU 코어·IRQ Affinity 충돌
DPDK PMD는 전용 코어에서 100% CPU를 소비하는 폴링 루프를 실행합니다. NVMe 인터럽트(IRQ)가 같은 코어에 할당되면, IRQ 처리가 지연되어 NVMe I/O 완료 레이턴시가 급증합니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| DPDK 코어와 NVMe IRQ 동일 코어 | PMD 폴링 루프가 코어를 100% 점유 → NVMe MSI-X IRQ의 softirq 처리 기아(starvation) | NVMe await 급증, /proc/interrupts에서 특정 코어에 NVMe IRQ 편중 |
cat /proc/interrupts | grep nvmeDPDK --lcores 설정과 비교 |
NVMe IRQ를 DPDK 미사용 코어로 이동: echo <mask> > /proc/irq/<N>/smp_affinity |
| Hyper-Threading 간섭 | DPDK가 물리 코어의 한 HT를 점유하면, 형제 HT에서 NVMe IRQ 처리 성능 40~60% 하락 | HT 형제 코어에서 NVMe 지연 악화, LLC miss 증가 | lscpu -e로 HT 쌍 확인perf stat -C <core> |
HT 형제를 동시에 DPDK에 할당하거나, NVMe IRQ를 물리적으로 다른 코어에 할당 |
| irqbalance 충돌 | irqbalance 데몬이 NVMe IRQ를 DPDK 코어로 재배치 |
운영 중 간헐적 NVMe 지연 스파이크 | systemctl status irqbalanceIRQ affinity 변화 모니터링 |
IRQBALANCE_BANNED_CPUS 환경변수로 DPDK 코어 제외, 또는 irqbalance 중지 후 수동 설정 |
# NVMe IRQ를 DPDK 미사용 코어(예: 코어 8~15)로 제한
for irq in $(grep nvme /proc/interrupts | awk '{print $1}' | tr -d ':'); do
echo ff00 > /proc/irq/$irq/smp_affinity # 코어 8~15 마스크
done
# irqbalance에서 DPDK 코어(0~7) 제외
echo 'IRQBALANCE_BANNED_CPUS="00ff"' >> /etc/default/irqbalance
systemctl restart irqbalance
3. NUMA 토폴로지 불일치
NUMA 시스템에서 NVMe 컨트롤러가 NUMA 노드 0에 연결되었는데, 애플리케이션이 NUMA 노드 1에서 I/O를 발급하면 Cross-NUMA 메모리 접근으로 지연이 30~100% 증가합니다. DPDK가 hugepage를 특정 NUMA 노드에서 대량 할당하면, 반대 노드의 NVMe 드라이버가 사용할 로컬 메모리가 부족해집니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| Cross-NUMA DMA | NVMe 컨트롤러와 NIC이 서로 다른 NUMA 노드 → DMA 완료 데이터가 리모트 메모리 통과 | NVMe 대역폭 저하, numastat에서 other_node 증가 |
cat /sys/block/nvme*/device/numa_nodenumastat -p <pid> |
NIC과 NVMe를 같은 NUMA 노드에 배치, DPDK --socket-mem 조정 |
| Hugepage NUMA 편향 | DPDK가 한 노드에서 hugepage를 대량 소비 → 해당 노드의 NVMe DMA 버퍼 할당 실패/폴백 | cat /sys/devices/system/node/node*/hugepages/*/free_hugepages에서 편향 |
numactl --hardwarehugepage 노드별 잔량 확인 |
노드별 hugepage 균등 할당: echo N > /sys/devices/system/node/nodeX/hugepages/.../nr_hugepages |
4. Hugepage 메모리 경합
DPDK는 기동 시 수 GB~수십 GB의 1G/2M hugepage를 예약합니다. 이로 인해 시스템 전체의 가용 메모리가 감소하여 NVMe 드라이버와 파일시스템에 영향을 줍니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| Page cache 축소 | hugepage 예약이 일반 메모리를 압박 → page cache 크기 감소 → NVMe 버퍼 I/O 캐시 히트율 하락 | free -m에서 buff/cache 급감, NVMe 읽기 IOPS 하락 |
free -m, vmstat 1로 bi/bo 모니터링 |
DPDK hugepage를 필요한 최소량으로 제한, --socket-mem 명시 |
| THP compaction 스톰 | THP(Transparent Hugepage) 활성 상태에서 DPDK hugepage 예약이 메모리 단편화 유발 → compaction이 NVMe I/O 경로에서 stall | kcompactd CPU 사용 급증, NVMe 지연 간헐적 스파이크 |
grep compact /proc/vmstatperf top에서 compact_zone_order |
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled |
| OOM 킬러 간접 효과 | hugepage로 인한 메모리 부족 → OOM 킬러가 NVMe I/O를 발행하는 프로세스 종료 | dmesg에 OOM 메시지, 워크로드 중단 | dmesg | grep -i oom |
시스템 총 메모리 대비 DPDK hugepage 비율 20% 이하 유지 |
5. IOMMU/VFIO 오버헤드
DPDK는 vfio-pci를 통해 NIC에 접근하며, 이때 IOMMU가 DMA 주소 변환을 수행합니다. NVMe도 IOMMU를 통과하므로, IOTLB 경합과 IOMMU 그룹 공유 문제가 발생할 수 있습니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| IOTLB miss/flush | DPDK가 대량의 DMA 매핑을 생성 → IOTLB 엔트리 갱신 빈번 → NVMe DMA의 IOTLB miss 증가 | IOMMU 통과 시 NVMe 지연 증가 (passthrough 대비 5~15%) | perf stat -e dTLB-load-missesdmesg | grep -i iommu |
Intel: intel_iommu=on iommu=pt (pass-through 모드)DPDK: --iova-mode=pa (가능한 경우) |
| IOMMU 그룹 공유 | NIC과 NVMe가 같은 IOMMU 그룹 → VFIO가 그룹 전체를 잠금, NVMe 드라이버 바인딩 충돌 가능 | NVMe 장치 인식 실패 또는 성능 저하 | find /sys/kernel/iommu_groups/ -type l |
ACS(Access Control Services) 지원 스위치 사용, 물리적으로 분리된 슬롯 배치 |
iommu=pt는 DPDK가 VFIO를 사용할 때 다른 장치(NVMe 포함)의 DMA를 pass-through로 처리하여 IOMMU 오버헤드를 제거합니다. 단, DMA 격리가 약화되므로 보안이 중요한 환경에서는 주의가 필요합니다.
6. LLC 캐시 오염 / 메모리 대역폭 포화
DPDK PMD는 대량의 패킷 버퍼를 연속적으로 접근하며 LLC(Last Level Cache)를 오염시킵니다. NVMe 드라이버의 Completion Queue 엔트리와 I/O 메타데이터가 캐시에서 밀려나면 성능이 저하됩니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| LLC 오염 | DPDK mbuf 대량 prefetch → LLC way를 점유 → NVMe CQE·SQE 캐시 miss | NVMe 지연 증가, perf stat에서 LLC-load-misses 증가 |
perf stat -e LLC-load-misses -C <nvme_core>Intel RDT: pqos -m all:0-15 |
Intel CAT(Cache Allocation Technology): DPDK 코어의 LLC way 제한pqos -e "llc:1=0x00f;llc:2=0xff0" |
| 메모리 대역폭 포화 | DPDK DMA + NVMe DMA + CPU 접근이 메모리 채널 대역폭을 초과 | 양쪽 모두 성능 저하, pcm-memory.x에서 채널 사용률 80%+ |
pcm-memory.x 또는 perf stat -e uncore_imc |
Intel MBA(Memory Bandwidth Allocation)로 DPDK 대역폭 상한 설정 |
7. 전원 관리·스케줄러·RCU 간섭
DPDK PMD의 busy-poll 루프는 커널의 전원 관리, 스케줄러, RCU 콜백 처리와 충돌할 수 있습니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| C-state / P-state 전환 | DPDK 미사용 코어가 deep C-state → NVMe IRQ 도착 시 웨이크업 지연 (수십 μs) | NVMe p99 지연에 간헐적 수십 μs 스파이크 | turbostat으로 C-state 분포 확인 |
NVMe 코어에 processor.max_cstate=1 또는cpupower idle-set -D 0 |
| 커널 스케줄러 간섭 | DPDK 코어가 isolcpus로 격리되지 않으면 커널 스레드(kworker 등)가 NVMe 처리를 방해 |
DPDK 코어에서 간헐적 패킷 드롭, NVMe 코어에서 스케줄링 지연 | ps -eo pid,psr,comm | grep kworker |
커널 부트 파라미터: isolcpus=0-7 nohz_full=0-7 rcu_nocbs=0-7 |
| RCU 콜백 지연 | DPDK 코어가 RCU grace period를 block → 커널 메모리 해제 지연 → NVMe 관련 slab 압박 | /sys/kernel/debug/rcu/rcu_preempt/rcudata에서 콜백 누적 |
cat /sys/kernel/debug/rcu/*/rcudata |
rcu_nocbs=<dpdk_cores>로 RCU 콜백을 다른 코어에 위임 |
# DPDK 코어 0~7 격리 + RCU 오프로드 (GRUB 커널 파라미터)
GRUB_CMDLINE_LINUX="isolcpus=0-7 nohz_full=0-7 rcu_nocbs=0-7 \
intel_idle.max_cstate=1 processor.max_cstate=1 \
intel_iommu=on iommu=pt"
8. blk-mq 큐 매핑·MSI-X·Thermal
NVMe 드라이버의 blk-mq 큐 할당, MSI-X 벡터 분배, 열 관리 등도 DPDK와의 상호작용에서 영향을 받습니다.
| 원인 | 메커니즘 | 증상 | 확인 방법 | 해결 |
|---|---|---|---|---|
| blk-mq 큐 매핑 왜곡 | isolcpus로 코어를 격리하면 blk-mq가 NVMe 큐를 남은 코어에만 매핑 → 큐 불균형 |
특정 NVMe 큐에 I/O 집중, /sys/block/nvme0n1/mq/*/cpu_list 편향 |
cat /sys/block/nvme0n1/mq/*/cpu_list |
managed_irq 파라미터 활용, blk_mq_update_nr_hw_queues() 재조정 |
| MSI-X 벡터 경합 | NIC과 NVMe의 MSI-X 벡터가 플랫폼 한계에 도달 → 인터럽트 공유/코얼레싱 | 인터럽트 공유로 NVMe 지연 증가 | cat /proc/interrupts에서 벡터 분포 확인 |
BIOS에서 MSI-X 벡터 풀 확대, 불필요한 장치 비활성화 |
| Thermal throttling | DPDK 코어의 100% 사용률 → CPU 패키지 온도 상승 → Thermal throttling이 NVMe 코어 클럭도 제한 | turbostat에서 PkgWatt 급증, 코어 클럭 하락 |
turbostat --show PkgWatt,CoreTmp |
적절한 냉각, DPDK power_pmd_mgmt API로 적응형 폴링 |
| NVMe poll 모드 경합 | 커널 NVMe 드라이버가 io_poll 모드(io_uring) 사용 시, DPDK PMD와 CPU 사이클 경합 |
양쪽 모두 처리량 하락 | cat /sys/block/nvme*/queue/io_poll |
NVMe poll 모드 비활성화 또는 전용 코어 분리 |
| SPDK와 DPDK 동시 사용 | SPDK(유저 공간 NVMe 드라이버)와 DPDK가 hugepage·코어·VFIO를 공유 → 리소스 경합 | 두 프레임워크 모두 성능 저하 | SPDK + DPDK 동시 기동 여부 확인 | hugepage·코어·IOMMU 그룹을 명시적으로 분리 할당 |
9. 종합 진단 체크리스트와 스크립트
아래 체크리스트를 순서대로 확인하면, DPDK–NVMe 간섭의 대부분을 빠르게 진단할 수 있습니다.
| # | 점검 항목 | 명령 / 확인 방법 | 정상 기준 |
|---|---|---|---|
| 1 | PCIe 토폴로지 분리 | lspci -tv | NIC과 NVMe가 다른 Switch 하위 |
| 2 | NUMA 노드 일치 | cat /sys/class/net/*/device/numa_nodecat /sys/block/nvme*/device/numa_node | NIC과 NVMe가 같은 NUMA 노드 |
| 3 | NVMe IRQ affinity | cat /proc/irq/*/smp_affinity_list (nvme) | DPDK 코어와 겹치지 않음 |
| 4 | HT 형제 분리 | lscpu -e | DPDK 코어의 HT 형제에 NVMe IRQ 없음 |
| 5 | irqbalance 제외 | grep BANNED /etc/default/irqbalance | DPDK 코어가 BANNED 목록에 포함 |
| 6 | Hugepage 잔량 | cat /sys/devices/system/node/node*/hugepages/*/free_hugepages | 각 노드에 충분한 여유 hugepage |
| 7 | 일반 메모리 여유 | free -m | buff/cache가 총 메모리의 20%+ |
| 8 | THP 설정 | cat /sys/kernel/mm/transparent_hugepage/enabled | madvise 또는 never |
| 9 | IOMMU 모드 | dmesg | grep -i iommu | iommu=pt 활성 |
| 10 | IOMMU 그룹 | find /sys/kernel/iommu_groups/ -type l | NIC과 NVMe가 다른 그룹 |
| 11 | LLC miss 비율 | perf stat -e LLC-load-misses -C <nvme_core> | DPDK 기동 전후 차이 <10% |
| 12 | 메모리 대역폭 | pcm-memory.x 또는 perf stat -e uncore_imc | 채널 사용률 <70% |
| 13 | C-state | turbostat --show Avg_MHz,Busy%,Bzy_MHz,PkgWatt | NVMe 코어가 deep C-state에 빠지지 않음 |
| 14 | Thermal | turbostat --show CoreTmp,PkgTmp | 패키지 온도 <85°C |
| 15 | blk-mq 큐 분포 | cat /sys/block/nvme0n1/mq/*/cpu_list | 큐가 NVMe NUMA 코어에 고르게 분포 |
| 16 | RCU 콜백 | cat /sys/kernel/debug/rcu/*/rcudata | DPDK 코어에서 콜백 누적 없음 |
#!/bin/bash
# dpdk-nvme-diag.sh — DPDK/NVMe 간섭 종합 진단 스크립트
set -euo pipefail
echo "=== 1. PCIe 토폴로지 ==="
lspci -tv 2>/dev/null | head -40
echo -e "\n=== 2. NUMA 노드 확인 ==="
for dev in /sys/class/net/*/device/numa_node; do
[ -f "$dev" ] && echo "$(dirname $(dirname $dev) | xargs basename): $(cat $dev)"
done
for dev in /sys/block/nvme*/device/numa_node; do
[ -f "$dev" ] && echo "$(echo $dev | grep -o 'nvme[0-9]*'): $(cat $dev)"
done
echo -e "\n=== 3. NVMe IRQ Affinity ==="
for irq in $(grep nvme /proc/interrupts 2>/dev/null | awk '{print $1}' | tr -d ':'); do
echo "IRQ $irq: $(cat /proc/irq/$irq/smp_affinity_list 2>/dev/null)"
done
echo -e "\n=== 4. Hugepage 상태 (노드별) ==="
for node in /sys/devices/system/node/node*; do
for hp in "$node"/hugepages/hugepages-*; do
[ -d "$hp" ] && echo "$(basename $node) $(basename $hp): \
free=$(cat $hp/free_hugepages)/total=$(cat $hp/nr_hugepages)"
done
done
echo -e "\n=== 5. 메모리 상태 ==="
free -m
echo -e "\n=== 6. THP 설정 ==="
cat /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
echo -e "\n=== 7. IOMMU 모드 ==="
dmesg 2>/dev/null | grep -i "iommu.*mode\|iommu.*pass" | tail -3
echo -e "\n=== 8. IOMMU 그룹 (NIC/NVMe) ==="
for dev in $(lspci -D | grep -iE 'nvme|ethernet' | awk '{print $1}'); do
grp=$(find /sys/kernel/iommu_groups/ -name "$dev" 2>/dev/null | grep -o 'iommu_groups/[0-9]*')
echo "$dev → $grp"
done
echo -e "\n=== 9. C-state (turbostat) ==="
command -v turbostat &>/dev/null && turbostat --show Avg_MHz,Busy%,CoreTmp -n 1 2>/dev/null | head -10 \
|| echo "turbostat not available"
echo -e "\n=== 10. blk-mq 큐 매핑 ==="
for mq in /sys/block/nvme*/mq/*/cpu_list; do
[ -f "$mq" ] && echo "$(echo $mq | grep -o 'nvme[^/]*/mq/[0-9]*'): $(cat $mq)"
done
echo -e "\n=== 진단 완료 ==="
- PCIe 토폴로지 분리 — 하드웨어 수준 경합 제거 (가장 큰 효과)
- NUMA 정렬 — NIC·NVMe·DPDK 코어를 같은 NUMA 노드에 배치
- CPU 코어/IRQ 분리 —
isolcpus+ IRQ affinity 설정 - Hugepage 최적화 — 노드별 균등 할당, 최소량 사용
- IOMMU 튜닝 —
iommu=pt, IOMMU 그룹 분리 - LLC/메모리 대역폭 격리 — Intel CAT/MBA (RDT) 적용
- 전원/스케줄러 튜닝 — C-state 제한,
nohz_full,rcu_nocbs - blk-mq/MSI-X/Thermal — 큐 재매핑, 냉각 확인
운영 하드닝과 트러블슈팅
DPDK 장애는 대개 "패킷 처리 코드"보다 권한, IOMMU 그룹, hugepage 배치, NUMA 불일치, 보안 부팅에서 먼저 발생합니다. 따라서 운영 체크리스트를 코드 리뷰와 별도로 가져가는 편이 안전합니다.
하드닝 체크리스트
| 항목 | 권장 설정 | 이유 |
|---|---|---|
| 장치 접근 | vfio-pci + IOMMU 기본 | DMA 격리와 비특권 실행 경로를 동시에 확보합니다. |
| UIO 사용 범위 | 랩/레거시 전용 | 공식 문서는 UEFI Secure Boot가 UIO 모듈 로드를 막을 수 있다고 경고합니다. |
| 코어 선택 | 코어 0은 OS용으로 남기고 sibling 충돌 회피 | Linux GSG도 코어 0 비사용을 권장합니다. |
| Hugepage 권한 | 전용 디렉터리, 최소 권한, 충분한 memlock | rootless 실행과 실패 원인 분리를 쉽게 합니다. |
| NUMA 정렬 | NIC, 워커 코어, mempool을 같은 노드에 둠 | 크로스소켓 지연과 IOTLB 비용을 줄입니다. |
| 호환성 고정 | OVS/DPDK/펌웨어 조합을 하나의 단위로 관리 | ABI와 PMD capability 차이로 인한 현장 편차를 줄입니다. |
자주 보는 증상과 원인
| 증상 | 원인 | 조치 |
|---|---|---|
cannot open /proc/self/pagemap | PA 모드 전제 또는 물리 주소 접근 권한 부족 | --iova-mode=va로 강제하거나 공식 문서가 요구하는 capability를 부여 |
VFIO group is not viable | IOMMU 그룹 안에 다른 함수/브리지 디바이스가 남아 있음 | 그룹 전체를 확인하고 같은 그룹의 불필요 디바이스를 커널에서 떼거나 ACS 토폴로지를 재검토 |
VFIO_IOMMU_MAP_DMA failed | memlock 부족 또는 dma_entry_limit 한계 | 리소스 한도를 올리고 hugepage 세그먼트 수를 줄이며, 필요 시 커널 dma_entry_limit를 확인 |
rx_nombuf 증가 | mempool 고갈, 잘못된 NUMA 배치, per-core 캐시 과소 | mempool 크기·캐시·NUMA를 다시 맞추고 show port xstats로 추적 |
| AF_XDP PMD가 zero-copy로 붙지 않음 | 드라이버 미지원 또는 XDP/queue 조합 불일치 | 커널 AF_XDP 드라이버 capability와 queue 구성을 먼저 확인 |
dpdk_initialized=false (OVS) | hugepage 디렉터리, socket-mem, VFIO 바인딩, ABI 조합 중 하나가 불일치 | ovs-vsctl get Open_vSwitch . dpdk_initialized, dpdk-devbind --status, OVS 로그를 같이 확인 |
| Secure Boot 환경에서 UIO 모듈 로드 실패 | 서명되지 않은 UIO 계열 모듈이 차단됨 | 가능하면 VFIO로 전환하고, 꼭 필요하면 MOK/모듈 서명 절차를 밟음 |
no-IOMMU 경로를 unsafe로 취급합니다.
테스트 랩에서만 쓰고, 운영 환경에서는 디바이스가 호스트 메모리 전체를 DMA로 건드릴 수 있다는 전제를 항상 기억해야 합니다.
보안 공격 표면과 위협 모델
DPDK는 커널 네트워크 스택을 우회하므로 커널이 제공하는 보안 계층(Netfilter, SELinux, seccomp, 네임스페이스)을 사용할 수 없습니다. 따라서 DPDK 애플리케이션 자체가 신뢰 경계(trust boundary)가 되며, 공격 표면을 명시적으로 인식하고 완화해야 합니다.
| 위협 | 완화 방법 | 운영 체크 |
|---|---|---|
| DMA 공격 | VFIO + IOMMU 필수 사용, UIO/no-IOMMU 금지 | dmesg | grep -i iommu "enabled" 확인 |
| 패킷 파싱 | 모든 수신 패킷 길이/헤더 검증, fuzzing 테스트 | AFL/libFuzzer로 패킷 파서 정기 테스트 |
| Hugepage 노출 | 전용 디렉터리 + 최소 권한, --in-memory 사용 | ls -la /dev/hugepages/ 권한 확인 |
| Telemetry 소켓 | 소켓 파일 권한 제한 (0600), 전용 그룹 | ls -la /var/run/dpdk/ |
| 공유 메모리 | secondary 프로세스를 신뢰된 바이너리로 제한 | hugetlbfs 파일 소유권과 접근 로그 |
| Side-channel | DPDK 코어에서 타 워크로드 격리, CPU 취약점 패치 적용 | lscpu vulnerability 항목 확인 |
| 펌웨어 공급망 | NIC 펌웨어 서명 검증, 공식 소스만 사용 | ethtool -i 펌웨어 버전 추적 |
| DoS | mempool 여유 모니터링, flow rule 수 제한, rate-limit | rx_nombuf xstat 감시 |
| IOMMU 그룹 | ACS 지원 확인, 불필요 디바이스 분리 | ls /sys/kernel/iommu_groups/*/devices/ |
- 1단계 —
dpdk-testpmd로 기본 포트 동작 이해 (io/mac/macswap 모드) - 2단계 —
examples/l2fwd소스 분석 (가장 단순한 DPDK 앱, ~300줄) - 3단계 —
examples/l3fwd분석 (LPM/EM 라우팅, RSS 활용) - 4단계 —
rte_ring,rte_mempool내부 구현 분석 - 5단계 — PMD 소스 분석 (
drivers/net/ixgbe/또는drivers/net/i40e/) - 6단계 — Eventdev, Cryptodev 등 고급 프레임워크
- VFIO 심화 — 가상화 (KVM) — VFIO + DPDK / SPDK
- OVS 연동 — Open vSwitch (OVS) 커널 데이터패스 심화
- AF_XDP / SmartNIC — BPF/XDP — SmartNIC과 DPDK/AF_XDP 연동
- Hugepage 관리 — 메모리 심화
- IOMMU / DMA — DMA 심화
- SmartNIC / DPU — SmartNIC / DPU 기반 네트워크 가속
- Secure Boot — Secure Boot
관련 문서
- Open vSwitch (OVS) 커널 데이터패스 심화 — OVS-DPDK, PMD 스레드, EMC/dpcls, 운영 호환 매트릭스
- VPP (FD.io) 심화 — 고성능 유저스페이스 패킷 처리 — FD.io VPP 벡터 패킷 처리, 그래프 노드 아키텍처, DPDK 통합, 플러그인, 커널
- AF_XDP (XDP Sockets) — Linux 커널 AF_XDP 소켓 — xsk_socket, UMEM, XDP_SHARED_
- NAPI (New API) - 네트워크 패킷 처리 — NAPI 인터럽트 완화 메커니즘 심화: napi_struct, 폴링 버짓, GRO, 멀티큐
- VFIO & mdev (디바이스 패스스루) — IOMMU 그룹, vfio-pci, 패스스루 디버깅
- Secure Boot — VFIO/UIO 모듈 서명, 실험 장비 부팅 정책 점검
- NFQUEUE & DPI 엔진 통합 — nfnetlink_queue 내부 구조, libnetfilter_queue API, Sur
- 네트워크 스택 고급 — NAPI 심화, GRO, RSS/RPS/RFS, XPS, aRFS, 멀티코어 네트워크 분산
- IPSec & xfrm — Linux 커널 xfrm 프레임워크와 IPSec — SA/SP 데이터베이스, ESP/AH/