20个基于DPDL开源项目,带你冲破内核瓶颈(中)

简介: 20个基于DPDL开源项目,带你冲破内核瓶颈(中)

六、VPP数据包处理框架

VPP(Vector Packet Processing)是一个高性能的数据包处理框架,它是Cisco开源的一个项目。VPP提供了一种可扩展的、用户空间的数据包处理引擎,可以通过与多种硬件平台和操作系统集成来实现快速、可扩展的网络应用程序。

VPP支持多个协议栈(例如TCP/IP、UDP/IP等),并提供了一系列基本功能模块,如ARP缓存管理、IP地址管理、ACL过滤等。它还支持虚拟化和容器化部署,并具有灵活的API和可扩展的插件架构,可以轻松地定制以适应不同类型的网络应用程序需求。

VPP是一个高度可编程化和可定制化的数据包处理框架,在运营商级网络设备、云计算环境中得到广泛应用。除了Cisco之外,许多公司和组织也在使用VPP构建自己的网络产品或服务。

以下安装方式在centos7上安装测试(可用)

有三种安装方式:源码安装、yum安装、vpp-config安装

源码安装:

1.使用git将VPP源码克隆下来(没有git可使用 yum install git -y 安装)

[root@localhost ~]# mkdir source
[root@localhost ~]# cd source
[root@localhost source]# git clone https://gerrit.fd.io/r/vpp

2. 安装依赖环境,进入VPP目录下执行:

[root@localhost source]# cd vpp
[root@localhost vpp]# yum install -y epel-release python-pip net-tools
[root@localhost vpp]# make install-dep

3. 安装dpdk,执行第4步代码编译时,会自动下载dpdk并一起编译(可忽略)

[root@localhost vpp]# make dpdk-install-dev

4. 进行代码编译(make distclean可以清除编译生成文件 )

[root@localhost vpp]# make build

5. 制作rpm包

[root@localhost vpp]# make pkg-rpm

6. 安装VPP

[root@localhost vpp]# cd build-root/
[root@localhost build-root]# rpm -i vpp*.rpm

7. 启动VPP(并设置开机启动)

[root@localhost ~]# systemctl enable vpp
[root@localhost ~]# systemctl start vpp
[root@localhost ~]# systemctl status vpp.service

8.测试安装是否成功

root@localhost ~]# vppctl
显示如下代表安装成功:

七、ClickOS高性能虚拟机

ClickOS是一种专门为云计算设计的高性能虚拟机,它基于Xen虚拟化技术,并集成了高效的网络协议栈和数据平面加速技术。ClickOS主要用于构建可定制、低延迟、高吞吐量的网络功能虚拟化(NFV)系统,如防火墙、负载均衡器、路由器等。ClickOS的特点在于其精简的操作系统内核,以及对热插拔设备和硬件辅助虚拟化技术的支持,使得它可以实现比传统虚拟机更高的性能和更低的资源开销。

八、http://FD.io数据平面开发工具包

http://FD.io是一个开放源代码的数据平面开发工具包,它提供了一组数据平面服务和库,以便于构建高性能、可扩展、灵活的网络功能虚拟化(NFV)系统。http://FD.io旨在为各种硬件和软件环境下的网络应用程序提供通用的、高效的数据平面支持,并且可以快速地配置和部署。http://FD.io项目由Linux基金会主导,采用Apache 2.0许可证发布,目前已成为全球最大的数据平面开源社区之一。

http://FD.io的一个关键项目是VPP(Vector Packet Processing:矢量报文处理)。VPP是高度模块化的项目,新开发的功能模块很容易被集成进VPP,而不影响VPP底层的代码框架。这就给了开发者很大的灵活性,可以创新不计其数的报文处理解决方案。

除了VPP,http://FD.io充分利用DPDK特性以支持额外的项目,包括NSH_SFC, Honeycomb 和ONE来加速网络功能虚拟化的数据面。此外,http://FD.io还与其他关键的开源项目进行集成,以支持网络功能虚拟化和软件定义网络。目前已经集成的开源项目包括:K8s、OpenStack、ONAP和OpenDaylight。

下图是http://FD.io的网络生态系统概览:

VPP简介

VPP到底是什么?一个软件路由器?一个虚拟交换机?一个虚拟网络功能?事实上,它包含所有这些,并且包含更多。VPP是一个模块化和可扩展的软件框架,用于创建网络数据面应用程序。更重要的是,VPP代码为现代通用处理器平台(x86、ARM、PowerPC等)而生,并把重点放在优化软件和硬件接口上,以便用于实时的网络输入输出操作和报文处理。

VPP充分利用通用处理器优化技术,包括矢量指令(例如Intel SSE, AVX)以及I/O和CPU缓存间的直接交互(例如 Intel DDIO),以达到最好的报文处理性能。利用这些优化技术的好处是:使用最少的CPU核心指令和时钟周期来处理每个报文。在最新的Intel Xeon-SP处理器上,可以达到Tbps的处理性能。

VPP架构

VPP是一个有效且灵活的数据面,它包括一系列按有向图组织的转发图形节点(graph node)和一个软件框架。该软件框架包含基本的数据结构、定时器、驱动程序、在图形节点间分配CPU时间片的调度器、性能调优工具,比如计数器和内建的报文跟踪功能。

VPP采用插件(plugin)架构,插件与直接内嵌于VPP框架中的模块一样被同等对待。原则上,插件是实现某一特定功能的转发图形节点,但也可以是一个驱动程序,或者另外的CLI。插件能被插入到VPP有向图的任意位置,从而有利于快速灵活地开发新功能。因此,插件架构使开发者能够充分利用现有模块快速开发出新功能。

VPP架构:报文处理有向图

输入节点轮询(或中断驱动)接口的接收队列,获取批量报文。接着把这些报文按照下个节点功能组成一个矢量(vector)或者一帧(frame)。比如:输入节点收集所有IPv4的报文并把它们传递给ip4-input节点;输入节点收集所有IPv6的报文并把它们传递给ip6-input节点。当ip6-input节点被调度时,它取出这一帧报文,利用双循环(dual-loop) 或四循环(quad-loop)以及预取报文到CPU缓存技术处理报文,以达到最优性能。这能够通过减少缓存未命中数来有效利用CPU缓存。当ip6-input节点处理完当前帧的所有报文后,把报文传递到后续不同的节点。比如:如果某报文校验失败,就被传送到error-drop节点;正常报文被传送到ip6-lookup节点。一帧报文依次通过不同的图形节点,直到它们被interface-output节点发送出去。

VPP图形节点的处理逻辑

按照网络功能一次处理一帧报文,有几个好处:

  • 从软件工程的角度看,每一个图形节点是独立和自治的。
  • 从性能的角度看,主要的好处是可以优化CPU指令缓存(i-cache)的使用。当前帧的第一个报文加载当前节点的指令到指令缓存,当前帧的后续报文就可以“免费”使用指令缓存。这里,VPP充分利用了CPU的超标量结构,使报文内存加载和报文处理交织进行,达到更有效地利用CPU处理流水线。
  • VPP也充分利用了CPU的预测执行功能来达到更好的性能。从预测重用报文间的转发对象(比如邻接表和路由查找表),以及预先加载报文内容到CPU的本地数据缓存(d-cache)供下一次循环使用,这些有效使用计算硬件的技术,使得VPP可以利用更细粒度的并行性。

VPP有向图处理的特性,使它成为一个松耦合、高度一致的软件架构。每一个图形节点利用一帧报文作为输入和输出的最小处理单位,这就提供了松耦合的特性。通用功能被组合到每个图形节点中,这就提供了高度一致的架构。

在有向图中的节点是可替代的。当这个特性和VPP支持动态加载插件节点相结合时,新功能能被快速开发,而不需要新建和编译一个定制的代码版本。

九、Pktgen-DPDK数据包生成工具

Pktgen-DPDK是一种基于DPDK(Data Plane Development Kit)的数据包生成工具,可以用于测试和评估网络设备、协议栈等各种网络应用程序。它提供了丰富的功能,可以进行不同类型和大小的数据包生成,以及流量控制、速率控制等测试。此外,Pktgen-DPDK还支持多核处理、超线程和NUMA架构,并提供了Web界面来方便用户使用。由于采用了DPDK技术,Pktgen-DPDK具有非常高的性能和低延迟,并广泛应用于云计算、虚拟化、SDN/NFV等领域。

pktgen-dpdk使用dpdk加速包的发送接收,也可以发送接收pcap包,命令行如下:

./app/app/x86_64-native-linuxapp-gcc/pktgen -l 0-4 -n 3 -- -P -m "[11:3].0,[2:4].1" -s 0:[.pcap_filepath] (pktgen-dpdk.3.4.8)

pktgen-dpdk逻辑设计

在pktgen-main.c文件中包含了main主入口函数main()以及参数配置函数pktgen_parse_args(),其中pktgen结构体是所有的参数都是用的,参数配置函数主要是对pktgen结构中个成员赋值。

main函数中pktgen初始化如下:

memset(&pktgen, 0, sizeof(pktgen));
pktgen.flags            = PRINT_LABELS_FLAG;
pktgen.ident            = 0x1234;
pktgen.nb_rxd           = DEFAULT_RX_DESC;
pktgen.nb_txd           = DEFAULT_TX_DESC;
pktgen.nb_ports_per_page = DEFAULT_PORTS_PER_PAGE;
if ( (pktgen.l2p = l2p_create()) == NULL)
  pktgen_log_panic("Unable to create l2p");
pktgen.portdesc_cnt = get_portdesc(pktgen.portlist,
             pktgen.portdesc,
             RTE_MAX_ETHPORTS,
             0);

然后初始化log,cpu

pktgen_init_log();
  pktgen_cpu_init();

配置初始化port信息

void
pktgen_config_ports(void)
{
  uint32_t lid, pid, i, s, q, sid;
  rxtx_t rt;
  pkt_seq_t   *pkt;
  port_info_t     *info;
  char buff[RTE_MEMZONE_NAMESIZE];
  int32_t ret, cache_size;
  char output_buff[256] = { 0 };
  uint64_t ticks;
  /* Find out the total number of ports in the system. */
  /* We have already blacklisted the ones we needed to in main routine. */
  pktgen.nb_ports = rte_eth_dev_count();
  if (pktgen.nb_ports > RTE_MAX_ETHPORTS)
    pktgen.nb_ports = RTE_MAX_ETHPORTS;
  if (pktgen.nb_ports == 0)
    pktgen_log_panic("*** Did not find any ports to use ***");
  pktgen.starting_port = 0;
  /* Setup the number of ports to display at a time */
  if (pktgen.nb_ports > pktgen.nb_ports_per_page)
    pktgen.ending_port = pktgen.starting_port +
      pktgen.nb_ports_per_page;
  else
    pktgen.ending_port = pktgen.starting_port + pktgen.nb_ports;
  pg_port_matrix_dump(pktgen.l2p);
....
....
    for (s = 0; s < NUM_TOTAL_PKTS; s++)
      pktgen_port_defaults(pid, s);

其中主要获取cpu端口数,以及填充结构体info,其中函数pktgen_port)defaults()主要设置一些默认显示的信息,其中里面的函数pktgen_packet_rate()计算传输率,此函数作用与动态的展示发送接收的数据参数。

main()往下调用的函数是pktgen_clear_display():

void
pktgen_clear_display(void)
{
  if (!scrn_is_paused()) {
    scrn_pause();
    scrn_cls();
    scrn_pos(100, 1);
    pktgen_update_display();
    scrn_resume();
    pktgen_page_display(NULL, NULL);
  }
}

其中pktgen_update_dsiplay()修改pktgen.flag标志:

void
pktgen_page_display(struct rte_timer *tim __rte_unused, void *arg __rte_unused)
{
  static unsigned int update_display = 1;
  /* Leave if the screen is paused */
  if (scrn_is_paused())
    return;
  scrn_save();
  if (pktgen.flags & UPDATE_DISPLAY_FLAG) {
    pktgen.flags &= ~UPDATE_DISPLAY_FLAG;
    update_display = 1;
  }
  update_display--;
  if (update_display == 0) {
    update_display = UPDATE_DISPLAY_TICK_INTERVAL;
    _page_display();
    if (pktgen.flags & PRINT_LABELS_FLAG)
      pktgen.flags &= ~PRINT_LABELS_FLAG;
  }
  scrn_restore();
  pktgen_print_packet_dump();
}

函数中主要执行两个函数_page_display(),pktgen_printf_paket_dump(),前者调用pktgen_page_stats(),显示端口上的统计信息:

void
pktgen_page_stats(void)
{
  port_info_t *info;
  unsigned int pid, col, row;
  struct rte_eth_stats *rate, *cumm, *prev;
  unsigned sp;
  char buff[32];
  int display_cnt;
  if (pktgen.flags & PRINT_LABELS_FLAG)
    pktgen_print_static_data();
...
...

其中函数pktgen_print_static_data()显示一些静态数据,动态数据主要通过info结构体传输。

然后main函数调用rte_timer_setup()

void
rte_timer_setup(void)
{
  int lcore_id = rte_get_master_lcore();
  /* init RTE timer library */
  rte_timer_subsystem_init();
  /* init timer structures */
  rte_timer_init(&timer0);
  rte_timer_init(&timer1);
  /* load timer0, every 1/2 seconds, on Display lcore, reloaded automatically */
  rte_timer_reset(&timer0,
      UPDATE_DISPLAY_TICK_RATE,
      PERIODICAL,
      lcore_id,
      pktgen_page_display,
      NULL);
  /* load timer1, every second, on timer lcore, reloaded automatically */
  rte_timer_reset(&timer1,
      pktgen.hz,
      PERIODICAL,
      lcore_id,
      pktgen_process_stats,
      NULL);
}

其中timer0,用于更新显示,timer1用于统计数据。

main函数接着调用pktgen_cli_start(),最终调用cli_start(),用于执行运行时参数。

pktgen 流量生成器

下面说一下发送接收包,在main函数的前部分还有一个重要的函数:

ret = rte_eal_remote_launch(pktgen_launch_one_lcore, NULL, i);

/* Configure and initialize the ports */
  pktgen_config_ports();
  pktgen_log_info("");
  pktgen_log_info("=== Display processing on lcore %d", rte_lcore_id());
  /* launch per-lcore init on every lcore except master and master + 1 lcores */
  for (i = 0; i < RTE_MAX_LCORE; i++) {
    if ( (i == rte_get_master_lcore()) || !rte_lcore_is_enabled(i) )
      continue;
    ret = rte_eal_remote_launch(pktgen_launch_one_lcore, NULL, i);
    if (ret != 0)
      pktgen_log_error("Failed to start lcore %d, return %d", i, ret);
  }
  rte_delay_ms(1000); /* Wait for the lcores to start up. */

其中pktgen_launch-one_lcore()调用pktgen_main_tx_loop(),pktgen_mian_rx_loop(),pktgen_main_rxtx_loop()。

这些函数相应的调用函数,同时更新pktgen结构或是其中的成员,以pktgen_main_rxtx_loop()为例。

pktgen_main_rxtx_loop() -> pktgen_main_transmit() -> pktgen_send_pkts() ->pktgen_send_burst() -> _send_burst_..()-> pkt_do_tx_tap() -> write()

下面说一下pktgen作为流量生成器的一些个人理解。

pktgen作为linux内核模块的一部分,因此可以直接加载内核模块generate流量(lsmod pktgen),网上参考资料说是产生udp类型的packet,在此详细探讨一下。

在pktgen_main_transmit()中:

static __inline__ void
pktgen_main_transmit(port_info_t *info, uint16_t qid)
{
  struct rte_mempool *mp = NULL;
  uint32_t flags;
  flags = rte_atomic32_read(&info->port_flags);
  /*
   * Transmit ARP/Ping packets if needed
   */
  if ((flags & SEND_ARP_PING_REQUESTS))
    pktgen_send_special(info, flags);
  /* When not transmitting on this port then continue. */
  if (flags & SENDING_PACKETS) {
    mp = info->q[qid].tx_mp;
    if (flags & (SEND_RANGE_PKTS | SEND_PCAP_PKTS | SEND_SEQ_PKTS)) {
      if (flags & SEND_RANGE_PKTS)
        mp = info->q[qid].range_mp;
      else if (flags & SEND_SEQ_PKTS)
        mp = info->q[qid].seq_mp;
      else if (flags & SEND_PCAP_PKTS)
        mp = info->q[qid].pcap_mp;
    }
    if (rte_atomic32_read(&info->q[qid].flags) & CLEAR_FAST_ALLOC_FLAG)
      pktgen_setup_packets(info, mp, qid);
    pktgen_send_pkts(info, qid, mp);
  }
  flags = rte_atomic32_read(&info->q[qid].flags);
  if (flags & DO_TX_FLUSH)
    pktgen_tx_flush(info, qid);
}

最终调用函数pktgen_setup_cb(),调用了pktgen_range_ctor()和pktgen_packet_ctor()

其中结构体:

typedef struct pkt_seq_s {
  /* Packet type and information */
  struct ether_addr eth_dst_addr; /**< Destination Ethernet address */
  struct ether_addr eth_src_addr; /**< Source Ethernet address */
  struct cmdline_ipaddr ip_src_addr;  /**< Source IPv4 address also used for IPv6 */
  struct cmdline_ipaddr ip_dst_addr;  /**< Destination IPv4 address */
  uint32_t ip_mask;     /**< IPv4 Netmask value */
  uint16_t sport;   /**< Source port value */
  uint16_t dport;   /**< Destination port value */
  uint16_t ethType; /**< IPv4 or IPv6 */
  uint16_t ipProto; /**< TCP or UDP or ICMP */
  uint16_t vlanid;  /**< VLAN ID value if used */
  uint8_t cos;    /**< 802.1p cos value if used */
  uint8_t tos;    /**< tos value if used */
  uint16_t ether_hdr_size;/**< Size of Ethernet header in packet for VLAN ID */
  uint32_t mpls_entry;  /**< MPLS entry if used */
  uint16_t qinq_outerid;  /**< Outer VLAN ID if Q-in-Q */
  uint16_t qinq_innerid;  /**< Inner VLAN ID if Q-in-Q */
  uint32_t gre_key; /**< GRE key if used */
  uint16_t pktSize; /**< Size of packet in bytes not counting FCS */
  uint16_t pad0;
  uint32_t gtpu_teid; /**< GTP-U TEID, if UDP dport=2152 */
  uint8_t seq_enabled;  /**< Enable or disable this sequence through GUI */
  pkt_hdr_t hdr __rte_cache_aligned;  /**< Packet header data */
  uint8_t pad[MBUF_SIZE - sizeof(pkt_hdr_t)];
} pkt_seq_t __rte_cache_aligned;


void  pktgen_packet_ctor(port_info_t *info, int32_t seq_idx, int32_t type){
.....
  if (likely(pkt->ethType == ETHER_TYPE_IPv4)) {
    if (likely(pkt->ipProto == PG_IPPROTO_TCP)) {        //构造TCP
      if (pkt->dport != PG_IPPROTO_L4_GTPU_PORT) {
        /* Construct the TCP header */
        pktgen_tcp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4);
        /* IPv4 Header constructor */
        pktgen_ipv4_ctor(pkt, l3_hdr);
      } else {
        /* Construct the GTP-U header */
        pktgen_gtpu_hdr_ctor(pkt, l3_hdr, pkt->ipProto,
            GTPu_VERSION | GTPu_PT_FLAG, 0, 0, 0);
        /* Construct the TCP header */
        pktgen_tcp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4);
        /* IPv4 Header constructor */
        pktgen_ipv4_ctor(pkt, l3_hdr);
      }
    } else if (pkt->ipProto == PG_IPPROTO_UDP) {
      if (pkt->dport != PG_IPPROTO_L4_GTPU_PORT) {
        /* Construct the UDP header */
        pktgen_udp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4);
        /* IPv4 Header constructor */
        pktgen_ipv4_ctor(pkt, l3_hdr);
      } else {
        /* Construct the GTP-U header */
        pktgen_gtpu_hdr_ctor(pkt, l3_hdr, pkt->ipProto,
            GTPu_VERSION | GTPu_PT_FLAG, 0, 0, 0);
        /* Construct the UDP header */
        pktgen_udp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4);
        /* IPv4 Header constructor */
        pktgen_ipv4_ctor(pkt, l3_hdr);
      }
    } else if (pkt->ipProto == PG_IPPROTO_ICMP) {
      udpip_t           *uip;
      icmpv4Hdr_t       *icmp;
      /* Start from Ethernet header */
      uip = (udpip_t *)l3_hdr;
      /* Create the ICMP header */
      uip->ip.src         = htonl(pkt->ip_src_addr.addr.ipv4.s_addr);
      uip->ip.dst         = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr);
      tlen                = pkt->pktSize - (pkt->ether_hdr_size + sizeof(ipHdr_t));
      uip->ip.len         = htons(tlen);
      uip->ip.proto       = pkt->ipProto;
      icmp = (icmpv4Hdr_t *)&uip->udp;
      icmp->code                      = 0;
      if ( (type == -1) || (type == ICMP4_TIMESTAMP)) {
        icmp->type                      =
          ICMP4_TIMESTAMP;
        icmp->data.timestamp.ident      = 0x1234;
        icmp->data.timestamp.seq        = 0x5678;
        icmp->data.timestamp.originate  = 0x80004321;
        icmp->data.timestamp.receive    = 0;
        icmp->data.timestamp.transmit   = 0;
      } else if (type == ICMP4_ECHO) {
        icmp->type                      = ICMP4_ECHO;
        icmp->data.echo.ident           = 0x1234;
        icmp->data.echo.seq             = 0x5678;
        icmp->data.echo.data            = 0;
      }
      icmp->cksum     = 0;
      /* ICMP4_TIMESTAMP_SIZE */
      tlen            = pkt->pktSize - (pkt->ether_hdr_size + sizeof(ipHdr_t));
      icmp->cksum     = cksum(icmp, tlen, 0);
      if (icmp->cksum == 0)
        icmp->cksum = 0xFFFF;
      /* IPv4 Header constructor */
      pktgen_ipv4_ctor(pkt, l3_hdr);
    }
  } else if (pkt->ethType == ETHER_TYPE_IPv6) {
    if (pkt->ipProto == PG_IPPROTO_TCP) {
      /* Construct the TCP header */
      pktgen_tcp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv6);
      /* IPv6 Header constructor */
      pktgen_ipv6_ctor(pkt, l3_hdr);
    } else if (pkt->ipProto == PG_IPPROTO_UDP) {
      /* Construct the UDP header */
      pktgen_udp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv6);
      /* IPv6 Header constructor */
      pktgen_ipv6_ctor(pkt, l3_hdr);
    }
  } else if (pkt->ethType == ETHER_TYPE_ARP) {
    /* Start from Ethernet header */
    arpPkt_t *arp = (arpPkt_t *)l3_hdr;
    arp->hrd = htons(1);
    arp->pro = htons(ETHER_TYPE_IPv4);
    arp->hln = ETHER_ADDR_LEN;
    arp->pln = 4;
    /* FIXME make request/reply operation selectable by user */
    arp->op  = htons(2);
    ether_addr_copy(&pkt->eth_src_addr,
        (struct ether_addr *)&arp->sha);
    arp->spa._32 = htonl(pkt->ip_src_addr.addr.ipv4.s_addr);
    ether_addr_copy(&pkt->eth_dst_addr,
        (struct ether_addr *)&arp->tha);
    arp->tpa._32 = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr);
  }

这里主要是根据类型填充首部结构,其中pktgen_tcp_hdr_ctor()填充ip首部和tcp首部,return l3_hdr,然后在调用pktgen_ipv4_ctor()填充ip header对于pktgen_range_ctor(), 主要是根据range_info_t结构体重的最大最下值以及步长递增地修改首部结构。

从上面可以看书pktgen不只是产生udp类型的数据包,也可以产生其他类型的数据包,但是只是产生了流量,而没有payload。

pktgen-dpdk发送pcap包

发送pcap数据包需要使用 -s:[pcap.filepcath]参数。

static int pktgen_parse_args(int argc, char **argv)
{
....
    case 's': /* Read a PCAP packet capture file (stream) */
      port = strtol(optarg, NULL, 10);   //将字符串port转换为长整型port
      p = strchr(optarg, ':');      // ++p指向待发送的pcap文件
      if ( (p == NULL) ||
           (pktgen.info[port].pcap =
              _pcap_open(++p, port)) == NULL) {
        pktgen_log_error(
          "Invalid PCAP filename (%s) must include port number as P:filename",
          optarg);
        pktgen_usage(prgname);
        return -1;
.......
}

此处p指向pcap文件(此处为绝对路劲)

pcap_info_t *
_pcap_open(char *filename, uint16_t port)
{
  pcap_info_t   *pcap = NULL;
  if (filename == NULL) {
    printf("%s: filename is NULL\n", __FUNCTION__);
    goto leave;
  }
  pcap = (pcap_info_t *)rte_malloc("PCAP info",
           sizeof(pcap_info_t),
           RTE_CACHE_LINE_SIZE);
  if (pcap == NULL) {
    printf("%s: malloc failed for pcap_info_t structure\n",
           __FUNCTION__);
    goto leave;
  }
  memset((char *)pcap, 0, sizeof(pcap_info_t));
  pcap->fd = fopen((const char *)filename, "r");
  if (pcap->fd == NULL) {
    printf("%s: failed for (%s)\n", __FUNCTION__, filename);
    goto leave;
  }
  if (fread(&pcap->info, 1, sizeof(pcap_hdr_t),
      pcap->fd) != sizeof(pcap_hdr_t) ) {
    printf("%s: failed to read the file header\n", __FUNCTION__);
    goto leave;
  }
  /* Default to little endian format. */
  pcap->endian    = LITTLE_ENDIAN;
  pcap->filename  = strdup(filename);
  /* Make sure we have a valid PCAP file for Big or Little Endian formats. */
  if ( (pcap->info.magic_number != PCAP_MAGIC_NUMBER) &&
       (pcap->info.magic_number != ntohl(PCAP_MAGIC_NUMBER)) ) {
    printf("%s: Magic Number does not match!\n", __FUNCTION__);
    fflush(stdout);
    goto leave;
  }
  /* Convert from big-endian to little-endian. */
  if (pcap->info.magic_number == ntohl(PCAP_MAGIC_NUMBER) ) {
    printf(
      "PCAP: Big Endian file format found, converting to little endian\n");
    pcap->endian                = BIG_ENDIAN;
    pcap->info.magic_number     = ntohl(pcap->info.magic_number);
    pcap->info.network          = ntohl(pcap->info.network);
    pcap->info.sigfigs          = ntohl(pcap->info.sigfigs);
    pcap->info.snaplen          = ntohl(pcap->info.snaplen);
    pcap->info.thiszone         = ntohl(pcap->info.thiszone);
    pcap->info.version_major    = ntohs(pcap->info.version_major);
    pcap->info.version_minor    = ntohs(pcap->info.version_minor);
  }
  _pcap_info(pcap, port, 0);
  return pcap;
leave:
  _pcap_close(pcap);
  fflush(stdout);
  return NULL;
}

在 pkgen_config_ports()中

/* Setup the PCAP file for each port */
      if (pktgen.info[pid].pcap != NULL)
        if (pktgen_pcap_parse(pktgen.info[pid].pcap, info, q) == -1)
          pktgen_log_panic("Cannot load PCAP file for port %d", pid);
      /* Find out the link speed to program the WTHRESH value correctly. */
      pktgen_get_link_status(info, pid, 0);

其中调用了pktgen_pcap_parse(),在里面读取pcap文件数据的函数是_pcap_read()

size_t
_pcap_read(pcap_info_t *pcap,
     pcaprec_hdr_t *pHdr,
     char *pktBuff,
     uint32_t bufLen)
{
  do {
    if (fread(pHdr, 1, sizeof(pcaprec_hdr_t),
        pcap->fd) != sizeof(pcaprec_hdr_t) )
      return 0;
    /* Convert the packet header to the correct format. */
    _pcap_convert(pcap, pHdr);
    /* Skip packets larger then the buffer size. */
    if (pHdr->incl_len > bufLen) {
      (void)fseek(pcap->fd, pHdr->incl_len, SEEK_CUR);
      return pHdr->incl_len;
    }
    return fread(pktBuff, 1, pHdr->incl_len, pcap->fd);
  } while (1);
}

然后调用dpdk的rte_mempool_create(),可以看到每次只能发送一个pcap文件,如果需要发送多个pcap文件,则需要解析多个pcap文件路劲,赋值给pcap.fd(可以使用缓存预先存储)。

相关文章
|
JavaScript API 虚拟化
20个基于DPDL开源项目,带你冲破内核瓶颈(上)
20个基于DPDL开源项目,带你冲破内核瓶颈
|
安全 架构师 编译器
鲲鹏开发重点-–扭转x86乾坤的挑战,ARM64内存模型
因为X86及其CISC架构生态的封闭性,中国市场对未来处理器的选择,将是更开放、更模块化的RISC架构。 鲲鹏处理器就是符合这个潮流的创新产品和生态,将直面一系列挑战,和Apple一样赢得这场挑战,来扭转X86的封闭性的乾坤,创造出中国的处理器新生态。
1078 0
鲲鹏开发重点-–扭转x86乾坤的挑战,ARM64内存模型
|
3月前
|
算法 调度 UED
揭秘操作系统背后的暗战:进程调度与优先级反转的惊心动魄!
【8月更文挑战第21天】操作系统核心管理计算机资源,进程调度为其关键功能,决定CPU使用权,影响系统性能与用户体验。优先级反转是多任务环境下常见挑战:高优先级进程因等待低优先级进程占用的资源而被阻塞,导致系统效率下降。通过优先级继承或提升机制可解决此问题,确保系统资源高效利用与响应时间优化。
41 1
|
1月前
|
边缘计算 算法 调度
探究操作系统的心脏:调度算法的进化与影响
【10月更文挑战第2天】 本文深入探讨了操作系统中核心组件——调度算法的历史演变、关键技术突破及其对现代计算的影响。通过详细回顾从单任务到多任务、实时系统及分布式计算环境下调度算法的发展,文章揭示了这些算法如何塑造我们的数字世界,并对未来的趋势进行了展望。不同于传统的摘要,本文特别聚焦于技术细节与实际应用的结合点,为读者提供一幅清晰的技术演进蓝图。
45 4
|
3月前
|
存储 人工智能 算法
操作系统的演化之路:从单一任务到多任务处理
【8月更文挑战第12天】 在计算机科学的历史长河中,操作系统作为硬件与软件之间的桥梁,其发展经历了由简单到复杂、由单一到多元的转变。本文旨在探究操作系统如何实现从执行单个任务到同时管理多个任务的飞跃,并分析这一变革对现代计算技术的影响。通过回顾操作系统的关键发展阶段,我们将理解多任务处理机制的起源和优化过程,以及它如何塑造了今天的数字世界。
|
2月前
|
机器学习/深度学习 算法 物联网
探究操作系统的心脏:调度算法的演变与优化
本文旨在深入探讨操作系统中核心组件——调度算法的发展脉络与优化策略。通过分析从单任务到多任务、实时系统的演进过程,揭示调度算法如何作为系统性能瓶颈的解决关键,以及在云计算和物联网新兴领域中的应用前景。不同于传统摘要,本文将注重于概念阐释与实例分析相结合,为读者提供直观且全面的理解视角。
|
2月前
|
存储 安全 算法
探索操作系统的心脏:内核架构与机制的深度剖析
本文旨在深入探讨操作系统的核心——内核,揭示其架构设计与运行机制的内在奥秘。通过对进程管理、内存管理、文件系统、设备控制及网络通信等关键组件的细致分析,展现内核如何高效协调计算机硬件与软件资源,确保系统稳定运行与性能优化。文章融合技术深度与通俗易懂的表述方式,旨在为读者构建一幅清晰、立体的内核运作全景图。
66 0
|
3月前
|
人工智能 并行计算 大数据
操作系统的演变之路:从单任务到多任务再到并行计算
本文将探讨操作系统(OS)的发展脉络,着重分析从早期单任务系统到现代多任务和并行处理系统的演进。我们将通过浅显易懂的语言和比喻,揭示这一技术变革如何影响我们的日常生活和工作方式,并展望未来可能的发展趋势。
|
3月前
|
机器学习/深度学习 人工智能 自动驾驶
操作系统的演化之路:从单任务到多任务处理
【8月更文挑战第16天】 本文将探索操作系统(OS)的演进历程,聚焦于它们如何从处理单一任务的简单系统,发展成为能够同时处理多个任务的复杂系统。我们将分析这一转变背后的技术驱动因素,以及它对用户体验和系统性能的影响。文章还将探讨现代操作系统在面对日益增长的计算需求时所面临的挑战,以及未来的发展方向。
|
6月前
|
缓存 算法 Java
Linux内核新特性年终大盘点-安卓杀后台现象减少的背后功臣MGLRU算法简介
MGLRU是一种新型内存管理算法,它的出现是为了弥补传统LRU(Least Recently Used)和LFU(Least Frequently Used)算法在缓存替换选择上的不足,LRU和LFU的共同缺点就是在做内存页面替换时,只考虑内存页面在最近一段时间内被访问的次数和最后一次的访问时间,但是一个页面的最近访问次数少或者最近一次的访问时间较早,可能仅仅是因为这个内存页面新近才被创建,属于刚刚完成初始化的年代代页面,它的频繁访问往往会出现在初始化之后的一段时间里,那么这时候就把这种年轻代的页面迁移出去
下一篇
无影云桌面