
应用层发送一个数据包的时候,是如何到达网卡的(下)
发布日期:2021-05-09 16:03:19
浏览次数:19
分类:精选文章
本文共 6194 字,大约阅读时间需要 20 分钟。
从前面的一篇文章可以知道,应用层发送一个数据包的时候首先经过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 intel_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层,数据包也不一定是直接发送出去的,他可能会先缓存在发送队列里,按序发送。如果发送失败,则放回发送队列,等待重发。
发表评论
最新留言
路过,博主的博客真漂亮。。
[***.116.15.85]2025年04月16日 23时56分57秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
call_user_func函数和call_user_func_array函数
2021-05-10
配置php.ini文件,关闭错误提示,打开错误日志,设置错误日志路径
2021-05-10
接收get或post数据使用fwrite写入文件中,方便追踪错误;或其他几种缓存方式
2021-05-10
mysql开启慢查询日志及查询
2021-05-10
Window平台Grpc框架搭建
2021-05-10
C中几道位运算的例题
2021-05-10
python入门(二)基础知识
2021-05-10
golang log4go 使用说明及丢失日志原因
2021-05-10
Android Studio打包生成Jar包的方法
2021-05-10
华为路由器配置VRRP
2021-05-10
Excel 如何根据单元格中的值设立不同的颜色(或渐变)?(222)
2021-05-10
python 文件操作 open()与with open() as的区别(打开文件)
2021-05-10
python中列表 元组 字典 集合的区别
2021-05-10
python struct 官方文档
2021-05-10
中级软考 计算机指令执行过程(取指、分析、执行)计算机重叠流水线问题
2021-05-10
Docker镜像加速
2021-05-10