网友通过本文主要向大家介绍了数据链路层,数据链路层协议,数据链路层的主要功能,物理层 数据链路层,数据链路层的功能等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com
二层(链路层)数据包发送过程分析
二层(链路层)数据包发送过程分析
——lvyilong316 说明:本系列博文所涉及内核版本为2.6.32
当上层准备好一个包之后,交给链路层,链路层数据包发送主要通过dev_queue_xmit函数处理。数据包的发送可分为两种,一种是正常的传输流程,即通过网卡驱动,另一种是通过软中断(见注3)。为了理解方便,首先看一下dev_queue_xmi函数的整体调用关系图。
ldev_queue_xmit
本函数用来将带发送的skb加入一个dev的队列(Queue),调用这个函数前必须设置好skb的device和priority,本函数可以在中断上下文中被调用。
返回值:
返回非0(正数或负数)表示函数出错,返回0表示成功,但是并不表示数据包被成功发送出去,因为数据包可能因为限速等原因被丢掉。
函数执行后传入的skb将被释放,所以如果想控制数据包,实现对skb的重传时需要增加skb的引用计数。
当调用此函数时中断必须是打开的,因为BHenable必须要求IRQenable,否则会造成死锁。
- 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;
- }
l__dev_xmit_skb
__dev_xmit_skb函数主要做两件事情:
(1)如果流控对象为空的,试图直接发送数据包。
(2)如果流控对象不空,将数据包加入流控对象,并运行流控对象。
- static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
- struct net_device *dev,
- struct netdev_queue *txq)
- {
- spinlock_t *root_lock = qdisc_lock(q);//见注2
- int rc;
- spin_lock(root_lock); //锁qdisc
- if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {//判断队列是否失效
- kfree_skb(skb);
- rc = NET_XMIT_DROP;
- } else if ((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);
- return rc;
- }
lqdisc_run
有两个时机将会调用qdisc_run():
1.__dev_xmit_skb()
2.软中断服务线程NET_TX_SOFTIRQ
- static inline void qdisc_run(struct Qdisc *q)
- {
- if (!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))//将队列设置为运行状态
- __qdisc_run(q);
- }
l__qdisc_run
- void __qdisc_run(struct Qdisc *q)
- {
- unsigned long start_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() |