【Godbach注】
论坛中也经常有网友问到这个问题,pt_prev在ptype_all和pypte_base中的作用是什么。我在LinuxForum上发现了一篇对该 问题解释的精华帖。这里转载过来,并将原帖中两个关键的回帖一并贴了过来,以飨大家。原文是有ID为getmoon发表于LinuxForum上的,连接 为:http://www.linuxforum.net/forum/ ... &page=&view=&sb=&o=。感谢getmoon的精彩分析。                                                                                         --Mar 27,2010
  
    看见noble_shi兄弟"关于net_rx_action函数的若干问题"贴中关于pt_prev的问题, 本来想在论坛上找到一个相关的帖子的链接告诉他。但是发现咱们论坛上关于pt_prev的讨论要么没有说明,要么理解的偏差,甚至是错误。而且关于 pt_prev的提问很多。故写了以下内容。 
    不过本人水平有限,难免说错。请执教getmoon@163.com 
  
    结论:pt_prev使用的原因是为了减少一次kfree_skb的调用,提高效率。
    如果有异议的请往下看。如果你对skb非常了解,那么请直接看<三>, 否则请一步一步往下看。
<一>相关知识 
在讲pt_prev的作用之前, 咱们先说明以下的东西。
(1)alloc_skb中初始化skb->users计数为1。
- struct sk_buff( ) 
 -   { 
 -   .... 
 -       atomic_set(&skb->users, 1); 
 -   ... 
 - }
 
-   static inline void kfree_skb(struct sk_buff *skb) 
 -   { 
 -   if (atomic_read(&skb->users) == 1 || atomic_dec_and_test(&skb->users)) 
 -   __kfree_skb(skb); 
 - }
 
(3)linux内核网络协议栈中到本机的skb包是在上层协议中释放的。
<二>实现ptype_base和ptype_all链
讲了上面的东西后咱们来看ptype_base及ptype_all链相关的东西。这两个链的作用在这里就不讲了。 因为有了上面的东西, 所以涉及到一个skbuff共享的问题, 如果都用skb_clone或者skb_copy,那么性能将是很低的。 所以在linux中使用了skb共享的计数,就是用skb->users计数来计算共享的地方。
许多人理解了ptype_all和ptype_base链的作用之后,就认为为什么不用下面的算法实现。
-   for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) { 
 -       if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { 
 -           atomic_inc(&skb->users); 
 -           ptype->func(skb, skb->dev, ptype); 
 -      } 
 -   } 
 - kfree_skb(skb);
 
再说明一下另外一个问题,如果for循环就调用了ptype->func函数一次的话,那么在整个包的流程中,是调用了kfree_skb两次。 一次在ptype->func函数中,第二次是在for循环之后, 就是上面代码中的kfree_skb 。
说了上面的这个例子之后, 如果你现在知道了为什么用pt_prev来提交效率,那么你就不用往下看了。
<三> 利用pt_prev来提高效率
理解了上面的内容之后,咱们来看看2.4中的代码。
-   for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) { 
 -       if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) {
 -           if (pt_prev) { 
 -               if (!pt_prev->data) 
 -                  deliver_to_old_ones(pt_prev, skb, 0);
 -              else { 
 -                   /*到这里,那么pt_prev指针不为空,ptype(当前的)不为空,那么肯定要共享一次了,所以加1*/ 
 -                   atomic_inc(&skb->users);
 -                   pt_prev->func(skb,skb->dev, pt_prev);
 -                  /*执行上面的函数之后,会在里面减1。所以相对来说,上面两句代码执行之后并没有对skb->users的值进行影响。*/ 
 -             } 
 -          } 
 -          pt_prev = ptype; 
 -   } 
 -   } 
 -   
 -   /*现在skb->users的计数还是为1*/ 
 -   if (pt_prev) { 
 -       if (!pt_prev->data) 
 -           deliver_to_old_ones(pt_prev, skb, 1); 
 -       else 
 -       /*在这里就没有用atomic_inc(&skb->users);因为到这里,skb->users就为1,并且这里是最后一次,所以不用加1,
 -          那么skb就直接在下面的pt_prev->func(skb, skb->dev,   pt_prev);函数中释放了。
 -      */ 
 -       pt_prev->func(skb, skb->dev, pt_prev);
 -   } else{
 -       /*到这里,已经没有对skb进行操作的了。所以必须调用kfree_skb把skb释放掉。*/
 -       kfree_skb(skb); 
 - }
 
你看,<二>和<三>相比是否少了一次调用kfree_skb呢。
到现在, 你是否理解了为什么么用pt_prev了。
你是否在为那些家伙的高深之处而感慨那。
anything i can help u , please email to : getmoon@163.com
后续讨论
ID:rainfall
今天我仔细看了一下linux2.2.x的net_bh,我认为pt_prev的作用是减少一次skb_clone(当然也少一次kfree_skb)。 得出这个结论的理由是:每次在处理skbuff时,相关的处理都会复制一次skbuff的头。如果链表上有n个元素,就要复制n次,然后还有释放n次。最 后还要释放结构本身。但是如果只复制n-1次,最后处理的就是数据本身(引用计数为1)。这样会少复制一次。不过getmoon的说法也没错,只是我觉得 从复制的角度看,可能更能体现
出高效的主题。毕竟,释放并不花什么时间。
ID:getmoon
实际上是这样的,2.2的net_bh里面也采用了这个pt_prev。 它的功能还是如我所言。
兄弟看见的是在调用每个pt->func之前clone了一个。 实际上这个clone在2.4里面并没有去掉。 只是把它移动每个具体的pt->funct里面。 你可以看arp_rcv , ip_rcv等函数都有一个
-   if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) 
 -       goto out_of_mem;
 
我想作者的想法是:如果在pt->func函数里面根本没有必要skb_clone一下, 我为什么
在硬给它clone一个呢。如果呢需要新的skb头,那么呢自己clone去。 因为可能有的人不需要。
没有评论:
发表评论