Vpp在实际应用场景中专注于网络数据包的处理,收发数据包的过程一般由第三方软件提供如:DPDK、NETMAP等,但自身也提供了丰富的虚拟网络接口,如:Virtio、Vxlan、Ipsex等。每种接口,不论是虚拟的还是物理的在启动的时候都会主动向Vpp注册自己的接口。
VPP编译过程中可以选择是否编译DPDK的IGB_UIO驱动,默认是不编译的,编译IGB_UIO驱动方法:
diff --git a/build/external/packages/dpdk.mk b/build/external/packages/dpdk.mk
index 7e8af07..afe32fd 100644
--- a/build/external/packages/dpdk.mk
+++ b/build/external/packages/dpdk.mk
@@ -229,7 +229,7 @@ $(B)/custom-config: $(B)/.dpdk-patch.ok Makefile
$(call set,RTE_LIBRTE_BBDEV_NULL,n)
$(call set,RTE_TEST_PMD,n)
$(call set,RTE_KNI_KMOD,n)
- $(call set,RTE_EAL_IGB_UIO,n)
+ $(call set,RTE_EAL_IGB_UIO,y)
@# currently broken in 18.02
$(call set,RTE_LIBRTE_DPAA_BUS,n)
$(call set,RTE_LIBRTE_DPAA_MEMPOOL,n)
DPDK接口使用Plugin的方式注册到VPP环境,DPDK Plugin在启动时首先使用“VNET_DEVICE_CLASS”宏定义初始化DPDK结构体
/* *INDENT-OFF* */
VNET_DEVICE_CLASS (dpdk_device_class) = {
.name = "dpdk",
.tx_function_n_errors = DPDK_TX_FUNC_N_ERROR,
.tx_function_error_strings = dpdk_tx_func_error_strings,
.format_device_name = format_dpdk_device_name,
.format_device = format_dpdk_device,
.format_tx_trace = format_dpdk_tx_trace,
.clear_counters = dpdk_clear_hw_interface_counters,
.admin_up_down_function = dpdk_interface_admin_up_down,
.subif_add_del_function = dpdk_subif_add_del_function,
.rx_redirect_to_node = dpdk_set_interface_next_node,
.mac_addr_change_function = dpdk_set_mac_address,
.format_flow = format_dpdk_flow,
.flow_ops_function = dpdk_flow_ops_fn,
};
/* *INDENT-ON* */
DPDK是作为插件的方式和VPP对接,VNET_DEVICE_CLASS宏定义使用__attribute__的__constructor__方法让DPDK插件在Load的时候首先执行指定函数,所以在DPDK插件刚加入到VPP时又VNET_DEVICE_CLASS宏定义修饰的函数就会被调用。
VNET_DEVICE_CLASS注册设备的过程分为两个动作,第一个是注册,把函数加入到vnm->device_class_registrations链表;第二个是实现卸载函数,这个函数作用是把注册到vnm->device_class_registrations链表的函数删除
#define VNET_DEVICE_CLASS(x,...) \
__VA_ARGS__ vnet_device_class_t x; \
static void __vnet_add_device_class_registration_##x (void) \
__attribute__((__constructor__)) ; \
static void __vnet_add_device_class_registration_##x (void) \
{ \
vnet_main_t * vnm = vnet_get_main(); \
x.next_class_registration = vnm->device_class_registrations; \
vnm->device_class_registrations = &x; \
} \
static void __vnet_rm_device_class_registration_##x (void) \
__attribute__((__destructor__)) ; \
static void __vnet_rm_device_class_registration_##x (void) \
{ \
vnet_main_t * vnm = vnet_get_main(); \
VLIB_REMOVE_FROM_LINKED_LIST (vnm->device_class_registrations, \
&x, next_class_registration); \
} \
__VA_ARGS__ vnet_device_class_t x
同理,VNET_HW_INTERFACE_CLASS方法也是用来初始化接口
Vpp启动过程中会在vnet_interface_init函数出遍历以上两个宏定义中的链表初始化Vpp接口
{
vnet_device_class_t *c;
c = vnm->device_class_registrations;
while (c)
{
c->index = vec_len (im->device_classes);
hash_set_mem (im->device_class_by_name, c->name, c->index);
if (c->tx_fn_registrations)
{
vlib_node_fn_registration_t *fnr = c->tx_fn_registrations;
int priority = -1;
................
................
{
vnet_hw_interface_class_t *c;
c = vnm->hw_interface_class_registrations;
while (c)
{
c->index = vec_len (im->hw_interface_classes);
hash_set_mem (im->hw_interface_class_by_name, c->name, c->index);
if (NULL == c->build_rewrite)
c->build_rewrite = default_build_rewrite;
if (NULL == c->update_adjacency)
c->update_adjacency = default_update_adjacency;
VPP在使用DPDK接口时会根据具体的应用UP/DOWN网络接口,或修改接口双工速率等,所以在Vpp内部需要有对应的DPDK API可调用
VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (admin_up_down_function);
VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (link_up_down_function);
这两个宏定义向Vpp注册了两种类型接口的ADMIN和LINK UP/DOWN函数
#define VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION(f) \
_VNET_INTERFACE_FUNCTION_DECL(f,sw_interface_admin_up_down)
#define VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION(f) \
_VNET_INTERFACE_FUNCTION_DECL(f,hw_interface_link_up_down)
#define _VNET_INTERFACE_FUNCTION_DECL(f,tag) \
_VNET_INTERFACE_FUNCTION_DECL_PRIO(f,tag,VNET_ITF_FUNC_PRIORITY_LOW)
以上通过VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION 和
VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION的封装sw_interface_admin_up_down和hw_interface_link_up_down标识把admin_up_down_function和link_up_down_function函数注册到vnm结构体中作为回调函数在需要的地方调用
#define _VNET_INTERFACE_FUNCTION_DECL_PRIO(f,tag,p) \
\
static void __vnet_interface_function_init_##tag##_##f (void) \
__attribute__((__constructor__)) ; \
\
static void __vnet_interface_function_init_##tag##_##f (void) \
{ \
vnet_main_t * vnm = vnet_get_main(); \
static _vnet_interface_function_list_elt_t init_function; \
init_function.next_interface_function = vnm->tag##_functions[p]; \
vnm->tag##_functions[p] = &init_function; \
init_function.fp = (void *) &f; \
} \
static void __vnet_interface_function_deinit_##tag##_##f (void) \
__attribute__((__destructor__)) ; \
\
static void __vnet_interface_function_deinit_##tag##_##f (void) \
{ \
vnet_main_t * vnm = vnet_get_main(); \
_vnet_interface_function_list_elt_t *next; \
if (vnm->tag##_functions[p]->fp == f) \
{ \
vnm->tag##_functions[p] = \
vnm->tag##_functions[p]->next_interface_function; \
return; \
} \
next = vnm->tag##_functions[p]; \
while (next->next_interface_function) \
{ \
if (next->next_interface_function->fp == f) \
{ \
next->next_interface_function = \
next->next_interface_function->next_interface_function; \
return; \
} \
next = next->next_interface_function; \
} \
}
如hw接口UP/DOWN处理函数vnet_hw_interface_set_flags_helper就会使用vnm的hw_interface_link_up_down_functions回调修改接口状态
static clib_error_t *
vnet_hw_interface_set_flags_helper (vnet_main_t * vnm, u32 hw_if_index,
vnet_hw_interface_flags_t flags,
vnet_interface_helper_flags_t
helper_flags)
{
vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
vnet_hw_interface_class_t *hw_class =
vnet_get_hw_interface_class (vnm, hi->hw_class_index);
error = call_elf_section_interface_callbacks
(vnm, hw_if_index, flags, vnm->hw_interface_link_up_down_functions);
....................
....................
....................
static clib_error_t *
call_elf_section_interface_callbacks (vnet_main_t * vnm, u32 if_index,
u32 flags,
_vnet_interface_function_list_elt_t **
elts)
{
_vnet_interface_function_list_elt_t *elt;
vnet_interface_function_priority_t prio;
clib_error_t *error = 0;
for (prio = VNET_ITF_FUNC_PRIORITY_LOW;
prio <= VNET_ITF_FUNC_PRIORITY_HIGH; prio++)
{
elt = elts[prio];
while (elt)
{
error = elt->fp (vnm, if_index, flags);
if (error)
return error;
elt = elt->next_interface_function;
}
}
return error;
}