kfree_skb()意外行为

时间:2015-01-16 10:21:33

标签: linux-kernel network-programming kernel-module

我在使用kfree_skb方面遇到了一些麻烦。以下3行代码以奇怪的方式表现,

printk(KERN_ALERT"1 - SKB user: %d", atomic_read(&skb->users));
kfree_skb(skb);
printk(KERN_ALERT"2 - SKB user: %d", atomic_read(&skb->users));

我希望第二个printk导致内核恐慌,因为我释放了skb,但事实并非如此。这些行的输出如下;

1 - SKB user: 1
2 - SKB user: 2

我错过了什么意思?

编辑:很抱歉我错误输入了第二个输出。它如下:

2 - SKB user: 1

2 个答案:

答案 0 :(得分:0)

当你释放skb时,很有可能在另一个线程中分配它。 试想一下这个

Thread 1:

printk(KERN_ALERT"1 - SKB user: %d", atomic_read(&skb->users));
kfree_skb
-------> scheduled out  

Thread 2:
alloc_skb()
//inc user count (whatever the kernel call)
-------> Scheduled out 

然后现在返回线程1

printk(KERN_ALERT"2 - SKB user: %d", atomic_read(&skb->users));

现在非常有可能获得用户数2。

由于skb是从slab缓存分配的,所以很有可能刚刚释放的skb首先被分配

答案 1 :(得分:0)

这个问题有点老了,但是我相信它仍然令人困惑(我不久前就在想这个问题)。因此,首先让我们快速了解一下kfree_skb()函数。该代码已在v4.13上更改,但其行为仍相同:

在v4.13之前(发布问题后):

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);
}

v4.13之后:

void kfree_skb(struct sk_buff *skb)
{
    if (!skb_unref(skb))
        return;

    trace_kfree_skb(skb, __builtin_return_address(0));
    __kfree_skb(skb);
}

static inline bool skb_unref(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return false;
    if (likely(refcount_read(&skb->users) == 1))
        smp_rmb();
    else if (likely(!refcount_dec_and_test(&skb->users)))
        return false;

    return true;
}

在两种情况下,我都注意到,当您调用kfree_skb()时,skb确实被释放了(已调用__kfree_skb())。但是,我还注意到,在两种情况下,如果skb->users在调用kfree_skb()之前为1,即使调用kfree_skb()之后它也将保持为1。
所以事情是,当应该释放skb时,kfree_skb()确实释放了skb,但它不会保护您免于双重释放skb的麻烦。看来它是有意为避免使用不必要的原子操作而创建的(您可以在skbuff.h代码的某处找到此注释):

/*
 * If users == 1, we are the only owner and can avoid redundant atomic changes.
 */

尽管如此,您仍然可以看到,在__dev_kfree_skb_irq()中的行为只是您所期望的(如果在调用skb->users之前__dev_kfree_skb_irq()为1,则skb->users设置为预期为0):

void __dev_kfree_skb_irq(struct sk_buff *skb, enum skb_free_reason reason)
{
    unsigned long flags;

    if (unlikely(!skb))
        return;

    if (likely(refcount_read(&skb->users) == 1)) {
        smp_rmb();
        refcount_set(&skb->users, 0);
    } else if (likely(!refcount_dec_and_test(&skb->users))) {
        return;
    }
    get_kfree_skb_cb(skb)->reason = reason;
    local_irq_save(flags);
    skb->next = __this_cpu_read(softnet_data.completion_queue);
    __this_cpu_write(softnet_data.completion_queue, skb);
    raise_softirq_irqoff(NET_TX_SOFTIRQ);
    local_irq_restore(flags);
}