DPDK

DPDK(Data Plane Development Kit) 고성능 패킷 처리 구조를 심층 분석합니다. EAL 초기화와 hugepage/NUMA 메모리 모델, PMD poll-mode 루프, rte_mbuf/ring/mempool 데이터 경로, VFIO/UIO 장치 바인딩, OVS-DPDK·VPP·AF_XDP 연계 시나리오, 초저지연 환경에서의 코어 배치·버스트·큐 튜닝과 운영 디버깅 포인트까지 실무 중심으로 다룹니다.

전제 조건: 네트워크 스택네트워크 디바이스 드라이버 문서를 먼저 읽으세요. 고성능 패킷 경로는 큐 구조, 메모리 배치, 드롭 위치가 성능을 좌우하므로 드라이버 경계를 먼저 이해하는 것이 중요합니다.
일상 비유: 이 주제는 고속 톨게이트 차선 분리와 비슷합니다. 일반 차선(커널 스택)과 하이패스 차선(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를 읽을 때 데이터가 아직 메모리에 쓰여지지 않은 "스pekulatif" 읽기를 방지합니다.
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이벤트 기반 패킷 분배

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 증가)

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.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, &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 영역에 접근 가능 */

패킷 처리 파이프라인 모델

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);

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으로 버퍼 오프셋 전달, 데이터 복사는 생략

OVS-DPDK (Open vSwitch + DPDK)

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

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

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 오버헤드 없이 이벤트 분배

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

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-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 학습 순서 권장:
  • 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 등 고급 프레임워크
관련 문서:
필수 관련 문서: 참고 문서: