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 관리와 하드닝·트러블슈팅까지 실무 중심으로 다룹니다.

전제 조건: 네트워크 스택네트워크 디바이스 드라이버 문서를 먼저 읽으세요. 고성능 패킷 경로는 큐 구조, 메모리 배치, 드롭 위치가 성능을 좌우하므로 드라이버 경계를 먼저 이해하는 것이 중요합니다.
일상 비유: 이 주제는 고속 톨게이트 차선 분리와 비슷합니다. 일반 차선(커널 스택)과 하이패스 차선(XDP/DPDK)을 구분해 보면 왜 지연과 처리량이 달라지는지 명확해집니다.

핵심 요약

  • 패킷 수명주기 — ingress, 처리, egress 경로를 연결합니다.
  • 큐/버퍼 모델 — sk_buff와 큐 지점의 역할을 분리합니다.
  • 정책/데이터 분리 — 제어 평면과 데이터 평면을 구분합니다.
  • 성능 지표 — PPS, 지연, 드롭 원인을 함께 분석합니다.
  • 오프로딩 경계 — NIC/XDP/DPDK 경계를 명확히 유지합니다.

단계별 이해

  1. 경로 고정
    문제가 발생한 ingress/egress 지점을 먼저 특정합니다.
  2. 큐 관찰
    백로그와 드롭 위치를 계측합니다.
  3. 정책 반영 확인
    라우팅/필터 변경이 데이터 경로에 반영됐는지 봅니다.
  4. 부하 검증
    실제 트래픽 패턴에서 재현성을 확인합니다.
하드웨어 가속: SmartNIC/DPU에서 하드웨어 기반 네트워크 가속 기술을 확인하세요.
관련 표준: DPDK API Documentation, PCIe Base Specification — 고성능 패킷 처리 프레임워크 및 하드웨어 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

DPDK는 Intel이 주도하여 개발한 고성능 패킷 처리 프레임워크로, 커널 네트워크 스택을 완전히 바이패스하여 유저 공간에서 NIC를 직접 제어합니다. 표준 커널 드라이버가 인터럽트 기반으로 패킷을 처리하는 반면, DPDK는 폴링(polling) 기반 PMD(Poll Mode Driver)를 사용하여 컨텍스트 스위칭과 인터럽트 오버헤드를 제거합니다. 10~400GbE 환경에서 수십~수백 Mpps(백만 패킷/초)의 처리량을 단일 서버에서 달성할 수 있으며, 통신사(NFV), 클라우드(가상 스위칭), 금융(초저지연 트레이딩), CDN, 보안 장비 등에서 핵심 기술로 사용됩니다.

DPDK와 커널의 관계: DPDK는 커널 모듈이 아니라 유저 공간 라이브러리입니다. 그러나 커널의 VFIO, UIO, hugepages, IOMMU 등 여러 서브시스템에 의존하며, 최근에는 커널의 AF_XDP 소켓을 PMD 백엔드로 사용하는 하이브리드 접근도 지원합니다. 커널 개발자가 DPDK의 동작 원리를 이해하면 드라이버 최적화, VFIO/IOMMU 튜닝, AF_XDP 개발 등에서 큰 도움이 됩니다.

DPDK 아키텍처 개요

User Space (DPDK Application) DPDK Application (l2fwd, OVS-DPDK, VPP ...) mbuf 패킷 버퍼 Ring Lock-free 큐 Mempool 메모리 풀 Hash / LPM 탐색 라이브러리 Cryptodev / Eventdev 가속 프레임워크 EAL (Environment Abstraction Layer) — hugepage, CPU 코어, PCI, 로깅, 타이머 PMD (VFIO) PMD (UIO) PMD (AF_XDP) PMD (virtio) Kernel Space VFIO / IOMMU UIO (igb_uio) AF_XDP socket Hugepages (hugetlbfs) vfio-pci / uio_pci_generic Hardware NIC (10/25/100/400 GbE) DMA Engine PCIe BAR / MMIO 레지스터
계층구성 요소역할
Applicationl2fwd, l3fwd, OVS-DPDK, VPP, Pktgen패킷 처리 로직 구현
Core Librariesmbuf, Ring, Mempool, Hash, LPM, ACL고성능 데이터 구조 및 알고리즘
EALEnvironment Abstraction Layerhugepage, CPU 코어, PCI 디바이스, 타이머, 로깅 추상화
PMDPoll Mode Driver (ixgbe, i40e, mlx5, virtio, af_xdp...)NIC별 RX/TX 드라이버 (유저 공간)
KernelVFIO, UIO, hugepages, IOMMU디바이스 접근 및 메모리 관리 지원
HardwareNIC, 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, eBPFdpdk-proc-info, dpdk-pdump, DPDK telemetry
적용 분야범용 서버/데스크탑/IoTNFV, SDN 스위칭, 패킷 브로커, DPI, 로드밸런서
DPDK 사용 시 트레이드오프: DPDK는 커널 네트워크 스택을 바이패스하므로 iptables, tc, conntrack, TCP/IP 프로토콜 처리 등 커널이 제공하는 모든 기능을 사용할 수 없습니다. 이런 기능이 필요하면 앱에서 직접 구현하거나 VPP/FD.io 같은 유저 공간 네트워크 스택을 사용해야 합니다. 또한 전용 CPU 코어를 상시 폴링에 사용하므로 코어 수가 제한된 환경에서는 비효율적입니다.

EAL (Environment Abstraction Layer)

EAL은 DPDK의 초기화 계층으로, 애플리케이션이 하드웨어와 OS 세부 사항에 독립적으로 동작하도록 추상화합니다. rte_eal_init() 호출 시 수행되는 핵심 작업:

rte_eal_init() 초기화 흐름 (Linux) 1) 커맨드라인 파싱 -l, --socket-mem, --proc-type 등 2) Hugepage 매핑 sysfs 조회, hugetlbfs 확인 mmap + pagemap, NUMA 분배 3) 메모리 서브시스템 준비 malloc_heap, memzone, mempool 4) CPU 코어 관리 lcore 파싱, pthread 생성 affinity 설정, main lcore 선택 5) PCI 버스 스캔 /sys/bus/pci/devices 순회 VFIO/UIO 바인딩 상태 확인 PMD와 vendor:device 매칭 6) 서비스 코어(선택) --service-lcore 7) 공통 런타임 초기화 완료 로깅, 타이머, 인터럽트 핸들러 준비 이후 포트/큐 초기화 및 main loop 진입
/* 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;
}
EAL 핵심 옵션:
  • -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)을 유저 공간 메모리에 배치합니다.

PMD가 필요한 이유
전통적인 네트워크 스택은 NIC가 패킷을 수신하면 커널이 Interrupt를 발생시켜 처리합니다. 하지만 고속 패킷 처리에서 Interrupt 기반 모델은:

PMD는 이 문제를 해결하기 위해 폴링(Polling) 방식으로 동작합니다. 애플리케이션이 직접 NIC를 폴링하여 패킷을 수신하므로 오버헤드가 없습니다.

RX (수신) 동작 원리

PMD의 RX 동작은 생산자-소비자(Producer-Consumer) 패턴을 따릅니다:

  1. 초기 상태: 빈 Descriptor
    PMD는 초기화 시에 mbuf 메모리 풀(mempool)에서 Descriptor마다 빈 mbuf를 할당하여 Descriptor Ring에 채워둡니다. 이 mbuf는 패킷 데이터를 저장할 수 있는 버퍼입니다.
  2. NIC의 DMA 동작
    NIC가 네트워크 케이블에서 패킷을 수신하면, Descriptor에 있는 mbuf 주소로 DMA를 통해 패킷 데이터를 직접 메모리에 기록합니다. 이때 Descriptor의 DD(Descriptor Done) 비트를 1로 설정합니다.
  3. PMD의 폴링
    애플리케이션이 rte_eth_rx_burst()를 호출하면, PMD는 Descriptor Ring을 순회하며 각 Descriptor의 DD 비트를 확인합니다.
  4. 패킷 처리
    DD=1인 Descriptor(패킷이 도착한)를 찾으면:
    • Descriptor에서 패킷 메타데이터(길이, RSS hash, VLAN 등)를 추출
    • mbuf에 패킷 데이터가 있으므로, 이를 mbuf 포인터 배열에 추가
  5. Descriptor 재사용
    사용된 Descriptor에 새로운 mbuf를 할당하여 채워 넣고, Tail Register를 갱신하여 NIC에게 "이 Descriptor를 사용 가능"함을 알려줍니다.

이 과정이 매번 반복되며, PMD는 Interrupt 없이도 패킷을 지속적으로 수신할 수 있습니다.

PMD RX (수신) 동작 원리 왼쪽: NIC 영역 | 중간: Descriptor Ring (공유메모리) | 오른쪽: Mempool (mbuf pool) ① 초기화 시 (PMD 포트 초기화) PMD가 Mempool에서 mbuf를 할당 → Descriptor Ring에 채워넣음 각 Descriptor에는 빈 mbuf의 DMA 주소(buf_iova)가 저장됨, DD=0 ② NIC가 패킷 수신 → DMA 기록 NIC가 패킷 수신 (이더넷 케이블) NIC가 Descriptor의 mbuf 주소로 DMA로 패킷 데이터 기록 Descriptor Ring mbuf_addr | DD=1 패킷 데이터 기록 mbuf (버퍼) 패킷 데이터 저장됨 ③ PMD가 폴링 (rte_eth_rx_burst) Application rte_eth_rx_burst() 호출 Descriptor Ring DD=1 검사 → pkt_len, RSS hash 등 추출 mbuf 패킷 데이터 + 메타데이터 (pkt_len, RSS, VLAN...) rx_pkts[] ④ Descriptor 재사용 (새 mbuf 할당) Mempool에서 새 mbuf 할당 rte_mbuf_raw_alloc() Descriptor Ring 새 mbuf 주소 기록 DD=0으로 초기화 Tail Register NIC에게 반환 NIC가 다시 사용 가능 핵심: Descriptor는 "빈 mbuf에 대한 티켓" 역할을 함 | DD=1: NIC가 패킷 기록 완료 | DD=0: 빈 Descriptor (사용 가능)
/* 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의 역순으로 진행됩니다:

  1. 애플리케이션의 패킷 준비
    애플리케이션이 전송할 패킷이 담긴 mbuf 배열을 준비합니다.
  2. Descriptor 채우기
    PMD는 각 mbuf의 DMA 주소와 길이를 TX Descriptor에 기록합니다. 명령어 필드에 패킷 끝(EOP), Report Status(RS) 플래그를 설정합니다.
  3. Tail Register 갱신
    모든 Descriptor를 채운 후, 메모리 배리어를 수행하고 Tail Register에 새 위치를 기록합니다. 이를 통해 NIC에게 "이제 전송해도 좋다"고 알려줍니다.
  4. NIC의 DMA 및 전송
    NIC가 Descriptor를 읽어 mbuf에서 데이터를 DMA로 읽어 네트워크 케이블로 전송합니다.
  5. Descriptor 회수
    전송이 완료되면 NIC가 Descriptor의 DD 비트를 설정하고, PMD가 다음 번 호출 시 사용된 mbuf를 회수하여 Mempool에 반환합니다.
메모리 배리어(Memory Barrier)의 중요성
rte_wmb()는 Descriptor 쓰기가 실제 메모리에 완료된 후 Tail Register를 갱신하도록 보장합니다. 이를 통해 NIC가 Descriptor를 읽을 때 데이터가 아직 메모리에 쓰여지지 않은 추측적 읽기를 방지합니다.
PMD TX (송신) 동작 원리 왼쪽: 애플리케이션/Descriptor | 오른쪽: mbuf | 아래: NIC ① 애플리케이션 준비 → PMD가 Descriptor 채움 tx_pkts[] (전송할 패킷) Descriptor Ring buffer_addr, len, EOP, RS mbuf 주소 읽음 mbuf (패킷 데이터) ② 메모리 배리어 + Tail Register 갱신 rte_wmb() Tail Register NIC에게 알림 "이제 전송" ③ NIC가 Descriptor 읽고 DMA로 데이터 전송 NIC가 Descriptor 읽음 mbuf 주소, 길이 획득 NIC가 DMA로 패킷 데이터 읽음 mbuf에서 데이터 읽음 → 네트워크로 전송 NIC 패킷 전송 완료 ④ 전송 완료 → Descriptor 회수 (mbuf 반환) NIC가 DD=1 설정 (전송 완료) PMD가 Descriptor 검사 DD=1 → 사용된 mbuf 회수 Mempool에 반환 rte_mbuf_raw_free() 핵심: EOP(End Of Packet) | RS(Report Status): NIC에게 전송 완료 후 DD 비트 설정 요청
PMD 종류백엔드대표 드라이버특징
Physical PMDVFIO / UIOixgbe, i40e, ice, mlx5, bnxt, ena실제 NIC 직접 제어, 최고 성능
Virtual PMDvirtiovirtio-net, vmxnet3, avfVM 내부에서 가상 NIC 접근
AF_XDP PMD커널 AF_XDP 소켓af_xdp커널 기능 유지하면서 고성능, VFIO 불필요
SW PMDlibpcap / TAPpcap, net_tap개발/테스트용, 커널 NIC에 연결
Crypto PMDQAT / AESNI / SWqat, aesni_mb, openssl암호화 가속기 접근
Compress PMDQAT / zlibqat_comp, zlib압축 가속기
Event PMD하드웨어 이벤트 스케줄러dlb2, sw_event이벤트 기반 패킷 분배

Rx/Tx 오프로드 플래그

DPDK PMD는 NIC 하드웨어의 오프로드 기능을 ol_flagstx_offload 필드로 노출합니다. 올바른 오프로드 설정은 CPU 부하를 NIC으로 이전하여 처리량을 크게 향상시킵니다.

Rx 오프로드플래그동작
IP Checksum 검증RTE_ETH_RX_OFFLOAD_IPV4_CKSUMNIC가 IPv4 헤더 체크섬을 검증, 결과를 ol_flags에 기록
TCP/UDP Checksum 검증RTE_ETH_RX_OFFLOAD_TCP_CKSUMNIC가 L4 체크섬 검증
VLAN 스트리핑RTE_ETH_RX_OFFLOAD_VLAN_STRIPNIC가 VLAN 태그를 제거하고 vlan_tci에 저장
RSSRTE_ETH_RX_OFFLOAD_RSS_HASHNIC가 패킷 해시 계산, hash.rss에 저장
Scatter (SG)RTE_ETH_RX_OFFLOAD_SCATTERJumbo Frame을 다중 mbuf에 분산 수신
LRORTE_ETH_RX_OFFLOAD_TCP_LRONIC가 TCP 세그먼트를 병합 (GRO의 하드웨어 버전)
TimestampRTE_ETH_RX_OFFLOAD_TIMESTAMPNIC가 수신 타임스탬프 기록 (PTP/IEEE 1588)
Tx 오프로드플래그동작
IP Checksum 계산RTE_ETH_TX_OFFLOAD_IPV4_CKSUMNIC가 송신 시 IPv4 체크섬 계산 (앱은 0으로 설정)
TCP/UDP ChecksumRTE_ETH_TX_OFFLOAD_TCP_CKSUMNIC가 L4 체크섬 계산
TSO (TCP Segmentation)RTE_ETH_TX_OFFLOAD_TCP_TSONIC가 대형 TCP 세그먼트를 MSS 단위로 분할
VLAN 삽입RTE_ETH_TX_OFFLOAD_VLAN_INSERTNIC가 VLAN 태그 삽입
Multi-segmentRTE_ETH_TX_OFFLOAD_MULTI_SEGSScatter-Gather 송신 (다중 mbuf 체인)
VXLAN/GRE TSORTE_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가 잘못된 위치에 체크섬을 계산합니다. testpmdcsum 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 polling100% (항상)최소 (~수백 ns)고 트래픽, 지연 민감 (NFV, 금융)
Interrupt + polling부하 비례첫 패킷 ~수 μs (인터럽트 복귀)저 트래픽, 전력 민감, 코어 부족
Empty poll power적응형설정에 따라 가변가변 부하, 전력 관리 필요
examples/l3fwd-power: DPDK 공식 예제 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 참고 */
};
rte_mbuf 메모리 배치 rte_mbuf struct headroom (128B) packet data (variable) tailroom buf_addr data_off 기준 데이터 시작
비교 항목sk_buff (커널)rte_mbuf (DPDK)
크기~240 바이트 (가변)2 캐시라인 (128 바이트) 고정
할당__alloc_skb() → SLABMempool에서 사전 할당 (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_shinfonext 포인터로 세그먼트 체인
복제skb_clone() (데이터 공유)rte_pktmbuf_clone() (refcnt 증가)

멀티 세그먼트 mbuf (Scatter-Gather)

단일 mbuf의 데이터 영역(기본 2048B)보다 큰 패킷(Jumbo Frame, 최대 9KB)은 여러 mbuf를 next 포인터로 체이닝하여 표현합니다. 이를 Scatter-Gather라고 하며, NIC의 SG DMA 기능과 연동됩니다.

멀티 세그먼트 mbuf 체인 (Jumbo Frame 9000B) mbuf[0] hdr data 2048B pkt_len=9000 next mbuf[1] data 2048B data_len=2048 next mbuf[2] data 2048B data_len=2048 next mbuf[3] 2856B next=NULL head mbuf: pkt_len = 전체 패킷 길이 (9000B), nb_segs = 4, data_len = 이 세그먼트만의 데이터 길이 후속 mbuf: pkt_len = 0, data_len = 이 세그먼트 데이터 길이, next = 다음 세그먼트 (마지막은 NULL) 해제: rte_pktmbuf_free()는 체인을 순회하며 모든 세그먼트를 각각의 mempool에 반환 주의: Scatter RX를 사용하려면 rte_eth_rxmode.offloads에 RTE_ETH_RX_OFFLOAD_SCATTER 설정 필요
/* 멀티 세그먼트 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 부족) */
Scatter-Gather 성능 영향: 멀티 세그먼트 mbuf는 벡터화 PMD 경로를 비활성화할 수 있습니다. Jumbo Frame이 필요하지 않다면 MTU를 1500으로 유지하여 단일 세그먼트 fast path를 사용하세요. 필요한 경우에도 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);
rte_ring (Lock-free) 개념도 [slot0][slot1]...[slotN-1] / prod.head, prod.tail, cons.head, cons.tail MP/MC: head는 CAS로 예약, tail은 순서 보장 갱신 SP/SC: CAS 생략 가능, 메모리 배리어 중심으로 더 빠름 rte_ring (size = 2^n) prod.head - prod.tail - ... - cons.head - cons.tail [slot0][slot1][slot2]...[slotN-1] 1) CAS로 슬롯 예약 2) 데이터 복사 3) tail 순차 갱신 MP/MC 모드 생산자/소비자 다수, CAS 필수 정합성 확보 대신 오버헤드 증가 SP/SC 모드 단일 생산자/소비자, CAS 생략 메모리 배리어 중심으로 저지연
Ring 모드 선택:
  • 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 메모리 레이아웃 (Hugepage 2MB / 1GB) rte_mempool header (메타데이터: 크기·이름·통계·캐시 포인터) Per-Core Cache (락 없이 할당/반환 — O(1)) Core 0: [mbuf ptr] × 256 Core 1: [mbuf ptr] × 256 Core 2: [mbuf ptr] × 256 ··· 캐시 미스 시 공용 Ring에서 배치(bulk) 로드 → CAS 1회만 발생 Ring (공용 풀) — [mbuf ptr] × N (캐시 부족 시 배치로 가져옴) mbuf Objects (물리적으로 연속된 Hugepage 상에 배치) mbuf[0] headroom data area mbuf[1] headroom data area ··· mbuf[N] headroom data area 각 mbuf = rte_mbuf 구조체(128B) + headroom(128B) + data(2048B) — 캐시라인 정렬 할당 경로 ① per-core 캐시에서 mbuf 포인터 가져옴 (O(1)) ② 캐시 비면 → 공용 Ring에서 bulk 가져옴 (CAS 1회) ③ Ring도 비면 → 할당 실패 반환 반환 경로 ① per-core 캐시에 mbuf 포인터 반환 (O(1)) ② 캐시 가득 차면 → 공용 Ring에 bulk 반환 ③ 전체 과정에서 락 없음 — lock-free CAS만 사용
/* 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);
Hugepage가 필수인 이유:
  • 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, &reg);
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 매핑이 단순해지기 때문입니다.

IOVA 모드 비교: VA 우선, PA는 예외적 RTE_IOVA_VA Hugepage VA IOVA = VA VFIO / IOMMU DMA 주소 변환과 격리 장점: rootless 친화, PA 접근 불필요, mempool 부팅 시간 유리 권장: 대부분의 최신 VFIO 환경 RTE_IOVA_PA Hugepage VA Physical Address /proc/self/pagemap CAP_SYS_ADMIN 필요 가능 장점: 레거시 드라이버/특정 PA 요구 경로와 호환 단점: 권한 요구 증가, 초기화 실패 가능성 높음
항목RTE_IOVA_VARTE_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-memoryhugetlbfs 파일 대신 익명 매핑 사용멀티프로세스에는 부적합
--single-file-segments페이지별 파일 대신 memseg list당 파일 수 축소vhost-user처럼 FD 수가 민감한 환경에 유리
--legacy-mem시작 시 모든 hugepage를 미리 잡아 연속 IOVA 확보메모리 사용량이 커지고 동적 해제가 어려움
--match-allocations할당과 해제를 더 엄격히 대응시킴--legacy-mem, --no-huge와 같이 쓰지 않음
--huge-unlinkhugetlbfs에 남는 backing file 오염을 줄임멀티프로세스를 사실상 포기하는 쪽에 가깝습니다
PA 모드의 추가 권한: 공식 문서는 물리 주소가 필요할 때 실행 파일에 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를 쓴다"가 항상 "NIC을 커널에서 떼어낸다"를 의미하지는 않습니다. SmartNIC/eSwitch/representor를 쓰는 환경에서는 커널 netdev + DPDK PMD + 하드웨어 스위치가 동시에 존재하는 모델을 먼저 떠올려야 합니다.

패킷 처리 파이프라인 모델

DPDK 애플리케이션은 두 가지 기본 패킷 처리 모델을 사용합니다:

Run-to-Completion 모델 — 코어당 독립 처리 파이프라인 RSS(Receive Side Scaling)로 NIC가 각 큐로 트래픽을 자동 분산 NIC (RSS 분산) Queue 0 Queue 1 Queue 2 Core 0 ① RX burst (Port0 Q0) ② Process pkt ③ TX burst (Port1 Q0) Core 1 ① RX burst (Port0 Q1) ② Process pkt ③ TX burst (Port1 Q1) Core 2 ① RX burst (Port1 Q0) ② Process pkt ③ TX burst (Port0 Q0) 코어 간 통신 없음 (Ring 불필요) — 각 코어가 완전 독립 처리 — 단순·확장성 높음 — L2/L3 포워딩에 최적
코어단계역할
Core 0RX burst수신 패킷을 가져와 다음 단계로 전달
Core 1DPI/파싱L3~L7 분류 및 메타데이터 생성
Core 2NAT/암호화정책 적용/변환 처리
Core 3TX 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 라이브러리: RTC와 Dispatch 실행 모델 RTC graph ethdev_rx parser/classify tx 한 lcore가 그래프를 끝까지 순회 기존 RTC 루프를 노드화해 유지보수성 확보 장점: 가장 단순, 예측 가능한 캐시 동작 권장: 고정 기능 L2/L3 포워더, NFV dataplane Dispatch graph Source node dispatcher worker A worker B Source node와 worker node를 분리 작업 종류별 분산이 쉬움 권장: 기능이 많은 서비스 체인, 노드 재사용이 많은 앱 주의: RTC보다 구조가 복잡하고 디버깅 지점이 늘어남 공식 Graph 문서는 x86/ARM64 서버에서 burst 256, ARM64 임베디드에서 64/128이 실용적인 출발점이라고 설명합니다.
/* 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 루프GraphEventdev
구현 단위직접 작성한 while 루프노드와 에지이벤트 큐와 스케줄러
장점가장 단순, 오버헤드 최소구조화, 재사용, export/통계유연한 워커 스케줄링, 플로우 순서 제어
적합한 경우짧은 포워딩 경로서비스 체인, 공통 노드 재사용부하 편차가 큰 멀티스테이지 파이프라인
주의점코드가 커지면 유지보수 어려움노드 설계와 디버깅 규율 필요가장 복잡, 디바이스 지원 차이 큼
실무 포인트: Graph는 Eventdev의 대체제가 아니라 코드 구조화 도구에 더 가깝습니다. "스케줄링을 NIC/하드웨어 이벤트 장치에 맡길 것인가"가 핵심이면 Eventdev, "run-to-completion을 유지하되 노드 재사용성을 높일 것인가"가 핵심이면 Graph가 더 맞습니다.

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_RAWIOCAP_NET_RAW / CAP_BPFCAP_BPF
NIC 공유불가 (DPDK 전용)가능 (큐별 분리)가능
설정 복잡도VFIO 바인딩, hugepageXDP 프로그램 로드, 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 드라이버가 자동 감지하여 활성화
AF_XDP 제로카피 흐름 NIC RX Queue UMEM (shared) AF_XDP Socket DPDK App Fill/Completion/RX/TX Ring으로 버퍼 오프셋 전달, 데이터 복사는 생략
AF_XDP와 DPDK의 최신 경계: 커널 문서는 AF_XDP를 통해 제로카피 고속 패킷 전달을 제공하고, DPDK는 이를 PMD 백엔드로 활용할 수 있습니다. 즉 "커널 기능을 남길 것인가"와 "NIC 전체를 유저 공간이 독점할 것인가"를 분리해 설계할 수 있다는 점이 2026년 시점의 중요한 변화입니다.

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 구성이 한 그림으로 연결됩니다.

representor 포트와 eSwitch transfer 규칙 PF / uplink port 외부 물리 네트워크 NIC 내장 eSwitch VF0/SF0 transfer rule VF1/SF1 DPDK 애플리케이션 representor 포트 rte_flow API 핵심: queue 단위 패킷 처리와 별개로, 하드웨어 스위치 도메인에는 representor와 transfer 규칙이 따로 존재합니다.
개념의미운영 포인트
PFPhysical Function. NIC의 기본 함수uplink 제어, VF/SF 관리, representor의 기준점
VFSR-IOV 가상 함수VM/컨테이너에 직결되기 쉽고 representor와 1:1 대응
SFSubFunction일부 최신 NIC/DPU가 제공하는 더 세밀한 함수 단위
RepresentoreSwitch 내부 포트를 호스트에서 표현한 가상 포트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);
대표적 오해: representor는 "느린 소프트웨어 포트"가 아니라 하드웨어 내부 포트의 제어 창입니다. 패킷이 항상 representor를 거쳐 복사된다고 생각하면 설계가 어긋납니다. 실제 데이터는 eSwitch에서 직접 전환되고, representor는 정책·가시성·예외 처리의 접점이 됩니다.

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_VLANVLAN 태그 (VID, PCP, DEI)VLAN별 큐 분배
RTE_FLOW_ITEM_TYPE_IPV4IPv4 헤더 (src/dst IP, protocol, TTL)서브넷별 라우팅
RTE_FLOW_ITEM_TYPE_IPV6IPv6 헤더 (src/dst IP, next_hdr, flow_label)IPv6 플로우 레이블 분류
RTE_FLOW_ITEM_TYPE_TCPTCP 헤더 (src/dst port, flags)특정 포트 트래픽 분리
RTE_FLOW_ITEM_TYPE_UDPUDP 헤더 (src/dst port)DNS/VXLAN 트래픽 식별
RTE_FLOW_ITEM_TYPE_VXLANVXLAN 헤더 (VNI)오버레이 네트워크 분류
RTE_FLOW_ITEM_TYPE_GREGRE 헤더 (protocol, key)터널 트래픽 분류
RTE_FLOW_ITEM_TYPE_GENEVEGeneve 헤더 (VNI, options)OVN/Geneve 오버레이
RTE_FLOW_ITEM_TYPE_MARK이전 규칙이 설정한 마크다단계 필터링
RTE_FLOW_ITEM_TYPE_META메타데이터 (드라이버 정의)eSwitch/representor 간 상태 전달
RTE_FLOW_ITEM_TYPE_CONNTRACKCT 상태 (established, new, invalid)상태 기반 방화벽
RTE_FLOW_ITEM_TYPE_PORT_REPRESENTORrepresentor 포트eSwitch 규칙에서 소스/대상 지정
액션동작사용 예
RTE_FLOW_ACTION_TYPE_QUEUE특정 RX 큐로 전달플로우별 코어 고정
RTE_FLOW_ACTION_TYPE_RSSRSS로 큐 그룹에 분배서비스별 RSS 분리
RTE_FLOW_ACTION_TYPE_DROP패킷 드롭DDoS 필터링, ACL
RTE_FLOW_ACTION_TYPE_MARKmbuf에 마크 값 설정소프트웨어 후처리 힌트
RTE_FLOW_ACTION_TYPE_COUNT매칭 패킷/바이트 카운터통계, 과금
RTE_FLOW_ACTION_TYPE_SET_MAC_SRC/DSTMAC 주소 변경L2 NAT
RTE_FLOW_ACTION_TYPE_SET_IPV4_SRC/DSTIP 주소 변경NAT, 로드밸런싱
RTE_FLOW_ACTION_TYPE_SET_TP_SRC/DSTTCP/UDP 포트 변경NAPT
RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP/DECAPVXLAN 캡슐화/해제오버레이 네트워크
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_CONNTRACKCT 상태 추적/업데이트하드웨어 상태 방화벽
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 = &eth_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 },
};
CT 오프로드와 OVS: OVS 2.17+ 는 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 규칙만 남기기 쉬움테스트 도구 없는 운영 전환은 위험합니다.
운영 포인트: isolated mode는 디버깅을 쉽게 만들기도 하지만, 반대로 기본 경로라는 안전망을 제거합니다. 따라서 처음에는 비격리 상태에서 rule을 검증하고, 이후 isolated mode로 전환하는 2단계 절차가 안전합니다.

Hairpin queue와 intra-device 포워딩

hairpin queue는 패킷을 호스트 메모리까지 올리지 않고 NIC 내부에서 다른 RX/TX 큐나 포트로 되돌리는 기능입니다. OVS hardware offload, representor 기반 서비스 체인, 고속 TAP 없는 VM 브리징, 장치 내부 loopback 테스트에서 중요합니다. 최신 testpmd 문서도 hairpin 관련 명령과 포트 프록시 조회 기능을 별도로 다룹니다.

Hairpin queue: 호스트 메모리를 거치지 않는 NIC 내부 경로 Uplink / PF ingress wire packet rte_flow match NIC datapath / eSwitch hairpin RX hairpin TX VF / representor / peer port 서비스 체인 다음 홉 호스트 메모리 미경유 핵심: mbuf를 올려서 CPU가 다시 밀어 넣는 대신, NIC 내부 큐 바인딩으로 우회합니다.
/* 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 공유 메모리 아키텍처 Guest VM (QEMU) Guest 애플리케이션 virtio-net 드라이버 virtqueue (vring) descriptor table avail ring (guest→host) used ring (host→guest) Host (DPDK vhost backend) OVS-DPDK / VPP rte_vhost 라이브러리 PMD thread busy-poll virtqueue rte_vhost_enqueue_burst rte_vhost_dequeue_burst UNIX Socket (제어 채널) 공유 Hugepage 메모리 (guest RAM = host hugepage mmap) virtqueue descriptor/ring 패킷 데이터 버퍼 mbuf 메타데이터 (호스트 측) Guest가 virtio 드라이버로 패킷을 avail ring에 게시 → Host PMD가 직접 hugepage에서 읽음 (복사 없음)
항목vhost-uservhost-net (커널)SR-IOV 패스스루
호스트 백엔드DPDK 유저 공간커널 vhost 모듈VF 직접 할당
스위칭OVS-DPDK / VPP커널 브릿지 / OVSNIC 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)
vhost-user vs vhost-vDPA: vDPA(virtio Data Path Acceleration)는 vhost-user의 데이터 경로를 하드웨어(SmartNIC)로 오프로드합니다. 제어 경로는 여전히 vhost 프로토콜을 사용하지만, 패킷 전달은 NIC가 직접 수행하므로 SR-IOV 수준의 성능과 vhost-user 수준의 유연성을 동시에 달성합니다. DPDK 24.x부터 vDPA 드라이버가 강화되고 있습니다.

OVS-DPDK (Open vSwitch + DPDK)

OVS-DPDK는 Open vSwitch의 데이터플레인을 DPDK로 교체하여 가상 스위칭 성능을 극대화한 구성입니다. 클라우드/NFV 환경에서 VM/컨테이너 간 네트워크 트래픽을 유저 공간에서 처리하여, 기존 커널 OVS 대비 5~10배의 처리량을 달성합니다.

버전 호환성: 2026년 3월 7일 기준 Open vSwitch 공식 최신 DPDK 설치 문서는 DPDK 25.11.0 지원을 명시합니다. OVS-DPDK는 커널 모듈보다 OVS 사용자 공간 바이너리와 DPDK ABI 조합에 더 민감하므로, 배포판 기본 패키지와 자체 빌드를 섞기 전에 지원 조합을 먼저 고정해야 합니다.
OVS-DPDK 아키텍처 VM1 Guest OS virtio-net VM2 Guest OS virtio-net VM3 Guest OS virtio-net 물리 NIC 25GbE (VFIO) DPDK PMD vhost-user vhost-user vhost-user OVS-DPDK (vswitchd) DPDK Datapath (PMD threads — busy-poll) EMC Exact Match Cache dpcls 튜플 기반 분류 upcall 미스 → ofproto • EMC: 최빈 플로우 해시 캐시 (O(1)) — 높은 hit rate가 핵심 • dpcls: 튜플-공간 탐색 (5-tuple 분류) — EMC 미스 시 • upcall: 완전 미스 시 ofproto의 OpenFlow 테이블 참조 OpenFlow Pipeline (ofproto) Table 0 Table 1 ··· Actions • output: 포트로 전송 • mod_vlan / drop • NORMAL: L2 학습 PMD threads: 전용 CPU 코어에서 busy-poll로 모든 포트(vhost-user + 물리 NIC) 폴링 vhost-user: UNIX 소켓으로 QEMU와 공유 메모리 설정 → 게스트 virtio 큐에 제로카피 직접 접근 EMC hit rate ↑ → 성능 극대화 (해시 O(1)) | 물리 NIC ↔ VM 간 패킷은 Hugepage 공유 메모리로 전달
# ━━━ 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를 유지할 수 있습니다.

rte_sched 계층적 QoS 스케줄러 (5단계 트리) Port (출력 포트) Subport 0 (고객 A) Subport 1 (고객 B) Subport 2 (고객 C) Pipe 0 (가입자 1) Pipe 1 (가입자 2) Pipe N ... TC0 (BE) TC1 (AF) TC2 (EF) TC3~12 Q0 Q1 Q2 Q3 5단계 계층 (Port → Subport → Pipe → TC → Queue) Port: 출력 포트 전체 대역폭 제한 (Token Bucket) Subport: 고객/서비스 그룹별 대역폭 보장 Pipe: 개별 가입자/회선, Token Bucket으로 셰이핑 TC: Traffic Class, Strict Priority 또는 WFQ Queue: TC 내부 큐, WRR(Weighted Round Robin) 스케줄링 WRED (Weighted Random Early Detection) 드롭 정책 지원 최대: 1 port × 64 subport × 4K pipe × 13 TC × 4 queue = 1,327,104개 리프 큐 / 포트
계층스케줄링 알고리즘설정 예
PortToken Bucket (전체 출력 rate)10 Gbps line rate
SubportToken Bucket + TC간 WFQ고객 A: 2 Gbps 보장
PipeToken Bucket (가입자 셰이핑)가입자당 100 Mbps
TCStrict Priority (높은 TC 우선)TC0=BE, TC1=AF, TC2=EF(VoIP)
QueueWRR (가중치 라운드 로빈)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와의 차이: 커널 tc/HTB는 커널 공간에서 sk_buff를 다루고 Qdisc 프레임워크를 사용합니다. DPDK rte_sched는 유저 공간에서 rte_mbuf를 직접 큐잉하므로 컨텍스트 스위칭 없이 동작하지만, 커널의 nftables/conntrack 기반 QoS 정책과는 통합되지 않습니다.

Eventdev — 이벤트 기반 패킷 스케줄링

rte_eventdev는 DPDK의 이벤트 기반 프로그래밍 모델로, 하드웨어 이벤트 스케줄러(Intel DLB2 등)나 소프트웨어 구현을 통해 패킷을 워커 코어에 동적으로 분배합니다. Run-to-Completion과 Pipeline 모델의 장점을 결합합니다.

RX Adapter NIC RX - event Event Scheduler Atomic Queue Ordered Queue Parallel Queue TX Adapter event - NIC TX Worker 0 Worker 1 Worker 2
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 아키텍처: Lookaside vs Inline Lookaside 모델 RX burst enqueue_burst dequeue_burst Crypto PMD QAT / AESNI / OpenSSL / SW 패킷이 crypto 장치로 갔다가 돌아오는 비동기 모델 장점: 유연, 다양한 알고리즘, 독립 큐 단점: enqueue/dequeue 추가 지연 Inline 모델 RX burst 패킷 처리 TX burst NIC Inline Crypto NIC 하드웨어가 직접 암복호화 NIC가 RX/TX 경로에서 직접 암복호화 수행 장점: 최소 지연, CPU 부하 없음 단점: NIC별 알고리즘 제한, 설정 복잡
/* 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백엔드특징
QATIntel QuickAssist하드웨어 가속, 대칭/비대칭/해시, 압축도 지원
AESNI-MBIntel AES-NI 명령어소프트웨어, x86 AES-NI/AVX2/AVX-512 활용
OpenSSLlibcrypto소프트웨어, 가장 넓은 알고리즘 지원
ARMv8 CEARM Crypto ExtensionARM 서버 네이티브 가속
Scheduler멀티 PMD 번들여러 crypto PMD를 묶어 부하 분산
Null무연산성능 베이스라인 측정용
모델작동 방식적합한 경우
Lookaside Protocol전체 프로토콜(ESP 등)을 crypto 장치가 처리IPSec 게이트웨이, 하드웨어가 프로토콜 인식
Lookaside None앱이 프로토콜 처리, crypto 장치는 알고리즘만커스텀 프로토콜, 세밀한 제어 필요 시
Inline CryptoNIC RX/TX 경로에서 하드웨어 암복호화와이어 스피드 IPSec, MACsec
Inline ProtocolNIC이 전체 IPSec/MACsec 프로토콜 처리최저 지연 IPSec 어플라이언스
IPSec + DPDK: examples/ipsec-secgw는 DPDK 기반 IPSec 게이트웨이 레퍼런스 구현으로, Lookaside/Inline 모델을 모두 지원합니다. VPP의 ipsec 노드와 함께 프로덕션 IPSec 구현의 두 가지 주요 선택지입니다.

DPDK 멀티프로세스 모드

DPDK는 primary-secondary 프로세스 모델을 지원합니다. Primary 프로세스가 EAL 초기화, 포트 설정, Mempool 생성을 수행하고, Secondary 프로세스가 공유 메모리(hugepage)를 통해 동일한 자원에 접근합니다.

DPDK 멀티프로세스 아키텍처 (Primary / Secondary) Primary Process --proc-type primary rte_eal_init() — Hugepage 매핑 생성 rte_mempool_create() — Mempool 생성 rte_eth_dev_configure() — 포트 설정/시작 rte_ring_create() — Ring 생성 패킷 포워딩 (RX → Process → TX) 모든 물리 NIC 소유 및 제어 Secondary Process(es) --proc-type secondary --file-prefix dpdk0 rte_eal_init() — 동일 Hugepage에 Attach rte_mempool_lookup() — Mempool 공유 참조 rte_eth_dev_get_port() — 포트 룩업(읽기 전용) rte_ring_lookup() — Ring 공유 참조 pdump / 통계 / 텔레메트리 수집 물리 NIC는 primary가 소유 — secondary는 공유 객체만 접근 Shared Hugepage Memory Mempool · Ring · mbuf (물리 주소 동일, VA도 동일 — mmap 동일 주소 보장) rte_config 공유 메모리 (EAL 메타데이터 — 모든 프로세스 공유) 포인터 기반 접근 가능 — VA 동일 보장으로 구조체 포인터 직접 사용 주요 사용 사례 • 패킷 캡처: primary가 포워딩, secondary(dpdk-pdump)가 미러 캡처 → pcap 저장 • 모니터링/텔레메트리: secondary가 통계 수집·분석 (primary 중단 없이) • 무중단 업그레이드: 신규 secondary 시작 후 기존 프로세스 교체 • 다중 앱 분리: primary(포워딩) + secondary(제어/관리) 역할 분리
# 멀티프로세스 실행 예시

# 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 PluginMultus CNI가 DPDK 워크로드에 VF를 동적 할당하는 표준 패턴입니다.

Kubernetes DPDK 배포 아키텍처 Worker Node (baremetal / VM) Kubelet CPU Manager (static policy) Topology Manager (NUMA) hugepage 리소스 관리 SR-IOV Device Plugin VF 풀 관리 VFIO 바인딩 자동화 리소스 어드버타이징 Multus CNI 다중 네트워크 인터페이스 DPDK VF를 Pod에 연결 NetworkAttachmentDefinition NIC (PF) VF0, VF1, ... SR-IOV 활성화 vfio-pci 바인딩 DPDK Pod DPDK 애플리케이션 PMD (vfio-pci VF) /dev/vfio/N 마운트 /dev/hugepages 마운트 resources: hugepages-1Gi: 2Gi, intel.com/sriov_dpdk: 1 Guaranteed QoS + cpuManagerPolicy=static → 전용 CPU 코어 할당 (isolcpus 효과) Pod 스펙 핵심 요소 • securityContext.privileged: false (VFIO는 privileged 불필요) • volumes: hugepages (emptyDir medium: HugePages-1Gi) • resources.limits: hugepages-1Gi: 2Gi (Guaranteed QoS 필수) • resources.limits: intel.com/sriov_dpdk: 1 (VF 할당) • CPU limits = requests (static CPU manager 트리거) • topologyManager: single-numa-node (NUMA 정렬 강제) • annotation: k8s.v1.cni.cncf.io/networks (Multus) • DPDK_DEVARGS 환경변수로 PCI 주소 주입
# 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
컨테이너 환경 항목설정이유
HugepageemptyDir.medium: HugePages-1GiDPDK는 hugetlbfs 필수, Pod 내 /dev/hugepages에 마운트
CPU ManagercpuManagerPolicy: staticGuaranteed QoS Pod에 전용 코어 할당 (busy-poll 필수)
Topology Managersingle-numa-nodeNIC VF, CPU, 메모리를 같은 NUMA 노드에 배치
SR-IOV Device PluginConfigMap으로 VF 풀 정의VF를 VFIO 바인딩하고 리소스로 어드버타이징
Multus CNINetworkAttachmentDefinition기본 CNI(Calico 등) + DPDK VF 다중 네트워크
securityContextIPC_LOCK, SYS_RAWIOhugepage mlock과 VFIO 접근에 필요 (privileged 불필요)
Docker 직접 실행--device /dev/vfio/N -v /dev/hugepagesK8s 없는 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
컨테이너 DPDK 주의점:
  • --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 횟수를 기준으로 절전과 지연을 절충할 수 있습니다.

Empty poll 기반 전력 관리 rx_burst() 0 packet empty poll counter 임계치 누적 pause/monitor frequency down 새 패킷 도착 즉시 복귀 지연 최저가 목표면 계속 busy-poll, 전력까지 관리해야 하면 empty poll 임계치와 wake-up 비용을 함께 튜닝합니다.
전략장점대가
순수 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 infoNUMA 노드와 큐 수가 워커 수에 맞음
xstatsshow port xstats 0rx_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-DPDKVPP/FD.io
주요 용도L2 가상 스위칭 (OpenFlow)L2~L4 라우팅/NAT/IPSec
패킷 처리플로우 테이블 매칭벡터 그래프 (노드 체인)
처리 모델플로우 캐시 + upcall벡터화: 같은 노드를 256패킷 배치로 처리
I-cache 효율보통 (플로우별 분기)높음 (동일 코드를 벡터 크기만큼 반복)
기능L2 스위칭 특화L2~L4, NAT44/NAT64, SRv6, IPSec, MPLS, VXLAN 등
설정OpenFlow / OVNCLI / 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.cHugepage 관리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 옵션기본값설명
-Dplatformnativenative(현재 CPU 최적화), generic(이식성), x86-64-v3(AVX2) 등
-Dmax_ethports32최대 이더넷 포트 수 (구조체 크기에 영향)
-Dmax_lcores128최대 논리 코어 수
-Ddefault_librarysharedshared/static — 정적 링크는 단일 바이너리 배포에 유리
-Ddisable_drivers없음불필요 PMD 제외 → 빌드 시간·바이너리 크기 감소
-Denable_kmodsfalseigb_uio 등 커널 모듈 빌드 (deprecated)
-Dteststrue유닛 테스트 빌드 (CI 환경)

코어 빌드 옵션 상세

Meson 빌드 시스템에서 -D옵션=값 형태로 지정하는 핵심 빌드 옵션의 전체 목록입니다. meson configure build 명령으로 현재 설정을 확인할 수 있습니다.

옵션기본값허용 값설명
-Dplatformnativenative, generic, x86-64-v2~v4CPU 최적화 수준. native는 빌드 호스트 CPU에 맞춰 최적화
-DmachineautoGCC -marchplatformnative일 때 세부 아키텍처 지정 (예: icelake-server)
-Dcpu_instruction_setautoauto, generic, nativeARM 전용: NEON/SVE/SVE2 명령어 세트 선택
-Dbuildtypereleaserelease, debug, debugoptimized, plainMeson 내장 옵션. debug-O0 -g, release-O3
-Doptimization30~3, sbuildtype 설정을 재정의하는 세부 최적화 수준
-Ddebugtruetrue/false디버그 심볼 포함 여부 (-g 플래그)
-Dwerrorfalsetrue/false경고를 에러로 처리 (-Werror). CI에서 권장
-Ddeveloper_modeautoauto, enabled, disabledGit 트리에서 자동 활성화. 추가 경고·werror·디버그 체크 포함
-Dcheck_includesfalsetrue/false각 공개 헤더의 독립 컴파일 가능성 검증
-Db_ltofalsetrue/falseLTO (Link-Time Optimization). 바이너리 크기 감소·성능 향상
-Db_pgooffoff, generate, usePGO (Profile-Guided Optimization). 2단계 빌드 필요
-Dprefix/usr/local경로설치 경로 접두사
-Dlibdirlib경로라이브러리 설치 디렉터리 (일부 배포판은 lib64)
platform vs machine 우선순위: platformnative 또는 generic이면 DPDK가 자동으로 적절한 -march 값을 선택합니다. machine을 명시하면 platform의 자동 감지를 재정의합니다. 크로스 컴파일 시에는 machine이 반드시 필요합니다.

드라이버·라이브러리 선택 옵션

DPDK는 200개 이상의 PMD(Poll Mode Driver)를 포함하며, 불필요한 드라이버를 제외하면 빌드 시간과 바이너리 크기를 크게 줄일 수 있습니다.

옵션기본값설명
-Ddisable_drivers없음제외할 드라이버 (쉼표 구분). 와일드카드 지원: net/i*
-Denable_drivers없음 (전체)포함할 드라이버만 지정 (화이트리스트 방식)
-Ddisable_libs없음제외할 라이브러리 (쉼표 구분). 의존성 있는 드라이버도 함께 제외
-Denable_kmodsfalseigb_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 + disable 동시 사용 규칙: enable_driversdisable_drivers를 동시에 지정하면 enable_drivers가 우선합니다. enable_drivers에 나열된 드라이버만 빌드 후보가 되고, 그중 disable_drivers에 해당하는 것이 제외됩니다. enable_drivers만 사용하는 것이 직관적입니다.

크기 제한 및 리소스 상수

DPDK의 내부 자료 구조 크기를 컴파일 타임에 결정하는 상수입니다. 기본값은 범용 서버를 기준으로 설정되어 있으며, 임베디드 환경이나 대규모 NIC 환경에서는 조정이 필요합니다.

옵션 / 매크로기본값영향
-Dmax_ethports32최대 이더넷 포트 수. 포트별 배열 크기 결정
-Dmax_lcores128최대 논리 코어 수. RTE_MAX_LCORE에 매핑
-Dmax_numa_nodes4최대 NUMA 노드 수. 메모리 풀 분할에 영향
RTE_MAX_ETHPORTSmax_ethportsrte_ethdev 배열 크기. SmartNIC VF가 많으면 증가 필요
RTE_MAX_LCOREmax_lcoresper-lcore 변수 배열 크기. 대형 NUMA 서버는 256+ 필요
RTE_MAX_MEMSEG_LISTS64hugepage 메모리 세그먼트 목록 수. 다중 hugepage 크기 시 증가
RTE_MAX_MEM_MB524288최대 hugepage 메모리 (MB). 512GB 기본, TB급 서버는 증가 필요
RTE_MAX_QUEUES_PER_PORT1024포트당 최대 큐 수. RSS 해시 분산 범위에 영향
값 조정 가이드: 상수를 과도하게 높이면 per-lcore 변수와 포트별 배열이 커져 캐시 효율이 저하됩니다. 실제 사용하는 포트·코어 수의 2배 정도로 설정하는 것이 적절합니다. RTE_MAX_MEM_MBrte_eal_init() 시 실제 hugepage 할당량과는 무관한 상한값입니다.

기능 토글 옵션

빌드에 포함할 기능을 세밀하게 제어하는 옵션입니다.

옵션기본값설명
-Dteststrue유닛 테스트 빌드. CI에서는 true, 프로덕션 빌드에서는 false
-Dexamples빈 문자열빌드할 예제 (쉼표 구분). all로 전체 빌드 가능
-Denable_apps전체빌드할 앱 선택 (test-pmd, proc-info, pdump 등)
-Denable_docsfalseAPI 문서(Doxygen) 및 가이드(Sphinx) 빌드
-Denable_trace_fpfalsefast-path trace point 활성화. 성능 오버헤드 미미
-Dmbuf_refcnt_atomictruembuf 참조 카운트 원자적 연산. 단일 스레드면 false로 성능 향상
-Dper_library_versionstrue라이브러리별 개별 SO 버전 관리. false면 단일 ABI 버전
-Denable_driver_sdkfalse드라이버 개발용 내부 헤더 설치. out-of-tree PMD 개발 시 필요
-Dlog_default_levelinfo기본 로그 수준: emergency~debug (8단계)
-Duse_hpetfalseHPET 타이머 사용. TSC가 정확한 현대 CPU에서는 불필요

크로스 컴파일 설정

DPDK는 Meson의 크로스 컴파일 파일(cross-file)을 사용하여 ARM, RISC-V 등 다양한 아키텍처용 빌드를 지원합니다.

x86_64 호스트 크로스 툴체인 cross-file Meson + Ninja 빌드 수행 install ARM64 타겟 libdpdk + PMD 배포 DPU BlueField
크로스파일 항목값 예시설명
[binaries] caarch64-linux-gnu-gcc크로스 C 컴파일러 경로
[binaries] araarch64-linux-gnu-ar크로스 아카이버
[binaries] stripaarch64-linux-gnu-strip심볼 제거 도구
[binaries] pkgconfigaarch64-linux-gnu-pkg-config크로스 pkg-config
[host_machine] cpu_familyaarch64타겟 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
의존성 크로스 설치: 크로스 컴파일 시 libnuma, libpcap 등의 타겟 아키텍처용 라이브러리가 필요합니다. Debian 멀티아치 환경에서는 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-DmachineGCC -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=sharedMeson 내장 옵션
CONFIG_RTE_EAL_IGB_UIO-Denable_kmodsdeprecated
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_stdatomicDPDK 23.11+: C11 stdatomic 사용
CONFIG_RTE_USE_HPET-Duse_hpetHPET 타이머
T= (target)--cross-file크로스 컴파일 대상 지정
O= (output dir)meson setup <dir>빌드 디렉터리 지정
EXTRA_CFLAGS-Dc_args추가 C 플래그 (Meson 내장)
자동 변환 도구 없음: DPDK 공식 프로젝트에서는 make → 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.x2013~2015Intel 초기 릴리스, make 빌드, ixgbe/i40e PMD, 기본 mbuf/ring/mempool
16.04~16.112016연월 버전 체계 도입, rte_flow API 초안, Eventdev 프레임워크
17.11 LTS2017첫 LTS, Cryptodev 성숙, Flow API 정식화, AF_XDP 실험
18.11 LTS2018ABI 안정성 정책 도입, Meson 빌드 추가, mlx5 bifurcated PMD
19.11 LTS2019AF_XDP PMD 정식, Graph 라이브러리, Telemetry v2, RCU 라이브러리
20.11 LTS2020API 대정비 (rte_eth → rte_ethdev), dmadev 초안, IOAT 드라이버
21.11 LTS2021make 빌드 제거, Meson 전용, dmadev 정식, GPU 디바이스, Trace 라이브러리
22.11 LTS2022ABI 23.x 시리즈, Graph dispatch 모델, flow aging, CT offload 강화
23.11 LTS2023vhost DMA offload, rte_flow async, ethdev 텔레메트리 확장
24.11 LTS2024ABI 25.x 시리즈, ARM SVE2 최적화, GPUdev DMA, power intrinsics 강화

버전별 옵션 변경 이력

DPDK 버전 업그레이드 시 빌드 스크립트를 수정해야 하는 옵션 변경 사항입니다.

버전추가된 옵션변경된 옵션제거/Deprecated
18.11Meson 빌드 도입 (make과 병행)
19.11enable_trace_fpmax_lcores 기본값 128→128 유지
20.11disable_apps, enable_appsdisable_drivers 와일드카드 지원enable_kmods deprecated
21.11platform (기존 machine 대체)machineplatform 하위로 이동make 빌드 완전 제거
22.11enable_driver_sdk, log_default_levelmax_ethports 기본값 32 유지KNI 라이브러리 deprecated
23.11enable_stdatomic, RISC-V 크로스파일cpu_instruction_set ARM 확장igb_uio 소스 제거
24.03x86-64-v2~v4 platform 값developer_mode 동작 세분화
24.11ARM SVE2 cpu_instruction_setmax_numa_nodes 기본값 4→4 유지use_hpet deprecated 예고
릴리스 노트 diff 확인법: 버전 간 옵션 변경을 정확히 파악하려면 diff <(cd dpdk-23.11 && meson configure build) <(cd dpdk-24.11 && meson configure build) 명령 또는 meson_options.txt 파일의 Git diff를 확인합니다. 릴리스 노트의 "Build System" 섹션도 참고하세요.
LTS 정책: DPDK LTS는 출시 후 최소 2년간 백포트 패치가 유지됩니다. OVS-DPDK, VPP 등 상위 프로젝트는 특정 DPDK LTS 버전에만 호환되므로, 버전 조합을 먼저 고정한 뒤 빌드해야 합니다.

타 바이패스 프레임워크 비교

DPDK는 유일한 커널 바이패스 기술이 아닙니다. 각 프레임워크는 서로 다른 설계 철학과 트레이드오프를 가집니다.

커널 바이패스 프레임워크 스펙트럼 커널 통합 ← → 완전 바이패스 XDP 커널 내 eBPF 훅 커널 기능 유지 AF_XDP 커널↔유저 공유 부분 바이패스 io_uring 커널 비동기 I/O 네트워크는 실험적 Netmap 커널 모듈 + 유저 공유 링 버퍼 DPDK 완전 유저 공간 NIC 전용 점유 XDP: 커널 내에서 패킷을 가장 먼저 처리, eBPF 프로그램, tc/iptables 공존 가능 AF_XDP: XDP에서 유저 공간으로 제로카피 전달, DPDK PMD 백엔드로도 사용, NIC 공유 가능 io_uring: 범용 비동기 I/O, 네트워크 제로카피는 6.x 커널에서 실험 중, 패킷 처리보다 I/O 멀티플렉싱에 강점 Netmap: FreeBSD 출신, 커널 모듈로 NIC 링을 유저 공간에 매핑, Linux에서는 주류가 아님 DPDK: 완전 유저 공간 제어, PMD 에코시스템 최대, 커널 기능 사용 불가, 가장 높은 처리량 선택 기준: 커널 기능 필요성, NIC 공유 여부, 에코시스템 성숙도, 운영 복잡성 허용 범위
항목DPDKXDP (eBPF)AF_XDPNetmapio_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, bpfilterDPDK 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가지 원인 중 하나(또는 복합)에 해당합니다.

핵심 전제: DPDK PMD는 NIC을 유저 공간에서 전용 점유하므로, NIC과 NVMe가 동일 PCIe Root Complex / Switch를 통과하면 하드웨어 수준에서 경합이 발생합니다. 소프트웨어 프로파일링만으로는 원인을 찾기 어려운 경우가 많습니다.

1. PCIe 버스 대역폭 경합

DPDK 10 GbE 라인레이트(~14.88 Mpps, 64B 프레임)는 PCIe 3.0 x8 대역폭의 상당 부분을 소비합니다. NVMe가 같은 Root Complex 하위 Switch에 연결되면 TLP(Transaction Layer Packet) 수준에서 중재 경합이 발생합니다.

PCIe 토폴로지와 NIC/NVMe 경합 경로 CPU + Root Complex PCIe Switch A PCIe Switch B NIC (DPDK PMD) 25/100 GbE NVMe SSD 경합 발생! NVMe SSD 분리 — 권장 ⚡ TLP 경합 해결: NIC과 NVMe를 서로 다른 PCIe Switch (또는 CPU 직결 슬롯)에 배치 확인: lspci -tv 로 토폴로지 확인, perf stat -e uncore_iio 로 포트별 트래픽 측정
원인메커니즘증상확인 방법해결
업스트림 대역폭 포화 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)
Relaxed Ordering: DPDK 20.11+는 mlx5/mlx4 PMD에서 Relaxed Ordering을 지원합니다. devargsmprq_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 nvme
DPDK --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 irqbalance
IRQ 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_node
numastat -p <pid>
NIC과 NVMe를 같은 NUMA 노드에 배치, DPDK --socket-mem 조정
Hugepage NUMA 편향 DPDK가 한 노드에서 hugepage를 대량 소비 → 해당 노드의 NVMe DMA 버퍼 할당 실패/폴백 cat /sys/devices/system/node/node*/hugepages/*/free_hugepages에서 편향 numactl --hardware
hugepage 노드별 잔량 확인
노드별 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 1bi/bo 모니터링 DPDK hugepage를 필요한 최소량으로 제한, --socket-mem 명시
THP compaction 스톰 THP(Transparent Hugepage) 활성 상태에서 DPDK hugepage 예약이 메모리 단편화 유발 → compaction이 NVMe I/O 경로에서 stall kcompactd CPU 사용 급증, NVMe 지연 간헐적 스파이크 grep compact /proc/vmstat
perf 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-misses
dmesg | 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 pass-through 모드: iommu=pt는 DPDK가 VFIO를 사용할 때 다른 장치(NVMe 포함)의 DMA를 pass-through로 처리하여 IOMMU 오버헤드를 제거합니다. 단, DMA 격리가 약화되므로 보안이 중요한 환경에서는 주의가 필요합니다.

6. LLC 캐시 오염 / 메모리 대역폭 포화

DPDK PMD는 대량의 패킷 버퍼를 연속적으로 접근하며 LLC(Last Level Cache)를 오염시킵니다. NVMe 드라이버의 Completion Queue 엔트리와 I/O 메타데이터가 캐시에서 밀려나면 성능이 저하됩니다.

CPU 리소스 간섭 계층 DPDK PMD 코어 L1/L2: 패킷 버퍼, 디스크립터 링 NVMe IRQ / IO 워커 코어 L1/L2: CQE, SQE, 페이지 캐시 LLC (Last Level Cache) — 공유 · 경합 DPDK mbuf prefetch가 NVMe CQE/메타데이터를 evict 메모리 대역폭 (DDR4/DDR5 채널) DMA 읽기/쓰기 + CPU 접근이 채널 대역폭 포화 해결: Intel CAT/MBA (RDT)로 LLC 파티셔닝, DPDK 코어를 별도 COS에 격리, pcm-memory.x로 대역폭 모니터링
원인메커니즘증상확인 방법해결
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 간섭의 대부분을 빠르게 진단할 수 있습니다.

#점검 항목명령 / 확인 방법정상 기준
1PCIe 토폴로지 분리lspci -tvNIC과 NVMe가 다른 Switch 하위
2NUMA 노드 일치cat /sys/class/net/*/device/numa_node
cat /sys/block/nvme*/device/numa_node
NIC과 NVMe가 같은 NUMA 노드
3NVMe IRQ affinitycat /proc/irq/*/smp_affinity_list (nvme)DPDK 코어와 겹치지 않음
4HT 형제 분리lscpu -eDPDK 코어의 HT 형제에 NVMe IRQ 없음
5irqbalance 제외grep BANNED /etc/default/irqbalanceDPDK 코어가 BANNED 목록에 포함
6Hugepage 잔량cat /sys/devices/system/node/node*/hugepages/*/free_hugepages각 노드에 충분한 여유 hugepage
7일반 메모리 여유free -mbuff/cache가 총 메모리의 20%+
8THP 설정cat /sys/kernel/mm/transparent_hugepage/enabledmadvise 또는 never
9IOMMU 모드dmesg | grep -i iommuiommu=pt 활성
10IOMMU 그룹find /sys/kernel/iommu_groups/ -type lNIC과 NVMe가 다른 그룹
11LLC miss 비율perf stat -e LLC-load-misses -C <nvme_core>DPDK 기동 전후 차이 <10%
12메모리 대역폭pcm-memory.x 또는 perf stat -e uncore_imc채널 사용률 <70%
13C-stateturbostat --show Avg_MHz,Busy%,Bzy_MHz,PkgWattNVMe 코어가 deep C-state에 빠지지 않음
14Thermalturbostat --show CoreTmp,PkgTmp패키지 온도 <85°C
15blk-mq 큐 분포cat /sys/block/nvme0n1/mq/*/cpu_list큐가 NVMe NUMA 코어에 고르게 분포
16RCU 콜백cat /sys/kernel/debug/rcu/*/rcudataDPDK 코어에서 콜백 누적 없음
#!/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=== 진단 완료 ==="
우선순위별 해결 순서: 간섭 원인을 찾았다면 아래 순서로 해결하는 것이 가장 효과적입니다.
  1. PCIe 토폴로지 분리 — 하드웨어 수준 경합 제거 (가장 큰 효과)
  2. NUMA 정렬 — NIC·NVMe·DPDK 코어를 같은 NUMA 노드에 배치
  3. CPU 코어/IRQ 분리isolcpus + IRQ affinity 설정
  4. Hugepage 최적화 — 노드별 균등 할당, 최소량 사용
  5. IOMMU 튜닝iommu=pt, IOMMU 그룹 분리
  6. LLC/메모리 대역폭 격리 — Intel CAT/MBA (RDT) 적용
  7. 전원/스케줄러 튜닝 — C-state 제한, nohz_full, rcu_nocbs
  8. 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 권한전용 디렉터리, 최소 권한, 충분한 memlockrootless 실행과 실패 원인 분리를 쉽게 합니다.
NUMA 정렬NIC, 워커 코어, mempool을 같은 노드에 둠크로스소켓 지연과 IOTLB 비용을 줄입니다.
호환성 고정OVS/DPDK/펌웨어 조합을 하나의 단위로 관리ABI와 PMD capability 차이로 인한 현장 편차를 줄입니다.

자주 보는 증상과 원인

증상원인조치
cannot open /proc/self/pagemapPA 모드 전제 또는 물리 주소 접근 권한 부족--iova-mode=va로 강제하거나 공식 문서가 요구하는 capability를 부여
VFIO group is not viableIOMMU 그룹 안에 다른 함수/브리지 디바이스가 남아 있음그룹 전체를 확인하고 같은 그룹의 불필요 디바이스를 커널에서 떼거나 ACS 토폴로지를 재검토
VFIO_IOMMU_MAP_DMA failedmemlock 부족 또는 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 모드: 커널 문서와 DPDK 문서는 모두 no-IOMMU 경로를 unsafe로 취급합니다. 테스트 랩에서만 쓰고, 운영 환경에서는 디바이스가 호스트 메모리 전체를 DMA로 건드릴 수 있다는 전제를 항상 기억해야 합니다.

보안 공격 표면과 위협 모델

DPDK는 커널 네트워크 스택을 우회하므로 커널이 제공하는 보안 계층(Netfilter, SELinux, seccomp, 네임스페이스)을 사용할 수 없습니다. 따라서 DPDK 애플리케이션 자체가 신뢰 경계(trust boundary)가 되며, 공격 표면을 명시적으로 인식하고 완화해야 합니다.

DPDK 보안 공격 표면 외부 네트워크 악의적 패킷 주입 NIC (DMA 엔진) DMA 범위 공격 DPDK 애플리케이션 파싱 취약점, 메모리 오염 mbuf 오버플로우 공유 메모리 멀티프로세스 격리 주요 위협 벡터 ① DMA 공격 (IOMMU 미사용 시) UIO/no-IOMMU: 악성 NIC 펌웨어가 호스트 메모리 전체에 DMA 가능 → 커널 메모리 변조, 권한 상승 ② 악성 패킷 파싱 취약점 Netfilter/conntrack 없이 raw 패킷을 직접 파싱 → 버퍼 오버플로우, 정수 오버플로우, 포맷 스트링 공격 ③ Hugepage 정보 유출 hugepage 파일 권한 미설정 → 다른 프로세스가 패킷 데이터 열람 가능, /proc/self/pagemap 물리 주소 노출 ④ Telemetry/UNIX 소켓 노출 DPDK telemetry 소켓 권한 미설정 → 비인가 사용자가 런타임 상태 조회·조작 가능 ⑤ 멀티프로세스 공유 메모리 ⑥ Side-channel (Spectre/Meltdown) busy-poll + 유저 공간 NIC 접근: 캐시 타이밍 공격에 노출, KPTI/Retpoline 미적용 ⑦ Supply chain (PMD/드라이버) 서드파티 PMD, NIC 펌웨어 업데이트: 신뢰할 수 없는 바이너리가 DMA 경로에 직접 접근 ⑧ DoS (리소스 고갈) mempool 고갈, descriptor ring 가득 참, 과도한 flow rule → 정상 트래픽 처리 불가 ⑨ VFIO 그룹 탈취 같은 IOMMU 그룹 내 다른 디바이스를 통해 격리 우회 가능성
위협완화 방법운영 체크
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-channelDPDK 코어에서 타 워크로드 격리, CPU 취약점 패치 적용lscpu vulnerability 항목 확인
펌웨어 공급망NIC 펌웨어 서명 검증, 공식 소스만 사용ethtool -i 펌웨어 버전 추적
DoSmempool 여유 모니터링, flow rule 수 제한, rate-limitrx_nombuf xstat 감시
IOMMU 그룹ACS 지원 확인, 불필요 디바이스 분리ls /sys/kernel/iommu_groups/*/devices/
DPDK CVE 사례: 과거 DPDK vhost-user 구현에서 가상 큐 크기 검증 누락으로 인한 힙 오버플로우(CVE-2020-10722~10726), rte_flow 규칙 처리 중 OOB 읽기 등의 취약점이 보고되었습니다. 유저 공간 라이브러리라고 해서 보안 감사에서 제외하면 안 됩니다.
DPDK 학습 순서 권장:
  • 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 등 고급 프레임워크
관련 문서:
필수 관련 문서: 참고 문서: