Description
【现象描述】
使用dpdk 18.11的dpvs,在网口初始化流程netif_port_init中,网口初始化完成后添加kni_dev,并设置kni的MAC地址使其和物理口的一致。实际发现设置kni的MAC地址的时候总是超时,提示 "linux_set_if_mac: fail to set kni0's MAC address: Timer expired"。
【原因分析】
设置MAC地址的linux_set_if_mac是通过ioctl(..., SIOCSIFHWADDR, ...)实现的,系统调用最终会执行kni内核模块的ndo_set_mac_address,也即kni_net_set_mac,其中的kni_net_process_request,会把MAC地址的变动作为request放到kni fifo队列,并等待用户态从kni fifo队列取走request,用户态处理完毕回一个reply到kni fifo队列,此后kni内核模块的等待队列被唤醒,整个流程走完:
ioctl --> ndo_set_mac_address --> kni_net_set_mac --> kni_net_process_request --> kni_fifo_put(req) --> wait_event_interruptible_timeout -> kni_fifo_get(resp)
问题出现在网口初始化的时候用户态没有执行kni_process_on_master响应kni内核模块的请求,导致wait event超时,ioctl返回'Timer expired'。事实上kni_process_on_master是在所有初始化完成后master线程的while循环里执行的,因此在网口初始化流程里企图改变kni口的一些信息如MAC地址、MTU等都会发生超时。以下回调凡是用了kni_net_process_request都会有这个问题:
static const struct net_device_ops kni_net_netdev_ops = {
.ndo_open = kni_net_open,
.ndo_stop = kni_net_release,
.ndo_set_config = kni_net_config,
.ndo_change_rx_flags = kni_net_set_promiscusity,
.ndo_start_xmit = kni_net_tx,
.ndo_change_mtu = kni_net_change_mtu,
.ndo_do_ioctl = kni_net_ioctl,
.ndo_set_rx_mode = kni_net_set_rx_mode,
.ndo_get_stats = kni_net_stats,
.ndo_tx_timeout = kni_net_tx_timeout,
.ndo_set_mac_address = kni_net_set_mac,
#ifdef HAVE_CHANGE_CARRIER_CB
.ndo_change_carrier = kni_net_change_carrier,
#endif
};
【影响范围】
影响程序启动的时间,每个port初始化都会3秒超时,程序启动将会拖长 3 * nr_ports 秒的时间。
不影响MAC地址的设置,kni口MAC地址仍能配置成功。
【为什么dpdk 17.11没有这个问题】
dpdk 17.11 的回调 kni_net_set_mac没有wait event的操作:
static int
kni_net_set_mac(struct net_device *netdev, void *p)
{
struct sockaddr *addr = p;
if (!is_valid_ether_addr((unsigned char *)(addr->sa_data)))
return -EADDRNOTAVAIL;
memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len);
return 0;
}
【解决办法】
这个问题似乎是无解的,除非使用异步的方式设置MAC地址。