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

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

[复制链接]

签到天数: 104 天

[LV.6]签到达人

发表于 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这样的函数,包含了读写功能,又是原子操作。

签到天数: 673 天

[LV.9]元老将成

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

使用道具 举报

签到天数: 673 天

[LV.9]元老将成

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

使用道具 举报

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

本版积分规则

返回顶部