在注册网络设备函数register_netdevice中将调用Qdisc初始化函数dev_init_scheduler,这里将创建设备的watchdog定时器,超时处理函数设置为dev_watchdog,用来监控网络设备的发送队列传输超时。
void dev_init_scheduler(struct net_device *dev) { dev->qdisc = &noop_qdisc; netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc); if (dev_ingress_queue(dev)) dev_init_scheduler_queue(dev, dev_ingress_queue(dev), &noop_qdisc); timer_setup(&dev->watchdog_timer, dev_watchdog, 0); }
讯享网
watchdog的开启
内核函数__netdev_watchdog_up负责开启watchdog定时器,如下所示,如果超时时长小于等于0,内核默认成5秒钟。网卡驱动程序可修改此时长,例如Mellanox的mlx5的驱动将此值设置为15秒;Intel的网卡驱动基本都是使用5秒的默认值。
另外,如果设备驱动中没有实现超时处理函数ndo_tx_timeout,这里并不启动watchdog定时器。驱动中为实现超时处理,可能是驱动并不能由导致发送超时的错误中进行恢复。
讯享网void __netdev_watchdog_up(struct net_device *dev) { if (dev->netdev_ops->ndo_tx_timeout) { if (dev->watchdog_timeo <= 0) dev->watchdog_timeo = 5*HZ; if (!mod_timer(&dev->watchdog_timer, round_jiffies(jiffies + dev->watchdog_timeo))) dev_hold(dev);
在检测到设备的物理链路UP之后,将开启设备的watchdog定时器。
void netif_carrier_on(struct net_device *dev) { if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) { if (dev->reg_state == NETREG_UNINITIALIZED) return; atomic_inc(&dev->carrier_up_count); linkwatch_fire_event(dev); if (netif_running(dev)) __netdev_watchdog_up(dev);
或者,在检测到网络设备由节能状态恢复回来,重新接入系统时,开启watchdog定时器,参见以下函数。
讯享网void netif_device_attach(struct net_device *dev) { if (!test_and_set_bit(__LINK_STATE_PRESENT, &dev->state) && netif_running(dev)) { netif_tx_wake_all_queues(dev); __netdev_watchdog_up(dev);
对于Intel的i40e网卡驱动程序,其实现了ndo_tx_timeout函数,即i40e_tx_timeout,并且,其显式指定了网络设备的watchdog定时器超时时间为5秒(这里与默认的相同)。
static const struct net_device_ops i40e_netdev_ops = { ... .ndo_tx_timeout = i40e_tx_timeout, static int i40e_config_netdev(struct i40e_vsi *vsi) { ... netdev->watchdog_timeo = 5 * HZ;
watchdog的关闭
如下,在函数dev_watchdog_down中,内核删除网络设备的watchdog定时器。
讯享网static void dev_watchdog_down(struct net_device *dev) { netif_tx_lock_bh(dev); if (del_timer(&dev->watchdog_timer)) dev_put(dev); netif_tx_unlock_bh(dev); }
以上函数的调用位于函数dev_deactivate_many中,其的调用有两处,一是__dev_close_many函数,即用户shutdown网络设备时,停止watchdog计时器。另一处是封装函数dev_deactivate。
void dev_deactivate_many(struct list_head *head) { struct net_device *dev; list_for_each_entry(dev, head, close_list) { ... dev_watchdog_down(dev);
函数dev_deactivate在设备链路状态发生变化时,参见netif_carrier_on和netif_carrier_off函数,linkwatch功能将在link事件处理函数linkwatch_do_dev中根据链路状态调用设备的活动和非活动函数。如果链路down,调用dev_deactivate,其中会删除设备watchdog定时器;反之,链路UP,将调用dev_activate函数,其中将开启watchdog计时器。
讯享网static void linkwatch_do_dev(struct net_device *dev) { if (dev->flags & IFF_UP && netif_device_present(dev)) { if (netif_carrier_ok(dev)) dev_activate(dev); else dev_deactivate(dev);
另外,以上节介绍的netif_carrier_on函数功能不同,在函数netif_carrier_off中并没有显示的关闭watchdog定时器,而是调用了linkwatch功能的函数linkwatch_fire_event,添加链路事件,最终也是由linkwatch_do_dev在事件处理时,关闭watchdog定时器。
void netif_carrier_off(struct net_device *dev) { if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) { if (dev->reg_state == NETREG_UNINITIALIZED) return; atomic_inc(&dev->carrier_down_count); linkwatch_fire_event(dev);
watchdog超时处理
首先看一下队列发送事件的计算,由函数txq_trans_update完成,记录在发送队列结构的成员trans_start中。
讯享网static inline void txq_trans_update(struct netdev_queue *txq) { if (txq->xmit_lock_owner != -1) txq->trans_start = jiffies; }
在内核的设备核心发送函数netdev_start_xmit中,发送成功之后,更新发送时间。
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq, bool more) { rc = __netdev_start_xmit(ops, skb, dev, more); if (rc == NETDEV_TX_OK) txq_trans_update(txq);
超时处理函数dev_watchdog如下所示,如果设备在所有发送队列上都使用Qdisc的noop类型,不进行处理。否则,变量每个队列,检查发送停止的队列,通过比较最近一次的发送时间和当前时间的差值,如果停止时间超过设置的超时时间watchdog_timeo,即认为此队列发送超时。
函数dev_watchdog将打印部分出错的队列信息,并且调用设备驱动的超时处理函数ndo_tx_timeout。
讯享网static void dev_watchdog(struct timer_list *t) { struct net_device *dev = from_timer(dev, t, watchdog_timer); netif_tx_lock(dev); if (!qdisc_tx_is_noop(dev)) { if (netif_device_present(dev) && netif_running(dev) && netif_carrier_ok(dev)) { for (i = 0; i < dev->num_tx_queues; i++) { struct netdev_queue *txq; txq = netdev_get_tx_queue(dev, i); trans_start = txq->trans_start; if (netif_xmit_stopped(txq) && time_after(jiffies, (trans_start + dev->watchdog_timeo))) { some_queue_timedout = 1; txq->trans_timeout++; break; } } if (some_queue_timedout) { WARN_ONCE(1, KERN_INFO "NETDEV WATCHDOG: %s (%s): transmit queue %u timed out\n", dev->name, netdev_drivername(dev), i); dev->netdev_ops->ndo_tx_timeout(dev); } if (!mod_timer(&dev->watchdog_timer, round_jiffies(jiffies + dev->watchdog_timeo))) dev_hold(dev);
驱动超时处理
此处以Intel的网卡驱动i40e为例,以下为其超时处理函数i40e_tx_timeout,可见,其第一部分的处理与上一节函数dev_watchdog中的基本相同,这里找出第一个超时的队列。
static void i40e_tx_timeout(struct net_device *netdev) { pf->tx_timeout_count++; /* find the stopped queue the same way the stack does */ for (i = 0; i < netdev->num_tx_queues; i++) { struct netdev_queue *q; unsigned long trans_start; q = netdev_get_tx_queue(netdev, i); trans_start = q->trans_start; if (netif_xmit_stopped(q) && time_after(jiffies, (trans_start + netdev->watchdog_timeo))) { hung_queue = i; break; } }
变量tx_timeout_last_recovery控制超时处理的时间间隔,不能小于watchdog_timeo的值,即处理完成一个队列之后才能处理下一个队列的超时。另外,函数中定义了恢复等级tx_timeout_recovery_level,超出20秒钟,由等级1开始。
讯享网 if (time_after(jiffies, (pf->tx_timeout_last_recovery + HZ*20))) pf->tx_timeout_recovery_level = 1; /* reset after some time */ else if (time_before(jiffies, (pf->tx_timeout_last_recovery + netdev->watchdog_timeo))) return; /* don't do any new action before the next timeout */ /* don't kick off another recovery if one is already pending */ if (test_and_set_bit(__I40E_TIMEOUT_RECOVERY_PENDING, pf->state)) return; pf->tx_timeout_last_recovery = jiffies;
以下,每次超时处理,将恢复等级加一,等级越高说明问题越严重,需要执行的恢复操作分别为PF、CORE和GLOBAL三个级别。
netdev_info(netdev, "tx_timeout recovery level %d, hung_queue %d\n", pf->tx_timeout_recovery_level, hung_queue); switch (pf->tx_timeout_recovery_level) { case 1: set_bit(__I40E_PF_RESET_REQUESTED, pf->state); break; case 2: set_bit(__I40E_CORE_RESET_REQUESTED, pf->state); break; case 3: set_bit(__I40E_GLOBAL_RESET_REQUESTED, pf->state); break; default: netdev_err(netdev, "tx_timeout recovery unsuccessful\n"); break; } i40e_service_event_schedule(pf); pf->tx_timeout_recovery_level++;
内核版本 5.0

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/127794.html