关于netif_receive_skb中的一个链表处理问题

在netif_receive_skb中有一段代码是将receive的数据包clone一份交给ptype_all的链,代码如下:
……
pt_prev=NULL;
list_for_each_entry_rcu(ptype,&ptype_all,list){
      if(!ptype->dev||ptype->dev==skb->dev){
           if(pt_prev)
                 ret=deliver_skb(skb,pt_prev,orig_dev);
           pt_prev=ptype;
      }
}

我感觉上述代码有点迷糊啊,既然是遍历ptype_all链,为什么需要一个pt_prev保存一下呢,直接下面这样不就就行了
list_for_each_entry_rcu(ptype,&ptype_all,list){
      if(!ptype->dev||ptype->dev==skb->dev){
                 ret=deliver_skb(skb,ptype,orig_dev);
      }
}
那位大虾给讲解一下

[ 本帖最后由 Godbach 于 2009-8-21 16:06 编辑 ]

作者: 瀚海书香   发布时间: 2009-08-21

LZ好啊,我把你的标题编辑了一下。希望有更多人讨论你的问题

作者: Godbach   发布时间: 2009-08-21



QUOTE:
原帖由 瀚海书香 于 2009-8-21 15:59 发表
在netif_receive_skb中有一段代码是将receive的数据包clone一份交给ptype_all的链,代码如下:
……
pt_prev=NULL;
list_for_each_entry_rcu(ptype,&ptype_all,list){
      if(!ptype->dev||ptype->dev==sk ...



这里是为了确定pt_prev是不是已经被投递了,如果已经被投递了,就往下遍历。
这个可能是为了避免smp并发处理造成对包的多次deliver。

作者: dreamice   发布时间: 2009-08-21

谢谢版主

麻烦版主给分析一下吧

作者: 瀚海书香   发布时间: 2009-08-21

看一下dreamice兄的解释。

但从程序上来看,基本上是遍历到当前节点时,将保存的前一个非空节点deliver。

作者: Godbach   发布时间: 2009-08-21

可否详细讲解一下
上面那种情况是如何防止smp的多次deliver,以及下面这种情况又是在怎样的情况下产生多次deliver的??
小弟愿闻其详

作者: 瀚海书香   发布时间: 2009-08-21

按理说遍历链表的时候已经加锁了啊

作者: Godbach   发布时间: 2009-08-21

而且更让我疑惑的是,按上面的那种情况,好像链表最后符合条件的那个ptype没有deliver啊

作者: 瀚海书香   发布时间: 2009-08-21



QUOTE:
原帖由 瀚海书香 于 2009-8-21 16:25 发表
可否详细讲解一下
上面那种情况是如何防止smp的多次deliver,以及下面这种情况又是在怎样的情况下产生多次deliver的??
小弟愿闻其详



我先看看代码

作者: dreamice   发布时间: 2009-08-21

因为后面还要用

作者: platinum   发布时间: 2009-08-21

之后的代码就是bridge和vlan,然后就是ptype_base对应的协议处理函数。
在ptype_base的链表遍历中的确会deliver给ptype_all中的最后一个,但是为什么要到这里才deilver给ptype_all中的最后一个呢?
还有如果说在bridge或者vlan的处理中就返回了,就走不到这一步,那样ptype_all的最后一个就不能收到数据包了

作者: 瀚海书香   发布时间: 2009-08-21



QUOTE:
原帖由 瀚海书香 于 2009-8-21 19:13 发表
之后的代码就是bridge和vlan,然后就是ptype_base对应的协议处理函数。
在ptype_base的链表遍历中的确会deliver给ptype_all中的最后一个,但是为什么要到这里才deilver给ptype_all中的最后一个呢?
还有如果说 ...



逻辑处理上没有问题。

pt_prev意思就是“前一个packet_type”。

可以这么说,最后一个ptype_all的func是在ptype_base的循环中调用的。(如果ptype_base的循环中if成立)

你看在ptype_base循环下面,还有个if(pt_prev) {......

我也不清楚为什么这么做

而且,我觉得这里有点问题。
因为deliver_skb(就是执行packet_type->func())是有可能kfree_skb()的。
这样的话,后面如果还有匹配到的话,skb已经被释放了。

[ 本帖最后由 ShadowStar 于 2009-8-22 01:46 编辑 ]

作者: ShadowStar   发布时间: 2009-08-22



QUOTE:
逻辑处理上没有问题。

pt_prev意思就是“前一个packet_type”。

可以这么说,最后一个ptype_all的func是在ptype_base的循环中调用的。(如果ptype_base的循环中if成立)

你看在ptype_base循环下面  


的确是这样的,但是如果说没有运行到这就return了呢?




QUOTE:
而且,我觉得这里有点问题。
因为deliver_skb(就是执行packet_type->func())是有可能kfree_skb()的。


这个地方是没问题的,因为deliver_skb的时候有操作atomic_inc(&skb->users);而之后的kfree_skb()也是atomic_dec(&skb->users),只有当skb->users=0的时候才真正的free(skb)和free(skb->data)

作者: 瀚海书香   发布时间: 2009-08-22

这个还是涉及到优化的问题。

如果只有一个packet_type match的话,我们就可以直接call pt_prev->func(skb, skb->dev, pt_prev, orig_dev),注意这里的话并没有increase skb->uesrs,这个skb就是这个handler所独有的。

如果存在多个handler match的话,在deliver_skb的时候需要increase skb->users,然后在这个handler里,如果这个skb是共享的,就需要skb_clone这个skb (see skb_share_check for details)

所以这么麻烦,就是为了优化最common的一个case,就是只有一个handler match的话,省去了skb_clone的开销。

[ 本帖最后由 eexplorer 于 2009-8-22 11:35 编辑 ]

作者: eexplorer   发布时间: 2009-08-22

的确是这样的。
但是在ptype_all类型的handle之后紧跟着是bridge和vlan的处理,之后是ptype_base类型handle的处理,但是如果bridge和vlan两个处理的某一个返回值为NULL,则直接跳转到out:处执行,这样的话最后一个handle就不能收到数据包了?

作者: 瀚海书香   发布时间: 2009-08-22



QUOTE:
原帖由 瀚海书香 于 2009-8-22 11:53 发表
的确是这样的。
但是在ptype_all类型的handle之后紧跟着是bridge和vlan的处理,之后是ptype_base类型handle的处理,但是如果bridge和vlan两个处理的某一个返回值为NULL,则直接跳转到out:处执行,这样的话最后一 ...




看了一下handle_bridge和hanel_macvlan,两个函数都有这样一段code:
        if (*pt_prev) {
                *ret = deliver_skb(skb, *pt_prev, orig_dev);
                *pt_prev = NULL;
        }

作者: eexplorer   发布时间: 2009-08-22

那就明白了
多谢了

作者: 瀚海书香   发布时间: 2009-08-22



QUOTE:
原帖由 瀚海书香 于 2009-8-22 13:20 发表
那就明白了
多谢了


之前早就说过了呀,后面还要用的

作者: platinum   发布时间: 2009-08-22

本帖最后由 donotgiveup 于 2011-01-13 10:11 编辑


QUOTE:
这个还是涉及到优化的问题。

如果只有一个packet_type match的话,我们就可以直接call pt_prev->func(sk ...
eexplorer 发表于 2009-08-22 11:33



看了下代码,
在收包的时候,只有一个匹配,调用的也是deliver_skb,skb->users会++。里面也没有skb_clone。
是不是处理程序如果要修改要自己clone?

在发包的时候,dev_queue_xmit_nit也会调用ptype_all注册的handler。如果是原封不动的转发包,那同一个包不是会调用两次注册的处理函数?

作者: donotgiveup   发布时间: 2011-01-13

回复 donotgiveup


   

QUOTE:
Signed-off-by: Changli Gao <xiaosuo@gmail.com>
---
net/core/dev.c |   30 ++++++++++++++++++++----------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index bf5ced5..888cb74 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1496,6 +1496,14 @@ int dev_forward_skb(struct net_device *dev, struct sk_buff *skb)
}
EXPORT_SYMBOL_GPL(dev_forward_skb);

+static inline int deliver_skb(struct sk_buff *skb,
+                             struct packet_type *pt_prev,
+                             struct net_device *orig_dev)
+{
+       atomic_inc(&skb->users);
+       return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
+}
+
/*
*     Support routine. Sends outgoing frames to any network
*     taps currently in use.
@@ -1504,6 +1512,8 @@ EXPORT_SYMBOL_GPL(dev_forward_skb);
static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
{
       struct packet_type *ptype;
+       struct sk_buff *skb2 = NULL;
+       struct packet_type *pt_prev = NULL;

#ifdef CONFIG_NET_CLS_ACT
       if (!(skb->tstamp.tv64 && (G_TC_FROM(skb->tc_verd) & AT_INGRESS)))
@@ -1520,7 +1530,13 @@ static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
               if ((ptype->dev == dev || !ptype->dev) &&
                   (ptype->af_packet_priv == NULL ||
                    (struct sock *)ptype->af_packet_priv != skb->sk)) {
-                       struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+                       if (pt_prev) {
+                               deliver_skb(skb2, pt_prev, skb->dev);
+                               pt_prev = ptype;
+                               continue;
+                       }
+
+                       skb2 = skb_clone(skb, GFP_ATOMIC);
                       if (!skb2)
                               break;

@@ -1542,9 +1558,11 @@ static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)

                       skb2->transport_header = skb2->network_header;
                       skb2->pkt_type = PACKET_OUTGOING;
-                       ptype->func(skb2, skb->dev, ptype, skb->dev);
+                       pt_prev = ptype;
               }
       }
+       if (pt_prev)
+               pt_prev->func(skb2, skb->dev, pt_prev, skb->dev);
       rcu_read_unlock();
}

@@ -2788,14 +2806,6 @@ static void net_tx_action(struct softirq_action *h)
       }
}

-static inline int deliver_skb(struct sk_buff *skb,
-                             struct packet_type *pt_prev,
-                             struct net_device *orig_dev)
-{
-       atomic_inc(&skb->users);
-       return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
-}
-
#if (defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)) && \
    (defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE))
/* This hook is defined here for ATM LANE */


最新的内核对这个地方进行了优化。上面是内核列表里的对这个改动的说明。

作者: 瀚海书香   发布时间: 2011-01-13