linux协议栈中提供的报文发送函数有两个,一个是链路层提供给网络层的发包函数dev_queue_xmit()。另一个就是软中断发包函数直接调用的函数sch_direct_xmit()。
这两个函数最终都会调用dev_hard_start_xmit()来发送报文。
一、发送函数的调用关系:
二、发送过程中锁的使用:
1、队列策略缓存报文的队列使用 qdisc->q.lock来保护。使用函数qdisc_lock来返回锁。
static inline spinlock_t *qdisc_lock(struct Qdisc *qdisc) { return &qdisc->q.lock; }
2、调用网卡驱动发送报文时,使用锁dev->dev_queue->_xmit_lock来保护,防止多个cpu核同时操作网络设备。使用宏HARD_TX_LOCK 来上锁。
dev->dev_queue->xmt_lock_owner 记录持有该锁的cpu id.
static inline void __netif_tx_lock(struct netdev_queue *txq, int cpu) { spin_lock(&txq->_xmit_lock); txq->xmit_lock_owner = cpu; }
#define HARD_TX_LOCK(dev, txq, cpu) { \ if ((dev->features & NETIF_F_LLTX) == 0) { \ __netif_tx_lock(txq, cpu); \ } \ } static inline void __netif_tx_lock(struct netdev_queue *txq, int cpu) { spin_lock(&txq->_xmit_lock); //记录持有队列锁的cpu id。 txq->xmit_lock_owner = cpu; }
三、发送函数详解
1、dev_hard_start_xmit()
调用该函数时必须持有dev->dev_queue->_xmit_lock锁。
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) { /*获得发送设备的操作函数*/ const struct net_device_ops *ops = dev->netdev_ops; int rc; /*当GSO发送大数据时,如果设备硬件不支持GSO功能, 就需要使用软件来对大报文进行切割, 这时切割后的所有小报文就挂在skb->next后面。 进入该函数时,大报文一般还没经过GSO的切割, skb->next = =NULL*/ if (likely(!skb->next)) { /*如果注册有嗅探器来关注发送报文, 就拷贝一份报文给嗅探器*/ if (!list_empty(&ptype_all)) { dev_queue_xmit_nit(skb, dev); } /*如果报文需要进行GSO软件分割*/ if (netif_needs_gso(dev, skb)) { /*如果调用GSO分割大包失败,就丢弃报文*/ if (unlikely(dev_gso_segment(skb))) { goto out_kfree_skb; } /*经过GSO分割后的报文不止一个, 跳到处理GSO处进行处理*/ if (skb->next) { goto gso; } } /*如果报文不需要GSO软件分割,直接发送*/ /* * If device doesnt need skb->dst, * release it right now while * its hot in this cpu cache */ if (dev->priv_flags & IFF_XMIT_DST_RELEASE) { skb_dst_drop(skb); } /*调用网络驱动提供的发送函数把报文发送出去*/ rc = ops->ndo_start_xmit(skb, dev); if (rc == NETDEV_TX_OK) { txq_trans_update(txq); } return rc; } /*如果报文经过软件分割后,skb->next后挂着一串分割出的小包, 循环把挂着的小包发送出去*/ gso: do { struct sk_buff *nskb = skb->next; skb->next = nskb->next; nskb->next = NULL; /* * If device doesnt need nskb->dst, * release it right now while * its hot in this cpu cache */ if (dev->priv_flags & IFF_XMIT_DST_RELEASE) { skb_dst_drop(nskb); } rc = ops->ndo_start_xmit(nskb, dev); if (unlikely(rc != NETDEV_TX_OK)) { nskb->next = skb->next; skb->next = nskb; return rc; } txq_trans_update(txq); if (unlikely(netif_tx_queue_stopped(txq) && skb->next)) { return NETDEV_TX_BUSY; } } while (skb->next); /*在GSO软件分割报文时,原始skb的destructor 函数被替换成 释放一串报文的释放函数,一旦发送失败后 保证分割后的一串skb 都能释放掉。 现在一串skb 已经都发送成功了, 还原原始skb 的destructor函数,释放原始skb时使用 */ skb->destructor = DEV_GSO_CB(skb)->destructor; out_kfree_skb: kfree_skb(skb); return NETDEV_TX_OK; }
2、sch_direct_xmit
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, struct net_device *dev, struct netdev_queue *txq, spinlock_t *root_lock) { int ret = NETDEV_TX_BUSY; /*调用该函数时队列策略的队列锁已经被锁了,现在解锁*/ /* And release qdisc */ spin_unlock(root_lock); /*取得发送队列的锁*/ HARD_TX_LOCK(dev, txq, smp_processor_id()); /*如果发送队列已经开启并且发送队列没有僵死, 直接调用 dev_hard_start_xmit发送报文*/ if (!netif_tx_queue_stopped(txq) && !netif_tx_queue_frozen(txq)) { ret = dev_hard_start_xmit(skb, dev, txq); } HARD_TX_UNLOCK(dev, txq); spin_lock(root_lock); switch (ret) { /*如果发送报文成功,返回队列策略队列中缓存报文的个数*/ case NETDEV_TX_OK: /* Driver sent out skb successfully */ ret = qdisc_qlen(q); break; /*如果没有获取到网络设备的发送锁,处理锁冲突*/ case NETDEV_TX_LOCKED: /* Driver try lock failed */ ret = handle_dev_cpu_collision(skb, txq, q); break; /*否则,表明发送报文失败,这时把报文存入队列策略的发送队列中*/ default: /* Driver returned NETDEV_TX_BUSY - requeue skb */ if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit())) printk(KERN_WARNING "BUG %s code %d qlen %d\n", dev->name, ret, q->q.qlen); ret = dev_requeue_skb(skb, q); break; } if (ret && (netif_tx_queue_stopped(txq) || netif_tx_queue_frozen(txq))) ret = 0; return ret; }
处理获取发送锁失败的函数:
static inline int handle_dev_cpu_collision(struct sk_buff *skb, struct netdev_queue *dev_queue, struct Qdisc *q) { int ret; /*如果cpu 获取锁失败,并且该锁还被本cpu 占用, 表示发送队列已经死锁,丢弃报文并打印警告*/ if (unlikely(dev_queue->xmit_lock_owner == smp_processor_id())) { /* * Same CPU holding the lock. It may be a transient * configuration error, when hard_start_xmit() recurses. We * detect it by checking xmit owner and drop the packet when * deadloop is detected. Return OK to try the next skb. */ kfree_skb(skb); if (net_ratelimit()) printk(KERN_WARNING "Dead loop on netdevice %s, " "fix it urgently!\n", dev_queue->dev->name); ret = qdisc_qlen(q); } /*锁被其他cpu锁占用,这时我们把报文 重新存入队列策略的发送队列,延迟发送*/ else { /* * Another cpu is holding lock, requeue & delay xmits for * some time. */ __get_cpu_var(netdev_rx_stat).cpu_collision++; ret = dev_requeue_skb(skb, q); } return ret; }
发送失败后重新入队函数:
static inline int dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) { //把skb 挂到 q->gs_skb上,下次优先发送 q->gso_skb = skb; q->qstats.requeues++; /* it's still part of the queue */ q->q.qlen++; //重新调度队列策略 __netif_schedule(q); return 0; }
3、__dev_xmit_skb
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q, struct net_device *dev, struct netdev_queue *txq) { spinlock_t *root_lock = qdisc_lock(q); int rc; /*取得队列策略的锁*/ spin_lock(root_lock); /*如果队列策略被显示的禁止,就丢弃报文*/ if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) { kfree_skb(skb); rc = NET_XMIT_DROP; } /*如果队列策略中没有等待发送的报文,发送该报文*/ else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) && !test_and_set_bit(__QDISC_STATE_RUNNING, &q->state)) { __qdisc_update_bstats(q, skb->len); if (sch_direct_xmit(skb, q, dev, txq, root_lock)) { /*如果发送报文返回不为0, 表示队列策略队列中还存在等待发送的报文, 这时重新调度队列策略*/ __qdisc_run(q); } else { /*如果发送报文返回为0, 表示发送成功后队列策略中没有等待发送的报文, 这时清除队列策略的running 状态*/ clear_bit(__QDISC_STATE_RUNNING, &q->state); } rc = NET_XMIT_SUCCESS; } /*如果队列策略中有等待发送的报文, 就把该报文放入队列中, 调用qdisc_run来进行调度发送*/ else { rc = qdisc_enqueue_root(skb, q); qdisc_run(q); } spin_unlock(root_lock); return rc; }
4、dev_queue_xmit
该函数是提供给协议栈中网络层调用的发送函数。
int dev_queue_xmit(struct sk_buff *skb) { struct net_device *dev = skb->dev; struct netdev_queue *txq; struct Qdisc *q; int rc = -ENOMEM; /*如果需要GSO,进行GSO分割处理*/ if (netif_needs_gso(dev, skb)) { goto gso; } /*如果skb 是一串ip 分片报文, 并且网络硬件不支持自动重组操作, 就进行分片重组成一个报文 */ /*如果失败,就丢弃报文*/ if (skb_has_frags(skb) && !(dev->features & NETIF_F_FRAGLIST) && __skb_linearize(skb)) { goto out_kfree_skb; } /*如果报文分别放在多个页中, 并且网络硬件不支持 G/S功能,就进行重组*/ /*如果重组失败,就丢弃报文*/ if (skb_shinfo(skb)->nr_frags && (!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) && __skb_linearize(skb)) { goto out_kfree_skb; } /*如果报文没有添写校验和*/ if (skb->ip_summed == CHECKSUM_PARTIAL) { /*设备传输层的指针*/ skb_set_transport_header(skb, skb->csum_start - skb_headroom(skb)); /*并且网络设备不支持自动填写校验和就要由 软件来填写,如果软件填写失败,就丢弃报文*/ if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb)) { goto out_kfree_skb; } } gso: rcu_read_lock_bh(); /*根据报文来取得发送队列*/ txq = dev_pick_tx(dev, skb); /*取得发送队列的队列策略*/ q = rcu_dereference(txq->qdisc); #ifdef CONFIG_NET_CLS_ACT skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS); #endif /*如果队列策略实现了入队操作, 就调用__dev_xmit_skb进行发送*/ if (q->enqueue) { rc = __dev_xmit_skb(skb, q, dev, txq); goto out; } /*如果队列策略没有实现入队操作,表明发送队列长度为0, *一般发送队列长度为0 的设备是网络虚拟设备,发送失败 *后不需要缓存*/ /*如果设备是up 状态,进行报文的发送*/ if (dev->flags & IFF_UP) { int cpu = smp_processor_id(); /*如果设备的发送队列锁的持有cpu 不是本cpu时*/ if (txq->xmit_lock_owner != cpu) { /*获取发送队列的锁*/ HARD_TX_LOCK(dev, txq, cpu); /*如果发送队列时开启的*/ if (!netif_tx_queue_stopped(txq)) { rc = NET_XMIT_SUCCESS; /*调用 dev_hard_start_xmit发送报文*/ if (!dev_hard_start_xmit(skb, dev, txq)) { HARD_TX_UNLOCK(dev, txq); goto out; } } HARD_TX_UNLOCK(dev, txq); /*如果发送队列被关闭,这时就要丢弃报文*/ if (net_ratelimit()) { printk(KERN_CRIT "Virtual device %s asks to " queue packet!\n", dev->name); } } else { if (net_ratelimit()) { printk(KERN_CRIT "Dead loop on virtual device " "%s, fix it urgently!\n", dev->name); } } } rc = -ENETDOWN; rcu_read_unlock_bh(); out_kfree_skb: kfree_skb(skb); return rc; out: rcu_read_unlock_bh(); return rc; } EXPORT_SYMBOL(dev_queue_xmit);