Module 08 — DPDK EAL Initialization

Reference code — requires DPDK installed. Read the code as a reference when building a real DPDK application. The concepts and API calls are production-accurate.

What you learn

How to initialize the DPDK Environment Abstraction Layer (rte_eal_init), build an EAL argument vector programmatically from config, assign CPU cores to roles (RX / TX / WORKER), launch per-lcore functions, and shut down cleanly.

This is the first code that runs in every DPDK application after the application-level setup (config, logger). Everything in subsequent modules — mempools, ports, rings, Hyperscan — only works after rte_eal_init succeeds.


What rte_eal_init actually does

rte_eal_init(argc, argv)
  │
  ├─ Locks hugepages
  │    mmap() pre-allocated 2MB pages into the process address space.
  │    These are pinned in physical memory — the OS never pages them out.
  │    All rte_mbuf packet buffers live here (zero kernel allocation at runtime).
  │
  ├─ Sets CPU affinity
  │    Each lcore in the list is pinned to a physical CPU core via
  │    pthread_setaffinity_np(). From this point on, the OS never
  │    migrates these threads — they run on their assigned physical core only.
  │
  ├─ Initialises per-lcore memory
  │    rte_malloc zones per NUMA socket.
  │
  ├─ Probes PCI devices (NICs)
  │    Finds NIC devices bound with dpdk-devbind --bind=vfio-pci.
  │    DPDK takes exclusive ownership — the kernel network stack can no
  │    longer see this NIC.
  │
  └─ Returns: number of EAL args consumed

Where this fits in the real application

main()
  │
  ├─► config_load()           (Module 01)
  ├─► logger_init()           (Module 02)
  │
  ├─► build_eal_args()        ← reads cores, socket_mem from config
  ├─► rte_eal_init()          ← THIS MODULE
  │
  ├─► rte_pktmbuf_pool_create()   (Module 09)
  ├─► port_init()                 (Module 10)
  ├─► hs_init_global_scratch() (Module 15/16)
  ├─► kafka_init()                (Module 19/20)
  ├─► rte_ring_create()           (Module 03 concept)
  │
  ├─► assign_lcore_roles()    ← THIS MODULE
  ├─► rte_eal_remote_launch() ← THIS MODULE (one call per worker lcore)
  │
  ├─► main lcore control loop (Kafka poll, stats, OAM)
  │
  └─► rte_eal_cleanup()       ← THIS MODULE

Files

File Purpose
eal_init.c Full EAL init, lcore topology, role assignment, launch, shutdown
Makefile DPDK build via pkg-config (RedHat/Rocky compatible)

Setup (RedHat / Rocky Linux)

# Install DPDK
dnf install dpdk dpdk-devel

# Allocate hugepages (2MB pages, 512 = 1 GB)
echo 512 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# Mount hugepage filesystem (if not auto-mounted)
mount -t hugetlbfs nodev /dev/hugepages

# For NIC binding (optional — needed for real packet IO)
dpdk-devbind --status
dpdk-devbind --bind=vfio-pci <PCI_addr>

# Build and run
make
sudo ./eal_init

Key concepts in the code

1. EAL arg vector — built by the app, not the shell

/* Instead of running: ./dp_app -l 0-7 --socket-mem 2048 */
/* The app builds argv[] programmatically from config: */

eal_add_arg("dp_app");
eal_add_arg("-l");
eal_add_arg(config_get_string(&cfg, "eal", "cores", "0-3"));
eal_add_arg("--socket-mem");
eal_add_arg(config_get_string(&cfg, "eal", "socket_mem", "1024"));
...
ret = rte_eal_init(eal_argc, eal_argv);

2. Lcore vs CPU core

A lcore is DPDK’s abstraction for a logical CPU core. Lcore IDs are DPDK’s numbering, not the OS’s.

rte_lcore_count()              /* total lcores handed to DPDK */
rte_get_main_lcore()           /* main thread's lcore ID      */
rte_lcore_to_socket_id(id)     /* which NUMA socket this lcore is on */
RTE_LCORE_FOREACH_WORKER(id)   /* iterate all non-main lcores */

3. Role assignment — why it matters

NIC port 0 queue 0 → RX lcore 1 → rte_ring → Worker lcores 3,4,5,6
                                                      ↓
                   TX lcore 2 ← rte_ring ←────────────┘

4. rte_eal_remote_launch vs pthread_create

/* DPDK way — lcore already pinned to CPU core, just start the function */
rte_eal_remote_launch(lcore_worker_func, &lcore_info[id], id);

/* NOT used in DPDK for worker cores — would create an unpinned thread */
pthread_create(&tid, NULL, lcore_worker_func, &lcore_info[id]);

5. rte_pause() — the polling spin hint

while (running) {
    nb_rx = rte_eth_rx_burst(port, queue, mbufs, BURST);
    if (nb_rx == 0) {
        rte_pause();   /* x86 PAUSE instruction — reduces power, improves HT */
        continue;
    }
    /* process mbufs */
}

Never use sleep() or usleep() in an lcore function. Sleeping yields the core to the OS scheduler, potentially stalling for milliseconds — during which time the NIC RX ring fills and packets are dropped.

6. Graceful shutdown sequence

SIGTERM received
  → signal_handler sets force_quit = 1
  → main lcore exits control loop
  → sets lcore_info[id].running = 0 for each worker
  → worker lcores see running=0, exit their loops
  → rte_eal_wait_lcore(id) for each worker  ← must call before cleanup
  → flush CDR to Kafka
  → rte_eal_cleanup()
  → exit(0)

Common errors

Error Cause Fix
No hugepages available Hugepages not allocated echo 512 > /sys/.../nr_hugepages
Cannot init EAL: invalid core list lcore range invalid Check CPU count with nproc
EAL: No probed ethernet devices NIC not bound to vfio-pci dpdk-devbind --bind=vfio-pci <addr>
rte_eal_init: Permission denied Not running as root sudo ./eal_init or set capabilities
Cannot create lock on hugepage file Another DPDK process running Kill other DPDK processes first

Next module

Module 09 — Mempool + mbuf: Create an rte_mempool for packet buffers (rte_mbuf), understand the mbuf lifecycle (alloc → fill → process → free), and learn why mempools exist.


Source files

File Download
eal_init.c eal_init.c
Makefile Makefile