在linux协议栈中,发送队列管理队列策略,而直接管理发送报文的是队列策略。所有发包软中断中调度的是队列策略,而不是发送队列。
一、softnet_date结构体中为报文的发送定义如下字段:struct softnet_data
{
//有报文需要发送的队列策略链表。
struct Qdisc *output_queue;
//调用 dev_kfree_skb_irq()函数延迟释放的skb链表。
struct sk_buff *completion_queue;
};
二、队列策略的调度:
1、队列策略的运行状态:
运行状态是由Qdisc->state字段来标记的。该字段使用的是位图,每一位定义如下enum qdisc_state_t
{
//队列策略处于运行状态
__QDISC_STATE_RUNNING,
//队列策略已经被调度了,既已经添加到了软中断管理的链表中了。
__QDISC_STATE_SCHED,
//队列策略被显示的禁用了。
__QDISC_STATE_DEACTIVATED,
};
2、发送队列策略的调度:void __netif_schedule(struct Qdisc *q)
{
/*如果队列策略没被调度,设置队列策略为调度状态*/
if (!test_and_set_bit(__QDISC_STATE_SCHED, &q->state))
{
/*调度设备队列策略*/
__netif_reschedule(q);
}
}static inline void __netif_reschedule(struct Qdisc *q)
{
struct softnet_data *sd;
unsigned long flags;
/*关闭硬中断*/
local_irq_save(flags);
/*取得当前CPU上的softnet_data结构*/
sd = &__get_cpu_var(softnet_data);
/*把设备的qdisc队列策略挂到softnet_data的 output_queue上*/
q->next_sched = sd->output_queue;
sd->output_queue = q;
/*调度发包软中断*/
raise_softirq_irqoff(NET_TX_SOFTIRQ);
/*恢复硬中断*/
local_irq_restore(flags);
}
三、启动/停止设备发送数据
网络设备的发送的控制是由发送队列来控制的。通过设置发送队列的运行状态来控制网络设备的发送。
1、发送队列的运行状态:
队列运行状态dev_queue->state:该字段是用位图来标记队列的运行状态。
enum netdev_queue_state_t
{
/*如果设置了该位,表示设备发送队列被关闭。
清除该位,表示开启发送队列。*/
__QUEUE_STATE_XOFF,
//如果设置了该位,表示设备发送队列已经僵死。
__QUEUE_STATE_FROZEN,
};
2、开启设备的发送:
void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
void netif_start_queue(struct net_device *dev)
{
netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}
/*开启全部发送队列:*/
void netif_tx_start_all_queues(struct net_device *dev)
{
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++)
{
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
netif_tx_start_queue(txq);
}
}
设备有一个队列时调用netif_start_queue(),有多个发送队列时用netif_tx_start_all_queues()。
开启发送队列一般由dev->netdev_ops->ndo_open(dev)函数进行设置。
3、关闭设备的发送:
void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
void netif_stop_queue(struct net_device *dev)
{
netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
}
/*关闭全部发送队列:*/
void netif_tx_stop_all_queues(struct net_device *dev)
{
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++)
{
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
netif_tx_stop_queue(txq);
}
}
设备有一个队列时调用netif_stop_queue(),有多个发送队列时调用netif_tx_stop_all_queues()。
关闭发送队列场景一般如下:
(1)、由dev->netdev_ops->ndo_stop(dev)函数进行设置。
(2)、当驱动程序意识到硬件没有足够的空间缓存新的发送报文时,驱动程序调用函数关闭发送队列,这样避免在将来的发送过程中浪费资源,因为内核知道发送会失败,就不会再发送新的报文给网络设备。
4、唤醒网络设备的发送队列:
唤醒网络设备发送队列,重新启动网络设备的发送功能。void netif_tx_wake_queue(struct netdev_queue *dev_queue)
{
/*开启发送队列,重新调度发送队列的队列策略*/
if (test_and_clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state))
__netif_schedule(dev_queue->qdisc);
}
void netif_wake_queue(struct net_device *dev)
{
netif_tx_wake_queue(netdev_get_tx_queue(dev, 0));
}
void netif_tx_wake_all_queues(struct net_device *dev)
{
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++) {
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
netif_tx_wake_queue(txq);
}
}
当发送队列已经被关闭,但网卡现在已经有资源可用了,就需要重新启动设备的发送过程,调用以上函数来唤醒发送队列。
四、发包软中断:
1、发包软中断的注册:
static int __init net_dev_init(void)
{
。。。。。。
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
。。。。。。
}
2、发包软中断处理函数:
该函数做两件工作
(1)、回收调用dev_kfree_skb函数后延迟释放的报文。
(2)、调用qdisc_restart()来处理等待发送的队列策略上的报文。
static void net_tx_action(struct softirq_action *h)
{
/*取得本地cpu的softnet_data*/
struct softnet_data *sd = &__get_cpu_var(softnet_data);
/*如果completion_queue上有要释放的报文,就把上面所有的报文释放掉*/
if (sd->completion_queue)
{
/*取得要释放的报文链,把softnet_data的completion_queue置空*/
struct sk_buff *clist;
local_irq_disable();
clist = sd->completion_queue;
sd->completion_queue = NULL;
local_irq_enable();
/*循环释放所有报文*/
while (clist)
{
struct sk_buff *skb = clist;
clist = clist->next;
WARN_ON(atomic_read(&skb->users));
__kfree_skb(skb);
}
}
/*如果softnet_data上有等待发送报文的队列策略*/
if (sd->output_queue)
{
/*取得等待发送报文的队列策略*/
struct Qdisc *head;
local_irq_disable();
head = sd->output_queue;
sd->output_queue = NULL;
local_irq_enable();
/*循环处理每个等待发送报文的队列策略*/
while (head)
{
struct Qdisc *q = head;
spinlock_t *root_lock;
head = head->next_sched;
/*发送队列策略上的发送队列由qdisc->q.lock来保护*/
root_lock = qdisc_lock(q);
/*如果获得锁成功,就处理队列策略上的报文*/
if (spin_trylock(root_lock))
{
smp_mb__before_clear_bit();
/*清除队列策略的被调度状态*/
clear_bit(__QDISC_STATE_SCHED,
&q->state);
/*调用qdisc_run 来处理队列策略上要发送的报文*/
qdisc_run(q);
spin_unlock(root_lock);
}
else
{
/*如果获得锁失败,并且队列策略没有被禁止,
就重新调度队列策略*/
if (!test_bit(__QDISC_STATE_DEACTIVATED,
&q->state))
{
__netif_reschedule(q);
}
/*如果获得锁失败,并且队列策略已经被禁止,
就清除队列策略的调度状态*/
else
{
smp_mb__before_clear_bit();
clear_bit(__QDISC_STATE_SCHED,
&q->state);
}
}
}
}
}
3、处理队列策略上报文的函数qdisc_run:
static inline void qdisc_run(struct Qdisc *q)
{
/*设置队列策略为运行状态*/
if (!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))
{
__qdisc_run(q);
}
}void __qdisc_run(struct Qdisc *q)
{
unsigned long start_time = jiffies;
/*循环处理队列策略上的报文*/
while (qdisc_restart(q))
{
/*如果需要重新调度或运行时间已经超过了一个jiffies了,
就停止运行并重新调度该队列策略*/
if (need_resched() || jiffies != start_time)
{
__netif_schedule(q);
break;
}
}
/*处理完成,清除队列策略的运行状态*/
clear_bit(__QDISC_STATE_RUNNING, &q->state);
}
处理队列策略上的一个报文static inline int qdisc_restart(struct Qdisc *q)
{
struct netdev_queue *txq;
struct net_device *dev;
spinlock_t *root_lock;
struct sk_buff *skb;
/* 从队列策略上取一个报文 */
skb = dequeue_skb(q);
if (unlikely(!skb))
return 0;
root_lock = qdisc_lock(q);
dev = qdisc_dev(q);
/*根据skb找到所属设备的发送队列*/
txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
/*直接发送出去,详见下一节*/
return sch_direct_xmit(skb, q, dev, txq, root_lock);
}
从队列策略所管理的报文队列上取的一个报文:static inline struct sk_buff *dequeue_skb(struct Qdisc *q)
{
struct sk_buff *skb = q->gso_skb;
/*如果q->gso_skb不为空,表示上次发送时有发送失败的报文,
这时要先发该报文,把该报文摘下来后返回*/
if (unlikely(skb))
{
struct net_device *dev = qdisc_dev(q);
struct netdev_queue *txq;
/* check the reason of requeuing without tx lock first */
txq = netdev_get_tx_queue(dev,
skb_get_queue_mapping(skb));
if (!netif_tx_queue_stopped(txq) &&
!netif_tx_queue_frozen(txq))
{
q->gso_skb = NULL;
q->q.qlen--;
}
else
{
skb = NULL;
}
}
/*q->gso_skb上没有报文,就调用队列策略自己的出队函数
取一个skb并返回*/
else
{
skb = q->dequeue(q);
}
return skb;
}