DPDK
DPDK(Data Plane Development Kit) 고성능 패킷 처리 구조를 심층 분석합니다. EAL 초기화와 hugepage/NUMA 메모리 모델, PMD poll-mode 루프, rte_mbuf/ring/mempool 데이터 경로, VFIO/UIO 장치 바인딩, OVS-DPDK·VPP·AF_XDP 연계 시나리오, 초저지연 환경에서의 코어 배치·버스트·큐 튜닝과 운영 디버깅 포인트까지 실무 중심으로 다룹니다.
핵심 요약
- 패킷 수명주기 — 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를 읽을 때 데이터가 아직 메모리에 쓰여지지 않은 "스pekulatif" 읽기를 방지합니다.| 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 | 이벤트 기반 패킷 분배 |
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 증가) |
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.py --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.py --bind=vfio-pci 0000:03:00.0
# 5. 바인딩 확인
dpdk-devbind.py --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.py --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 영역에 접근 가능 */
패킷 처리 파이프라인 모델
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);
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 드라이버가 자동 감지하여 활성화
OVS-DPDK (Open vSwitch + DPDK)
OVS-DPDK는 Open vSwitch의 데이터플레인을 DPDK로 교체하여 가상 스위칭 성능을 극대화한 구성입니다. 클라우드/NFV 환경에서 VM/컨테이너 간 네트워크 트래픽을 유저 공간에서 처리하여, 기존 커널 OVS 대비 5~10배의 처리량을 달성합니다.
# ━━━ 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
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 오버헤드 없이 이벤트 분배
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
DPDK 디버깅과 모니터링
| 도구 | 용도 | 사용법 |
|---|---|---|
dpdk-proc-info |
포트 통계, 큐 통계, 메모리 사용량 | dpdk-proc-info -- --stats --xstats |
dpdk-pdump |
패킷 캡처 (pcap 형식) | dpdk-pdump -- --pdump 'port=0,...' |
dpdk-telemetry.py |
JSON 기반 텔레메트리 API | UNIX 소켓으로 런타임 통계 조회 |
dpdk-devbind.py |
PCI 디바이스 바인딩 관리 | dpdk-devbind.py --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
# 텔레메트리 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);
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
- 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
- AF_XDP / SmartNIC — BPF/XDP — SmartNIC과 DPDK/AF_XDP 연동
- Hugepage 관리 — 메모리 심화
- IOMMU / DMA — DMA 심화
- SmartNIC / DPU — SmartNIC / DPU 기반 네트워크 가속
관련 문서
- 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, 멀티큐
- NFQUEUE & DPI 엔진 통합 — nfnetlink_queue 내부 구조, libnetfilter_queue API, Sur
- 네트워크 스택 고급 — NAPI 심화, GRO, RSS/RPS/RFS, XPS, aRFS, 멀티코어 네트워크 분산
- IPSec & xfrm — Linux 커널 xfrm 프레임워크와 IPSec — SA/SP 데이터베이스, ESP/AH/