网络设备驱动发送锁

在协议栈上层提交报文到设备驱动时,内核使用发送锁锁住驱动的报文发送函数netdev_start_xmit,或者其封装函数dev_hard_start_xmit,如下为函数dev_direct_xmit中的调用。

int dev_direct_xmit(struct sk_buff *skb, u16 queue_id)
{
     local_bh_disable();
     
     HARD_TX_LOCK(dev, txq, smp_processor_id());
     if (!netif_xmit_frozen_or_drv_stopped(txq))
         ret = netdev_start_xmit(skb, dev, txq, false);
     HARD_TX_UNLOCK(dev, txq);

内核使用两个宏(HARD_TX_LOCK/HARD_TX_UNLOCK)来完成lock和unlock操作,注意网络设备features中的NETIF_F_LLTX标志,如果设置了此标志,表明此设备将在内部执行锁操作,此时宏HARD_TX_LOCK将不执行锁操作,而在驱动内部执行trylock来获取内部锁,如果获取失败,返回NETDEV_TX_LOCKED ,这需要协议栈稍后重新尝试发送。由于内核的开发者不赞同这种上下层代码的交互设计方式,目前此标志已经不建议在新的设备驱动中使用。

#define HARD_TX_LOCK(dev, txq, cpu) {           \
    if ((dev->features & NETIF_F_LLTX) == 0) {  \
        __netif_tx_lock(txq, cpu);      \
    } else {                    \
        __netif_tx_acquire(txq);        \
    }                       \
}    
#define HARD_TX_UNLOCK(dev, txq) {          \
    if ((dev->features & NETIF_F_LLTX) == 0) {  \
        __netif_tx_unlock(txq);         \
    } else {                    \
        __netif_tx_release(txq);        \
    }                       \
} 

但是,对于内核中的隧道虚拟设备,如vlan、gre、vti、ipip和tun等设备,其都是建立在物理网络设备之上,报文的顺序发送可由底层的物理设备结构中的发送锁实现,而不需要在自身发送报文时,进行锁操作,所以tunnel设备的features设置NETIF_F_LLTX标志,避免两次锁操作。

./net/8021q/vlan_dev.c:569:     dev->features |= dev->hw_features | NETIF_F_LLTX;
./net/bridge/br_device.c:430:   dev->features = COMMON_FEATURES | NETIF_F_LLTX | NETIF_F_NETNS_LOCAL |
./net/ipv4/ip_gre.c:813:        dev->features |= NETIF_F_LLTX;
./net/ipv4/ip_vti.c:433:        dev->features           |= NETIF_F_LLTX;
./net/ipv4/ipip.c:382:          dev->features           |= NETIF_F_LLTX;
./drivers/net/tun.c:2821:       dev->features = dev->hw_features | NETIF_F_LLTX;
./drivers/net/geneve.c:1137:    dev->features    |= NETIF_F_LLTX;

以下为发送锁函数__netif_tx_lock,可见锁的粒度为设备的发送队列,并且,在spin_lock成功锁定之后,将记录当前执行发送的处理器核心。

static inline void __netif_tx_lock(struct netdev_queue *txq, int cpu)
{
    spin_lock(&txq->_xmit_lock);
    txq->xmit_lock_owner = cpu;
} 

如下发送函数__dev_queue_xmit,如果当前选择的发送队列并没有被当前的处理器cpu锁定,可继续执行发送操作。

static int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
{
    if (dev->flags & IFF_UP) {
        int cpu = smp_processor_id(); /* ok because BHs are off */

        if (txq->xmit_lock_owner != cpu) {
            if (unlikely(__this_cpu_read(xmit_recursion) >
                     XMIT_RECURSION_LIMIT))
                goto recursion_alert;
            HARD_TX_LOCK(dev, txq, cpu);

            if (!netif_xmit_stopped(txq)) {
                __this_cpu_inc(xmit_recursion);
                skb = dev_hard_start_xmit(skb, dev, txq, &rc);
				__this_cpu_dec(xmit_recursion);
                ...
            }
            HARD_TX_UNLOCK(dev, txq);
        } else {
            /* Recursion is detected! It is possible, unfortunately
             */
recursion_alert:
            net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", dev->name);
        }

另外,前面提到对于虚拟隧道设备,此处并未获取发送锁,因此,对于一些存在设计缺陷的隧道设备驱动,有可能在此陷入递归,内核使用每处理器变量xmit_recursion记录递归的次数,如果超过最大值XMIT_RECURSION_LIMIT(10),则认为出错,打印警告信息。

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页