从零开始学习DPDK:掌握这些常用库函数就够了(下)

简介: 从零开始学习DPDK:掌握这些常用库函数就够了

3.3 Mempool库

DPDK提供了内存池机制,使得内存的管理的使用更加简单安全。在设计大的数据结构时,都可以使用mempool分配内存,同时,mempool也提供了内存的获取和释放等操作接口。对于数据包mempool甚至提供了更加详细的接口-rte_pktmbuf_pool_create()。

Mempool是固定大小的对象分配器。 在DPDK中,它由名称唯一标识,并且使用mempool操作来存储空闲对象。Mempool的组织是通过三个部分实现的:

  • mempool对象节点:mempool的对象挂接在 static struct rte_tailq_elem rte_mempool_tailq 全局队列中,可以通过名字进行唯一标识符;此队列只是mempool的一个对象指示结构,并不是实际的内存区;
  • mempool实际内存区: struct rte_memzone 是实际分配的连续内存空间,存储所创建的mempool对象;
  • ring无锁队列:作为一个无锁环形队列 struct rte_ring ,存储着mempool对象的指针,提供了方便存取使用mempool的空间的办法。

一般结构

如图所示,mempool的对象通过与ring无锁队列建立关联方便存取;同时,为了减少多核访问造成的冲突,引入了local_cache对象缓冲区。该local_cache非硬件上的cache,而是为了减少多核访问ring造成的临界区访问,

coreX app会优先访问该local_cache上的对象。入队的时候优先入local_cache中,出队的时候优先从local_cache中出队。

mempool的创建和使用

先注意一下 rte_mempool_create 的参数中的两个 mp_init 和 obj_init ,前者负责初始化mempool中配置的私有参数,如在数据包中加入的我们自己的私有结构;后者负责初始化每个mempool对象。我们然后按照mempool的3个关键部分展开说明。

(1)mempool头结构的创建

mempool头结构包含3个部分: struct rte_mempool , struct rte_mempool_cache 和mempool private。创建是在 rte_mempool_create_empty() 中完成的,看这个函数,先进行了对齐的检查:

RTE_BUILD_BUG_ON((sizeof(struct rte_mempool) &
              RTE_CACHE_LINE_MASK) != 0);
    RTE_BUILD_BUG_ON((sizeof(struct rte_mempool_cache) &
              RTE_CACHE_LINE_MASK) != 0);

然后从mempool队列中取出头节点,我们创建的mempool结构填充好,就挂接在这个节点上。接下来做一些检查工作和创建flag的设置。

rte_mempool_calc_obj_size() 计算了每个obj的大小,这个obj又是由三个部分组成的,header_size、elt_size、trailer_size,即头,数据区,尾。在没有开启RTE_LIBRTE_MEMPOOL_DEBUG调试时,没有尾部分;头部分的结构为: struct rte_mempool_objhdr ,

通过这个头部,mempool中的obj都是链接到队列中的,所以,提供了遍历obj的方式(尽管很少这么用)。函数返回最后计算对齐后的obj的大小,为后面分配空间提供依据。然后分配了一个mempool队列条目,为后面挂接在队列做准备。

/* try to allocate tailq entry */
    te = rte_zmalloc("MEMPOOL_TAILQ_ENTRY", sizeof(*te), 0);
    if (te == NULL) {
        RTE_LOG(ERR, MEMPOOL, "Cannot allocate tailq entry!\n");
        goto exit_unlock;
    }

接下来,就是计算整个mempool头结构多大。

mempool_size = MEMPOOL_HEADER_SIZE(mp, cache_size);
    mempool_size += private_data_size;
    mempool_size = RTE_ALIGN_CEIL(mempool_size, RTE_MEMPOOL_ALIGN);

这里指的是计算mempool的头结构的大小。而不是内存池实际的大小。在这里可以清晰的看出这个mempool头结构是由三部分组成的。cache计算的是所有核上的cache之和。

然后,使用 rte_memzone_reserve() 分配这个mempool头结构大小的空间,填充mempool结构体,并把mempool头结构中的cache地址分配给mempool。初始化这部分cache。

最后就是挂接mempool结构。 TAILQ_INSERT_TAIL(mempool_list, te, next); (这里上了锁?)。

(2)mempool实际空间的创建

这部分的创建是在函数 rte_mempool_populate_default(struct rte_mempool *mp) 中完成的。

首先计算为这些元素需要分配多大的空间, rte_mempool_ops_calc_mem_size()

接着 rte_memzone_reserve_aligned() 分配空间。把元素添加到mempool,实际上就是把申请的空间分给每个元素。

(3)ring的创建

先看到的是这么一段代码:

static int
mempool_ops_alloc_once(struct rte_mempool *mp)
{
    int ret;
    /* create the internal ring if not already done */
    if ((mp->flags & MEMPOOL_F_POOL_CREATED) == 0) {
        ret = rte_mempool_ops_alloc(mp);
        if (ret != 0)
            return ret;
        mp->flags |= MEMPOOL_F_POOL_CREATED;
    }
    return 0;
}

这就是创建ring的过程咯,其中的函数rte_mempool_ops_alloc()就是实现。那么,对应的ops->alloc()在哪注册的呢?

/*
     * Since we have 4 combinations of the SP/SC/MP/MC examine the flags to
     * set the correct index into the table of ops structs.
     */
    if ((flags & MEMPOOL_F_SP_PUT) && (flags & MEMPOOL_F_SC_GET))
        ret = rte_mempool_set_ops_byname(mp, "ring_sp_sc", NULL);
    else if (flags & MEMPOOL_F_SP_PUT)
        ret = rte_mempool_set_ops_byname(mp, "ring_sp_mc", NULL);
    else if (flags & MEMPOOL_F_SC_GET)
        ret = rte_mempool_set_ops_byname(mp, "ring_mp_sc", NULL);
    else
        ret = rte_mempool_set_ops_byname(mp, "ring_mp_mc", NULL);

就是根据ring的类型,来注册对应的操作函数,如默认的就是ring_mp_mc,多生产者多消费者模型,其操作函数不难找到:

static const struct rte_mempool_ops ops_mp_mc = {
    .name = "ring_mp_mc",
    .alloc = common_ring_alloc,
    .free = common_ring_free,
    .enqueue = common_ring_mp_enqueue,
    .dequeue = common_ring_mc_dequeue,
    .get_count = common_ring_get_count,
};

接下来,又分配了一个 struct rte_mempool_memhdr *memhdr; 结构的变量,就是这个变量管理着mempool的实际内存区,它记录着mempool实际地址区的物理地址,虚拟地址,长度等信息。

再然后,就是把每个元素对应到mempool池中了: mempool_add_elem() 。在其中,把每个元素都挂在了elt_list中,可以遍历每个元素。最后 rte_mempool_ops_enqueue_bulk(struct rte_mempool *mp, void * const *obj_table, ,最终,把元素对应的地址入队,这样,mempool中的每个元素都放入了ring中。

四:mempool的使用

mempool的常见使用是获取元素空间和释放空间。

  • rte_mempool_get可以获得池中的元素,其实就是从ring取出可用元素的地址。
  • rte_mempool_put可以释放元素到池中。
  • rte_mempool_in_use_count查看池中已经使用的元素个数
  • rte_mempool_avail_count 查看池中可以使用的元素个数

3.4 mbuf库

报文缓冲区库(Mbuf)提供了申请和释放缓冲区的功能,DPDK应用程序使用这些buffer存储消息缓冲。消息缓冲存储在mempool中,使用内存池库 。

数据结构rte_mbuf可以承载网络数据包buffer或者通用控制消息buffer(由CTRL_MBUF_FLAG指示)。也可以扩展到其他类型。rte_mbuf头部结构尽可能小,目前只使用两个缓存行,最常用的字段位于第一个缓存行中。

报文缓冲区设计

为了存储数据包数据(包括协议头部),考虑了两种方法:

  1. 在单个存储buffer中嵌入metadata,后面跟着数据包数据固定大小区域
  2. 为metadata和报文数据分别使用独立的存储buffer。

第一种方法的优点是他只需要一个操作来分配/释放数据包的整个存储表示。但是,第二种方法更加灵活,并允许将元数据的分配与报文数据缓冲区的分配完全分离。

DPDK选择了第一种方法。Metadata包含诸如消息类型,长度,到数据开头的偏移量等控制信息,以及允许缓冲链接的附加mbuf结构指针。

用于承载网络数据包buffer的消息缓冲可以处理需要多个缓冲区来保存完整数据包的情况。许多通过下一个字段链接在一起的mbuf组成的jumbo帧,就是这种情况。

对于新分配的mbuf,数据开始的区域是buffer之后 RTE_PKTMBUF_HEADROOM 字节的位置,这是缓存对齐的。 Message buffers可以在系统中的不同实体中携带控制信息,报文,事件等。 Message buffers也可以使用起buffer指针来指向其他消息缓冲的数据字段或其他数据结构。

Buffer Manager实现了一组相当标准的buffer访问操作来操纵网络数据包。

存储在内存池中的缓冲区

Buffer Manager使用内存池库来申请buffer。因此确保了数据包头部均衡分布到信道上,有利于L3处理。mbuf中包含一个字段,用于表示它从哪个池中申请出来。当调用 rte_ctrlmbuf_free(m) 或 rte_pktmbuf_free(m),mbuf被释放到原来的池中。

构造函数

Packet及control mbuf构造函数由API提供。接口rte_pktmbuf_init()及rte_ctrlmbuf_init()初始化mbuf结构中的某些字段,这些字段一旦创建将不会被用户修改(如mbuf类型、源池、缓冲区起始地址等)。此函数在池创建时作为rte_mempool_create()函数的回掉函数给出。


缓冲区申请及释放

分配一个新mbuf需要用户指定从哪个池中申请。对于任意新分配的mbuf,它包含一个段,长度为0。 缓冲区到数据的偏移量被初始化,以便使得buffer具有一些字节(RTE_PKTMBUF_HEADROOM)的headroom。

释放mbuf意味着将其返回到原始的mempool。当mbuf的内容存储在一个池中(作为一个空闲的mbuf)时,mbuf的内容不会被修改。由构造函数初始化的字段不需要在mbuf分配时重新初始化。

当释放包含多个段的数据包mbuf时,他们都被释放,并返回到原始mempool。


缓冲区操作

这个库提供了一些操作数据包mbuf中的数据的功能。例如:

  • 获取数据长度
  • 获取指向数据开始位置的指针
  • 数据前插入数据
  • 数据之后添加数据
  • 删除缓冲区开头的数据(rte_pktmbuf_adj())
  • 删除缓冲区末尾的数据(rte_pktmbuf_trim())详细信息请参阅 DPDK API Reference

元数据信息

数据包的一些信息由网络驱动程序检索并存储在mbuf中使得处理更简单。例如,VLAN、RSS哈希结果(参见 Poll Mode Driver)及校验和由硬件计算的标志等。

一个报文缓冲区中还包含数据源端口和报文链中mbuf数目。对于链接的mbuf,只有链的第一个mbuf存储这个元信息。

例如,对于IEEE1588数据包,RX侧就是这种情况,时间戳机制,VLAN标记和IP校验和计算。

在TX端,应用程序还可以将一些处理委托给硬件。 例如,PKT_TX_IP_CKSUM标志允许卸载IPv4校验和的计算。

以下示例说明如何在vxlan封装的tcp数据包上配置不同的TX offloads:

out_eth/out_ip/out_udp/vxlan/in_eth/in_ip/in_tcp/payload

计算out_ip的校验和:

mb->l2_len = len(out_eth)
mb->l3_len = len(out_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM
set out_ip checksum to 0 in the packet
  • 配置DEV_TX_OFFLOAD_IPV4_CKSUM支持在硬件计算。

计算out_ip 和 out_udp的校验和:

mb->l2_len = len(out_eth)
mb->l3_len = len(out_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM | PKT_TX_UDP_CKSUM
set out_ip checksum to 0 in the packet
set out_udp checksum to pseudo header using rte_ipv4_phdr_cksum()

配置DEV_TX_OFFLOAD_IPV4_CKSUM 和 DEV_TX_OFFLOAD_UDP_CKSUM支持在硬件上计算。

计算in_ip的校验和:

mb->l2_len = len(out_eth + out_ip + out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM
set in_ip checksum to 0 in the packet

这以情况1类似,但是l2_len不同。 配置DEV_TX_OFFLOAD_IPV4_CKSUM支持硬件计算。 注意,只有外部L4校验和为0时才可以工作。

计算in_ip 和 in_tcp的校验和:

mb->l2_len = len(out_eth + out_ip + out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM | PKT_TX_TCP_CKSUM
在报文中设置in_ip校验和为0
使用rte_ipv4_phdr_cksum()将in_tcp校验和设置为伪头

这与情况2类似,但是l2_len不同。 配置DEV_TX_OFFLOAD_IPV4_CKSUM 和 DEV_TX_OFFLOAD_TCP_CKSUM支持硬件实现。 注意,只有外部L4校验和为0才能工作。


segment inner TCP:

mb->l2_len = len(out_eth + out_ip + out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->l4_len = len(in_tcp)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CKSUM | PKT_TX_TCP_CKSUM | PKT_TX_TCP_SEG;
在报文中设置in_ip校验和为0
将in_tcp校验和设置为伪头部,而不使用IP载荷长度
配置DEV_TX_OFFLOAD_TCP_TSO支持硬件实现。 注意,只有L4校验和为0时才能工作。

计算out_ip, in_ip, in_tcp的校验和:

mb->outer_l2_len = len(out_eth)
mb->outer_l3_len = len(out_ip)
mb->l2_len = len(out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->ol_flags|=PKT_TX_OUTER_IPV4|PKT_TX_OUTER_IP_CKSUM | PKT_TX_IP_CKSUM |  PKT_TX_TCP_CKSUM;
设置 out_ip 校验和为0
设置 in_ip 校验和为0
使用rte_ipv4_phdr_cksum()设置in_tcp校验和为伪头部

配置DEV_TX_OFFLOAD_IPV4_CKSUM、DEV_TX_OFFLOAD_UDP_CKSUM、DEV_TX_OFFLOAD_OUTER_IPV4_CKSUM支持硬件实现。

Flage标记的意义在mbuf API文档(rte_mbuf.h)中有详细描述。 更多详细信息还可以参阅testpmd 源码(特别是csumonly.c)。

直接缓冲区和间接缓冲区

直接缓冲区是指缓冲区完全独立。间接缓冲区的行为类似于直接缓冲区,但缓冲区的指针和数据偏移量指的是另一个直接缓冲区的数据。这在数据包需要复制或分段的情况下是很有用的,因为间接缓冲区提供跨越多个缓冲区重用相同数据包数据的手段。

当使用接口 rte_pktmbuf_attach() 函数将缓冲区附加到直接缓冲区时,该缓冲区变成间接缓冲区。每个缓冲区有一个引用计数器字段,每当直接缓冲区附加一个间接缓冲区时,直接缓冲区上的引用计数器递增。类似的,每当间接缓冲区被分裂时,直接缓冲区上的引用计数器递减。如果生成的引用计数器为0,则直接缓冲区将被释放,因为它不再使用。

处理间接缓冲区时需要注意几件事情。首先,间接缓冲区从不附加到另一个间接缓冲区。尝试将缓冲区A附加到间接缓冲区B(且B附加到C上了),将使得rte_pktmbuf_attach() 自动将A附加到C上。其次,为了使缓冲区变成间接缓冲区,其引用计数必须等于1,也就是说它不能被另一个间接缓冲区引用。最后,不可能将间接缓冲区重新链接到直接缓冲区(除非它已经被分离了)。

虽然可以使用推荐的rte_pktmbuf_attach()和rte_pktmbuf_detach()函数直接调用附加/分离操作,但建议使用更高级的rte_pktmbuf_clone()函数,该函数负责间接缓冲区的正确初始化,并可以克隆具有多个段的缓冲区。

由于间接缓冲区不应该实际保存任何数据,间接缓冲区的内存池应配置为指示减少的内存消耗。可以在几个示例应用程序中找到用于间接缓冲区的内存池(以及间接缓冲区的用例示例)的初始化示例,例如IPv4组播示例应用程序。


调试

在调试模式(CONFIG_RTE_MBUF_DEBUG使能)下,mbuf库的函数在任何操作之前执行完整性检查(如缓冲区检查、类型错误等)。

用例

所有网络应用程序都应该使用mbufs来传输网络数据包。

3.5 PMD驱动

为了支持Userspace IO,DPDK可以选择如下三种类型的驱动:

  • uio_pci_generic
  • uio + igb_uio
  • vfio-pci

uio_pci_generic是内核原生的一种uio驱动,该驱动提供了uio功能,直接使用如下命令加载:

sudo modprobe uio_pci_generic

这个原生驱动是不支持VF设备创建的,因此DPDK也提供了另外一种uio驱动igb_uio,它是可以用于宿主机上来创建VF设备的。适用性比内核原生的uio_pci_generic更强一些,其中igb_uio.ko是由dpdk代码库编译出来的:

sudo modprobe uio
sudo insmod kmod/igb_uio.ko

从DPDK release 1.7开始,DPDK对VFIO进行了支持,因此VFIO Driver成了新的可选项:

sudo modprobe vfio-pci

当DPDK使用vfio来实现网络功能时可以直接加载该驱动。

特别注意:

对于使用VFIO驱动来使用DPDK的场景,必须保证:

  • 1.硬件上支持支持VT-x、VT-d,BIOS中需要打开相关特性
  • 2.对于物理机的内核中需要支持IOMMU特性(在启动参数添加 iommu=pt, intel_iommu=on)

物理机上使用DPDK

在物理机上使用DPDK,需要内核中加载DPDK PMD Driver,那么需要使用如下命令加载DPDK的驱动:

modprobe uio
insmod igb_uio
usertools/dpdk-devbind.py --bind=igb_uio bb:ss.f

当然这里我们也可以使用上面介绍过的其他类型的内核模块:uio_pci_generic或者vfio-pci

虚拟机中使用DPDK

对于支持SR-IOV的网卡来说,比如Intel的X710/XL710网卡,在虚拟化的环境中使用,网卡可以进行透传,本文以透传的方式来进行实践介绍,对于支持SR-IOV的网卡来说,它分为PF和VF模块,在宿主机中需要加载对应的PF Driver和VF Driver来驱动这两个子模块。

宿主机

在宿主机上可以直接使用Linux kernel官方的intel PF驱动,比如i40e,也可以使用DPDK专用的 PMD PF驱动。如果使用了DPDK PMD PF 驱动,那么这个宿主机网络的管理权就完全交给DPDK了。

方案一:i40e驱动

rmmod i40e (To remove the i40e module)
insmod i40e.ko max_vfs=2,2 (To enable two Virtual Functions per port)

通过重新加载intel提供的i40e驱动,并指定max_vfs参数来创建VF功能,对于该网卡的VF功能内核默认使用的驱动为i40evf,因此在使用dpdk之前,还需要在Host上将VF与i40evf驱动解绑,重新绑定到vfio-pci驱动上:

modprobe vfio-pci

宿主机中需要使用vfio_pci这个内核模块来对需要分配给客户机的设备进行隐藏, 从而让宿主机和未被分配该设备的客户机都无法使用该设备, 达到隔离和安全使用的目的。而在客户机不需要使用该设备后, 让宿主机使用该设备, 则需要将其恢复到使用原本的驱动。

这里也可以利用DPDK提供的脚本dpdk_bind_nic.py来设置:

usertools/dpdk-devbind.py --bind=vfio-pci  bb:ss.f

此时该VF已经由vfio驱动接管,对于上一章介绍的,如果在宿主机上使用VFIO DPDK,那么此时就已经满足了条件,但是我们此处是为了演示虚拟机中使用DPDK,所以此时不能启动DPDK去使用该VF,而需要在虚拟机中透传该设备来使用。

方案二:DPDK PMD PF驱动

需要内核启动参数中使能iommu=pt, intel_iommu=on,然后启动后加载DPDK驱动:

modprobe uio
insmod kmod/igb_uio.ko
usertools/dpdk-devbind.py --bind=igb_uio bb:ss.f
echo 2 > /sys/bus/pci/devices/0000\:bb\:ss.f/max_vfs (To enable two VFs on a specific PCI device)

虚拟机

对于虚拟机来说,透传过来的VFIO网卡对于虚拟机来说就相当于是一个常规的物理网卡,默认就会使用该物理网卡对应的驱动,比如i40e driver,那么如果要在虚拟机中使用DPDK,就需要把虚拟网卡重新绑定到igb_uio驱动,这样就可以在虚拟机中使用DPDK了。实际上操作还是与宿主机中一样:

modprobe uio
insmod kmod/igb_uio.ko
usertools/dpdk-devbind.py --bind=igb_uio bb:ss.f

3.6 IVSHMEM库

3.7 Timer库

初始化dpdk定时器库

1.void rte_timer_subsystem_init (   void        )

功能:初始化dpdk定时器库,初始化内部变量(链表和锁)

初始化timer定时器

void rte_timer_init (   struct rte_timer *  tim )
参数tim:带初始化的timer

启动定时器

int rte_timer_reset (   struct rte_timer *  tim,
                    uint64_t    ticks,
                    enum rte_timer_type     type,
                    unsigned    tim_lcore,
                    rte_timer_cb_t  fct,
                    void *  arg 
)   
功能:启动或者重置定时器,当定时器经过一定时间间隔超时后,会在tim_lcore指定的core上调用fct函数,函数参数是arg。
如果timer当前处于运行状态(Running),函数会调用失败,所以应检查函数返回值查看timer是否处于运行状态
如果timer在其他core上被设置,即(CONFIG 状态),函数也返回失败。
参数time:timer句柄
参数ticks:超时时间,参考rte_get_hpet_hz()的使用
参数type:取值PERIODICAL或SINGLE
    PERIODICAL:定时器触发,并执行后自动加载
    SINGLE:定时器仅仅触发一次。执行后进入STOPPED 状态
参数tim_lcore:指定这个定时器回调函数在哪个core上面运行;,如果tim_lcore 值为LCORE_ID_ANY,则以轮询方式在不同的核上执行回调函数。
参数fct:定时器回调函数
参数arg:回调函数的参数

定时器调度和管理

rte_timer_manage();  
 管理定时器链表,执行定时器回调函数
 这个函数必须在EAL core的main_loop()函数中调用。它浏览挂起的timer,然后执行已经超时的定时器。
定时器的精度取决于这个函数的调用次序。

停止定时器

rte_timer_stop停止定时器

x timer例子代码分析:

/* timer0 callback */
static void
timer0_cb(__attribute__((unused)) struct rte_timer *tim,
      __attribute__((unused)) void *arg)
{
    static unsigned counter = 0;
    unsigned lcore_id = rte_lcore_id();
    printf("%s() on lcore %u\n", __func__, lcore_id);
    /* this timer is automatically reloaded until we decide to
     * stop it, when counter reaches 20. */
    if ((counter ++) == 20)
        rte_timer_stop(tim);
}
timer1定时器,我们传递了一个参数SINGLE,就是指让这个定时器在某个core上执行一次,然后停止;
当前启动调用的回调函数是timer1_cb()
在timer1_cb函数中再次执行一次rte_timer_reset就可以启动定时器,
/* timer1 callback */
static void
timer1_cb(__attribute__((unused)) struct rte_timer *tim,
      __attribute__((unused)) void *arg)
{
    unsigned lcore_id = rte_lcore_id();
    uint64_t hz;
    printf("%s() on lcore %u\n", __func__, lcore_id);
    /* reload it on another lcore */
    hz = rte_get_timer_hz();
    lcore_id = rte_get_next_lcore(lcore_id, 0, 1);
    rte_timer_reset(tim, hz/3, SINGLE, lcore_id, timer1_cb, NULL);
}
static __attribute__((noreturn)) int
lcore_mainloop(__attribute__((unused)) void *arg)
{
    uint64_t prev_tsc = 0, cur_tsc, diff_tsc;
    unsigned lcore_id;
    lcore_id = rte_lcore_id();
    printf("Starting mainloop on core %u\n", lcore_id);
    while (1) {
        /*
         * Call the timer handler on each core: as we don't
         * need a very precise timer, so only call
         * rte_timer_manage() every ~10ms (at 2Ghz). In a real
         * application, this will enhance performances as
         * reading the HPET timer is not efficient.
         */
        //函数中会每10ms执行一次调度,让rte_timer_manage()管理当前core上的定时器,rte_timer_manage函数必须调用,否则timer定时器不会运行
        cur_tsc = rte_rdtsc();
        diff_tsc = cur_tsc - prev_tsc;
        if (diff_tsc > TIMER_RESOLUTION_CYCLES) {
            rte_timer_manage();
            prev_tsc = cur_tsc;
        }
    }
}


int
main(int argc, char **argv)
{
    int ret;
    uint64_t hz;
    unsigned lcore_id;
    /* init EAL */
    ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_panic("Cannot init EAL\n");
    /* init RTE timer library */
    rte_timer_subsystem_init();
    /* init timer structures */
    rte_timer_init(&timer0);
    rte_timer_init(&timer1);
    /* load timer0, every second, on master lcore, reloaded automatically */
    hz = rte_get_timer_hz();
    lcore_id = rte_lcore_id();
    rte_timer_reset(&timer0, hz, PERIODICAL, lcore_id, timer0_cb, NULL);
    /* load timer1, every second/3, on next lcore, reloaded manually */
    //初始化的另一个定时器timer1;这用hz/3  是指定时只有1秒的三分之一;SINGLE是指只执行一次,如想让它再运行就需要从新初始化它;
    lcore_id = rte_get_next_lcore(lcore_id, 0, 1);
    rte_timer_reset(&timer1, hz/3, SINGLE, lcore_id, timer1_cb, NULL);
    /* call lcore_mainloop() on every slave lcore */
    RTE_LCORE_FOREACH_SLAVE(lcore_id) {
        rte_eal_remote_launch(lcore_mainloop, NULL, lcore_id);
    }
    /* call it on master lcore too */
    (void) lcore_mainloop(NULL);
    return 0;
}

注意事项:

  • 1.不管是slave core还是master core,rte_timer_manage函数必须调用,否则timer定时器不会运行
  • 2.一个core上不能同时运行两个以上,PERIODICAL类型的定时器
  • 3.一个core能同时运行两个,SINGLE类型的单次定时器
  • 4.可以在一个core上,启动另一个core上的定时器

3.8 LPM库

DPDK LPM库组件实现了32位Key的最长前缀匹配(LPM)表搜索方法,该方法通常用于在IP转发应用程序中找到最佳路由。

LPM API概述

LPM组件实例的主要配置参数是要支持的最大数量的规则。LPM前缀由一对参数(32位Key,深度)表示,深度范围为1到32。LPM规则由LPM前缀和与前缀相关联的一些用户数据表示。该前缀作为LPM规则的唯一标识符。在该实现中,用户数据为1字节长,被称为下一跳,与其在路由表条目中存储下一跳的ID的主要用途相关。

LPM组件导出的主要方法有:

  • 添加LPM规则:LPM规则作为输入参数。如果表中没有存在相同前缀的规则,则将新规则添加到LPM表中。如果表中已经存在具有相同前缀的规则,则会更新规则的下一跳。当没有可用的规则空间时,返回错误。
  • 删除LPM规则:LPM规则的前缀作为输入参数。如果具有指定前缀的规则存在于LPM表中,则会被删除。
  • LPM规则查找:32位Key作为输入参数。该算法用于选择给定Key的最佳匹配的LPM规则,并返回该规则的下一跳。在LPM表中具有多个相同32位Key的规则的情况下,算法将最高深度的规则选为最佳匹配规则(最长前缀匹配),这意味着该规则Key和输入的Key之间具有最高有效位的匹配。

实现细节

目前的实现使用DIR-24-8算法的变体,可以改善内存使用量,以提高LPM查找速度。该算法允许以典型的单个存储器读访问来执行查找操作。在统计上看,即便是不常出现的情况,当即最佳匹配规则的深度大于24时,查找操作也仅需要两次内存读取访问。因此,特定存储器位置是否存在于处理器高速缓存中将很大程度上影响LPM查找操作的性能。

主要数据结构使用以下元素构建:

  • 一个2^24个条目的表。
  • 多个表(RTE_LPM_TBL8_NUM_GROUPS),每个表有2 ^ 8个条目。

第一个表,称为tbl24,使用要查找的IP地址的前24位进行索引;而第二个表,称为tbl8使用IP地址的最后8位进行索引。这意味着根据输入数据包的IP地址与存储在tbl24中的规则进行匹配的结果,我们可能需要在第二级继续查找过程。

由于tbl24的每个条目都可以指向tbl8,理想情况下,我们将具有2 ^ 24 tbl8,这与具有2 ^ 32个条目的单个表占用空间相同。因为资源限制,这显然是不可行的。相反,这种组织方法就是利用了超过24位的规则是非常罕见的这一特定。通过将这个过程分为两个不同的表/级别并限制tbl8的数量,我们可以大大降低内存消耗,同时保持非常好的查找速度(大部分时间仅一个内存访问)。

tbl24中的条目包含以下字段:

  • 下一跳,或者下一级查找表tbl8的索引值。
  • 有效标志。
  • 外部条目标志。
  • 规则深度。

第一个字段可以包含指示查找过程应该继续的tbl8的数字,或者如果已经找到最长的前缀匹配,则可以包含下一跳本身。两个标志字段用于确定条目是否有效,以及搜索过程是否分别完成。规则的深度或长度是存储在特定条目中的规则的位数。

tbl8中的条目包含以下字段:

  • 下一跳。
  • 有效标志。
  • 有效组。
  • 深度。

下一跳和深度包含与tbl24中相同的信息。两个标志字段显示条目和表分别是否有效。其他主要数据结构是包含有关规则(IP和下一跳)的主要信息的表。这是一个更高级别的表,用于不同的东西:

  • 在添加或删除之前,检查规则是否已经存在,而无需实际执行查找。
  • 删除时,检查是否存在包含要删除的规则。这很重要,因为主数据结构必须相应更新。

添加

添加规则时,存在不同的可能性。如果规则的深度恰好是24位,那么:

  • 使用规则(IP地址)作为tbl24的索引。
  • 如果条目无效(即它不包含规则),则将其下一跳设置为其值,将有效标志设置为1(表示此条目正在使用中),并将外部条目标志设置为0(表示查找 此过程结束,因为这是匹配的最长的前缀)。

如果规则的深度正好是32位,那么:

  • 使用规则的前24位作为tbl24的索引。
  • 如果条目无效(即它不包含规则),则查找一个空闲的tbl8,将该值的tbl8的索引设置为该值,将有效标志设置为1(表示此条目正在使用中),并将外部 条目标志为1(意味着查找过程必须继续,因为规则尚未被完全探测)。

如果规则的深度是任何其他值,则必须执行前缀扩展。这意味着规则被复制到所有下一级条目(只要它们不被使用),这也将导致匹配。

作为一个简单的例子,我们假设深度是20位。这意味着有可能导致匹配的IP地址的前24位的2 ^(24 - 20)= 16种不同的组合。因此,在这种情况下,我们将完全相同的条目复制到由这些组合索引的每个位置。

通过这样做,我们确保在查找过程中,如果存在与IP地址匹配的规则,则可以在一个或两个内存访问中找到,具体取决于是否需要移动到下一个表。前缀扩展是该算法的关键之一,因为它通过添加冗余来显着提高速度。

查询

查找过程要简单得多,速度更快。在这种情况下:

  • 使用IP地址的前24位作为tbl24的索引。如果该条目未被使用,那么这意味着我们没有匹配此IP的规则。如果它有效并且外部条目标志设置为0,则返回下一跳。
     如果它是有效的并且外部条目标志被设置为1,那么我们使用tbl8索引来找出要检查的tbl8,并且将该IP地址的最后8位作为该表的索引。类似地,如果条目未被使用,那么我们没有与该IP地址匹配的规则。如果它有效,则返回下一跳。

规则数目的限制

规则数量受到诸多不同因素的限制。第一个是规则的最大数量,这是通过API传递的参数。一旦达到这个数字,就不可能再添加任何更多的规则到路由表,除非有一个或多个删除。

第二个因素是算法的内在限制。如前所述,为了避免高内存消耗,tbl8的数量在编译时间有限(此值默认为256)。如果我们耗尽tbl8,我们将无法再添加任何规则。特定路由表中需要多少路由表是很难提前确定的。

只要我们有一个深度大于24的新规则,并且该规则的前24位与先前添加的规则的前24位不同,就会消耗tbl8。如果相同,那么新规则将与前一个规则共享相同的tbl8,因为两个规则之间的唯一区别是在最后一个字节内。

默认值为256情况下,我们最多可以有256个规则,长度超过24位,且前三个字节都不同。由于长度超过24位的路由不太可能,因此在大多数设置中不应该是一个问题。即便如此,tbl8的数量也可以通过设置更改。


用例:IPv4转发

LPM算法用于实现IPv4转发的路由器所使用的无类别域间路由(CIDR)策略。

3.9 Hash库

Hash表和算法是高性能流量处理中常用技术,在DPDK中也提供了相应的高性能实现用于支持流表、转发规则表等设施的开发。DPDK18的Hash库实现方式与其早期版本(1.6)时期的实现有了根本性的差异,本文介绍的实现基于18.05版本代码。

Hash算法原理——Cuckoo Hash

DPDK中的Hash库采用的hash算法和数据结构基于CuckooHash哈希表中的节点采用一个连续数组保存,其原理是每个节点key可以对应两个哈希散列位置(主、从)。查找和删除key时需要查找两个位置来确定key是否存在;插入时将key插入到主位,如果主位已经被key2占用,则将占用主位的key2移动到其另一位置存储,如果key2的另一位置也已经被key3占用,则再将key3移动到其另一位置存储......直到最后一次移动不再冲突为止,如果冲突次数达到一定阈值,则判定哈希表已满。

具体实现方式

在DPDK的实现中,每个节点的主从存储位置被实现为两个桶数组。哈希结构由两部分构成:

一部分是一系列的哈希桶,每个桶中可以保存8个节点的主次signature和key_index;

另一部分则是保存节点key和内容的数组,通过key_index下标进行索引,其中节点内容部分共8字节,可以直接保存节点信息,也可以保存一个指针来指向节点的数据结构。

在查找一个key时,流程如下:

首先通过散列算法获得两个4字节的signature和对应的两个桶。主signature散列算法可以是CRC、JHash或自定义算法;次signature是对主signature经过一个变换产生的。

在桶中查找signature匹配的节点信息

根据key_index索引到完整的key

比对key是否一致,如果一致则查找成功

可见,一次成功的查找至少要访问两个数据结构各一次,分别比对signature和key。一次失败的查找则要访问主从桶各一次,比对signature。

代码分析——数据结构

/** A hash table structure. */
struct rte_hash {
  char name[RTE_HASH_NAMESIZE];   /**< Name of the hash. */
  uint32_t entries;               /**< Total table entries. */
  uint32_t num_buckets;           /**< Number of buckets in table. */
  struct rte_ring *free_slots;
  /**< Ring that stores all indexes of the free slots in the key table */
  uint8_t hw_trans_mem_support;
  /**< Hardware transactional memory support */
  struct lcore_cache *local_free_slots;
  /**< Local cache per lcore, storing some indexes of the free slots */
  enum add_key_case add_key; /**< Multi-writer hash add behavior */
  rte_spinlock_t *multiwriter_lock; /**< Multi-writer spinlock for w/o TM */
  /* Fields used in lookup */
  uint32_t key_len __rte_cache_aligned;
  /**< Length of hash key. */
  rte_hash_function hash_func;    /**< Function used to calculate hash. */
  uint32_t hash_func_init_val;    /**< Init value used by hash_func. */
  rte_hash_cmp_eq_t rte_hash_custom_cmp_eq;
  /**< Custom function used to compare keys. */
  enum cmp_jump_table_case cmp_jump_table_idx;
  /**< Indicates which compare function to use. */
  enum rte_hash_sig_compare_function sig_cmp_fn;
  /**< Indicates which signature compare function to use. */
  uint32_t bucket_bitmask;
  /**< Bitmask for getting bucket index from hash signature. */
  uint32_t key_entry_size;         /**< Size of each key entry. */
  void *key_store;                /**< Table storing all keys and data */
  struct rte_hash_bucket *buckets;
  /**< Table with buckets storing all the hash values and key indexes
   * to the key table.
   */
} __rte_cache_aligned;

一个哈希表的数据结构如上,其中包括节点最大数量、哈希桶数量、哈希key的长度、key的散列值算法等。该结构通过rte_hash_create创建和初始化,其中最重要的数据结构是:

  • struct rte_ring *free_slots,表示哈希节点数组中空闲的节点;
  • void *key_store,保存实际的key和节点信息的数组
  • struct rte_hash_bucket *buckets,哈希桶数组,保存key的signature和在key_store中的下标位置
  • 每个成员的意义和功能可以结合上面的注释以及rte_hash_create函数中的初始化逻辑来深入了解。

关键逻辑

哈希表中的节点查找和删除逻辑都比较简单,这里不再分析。节点插入的操作相对复杂一点,关键逻辑在于发现节点对应的主次桶都已经填满时的已有节点移动逻辑。这个逻辑在make_space_bucket函数中实现。

make_space_bucket函数主要分为两步:

1. 桶中是否有节点对应的次桶中还有空间?如果有的话就将该节点移动它的次桶中。代码如下:

/*
   * Push existing item (search for bucket with space in
   * alternative locations) to its alternative location
   */
  for (i = 0; i < RTE_HASH_BUCKET_ENTRIES; i++) {
    /* Search for space in alternative locations */
    next_bucket_idx = bkt->sig_alt[i] & h->bucket_bitmask;
    next_bkt[i] = &h->buckets[next_bucket_idx];
    for (j = 0; j < RTE_HASH_BUCKET_ENTRIES; j++) {
      if (next_bkt[i]->key_idx[j] == EMPTY_SLOT)
        break;
    }
    if (j != RTE_HASH_BUCKET_ENTRIES)
      break;
  }
  /* Alternative location has spare room (end of recursive function) */
  if (i != RTE_HASH_BUCKET_ENTRIES) {
    next_bkt[i]->sig_alt[j] = bkt->sig_current[i];
    next_bkt[i]->sig_current[j] = bkt->sig_alt[i];
    next_bkt[i]->key_idx[j] = bkt->key_idx[i];
    return i;
  }

2. 如果没有节点的次桶有空间了,则只能强行移动一个节点到次桶,再在次桶中再移动一个节点。这里会递归调用make_space_bucket函数来移动次桶中的节点。如果桶中的每个节点都已经被强行移动过,或者此次操作移动的节点数达到上限,则认为哈希表已满,操作失败。代码如下:

/* Pick entry that has not been pushed yet */
  for (i = 0; i < RTE_HASH_BUCKET_ENTRIES; i++)
    if (bkt->flag[i] == 0)
      break;
  /* All entries have been pushed, so entry cannot be added */
  if (i == RTE_HASH_BUCKET_ENTRIES || ++(*nr_pushes) > RTE_HASH_MAX_PUSHES)
    return -ENOSPC;
  /* Set flag to indicate that this entry is going to be pushed */
  bkt->flag[i] = 1;
  /* Need room in alternative bucket to insert the pushed entry */
  ret = make_space_bucket(h, next_bkt[i], nr_pushes);
  /*
   * After recursive function.
   * Clear flags and insert the pushed entry
   * in its alternative location if successful,
   * or return error
   */
  bkt->flag[i] = 0;
  if (ret >= 0) {
    next_bkt[i]->sig_alt[ret] = bkt->sig_current[i];
    next_bkt[i]->sig_current[ret] = bkt->sig_alt[i];
    next_bkt[i]->key_idx[ret] = bkt->key_idx[i];
    return i;
  } else
    return ret;

综合上述分析,可以发现,DPDK实现的hash库的最大优点,在于其一次查找的时间是有上限的,一般情况下最多产生3次随机内存访问:主桶、从桶、key节点。同时通过CuckooHash算法,很大程度上避免了普通的基于数组和桶的哈希表在同一个桶冲突严重的情况下节点会插入失败的问题。官方实验结果显示只有当哈希表节点使用率达到95%时才会出现插入失败,在50%以下时基本可以保证绝大部分节点都保存在主桶中。此外,通过signature和key两阶段比较的方法减少了key比较的次数,在key比较长时能提升一定的性能。

但这个方法的逻辑相当复杂,在表完全为空时,一次失败的查找也要访问两次内存,还要基于ring来获取释放数组索引资源,其中实现的本核心缓存逻辑感觉也不太合理。综合来看其性能未必高于普通的基于大规模哈希数组和链表的哈希表结构。采用时需要经过具体测试评估。

3.10多进程支持

DPDK库里是支持多进程和多线程,本文主要总结多进程的相关的操作。

DPDK多进程使用的关键启动参数:

  • --proc-type:指定一个dpdk进程是主进程还是副进程(参数值就用上面的primary或是secondary,或者是auto)
  • --file-prefix:允许非合作的进程拥有不同的内存区域。主副进程默认文件路径/var/run/.rte_config,同一个处理组的主副进程使用相同的参数,
    如果想运行多个主进程,这个参数就必须指定!
  • --socket-mem:设置从hugepages分配多大的存储空间。默认会用掉所有的hugepages,所以建议指定这个参数,不管是单cpu还是在NUMA中。
    eg:单socket,--socket-mem=512;在numa中,--socket-mem=512,512;多个socket间用‘,’号隔开;
  • -w : 后面跟网卡的PCI号,指定使用网卡。设置了这参数,DPDK只会使用这个参数对应的网卡,不会初始化其他的。

在Multi-process Sample Application中介绍了4种使用场景:

Basic Multi-process Example,DPDK进程间通过ring,内存池,队列,进行信息交互。

Symmetric Multi-process Example,主进程初始化所有资源,副进程直接获取资源进行数据包处理,副进程除了不初始化资源,数据包处理和主进程是一样的。每个进程获取每个端口的一个RX, TX队列。

Client-Server Multi-process Example,主进程初始化资源和接收所有收到的数据包并轮询分发给副进程处理。

Master-slave Multi-process Example,这个模式主要是介绍各进程之间存在依赖关系,主进程和副进程,副进程和副进程

[root@localhost simple_mp]#   ./build/simple_mp -l 0-1   --proc-type=primary
EAL: Detected 128 lcore(s)
EAL: Detected 4 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: PCI device 0000:05:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic
net_hinic: Initializing pf hinic-0000:05:00.0 in primary process
net_hinic: Device 0000:05:00.0 hwif attribute:
net_hinic: func_idx:0, p2p_idx:0, pciintf_idx:0, vf_in_pf:0, ppf_idx:0, global_vf_id:15, func_type:2
net_hinic: num_aeqs:4, num_ceqs:4, num_irqs:32, dma_attr:2
net_hinic: API CMD poll status timeout
net_hinic: chain type: 0x7
net_hinic: chain hw cpld error: 0x1
net_hinic: chain hw check error: 0x0
net_hinic: chain hw current fsm: 0x0
net_hinic: chain hw current ci: 0x0
net_hinic: Chain hw current pi: 0x1
net_hinic: Send msg to mgmt failed
net_hinic: Failed to get board info, err: -110, status: 0x0, out size: 0x0
net_hinic: Check card workmode failed, dev_name: 0000:05:00.0
net_hinic: Create nic device failed, dev_name: 0000:05:00.0
net_hinic: Initialize 0000:05:00.0 in primary failed
EAL: Requested device 0000:05:00.0 cannot be used
EAL: PCI device 0000:06:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic
EAL: PCI device 0000:7d:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:a222 net_hns3
EAL: PCI device 0000:7d:00.1 on NUMA socket 0
EAL:   probe driver: 19e5:a221 net_hns3
EAL: PCI device 0000:7d:00.2 on NUMA socket 0
EAL:   probe driver: 19e5:a222 net_hns3
EAL: PCI device 0000:7d:00.3 on NUMA socket 0
EAL:   probe driver: 19e5:a221 net_hns3
APP: Finished Process Init.
Starting core 1
simple_mp > primary send hello1
Command not found
simple_mp > send hello by primary
Bad arguments
simple_mp > 
simple_mp > send hello_by_primary
simple_mp > core 1: Received 'hello_by_sencondary'
simple_mp >

./build/simple_mp -l 0-1 --proc-type=secondary

[root@localhost lib]# ps -elf | grep simple_mp
0 S root       8457 124128 13  80   0 - 8389378 wait_w 03:24 pts/1  00:00:16 ./build/simple_mp -l 0-1 --proc-type=primary
0 S root       8471   7504  6  80   0 - 8389498 wait_w 03:24 pts/2  00:00:06 ./build/simple_mp -l 0-1 --proc-type=secondary
0 S root       8564  57486  0  80   0 -  1729 pipe_w 03:26 pts/0    00:00:00 grep --color=auto simple_mp
[root@localhost lib]# ps -mo pid,tid,%cpu,psr -p 8471
   PID    TID %CPU PSR
  8471      -  6.0   -
     -   8471  0.0   0
     -   8472  0.0  33
     -   8473  0.0  10
     -   8474  6.0   1
[root@localhost lib]# ps -T  -p 8471
   PID   SPID TTY          TIME CMD
  8471   8471 pts/2    00:00:00 simple_mp
  8471   8472 pts/2    00:00:00 eal-intr-thread
  8471   8473 pts/2    00:00:00 rte_mp_handle
  8471   8474 pts/2    00:00:10 lcore-slave-1
[root@localhost lib]# ps -mo pid,tid,%cpu,psr -p 8457
   PID    TID %CPU PSR
  8457      - 10.7   -
     -   8457  5.1   0
     -   8458  0.0   9
     -   8459  0.0   9
     -   8460  5.6   1
[root@localhost lib]# ps -T  -p 8457
   PID   SPID TTY          TIME CMD
  8457   8457 pts/1    00:00:10 simple_mp
  8457   8458 pts/1    00:00:00 eal-intr-thread
  8457   8459 pts/1    00:00:00 rte_mp_handle
  8457   8460 pts/1    00:00:11 lcore-slave-1
[root@localhost lib]#

两个mp_socket, mp_socket_8471_167e85391023是seconary进程的:

[root@localhost dpdk-stable-17.11.2]# killall simple_mp
[root@localhost dpdk-stable-17.11.2]# ps -elf | grep simple_mp
0 S root       9154  36716  0  80   0 -  1729 pipe_w 03:38 pts/3    00:00:00 grep --color=auto simple_mp
[root@localhost dpdk-stable-17.11.2]#
kill之后还存在哦[root@localhost lib]# ls /var/run/dpdk/rte/ -al
total 15680
drwx------. 2 root root   1100 Aug 28 03:24 .
drwx------. 3 root root     60 Aug 26 03:45 ..
-rw-------. 1 root root  18816 Aug 28 03:24 config
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-0
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-0_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-1
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-1_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-2
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-2_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-3
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-0-3_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-0
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-0_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-1
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-1_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-2
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-2_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-3
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-1-3_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-0
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-0_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-1
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-1_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-2
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-2_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-3
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-2-3_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-0
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-0_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-1
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-1_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-2
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-2_8471
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-3
-rw-------. 1 root root 458752 Aug 28 03:24 fbarray_memseg-2048k-3-3_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-0-0
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-0-0_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-0-1
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-0-1_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-1-0
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-1-0_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-1-1
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-1-1_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-2-0
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-2-0_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-2-1
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-2-1_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-3-0
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-3-0_8471
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-3-1
-rw-------. 1 root root  65536 Aug 28 03:24 fbarray_memseg-524288k-3-1_8471
-rw-------. 1 root root 196608 Aug 28 03:24 fbarray_memzone
-rw-------. 1 root root  16576 Aug 28 03:24 hugepage_info
srwxr-xr-x. 1 root root      0 Aug 28 03:24 mp_socket
srwxr-xr-x. 1 root root      0 Aug 28 03:24 mp_socket_8471_167e85391023
[root@localhost lib]#


[root@localhost simple_mp]#   ./build/simple_mp -l 126-127   --proc-type=primary
EAL: Detected 128 lcore(s)
EAL: Detected 4 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: PCI device 0000:05:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic
net_hinic: Initializing pf hinic-0000:05:00.0 in primary process
net_hinic: Device 0000:05:00.0 hwif attribute:
net_hinic: func_idx:0, p2p_idx:0, pciintf_idx:0, vf_in_pf:0, ppf_idx:0, global_vf_id:15, func_type:2
net_hinic: num_aeqs:4, num_ceqs:4, num_irqs:32, dma_attr:2
net_hinic: API CMD poll status timeout
net_hinic: chain type: 0x7
net_hinic: chain hw cpld error: 0x1
net_hinic: chain hw check error: 0x0
net_hinic: chain hw current fsm: 0x0
net_hinic: chain hw current ci: 0x0
net_hinic: Chain hw current pi: 0x1
net_hinic: Send msg to mgmt failed
net_hinic: Failed to get board info, err: -110, status: 0x0, out size: 0x0
net_hinic: Check card workmode failed, dev_name: 0000:05:00.0
net_hinic: Create nic device failed, dev_name: 0000:05:00.0
net_hinic: Initialize 0000:05:00.0 in primary failed
EAL: Requested device 0000:05:00.0 cannot be used
EAL: PCI device 0000:06:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic
EAL: PCI device 0000:7d:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:a222 net_hns3
EAL: PCI device 0000:7d:00.1 on NUMA socket 0
EAL:   probe driver: 19e5:a221 net_hns3
EAL: PCI device 0000:7d:00.2 on NUMA socket 0
EAL:   probe driver: 19e5:a222 net_hns3
EAL: PCI device 0000:7d:00.3 on NUMA socket 0
EAL:   probe driver: 19e5:a221 net_hns3
APP: Finished Process Init.
Starting core 127
simple_mp >


[root@localhost simple_mp]#   ./build/simple_mp -l 120-121  --proc-type=secondary
EAL: Detected 128 lcore(s)
EAL: Detected 4 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket_9543_169b3a72effc
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: PCI device 0000:05:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic
EAL: Cannot find resource for device
EAL: PCI device 0000:06:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic
EAL: PCI device 0000:7d:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:a222 net_hns3
EAL: PCI device 0000:7d:00.1 on NUMA socket 0
EAL:   probe driver: 19e5:a221 net_hns3
EAL: PCI device 0000:7d:00.2 on NUMA socket 0
EAL:   probe driver: 19e5:a222 net_hns3
EAL: PCI device 0000:7d:00.3 on NUMA socket 0
EAL:   probe driver: 19e5:a221 net_hns3
APP: Finished Process Init.
Starting core 121
simple_mp >


[root@localhost lib]# ls /var/run/dpdk/rte/ -al
total 15680
drwx------. 2 root root   1100 Aug 28 03:45 .
drwx------. 3 root root     60 Aug 26 03:45 ..
-rw-------. 1 root root  18816 Aug 28 03:45 config
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-3_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-3_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-3_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-3_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-1_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-1_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-1_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-1_9543
-rw-------. 1 root root 196608 Aug 28 03:45 fbarray_memzone
-rw-------. 1 root root  16576 Aug 28 03:45 hugepage_info
srwxr-xr-x. 1 root root      0 Aug 28 03:45 mp_socket
srwxr-xr-x. 1 root root      0 Aug 28 03:45 mp_socket_9543_169b3a72effc
[root@localhost lib]#


[root@localhost simple_mp]#   ./build/simple_mp -l 126-127   --proc-type=primary
EAL: Detected 128 lcore(s)
EAL: Detected 4 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: PCI device 0000:05:00.0 on NUMA socket 0
EAL:   probe driver: 19e5:200 net_hinic


simple_mp > Terminated
[root@localhost simple_mp]#   ./build/simple_mp -l 120-121  --proc-type=secondary
EAL: Detected 128 lcore(s)


[root@localhost lib]# ps -mo pid,tid,%cpu,psr -p 9530
   PID    TID %CPU PSR
  9530      -  6.9   -
     -   9530  0.2 126
     -   9531  0.0   9
     -   9532  0.0  10
     -   9533  6.6 127
[root@localhost lib]# ps -T  -p 9530
   PID   SPID TTY          TIME CMD
  9530   9530 pts/1    00:00:10 simple_mp
  9530   9531 pts/1    00:00:00 eal-intr-thread
  9530   9532 pts/1    00:00:00 rte_mp_handle
  9530   9533 pts/1    00:04:11 lcore-slave-127
[root@localhost lib]# ps -mo pid,tid,%cpu,psr -p 9543
   PID    TID %CPU PSR
  9543      -  6.6   -
     -   9543  0.0 120
     -   9544  0.0  10
     -   9545  0.0  11
     -   9546  6.6 121
[root@localhost lib]# ps -T  -p 9543
   PID   SPID TTY          TIME CMD
  9543   9543 pts/2    00:00:00 simple_mp
  9543   9544 pts/2    00:00:00 eal-intr-thread
  9543   9545 pts/2    00:00:00 rte_mp_handle
  9543   9546 pts/2    00:04:08 lcore-slave-121
[root@localhost lib]#


[root@localhost dpdk-stable-17.11.2]# ls /var/run/dpdk/rte/ -al
total 15680
drwx------. 2 root root   1100 Aug 28 03:45 .
drwx------. 3 root root     60 Aug 26 03:45 ..
-rw-------. 1 root root  18816 Aug 28 03:45 config
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-0-3_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-1-3_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-2-3_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-0
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-0_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-1
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-1_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-2
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-2_9543
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-3
-rw-------. 1 root root 458752 Aug 28 03:45 fbarray_memseg-2048k-3-3_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-0-1_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-1-1_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-2-1_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-0
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-0_9543
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-1
-rw-------. 1 root root  65536 Aug 28 03:45 fbarray_memseg-524288k-3-1_9543
-rw-------. 1 root root 196608 Aug 28 03:45 fbarray_memzone
-rw-------. 1 root root  16576 Aug 28 03:45 hugepage_info
srwxr-xr-x. 1 root root      0 Aug 28 03:45 mp_socket
srwxr-xr-x. 1 root root      0 Aug 28 03:45 mp_socket_9543_169b3a72effc
[root@localhost dpdk-stable-17.11.2]# ls

退出


相关文章
|
7月前
|
存储 传感器 Linux
Linux应用开发基础知识——I2C应用编程(十二)
Linux应用开发基础知识——I2C应用编程(十二)
277 0
Linux应用开发基础知识——I2C应用编程(十二)
|
7月前
|
传感器 Linux 开发工具
Linux应用开发基础知识——I2C应用编程(十三)
Linux应用开发基础知识——I2C应用编程(十三)
356 0
Linux应用开发基础知识——I2C应用编程(十三)
|
3月前
|
网络协议 安全 网络安全
震惊!Python Socket竟能如此玩转网络通信,基础到进阶全攻略!
【9月更文挑战第12天】在网络通信中,Socket编程是连接不同应用与服务的基石。本文通过问答形式,从基础到进阶全面解析Python Socket编程。涵盖Socket的重要性、创建TCP服务器与客户端、处理并发连接及进阶话题如非阻塞Socket、IO多路复用等,帮助读者深入了解并掌握网络通信的核心技术。
144 6
|
5月前
|
算法 JavaScript 前端开发
编程入门:从零开始学习编程的方法与步骤
编程入门:从零开始学习编程的方法与步骤
|
存储 安全 Linux
从零开始学习DPDK:掌握这些常用库函数就够了(上)
从零开始学习DPDK:掌握这些常用库函数就够了
|
编译器 程序员 C语言
游戏编程之二 windows编程基础
游戏编程之二 windows编程基础
84 0
|
存储 编解码 API
游戏编程之四 diectxdarw基础篇
游戏编程之四 diectxdarw基础篇
72 0
|
负载均衡 安全 网络协议
网络编程懒人入门(十五):外行也能读懂的网络硬件设备功能原理速成
本文是《网络编程懒人入门》系列文章的第15篇,本篇将继续以通俗易懂的文字,帮你无脑理解各种基础网络硬件设备的功能原理。
133 0
|
Linux
【Linux网络编程】服务端编程初体验
【Linux网络编程】服务端编程初体验
106 0
|
存储 人工智能 算法
C++ Primer Plus 第6版 读书笔记(7)第 7 章 函数——C++的编程模块
乐趣在于发现。仔细研究,读者将在函数中找到乐趣。C++自带了一个包含函数的大型库(标准 ANSI 库加上多个 C++类),但真正的编程乐趣在于编写自己的函数;另一方面,要提高编程效率,本章和第 8 章介绍如何定义函数、给函数传递信息以及从函数那里获得信息。
172 0