DPVS DR模式处理流程

DR模式的原理通过上篇笔记已经了解的比较透彻了,只不过是用的LVS来模拟进行抓包分析的,但是DR模式的原理是一样的,所以先跟着DPVS的源码了解一下DPVS实现DR模式的流程(这次分析主要跟着DR模式的处理流程走,会跳过其他的处理流程)。先跳过DPVS的启动流程,从main函数的netif_lcore_start()函数开始分析,从这个函数开始DPVS的收发过程就开始启动,这个函数内容如下

int netif_lcore_start(void) {
    rte_eal_mp_remote_launch(netif_loop, NULL, SKIP_MASTER);
    return EDPVS_OK;
}

可以看到同样调用的是rte_eal_mp_remote_launch函数设置slave lcore的lcore_config结构体中的lcore_function_t值来让其执行netif_loop函数,启动方式和dpdk的l2fwd也是一样的,来看一下netif_loop函数的主要内容,先删掉用于调试的log信息

static int netif_loop(void *dummy)
{
    struct netif_lcore_loop_job *job;
    lcoreid_t cid = rte_lcore_id();// 获取自己的lcore id
……
#ifdef DPVS_MAX_LCORE
    if (cid >= DPVS_MAX_LCORE)
        return EDPVS_IDLE;
#endif
    assert(LCORE_ID_ANY != cid);
	// 某个核可能专用来收取包,死循环,如果没有配置那么继续
    try_isol_rxq_lcore_loop();
    if (0 == lcore_conf[lcore2index[cid]].nports) {
        RTE_LOG(INFO, NETIF, "[%s] Lcore %d has nothing to do.\n", __func__, cid);
        return EDPVS_IDLE;
    }
    list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_INIT], list) {
        do_lcore_job(job);
    }
    while (1) {
……
        // 运行所有 NETIF_LCORE_JOB_LOOP 类型的任务
        lcore_stats[cid].lcore_loop++; // 记录循环的次数
        list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_LOOP], list) {
            do_lcore_job(job);
        }
        ++netif_loop_tick[cid];

        // 慢速任务 NETIF_LCORE_JOB_SLOW
        list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_SLOW], list) {
            if (netif_loop_tick[cid] % job->skip_loops == 0) {
                do_lcore_job(job);
                //netif_loop_tick[cid] = 0;
            }
        }
……
    }
    return EDPVS_OK;
}

如果配置了一些lcore是专职用于接收消息的,那么就会调用try_isol_rxq_lcore_loop()函数,然后就会进入一个while(1)的死循环中,首先会执行list_for_each_entry宏,这个宏定义如下

/**
 * list_for_each_entry    -    iterate over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_head within the struct.
 */
#define list_for_each_entry(pos, head, member)                \
    for (pos = list_first_entry(head, typeof(*pos), member);    \
         &pos->member != (head);                    \ // pos指向每一个member
         pos = list_next_entry(pos, member))

可以看到这个宏主要是用来迭代一个list的,head是list的头部,member是这个list中的成员变量,然后让pos调用list_next_entry不断指向下一个member,这个pos的变量类型就是netif_lcore_loop_job,然后调用do_lcore_job函数来执行每一个job,可以看到这里对job也是由分类的,每一类job都会对应一个list,然后迭代来执行,dpvs中由如下三类job类型

/**************************** lcore loop job ****************************/
enum netif_lcore_job_type { // 三类job任务
    NETIF_LCORE_JOB_INIT      = 0,
    NETIF_LCORE_JOB_LOOP      = 1,
    NETIF_LCORE_JOB_SLOW      = 2,
    NETIF_LCORE_JOB_TYPE_MAX  = 3,
};

do_lcore_job函数的主要内容为

static inline void do_lcore_job(struct netif_lcore_loop_job *job)
{
……
    job->func(job->data); // 调用job执行的function
……
}

也就是执行job结构体所指向的func函数,入参是job指向的data,通过这样的方式就开始了dpvs的报文收发的过程,而不同类型的job是在main中的netif_init函数中注册的

int netif_init(const struct rte_eth_conf *conf){
    cycles_per_sec = rte_get_timer_hz();
    netif_pktmbuf_pool_init(); // 初始化mbuf pool 
    netif_arp_ring_init(); // 二层数据包环形数组初始化
    netif_pkt_type_tab_init(); // 不同类型数据包处理函数表的初始化
    netif_lcore_jobs_init(); //初始化 jobs 数组
    // use default port conf if conf=NULL
    netif_port_init(conf); // 初始化端口
    netif_lcore_init();// 注册job
    return EDPVS_OK;
}

netif_lcore_jobs_init()用于初始化jobs数组

static inline void netif_lcore_jobs_init(void){
    int ii;
    for (ii = 0; ii < NETIF_LCORE_JOB_TYPE_MAX; ii++) {
        INIT_LIST_HEAD(&netif_lcore_jobs[ii]);
    }
}

可以看到netif_lcore_jobs_init()就是遍历了3类job然后通过INIT_LIST_HEAD宏,将netif_lcore_jobs[ii]的pre和next指针都指向自己,也就是为每一个netif_lcore_jobs[ii]初始化头部,结合刚才的job的迭代过程,可以看出来job的数据结构是这样的

也就是一个数组,数组的每一个元素都是链表的头部,头部连接了后续的元素,后边可以看到dpvs用了很多这样的数据结构来存储数据,这个存储结构和java里的HashMap的存储结构类似

接下来来看netif_port_init(conf)

/* Allocate and register all DPDK ports available */
inline static void netif_port_init(const struct rte_eth_conf *conf)
{
……
    port_tab_init(); // 初始化port_tab数组 数据结构类似job
    port_ntab_init();// 初始化port_ntab数组 数据结构类似job
    if (!conf)
        conf = &default_port_conf; //rte_eth_conf
    this_eth_conf = *conf;
	// 初始化kni模块 用于将不关心的网络数据包透传到内核
    rte_kni_init(NETIF_MAX_KNI);
    kni_init();
    for (pid = 0; pid < nports; pid++) {
        /* queue number will be filled on device start */
        port = netif_rte_port_alloc(pid, 0, 0, &this_eth_conf); // 分配内存 设置队列 设置结构体name mac type等字段的值
        if (!port)
            rte_exit(EXIT_FAILURE, "Port allocate fail, exiting...\n");
        if (netif_port_register(port) < 0) // 加入到port_tab和port_ntab数组中
            rte_exit(EXIT_FAILURE, "Port register fail, exiting...\n");
    }
    if (relate_bonding_device() < 0)
        rte_exit(EXIT_FAILURE, "relate_bonding_device fail, exiting...\n");

    /* auto generate KNI device for all build-in
     * phy ports and bonding master ports, but not bonding slaves */
    for (pid = 0; pid < nports; pid++) {
        port = netif_port_get(pid);
        assert(port);
// 每个网卡还要增加 kni 网卡接口,比如原来是 dpdk0, 那么 kni 就叫 dpdk0.kni
        if (port->type == PORT_TYPE_BOND_SLAVE)
            continue;
        kni_name = find_conf_kni_name(pid);
        /* it's ok if no KNI name (kni_name is NULL) */
        if (kni_add_dev(port, kni_name) < 0)
            rte_exit(EXIT_FAILURE, "add KNI port fail, exiting...\n");
    }
}

首先会初始化port_tab和port_ntab数组,结构和job类似,用于后边快速获取各个port的信息,netif_rte_port_alloc会为每一个端口分配内存,设置port的netif_port结构体中的一些字段的值,后边用netif_port_register函数将port加入到port_tab和port_ntab数组,最后会为每一个port添加kni的网卡接口,然后来看netif_lcore_init函数,这个函数会初始化每一个lcore,也就是设置lcore的lcore_conf结构体,会配置每一个lcore所管理的端口等,来看后边关键的注册job的部分

#define NETIF_JOB_COUNT 3
struct netif_lcore_loop_job netif_jobs[NETIF_JOB_COUNT];
static void netif_lcore_init(void){
    ……
    /* register lcore jobs*/
    snprintf(netif_jobs[0].name, sizeof(netif_jobs[0].name) - 1, "%s", "recv_fwd");
    netif_jobs[0].func = lcore_job_recv_fwd; // 收发数据包的job
    netif_jobs[0].data = NULL;
    netif_jobs[0].type = NETIF_LCORE_JOB_LOOP;
    snprintf(netif_jobs[1].name, sizeof(netif_jobs[1].name) - 1, "%s", "xmit");
    netif_jobs[1].func = lcore_job_xmit; // 发送网卡数据包
    netif_jobs[1].data = NULL;
    netif_jobs[1].type = NETIF_LCORE_JOB_LOOP;
    snprintf(netif_jobs[2].name, sizeof(netif_jobs[2].name) - 1, "%s", "timer_manage");
    netif_jobs[2].func = lcore_job_timer_manage; // 定时器管理
    netif_jobs[2].data = NULL;
    netif_jobs[2].type = NETIF_LCORE_JOB_LOOP;
    for (ii = 0; ii < NETIF_JOB_COUNT; ii++) {
        res = netif_lcore_loop_job_register(&netif_jobs[ii]); // 注册每个job
        if (res < 0) {
            rte_exit(EXIT_FAILURE,
                    "[%s] Fail to register netif lcore jobs, exiting ...\n", __func__);
            break;
        }
    }
}

可以看到,这里注册的3个类型是NETIF_LCORE_JOB_LOOP的job,分别为lcore_job_recv_fwd,lcore_job_xmit和lcore_job_timer_manage,通过函数指针指向了这三个函数,这三个函数也就

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值