00 DPDK nedir?
Data Plane Development Kit (DPDK), Intel tarafından 2010'da başlatılmış ve 2017'de Linux Foundation'a devredilmiş açık kaynak bir kütüphane ve sürücü koleksiyonudur. Temel amacı, ağ paketlerini Linux kernel network stack'ini tamamen atlayarak (kernel bypass) doğrudan kullanıcı alanından işlemektir.
Neden kernel bypass?
Geleneksel Linux network stack'inde bir paketin serüveni şöyledir: NIC kesmesi → kernel IRQ handler → socket buffer kopyalama → sistem çağrısı geçişi → kullanıcı alanı. Bu yol her paket için yüzlerce CPU döngüsü tüketir, context switch ve bellek kopyalama ek yük getirir. 10GbE hattında ~14.8 milyon paket/saniye hedeflendiğinde bu yük kabul edilemez hale gelir.
Geleneksel Linux:
NIC → Kernel Driver → SKB Alloc → TCP/IP Stack → Socket → Memcpy → Uygulama
[kesme] [kopyalama] [işleme] [syscall] [kopyalama]
DPDK:
NIC → PMD (user-space poll) → rte_mbuf → Uygulama
[polling, sıfır kopya] [DMA, zero-copy]
Kullanım alanları
01 Mimari: EAL, lcore, hugepage, PMD
DPDK'nın temel mimarisi birkaç temel bileşenden oluşur. Her bileşen belirli bir sorunu çözmek için tasarlanmıştır.
EAL — Environment Abstraction Layer
EAL, DPDK uygulamalarının çalıştığı temel çerçevedir. CPU affinity, bellek yönetimi, PCI cihaz erişimi ve loglama gibi işletim sistemi bağımlı işlemleri soyutlar. Her DPDK uygulaması rte_eal_init() çağrısıyla EAL'ı başlatmak zorundadır.
┌────────────────────────────────────────────────────┐
│ DPDK Uygulama │
├────────────────────────────────────────────────────┤
│ rte_ethdev rte_mbuf rte_ring rte_mempool │
│ (Ethernet) (buffer) (queue) (memory) │
├────────────────────────────────────────────────────┤
│ EAL — Environment Abstraction Layer │
│ lcore mgmt │ hugepage alloc │ PCI probe │
├───────────────┬──────────────────────────────────-──┤
│ PMD (igb) │ PMD (mlx5) │ PMD (virtio) │
│ user-space │ user-space │ user-space │
│ NIC driver │ NIC driver │ NIC driver │
└───────────────┴───────────────┴────────────────────-┘
lcore — Logical Core
DPDK, ağ işleme iş yükünü belirli CPU çekirdeklerine (lcore) sabitler. Bu çekirdekler polling modunda çalışır; kesme bekleme yerine sürekli RX kuyruğunu kontrol eder. taskset veya EAL parametreleriyle belirlenen bu çekirdekler hiçbir zaman OS scheduler'a geri verilmemelidir.
# lcore 1 ve 2'yi DPDK'ya tahsis et, lcore 0'ı master olarak kullan:
./dpdk-app -l 0-2 -n 4
# CPU izolasyonu — kernel cmdline:
isolcpus=1,2 nohz_full=1,2 rcu_nocbs=1,2
Memory model ve hugepage
DPDK, 4KB standart sayfa boyutu yerine 2MB veya 1GB hugepage kullanır. Bu sayede TLB miss sayısı dramatik biçimde azalır. Büyük paket tamponları için TLB thrashing kritik bir darboğaz olabilir.
02 Hugepage kurulumu
DPDK çalışmaya başlamadan önce hugepage'ler tahsis edilmelidir. Bu tahsis sistem başlangıcında veya çalışma zamanında yapılabilir.
Çalışma zamanı hugepage tahsisi
# 2MB hugepage — mevcut durumu kontrol et
cat /proc/meminfo | grep -i huge
# HugePages_Total: 0
# HugePages_Free: 0
# Hugepagesize: 2048 kB
# 1024 adet 2MB hugepage tahsis et (= 2GB)
echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# NUMA sistemlerde node bazında tahsis:
echo 512 | sudo tee /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 512 | sudo tee /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# 1GB hugepage (destekleniyorsa):
echo 4 | sudo tee /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
Kalıcı hugepage (kernel cmdline)
# /etc/default/grub içinde GRUB_CMDLINE_LINUX'a ekle:
GRUB_CMDLINE_LINUX="default_hugepagesz=2M hugepagesz=2M hugepages=2048 \
hugepagesz=1G hugepages=4 \
isolcpus=1-7 nohz_full=1-7"
sudo update-grub
sudo reboot
Hugepage dosya sistemi mount
# hugetlbfs mount — DPDK bunu otomatik yapar ama elle de yapılabilir:
sudo mkdir -p /dev/hugepages
sudo mount -t hugetlbfs hugetlbfs /dev/hugepages
# 1GB hugepage için ayrı mount point:
sudo mkdir -p /dev/hugepages1G
sudo mount -t hugetlbfs -o pagesize=1G hugetlbfs /dev/hugepages1G
# /etc/fstab'a ekle:
echo "hugetlbfs /dev/hugepages hugetlbfs defaults 0 0" | sudo tee -a /etc/fstab
dpdk-hugepages.py yardımcı scripti
# DPDK kurulumunda gelir:
sudo dpdk-hugepages.py --setup 2G # 2GB 2M hugepage
sudo dpdk-hugepages.py --show # mevcut durumu göster
sudo dpdk-hugepages.py --reserve 1G --pagesize 1G # 1G hugepage
Hugepage tahsisi sistem çalışırken yapıldığında bellek fragmantasyonu nedeniyle başarısız olabilir. Kritik prodüksiyon sistemleri için hugepage tahsisini boot time'da hugepages= kernel parametresiyle yapın. NUMA sistemlerde her node'a eşit dağıtım yapın.
03 PMD sürücüler
Poll Mode Driver (PMD), donanıma özgü NIC erişimini sağlayan kullanıcı alanı sürücüsüdür. Kernel sürücüsü devre dışı bırakılır, NIC doğrudan kullanıcı alanına bağlanır.
Temel PMD'ler ve özellikleri
| PMD | NIC | Mekanizma | Özellik |
|---|---|---|---|
igb / e1000 | Intel 1GbE | UIO/VFIO | Geliştirme ve test |
ixgbe | Intel 10GbE X540/X550 | UIO/VFIO | Prodüksiyon, SR-IOV |
i40e | Intel XL710/X710 40GbE | VFIO | PF/VF, Flow Director |
ice | Intel E810 100GbE | VFIO | En yeni Intel NIC |
mlx5 | Mellanox/NVIDIA 25/100GbE | Verbs (libibverbs) | Kernel sürücü ile birlikte çalışır |
virtio | KVM virtio-net | Virtio spec | Sanallaştırma, QEMU/KVM |
vfio-pci | Tüm VFIO uyumlu | VFIO-PCI | IOMMU koruması, önerilen |
af_xdp | Kernel AF_XDP socket | AF_XDP | Kernel sürücü tutulur, kısmi bypass |
vfio-pci ile NIC bağlama
# 1. Mevcut NIC'i tespit et
lspci | grep Ethernet
# 0000:03:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit
# 2. Kernel sürücüyü kaldır
sudo modprobe vfio-pci
PCI_ADDR="0000:03:00.0"
sudo dpdk-devbind.py --status
sudo dpdk-devbind.py --bind=vfio-pci $PCI_ADDR
# 3. IOMMU etkinleştirme (kernel cmdline):
# Intel: intel_iommu=on iommu=pt
# AMD: amd_iommu=on iommu=pt
# 4. Bağlamayı doğrula
sudo dpdk-devbind.py --status | grep vfio
mlx5 özel durum
# Mellanox mlx5 için özel durum: kernel sürücü kalmaya devam eder
# OFED veya inbox mlx5_core sürücüsü gerekir
sudo apt install rdma-core libibverbs-dev libmlx5-dev
# Bağlama GEREKMEZ — mlx5 PMD kernel sürücüyle konuşur:
sudo dpdk-devbind.py --status
# şu şekilde görünür: drv=mlx5_core (kullanılabilir: mlx5)
04 rte_mbuf — paket tamponu
rte_mbuf, DPDK'nın temel paket tamponu yapısıdır. Her ağ paketi bir rte_mbuf'ta saklanır. Sıfır kopya (zero-copy) tasarımı ile donanım DMA'sından doğrudan kullanıcı alanı işlemine gider.
rte_mbuf yapısı
┌─────────────────────────────────────────────────────────┐
│ rte_mbuf metadata │
│ next│pool│buf_addr│data_off│pkt_len│data_len│ol_flags │
├─────────────────────────────────────────────────────────┤
│ headroom (RTE_PKTMBUF_HEADROOM = 128B) │
├─────────────────────────────────────────────────────────┤
│ data_off │
│ ┌───────────────────────────────────────────────┐ │
│ │ Ethernet Header │ IP Header │ TCP │ Payload │ │
│ └───────────────────────────────────────────────┘ │
│ ← data_len → │
├─────────────────────────────────────────────────────────┤
│ tailroom │
└─────────────────────────────────────────────────────────┘
Total buf_len = RTE_MBUF_DEFAULT_BUF_SIZE (2048B)
mempool oluşturma
#include <rte_mbuf.h>
#include <rte_mempool.h>
#define NUM_MBUFS 8191 /* 2^13 - 1, Mersenne sayısı önerilir */
#define MBUF_CACHE_SIZE 250
#define MBUF_DATA_SIZE RTE_MBUF_DEFAULT_BUF_SIZE /* 2048 */
struct rte_mempool *mbuf_pool;
mbuf_pool = rte_pktmbuf_pool_create(
"MBUF_POOL", /* isim */
NUM_MBUFS, /* eleman sayısı */
MBUF_CACHE_SIZE, /* per-lcore cache */
0, /* private data boyutu */
MBUF_DATA_SIZE, /* veri alan boyutu */
rte_socket_id() /* NUMA node */
);
if (mbuf_pool == NULL)
rte_exit(EXIT_FAILURE, "mempool oluşturulamadı\n");
mbuf erişim makroları
struct rte_mbuf *m;
/* Veri pointer'ı */
void *data = rte_pktmbuf_mtod(m, void *);
/* Belirli offset'ten cast */
struct rte_ether_hdr *eth =
rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
struct rte_ipv4_hdr *ip =
rte_pktmbuf_mtod_offset(m, struct rte_ipv4_hdr *,
sizeof(struct rte_ether_hdr));
/* Paket uzunluğu */
uint16_t pkt_len = rte_pktmbuf_pkt_len(m);
/* Offload flag'leri (checksum, VLAN, tunnel) */
uint64_t flags = m->ol_flags;
if (flags & RTE_MBUF_F_RX_IP_CKSUM_BAD)
/* IP checksum hatalı */;
/* scatter-gather: zincirleme mbuf'lar */
struct rte_mbuf *seg = m;
while (seg != NULL) {
/* her segmenti işle */
seg = seg->next;
}
/* Serbest bırakma */
rte_pktmbuf_free(m);
05 rte_ring — kilitsiz kuyruk
rte_ring, DPDK'nın lock-free (kilitsiz) FIFO kuyruk implementasyonudur. lcore'lar arası mbuf pointer aktarımında kullanılır. CAS (Compare-and-Swap) atomic operasyonlarıyla thread-safe çalışır.
Ring türleri
#include <rte_ring.h>
/* Ring oluşturma */
struct rte_ring *ring;
ring = rte_ring_create(
"PKT_RING", /* isim */
1024, /* boyut — 2^N olmalı */
rte_socket_id(), /* NUMA socket */
RING_F_SP_ENQ | /* Single Producer */
RING_F_SC_DEQ /* Single Consumer */
);
/* Enqueue (paket gönder) */
struct rte_mbuf *m;
int ret = rte_ring_enqueue(ring, (void *)m);
if (ret == -ENOBUFS)
/* ring dolu — paketi at */
rte_pktmbuf_free(m);
/* Bulk enqueue — daha verimli */
struct rte_mbuf *burst[32];
unsigned int sent = rte_ring_enqueue_burst(ring,
(void **)burst, 32, NULL);
/* Dequeue */
struct rte_mbuf *rx_mbuf;
ret = rte_ring_dequeue(ring, (void **)&rx_mbuf);
/* Bulk dequeue */
unsigned int rcvd = rte_ring_dequeue_burst(ring,
(void **)burst, 32, NULL);
/* Doluluk kontrolü */
unsigned count = rte_ring_count(ring);
unsigned free = rte_ring_free_count(ring);
Ring boyutu her zaman 2'nin kuvveti olmalıdır. Modulo operasyonu yerine bitmask kullanımı (count & (size-1)) performansı artırır. NUMA sistemlerde ring ve onu kullanan lcore'lar aynı NUMA node'unda olmalıdır; aksi takdirde remote memory access gecikmesi oluşur.
06 Temel DPDK uygulaması
Minimal bir DPDK uygulamasının iskelet kodu üç aşamadan oluşur: EAL başlatma, port kurulumu ve ana paket döngüsü.
EAL başlatma ve port kurulumu
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#define RX_RING_SIZE 1024
#define TX_RING_SIZE 1024
#define BURST_SIZE 32
static const struct rte_eth_conf port_conf = {
.rxmode = {
.mq_mode = RTE_ETH_MQ_RX_RSS, /* RSS ile çoklu kuyruk */
},
.txmode = {
.mq_mode = RTE_ETH_MQ_TX_NONE,
},
.rx_adv_conf.rss_conf = {
.rss_key = NULL,
.rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP,
},
};
int port_init(uint16_t port, struct rte_mempool *mbuf_pool) {
struct rte_eth_dev_info dev_info;
int ret;
if (!rte_eth_dev_is_valid_port(port))
return -1;
rte_eth_dev_info_get(port, &dev_info);
/* Port yapılandırması */
ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
if (ret != 0) return ret;
/* MTU ayarla */
rte_eth_dev_set_mtu(port, 9000); /* Jumbo frame */
/* RX queue kur */
ret = rte_eth_rx_queue_setup(port, 0, RX_RING_SIZE,
rte_eth_dev_socket_id(port), NULL, mbuf_pool);
if (ret < 0) return ret;
/* TX queue kur */
ret = rte_eth_tx_queue_setup(port, 0, TX_RING_SIZE,
rte_eth_dev_socket_id(port), NULL);
if (ret < 0) return ret;
/* Port başlat */
ret = rte_eth_dev_start(port);
if (ret < 0) return ret;
/* Promiscuous mode */
rte_eth_promiscuous_enable(port);
return 0;
}
Ana paket döngüsü (polling loop)
static int lcore_main(void *arg) {
uint16_t port;
struct rte_mbuf *bufs[BURST_SIZE];
uint16_t nb_rx, nb_tx;
printf("lcore %u başlıyor\n", rte_lcore_id());
RTE_ETH_FOREACH_DEV(port) {
if (rte_eth_dev_socket_id(port) >= 0 &&
rte_eth_dev_socket_id(port) != (int)rte_socket_id())
printf("UYARI: port %u uzak NUMA node'unda\n", port);
}
/* Sonsuz polling döngüsü */
for (;;) {
RTE_ETH_FOREACH_DEV(port) {
/* RX burst — BURST_SIZE'a kadar paket al */
nb_rx = rte_eth_rx_burst(port, 0, bufs, BURST_SIZE);
if (nb_rx == 0) continue;
/* Paketleri işle (bu örnekte basitçe ilet) */
uint16_t out_port = port ^ 1; /* 0→1, 1→0 */
/* TX burst */
nb_tx = rte_eth_tx_burst(out_port, 0, bufs, nb_rx);
/* İletilemeyen paketleri serbest bırak */
if (unlikely(nb_tx < nb_rx)) {
for (uint16_t i = nb_tx; i < nb_rx; i++)
rte_pktmbuf_free(bufs[i]);
}
}
}
return 0;
}
int main(int argc, char *argv[]) {
struct rte_mempool *mbuf_pool;
uint16_t nb_ports, portid;
/* EAL başlat */
int ret = rte_eal_init(argc, argv);
if (ret < 0) rte_exit(EXIT_FAILURE, "EAL başlatılamadı\n");
nb_ports = rte_eth_dev_count_avail();
if (nb_ports < 2 || (nb_ports & 1))
rte_exit(EXIT_FAILURE, "Çift sayıda port gerekli\n");
mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL",
NUM_MBUFS * nb_ports, MBUF_CACHE_SIZE, 0,
RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
/* Her port için başlatma */
RTE_ETH_FOREACH_DEV(portid)
port_init(portid, mbuf_pool);
/* lcore'lara görev dağıt */
rte_eal_mp_remote_launch(lcore_main, NULL, CALL_MAIN);
rte_eal_mp_wait_lcore();
rte_eal_cleanup();
return 0;
}
07 Pipeline modeli
Basit burst modeline ek olarak DPDK, P4 benzeri bir pipeline modeli sunar. Bu modelde paket işleme adımları (port → table → action) birer pipeline bileşenine dönüştürülür.
Pipeline bileşenleri
Basit LPM tablosu örneği
#include <rte_lpm.h>
struct rte_lpm *lpm;
struct rte_lpm_config config = {
.max_rules = 1 << 16, /* 65536 kural */
.number_tbl8s = 1 << 8,
.flags = 0,
};
lpm = rte_lpm_create("IPV4_LPM", rte_socket_id(), &config);
/* Kural ekle: 10.0.0.0/8 → next_hop 1 */
uint32_t ip = RTE_IPV4(10, 0, 0, 0);
rte_lpm_add(lpm, ip, 8, 1);
/* 192.168.1.0/24 → next_hop 2 */
ip = RTE_IPV4(192, 168, 1, 0);
rte_lpm_add(lpm, ip, 24, 2);
/* Lookup */
uint32_t next_hop;
struct rte_ipv4_hdr *ipv4_hdr = /* ... */;
int rc = rte_lpm_lookup(lpm, rte_be_to_cpu_32(ipv4_hdr->dst_addr),
&next_hop);
if (rc == 0)
/* next_hop port'una ilet */
else
/* drop */;
08 Pratik: L2 forwarding uygulaması
Bu bölümde iki NIC portu arasında wire-speed L2 forwarding yapan tam bir DPDK uygulaması geliştirilecektir. QEMU virtio portlarıyla test edilebilir.
Makefile
APP = l2fwd
# DPDK kurulum yolu
RTE_SDK ?= /usr/local/share/dpdk
RTE_TARGET ?= x86_64-native-linux-gcc
CFLAGS += -O3 -march=native
LDFLAGS += -lrte_eal -lrte_mbuf -lrte_ethdev -lrte_mempool \
-lrte_ring -lrte_kvargs -lrte_pci -lrte_bus_pci
# pkg-config kullanımı (DPDK 20.11+)
CFLAGS += $(shell pkg-config --cflags libdpdk)
LDFLAGS += $(shell pkg-config --libs libdpdk)
$(APP): l2fwd.c
gcc $(CFLAGS) -o $@ $< $(LDFLAGS)
QEMU ile test ortamı kurma
# iki virtio-net interface ile QEMU başlatma:
sudo qemu-system-x86_64 \
-enable-kvm \
-m 4G \
-cpu host \
-smp 4 \
-mem-path /dev/hugepages \
-mem-prealloc \
-netdev tap,id=net0,ifname=tap0,script=no,downscript=no \
-device virtio-net-pci,netdev=net0,id=nic0 \
-netdev tap,id=net1,ifname=tap1,script=no,downscript=no \
-device virtio-net-pci,netdev=net1,id=nic1 \
-drive file=ubuntu.img,format=qcow2 \
-nographic
L2 forwarding çalıştırma ve performans ölçümü
# Hugepage tahsis et
echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# NIC'leri DPDK'ya bağla
sudo modprobe vfio-pci
sudo dpdk-devbind.py --bind=vfio-pci 0000:00:03.0 0000:00:04.0
# L2 forwarding başlat (lcore 0: master, lcore 1-2: packet processing)
sudo ./l2fwd -l 0-2 -n 4 -- -p 0x3 -T 1
# Çıktı:
# Port statistics ====================================
# Statistics for port 0 ------------------------------
# Packets sent: 10000000
# Packets received: 10000000
# Packets dropped: 0
# Port statistics ====================================
# Aggregate statistics ==============================
# Total packets sent: 20000000
# Total packets received: 20000000
# Total packets dropped: 0
Performans tuning ipuçları
isolcpus= + nohz_full= — timer interrupt'larını uzaklaştır, %100 polling yaptırnumactl --cpubind=0 --membind=0RTE_ETH_TX_OFFLOAD_IPV4_CKSUM CPU yükünü azaltırDPDK uygulamaları root ayrıcalığı gerektirdiğinden prodüksiyon ortamlarında dikkatli olun. --user flag'i ile non-root kullanım mümkündür ancak VFIO-noIOMMU veya özel grup/capability ayarları gerektirir. Container ortamlarında --privileged yerine minimum gerekli capability'leri (CAP_SYS_ADMIN, CAP_NET_ADMIN) tercih edin.