免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 13769 | 回复: 5

关于ptype_all和pypte_base中的pt_prev的说明[转] [复制链接]

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-03-27 17:50 |显示全部楼层
本帖最后由 Godbach 于 2010-03-27 17:58 编辑

【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。
  1. struct sk_buff( )
  2. {
  3. ....
  4.     atomic_set(&skb->users, 1);
  5. ...
  6. }
复制代码
(2)kfree_skb中如果计数skb->users不为1则不会释放skbuff 。
  1. static inline void kfree_skb(struct sk_buff *skb)
  2. {
  3. if (atomic_read(&skb->users) == 1 || atomic_dec_and_test(&skb->users))
  4. __kfree_skb(skb);
  5. }
复制代码
当引用数为1或者引用数减1等于零时, 回收包缓冲。

(3)linux内核网络协议栈中到本机的skb包是在上层协议中释放的。

<二>实现ptype_base和ptype_all链

    讲了上面的东西后咱们来看ptype_base及ptype_all链相关的东西。这两个链的作用在这里就不讲了。 因为有了上面的东西, 所以涉及到一个skbuff共享的问题, 如果都用skb_clone或者skb_copy,那么性能将是很低的。 所以在linux中使用了skb共享的计数,就是用skb->users计数来计算共享的地方。
许多人理解了ptype_all和ptype_base链的作用之后,就认为为什么不用下面的算法实现。
  1. for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
  2.     if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) {
  3.         atomic_inc(&skb->users);
  4.         ptype->func(skb, skb->dev, ptype);
  5.    }
  6. }
  7. kfree_skb(skb);
复制代码

    我来说说为什么最后还要一个kfree_skb。在进入这个for循环之前,skb->users的计数为1,每进入一个ptype->fun函数之前都会加,并且在每个ptype->fun函数里面都会有kfree_skb函数(会减users),但是并没有真正的把skb释放掉。还记得刚开始说明中kfree_skb里面的atomic_dec_and_test(&skb->users)吗。 所以atomic_inc(&skb->users);ptype->func(skb, skb->dev, ptype);两句代码执行之后并没有改变skb->users的计数,还是1。所以这样可以在for循环中循环好几次, 无论几个ptype->func都可以共享这个skb。退出for循环之后, skb->users还是1,并且之前并没有真正的释放掉内存。因此你要调用kfree_skb(skb) 来释放内存。
    再说明一下另外一个问题,如果for循环就调用了ptype->func函数一次的话,那么在整个包的流程中,是调用了kfree_skb两次。 一次在ptype->func函数中,第二次是在for循环之后, 就是上面代码中的kfree_skb 。
说了上面的这个例子之后, 如果你现在知道了为什么用pt_prev来提交效率,那么你就不用往下看了。

<三> 利用pt_prev来提高效率

    理解了上面的内容之后,咱们来看看2.4中的代码。
  1. for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
  2.     if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) {
  3.         if (pt_prev) {
  4.             if (!pt_prev->data)
  5.                deliver_to_old_ones(pt_prev, skb, 0);
  6.            else {
  7.                 /*到这里,那么pt_prev指针不为空,ptype(当前的)不为空,那么肯定要共享一次了,所以加1*/
  8.                 atomic_inc(&skb->users);
  9.                 pt_prev->func(skb,skb->dev, pt_prev);
  10.                /*执行上面的函数之后,会在里面减1。所以相对来说,上面两句代码执行之后并没有对skb->users的值进行影响。*/
  11.           }
  12.        }
  13.        pt_prev = ptype;
  14. }
  15. }

  16. /*现在skb->users的计数还是为1*/
  17. if (pt_prev) {
  18.     if (!pt_prev->data)
  19.         deliver_to_old_ones(pt_prev, skb, 1);
  20.     else
  21.     /*在这里就没有用atomic_inc(&skb->users);因为到这里,skb->users就为1,并且这里是最后一次,所以不用加1,
  22.        那么skb就直接在下面的pt_prev->func(skb, skb->dev,   pt_prev);函数中释放了。
  23.    */
  24.     pt_prev->func(skb, skb->dev, pt_prev);
  25. } else{
  26.     /*到这里,已经没有对skb进行操作的了。所以必须调用kfree_skb把skb释放掉。*/
  27.     kfree_skb(skb);
  28. }
复制代码

    你看,<二>和<三>相比是否少了一次调用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等函数都有一个
  1. if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
  2.     goto out_of_mem;
复制代码

    这个函数实际上也是clone一个。而2.2的每个pt->func如arp_rcv , ip_rcv里面都是没有的。 因为在调用pt->func之前就clone了。所以2.4的做法是实际上把skb_clone往后移动了。 为什么呢。我想这个还是重效率上考虑的。
    我想作者的想法是:如果在pt->func函数里面根本没有必要skb_clone一下, 我为什么
在硬给它clone一个呢。如果呢需要新的skb头,那么呢自己clone去。 因为可能有的人不需要。

论坛徽章:
0
发表于 2014-06-14 15:10 |显示全部楼层
本帖最后由 huangbanban 于 2014-06-14 15:24 编辑

哈哈,我的理解是下面两个函数出现的次数要一致,减少逻辑思维量
atomic_inc()
pt_prev->func()
而skb初始化中atomic_set(&skb->users, 1);就相当于调用了一次atomic_inc()
不用再加多余的kfree_skb(),因为pt_prev->func()已经包含了kfree_skb()

论坛徽章:
0
发表于 2013-07-19 17:13 |显示全部楼层
+1回复 3# 卖萌犯法


   

论坛徽章:
0
发表于 2013-06-24 00:02 |显示全部楼层
本帖最后由 honkiko 于 2013-06-24 00:06 编辑

回复 1# Godbach

顶。 解答了我很久以来一直存在的一个疑惑
   

论坛徽章:
0
发表于 2013-06-21 10:44 |显示全部楼层
我认为这个理解是不正确的。

首先我很怀疑linux内核设计者是不是会为了节省一个atomic_dec_and_test,而破坏代码的逻辑结构。

我认为:使用pt_prev是为了提升自定义packet_type的优先级,使之位于某些特殊处理之前执行,而内核默认的packet_type处理将位于这些特殊处理之后。

1.由于dev_add_pack的头插方式,导致循环ptype_all和ptype_base结束之后,pt_prev将指向内核默认的packet_type,例如:对于ptype_all ,默认是AF_PACKET;

2.在最后的pt_prev->fun(也就是内核默认处理)执行之前,可以附加特殊操作,例如:2.4.21中的handle_diverter(netif_receive_skb,net\core\dev.c:1498),和2.6.34中的VLAN处理(netif_receive_skb,net\core\dev.c:2574);

3.由于添加特殊处理的位置位于for循环之后和最后pt_prev之前,导致内核默认的packet_type将位于这些处理之后执行,而其他模块自定义的packet_type将位于这些处理之前执行(提升了优先级);

4.所有packet_type处理的都是采取pt_prev的方式,可能就是为了给添加这些特殊处理留下空间。

至于最后一段缺少atomic_inc(&skb->users);的处理片段,只是追求额外福利罢了。

论坛徽章:
0
发表于 2011-06-22 15:11 |显示全部楼层
回复 1# Godbach


有点不明白:
1)
kfree_skb中如果计数skb->users不为1则不会释放skbuff 。
static inline void kfree_skb(struct sk_buff *skb)

{

if (atomic_read(&skb->users) == 1 || atomic_dec_and_test(&skb->users))

__kfree_skb(skb);

}
当引用数为1或者引用数减1等于零时, 回收包缓冲。

2)    结论:pt_prev使用的原因是为了减少一次kfree_skb的调用,提高效率。

不管有没有pt_prev,最终都会调用__kfree_skb(skb)真正释放内存吧?那么结论中减少一次kfree_skb的调用是不是可以理解为 减少了一次引用计数减1的调用,这个应该不怎么耗时吧?
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP