TCP/IP学习(35)——IP包的发送流程(2)

简介:

原文地址:TCP/IP学习(35)——IP包的发送流程(2) 作者:GFree_Wind

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
    

上次学习IP包的发送流程时,学习到了dev_queue_xmit这个函数。
  1. int dev_queue_xmit(struct sk_buff *skb)
  2. {
  3.     struct net_device *dev = skb->dev;
  4.     struct netdev_queue *txq;
  5.     struct Qdisc *q;
  6.     int rc = -ENOMEM;

  7.     /* Disable soft irqs for various locks below. Also
  8.      * stops preemption for RCU.
  9.      */
  10.     rcu_read_lock_bh();
     /* 得到发送队列 */
  1.     txq = dev_pick_tx(dev, skb);
  2.     q = rcu_dereference_bh(txq->qdisc);

  3. #ifdef CONFIG_NET_CLS_ACT
  4.     skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
  5. #endif
  6.     if (q->enqueue) {
  7.         /* 一般的dev都应该进入这里 */
  8.         rc = __dev_xmit_skb(skb, q, dev, txq);
  9.         goto out;
  10.     }
     
     ...... ......
  1. }
进入函数__dev_xmit_skb
  1. static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
  2.                  struct net_device *dev,
  3.                  struct netdev_queue *txq)
  4. {
  5.     spinlock_t *root_lock = qdisc_lock(q);
  6.     bool contended = qdisc_is_running(q);
  7.     int rc;

  8.     /*
  9.      * Heuristic to force contended enqueues to serialize on a
  10.      * separate lock before trying to get qdisc main lock.
  11.      * This permits __QDISC_STATE_RUNNING owner to get the lock more often
  12.      * and dequeue packets faster.
  13.      */
  14.     if (unlikely(contended))
  15.         spin_lock(&q->busylock);

  16.     spin_lock(root_lock);
  17.     if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
  18.         /* 该quque的状态为非活动的,drop该数据包 */
  19.         kfree_skb(skb);
  20.         rc = NET_XMIT_DROP;
  21.     } else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
  22.          qdisc_run_begin(q)) {
         /* 
         这部分代码,从注释上看,似乎选中的queue是一个保留的工作queue。
         想来也是非正常路径,暂时保留不看。
         */
  1.         /*
  2.          * This is a work-conserving queue; there are no old skbs
  3.          * waiting to be sent out; and the qdisc is not running -
  4.          * xmit the skb directly.
  5.          */
  6.         if (!(dev->priv_flags & IFF_XMIT_DST_RELEASE))
  7.             skb_dst_force(skb);
  8.         __qdisc_update_bstats(q, skb->len);
  9.         if (sch_direct_xmit(skb, q, dev, txq, root_lock)) {
  10.             if (unlikely(contended)) {
  11.                 spin_unlock(&q->busylock);
  12.                 contended = false;
  13.             }
  14.             __qdisc_run(q);
  15.         } else
  16.             qdisc_run_end(q);

  17.         rc = NET_XMIT_SUCCESS;
  18.     } else {
         /* 正常路径 */

         /* 确保dst被引用,防止被其他模块释放 */
  1.         skb_dst_force(skb);
  2.         /* 将数据包加入到queue中 */
  3.         rc = qdisc_enqueue_root(skb, q);
         /* 如果queue不是运行状态,将其置为运行状态 */
  1.         if (qdisc_run_begin(q)) {
  2.             if (unlikely(contended)) {
  3.                 spin_unlock(&q->busylock);
  4.                 contended = false;
  5.             }
  6.             __qdisc_run(q);
  7.         }
  8.     }
  9.     spin_unlock(root_lock);
  10.     if (unlikely(contended))
  11.         spin_unlock(&q->busylock);
  12.     return rc;
  13. }
将数据包加入队列的函数是通过q->enque的回调实现的,那么这个enque的回调钩子函数是何时注册上的呢?
请看dev_activate,用于激活网卡。
  1. void dev_activate(struct net_device *dev)
  2. {
  3.     int need_watchdog;

  4.     /* No queueing discipline is attached to device;
  5.      create default one i.e. pfifo_fast for devices,
  6.      which need queueing and noqueue_qdisc for
  7.      virtual interfaces
  8.      */
      /*
     当没有指定queueing discipline时,就使用默认的discipline
     */

  1.     if (dev->qdisc == &noop_qdisc)
  2.         attach_default_qdiscs(dev);

  3.     ...... ......   
  4. }
这里不列出attach_default_qdiscs的代码了,一般情况下,网卡只有一个queue时,这个默认的discipline为
  1. struct Qdisc_ops pfifo_fast_ops __read_mostly = {
  2.     .id        =    "pfifo_fast",
  3.     .priv_size    =    sizeof(struct pfifo_fast_priv),
  4.     .enqueue    =    pfifo_fast_enqueue,
  5.     .dequeue    =    pfifo_fast_dequeue,
  6.     .peek        =    pfifo_fast_peek,
  7.     .init        =    pfifo_fast_init,
  8.     .reset        =    pfifo_fast_reset,
  9.     .dump        =    pfifo_fast_dump,
  10.     .owner        =    THIS_MODULE,
  11. };
那么对于我们来说,就确定了默认的要是一般情况下的enque函数为pfifo_fast_enqueue。
  1. static int pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc)
  2. {
  3.     if (skb_queue_len(&qdisc->q) < qdisc_dev(qdisc)->tx_queue_len) {
  4.         int band = prio2band[skb->priority & TC_PRIO_MAX];
  5.         struct pfifo_fast_priv *priv = qdisc_priv(qdisc);
  6.         struct sk_buff_head *list = band2list(priv, band);

  7.         priv->bitmap |= (1 << band);
  8.         qdisc->q.qlen++;
  9.         return __qdisc_enqueue_tail(skb, qdisc, list);
  10.     }

  11.     return qdisc_drop(skb, qdisc);
  12. }
上面就是 __dev_xmit_skb中调用的q->enque的代码,将数据包加入到了dev->_tx所对应的队列中。
然后我还需要回到__dev_xmit_skb中,在加数据包加入到队列中后。要保证qdisc为运行态。
  1.         rc = qdisc_enqueue_root(skb, q);
  2.         if (qdisc_run_begin(q)) {
  3.             if (unlikely(contended)) {
  4.                 spin_unlock(&q->busylock);
  5.                 contended = false;
  6.             }
  7.             __qdisc_run(q);
  8.         }
查看__qdisc_run的代码。
  1. void __qdisc_run(struct Qdisc *q)
  2. {
  3.     unsigned long start_time = jiffies;

      /*
     qdisc_restart中发送了数据包。
     这里是循环发送,直至qdisc_restart返回0
     或者其它进程请求CPU或发送已运行比较长的时间(1jiffie)则也跳出循环体。
     */

  1.     while (qdisc_restart(q)) {
  2.         /*
  3.          * Postpone processing if
  4.          * 1. another process needs the CPU;
  5.          * 2. we've been doing it for too long.
  6.          */
  7.         if (need_resched() || jiffies != start_time) {
  8.             /*
  9.             需要以后再执行发送动作(利用softirq)
  10.             */
  11.             __netif_schedule(q);
  12.             break;
  13.         }
  14.     }

  15.     qdisc_run_end(q);
  16. }
进入qdisc_restart->sch_direct_xmit,该函数用于发送一个数据包
  1. int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
  2.          struct net_device *dev, struct netdev_queue *txq,
  3.          spinlock_t *root_lock)
  4. {
  5.     int ret = NETDEV_TX_BUSY;

  6.     /* And release qdisc */
  7.     spin_unlock(root_lock);

  8.     HARD_TX_LOCK(dev, txq, smp_processor_id());
  9.     //设备没有被停止,且发送队列没有被冻结
  10.     if (!netif_tx_queue_stopped(txq) && !netif_tx_queue_frozen(txq))
  11.         ret = dev_hard_start_xmit(skb, dev, txq); //发送数据包

  12.     HARD_TX_UNLOCK(dev, txq);

  13.     spin_lock(root_lock);

  14.     if (dev_xmit_complete(ret)) {
  15.         /* Driver sent out skb successfully or skb was consumed */
  16.         //发送成功,返回qdisc新的队列产的
  17.         ret = qdisc_qlen(q);
  18.     } else if (ret == NETDEV_TX_LOCKED) {
  19.         /* Driver try lock failed */
  20.         //锁冲突
  21.         ret = handle_dev_cpu_collision(skb, txq, q);
  22.     } else {
  23.         /* Driver returned NETDEV_TX_BUSY - requeue skb */
  24.         if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))
  25.             printk(KERN_WARNING "BUG %s code %d qlen %d\n",
  26.              dev->name, ret, q->q.qlen);
         //设备繁忙,重新调度发送(利用softirq)
  1.         ret = dev_requeue_skb(skb, q);
  2.     }

  3.     if (ret && (netif_tx_queue_stopped(txq) ||
  4.          netif_tx_queue_frozen(txq)))
  5.         ret = 0;

  6.     return ret;
  7. }
到此,本文长度已经不短了,先就此结尾。IP包的发送流程比接收流程要复杂得多,估计还需一篇博文才能基本走完。
相关文章
|
9月前
|
存储 缓存 网络协议
网络基础学习:什么是tcp/ip协议
网络基础学习:什么是tcp/ip协议
107 0
|
网络协议
TCP/IP协议的介绍
TCP/IP协议是众多协议的统称,通过分层结构来管理。可分为七层模型或四层结构
|
网络协议 网络架构
六、TCP/IP模型 和 5层参考模型
六、TCP/IP模型 和 5层参考模型
六、TCP/IP模型 和 5层参考模型
|
域名解析 网络协议
IP协议, TCP协议 和DNS 服务分别是干什么的?
大家好,我是阿萨。昨天讲解了网络四层协议[TCP/IP协议族分为哪4层?]今天我们学习下IP 协议, TCP 协议和DNS 协议分别是干什么的。
211 0
IP协议, TCP协议 和DNS 服务分别是干什么的?
|
网络协议
TCP/IP协议族有哪些?
大家好,我是阿萨。昨天我们学习了[URI 和URL 的区别是什么?]了解了URI 和URL的区别。 学习HTTP, 绕不开TCP/IP,那么TCP/IP 协议族分为哪4层?
254 0
TCP/IP协议族有哪些?
|
网络协议
计算机网络学习27:TCP连接与连接释放
客户端和服务端都是先建立传输控制模块
计算机网络学习27:TCP连接与连接释放
|
缓存 网络协议 算法
计算机网络学习26:TCP/UDP对比区别、TCP流量控制、拥塞控制、超时重传时间的选择、可靠传输的实现
UDP: User Datagram Protocol 用户数据报协议 TCP: Transmission Control Protocol 传输控制协议 同时这里指的连接是指逻辑连接,而不是物理连接。
计算机网络学习26:TCP/UDP对比区别、TCP流量控制、拥塞控制、超时重传时间的选择、可靠传输的实现
|
网络协议 网络性能优化 网络安全
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
|
域名解析 网络协议 安全
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(1)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(1)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(1)