Keep looking Donot settle
分类: 嵌入式
2014-10-28 15:29:48
我们知道对应每一个设备都有一个或几个发送的队列,它们是在驱动加载的时候就初始化的。
RTL8139的发送队列只有一个,在alloc_netdev_mq中初始化:
tx = kcalloc(queue_count, sizeof(struct netdev_queue), GFP_KERNEL);
if (!tx) {
printk(KERN_ERR "alloc_netdev: Unable to allocate "
"tx qdiscs./n");
kfree(p);
return NULL;
}
。。。。。。。。。。。
dev->_tx = tx; /*结合分析Ethernet wathchdog时的netif_stop_queue*/
………………………………………………………
而对应于队列有2个状态位:
enum netdev_queue_state_t
{
__QUEUE_STATE_XOFF, 这是个和流控相关的状态,netif_stop_queue,netif_start_queue,netif_wake_queue都是和它有关
__QUEUE_STATE_FROZEN,
};
而对应于每个队列都有个Qdisc结构,它是真正描述队列的数据结构,像排队发送的数据包都存放这里,数据包的出队,入队等策略都是它描述的。
这个是在调用注册函数register_netdevice的时候初始化的dev_init_scheduler 》netdev_for_each_tx_queue。
同时Qdisc也有几个状态位:
enum qdisc_state_t
{
__QDISC_STATE_RUNNING, 每次调用qdisc_run的时候就置位,调用完成后就清位
__QDISC_STATE_SCHED, 表面所在的队列加到了CPU的softnet_data的output_queue中,以在软中断中得以处理
__QDISC_STATE_DEACTIVATED,
};
发送软中断触发的条件是:
1 __netif_reschedule或者其封装函数netif_wake_queue等;
2 释放已发送的skb
当然后者比较容易看懂,前者则比较复杂
在前面的发送流程中,就可以看到几种情况:
1 qdisc_restart中发送失败,数据包重新入队,且调用__netif_reschedule来等待下次软中断的处理
2 发送没有完成(没有全部发出),但是CPU时间已到或者时间限制,调用__netif_reschedule来等待下次软中断的处理
__netif_reschedule调用的时候会把当前队列加入到CPU的softnet_data的output_queue中,以在软中断中得以处理;
注意的是:只有当前队列的__QDISC_STATE_SCHED没有置位的时候,才能加入
因为__QDISC_STATE_SCHED置位的话,则表明已经在output_queue中了。
下面看看net_tx_action软中断处理函数
static void net_tx_action(struct softirq_action *h)
{
struct softnet_data *sd = &__get_cpu_var(softnet_data);
// 完成对skb的回收
if (sd->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);
}
}
// 发送数据包
if (sd->output_queue) {
struct Qdisc *head;
local_irq_disable();
// 取出CPU发送队列的所有网卡设备的发送队列,并置其为空
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;
root_lock = qdisc_lock(q);
if (spin_trylock(root_lock)) {
smp_mb__before_clear_bit();
// 因为该队列已经从CPU的output_queue中取出,所以清除__QDISC_STATE_SCHED,以期下次可以再被调度
clear_bit(__QDISC_STATE_SCHED,
&q->state);
// 处理该队列,在该函数中,如果没有处理完或者发送出现问题,该队列调用__netif_reschedule再次加入output_queue
qdisc_run(q);
spin_unlock(root_lock);
} else {
// 如果获取锁失败,并且队列没有被__QDISC_STATE_DEACTIVATED,就放回output_queue
if (!test_bit(__QDISC_STATE_DEACTIVATED,
&q->state)) {
__netif_reschedule(q);
} else {
// __QDISC_STATE_DEACTIVATED置位了,那么清除__QDISC_STATE_SCHED,以期下次__QDISC_STATE_DEACTIVATED
// 清位后,可以调用__netif_reschedule再次加入output_queue。
smp_mb__before_clear_bit();
clear_bit(__QDISC_STATE_SCHED,&q->state);
}
}
}
}
}
剩下一种情况就是在网卡驱动中对队列的操作:停止发送队列,以及唤醒发送队列。
当网卡驱动没有剩余的空间可以接纳数据时,就调用netif_stop_queue来阻止数据包的发送,(在qdisc_run的调用中碰到这种情况时,不会把该网卡的队列调用__netif_reschedule加入output_queue),加入output_queue是在netif_wake_queue完成的,该函数在发送数据包完成后产生的中断函数中被调用,如果说在调用的时候队列是stopped,那么就调用__netif_schedule把该队列加入到output_queue中(我觉得这个时候__QDISC_STATE_SCHED肯定是处于复位的状态,所以总能加入output_queue)。
from: http://blog.csdn.net/chensichensi/article/details/3974254