请选择 进入手机版 | 继续访问电脑版
    查看: 134|回复: 2

    [经验分享] 基于kfree_skb的解析

    [复制链接]

    签到天数: 178 天

    [LV.7]化身百千

    发表于 2018-10-9 10:58:15 | 显示全部楼层 |阅读模式

    1、关于引用计数
        在Linux源码中引用计数随处可见,其作用是保护被引用的对象不会被其他模块销毁/释放。
        引用计数的操作需要使用原子函数。例如:
        1)读计数:atomic_read;
        2)计数初始化:atomic_set;
        3)计数加/减: atomic_inc/atomic_dec;
        4)计数减并返回新值:atomic_dec_and_test(同样是原子操作);
        内核对skb(包括data指向的内存)、ct(链接跟踪)、路由cache、dev的保护都需要引用计数,但其使用方法各不相同。本文只针对skb的释放来介绍skb引用计数的使用。
    2、skb的引用计数
        skb有关的引用计数主要有两个:
        1)skb->users:              对skb自身的保护
        2)skb_shinfo(skb)->dataref:对skb->data的保护
        skb可以在任何上下文(中断及进程上下文)中申请及释放。
        skb被释放的唯一条件就是skb->users被减为0。当然,skb被释放并不代表其data区域也被释放,因为有可能多个skb指向同一个data。而data是否被释放取决于skb_shinfo(skb)->dataref是否被减为0.
    3、kfree_skb原型
    void kfree_skb(struct sk_buff *skb)
    {
        if (unlikely(!skb))
            return;
        if (likely(atomic_read(&skb->users) == 1))
            smp_rmb();
        else if (likely(!atomic_dec_and_test(&skb->users)))
            return;
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }
    4、kfree_skb分析
    void kfree_skb(struct sk_buff *skb)
    {
        if (unlikely(!skb))    参数检查
            return;
        若引用计数为1说明只有当前模块引用skb,减1后skb可被释放
        if (likely(atomic_read(&skb->users) == 1))   
            smp_rmb();
        此处引用计数减1,并判断新值是否为0(也即当前值是否为1),若是,则继续执行,否则返回
        但问题是,前面已经判断了计数是否为1,此处重复判断,显然有些多余,因此该函数可优化成下面的kfree_skb1
        else if (likely(!atomic_dec_and_test(&skb->users)))
            return;
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }
        注:atomic_dec_and_test作用为:减1,并判断新值是否为0
    5、kfree_skb优化
    void kfree_skb1(struct sk_buff *skb)
    {
        if (unlikely(!skb))    参数检查
            return;
        判断引用计数是否为1,若是,说明只用当前模块引起skb,可以安全释放skb
        if (likely(atomic_read(&skb->users) == 1))   
            smp_rmb();
        else {
            atomic_dec(&skb->users);        否则将计数减1,并返回
            return;
        }
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }
        显然,优化后的kfree_skb1相对于标准的kfree_skb,无论是逻辑还是效率都好很多。
    6、kfree_skb的变种
    void kfree_skb2(struct sk_buff *skb)
    {
        if (unlikely(!skb))    参数检查
            return;
        引用计数减1,并判断新值是否为0,若是,说明只用当前模块引用skb,可以安全释放skb
        if (likely(atomic_dec_and_test(&skb->users)))   
            smp_rmb();
        else
            return;                    否则返回(相当于只是将计数减1)
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }
    7、继续分析
        kfree_skb1对kfree_skb做了性能和逻辑上的优化,而kfree_skb2仅仅是逻辑上优化。但是,kfree_skb真的就可以轻松的进行优化吗?Linux社区藏龙卧虎,就没有发现这些问题(性能及逻辑)吗?
        让我们再从头好好分析分析kfree_skb1和kfree_skb2函数。
    假设两个模块A、B(A、B既可以是进程/内核线程,也可以是软中断)引用该skb,并分别调用kfree_skb1/kfree_skb2。下面以数字来表示执行的先后顺序。
    void kfree_skb1(struct sk_buff *skb)
    {
        if (unlikely(!skb))
            return;
        1)A、B先后到达此处,计数为2;
        if (likely(atomic_read(&skb->users) == 1))
            smp_rmb();
        else {
            2)A、B先后到达此处,并依次减1(减1是原子操作),并均会返回,但此时计数已经为0,skb并未被释放,这显然是有问题的(问题出在:atomic_read和atomic_dec分别是原子操作,但合在一起就不是原子操作);
            atomic_dec(&skb->users);
            return;
        }
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }

    void kfree_skb2(struct sk_buff *skb)
    {
        if (unlikely(!skb))
            return;
        1)A到达此处,此时计数为2,减1后返回(注意:减1和判断是原子操作,不可分割)
        2)B到达此处,此时计数为1,减1后变成0,则继续执行,后面释放skb
        此时skb也可以正常被释放,也即功能没问题。
        但A和B共调用atomic_dec_and_test两次,而对于kfree_skb函数而言,调用一次atomic_dec_and_test和两次atomic_read,很难说哪个效率高。但是考虑到skb引用计数为1的情况较多,因此kfree_skb相对效率较高。
        if (likely(atomic_dec_and_test(&skb->users)))
            smp_rmb();
        else
            return;
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }

    8、结论
        1)kfree_skb1函数存在bug,在特殊情况下,skb无法被正确释放。
        2)总体来说,kfree_skb要比kfree_skb2效率要高。
        3)atomic_read和atomic_dec分别是原子操作,但合在一起就不是原子操作,因此需要atomic_dec_and_test这样的函数,包含了读写功能,又是原子操作。

    签到天数: 693 天

    [LV.9]元老将成

    发表于 2018-10-9 11:50:13 | 显示全部楼层
    不错的资料
    回复 支持 反对

    使用道具 举报

    签到天数: 693 天

    [LV.9]元老将成

    发表于 2018-10-9 11:50:15 | 显示全部楼层
    不错的资料
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    返回顶部