qdisc_restart的执行流程:
qdisc_restart
--->sch_direct_xmit
--->dev_hard_start_xmit(skb, dev, txq)
---->ops->ndo_start_xmit(skb, dev)
/*
* NOTE: Called under qdisc_lock(q) with locally disabled BH.
*
* __QDISC_STATE_RUNNING guarantees only one CPU can process
* this qdisc at a time. qdisc_lock(q) serializes queue accesses for
* this queue.
*
* netif_tx_lock serializes accesses to device driver.
*
* qdisc_lock(q) and netif_tx_lock are mutually exclusive,
* if one is grabbed, another must be free.
*
* Note, that this procedure can be called by a watchdog timer
*
* Returns to the caller:
* 0 - queue is empty or throttled.
* >0 - queue is not empty.
*
*/
static inline int qdisc_restart(struct Qdisc *q)
{
struct netdev_queue *txq;
struct net_device *dev;
spinlock_t *root_lock;
struct sk_buff *skb;
/* Dequeue packet */
skb = dequeue_skb(q);
if (unlikely(!skb))
return 0;
WARN_ON_ONCE(skb_dst_is_noref(skb));
// 因为sch_direct_xmit里面在调用设备的发送函数前要释放掉root_lcok, 这里准备作为一个参数传递进去
root_lock = qdisc_lock(q);
dev = qdisc_dev(q);
txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));// 这里通过qdisc找到txq不行吗?为什么非要通过skb来找到?
// 如果sch_direct_xmit返回值大于0,表示队列非空;返回0表示队列空了或者发送失败
return sch_direct_xmit(skb, q, dev, txq, root_lock);
}
/*
* Transmit one skb, and handle the return status as required. Holding the
* __QDISC_STATE_RUNNING bit guarantees that only one CPU can execute this
* function.
*
* Returns to the caller:
* 0 - queue is empty or throttled.
* >0 - queue is not empty.
*/
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);
// 可以看到这里把root_lock释放掉了,也就是说另外一个等待root_lock的进程或softirq可以操作此qdisc的队列了, 即enqueue/dequeue 操作可以工作了。
// 这样可以使得enqueue/dequeue 的操作与硬件BD的操作并行。因为enqueue/dequeue 的操作与硬件BD的操作是互不关联的动作,可以并行
// 但是对于同一个工作队列来说,BD的操作应当互斥,所以HARD_TX_LOCK考虑保护同一个工作队列内部的BD操作。
// 如果硬件上是多队列的,队列之间不存在互相干扰的关系, 因为队列之间的硬件寄存器本来就是多套。而假如硬件上是单队列,软件上想实现多队列,这里就
// 需要在driver的transimit函数里加一把自旋锁防止多cpu同时访问同一队列的BD。
/*
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); \
} \
}
#define HARD_TX_UNLOCK(dev, txq) { \
if ((dev->features & NETIF_F_LLTX) == 0) { \
__netif_tx_unlock(txq); \
} \
}
如果当前设备没有NETIF_F_LLTX特性,则取txq->_xmit_lock这个spin_lock,
如果当前设备有NETIF_F_LLTX特性, 表示设备驱动里面会实现一个锁机制,这个框架就不要担心了
*/
HARD_TX_LOCK(dev, txq, smp_processor_id());
if (!netif_xmit_frozen_or_stopped(txq))
ret = dev_hard_start_xmit(skb, dev, txq);
HARD_TX_UNLOCK(dev, txq);
spin_lock(root_lock);
if (dev_xmit_complete(ret)) {//发送成功,返回当前队列的长度
/* Driver sent out skb successfully or skb was consumed */
ret = qdisc_qlen(q);
} else if (ret == NETDEV_TX_LOCKED) { //只有使能NETIF_F_LLTX feature时,才会返回该值,表示无法获得驱动锁
/* Driver try lock failed */
ret = handle_dev_cpu_collision(skb, txq, q);
// 如果不能获得驱动锁,表示其他cpu在访问这个发送驱动锁
// a. 如果是当前cpu已经获得了这个锁,有可能递归了。 b. 如果其他cpu获得了这个锁,把skb重新放回队列
} else { // NETDEV_TX_BUSY,表示dev的发送缓冲区不够,需要把skb重新放回队列
/* Driver returned NETDEV_TX_BUSY - requeue skb */
if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))
pr_warning("BUG %s code %d qlen %d\n",
dev->name, ret, q->q.qlen);
ret = dev_requeue_skb(skb, q);
}
if (ret && netif_xmit_frozen_or_stopped(txq))
ret = 0;
return ret;
}