首页 > 文章 > DPDK L3fwd 源码阅读

DPDK L3fwd 源码阅读

代码部分

整个L3fwd有三千多行代码,但总体思想就是在L2fwd的基础上,增加网络层的根据 IP 地址进行路由查找的内容。

main.c 文件

int
main(int argc, char **argv)
{
    struct lcore_conf *qconf;           // 见l3fwd.h
    struct rte_eth_dev_info dev_info;   // 以太网设备信息
    struct rte_eth_txconf *txconf;      // 用于 tx queue setup 的信息
    int ret;
    unsigned nb_ports;
    uint16_t queueid, portid;
    unsigned lcore_id;
    uint32_t n_tx_queue, nb_lcores;
    uint8_t nb_rx_queue, queue, socketid;

    /* init EAL */
    ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Invalid EAL parameters\n");
    argc -= ret;
    argv += ret;

    force_quit = false;
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

    /* pre-init dst MACs for all ports to 02:00:00:00:00:xx */
    // 初始化每个端口的目的MAC地址数组,初始化为 02:00:00:00:00:<port id>
    for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) {
        dest_eth_addr[portid] =
            ETHER_LOCAL_ADMIN_ADDR/*0x02*/ + ((uint64_t)portid << 40);

        *(uint64_t *)(val_eth + portid) = dest_eth_addr[portid];
    }

    /* parse application arguments (after the EAL ones) */
    // 解析命令行参数
    ret = parse_args(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Invalid L3FWD parameters\n");

     // 检查 lcore 配置数组的正确性
    if (check_lcore_params() < 0)
        rte_exit(EXIT_FAILURE, "check_lcore_params failed\n");
    // 检查 rx 队列数是否过多,把 param 的东西放到 conf 里
    ret = init_lcore_rx_queues();
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "init_lcore_rx_queues failed\n");

    nb_ports = rte_eth_dev_count(); // 获取以太网端口数量

    if (check_port_config() < 0) // 验证 port 掩码、 port id 的正确性
        rte_exit(EXIT_FAILURE, "check_port_config failed\n");

    nb_lcores = rte_lcore_count(); // 获取 lcore 数量

    /* Setup function pointers for lookup method. */
    setup_l3fwd_lookup_tables();// 设定是 Exact 还是 LPM 

    /* initialize all ports */
    // 初始化端口
    RTE_ETH_FOREACH_DEV(portid) {
        struct rte_eth_conf local_port_conf = port_conf;

        /* skip ports that are not enabled */
        if ((enabled_port_mask & (1 << portid)) == 0) {
            printf("\nSkipping disabled port %d\n", portid);
            continue;
        }

        /* init port */
        printf("Initializing port %d ... ", portid );
        fflush(stdout);

        nb_rx_queue = get_port_n_rx_queues(portid); // rx 队列数
        n_tx_queue = nb_lcores; // tx 队列数
        if (n_tx_queue > MAX_TX_QUEUE_PER_PORT)
            n_tx_queue = MAX_TX_QUEUE_PER_PORT;
        printf("Creating queues: nb_rxq=%d nb_txq=%u... ",
            nb_rx_queue, (unsigned)n_tx_queue );

        rte_eth_dev_info_get(portid, &dev_info); // 获取以太网设备信息
        if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_MBUF_FAST_FREE)
            local_port_conf.txmode.offloads |=
                DEV_TX_OFFLOAD_MBUF_FAST_FREE; // 如果有这项功能就开启
        ret = rte_eth_dev_configure(portid, nb_rx_queue,
                    (uint16_t)n_tx_queue, &local_port_conf); 
                    // 配置以太网设备,rx 队列数量根据 param 数组来,tx 数量则是和 lcore 数量相同
        if (ret < 0)
            rte_exit(EXIT_FAILURE,
                "Cannot configure device: err=%d, port=%d\n",
                ret, portid);

        ret = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd,
                               &nb_txd);
        if (ret < 0)
            rte_exit(EXIT_FAILURE,
                 "Cannot adjust number of descriptors: err=%d, "
                 "port=%d\n", ret, portid);

        // 获取 port id 的 MAC 地址信息并打印
        rte_eth_macaddr_get(portid, &ports_eth_addr[portid]); 
        print_ethaddr(" Address:", &ports_eth_addr[portid]); 
        printf(", ");
        print_ethaddr("Destination:",
            (const struct ether_addr *)&dest_eth_addr[portid]);
        printf(", ");

        /*
         * prepare src MACs for each port. (todo)
         */
        ether_addr_copy(&ports_eth_addr[portid],
            (struct ether_addr *)(val_eth + portid) + 1);

        /* init memory */
        // 分配内存,设置 LPM 或 Hash 表
        ret = init_mem(NB_MBUF);
        if (ret < 0)
            rte_exit(EXIT_FAILURE, "init_mem failed\n");

        /* init one TX queue per couple (lcore,port) */
        // 设置 Tx queue,每个端口只设置一条
        queueid = 0;
        for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
            if (rte_lcore_is_enabled(lcore_id) == 0)
                continue;

            if (numa_on)
                socketid =
                (uint8_t)rte_lcore_to_socket_id(lcore_id);
            else
                socketid = 0;

            printf("txq=%u,%d,%d ", lcore_id, queueid, socketid);
            fflush(stdout);

            txconf = &dev_info.default_txconf;
            txconf->txq_flags = ETH_TXQ_FLAGS_IGNORE;
            txconf->offloads = local_port_conf.txmode.offloads;
            ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd,
                             socketid, txconf);
            if (ret < 0)
                rte_exit(EXIT_FAILURE,
                    "rte_eth_tx_queue_setup: err=%d, "
                    "port=%d\n", ret, portid);

            qconf = &lcore_conf[lcore_id];
            qconf->tx_queue_id[portid] = queueid;
            queueid++;

            qconf->tx_port_id[qconf->n_tx_port] = portid;
            qconf->n_tx_port++;
        }
        printf("\n");
    }

    for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
        if (rte_lcore_is_enabled(lcore_id) == 0)
            continue;
        qconf = &lcore_conf[lcore_id];
        printf("\nInitializing rx queues on lcore %u ... ", lcore_id );
        fflush(stdout);
        /* init RX queues */
        // 配置 Rx 队列,每个端口上设置多条。
        for(queue = 0; queue < qconf->n_rx_queue; ++queue) {
            struct rte_eth_dev *dev;
            struct rte_eth_conf *conf;
            struct rte_eth_rxconf rxq_conf;

            portid = qconf->rx_queue_list[queue].port_id;
            queueid = qconf->rx_queue_list[queue].queue_id;
            dev = &rte_eth_devices[portid];
            conf = &dev->data->dev_conf;

            if (numa_on)
                socketid =
                (uint8_t)rte_lcore_to_socket_id(lcore_id);
            else
                socketid = 0;

            printf("rxq=%d,%d,%d ", portid, queueid, socketid);
            fflush(stdout);

            rte_eth_dev_info_get(portid, &dev_info);
            rxq_conf = dev_info.default_rxconf;
            rxq_conf.offloads = conf->rxmode.offloads;
            ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd,
                    socketid,
                    &rxq_conf,
                    pktmbuf_pool[socketid]);
            if (ret < 0)
                rte_exit(EXIT_FAILURE,
                "rte_eth_rx_queue_setup: err=%d, port=%d\n",
                ret, portid);
        }
    }

    printf("\n");

    /* start ports */
    // 启用设备
    RTE_ETH_FOREACH_DEV(portid) {
        if ((enabled_port_mask & (1 << portid)) == 0) {
            continue;
        }
        /* Start device */
        ret = rte_eth_dev_start(portid);
        if (ret < 0)
            rte_exit(EXIT_FAILURE,
                "rte_eth_dev_start: err=%d, port=%d\n",
                ret, portid);

        /*
         * If enabled, put device in promiscuous mode.
         * This allows IO forwarding mode to forward packets
         * to itself through 2 cross-connected  ports of the
         * target machine.
         */
        if (promiscuous_on) // 设置混杂模式
            rte_eth_promiscuous_enable(portid);
    }

    printf("\n");

    for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
        if (rte_lcore_is_enabled(lcore_id) == 0)
            continue;
        qconf = &lcore_conf[lcore_id];
        for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
            portid = qconf->rx_queue_list[queue].port_id;
            queueid = qconf->rx_queue_list[queue].queue_id;
            if (prepare_ptype_parser(portid, queueid) == 0) // 设定检查 pkt type;检查设备支不支持 IP 协议。
                rte_exit(EXIT_FAILURE, "ptype check fails\n");
        }
    }

    // 检查链路状态
    check_all_ports_link_status(enabled_port_mask);

    ret = 0;
    /* launch per-lcore init on every lcore */
    // 在每个lcore上执行函数,包括master lcore,根据选定的 LPM 还是 exact 执行对应的 main_loop 函数
    rte_eal_mp_remote_launch(l3fwd_lkp.main_loop, NULL, CALL_MASTER);
    RTE_LCORE_FOREACH_SLAVE(lcore_id) {
        if (rte_eal_wait_lcore(lcore_id) < 0) {
            ret = -1;
            break;
        }
    }

    /* stop ports */
    RTE_ETH_FOREACH_DEV(portid) {
        if ((enabled_port_mask & (1 << portid)) == 0)
            continue;
        printf("Closing port %d...", portid);
        rte_eth_dev_stop(portid);
        rte_eth_dev_close(portid);
        printf(" Done\n");
    }
    printf("Bye...\n");

    return ret;
}

main 函数中,代码思路就是L2fwd+helloworld。首先分配内存,配置队列、初始化端口等部分与L2fwd相似。除此之外,多出来的几个部分就是L3层的事情:选取网络层路由模式(精确匹配 Exact or 最长前缀匹配LPM);配置路由表(从代码看就是静态路由表);检查设备是否支持IP协议(还有不支持IP协议的以太网网卡吗?);在最后,用 rte_eal_mp_remote_launch() 在各个逻辑核上执行函数,这里使用了函数指针,根据你是选择Exact 还是 LPM 会注册不同的函数。

选取 LPM or Exact Match 的逻辑:

首先,程序会解析命令行参数,可以用命令行参数来选取路由模式。-E 就是精确匹配,-L 就是 LPM。具体可以参考L3 forward sample guide

/* Parse the argument given in the command line of the application */
static int
parse_args(int argc, char **argv)
{
    /*......*/

    while ((opt = getopt_long(argc, argvopt, short_options,
                lgopts, &option_index)) != EOF) {

        switch (opt) {
        /*......*/

        case 'P': // 开启混杂模式
            promiscuous_on = 1;
            break;

        case 'E': // 精确匹配
            l3fwd_em_on = 1;
            break;

        case 'L': // 最长前缀匹配
            l3fwd_lpm_on = 1;
            break;

                /*......*/

    /* If both LPM and EM are selected, return error. */
    if (l3fwd_lpm_on && l3fwd_em_on) { // Exact match 和 LPM 只能选一种
        fprintf(stderr, "LPM and EM are mutually exclusive, select only one\n");
        return -1;
    }

    /*
     * Nothing is selected, pick longest-prefix match
     * as default match.
     */
    if (!l3fwd_lpm_on && !l3fwd_em_on) { // 如果两者都没选,选择 LPM
        fprintf(stderr, "LPM or EM none selected, default LPM on\n");
        l3fwd_lpm_on = 1;
    }

    /*
     * ipv6 and hash flags are valid only for
     * exact macth, reset them to default for
     * longest-prefix match.
     */
    if (l3fwd_lpm_on) { // 如果选择了LPM,不适用ipv6 和 hash 
        ipv6 = 0;
        hash_entry_number = HASH_ENTRY_NUMBER_DEFAULT;
    }

    if (optind >= 0)
        argv[optind-1] = prgname;

    ret = optind-1;
    optind = 1; /* reset getopt lib */
    return ret;
}

LPM 和 精确匹配在 L3fwd 里分别用了两套代码来实现整个网络层协议栈。根据你选择的模式,会通过设置变量和函数指针的方式来调用特定的代码块。


struct l3fwd_lkp_mode { 
    void  (*setup)(int);// 各种函数指针
    int   (*check_ptype)(int);
    rte_rx_callback_fn cb_parse_ptype;
    int   (*main_loop)(void *);
    void* (*get_ipv4_lookup_struct)(int);
    void* (*get_ipv6_lookup_struct)(int);
};

static struct l3fwd_lkp_mode l3fwd_lkp; // 会根据是使用 lpm 还是 exact,赋值给下面两个中的一个

static struct l3fwd_lkp_mode l3fwd_em_lkp = {
    .setup                  = setup_hash,
    .check_ptype        = em_check_ptype,
    .cb_parse_ptype     = em_cb_parse_ptype,
    .main_loop              = em_main_loop,
    .get_ipv4_lookup_struct = em_get_ipv4_l3fwd_lookup_struct,
    .get_ipv6_lookup_struct = em_get_ipv6_l3fwd_lookup_struct,
};

static struct l3fwd_lkp_mode l3fwd_lpm_lkp = {
    .setup                  = setup_lpm,
    .check_ptype        = lpm_check_ptype,
    .cb_parse_ptype     = lpm_cb_parse_ptype,
    .main_loop              = lpm_main_loop,
    .get_ipv4_lookup_struct = lpm_get_ipv4_l3fwd_lookup_struct,
    .get_ipv6_lookup_struct = lpm_get_ipv6_l3fwd_lookup_struct,
};


static void
setup_l3fwd_lookup_tables(void) // 设定 IP 查表方法是 Exact 还是 LPM 
{
    /* Setup HASH lookup functions. */
    if (l3fwd_em_on) 
        l3fwd_lkp = l3fwd_em_lkp; 
    /* Setup LPM lookup functions. */
    else 
        l3fwd_lkp = l3fwd_lpm_lkp;
}

LPM 相关

LPM 就是最长前缀匹配。在DPDK中有个一个专门的 LPM library 来实现LPM的相关模块。LPM 的条目(或者说规则)是由三个部分组成,IP地址、前缀长度、下一跳。分别是 4、1、1个字节。而LPM table,也就是条目的集合,集合组成了路由表。

struct ipv4_l3fwd_lpm_route { // 路由表中一个 entry 的结构
    uint32_t ip;    // IP 地址
    uint8_t  depth; // (掩码)前缀位数
    uint8_t  if_out;// 下一跳
};

// LPM的路由表:IP地址、掩码长度、下一跳
static struct ipv4_l3fwd_lpm_route ipv4_l3fwd_lpm_route_array[] = {
    {IPv4(1, 1, 1, 0), 24, 0},
    {IPv4(2, 1, 1, 0), 24, 1},
    {IPv4(3, 1, 1, 0), 24, 2},
    {IPv4(4, 1, 1, 0), 24, 3},
    {IPv4(5, 1, 1, 0), 24, 4},
    {IPv4(6, 1, 1, 0), 24, 5},
    {IPv4(7, 1, 1, 0), 24, 6},
    {IPv4(8, 1, 1, 0), 24, 7},
};

LPM library 中主要用到的几个功能如下:

  1. 创建 LPM table 对象。
  2. 朝 LPM table 插入一个条目。拿上面那个条目的结构体和LPM table对象作为参数。如果表中没有相同前缀的规则,则新规则将添加到LPM表中。如果表中已存在具有相同前缀的规则,则更新规则的下一跳。
  3. 路由功能。输入就是目的IP地址,算法会选择匹配的规则,并返回该规则的下一跳。如果LPM表中存在多个具有能匹配的规则,则算法选择具有最长前缀位数的规则作为最佳匹配规则。

(以上都是TCP/IP的基础知识)

LPM的思路是挺简单的,然而具体的代码实现算是一个大工程,思路也是非常有意思。具体的工程方法叫做 DIR-24-8,可以参考LPM library - implementation details(这足以让一个Stanford phd 毕业~文末有给出2000年研究这个方法的phd论文)

Exact match 相关

实现精确匹配时,路由表的内容是<五元组,下一跳>,实现思路是将五元组信息过 hash 函数,得到一个hash value用作路由表的 index。只有五元组都相同才会匹配成功,才能得到正确的路由表的index,才能得到合法的下一跳地址。这样就实现了精确匹配。DPDK中为了实现快速的hash查找有专门的 hash library。DPDK 为了性能要求,在 hash library 中对 hash table有特定的要求:hash key 的长度必须固定,hash 表的条目也是有限的。这两点在创建 hash table 对象时必须配置。hash library 中有用到的几个功能如下:

  1. 创建新的 hash table 对象
  2. 在 hash table 中加入一个条目,键值是 key。如果添加成功,返回值为一个正值,此值对于此键是唯一的。
  3. 以键值 key 查询 hash table,若查询成功,会返回一个正值。此正值对于此键是唯一的,并且与添加键时返回的值相同。使用这两个API的返回的正值作为某个数组(下一跳数组)或者结构体数组的下标,就可以实现精确匹配了。

hash 库中使用了所谓 cuckoo hash 的实现方法。

// 精确匹配的路由表,是五元组和下一跳
static struct ipv4_l3fwd_em_route ipv4_l3fwd_em_route_array[] = {
    // 目的IP地址          源IP地址            目的/源 端口号 协议类型   下一跳
    {{IPv4(101, 0, 0, 0), IPv4(100, 10, 0, 1),  101, 11, IPPROTO_TCP}, 0},
    {{IPv4(201, 0, 0, 0), IPv4(200, 20, 0, 1),  102, 12, IPPROTO_TCP}, 1},
    {{IPv4(111, 0, 0, 0), IPv4(100, 30, 0, 1),  101, 11, IPPROTO_TCP}, 2},
    {{IPv4(211, 0, 0, 0), IPv4(200, 40, 0, 1),  102, 12, IPPROTO_TCP}, 3},
    // 所谓精确匹配就是五元组的信息 hash后,只有五元组都相同才会匹配成功。
    // 所以 exact 就是配置 hash
};

三层转发

// 检查packet是否符合网络层的要求:

static inline int
is_valid_ipv4_pkt(struct ipv4_hdr *pkt, uint32_t link_len)
{
    /* From http://www.rfc-editor.org/rfc/rfc1812.txt section 5.2.2 */
    /*
     * 1. The packet length reported by the Link Layer must be large
     * enough to hold the minimum length legal IP datagram (20 bytes).
     */
    if (link_len < sizeof(struct ipv4_hdr)) // 正常的包长度不能短于 header 长度(20 bytes)
        return -1;

    /* 2. The IP checksum must be correct. */
    /* this is checked in H/W */

    /*
     * 3. The IP version number must be 4. If the version number is not 4
     * then the packet may be another version of IP, such as IPng or
     * ST-II.
     */
    if (((pkt->version_ihl) >> 4) != 4) // 版本号字段必须是 4
        return -3;
    /*
     * 4. The IP header length field must be large enough to hold the
     * minimum length legal IP datagram (20 bytes = 5 words).
     */
    if ((pkt->version_ihl & 0xf) < 5) // IP header length 字段的值必须大于 5
        return -4;

    /*
     * 5. The IP total length field must be large enough to hold the IP
     * datagram header, whose length is specified in the IP header length
     * field.
     */
    if (rte_cpu_to_be_16(pkt->total_length) < sizeof(struct ipv4_hdr)) // 总长度字段的值要大于等于 20
        return -5;

    return 0;
}

// 网络层的处理:
static __rte_always_inline void
l3fwd_lpm_simple_forward(struct rte_mbuf *m, uint16_t portid,
        struct lcore_conf *qconf)
{
    struct ether_hdr *eth_hdr;
    struct ipv4_hdr *ipv4_hdr;
    uint16_t dst_port;

    eth_hdr = rte_pktmbuf_mtod(m, struct ether_hdr *); // 从 pkt m 中获取 MAC header

    if (RTE_ETH_IS_IPV4_HDR(m->packet_type)) { // 查看 pkt 的 L3 header type 是不是 IPv4 
        /* Handle IPv4 headers.*/
        ipv4_hdr = rte_pktmbuf_mtod_offset(m, struct ipv4_hdr *,
                           sizeof(struct ether_hdr)); // 从 pkt m 中获取 IP header

#ifdef DO_RFC_1812_CHECKS
        /* Check to make sure the packet is valid (RFC1812) */
        
        if (is_valid_ipv4_pkt(ipv4_hdr, m->pkt_len) < 0) { // 根据 RFC1812 的内容对 pkt 进行验证。
            rte_pktmbuf_free(m); // 如果不是合法的包就丢包
            return;
        }
#endif
         dst_port = lpm_get_ipv4_dst_port(ipv4_hdr, portid,
                        qconf->ipv4_lookup_struct); // 获取“下一跳” (目的端口),这会根据你选择的方法是LPM还是Exact而调用不同的代码。

        if (dst_port >= RTE_MAX_ETHPORTS ||
            (enabled_port_mask & 1 << dst_port) == 0)
            dst_port = portid; // 如果成功获取了目的端口,但端口没有启用或是超过了最大数量的限制,就设置目的端口与收包的端口一样。
                                //(从哪里收到的就原路返回。

#ifdef DO_RFC_1812_CHECKS
        /* Update time to live and header checksum */
        --(ipv4_hdr->time_to_live); // TTL 自减 1
        ++(ipv4_hdr->hdr_checksum);
#endif
        /* dst addr */
        *(uint64_t *)&eth_hdr->d_addr = dest_eth_addr[dst_port]; 
        //根据查表得出的下一跳 port id,根据dest_eth_addr[dst_port]中的信息,改写 eth_hdr 中的 目的 MAC 地址字段。

        /* src addr */
        ether_addr_copy(&ports_eth_addr[dst_port], &eth_hdr->s_addr);
        // 根据ports_eth_addr数组改写 eth_hdr 中的 源 MAC 地址字段。

        send_single_packet(qconf, m, dst_port); // 协议栈的东西都处理完之后就加入发包队列

    } else if (RTE_ETH_IS_IPV6_HDR(m->packet_type)) { // 如果不是 IPv4 而是 IPv6 的话
        /* Handle IPv6 headers.*/
        
                /*......*/
                
    } else { // 网络层协议不是 IP
        /* Free the mbuf that contains non-IPV4/IPV6 packet */
        rte_pktmbuf_free(m); // 丢包
    }
}

这部分如果传统TCP/IP网络知识掌握的比较扎实,看懂是没什么难度的。

二层发包

/* Enqueue a single packet, and send burst if queue is filled */
static inline int
send_single_packet(struct lcore_conf *qconf,
           struct rte_mbuf *m, uint16_t port)
{
    uint16_t len;

    len = qconf->tx_mbufs[port].len;
    qconf->tx_mbufs[port].m_table[len] = m;
    len++; // 将该 pkt 进入发包队列

    /* enough pkts to be sent */
    if (unlikely(len == MAX_PKT_BURST)) { // 当 tx queue 长度达到 Burst 就一次性发出
        send_burst(qconf, MAX_PKT_BURST, port);
        len = 0; // 清空发包队列长度
    }

    qconf->tx_mbufs[port].len = len;
    return 0;
}

/* Send burst of packets on an output interface */
static inline int
send_burst(struct lcore_conf *qconf, uint16_t n, uint16_t port)
{
    struct rte_mbuf **m_table;
    int ret;
    uint16_t queueid;

    queueid = qconf->tx_queue_id[port];
    m_table = (struct rte_mbuf **)qconf->tx_mbufs[port].m_table;

    ret = rte_eth_tx_burst(port, queueid, m_table, n); // 参数:从哪个端口/哪条队列/发出的pkt 的mbuf/发多少个包
    if (unlikely(ret < n)) {
        do {
            rte_pktmbuf_free(m_table[ret]);
        } while (++ret < n);
    }

    return 0;
}

// 逻辑核上的main函数
/* main processing loop */
int
lpm_main_loop(__attribute__((unused)) void *dummy)
{
    struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
    unsigned lcore_id;
    uint64_t prev_tsc, diff_tsc, cur_tsc;
    int i, nb_rx;
    uint16_t portid;
    uint8_t queueid;
    struct lcore_conf *qconf;
    const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) /
        US_PER_S * BURST_TX_DRAIN_US; // 每隔一段时间发包的计时器

    prev_tsc = 0;

    lcore_id = rte_lcore_id();
    qconf = &lcore_conf[lcore_id];

    if (qconf->n_rx_queue == 0) { // 该 lcore 没有配置收包队列。
        RTE_LOG(INFO, L3FWD, "lcore %u has nothing to do\n", lcore_id);
        return 0;
    }

    RTE_LOG(INFO, L3FWD, "entering main loop on lcore %u\n", lcore_id);

    for (i = 0; i < qconf->n_rx_queue; i++) { // 打印该 lcore 负责的每条收包队列

        portid = qconf->rx_queue_list[i].port_id;
        queueid = qconf->rx_queue_list[i].queue_id;
        RTE_LOG(INFO, L3FWD,
            " -- lcoreid=%u portid=%u rxqueueid=%hhu\n", 
            lcore_id, portid, queueid);
    }

    while (!force_quit) {

        cur_tsc = rte_rdtsc();

        /*
         * TX burst queue drain
         */
        diff_tsc = cur_tsc - prev_tsc;
        if (unlikely(diff_tsc > drain_tsc)) { // 该发包了

            for (i = 0; i < qconf->n_tx_port; ++i) {
                portid = qconf->tx_port_id[i];
                if (qconf->tx_mbufs[portid].len == 0)
                    continue;
                send_burst(qconf,
                    qconf->tx_mbufs[portid].len,
                    portid); // 该函数见 l3fwd.h
                qconf->tx_mbufs[portid].len = 0;
            }

            prev_tsc = cur_tsc;
        }

        /*
         * Read packet from RX queues
         */
        for (i = 0; i < qconf->n_rx_queue; ++i) { // 对 lcore 负责的每条 rx queue
            portid = qconf->rx_queue_list[i].port_id;
            queueid = qconf->rx_queue_list[i].queue_id;
            nb_rx = rte_eth_rx_burst(portid, queueid, pkts_burst,
                MAX_PKT_BURST); // 收包
            if (nb_rx == 0)
                continue;

// 转发
#if defined RTE_ARCH_X86 || defined RTE_MACHINE_CPUFLAG_NEON              || defined RTE_ARCH_PPC_64 
             // 对于 x86 系统架构的,使用优化 buffer 的转发方法(没看
            l3fwd_lpm_send_packets(nb_rx, pkts_burst,
                        portid, qconf);
#else
            // 否则是普通的tx buffer转发方法
            l3fwd_lpm_no_opt_send_packets(nb_rx, pkts_burst,
                            portid, qconf); // 参数:包数量/包的mbuf/收包的port/收包的qconf
#endif /* X86 */
        }
    }

    return 0;
}

二层发包就参考L2fwd。

时间:2018-07-28 15:29:38阅读(1077)
联系我们 - 留言反馈
© 2017 版权所有 鲁ICP备17052893号