RT-Thread论坛
直播中

小芳

14年用户 1022经验值
私信 关注
[问答]

调用sys_sem_free()时异常的原因?

在使用lwip的时候,发现调用lwip_select()函数时,在该函数2138行,sys_sem_free(&API_SELECT_CB_VAR_REF(select_cb).sem);
此时该传入的参数在莫名的情况下变成了NULL,该问题发生在频繁建立和断开TCP连接的测试案例中。
发生问题的板子是当TCP服务器,对端当TCP客户端,反复来进行链接测试。
请教一下引起这个现象的主要原因有哪些?
非常感谢!

回帖(1)

李明

2025-10-9 17:33:50

在lwIP的lwip_select()函数中调用sys_sem_free()时参数异常变为NULL,尤其在频繁建立/断开TCP连接的场景下,该问题通常由以下几个原因导致:


1. 竞争条件(Race Condition)



  • 根本原因

    当客户端频繁连接/断开时,多个线程(如TCP回调线程和主线程)可能同时操作同一个select_cb结构体。例如:

    • 线程A正在释放select_cb.sem,但尚未标记结构体为未使用。

    • 线程B立即复用了同一个select_cb结构体,并重新初始化(sem可能被覆盖为NULL或其他值)。

    • 线程A继续执行sys_sem_free()时,传入的sem已被篡改。


  • 触发场景

    高频连接/断开会加剧select_cb结构体的分配/释放频率,放大竞争窗口。


2. 内存越界(Memory Corruption)



  • 根本原因

    其他模块(如TCP接收/发送缓冲区)发生内存越界,意外覆盖了select_cb结构体,将其中的sem字段篡改为NULL

  • 典型线索

    若问题出现时伴随其他随机崩溃(如非法地址访问),内存越界的可能性较高。


3. 代码逻辑缺陷



  • 重复释放

    select_cb.sem可能已被释放(如因连接断开触发提前清理),但未置为NULL,后续再次释放时传入无效指针。

  • 未初始化使用

    在复用select_cb结构体时,未正确初始化sem(如未调用sys_sem_new()),导致sem残留为NULL

  • 回调函数错误

    TCP连接断开时,回调函数(如tcp_err()tcp_recv())可能错误地清除了sem字段。


4. 资源耗尽(Resource Exhaustion)



  • 信号量创建失败

    频繁连接可能导致信号量资源耗尽(如内存不足),使得sys_sem_new()失败并返回NULL,但后续未检查错误直接使用该sem




调试建议


1. 增加防御性检查


   // 在调用 sys_sem_free() 前添加断言
   LWIP_ASSERT("sem != NULL", API_SELECT_CB_VAR_REF(select_cb).sem != NULL);
   sys_sem_free(&API_SELECT_CB_VAR_REF(select_cb).sem);


  • 触发断言时捕获堆栈,定位操作select_cb的代码路径。


2. 添加日志追踪


select_cb生命周期关键点记录日志:


   // 分配 select_cb 时
   LWIP_DEBUGF(SELECT_DEBUG, ("Alloc select_cb=%p, sem=%pn", select_cb, &select_cb->sem));

   // 释放 select_cb 时
   LWIP_DEBUGF(SELECT_DEBUG, ("Free select_cb=%p, sem=%pn", select_cb, &select_cb->sem));


  • 观察sem何时被置为NULL


3. 检查并发保护



  • 确认select_cb全局链表/数组的操作(如alloc/free)已有锁保护(如sys_mutex)。

  • 锁范围:锁必须覆盖从分配结构体到信号量释放的全过程。


4. 内存完整性检查



  • 使用内存检测工具(如Valgrind、AddressSanitizer)检查越界访问。

  • select_cb前后添加保护字段(Guard Bytes),定期验证是否被篡改。


5. 压力测试复现



  • 逐步增加客户端连接/断开频率,观察问题阈值。

  • 模拟信号量创建失败(如强制sys_sem_new()返回NULL),验证错误处理逻辑。




解决方案




  1. 修复竞争条件


    // 示例:扩展锁的保护范围
    sys_mutex_lock(&select_mutex);
    select_cb_t* select_cb = allocate_select_cb(); // 包含初始化 sem
    /* ... 其他操作 ... */
    sys_sem_free(&select_cb->sem);
    mark_select_cb_unused(select_cb); // 释放前标记为未使用
    sys_mutex_unlock(&select_mutex);  // 确保释放完成后再解锁



  2. 防御性编程


    if (select_cb->sem != NULL) {
    sys_sem_free(select_cb->sem);
    select_cb->sem = NULL; // 避免重复释放
    }



  3. 资源管理优化



    • 限制最大并发连接数,避免资源耗尽。

    • 检查sys_sem_new()返回值,失败时直接返回错误。




通过以上步骤,可定位是竞争、内存错误还是逻辑缺陷导致的问题,并针对性修复。重点优先检查竞争条件和内存越界,因高频测试场景下这两类问题最为常见。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分