【问题标题】:kfree_skb() after skb_dequeue() freezes linux kernelskb_dequeue() 冻结 Linux 内核后的 kfree_skb()
【发布时间】:2016-08-01 12:50:20
【问题描述】:

我正在 linux 内核的自定义协议中实现流控制。当我收到 ACK 时,我想从写入队列中删除 acked 数据包。这是一些代码

for(i = (ack->sequence - qp->first_unack); i>0&&sk->sk_write_queue.qlen>0; i++){
    skb_del = skb_dequeue(&sk->sk_write_queue);
    qp->first_unack++;
    kfree_skb(skb_del);
}

我从这段代码中得到了内核冻结。但是,当我注释掉 kfree(skb_del) 时,一切都运行良好。任何想法为什么会发生这种情况?我还能如何释放内存?

【问题讨论】:

  • 能否给出内核冻结消息或内核崩溃转储
  • 内核日志文件不包含任何内容,因为冻结会停止写入文件
  • 如果您在源上分享更多上下文会很好。当您在注释 kfree() 后说它可以工作时,它看起来像是导致此崩溃的 skb_del 的双重释放。

标签: memory-management linux-kernel network-programming


【解决方案1】:

由于 skb 排队到套接字,您可以使用已经提供的套接字 API;

sk_eat_skb(struct sock *sk, struct sk_buff *skb, bool copied_early)  // copied_ealy = 0

更多细节你可以跟踪tcp_recvmsg,在那里你会得到执行流程

进一步了解您为什么要自己使用来自队列/出队循环的自定义 API。只需通过include/net/sock.h 我希望你能得到必要的细节

【讨论】:

  • 这是个好主意。但我使用的函数不是标准套接字 API 的一部分吗?
  • 你可以通过include /linux/skbuff.h ,你会得到更多的想法。是的,API 是标准的,但 linux 提供了标准的 API,例如 skb_queue_walk(&sk->sk_receive_queue, skb)
【解决方案2】:

这可能是因为双重释放skb_del

理论上,在调用kfree_skb(skb_del)之前,可以通过refcount_read(&skb_del->users)检查skb_del->users的值,如果skb_del->users为0,则说明skb_del已经被释放。

实际上kfree_skb()函数在skb_del最终发布时并没有设置skb_del->users为0(出于一些优化考虑),所以在skb_del之后会发布它将保持为 1,您将无法知道 skb_del 是否已被释放。

如果您仍然好奇这是否是双重释放问题,并且您可以对 skbuff 基础架构进行一些更改(仅用于本次调查),那么我们需要修改一些 skbuff 函数。

警告:玩这个功能很容易导致内核崩溃,所以要小心。但是这些修改是有效的(通过这种方式我发现了一个双免的skb)。请记住,这只是用于调查双重释放问题的建议,我不知道这些修改是否会长期影响您的系统。

我们将修改以下函数(基于内核 v5.9.1):

skb_unref()   // from include/linux/skbuff.h
__kfree_skb() // from net/core/skbuff.c
kfree_skb()   // from net/core/skbuff.c
consume_skb() // from net/core/skbuff.c

原 skb_unref()

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

修改后的 skb_unref()

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

    return true;
}

原文__kfree_skb()

void __kfree_skb(struct sk_buff *skb)
{
    skb_release_all(skb);
    kfree_skbmem(skb);
}

修改了 __kfree_skb()

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

    skb_release_all(skb);
    kfree_skbmem(skb);
}

原来的 kfree_skb()

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

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

修改后的 kfree_skb()

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

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

原始的consume_skb()

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

    trace_consume_skb(skb);
    __kfree_skb(skb);
}

修改后的 consume_skb()

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

    trace_consume_skb(skb);
    __kfree_skb(skb);
}

祝调查顺利。
愿上帝与你同在。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-24
    • 2019-10-08
    相关资源
    最近更新 更多