专栏首页原创分享应用层发送一个数据包的时候,是如何到达网卡的(下)

应用层发送一个数据包的时候,是如何到达网卡的(下)

从前面的一篇文章应用层发送一个数据包的时候,是如何到达网卡的(上)可以知道,应用层发送一个数据包的时候首先经过tcp_write和ip_queue_xmit函数,然后调用mac层的dev_queue_xmit函数。该函数代码如下,主要功能是完成arp解析(如果还没解析的话)、把数据包复制一份和对所有数据包都感兴趣的协议、把数据包插入发送队列,然后发送发送队列中的数据包。如果发送失败则加到发送队列里等待重发。

// dev为路由项对应的设备,在数据包路由的时候赋值
void dev_queue_xmit(struct sk_buff *skb, struct device *dev, int pri)
{
    unsigned long flags;
    int nitcount;
    struct packet_type *ptype;
    int where = 0;      /* used to say if the packet should go  */
                /* at the front or the back of the  */
                /* queue - front is a retransmit try    */

    if (dev == NULL) 
    {
        printk("dev.c: dev_queue_xmit: dev = NULL\n");
        return;
    }

    if(pri>=0 && !skb_device_locked(skb))
        skb_device_lock(skb);   /* Shove a lock on the frame */
#ifdef CONFIG_SLAVE_BALANCING
    save_flags(flags);
    cli();
    if(dev->slave!=NULL && dev->slave->pkt_queue < dev->pkt_queue &&
                (dev->slave->flags & IFF_UP))
        dev=dev->slave;
    restore_flags(flags);
#endif        
#ifdef CONFIG_SKB_CHECK 
    IS_SKB(skb);
#endif    
    skb->dev = dev;

    /*
     *  This just eliminates some race conditions, but not all... 
     */

    if (skb->next != NULL) 
    {
        /*
         *  Make sure we haven't missed an interrupt. 
         */
        printk("dev_queue_xmit: worked around a missed interrupt\n");
        start_bh_atomic();
        dev->hard_start_xmit(NULL, dev);
        end_bh_atomic();
        return;
      }

    /*
     *  Negative priority is used to flag a frame that is being pulled from the
     *  queue front as a retransmit attempt. It therefore goes back on the queue
     *  start on a failure.
     */
    // 类似坐标轴,-1和1是对称的,如果传的是负数,则先取他的对边,因为数组是从0开始,需要减一,比如传2即数组下标是1
      if (pri < 0) 
      {
        pri = -pri-1;
        // 是直接发送还是先缓存到发送队列,1说明直接发送,0说明当前的skb先入队尾,先发送队列的队头节点
        where = 1;
      }

    if (pri >= DEV_NUMBUFFS) 
    {
        printk("bad priority in dev_queue_xmit.\n");
        pri = 1;
    }

    /*
     *  If the address has not been resolved. Call the device header rebuilder.
     *  This can cover all protocols and technically not just ARP either.
     */
    /*
        还没有完成arp解析,重新构建mac头,如果当前arp解析还没成功则直接返回,
        等待解析完成重新执行该函数(arp_rcv->arp_send_q).
    */
    if (!skb->arp && dev->rebuild_header(skb->data, dev, skb->raddr, skb)) {
        return;
    }

    save_flags(flags);
    cli();  
    /*
        1 where一般是0,即pri是正整数,这时候skb会先插入队尾,先发送队头的节点,
        并且把数据包复制一份给对数据包感兴趣的协议,然后发送。
        2 where等于1,即pri是负数代表这个skb是发送失败后重发的,这时候这个数据包时直接发送出去的,不再走1的那些流程
    */
    if (!where) {
#ifdef CONFIG_SLAVE_BALANCING    
        skb->in_dev_queue=1;
#endif        
        // 插入队尾,取出队头节点发送
        skb_queue_tail(dev->buffs + pri,skb);
        skb_device_unlock(skb);     /* Buffer is on the device queue and can be freed safely */
        skb = skb_dequeue(dev->buffs + pri);
        skb_device_lock(skb);       /* New buffer needs locking down */
#ifdef CONFIG_SLAVE_BALANCING        
        skb->in_dev_queue=0;
#endif        
    }
    restore_flags(flags);

    /* copy outgoing packets to any sniffer packet handlers */
    if(!where)
    {   
        // 把所有发出去的数据包传一份给其他协议
        for (nitcount= dev_nit, ptype = ptype_base; nitcount > 0 && ptype != NULL; ptype = ptype->next) 
        {
            /* Never send packets back to the socket
             * they originated from - MvS (miquels@drinkel.ow.org)
             */
            // 对所有包都感兴趣的、不是packet协议产生的packet_type节点
            if (ptype->type == htons(ETH_P_ALL) &&
               (ptype->dev == dev || !ptype->dev) &&
               ((struct sock *)ptype->data != skb->sk))
            {
                struct sk_buff *skb2;
                if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL)
                    break;
                /*
                 *  The protocol knows this has (for other paths) been taken off
                 *  and adds it back.
                 */
                skb2->len-=skb->dev->hard_header_len;
                ptype->func(skb2, skb->dev, ptype);
                nitcount--;
            }
        }
    }
    start_bh_atomic();
    // 调用驱动层发送
    if (dev->hard_start_xmit(skb, dev) == 0) {
        end_bh_atomic();
        /*
         *  Packet is now solely the responsibility of the driver
         */
        return;
    }
    end_bh_atomic();

    /*
     *  Transmission failed, put skb back into a list. Once on the list it's safe and
     *  no longer device locked (it can be freed safely from the device queue)
     */
    cli();
#ifdef CONFIG_SLAVE_BALANCING
    skb->in_dev_queue=1;
    dev->pkt_queue++;
#endif        
    skb_device_unlock(skb);
    // 发送失败则把数据包重新加入队列
    skb_queue_head(dev->buffs + pri,skb);
    restore_flags(flags);
}

由以上代码可知mac层最后调用驱动层的函数去发送数据包,这里以3c501网卡为例。下面是发送函数的代码。

static int
el_start_xmit(struct sk_buff *skb, struct device *dev)
{
    struct net_local *lp = (struct net_local *)dev->priv;
    int ioaddr = dev->base_addr;
    unsigned long flags;

    if (dev->tbusy) {
    if (jiffies - dev->trans_start < 20) {
        if (el_debug > 2)
        printk(" transmitter busy, deferred.\n");
        return 1;
    }
    if (el_debug)
        printk ("%s: transmit timed out, txsr %#2x axsr=%02x rxsr=%02x.\n",
            dev->name, inb(TX_STATUS), inb(AX_STATUS), inb(RX_STATUS));
    lp->stats.tx_errors++;
    outb(TX_NORM, TX_CMD);
    outb(RX_NORM, RX_CMD);
    outb(AX_OFF, AX_CMD);   /* Just trigger a false interrupt. */
    outb(AX_RX, AX_CMD);    /* Aux control, irq and receive enabled */
    dev->tbusy = 0;
    dev->trans_start = jiffies;
    }

    if (skb == NULL) {
    dev_tint(dev);
    return 0;
    }

    save_flags(flags);
    /* Avoid incoming interrupts between us flipping tbusy and flipping
       mode as the driver assumes tbusy is a faithful indicator of card
       state */
    cli();
    /* Avoid timer-based retransmission conflicts. */
    if (set_bit(0, (void*)&dev->tbusy) != 0)
    {
        restore_flags(flags);
    printk("%s: Transmitter access conflict.\n", dev->name);
    }
    else {
    int gp_start = 0x800 - (ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN);
    unsigned char *buf = skb->data;

    lp->tx_pkt_start = gp_start;
        lp->collisions = 0;

    /*
     *  Command mode with status cleared should [in theory]
     *  mean no more interrupts can be pending on the card.
     */
    outb(AX_SYS, AX_CMD);
    inb(RX_STATUS);
    inb(TX_STATUS);
    /* 
     *  Turn interrupts back on while we spend a pleasant afternoon
     *  loading bytes into the board 
     */
    restore_flags(flags);
    outw(0x00, RX_BUF_CLR);     /* Set rx packet area to 0. */
    outw(gp_start, GP_LOW);     /* aim - packet will be loaded into buffer start */
    // 传输数据到网卡
    outsb(DATAPORT,buf,skb->len);   /* load buffer (usual thing each byte increments the pointer) */
    outw(gp_start, GP_LOW);     /* the board reuses the same register */
    outb(AX_XMIT, AX_CMD);      /* fire ... Trigger xmit.  */
    dev->trans_start = jiffies;
    }

    if (el_debug > 2)
    printk(" queued xmit.\n");
    dev_kfree_skb (skb, FREE_WRITE);
    return 0;
}

通过对整个过程的分析我们知道,一个数据包从应用层到网卡的过程中,数据包在tcp层处理完后下发到ip层,ip层会缓存一份数据到缓存队列,以备重传,但其实这里tcp层的工作。再到mac层,数据包也不一定是直接发送出去的,他可能会先缓存在发送队列里,按序发送。如果发送失败,则放回发送队列,等待重发。

本文分享自微信公众号 - 编程杂技(theanarkh)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 网卡收到一个数据包的时候,是如何传给应用层的(上)

    这里以3c501网卡为例,每个设备对应一个device的结构体,下面代码即对3c501网卡的数据结构进行初始化,包括发送函数,注册中断回调,mac头长度等。

    theanarkh
  • 虚拟文件系统源码解析之初始化(基于linux1.2.13)

    从main函数开始,直到虚拟文件系统的初始化,路径是init()->setup()->syssetup();sys_setup主要是注册了虚拟文件系统下面所有的...

    theanarkh
  • js引擎v8源码解析之zone(基于0.1.5)

    theanarkh
  • 晚期(运行期)优化

      即时编译器(Just In Time Compiler)为了提高执行效率将热点代码编译成与本地平台相关的机器码,并进行各种层次的优化的编译器。它并不是虚拟机...

    YGingko
  • 从Timer中学习优先队列的实现

    Timer是Java定时器的实现,用来调度定时执行的任务和执行一次的任务,就像JavaScript的setInterval和setTimeout的意思,它也可以...

    用户3579639
  • yaffs_guts(一)

    1. 计算所给Block所在位图 static __inline __u8 *yaffs_BlockBits(yaffs_Device *dev, int bl...

    瓜大三哥
  • 网络兼职套路深

    网络兼职诈骗:骗子利用网络社交平台,仿冒正规的网络兼职招聘者的宣传手段和宣传内容,如网络打字员、商城刷单、刷信誉等方式,对受害人实施远程、非接触式诈骗,诱使受害...

    用户6966869
  • 《数学之美》拾遗——TF-IDF

    开篇序     在学习机器学习的过程中,我写了简单易学的机器学习算法的专题,依然还有很多的算法会陆续写出来。网上已经有很多人分享过类似的材料,我只是通过自己的理...

    zhaozhiyong
  • 需求管理那些事儿

    在实际工作中,大家很少有机会经历从0到1的项目,绝大多数情况是加入到一个已经发展了一段时间的团队,参与维护已经运行了几年的项目。

    阿杜
  • 腾讯优图CVPR中标论文:不靠硬件靠算法,暗光拍照也清晰

    他们提出基于深度学习优化光照的暗光下的图像增强模型,用端到端网络增强曝光不足的照片。

    量子位

扫码关注云+社区

领取腾讯云代金券