You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

lwIP UDP接收异常:PBUF_POOL_SIZE次后失效但pbuf已释放

解决lwIP NO_SYS模式下UDP通信PBUF_POOL_SIZE次后无法接收响应的问题

首先,咱们直接戳破核心问题:你看到的"pbuf池耗尽"现象是真的,但不是因为发送用的p_out没释放,而是接收路径的pbuf完全没被你释放!这是lwIP开发中非常容易踩的坑,结合你的代码和现象,我来一步步拆解修复方案:

1. 最致命的问题:接收回调未释放pbuf

lwIP在把接收到的数据包传给你的rcv_app_msg回调时,会把pbuf的管理权完全交给用户。如果回调里不调用pbuf_free(p),这个pbuf会一直占用池里的节点——每次接收响应都会耗掉一个pbuf,直到PBUF_POOL_SIZE次后,池里没有可用资源,lwIP就无法再处理新的接收数据包了(Wireshark能抓到是因为硬件收到了,但lwIP拿不到内存来解析)。

修复你的接收回调:

void rcv_app_msg(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) {
    // callback from the ISR
    rx_flag = 1;
    // 必须释放接收到的pbuf,否则pbuf池会被彻底耗尽
    if (p != NULL) {
        pbuf_free(p);
    }
}

2. 发送pbuf的payload赋值错误

你直接写p_out->payload = msg;是完全错误的:pbuf_alloc分配的RAM是独立的,你需要把msg的内容拷贝到pbuf的payload内存里,而不是直接赋值指针(否则pbuf指向的是msg的地址,若msg是栈变量或被覆盖,会出现数据混乱,同时lwIP无法正确管理这段内存)。

修复发送逻辑:

p_out = pbuf_alloc(PBUF_TRANSPORT, sz, PBUF_RAM);
if ( p_out == NULL ) {
    fatal_error("%s : could not allocate out pbuf of %d\n", __func__, sz);
}
// 替换直接赋值指针为内存拷贝
memcpy(p_out->payload, msg, sz);
udp_sendto(pcb_out, p_out, IP_ADDR_BROADCAST, APP_PORT_NUM);

3. 重复配置UDP PCB导致的冗余问题

你在send_app_broadcast_msg和app_inc_pcb_refcount里重复调用udp_bind、udp_connect、udp_recv,这会导致端口重复绑定、回调重复注册,可能引发未知的异常行为。

修改send_app_broadcast_msg,移除重复的PCB配置代码:

void send_app_broadcast_msg(char *msg) {
    uint32_t sz = strlen(msg);
    if ( sz > APP_MAX_MSG_SIZE ) {
        fatal_error("%s : requested message too large (%d, %d) \n", __func__, sz, APP_MAX_MSG_SIZE);
    }
    xprintf("sending broadcast msg \"%s\"... ", msg);
    app_inc_pcb_refcount(); // 这里已经完成了PCB的初始化、绑定、回调注册
    if ( pcb_out == NULL ) {
        fatal_error("%s : could not allocate out pcb\n", __func__);
    }
    ip_set_option(pcb_out, SOF_BROADCAST);
    
    // 移除下面重复的配置代码
    // udp_bind(pcb_out, IP4_ADDR_ANY, APP_PORT_NUM);
    // udp_connect(pcb_out, IP4_ADDR_ANY, APP_PORT_NUM);
    // udp_recv(pcb_out, rcv_app_msg, NULL);
    
    p_out = pbuf_alloc(PBUF_TRANSPORT, sz, PBUF_RAM);
    if ( p_out == NULL ) {
        fatal_error("%s : could not allocate out pbuf of %d\n", __func__, sz);
    }
    memcpy(p_out->payload, msg, sz);
    udp_sendto(pcb_out, p_out, IP_ADDR_BROADCAST, APP_PORT_NUM);
}

4. 引用计数的拼写错误

你在app_inc_pcb_refcount里写了pixee_pcb_refcount++;,但实际定义的变量是app_pcb_refcount,这个拼写错误会导致引用计数失效,可能引发PCB被错误释放的问题:

static void app_inc_pcb_refcount(void) {
    if (app_pcb_refcount == 0) {
        if ( pcb_out != NULL ) {
            fatal_error( "%s : memory leak\n", __func__ );
        }
        pcb_out = udp_new();
        if (pcb_out == NULL) {
            fatal_error( "%s : could not allocate pcb\n", __func__ );
        }
        udp_bind(pcb_out, IP4_ADDR_ANY, APP_PORT_NUM);
        udp_connect(pcb_out, IP4_ADDR_ANY, APP_PORT_NUM);
        udp_recv(pcb_out, rcv_app_msg, NULL);
    }
    // 修复拼写错误
    app_pcb_refcount++;
    return;
}

5. 释放p_out后的无效指针访问

在app_check_timeouts里,你先调用pbuf_free(p_out),然后还访问p_out->ref——这是未定义行为,因为pbuf_free后p_out已经是无效指针了。修改为:

if (end_flag) {
    // 先获取ref计数,再释放pbuf
    u8_t ref_count = p_out->ref;
    uint8_t c = pbuf_free(p_out);
    xprintf("p_out=%p ref=%d, c=%d\n", (void*)p_out, ref_count, c);
    app_dec_pcb_refcount();
    p_out = NULL;
    pcb_out = NULL;
    timeout_ctr = 0;
    rx_flag = 0;
    signal_state = SIGNAL_STATE_IDLE;
}

为什么之前的现象和PBUF_POOL_SIZE强相关?

每次接收服务器响应时,lwIP都会从pbuf池分配一个节点来存储数据包,然后传给你的回调函数。你之前没有释放这些接收用的pbuf,所以每通信一次就耗掉一个池节点,当次数达到PBUF_POOL_SIZE时,池被彻底耗尽,lwIP无法再接收新的数据包,自然就会超时。

把这些修复点全部落实后,即使PBUF_POOL_SIZE设为5,也能持续正常通信,不会再出现超时问题。

内容的提问来源于stack exchange,提问作者danmcb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 10:22:28