网络协议到网卡数据报传输的具体过程 skb hard_queue_xmit device

从网络协议到网卡数据报传输的具体过程,以下研究的代码来基于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 *= 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, fixit 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:NAPInon-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 driverlock.

3)传输失败.

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

 

============================================================================================

二层(链路层)数据包发送过程分析

涉及内核版本为2.6.32
当上层准备好一个包之后,交给链路层,链路层数据包发送主要通过dev_queue_xmit函数处理。数据包的发送可分为两种,一种是正常的传输流程,即通过网卡驱动,另一种是通过软中断(见注3)。为了理解方便,首先看一下dev_queue_xmi函数的整体调用关系图。

//000

  • dev_queue_xmit

本函数用来将带发送的skb加入一个dev的队列(Queue),调用这个函数前必须设置好skb的device和priority,本函数可以在中断上下文中被调用。

返回值:

返回非0(正数或负数)表示函数出错,返回0表示成功,但是并不表示数据包被成功发送出去,因为数据包可能因为限速等原因被丢掉。

函数执行后传入的skb将被释放,所以如果想控制数据包,实现对skb的重传时需要增加skb的引用计数。

当调用此函数时中断必须是打开的,因为BH enable必须要求IRQ enable,否则会造成死锁。

C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

int dev_queue_xmit(struct sk_buff *skb)

{

    struct net_device *dev = skb->dev;

    struct netdev_queue *txq;

    struct Qdisc *q;

    int rc = -ENOMEM;

    /* GSO will handle the following emulations directly. */

    if (netif_needs_gso(dev, skb))

        goto gso;

    if (skb_has_frags(skb) &&

        !(dev->features & NETIF_F_FRAGLIST) &&

        __skb_linearize(skb))

        goto out_kfree_skb;

//如果skb有分片但是发送设备不支持分片,或分片中有分片在高端内存但发送设备不支持DMA,需要将所有段重新组合成一个段 ,这里__skb_linearize其实就是__pskb_pull_tail(skb, skb->data_len),这个函数基本上等同于pskb_may_pull ,pskb_may_pull的作用就是检测skb对应的主buf中是否有足够的空间来pull出len长度,如果不够就重新分配skb并将frags中的数据拷贝入新分配的主buff中,而这里将参数len设置为skb->datalen, 也就是会将所有的数据全部拷贝到主buff中,以这种方式完成skb的线性化。

    if (skb_shinfo(skb)->nr_frags &&

        (!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) &&

        __skb_linearize(skb))

        goto out_kfree_skb;

//如果数据包没有被计算校验和并且发送设备不支持这个协议的校验,则在此进行校验和的计算(注1)。如果上面已经线性化了一次,这里的__skb_linearize就会直接返回,注意区别frags和frag_list,前者是将多的数据放到单独分配的页面中,sk_buff只有一个。而后者则是连接多个sk_buff

    if (skb->ip_summed == CHECKSUM_PARTIAL) {

        skb_set_transport_header(skb, skb->csum_start -

                          skb_headroom(skb));

        if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb))

            goto out_kfree_skb;

    }

gso:

//关闭软中断,禁止cpu抢占

    rcu_read_lock_bh();

//选择一个发送队列,如果设备提供了select_queue回调函数就使用它,否则由内核选择一个队列,这里只是Linux内核多队列的实现,但是要真正的使用都队列,需要网卡支持多队列才可以,一般的网卡都只有一个队列。在调用alloc_etherdev分配net_device是,设置队列的个数

txq = dev_pick_tx(dev, skb);

// 从netdev_queue结构上获取设备的qdisc

    q = rcu_dereference(txq->qdisc);

//如果该设备有队列可用,就调用__dev_xmit_skb

    if (q->enqueue) {

        rc = __dev_xmit_skb(skb, q, dev, txq);

        goto out;

    }

//下面的处理是在没有发送队列的情况,软设备一般没有发送队列:如lo、tunnle;我们所要做的就是直接调用驱动的hard_start_xmit将它发送出去 如果发送失败就直接丢弃,因为没有队列可以保存它

    if (dev->flags & IFF_UP) { //确定设备是否开启

        int cpu = smp_processor_id(); /* ok because BHs are off */

        if (txq->xmit_lock_owner != cpu) {//是否在同一个cpu上

            HARD_TX_LOCK(dev, txq, cpu);

            if (!netif_tx_queue_stopped(txq)) {//确定队列是运行状态

                rc = NET_XMIT_SUCCESS;

                if (!dev_hard_start_xmit(skb, dev, txq)) {

                    HARD_TX_UNLOCK(dev, txq);

                    goto out;

                }

            }

            HARD_TX_UNLOCK(dev, txq);

            if (net_ratelimit())

                printk(KERN_CRIT "Virtual device %s asks to "

                       "queue packet!\n", dev->name);

        } else {// txq->xmit_lock_owner == cpu的情况,说明发生递归

            if (net_ratelimit())

                printk(KERN_CRIT "Dead loop on virtual device "

                       "%s, fix it urgently!\n", dev->name);

        }

    }

    rc = -ENETDOWN;

    rcu_read_unlock_bh();

out_kfree_skb:

    kfree_skb(skb);

    return rc;

out:

    rcu_read_unlock_bh();

    return rc;

}

 

  • __dev_xmit_skb

__dev_xmit_skb函数主要做两件事情:

(1)如果流控对象为空的,试图直接发送数据包。

(2)如果流控对象不空,将数据包加入流控对象,并运行流控对象。

C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

staticinlineint__dev_xmit_skb(structsk_buff*skb,structQdisc*q,

                structnet_device*dev,

                structnetdev_queue*txq)

{

    spinlock_t*root_lock=qdisc_lock(q);//见注2

    intrc;

    spin_lock(root_lock);//锁qdisc

    if(unlikely(test_bit(__QDISC_STATE_DEACTIVATED,&q->state))){//判断队列是否失效

        kfree_skb(skb);

        rc=NET_XMIT_DROP;

    }elseif((q->flags&TCQ_F_CAN_BYPASS)&&!qdisc_qlen(q)&&

          !test_and_set_bit(__QDISC_STATE_RUNNING,&q->state)){

        /*

         * This is a work-conserving queue; there are no old skbs

         * waiting to be sent out; and the qdisc is not running -

         * xmit the skb directly.

         */

        __qdisc_update_bstats(q,skb->len);

        if(sch_direct_xmit(skb,q,dev,txq,root_lock))

            __qdisc_run(q);

        else

            clear_bit(__QDISC_STATE_RUNNING,&q->state);

        rc=NET_XMIT_SUCCESS;

    }else{

        rc=qdisc_enqueue_root(skb,q);

        qdisc_run(q);

    }

    spin_unlock(root_lock);

    returnrc;

}

lqdisc_run

有两个时机将会调用qdisc_run():

1.__dev_xmit_skb()

2.软中断服务线程NET_TX_SOFTIRQ

点击(此处)折叠或打开

staticinlinevoidqdisc_run(structQdisc*q)

{

    if(!test_and_set_bit(__QDISC_STATE_RUNNING,&q->state))//将队列设置为运行状态

        __qdisc_run(q);

}

l__qdisc_run

点击(此处)折叠或打开

void__qdisc_run(structQdisc*q)

{

    unsignedlongstart_time=jiffies;

    while(qdisc_restart(q)){//返回值大于0,说明流控对象非空

        /*如果发现本队列运行的时间太长了,将会停止队列的运行,并将队列加入output_queue链表头

         * Postpone processing if (延迟处理)

         * 1. another process needs the CPU;

         * 2. we've been doing it for too long.

         */

        if(need_resched()||jiffies!=start_time){//已经不允许继续运行本流控对象

            __netif_schedule(q);//将本qdisc加入每cpu变量softnet_data的output_queue链表中

            break;

        }

    }

  //清除队列的运行标识

    clear_bit(__QDISC_STATE_RUNNING,&q->state);

}

循环调用qdisc_restart发送数据,下面这个函数qdisc_restart是真正发送数据包的函数,它从队列上取下一个帧,然后尝试将它发送出去,若发送失败则一般是重新入队。

此函数返回值为:发送成功时返回剩余队列长度,发送失败时返回0(若发送成功且剩余队列长度为0也返回0)

  • qdisc_restart

__QDISC_STATE_RUNNING状态保证同一时刻只有一个cpu在处理这个qdisc,qdisc_lock(q)用来保证对这个队列的顺序访问。

通常netif_tx_lock用来确保本设备驱动的顺序(独占)访问的,qdisc_lock(q)用来保证qdisc的顺序访问,这两个是互斥的,获得其中一个必须释放另一个。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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); //一开始就调用dequeue函数

    if (unlikely(!skb))

        return 0; //返回0说明队列是空的或者被限制

    root_lock = qdisc_lock(q);

    dev = qdisc_dev(q);

    txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));

    return sch_direct_xmit(skb, q, dev, txq, root_lock); //用于发送数据包

}

 

  • sch_direct_xmit

发送一个skb,将队列置为__QDISC_STATE_RUNNING状态,保证只有一个cpu运行这个函数,返回0表示队列为空或者发送受限,大于0表示队列非空。

C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

intsch_direct_xmit(structsk_buff*skb,structQdisc*q,

            structnet_device*dev,structnetdev_queue*txq,

            spinlock_t*root_lock)

{

    intret=NETDEV_TX_BUSY;

    spin_unlock(root_lock);// release qdisc,因为后面要获取设备锁

  // 调用__netif_tx_lockà spin_lock(&txq->_xmit_lock,,保证设备驱动的独占访问

    HARD_TX_LOCK(dev,txq,smp_processor_id());

    if(!netif_tx_queue_stopped(txq)&&//设备没有被停止,且发送队列没有被冻结

        !netif_tx_queue_frozen(txq))

        ret=dev_hard_start_xmit(skb,dev,txq);//发送数据包

    HARD_TX_UNLOCK(dev,txq);// 调用__netif_tx_unlock

    spin_lock(root_lock);

    switch(ret){

    caseNETDEV_TX_OK://如果设备成功将数据包发送出去

        ret=qdisc_qlen(q);//返回剩余的队列长度

        break;

    caseNETDEV_TX_LOCKED://获取设备锁失败

        ret=handle_dev_cpu_collision(skb,txq,q);

        break;

    default://设备繁忙,重新入队发送(利用softirq)

        if(unlikely(ret!=NETDEV_TX_BUSY&&net_ratelimit()))

            printk(KERN_WARNING"BUG %s code %d qlen %d\n",

                  dev->name,ret,q->q.qlen);

        ret=dev_requeue_skb(skb,q);

        break;

    }

    if(ret&&(netif_tx_queue_stopped(txq)||

            netif_tx_queue_frozen(txq)))

        ret=0;

    returnret;

}

 

  • dev_hard_start_xmit

 

C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,

            struct netdev_queue *txq)

{

    const struct net_device_ops *ops = dev->netdev_ops;

    int rc;

if (likely(!skb->next)) {

//从这里可以看出,对于每一个发送的包也会发给ptype_all一份, 而packet套接字创建时对于proto为ETH_P_ALL的会在ptype_all中注册一个成员,因此对于协议号为ETH_P_ALL的packet套接字来说,发送和接受的数据都能收到

        if (!list_empty(&ptype_all))

            dev_queue_xmit_nit(skb, dev);

        if (netif_needs_gso(dev, skb)) {

            if (unlikely(dev_gso_segment(skb)))

                goto out_kfree_skb;

            if (skb->next)

                goto gso;

        }

       //如果发送设备不需要skb->dst,则在此将其释放

        if (dev->priv_flags & IFF_XMIT_DST_RELEASE)

            skb_dst_drop(skb);

       //调用设备注册的发送函数,即dev->netdev_ops-> ndo_start_xmit(skb, dev)

        rc = ops->ndo_start_xmit(skb, dev);

        if (rc == NETDEV_TX_OK)

txq_trans_update(txq);

        return rc;

    }

gso:

……

}

 

  • dev_queue_xmit_nit

 

C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

staticvoiddev_queue_xmit_nit(structsk_buff*skb,structnet_device*dev)

{

    structpacket_type*ptype;

#ifdef CONFIG_NET_CLS_ACT

    if(!(skb->tstamp.tv64&&(G_TC_FROM(skb->tc_verd)&AT_INGRESS)))

        net_timestamp(skb);//记录该数据包输入的时间戳

#else

    net_timestamp(skb);

#endif

    rcu_read_lock();

    list_for_each_entry_rcu(ptype,&ptype_all,list){

        /* Never send packets back to the socket they originated from */

      //遍历ptype_all链表,查找所有符合输入条件的原始套接口,并循环将数据包输入到满足条件的套接口

        if((ptype->dev==dev||!ptype->dev)&&

            (ptype->af_packet_priv==NULL||

(structsock*)ptype->af_packet_priv!=skb->sk)){

      //由于该数据包是额外输入到这个原始套接口的,因此需要克隆一个数据包

            structsk_buff*skb2=skb_clone(skb,GFP_ATOMIC);

            if(!skb2)

                break;

            /* skb->nh should be correctly(确保头部偏移正确)

               set by sender, so that the second statement is

               just protection against buggy protocols.

             */

            skb_reset_mac_header(skb2);

            if(skb_network_header(skb2)<skb2->data||

                skb2->network_header>skb2->tail){

                if(net_ratelimit())//net_ratelimit用来保证网络代码中printk的频率

                    printk(KERN_CRIT"protocol %04x is "

                          "buggy, dev %s\n",

                          skb2->protocol,dev->name);

                skb_reset_network_header(skb2);//重新设置L3头部偏移

            }

            skb2->transport_header=skb2->network_header;

            skb2->pkt_type=PACKET_OUTGOING;

            ptype->func(skb2,skb->dev,ptype,skb->dev);//调用协议(ptype_all)接受函数

        }

    }

    rcu_read_unlock();

}

 

  • 环回设备

对于环回设备loopback,设备的ops->ndo_start_xmit被初始化为loopback_xmit函数。

 

1

2

3

4

5

static const struct net_device_ops loopback_ops = {

    .ndo_init = loopback_dev_init,

    .ndo_start_xmit= loopback_xmit,

    .ndo_get_stats = loopback_get_stats,

};

 

  • drivers/net/loopback.c

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

staticnetdev_tx_tloopback_xmit(structsk_buff *skb,

                structnet_device *dev)

{

    structpcpu_lstats *pcpu_lstats,*lb_stats;

    intlen;

    skb_orphan(skb);

    skb->protocol=eth_type_trans(skb,dev);

    /* it's OK to use per_cpu_ptr() because BHs are off */

    pcpu_lstats=dev->ml_priv;

    lb_stats=per_cpu_ptr(pcpu_lstats,smp_processor_id());

    len=skb->len;

    if(likely(netif_rx(skb)==NET_RX_SUCCESS)){//直接调用了netif_rx进行了接收处理

        lb_stats->bytes+=len;

        lb_stats->packets++;

    }else

        lb_stats->drops++;

    returnNETDEV_TX_OK;

}

 

  • 注:

1. CHECKSUM_PARTIAL表示使用硬件checksum ,L4层的伪头的校验已经完毕,并且已经加入uh->check字段中,此时只需要设备计算整个头4层头的校验值。

2. 整个数据包发送逻辑中会涉及到三个用于互斥访问的代码:

(1)spinlock_t *root_lock = qdisc_lock(q);

(2)test_and_set_bit(__QDISC_STATE_RUNNING, &q->state)

(3)__netif_tx_lockà spin_lock(&txq->_xmit_lock)

其中(1)(3)分别对应一个spinlock,(2)对应一个队列状态。在了解代码中如何使用这三个同步方法时,首先看一下相关数据结构的关系,如下。

 

图中绿色部分表示(1)(3)两处spinlock。首先看(1)处对应的代码:

 

1

2

3

4

staticinlinespinlock_t *qdisc_lock(structQdisc *qdisc)

{

    return&qdisc->q.lock;

}

所以root_lock是用于控制qdisc中skb队列访问的锁,当需要对skb队列进行enqueue、dequeue、requeue时,就需要加锁。

__QDISC_STATE_RUNNING标志用于保证一个流控对象(qdisc)不会同时被多个cpu访问。

而(3)处的spinlock,即struct netdev_queue中的_xmit_lock,则用于保证dev的注册函数的互斥访问,即deriver的同步。

另外,内核代码注释中写到,(1)和(3)是互斥的,获得(1)处的锁时必须先保证释放(3)处的锁,反之亦然,为什么要这样还没有想明白。。。。哪位大神知道还望指点

3. 已经有了dev_queue_xmit函数,为什么还需要软中断来发送呢?

我们可以看到在dev_queue_xmit中将skb进行了一些处理(比如合并成一个包,计算校验和等),处理完的skb是可以直接发送的了,这时dev_queue_xmit也会先将skb入队(skb一般都是在这个函数中入队的),并且调用qdisc_run尝试发送,但是有可能发送失败,这时就将skb重新入队,调度软中断,并且自己直接返回。

软中断只是发送队列中的skb以及释放已经发送的skb,它无需再对skb进行线性化或者校验和处理。另外在队列被停止的情况下,dev_queue_xmit仍然可以把包加入队列,但是不能发送,这样在队列被唤醒的时候就需要通过软中断来发送停止期间积压的包。简而言之,dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去。(其实发送软中断还有一个作用,就是释放已经发送的包,因为某些情况下发送是在硬件中断中完成的,为了提高硬件中断处理效率,内核提供一种方式将释放skb放到软中断中进行,这时只要调用dev_kfree_skb_irq,它将skb加入softnet_data的completion_queue中,然后开启发送软中断,net_tx_action会在软中断中将completion_queue中的skb全部释放掉)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值