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,通过函数指针指向了这三个函数,这三个函数也就