当前位置: 首页 > ds >正文

LWIP流程全解

lwIP 数据流与 tcpip_thread

本文对 lwIP 从网卡接收数据到内核处理的完整流程做系统、结构化且可操作的说明。结合 tcpip 源码片段(tcpip_thread / tcpip_inpkt / tcpip_input / tcpip_timeouts_mbox_fetch),重点说明:消息构造与传递、pbuf(所有权)约定、线程与中断的交互、锁策略选项的影响、以及实现驱动时的注意点与常见问题排查方法。


一、整体流程概览

在这里插入图片描述
硬件接收帧 → 中断/底层驱动唤醒接收线程 → 驱动把帧打包成 pbuf 并构造 TCPIP_MSG_INPKT 投递到 tcpip_mbox → tcpip_thread 从邮箱取消息并在内核线程上下文执行 ethernet_input/ip_input 等处理(完成后由内核负责后续释放或转发)。

图示中关键环节对应源码:ethernet IRQ → s_xSemaphore → ethernetif_input → low_level_input → tcpip_input/tcpip_inpkt → sys_mbox_post(&tcpip_mbox, msg) → tcpip_thread 的 TCPIP_MBOX_FETCH → tcpip_thread_handle_msg()。


二、分步详解

  1. 硬件中断与网卡驱动(IRQ 上下文)
  • ETH_IRQHandler 中通常只做最小工作:读取/清中断、简短统计、释放接收信号量(例如 s_xSemaphore)或直接使用 sys_mbox_trypost_fromisr/tcpip_callbackmsg_trycallback_fromisr。不要在 ISR 中做耗时拷贝或 pbuf_alloc(可能会阻塞/失败)。
  1. 接收线程(ethernetif_input)——驱动任务级处理
  • 等待信号量被唤醒后调用 low_level_input 获取 DMA 描述符里帧信息。此处会:invalidate D-Cache、计算帧长、决定是否为零拷贝或 memcpy 到 pbuf。常见做法:
    • p = pbuf_alloc(PBUF_RAW/PBUF_POOL, len, PBUF_POOL); memcpy 或 pbuf_alloced_custom 填充 DMA 缓冲(零拷贝)。
  • 关键:当把 pbuf 交给 tcpip_input 时,驱动不再持有 pbuf 的所有权(除非采用自定义 free 回调归还 DMA)。
  • 如果驱动无法上送(mbox 满、pbuf NULL),需统计并正确释放 DMA 描述符,避免卡死 RX。
  1. tcpip_input / tcpip_inpkt(接口层)
  • tcpip_input 根据是否以太网选择 ethernet_input 或 ip_input:
    • tcpip_input() → tcpip_inpkt(p, inp, ethernet_input)
  • tcpip_inpkt 在 NO_SYS=0 且 !LWIP_TCPIP_CORE_LOCKING_INPUT 情况下,构造 struct tcpip_msg(类型 TCPIP_MSG_INPKT),并 memp_malloc(MEMP_TCPIP_MSG_INPKT),然后 sys_mbox_trypost(&tcpip_mbox, msg)。
  • 成功投递后立即返回 ERR_OK;失败需调用方处理(驱动通常在失败路径释放 pbuf 或计数丢包)。
  • 在这里插入图片描述
  1. tcpip_thread:邮箱取消息并处理(核心)
  • tcpip_thread 主循环:TCPIP_MBOX_FETCH(&tcpip_mbox, &msg);取到 msg 后调用 tcpip_thread_handle_msg(msg)。
  • TCPIP_MBOX_FETCH 在启用定时器(LWIP_TIMERS)时会调用 tcpip_timeouts_mbox_fetch:在等待消息期间处理超时(sys_check_timeouts),保证定时器回调在 tcpip_thread 上下文执行。
  • tcpip_thread_handle_msg 对 TCPIP_MSG_INPKT:调用 msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif)。注意:如果 input_fn 返回非 ERR_OK,代码会 pbuf_free(msg->msg.inp.p)。这说明:一旦消息交给 tcpip_thread,上下文切换后失败由内核释放 pbuf。
  1. ethernet_input / ip_input 处理(协议栈内)
  • ethernet_input 解析以太网帧类型,ARP 包由 etharp_input 处理并更新 ARP 表;IP 包调用 ip_input。ip_input 进一步将分片、路由、传给传输层(TCP/UDP)。
  • 在处理过程中,pbuf 可能被链式分割、clone 或引用计数(例如转发给上层 socket)。引用计数管理很重要:若 pbuf 被多个对象持有,必须使用 pbuf_ref,释放时由最后持有者调用 pbuf_free。

从上图可以看出Lwip并没有明确的分层。接收通常有一个驱动级的“接收线程”(ethernetif_input),或者只有 ISR + 驱动任务。也就是说常见部署会有 tcpip_thread + 一个或多个网卡驱动线程(再加上若干用户线程)。通常没有一个独立的“发送线程”在 lwIP core 中。发送由调用方(应用线程/stack 内的处理函数)触发,通过 netif->linkoutput / low_level_output 启动 DMA 发送。驱动的 TX 完成由 ISR 处理并通知(或通过回调在 tcpip_thread 上释放 pbuf)。当然如果你想的话也可以把接受线程去掉,直接在中断做接受,不过不建议这么做。


三、消息类型与同步方式(源码对应)

  • TCPIP_MSG_API / TCPIP_MSG_API_CALL:用于各类 API 请求(netconn/socket 等)。API_CALL 会带信号量用于同步等待(tcpip_api_call / tcpip_send_msg_wait_sem)。
  • TCPIP_MSG_INPKT:接收数据包消息,由驱动投递到 tcpip_mbox。
  • TCPIP_MSG_CALLBACK / TCPIP_MSG_CALLBACK_STATIC:用于在 tcpip_thread 上执行回调(pbuf_free_callback、mem_free_callback、或其他延迟处理)。
  • 若定义 LWIP_TCPIP_CORE_LOCKING:可以在调用线程直接通过 LOCK_TCPIP_CORE() 执行内核相关函数,避免消息收发开销(但要正确处理互斥)。若定义 LWIP_TCPIP_CORE_LOCKING_INPUT:驱动可以直接在驱动线程调用 input_fn(p, inp)(在驱动线程内获得锁),不再构造 TCPIP_MSG_INPKT。选择策略与系统可预见性/实时性有关。

四、tcpip_timeouts_mbox_fetch 的作用

  • 当启用 LWIP_TIMERS 时,tcpip_thread 需要既能处理来自驱动的消息,也要在正确线程上下文调用 sys_check_timeouts。tcpip_timeouts_mbox_fetch 的逻辑:
    • 计算下一次超时 sleeptime;若为 infinite,则阻塞等待消息;若为 0,立即调用 sys_check_timeouts 并重试取消息;否则在解锁并等待 mbox 的 sleeptime 时间,若超时则处理超时后重试取消息。
  • 结果:tcpip_thread 同时成为消息处理与系统定时器的执行线程。实现上要求 lock/unlock 核心时机正确,避免死锁。

五、pbuf 所有权与调用约定

  • 驱动分配 pbuf 后上送 tcpip_input(投递消息)时,驱动必须遵循:
    • 一旦将 pbuf 交给 tcpip_inpkt 并成功投递消息,驱动不能再直接访问或释放该 pbuf(除非是自定义 pbuf 的 free 回调机制)。
    • 如果 sys_mbox_trypost 失败(投递失败路径),驱动必须负责释放 pbuf(pbuf_free)并归还 DMA 描述符。否则会泄漏或丢帧。- tcpip_thread 在处理 input_fn 时若返回 ERR_OK,则内核继续持有并按协议层逻辑转交;若返回非 ERR_OK,tcpip_thread 会调用 pbuf_free。
  • 若使用零拷贝(pbuf_alloced_custom),必须提供 custom_free_function 在最后释放时归还 DMA buffer;确保 custom_free_function 在 tcpip_thread 或安全上下文中运行或做原子归还。

六、实现驱动与系统配置建议

  • 在 IRQ 中尽量只做通知(sem/give/try_post_fromisr),不要做 pbuf_alloc/mem_malloc。- ethernetif_input 中做 pbuf_alloc(PBUF_POOL, len, PBUF_POOL) 或 pbuf_alloced_custom(零拷贝)并尽快 sys_mbox_trypost;若 mbox 满,采用退避/统计丢包并及时归还 DMA 描述符。- lwipopts.h 推荐关注:TCPIP_MBOX_SIZE(邮箱深度)、PBUF_POOL_SIZE、MEMP_TCPIP_MSG_INPKT 的池大小、LWIP_TCPIP_CORE_LOCKING 与 LWIP_TCPIP_CORE_LOCKING_INPUT 的配置。mbox 太小会导致驱动投递失败并丢包。- 若平台有 CPU cache,务必在传给 DMA/从 DMA 读取前做 DCache Clean/Invalidate。零拷贝更依赖缓存一致性。- 若对延迟敏感,考虑:LWIP_TCPIP_CORE_LOCKING 可以减少消息切换,但需要正确使用 LOCK_TCPIP_CORE/UNLOCK。

七、常见故障与排查要点

  • 接收突然停止 / pbuf 在驱动中无法分配:检查 PBUF_POOL_SIZE 与 MEMP_NUM_PBUF 使用率(开启 MEMP_STATS)并统计 memp_malloc 失败计数。- mailbox 满导致丢包:增加 TCPIP_MBOX_SIZE 或在驱动中使用带退避的重试/丢弃策略;记录 sys_mbox_trypost 失败的次数。- pbuf 双重释放/崩溃:检查是否在驱动把 pbuf 交给 tcpip 后仍然访问/释放;检查是否正确使用 pbuf_ref。- 定时器不执行:检查 LWIP_TIMERS 与 tcpip_timeouts_mbox_fetch 是否启用;确认 tcpip_thread 正常运行且没有长时间阻塞。- 中断上下文调用错误 API:不能在 ISR 中直接调用 pbuf_free(应使用 pbuf_free_callback/tcpip_try_callback_fromisr)。

八、构造时序

在这里插入图片描述

  1. 用户线程调用 netconn_bind()/socket API

    • 由上层 API 构造“api_msg”或 netconn_apimsg(携带参数、指针、等待信号等)。
  2. 构造 tcpip_msg(包装 API)

    • tcpip 层用 memp_malloc(MEMP_TCPIP_MSG_API) 分配 struct tcpip_msg,设置 type = TCPIP_MSG_API 或 TCPIP_MSG_API_CALL,并填充 msg.api_msg / msg.api_call 字段(即要在 tcpip_thread 调用的函数指针与参数)。
  3. 投递消息到 tcpip_mbox(sys_mbox_post 或 trypost)

    • 阻塞型一般用 sys_mbox_post;从 ISR/try 用 trypost_fromisr/trypost。若失败要返回 ERR_MEM 并释放相关资源(pbuf/参数)。
  4. 用户线程等待信号量(sys_arch_sem_wait)

    • 等待方式视实现:tcpip_send_msg_wait_sem 会创建/使用一个信号量并在投递后等待;当使用 LWIP_NETCONN_SEM_PER_THREAD 时,线程复用 semaphore,降低开销。
  5. tcpip_thread 从 tcpip_mbox 取到消息(TCPIP_MBOX_FETCH)并分发处理

    • tcpip_thread_handle_msg 对 TCPIP_MSG_API:执行 msg->msg.api_msg.function(msg->msg.api_msg.msg)
    • 对 TCPIP_MSG_API_CALL:执行并将返回值写入 call->err,随后 sys_sem_signal 用于唤醒等待者
  6. 实际的 API 实现函数在 tcpip_thread 上下文执行(例如 wip_netconn_do_bind)

    • 在这里可以安全访问 lwIP 内核数据结构(netif、pcb、memp 等),无需再跨线程同步(除非你禁用 core locking)。
  7. 处理完毕后释放/signal

    • 对于 API_CALL 模式,tcpip_thread 会直接 sys_sem_signal(msg->msg.api_call.sem)。
    • 对于普通 API(TCPIP_MSG_API),被调用的函数通常负责在完成时做 sys_sem_signal(tcpip_send_msg_wait_sem 的设计),调用者随后继续执行并做 TCPIP_MSG_VAR_FREE。
  8. 用户线程获得信号量,继续执行并清理(free msg 等)

    • TCPIP_MSG_VAR_FREE 或 memp_free 会由调用者或 tcpip_thread 按配置释放(见 API_VAR_* 宏的行为)。

九、结论

  • 明确“pbuf 的所有权转移”是理解整个流程的关键:驱动把 pbuf 交给 tcpip_thread 后不再访问;tcpip_thread(或协议栈)负责释放或转移所有权。- 选择 LWIP_TCPIP_CORE_LOCKING 与 LWIP_TCPIP_CORE_LOCKING_INPUT 影响性能与复杂度:锁能减少消息开销但需正确互斥;无锁设计更通用但会有线程切换开销。- 对于高吞吐场景,优先保证 RX 不会被 PBUF_POOL/TCPIP_MBOX 等资源饿死:合适的缓冲大小、合理的 mailbox 大小与驱动退避/统计是必要的工程手段。

http://www.xdnf.cn/news/18369.html

相关文章:

  • java实现url 生成二维码, 包括可叠加 logo、改变颜色、设置背景颜色、背景图等功能,完整代码示例
  • 【运维进阶】Ansible 角色管理
  • 记一次 .NET 某自动化智能制造软件 卡死分析
  • 流程进阶——解读 49页 2023 IBM流程管理与变革赋能【附全文阅读】
  • Redis缓存加速测试数据交互:从前缀键清理到前沿性能革命
  • 微服务-07.微服务拆分-微服务项目结构说明
  • 236. 二叉树的最近公共祖先
  • 从密度到聚类:DBSCAN算法的第一性原理解析
  • 100202Title和Input组件_编辑器-react-仿低代码平台项目
  • git 创用操作
  • 【集合框架LinkedList底层添加元素机制】
  • Python网络爬虫全栈教程 – 从基础到实战
  • 网络编程day4
  • 电商平台接口自动化框架实践
  • Codeforces 斐波那契立方体
  • 寻找旋转排序数组中的最小值
  • 企业知识管理革命:RAG系统在大型组织中的落地实践
  • RNN如何将文本压缩为256维向量
  • Voice Agents:下一代语音交互智能体的架构革命与产业落地
  • 缓存-变更事件捕捉、更新策略、本地缓存和热key问题
  • 20.2 QLoRA微调全局参数实战:高点击率配置模板+显存节省50%技巧
  • 【论文阅读】DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D Queries
  • 《WASM驱动本地PDF与Excel预览组件的深度实践》
  • 使用 Ansys Discovery 探索外部空气动力学
  • 决策树算法详解
  • Esp32基础(⑨RGB LED)
  • Python网络爬虫(三) - 爬取动态网页数据
  • 18650锂电池自动化生产线:智能集成提升制造效能
  • 【库的操作】
  • 如何使用tar备份整个openEuler系统