Chinaunix首页 | 论坛 | 博客
  • 博客访问: 81801
  • 博文数量: 21
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 170
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-29 10:03
文章分类
文章存档

2010年(21)

我的朋友

分类: LINUX

2010-07-29 10:20:38

如题所示,此篇学习笔记记录的是数据报的发送过程,关于接收的相关过程,另一篇笔记有总结.以下总结了从网络协议到网卡数据报传输的具体过程,以下研究的代码来自于2.4.
无论是ip还是arp协议,当有数据要发送的时候都会调用dev_queue_xmit函数,也就是说,dev_queue_xmit是驱动程序对上面的接口.
 

/*
 *功能:发送一个skb
 *主要执行步骤:
 *1,检查skb是否为碎片,检查device是否能处理skb的碎片操作.
 *2,确定检验和已经被正确计算.
 *3,发送skb.此时有两种情况,一种情况是有Qos层,调用qdisc_run(执行过程中真正的调用函数是qdisc_restart),另一种情况是直接调用hard_queue_xmit(如虚设备)
 * 3.1:根据出队规则选择一个skb发送出去,注意此时的skb不一定就是传递进来的skb,因为要考虑到出队规则.(第一种情况)
 * 3.2:直接调用hard_queue_xmit将数据报发送出去.(如虚拟设备)
 *注意:此时发送一个skb所需要的东西都已经知道了,比如说,skb->dev为outgoing device,即选择传输的网络接口.
 */

int dev_queue_xmit(struct sk_buff *skb)
{
    struct net_device *dev = skb->dev;
    struct Qdisc *q;
    
    
/*首先检查skb_shinfo(skb)->frag_list是否有值,如果有,但是网络设备不支持skb的碎片列表,
     *则通过skb_linearize把这些碎片重组到一个完整的skb中.
     */

    if (skb_shinfo(skb)->frag_list &&
     !(dev->features&NETIF_F_FRAGLIST) &&
     skb_linearize(skb, GFP_ATOMIC) != 0) {
        kfree_skb(skb);
        return -ENOMEM;
    }

    if (skb_shinfo(skb)->nr_frags &&
     (!(dev->features&NETIF_F_SG) || illegal_highdma(dev, skb)) &&
     skb_linearize(skb, GFP_ATOMIC) != 0) {
        kfree_skb(skb);
        return -ENOMEM;
    }

    
/* 如果数据包未做检验和,并且设备对该协议不支持检验和计算,则在
     * 此处计算检验和.
     */

    if (skb->ip_summed == CHECKSUM_HW &&
     (!(dev->features&(NETIF_F_HW_CSUM|NETIF_F_NO_CSUM)) &&
     (!(dev->features&NETIF_F_IP_CSUM) ||
     skb->protocol != htons(ETH_P_IP)))) {
        if ((skb = skb_checksum_help(skb)) == NULL)
            return -ENOMEM;
    }

    /* Grab device queue */
    spin_lock_bh(&dev->queue_lock);
    /*net_device的成员qdisk是一个发送队列,缓冲等待网络设备进行发送的skb*/
    q = dev->qdisc;
    
//如果队列存在,入队,一般情况下为实设备,有自己的发送队列,如果是虚设备,则一般没有自己的发送队列.

    if (q->enqueue) {
        int ret = q->enqueue(skb, q);
        /*根据出队规则,挑选一个skb发送出去*/
        qdisc_run(dev);

        spin_unlock_bh(&dev->queue_lock);
        /*返回状态信息*/
        return ret == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : ret;
    }
    
    /*如执行到此步,则表明网络接口没有自己的发送队列,一般为虚设备的情况,如loopback,ip隧道等*/
    if (dev->flags&IFF_UP) {
        int cpu = smp_processor_id();

        if (dev->xmit_lock_owner != cpu) {
            spin_unlock(&dev->queue_lock);
            spin_lock(&dev->xmit_lock);
            dev->xmit_lock_owner = cpu;

            if (!netif_queue_stopped(dev)) {
                
//如果ptype_all中有成员,则先发给其中注册的处理函数

                if (netdev_nit)
                    dev_queue_xmit_nit(skb,dev);
                
                
//dev->hard_start_xmit对应实际设备驱动程序的发送函数

                if (dev->hard_start_xmit(skb, dev) == 0) {
                    dev->xmit_lock_owner = -1;
                    spin_unlock_bh(&dev->xmit_lock);
                    return 0;
                }
            }
            /*如果device的发送队列被禁止*/
            dev->xmit_lock_owner = -1;
            spin_unlock_bh(&dev->xmit_lock);
            if (net_ratelimit())
                printk(KERN_CRIT "Virtual device %s asks to queue packet!\n", dev->name);
            kfree_skb(skb);
            return -ENETDOWN;
        } else {
            /* Recursion is detected! It is possible, unfortunately */
            if (net_ratelimit())
                printk(KERN_CRIT "Dead loop on virtual device %s, fix it urgently!\n", dev->name);
        }
    }
    spin_unlock_bh(&dev->queue_lock);

    /*如果设备没有被打开*/
    kfree_skb(skb);
    return -ENETDOWN;
}

由上面的注释可以看到,dev_queue_xmit会遇到两种情况,一种情况是有traffic control层(QoS层),一种是直接调用设备驱动程序的发送函数hard_start_xmit(比如一般的虚拟设备都是如此)前种情况的路线是:dev_queue_xmit-->qdisc_run-->qdisc_restart(dev),后者的路线是:dev_queue_xmit-->hard_start_xmit.

qdisc的源代码为:

static inline void qdisc_run(struct net_device *dev)
{
    while (!netif_queue_stopped(dev) &&
     qdisc_restart(dev)<0)
        /* NOTHING */;
}

可以看出,qdisc_run只是qdisc_restart的包裹函数,此函数过滤了发送队列被禁止的dev,然后调用qdisc_restart.

/*函数作用:当device有自己的发送队列时的发送函数.
 *此函数涉及到的两种锁:
 * dev->queue_lock: 是device发送队列queue的锁,
 * dev->xmit_lock: 是driver 的发送程序hard_start_xmit的锁.
 *
 *函数的执行路线:qdisc_restart---->sniffer(如果有)---->dev_hard_xmit.
 *
 *返回值:
 * 0 : 发送队列为空时.
 * 1 : 队列非空,但由于某种原因没能发送数据,比如不能获得dev的发送队列的锁dev->xmit_lock.
 * -1: 发送成功时.
 */

int qdisc_restart(struct net_device *dev)
{
    /*dev->qdisc为dev的发送队列*/
    struct Qdisc *q = dev->qdisc;
    struct sk_buff *skb;

    /* 根据一定的规则,挑选一个skb发送出去,此处为体现有traffic control时的区别*/

    if ((skb = q->dequeue(q)) != NULL) {
        if (spin_trylock(&dev->xmit_lock)) {
            /* Remember that the driver is grabbed by us. */
            dev->xmit_lock_owner = smp_processor_id();

            /* And release queue */
            spin_unlock(&dev->queue_lock);

            if (!netif_queue_stopped(dev)) {
                /*如果有注册的sniffer,则发送个各个sniffer,当sniffer注册时,netdev_nit会相应的加1,所以netdev_nit代表了sniffer的数量*/
                if (netdev_nit)
                    dev_queue_xmit_nit(skb, dev);

                if (dev->hard_start_xmit(skb, dev) == 0) {
                    dev->xmit_lock_owner = -1;
                    spin_unlock(&dev->xmit_lock);

                    spin_lock(&dev->queue_lock);
                    return -1;
                }
            }

            /* Release the driver */
            dev->xmit_lock_owner = -1;
            spin_unlock(&dev->xmit_lock);
            spin_lock(&dev->queue_lock);
            q = dev->qdisc;
        }
         /*如果没得到device的发送函数的锁,则说明此device已经被别的cpu调用在发送数据*/
         else {
            /* So, someone grabbed the driver. */

            
/* It may be transient configuration error,
             when hard_start_xmit() recurses. We detect
             it by checking xmit owner and drop the
             packet when deadloop is detected.
             */

            /*如果device的发送函数锁为本cpu所有,但却还忙,则free掉sk_buff*/
            if (dev->xmit_lock_owner == smp_processor_id()) {
                kfree_skb(skb);
                if (net_ratelimit())
                    printk(KERN_DEBUG "Dead loop on netdevice %s, fix it urgently!\n", dev->name);
                return -1;
            }
            /*更新冲突状态信息*/
            netdev_rx_stat[smp_processor_id()].cpu_collision++;
        }

        
/* Device kicked us out :(
         This is possible in three cases:

         0. driver is locked
         1. fastroute is enabled
         2. device cannot determine busy state
         before start of transmission (f.e. dialout)
         3. device is buggy (ppp)
         */

        /*走到此,应该是没发送成功,则把skb重新放回到队列中,然后调用net0f_schedule(dev)再次试图将其通过dev发送出去*/
        q->ops->requeue(skb, q);
        netif_schedule(dev);
        return 1;
    }
    /*程序走到这时,说明队列为空,q.qlen应该是0*/
    return q->q.qlen;
}

可以看到,调用了qdisk_restart函数的一般都是device有自己的发送队列的情况,此时在出队列函数dequeue处体现了traffic control的作用.

由代码可知,在发送失败时,会将skb重新放入队列中,然后调用netif_schedule(dev)将其重新发送.下面来看看netif_schedule的源代码.

static inline void netif_schedule(struct net_device *dev)
{
    if (!test_bit(__LINK_STATE_XOFF, &dev->state))
        __netif_schedule(dev);
}

可以看到,netif_schedule是__netif_schedule(dev)的包裹函数.

/*
 *函数作用:把参数dev加入到softnet_data中的output_queue链表首,优先调度,然后触发发送软中断,调用net_tx_action.
 */

static inline void __netif_schedule(struct net_device *dev)
{
    if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state)) {
        unsigned long flags;
        int cpu = smp_processor_id();

        local_irq_save(flags);
        dev->next_sched = softnet_data[cpu].output_queue;
        softnet_data[cpu].output_queue = dev;
        cpu_raise_softirq(cpu, NET_TX_SOFTIRQ);
        local_irq_restore(flags);
    }
}

如注释所示,此函数功能很简单,把参数传进来的dev放到softnet_data[cpu].output_queue的链表首,优先等待调度,然后触发发送软中断,调用相应的发送处理函数,即net_tx_action.读到此,产生了两个问题,就是说,发送成功后,直接返回-1,由于某种原因不能发送skb时才会有这些操作,也就是说,只有在因为某种原因不能发送skb时,需要将数据报重新放回队列,然后把dev放在output_queue的链表首,此时才触发发送软中断,不是每次发送数据时都触发发送软中断?还有,我记得当数据报发送数据包成功后会把skb加入到completion_queue中,但是怎么没看到相关代码?这两个问题先放下,看看读完net_tx_action的代码后能不能找到答案.

/*
 *net_tx_action为发送软中断的处理函数.
 *函数作用:
 * 1 释放softnet_data[cpu].completion_queue中发送成功的sk_buff.
 * 2 调度softnet_data[cpu].output_queue中的device发送数据.
 */

static void net_tx_action(struct softirq_action *h)
{
    int cpu = smp_processor_id();

    if (softnet_data[cpu].completion_queue) {
        struct sk_buff *clist;
        
        
/*因为net_tx_action不是在中断的环境中运行的,所以驱动程序可以在任意时候不顾net_tx_action的执行而向completion_queue中添加数据,
        所以此处要禁止中断,因为禁止中断时间越短越好,所以先用本地变量clist指向softnet_data[cpu].completion_queue,然后将softnet_data[cpu].completion_queue
        设置为NULL,开中断,由于本地变量是不能被外界访问的,所以之后再慢慢的处理skb*/

        local_irq_disable();
        clist = softnet_data[cpu].completion_queue;
        softnet_data[cpu].completion_queue = NULL;
        local_irq_enable();

        while (clist != NULL) {
            struct sk_buff *skb = clist;
            clist = clist->next;

            BUG_TRAP(atomic_read(&skb->users) == 0);
            __kfree_skb(skb);
        }
    }

    if (softnet_data[cpu].output_queue) {
        struct net_device *head;
        
        /*禁止中断的原因同上*/
        local_irq_disable();
        head = softnet_data[cpu].output_queue;
        softnet_data[cpu].output_queue = NULL;
        local_irq_enable();

        while (head != NULL) {
            struct net_device *dev = head;
            head = head->next_sched;

            smp_mb__before_clear_bit();
            /*调用output_queue中的device进行发送数据*/
            clear_bit(__LINK_STATE_SCHED, &dev->state);
            
            /*在调用一个device发送数据时,应先获得此设备的发送队列的锁*/
            if (spin_trylock(&dev->queue_lock)) {
                qdisc_run(dev);
                spin_unlock(&dev->queue_lock);
            }
             /*如果得不到此设备的发送队列的锁,此时可能有另外的cpu在用到此设备发送数据,则重新调度此设备发送数据*/
             else {
                netif_schedule(dev);
            }
        }
    }
}

此函数完成两件事,如上面代码注释可知: 1,释放softnet_data[cpu].completion_queue中发送成功的sk_buff. 2,调度softnet_data[cpu].output_queue中的device发送其发送队列中的数据.突然想起在以前读ldd2的时候看见过如下一句话:网络接口在两种可能的事件下中断处理器:新数据包的到达,或者外发数据包的传输已经完成.好像想起了点什么,觉得应该是这样:当数据报成功传送完之后,把其加入到completion_queue中的代码应该是在网卡驱动中具体实现的,传输成功后,会调用net_tx_action来处理completion_queue中发送成功的数据报,然后在调度output_queue中的device传送其发送队列中的数据包.读到这时,最少两种情况会调用net_tx_action,第一种情况是在由于某种原因不能发送skb时(比如不能获得dev的发送队列的锁dev->xmit_lock),或者在网卡驱动程序成功发送完数据包后,会产生中断,然后执行发送软中断处理函数.这样理解可以理解通,不知道实事上是不是这样,如果我想错了,知道正确答案的朋友,希望能给予及时指正,不胜感激.

这里要知道,跟接收数据包时的原理一样,一般不会将所有事情都在中断环境中解决,所以当一个网卡发送完数据包后,并不是直接的在中断环境下释放skb资源,而是将其放入completion_queue中,这块也只是指针的指向,而不是数据包的拷贝,然后调用发送软中断来处理数据包.当驱动程序传输完数据包后,会调用dev_kfree_skb_irq来释放sk_buff.

static inline void dev_kfree_skb_irq(struct sk_buff *skb)
{
    if (atomic_dec_and_test(&skb->users)) {
        struct softnet_data *sd;
        unsigned long flags;
 
        local_irq_save(flags);
           sd = &_ _get_cpu_var(softnet_data);
        skb->next = sd->completion_queue;
        sd->completion_queue = skb;
        raise_softirq_irqoff(NET_TX_SOFTIRQ);
        local_irq_restore(flags);
    }
}

此处可以看出来,此函数只是把skb放入completion_queue的链表头,真正的释放工作在发送软中断net_tx_action中完成.

ok,分析到这,基本上算是通了.下面是自己在研究以上代码时随手记的一些零散知识点,有看到这篇笔记的朋友们可以直接pass了,自己不舍得扔掉,于是贴在此.

数据包的发送过程和接收过程大体上类似:

1,NET_TX_SOFTIRQ:发送软中断 <--------> NET_RX_SOFTIRQ:接收软中断 ; net_tx_action:发送软中断处理函数 <--------> net_rx_action:接收软中断处理函数 ; dev_queue_xmit <--------> netif_rx ; output_queue:NAPI和non-NAPI都用<-------->poll_list:只有non_NAPI用.

2,当一个device被调用以用来接收数据时,其device的__LINK_STATE_RX_SCHED被标记.当一个device被调用以用来发送数据时,其device的__LINK_STATR_SCHED被标记.

3,当一个device正在被调用时,为什么要使能或禁止队列的传输功能呢?

一个原因是,一个device可能暂时用光其存储空间,所以会导致传输失败.

解决办法:当驱动程序获知devicedevice没有足够的space来存储一个数据包时,调用netif_stop_queue禁止传输.相反,当驱动程序获知device有足够的space来存储一个skb时,则调用netif_start_queue使能传输功能.

4,netif_wake_queue = netif_start_queue + netif_schedule

5,当传输一个数据时.当以下条件发生时,需要把数据包重新入队.

1)发送队列被禁止(netif_queue_stopped(dev) is true)

2)另一个cpu正持有此device driver的lock.

3)传输失败.

6,dev_kfree_skb:释放skb  ;  dev_kfree_skb_irq:仅仅把skb放入到softnet_data的completion_queue中,然后调用发送软中断,让net_tx_action完成真正的释放工作.

由于本人刚开始研究网络的底层实现不长时间,所以难免有理解错误的地方,如果本篇笔记存在错误,希望看到的达人们给予指正,不胜感激.

阅读(1495) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~