Chinaunix首页 | 论坛 | 博客
  • 博客访问: 57157
  • 博文数量: 7
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 90
  • 用 户 组: 普通用户
  • 注册时间: 2015-05-10 20:54
个人简介

多学习,多分享!

文章分类
文章存档

2018年(7)

我的朋友

分类: LINUX

2018-08-16 23:14:26

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

阅读(7492) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

misteryoung2018-08-20 22:45:13

dev_kfree_skb_irq对skb的操作包括以下几步:
1)skb引用计数(skb->users)减1;
2)若未减到0,则返回什么也不做;
3)若是减到0,则将该skb插入到当前CPU对应的completion_queue队列的首部,并触发NET_TX_SOFTIRQ软中断;
4)在软中断处理函数net_tx_action中,会将completion_queue队列中所有的skb释放;

简单来说,硬中断中只负责引用计数减1,并将skb插入到当前CPU队列,然后触发软中断,后者负责skb的真正释放。

misteryoung2018-08-20 22:36:08

需要注意是,若在硬中断上下文中释放skb,需要使用dev_kfree_skb_irq。
若是不清楚是否在硬中断上下文,为保险起见可以使用dev_kfree_skb_any,该函数一般被驱动程序调用。