dpdk探究1-理解dpdk的运行逻辑

2023-05-16

DPDK介绍

DPDK主要功能:利用IA(intel architecture)多核处理器进行高性能数据包处理

Linux下传统的网络设备驱动包处理的动作可以概括如下:

  • 数据包到达网卡设备
  • 网卡设备依据配置进行DMA操作
  • 网卡发送中断,唤醒处理器
  • 驱动软件填充读写缓冲区数据结构
  • 数据报文到达内核协议栈,进行高层处理
  • 如果最终应用在用户态,数据从内核搬移到用户态
  • 如果最终应用在内核态,在内核继续进行

频繁的中断会降低系统处理数据包的速度。

DPDK可以很好的在英特尔架构下执行高性能网络数据包处理,主要使用了以下技术:

  • 轮询
    • 避免中断上下文切换的开销
  • 用户态驱动(UIO驱动)
    • 规避了不必要的内存拷贝,避免了系统调用。
  • 亲和性与独占
    • DPDK工作在用户态,线程的调度依然依赖内核,利用线程的CPU亲和性绑定的方式,特定任务可以被指定只在某个核上工作
  • 降低访存开销
    • 如内存大页,内存多通道的交错访问,NUMA系统的使用
  • 软件调优
    • 内存对齐,数据预取,避免跨cache行共享
  • 利用IA新硬件技术
  • 充分挖掘网卡的潜能

UIO驱动简介

UIO提供了用户态驱动开发的框架,主要是由于驱动依赖的内核函数和宏因内核版本变化,导致驱动可能也需要改。所以改为用户态驱动来完成任务。

dpdk中的驱动需要定期检查设备是否有中断产生,并不是用来收发数据。另外通过mmap来操作设备的设备内存。UIO框架本身要处理设备的中断,中断只能在内核态处理,uio的中断处理函数也只是增加中断的计数而已

mmap可以处理物理内存映射,逻辑内存,内核虚拟内存映射。 UIO也是通过mmap将设备的内存映射到用户空间,当然用户态也可以通过/sys/class/uio/uioX/maps/mapX来实现对设备内存的访问,所以发送和接受数据是通过操作设备的内存来完成的。

DPDK框架简介

  1. 核心库Core Libs:提供系统抽象,打野内存,缓存池,定时器和无锁环等基础组件
  2. PMD库:提供全用户态的驱动,以便通过轮询和线程板顶得到极高的网络吞吐
  3. Classify库:支持精确匹配,最长匹配和通配符匹配,提供常用包处理的查表操作
  4. QoS库:提供网络服务质量相关组件,如限速和调度

解读简单的示例程序,初步了解DPDK

解读helloworld程序,探究DPDK运行思路

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/queue.h>
#include <rte_memory.h>
#include <rte_launch.h>
#include <rte_eal.h>
#include <rte_per_lcore.h>
#include <rte_lcore.h>
#include <rte_debug.h>
static int
lcore_hello(__attribute__((unused)) void *arg)
{
        unsigned lcore_id;
        lcore_id = rte_lcore_id();  //获得当前核号
        printf("hello from core %u\n", lcore_id);
        return 0;
}
int
main(int argc, char **argv)
{
        int ret;
        unsigned lcore_id;
        ret = rte_eal_init(argc, argv); //EAL层的初始化
        if (ret < 0)
                rte_panic("Cannot init EAL\n");
        /* call lcore_hello() on every slave lcore */
        RTE_LCORE_FOREACH_SLAVE(lcore_id) {
                rte_eal_remote_launch(lcore_hello, NULL, lcore_id);
        }
        /* call it on master lcore too */
        lcore_hello(NULL);
        rte_eal_mp_wait_lcore();
        return 0;
}

首先看lcore_hello(),这个函数是运行在每个核上的回调函数,在主线程中进行调用,函数的参数中存在一个__attribute__((unused)),作用是让编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

主线程就是主函数,首先是EAL层的初始化rte_eal_init(argc, argv),这个初始化可以读取可执行程序运行时写入的系统参数(包括用什么核,用什么网卡,内存通道数量),具体参数可以参照DPDK官方网站。

int rte_eal_init(int argc, char ** argv);

这个函数最需要的参数是核心掩码,例如-c ffff代表十六个核,当想选择一部分核可以用-l,因为线程的分配需要核的信息。

函数通过读取入口参数,解析并保存为DPDK运行的系统信息,依赖这些信息,构建一个针对包处理设计的运行环境。

接下来是一个宏RTE_LCORE_FOREACH_SLAVE(int id),这个宏的作用是for循环遍历除主核(master core)之外的所有核:

for (i = rte_get_next_lcore(-1, 1, 0);                          \
         i<RTE_MAX_LCORE;                                           \
         i = rte_get_next_lcore(i, 1, 0))

rte_eal_remote_launch声明如下:

int rte_eal_remote_launch(lcore_function_t * f,
                            void * arg,
                            unsigned slave_id 
                         );

类似于多线程编程中的pthread_creat(),是在对应的逻辑核上运行相应的线程,线程的回调函数是f,参数是arg,运行在核号是slave_id的核上。

rte_eal_mp_wait_lcore()函数是等待所有的逻辑核(从核slave lcore)完成任务,类似于多线程编程的pthread_join(),这里所有的核心都完成工作后(从RUNNING切换到FINISH状态),状态变为WAIT状态。

If the slave lcore identified by the slave_id is in a FINISHED state, switch to the WAIT state. If the lcore is in RUNNING state, wait until the lcore finishes its job and moves to the FINISHED state.

这便是DPDK的基本运行思路,事实上,DPDK的所有程序都是这样的运行思路:

  1. 主核进行EAL层次的初始化,读取系统参数
  2. 主核读取其他必要的参数
  3. 主核依据读入的参数确定核数,网卡数,进行线程的启动,将对应的函数和参数传入其中
  4. 主核进行自己的工作(一般是定时打印程序状态)
  5. 所有程序结束(这里程序的结束一般是通过linux下的信号来进行的,一般而言,从核运行都是死循环,而信号的到来,如Ctrl+C,会按顺序结束相应的线程,再由主核接收到所有线程结束的消息,结束整个程序)

解读skeleton程序,探究DPDK最基本收发包逻辑

这是一个简单的单核收发包示例程序,对收入报文不做处理,直接进行转发,简单介绍一下代码:

主函数代码如下,可以看到主线程做的前期工作

int main(int argc, char *argv[])
{
    struct rte_mempool *mbuf_pool;
    unsigned nb_ports;
    uint8_t portid;

    int ret = rte_eal_init(argc, argv); //初始化EAL层
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

    argc -= ret;
    argv += ret;

    nb_ports = rte_eth_dev_count(); //读取网口数量,转发包要求网口数为偶数
    if (nb_ports < 2 || (nb_ports & 1))
        rte_exit(EXIT_FAILURE, "Error: number of ports must be even\n");

    //创建mbuf池,有了mbuf池就可以创建mbuf了
    mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
        MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

    if (mbuf_pool == NULL)
        rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

    //初始化所有网口
    for (portid = 0; portid < nb_ports; portid++)
        if (port_init(portid, mbuf_pool) != 0)
            rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu8 "\n",
                    portid);

    if (rte_lcore_count() > 1)
        printf("\nWARNING: Too many lcores enabled. Only 1 used.\n");

    lcore_main();   //主核进行的工作

    return 0;
}

网卡初始化函数为port_init(),对指定的端口设置队列数目,在收发两个方向上,基于端口和队列进行配置设置,缓冲区进行关联设置。

这里的初始化代码也是自行调用API编写:

static inline int port_init(uint8_t port, struct rte_mempool *mbuf_pool)
{
    struct rte_eth_conf port_conf = port_conf_default;
    //这里使用一个默认的单队列结构体进行队列的初始化
    const uint16_t rx_rings = 1, tx_rings = 1;  //收发队列数量(各为1)
    uint16_t nb_rxd = RX_RING_SIZE;// 1<<16,大小为64k
    uint16_t nb_txd = TX_RING_SIZE;//同上
    int retval;
    uint16_t q;

    if (port >= rte_eth_dev_count())
        return -1;

    //网口设置:配置网卡设备,参数包括网口,收发队列数目,配置结构体
    retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
    if (retval != 0)
        return retval;
    //检查Rx和Tx描述符(mbuf)的数量是否满足网卡的描述符限制,不满足将其调整为边界(改变其值)
    retval = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd);
    if (retval != 0)
        return retval;

    //队列初始化:对指定端口的某个队列,指定内存描述符数量,报文缓冲区,并配置队列
    for (q = 0; q < rx_rings; q++) {
        retval = rte_eth_rx_queue_setup(port, q, nb_rxd,
                rte_eth_dev_socket_id(port), NULL, mbuf_pool);
        if (retval < 0)
            return retval;
    }

    for (q = 0; q < tx_rings; q++) {
        retval = rte_eth_tx_queue_setup(port, q, nb_txd,
                rte_eth_dev_socket_id(port), NULL);
        if (retval < 0)
            return retval;
    }

    //初始化完成后启动网口 
    retval = rte_eth_dev_start(port);
    if (retval < 0)
        return retval;

    //检索网卡设备的MAC地址并存入addr中
    struct ether_addr addr;
    rte_eth_macaddr_get(port, &addr);
    printf("Port %u MAC: %02" PRIx8 " %02" PRIx8 " %02" PRIx8
               " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "\n",
            (unsigned)port,
            addr.addr_bytes[0], addr.addr_bytes[1],
            addr.addr_bytes[2], addr.addr_bytes[3],
            addr.addr_bytes[4], addr.addr_bytes[5]);

    //将网卡设置为混杂模式
    rte_eth_promiscuous_enable(port);

    return 0;
}

默认的队列初始化结构体如下,仅仅指定了最大包长度为以太网最大长度1518。

static const struct rte_eth_conf port_conf_default = {
    .rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN }   //前面加.代表指定成员进行初始化
};

初始化网卡之后,主核直接运行业务逻辑lcore_main()

static __attribute__((noreturn)) void lcore_main(void)
{
    const uint8_t nb_ports = rte_eth_dev_count();
    uint8_t port;

    //检测网口和运行线程是不是属于同一NUMA节点,加快运行速度
    for (port = 0; port < nb_ports; port++)
        if (rte_eth_dev_socket_id(port) > 0 && //网卡所在的NUMA套接字
                rte_eth_dev_socket_id(port) !=
                        (int)rte_socket_id())   //逻辑线程所在CPU的id(CPU和NUMA是对应的)
            printf("WARNING, port %u is on remote NUMA node to "
                    "polling thread.\n\tPerformance will "
                    "not be optimal.\n", port);

    printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n",
            rte_lcore_id());

    for (;;) {  //死循环
        //遍历网口
        for (port = 0; port < nb_ports; port++) {
            struct rte_mbuf *bufs[BURST_SIZE];  //一组mbuf集合,按照cache行,一次性最多收8个数据包(的mbuf地址)
            //收一组包,返回收到的包的个数,从网卡队列取包放到bufs数组中(传地址,零拷贝)
            const uint16_t nb_rx = rte_eth_rx_burst(port, 0,
                    bufs, BURST_SIZE);

            if (unlikely(nb_rx == 0))
                continue;

            //转发到相邻(port->port^1,即0->1,1->0,2->3,3->2)网口
            const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0,
                    bufs, nb_rx);

            //如果出现没有转发的数据包(我猜测是性能不够的原因),就要把没有转发的mbuf手动释放
            if (unlikely(nb_tx < nb_rx)) {
                uint16_t buf;
                for (buf = nb_tx; buf < nb_rx; buf++)
                    rte_pktmbuf_free(bufs[buf]);    //释放mbuf
            }
        }
    }
}

对于DPDK的收包和转发来说,都是一次处理多个数据包,原因是cache行的内存对齐可以一次处理多个地址,并且可以充分利用处理器内部的乱序执行和并行处理能力。

这就构成了最基本的DPDK收发包逻辑,不涉及任何硬件部分。

简要介绍L3fwd

这个样例是用来进行三层转发(即网络层转发,类似于路由器功能),数据包收入到系统中会查询IP报文头部,依据目标地址进行路由查找,发现目的网口,就修改IP头部,将报文从目的端口送出。路由查找有两种方式:1、基于目标IP地址的完全匹配;2、基于路由表的最长掩码匹配。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

dpdk探究1-理解dpdk的运行逻辑 的相关文章

  • dpdk探究1-理解dpdk的运行逻辑

    DPDK介绍 DPDK主要功能 xff1a 利用IA xff08 intel architecture xff09 多核处理器进行高性能数据包处理 Linux下传统的网络设备驱动包处理的动作可以概括如下 xff1a 数据包到达网卡设备网卡设
  • dpdk无锁队列

    这篇博客是从网上博客整理摘抄而来 xff0c 具体参考的博客内容在文末给出 Linux无锁队列 kfifo概述 Linux内核中有一个先进先出的数据结构 xff0c 采用环形队列的数据结构来实现 xff0c 提供一个无边界的字节流服务 最重
  • ubuntu20 编译dpdk错误 -Werror=address-of-packed-member

    0x0 在ubuntu20上编译dpdk 18 11报错 xff0c gcc 版本为9 3 0 xff0c 报错如下 xff1a error converting a packed const struct ether addr point
  • 虚拟机支持本地nvme ssd

    提起存储都是血泪史 不知道丢了多少数据 脑子首先想到的就是 你说啥 洗脑神曲 我就像那个大妈一样 千万个问号 hdd是啥 ssd又是啥 mbr是啥 gpt又是啥 primary partion是啥 logical partion又是啥 sa
  • ovs+dpdk 三级流表(microflow/megaflow/openflow)

    本文介绍在ovs dpdk下 三级流表的原理及其源码实现 普通模式ovs的第一和二级流表原理和ovs dpdk下的大同小异 三级流表完全一样 基本概念 microflow 最开始openflow流表是在kernel中实现的 但是因为在ker
  • 网络性能测试工具:iperf3

    一 iperf3简介 iperf3是一个网络性能测试工具 iperf3下载地址 iperf可以测试TCP和UDP带宽质量 iperf可以测量最大TCP带宽 具有多种参数和UDP特性 iperf可以报告带宽 延迟抖动和数据包丢失 iperf3
  • Linux HugePage

    1 闲聊 有一段时间 数据库上出现过CPU消耗非常高的问题 最后分析到了Linux HugePage 发现自己对这一块都没什么了解 于是做了 些了解 Linux 下的大页分为两种类型 标准大页 Huge Pages 和透明大页 Transp
  • docker存储管理及实例

    一 Docker存储概念 1 容器本地存储与Docke存储驱动 容器本地存储 每个容器都被自动分配了内部存储 即容器本地存储 采用的是联合文件系统 通过存储驱动进行管理 存储驱动 控制镜像和容器在 docker 主机上的存储和管理方式 容器
  • openssl生成椭圆曲线的私钥是如何做到每次不同的?

    目录 例子 排查 随机算法 小结 例子 生成一个私钥只需要3步 1 获得指定曲线的group 如比特币的secp256k1 2 group和key绑定 3 用key来生成私钥 先上一段代码例子 key1 EC KEY new if key1
  • SPDK块设备

    SPDK视角每个App由多个子系统 subsystem 构成 同时每个子系统又包含多个模块 module 子系统和模块的注入都是可插拔的 通过相关的宏定义声明集成到SPDK组件容器里 其中子系统的注入可通过声明SPDK SUBSYSTEM
  • DPDK的PMD(uio/igb_uio/vfio-pci/uio_pci_generic)

    目录 linux收包的方式 中断对性能的影响有多大 轮询对性能的提升有多大 PMD 介绍 收包对比 内核收包的弊端 DPDK 收包的优点 uio igb uio uio pci generic vfio pci igb uio IGB UI
  • ovs 流表机制(一)

    ip netns add ns1 ip netns add ns2 ip link add tap0 type veth peer name tap0 br ip link add tap3 type veth peer name tap3
  • TCP/IP 网络设备与基础概念

    本文目的在于按照自己的理解 解释清楚网络中的一些基本概念 以及支撑概念落地的网络设备的工作原理 从而解决网络联通性问题 以及为定量分析网络性能问题打基础 如有错漏 欢迎指正 什么是 WAN vs LAN 什么是子网 网关 LAN vs 子网
  • DPDK+Pktgen 高速发包测试

    Pktgen概述 Pktgen Packet Gen erator 是一个基于DPDK的软件框架 发包速率可达线速 提供运行时管理 端口实时测量 可以控制 UDP TCP ARP ICMP GRE MPLS and Queue in Que
  • DPDK — 安装部署

    1 基础环境 1 1 硬件配置 1 2 操作系统要求 2 测试环境编译测试过程 2 1 升级GCC版本至GCC 7 3 0 步骤1 升级GCC依赖包设置 编译安装gmp cd home tar xvf home gmp 6 1 2 tar
  • 查看linux中的TCP连接数

    一 查看哪些IP连接本机 netstat an 二 查看TCP连接数 1 统计80端口连接数 netstat nat grep i 80 wc l 2 统计httpd协议连接数 ps ef grep httpd wc l 3 统计已连接上的
  • DPDK RX/TX 回调示例应用程序中没有流量出现

    我是DPDK领域的新生 我从 DPDK 主页给出的示例应用程序开始 我被这个例子困住了 DPDK RX TX 回调示例应用程序 https doc dpdk org guides sample app ug rxtx callbacks h
  • 在 SR-IOV 虚拟功能 (VF) NIC 之间转发数据包

    我有一个支持 Intel SR IOV 的 Intel 82599ES 10G NIC 我已成功创建了 8 个虚拟功能 VF 并将其分配给 2 个 qemu kvm VM 每个 VM 2 个 VF 两台虚拟机都使用分配的 VF 运行 DPD
  • DPDK“端口数必须为偶数”一台以太网设备

    我正在尝试从 DPDK 源代码运行骨架示例 但每当我尝试在 make 过程后构建代码时 我都会收到一条错误消息 端口数必须为偶数 但是当我尝试查看以太网设备列表时我只能看到一台设备 我在 vmware 工作站环境下的 Ubuntu 中运行框
  • 如何通过 Pktgen-DPDK 生成随机流量?

    I use range

随机推荐

  • visual studio 2017出现MSB8020,MSB8036等SDK版本选择的错误

    1 xff0c 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 MSB8020 无法找到 v140 的生成 xff1b 2 xff0c 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 MSB8036 找不到 Windows
  • C++中循环include问题的讨论

    问题 C语言中未避免头文件的重复引用 xff0c 一般都会使用include guard 如pragma once或 ifndef等 xff0c 但这样做以后并不是万事大吉了 循环使用include可能会出现一些意想不到的错误 如果代码较为
  • flask+gevent+gunicorn+supervisor+nginx异步高并发部署

    背景 flask是一款同步阻塞框架 xff0c 在调用外部http服务时 xff0c 当前进程将阻塞 多进程模式下 xff0c 无法响应其他用户的请求 xff0c 本文则是研究的是如何利用gevent提升flask的并发能力 xff0c 以
  • 小白学SAS--自学笔记

    64 TOC 目录 xff09 第一章初识SAS 数据集的命名 数据导入 建立永久数据集 用菜单新建文件夹 xff0c 并与电脑上已有文件夹关联 用libname语句指定文件夹名 xff0c 并与电脑上已有文件夹关联 用data语句直接指定
  • http协议常用请求头与响应头

    请求头 xff1a Accept 用于告诉服务器 xff0c 客户机支持的数据类型 Accept Charset xff1a 用于告诉服务器 xff0c 客户机所采用的编码 Accept Encoding xff1a 用于告诉服务器 xff
  • mysqlId 不能自启的问题(错误代号2003)

    计算机服务里看下有没有mysql的服务 xff0c 如果有 xff0c 把服务的启动类型改为自动 xff1b 如果没有 xff0c 则要进入安装目录的bin文件夹双击mysqld exe启动mysql 然后 cmd到 Mysql的安装目录的
  • RabbitMQ学习笔记1-"Hello World!"simple模型

    simple模型是RabbitMQ队列模型中最简单的一个模型 如图 xff1a P 是我们的生产者 xff08 producer xff09 xff0c C 是我们的消费者 xff08 consumer xff09 中间的红色框是队列 xf
  • RabbitMQ学习笔记2-Work queues

    接下来学习第二种模型 xff0c Work queues模型 xff0c 如图所示 xff1a 该模型描述的是 xff1a 一个生产者 xff08 P xff09 向队列发送一个消息 xff0c 然后多个消费者 xff08 P xff09
  • RabbitMQ学习笔记3-Publish/Subscribe

    在这部分中 xff0c 我们会做一些完全不同的事情 我们会向多个消费者传递相同的信息 这种模式就是 发布 订阅 该模型中生产者从不将任何消息直接发送到队列 xff0c 生产者通常甚至不知道要将消息发送到哪个队列 xff0c 通常是 xff0
  • ssh -X 使用遇到的问题

    ssh可以在登录时通过 X选项开启远程服务的X服务 xff0c 这样服务器端的需要调用X服务的程序就能开启了 最简单的例子就是可以使用服务器段的gedit程序编译文件了 问题是这样的 xff1a 在IDL程序中有个write gif程序 x
  • Docker部署Gitlab和gitlab-runner,搭建一站式DevOps平台

    一 首次安装Gitlab并配置Gitlab runner CI CD Gitlab Docker 官方安装文档 xff1a https docs gitlab cn jh install docker html 设置Gitlab数据和配置挂
  • 打开 word 显示内存或磁盘空间不足 ,Word 无法显示所请求的字体

    打开word显示内存或磁盘空间不足 xff0c Word无法显示所请求的字体 使用360加速球优化一下 xff0c 恢复正常
  • 使用win10工具远程连接树莓派

    win10远程连接树莓派 1 使用ssh远程连接1 1系统烧录1 2 SSH登录 2 使用win10的mstsc工具远程连接2 1进入远程ssh后 xff0c 修改软件源 xff0c 否则太慢 xff08 清华软件源地址 https mir
  • 网络 - 笔记本无线转为有线

    工具 路由器一个 计算机两台C1和C2 场景说明 xff1a 将C1计算机的无线网络通过路由器转为有线网络 xff0c 并提供给C2计算机使用 第一步 xff1a 硬件搭建 连接C1和路由器 xff1a 将网线的A端插入路由器的WAN口 x
  • 医学案例统计分析与SAS应用--自学笔记

    目录 第二章医学研究设计与SAS实现科研设计思路样本含量估计实验设计 科研设计的sas实现完全随机设计随机区组设计析因设计关系型研究 第三章 统计描述与SAS分析统计描述及sas命令简介定量资料的统计描述分类资料的统计描述 第四章 定量资料
  • 计算机基础 - 左移、右移和计算逻辑

    左移 指的是位移动 xff0c 左移就是将数据位向左移动 xff0c 例如十进制10 二进制为0000 1010 左移4位后得到1010 0000 xff0c 转为十进制后为160 如果是左移5位 xff0c 那么超出部分被丢弃得到的就是0
  • C++ 二叉树实现词频分析

    通过二叉树存单词 xff0c 并且对总共的单词数量进行计数 xff0c 二叉树自适应的将出现频率高的单词往上移动以减少二叉树的搜索时间 代码如下 span class hljs comment genSplay h span span cl
  • C++ cout输出字符

    cout输出字符时 xff0c 可以使用单引号 xff1a cout lt lt span class hljs string 39 39 span lt lt endl span class hljs regexp span 输出分号 s
  • Linux 多进程多线程编程

    一 创建进程 1 进程号 进程号的类型是pid t xff08 typedef unsigned int pid t xff09 获得进程和父进程ID的API如下 xff1a include lt sys types h gt includ
  • dpdk探究1-理解dpdk的运行逻辑

    DPDK介绍 DPDK主要功能 xff1a 利用IA xff08 intel architecture xff09 多核处理器进行高性能数据包处理 Linux下传统的网络设备驱动包处理的动作可以概括如下 xff1a 数据包到达网卡设备网卡设