发送报文过程的调度 (linux网络子系统学习 第十二节 )

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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章