Linux网络之设备接口层:发送数据包流程dev_queue_xmit(二)

来源:互联网 发布:王者荣耀安卓数据迁移 编辑:程序博客网 时间:2024/05/01 08:22


本文主要分析在发送数据包过程中,设备接口层容易出现的问题及分析思路。

从抓包工具中抓出来的网络日志的现象以及其他可疑的点来看可以分为2类:

1. 有些数据包被不断的重传(并不是TCP/UDP层的重传),同一个ipid的包不断的被抓到

2.数据包被送到netdevice层,但是并没有看到被送到driver


问题一:

   主要原因是driver 发送数据包出现问题,返回Error,导致数据包被放到Qdisc的queue中,但是还是会不断被尝试着重新发送!

下面我们从code 逻辑来分析:

正常的flow一般情况下是不会enqueue的,会直接发送,如果一旦driver发送失败了,就先将数据包enqueue,详细的发送过程可以参考文章:

最接近driver的函数__netdev_start_xmit如果发送失败会返回一个Error,如下面的值,例如Tx_Busy


enum netdev_tx {__NETDEV_TX_MIN = INT_MIN,/* make sure enum is signed */NETDEV_TX_OK = 0x00,/* driver took care of packet */NETDEV_TX_BUSY = 0x10,/* driver tx path was busy*/NETDEV_TX_LOCKED = 0x20,/* driver tx lock was already taken */};

static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,      struct sk_buff *skb, struct net_device *dev,      bool more){skb->xmit_more = more ? 1 : 0;return ops->ndo_start_xmit(skb, dev);}
返回一个错误的值,表示这个数据包发送出现问题,但是目前Qdisc会进行拥塞处理,将数据包在放到queue中,下面的代码段是在sch_direct_xmit函数中返回值处理的部分

如果发送成功,就会返回Q的len,如果NETDEV_TX_LOCKED会说明driver去lock的时候失败,其他状况下一般认为是默认的错误即TX BUSY,这个时候会调用dev_requeue_skb把数据包重新放到Qdisc的队列中!

if (dev_xmit_complete(ret)) {/* Driver sent out skb successfully or skb was consumed */ret = qdisc_qlen(q);} else if (ret == NETDEV_TX_LOCKED) {/* Driver try lock failed */ret = handle_dev_cpu_collision(skb, txq, q);} else {/* Driver returned NETDEV_TX_BUSY - requeue skb */if (unlikely(ret != NETDEV_TX_BUSY))net_warn_ratelimited("BUG %s code %d qlen %d\n",     dev->name, ret, q->q.qlen);ret = dev_requeue_skb(skb, q);}

简单的来看下这个函数,把skb放到q->gso_skb中,然后增加q数量,然后调用__netif_schedule,这个是来使用软中断去发送数据包!
/* Main transmission queue. *//* Modifications to data participating in scheduling must be protected with * qdisc_lock(qdisc) spinlock. * * The idea is the following: * - enqueue, dequeue are serialized via qdisc root lock * - ingress filtering is also serialized via qdisc root lock * - updates to tree and tree walking are only done under the rtnl mutex. */static inline int dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q){q->gso_skb = skb;q->qstats.requeues++;q->q.qlen++;/* it's still part of the queue */__netif_schedule(q);return 0;}
所以说基本上这个包被放入queue中,还会通过软中断,或者下一个数据包包来的时候去触发__qdisc_run, 这里应该就是谁快就使用谁吧。

中间发送的过程我们不再详细的解释,这个地方重点说一点就是dequeue skb的时候,发现TX stop的状况下都不会dequeue出skb

/* Note that dequeue_skb can possibly return a SKB list (via skb->next). * A requeued skb (via q->gso_skb) can also be a SKB list. */static struct sk_buff *dequeue_skb(struct Qdisc *q, bool *validate,   int *packets){       //这里先看Q中有没有requeue的skb,一般情况下存在发送失败的状况就会被requeuestruct sk_buff *skb = q->gso_skb;const struct netdev_queue *txq = q->dev_queue;*packets = 1;*validate = true;if (unlikely(skb)) {/* check the reason of requeuing without tx lock first */txq = skb_get_tx_queue(txq->dev, skb);//如果gso_skb有,并且没有TXQ stop,那么就dequeu这个gso skb,发送失败的优先..if (!netif_xmit_frozen_or_stopped(txq)) {q->gso_skb = NULL;q->q.qlen--;} else//如果TXQ被stop了,那么无法dequeue出skb 就返回空skb = NULL;/* skb in gso_skb were already validated */*validate = false;} else {        //如果是多Q或者txq没有被stop 就dequeue skbif (!(q->flags & TCQ_F_ONETXQUEUE) ||    !netif_xmit_frozen_or_stopped(txq)) {skb = q->dequeue(q);if (skb && qdisc_may_bulk(q))try_bulk_dequeue_skb(q, skb, txq, packets);}}return skb;}

从上面来看,我们的数据包仍然是能够被dequeue出来,之后还是会走到dev_hard_start_xmit 发送给driver, 如果driver还是无作为的话,那么我们就看到netdevice层同一个ipid的包被不断的重发...


问题二:

    其实问题二在上述的分析中已经提及到了,如果TCP协议中有数据包发送,送到netdevice了,但是driver并没有收到,在网络抓包工具中也没有显示出来任何发包的痕迹,不过一般情况下是 发着发着就不能发了。这个的原因主要是上层管理interface的process或者driver调用了netif_tx_stop_queue, 这个函数的作用就是把interface的txq停掉,不让发包

static inline void netif_tx_stop_queue(struct netdev_queue *dev_queue){if (WARN_ON(!dev_queue)) {pr_info("netif_stop_queue() cannot be called before register_netdev()\n");return;}set_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);}

那么我们再来看下发包过程中会不会遇到阻碍,假设没有enqueue的状况下,一个数据包跑到了sch_direct_xmit里面,会判断txq是否为stop或者frozen状态,如果是 就不发了

直接requeue了.

if (!netif_xmit_frozen_or_stopped(txq))skb = dev_hard_start_xmit(skb, dev, txq, &ret);...........省掉N行/* Driver returned NETDEV_TX_BUSY - requeue skb */if (unlikely(ret != NETDEV_TX_BUSY))net_warn_ratelimited("BUG %s code %d qlen %d\n",     dev->name, ret, q->q.qlen);ret = dev_requeue_skb(skb, q);

那么好 既然requeue了,我还可以dequeue再去发,想一下,及时能够dequeue出来,还是要调用到sch_direct_xmit啊,还是被当掉的继续requeue,这样其实不免白白的操作了链表,实际上kernel也没有那么傻了,在dequeue的时候就已经做了手脚,可以参考dequeue_skb的解释了... 如果txq 被stop直接就dequeue失败了...确实想的周到

//如果gso_skb有,并且没有TXQ stop,那么就dequeu这个gso skb,发送失败的优先..if (!netif_xmit_frozen_or_stopped(txq)) {q->gso_skb = NULL;q->q.qlen--;} else//如果TXQ被stop了,那么无法dequeue出skb 就返回空skb = NULL;


所以我们会看到不能上网,但上层确实有数据包发出来,不过Q最多帮你顶到1000个包,如果超了就彻底被drop了。这时就会产生丢包现象,另外提一下就是在driver发送速度较慢,上层输送数据较快的状况下,这样会不断的有数据包被enqueue,当达到一定的瓶颈的时候,也就是1000的时候,后面灌输过来的数据包就会被丢掉了,这样就会产生掉包的现象,这个没有太好的办法,需要driver优化!


总结:

   本文主要分析了在netdevice Tx方向比较常见的问题,当然小的系统可能遇不到,在像手机,平板这样的大的复杂的系统中,这种问题在所难免,记住逻辑行为,结合抓包分析工具去分析!


1 0
原创粉丝点击