VPP的DPDK插件默认是不支持动态加载网卡的,需要在启动前在配置文件startup.conf设置网卡的PCI编号。也就是不支持网卡的热插拔动作。网卡热插拔主要解决高可靠性持续不间断运行的问题。在某些特殊的应用场合,如服务器、数据中心等,可能需要不关闭系统电源的情况下更换网卡。
最近社区提交了一个patch支持dpdk网卡热插拔功能,下面就来环境上实验一下:
此提交尚未合入主线,需要手动打补丁,提交链接: https://gerrit.fd.io/r/c/vpp/ /39121
下面是实验环境上存在2个virto-pci网卡,其中00:02.0已经作为管理口在用,我们可以将网卡0000:00:06.0动态加载到vpp上。
代码语言:javascript复制root@jinsh:~/workspace/vpp/build-root/install-vpp-native/external/bin# ./dpdk-devbind.py -s
Network devices using kernel driver
===================================
0000:00:02.0 'Virtio network device 1000' if=ens2 drv=virtio-pci unused=vfio-pci,uio_pci_generic *Active*
0000:00:06.0 'Virtio network device 1000' if=ens6 drv=virtio-pci unused=vfio-pci,uio_pci_generic
vppctl命令登录vpp命令行视图下,执行“create interface dpdk”命令完成网卡动态加载。
代码语言:javascript复制#1、加载网卡。需要注意的PCI 编译需要写全格式,否则无法识别。
vpp# create interface dpdk 00:00:06.0 num-rx-desc 1024 num-tx-desc 1024
GigabitEthernet0/6/0
#2、接口设置接口up,并设置dhcp client模式。
vpp# set interface state GigabitEthernet0/6/0 up
vpp# set dhcp client intfc GigabitEthernet0/6/0
#3、查询接口已获取到ip地址,ping网关也正常。
vpp# show interface address
GigabitEthernet0/6/0 (up):
L3 192.168.100.133/24
local0 (dn):
vpp# ping 192.168.100.1
116 bytes from 192.168.100.1: icmp_seq=1 ttl=64 time=.1632 ms
116 bytes from 192.168.100.1: icmp_seq=2 ttl=64 time=.0990 ms
116 bytes from 192.168.100.1: icmp_seq=3 ttl=64 time=.0898 ms
116 bytes from 192.168.100.1: icmp_seq=4 ttl=64 time=.0911 ms
Aborted due to a keypress.
Statistics: 4 sent, 4 received, 0% packet loss
代码语言:javascript复制
代码语言:javascript复制执行“delete interface dpdk”可以将网卡从vpp中卸载掉。
代码语言:javascript复制
代码语言:javascript复制vpp# delete interface dpdk GigabitEthernet0/6/0
vpp# show interface
Name Idx State MTU (L3/IP4/IP6/MPLS) Counter Count
local0 0 down 0/0/0/0
vpp#
代码语言:javascript复制下面是网卡加载和卸载的函数,代码相当简洁,如下:
代码语言:javascript复制int
dpdk_create_if (vlib_main_t *vm, dpdk_create_if_args_t *args)
{
u8 *pci_addr = 0;
dpdk_main_t *dm = &dpdk_main;
vnet_main_t *vnm = vnet_get_main ();
u16 port_id = ~0;
args->rv = 0;
args->error = NULL;
dpdk_device_t *xd = NULL;
vec_reset_length (pci_addr);
pci_addr =
format (0, "%U%c", format_vlib_pci_addr, &args->config.pci_addr, 0);
/*查询当前网卡是否已经被dpdk纳管*/
args->rv = rte_eth_dev_get_port_by_name ((char *) pci_addr, &port_id);
if (!args->rv && vec_len (dm->devices))
{
vec_foreach (xd, dm->devices)
{
if (xd->port_id == port_id)
{
goto cleanup;
}
}
}
/*网卡绑定UIO*/
args->error =
vlib_pci_bind_to_uio (vm, &args->config.pci_addr, (char *) "auto", 1);
if (args->error)
{
args->rv = args->error->code;
goto cleanup;
}
/*执行网卡热加载函数*/
args->rv = rte_eal_hotplug_add ("pci", (char *) pci_addr, NULL);
if (args->rv)
{
args->error = clib_error_return (0, "rte_eal_hotplug_add failed");
goto cleanup;
}
/*查询网卡port id*/
args->rv = rte_eth_dev_get_port_by_name ((char *) pci_addr, &port_id);
if (args->rv)
{
args->error =
clib_error_return (0, "rte_eth_dev_get_port_by_name failed");
goto cleanup;
}
/*根据实际workers数量更新rx tx队列*/
if (args->config.workers && args->config.num_rx_queues == 0)
args->config.num_rx_queues =
clib_bitmap_count_set_bits (args->config.workers);
else if (args->config.workers &&
clib_bitmap_count_set_bits (args->config.workers) !=
args->config.num_rx_queues)
{
args->error =
clib_error_return (0,
"%U: number of worker threads must be "
"equal to number of rx queues",
format_vlib_pci_addr, &args->config.pci_addr);
goto cleanup;
}
/*在vpp中创建网卡接口*/
u32 hw_if_index = dpdk_port_init (dm, port_id, &args->config);
vnet_hw_interface_t *hif = vnet_get_hw_interface (vnm, hw_if_index);
args->sw_if_index = hif->sw_if_index;
xd = vec_elt_at_index (dm->devices, hif->dev_instance);
vnet_hw_if_update_runtime_data (vnm, hw_if_index);
/*更新网卡状态*/
f64 now = vlib_time_now (vm);
dpdk_update_link_state (xd, now);
cleanup:
vec_free (pci_addr);
return args->rv;
}
int
dpdk_delete_if (vlib_main_t *vm, u32 sw_if_index)
{
int rv = 0;
dpdk_main_t *dm = &dpdk_main;
vnet_main_t *vnm = vnet_get_main ();
struct rte_eth_dev_info di = {};
if (sw_if_index == ~0)
{
dpdk_log_warn ("[%u] invalid sw interface.", sw_if_index);
return VNET_ERR_INVALID_SW_IF_INDEX;
}
vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, sw_if_index);
if (!si)
{
dpdk_log_warn ("[%u] failed to get sw interface.", sw_if_index);
return VNET_ERR_INVALID_SW_IF_INDEX;
}
vnet_hw_interface_t *hif = vnet_get_hw_interface (vnm, si->hw_if_index);
if (!hif)
{
dpdk_log_warn ("[%u] failed to get hw interface.", si->hw_if_index);
return VNET_ERR_NO_SUCH_ENTRY;
}
dpdk_device_t *xd = vec_elt_at_index (dm->devices, hif->dev_instance);
if (!xd)
{
dpdk_log_warn ("[%u] failed to get dpdk device.", hif->dev_instance);
return VNET_ERR_NO_SUCH_ENTRY;
}
/*删除接口*/
ethernet_delete_interface (vnm, si->hw_if_index);
/*网卡设备stop掉*/
dpdk_device_stop (xd);
if ((rv = rte_eth_dev_info_get (xd->port_id, &di)) != 0)
{
dpdk_log_warn ("[%u] failed to get device info. skipping device.",
xd->port_id);
return VNET_ERR_NO_SUCH_ENTRY;
}
/*将网卡卸载掉*/
rv = rte_dev_remove (di.device);
if (rv)
{
dpdk_log_warn ("rte_dev_remove failed.");
return VNET_ERR_NO_SUCH_ENTRY;
}
vec_del1 (dm->devices, xd - dm->devices);
return 0;
}
代码语言:javascript复制
代码语言:javascript复制
此插件在vpp不重启的情况下已经实现了网卡加载和卸载的基本功能接口,但是并不是和系统热插拔事件相关联的。插件功能并不是很完善,当网卡绑定其他资源(子接口、l2xc)等功能时,卸载网卡可能会导致vpp异常,还需要释放相应的资源。
参考资料:
1、https://blog.csdn.net/longyu_wlz/article/details/127243979 2、https://zhuanlan.zhihu.com/p/560645934