【LwIP源码学习4】主线程tcpip_thread

前言

本文对lwip的主要线程tcpip_thread进行分析。

正文

tcpip_thread是lwip最主要的线程,其创建在tcpip_init函数中

sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);

tcpip_init函数被TCPIP_Init函数调用。
TCPIP_Init函数被用户应用程序调用。
在这里插入图片描述
创建任务的sys_thread_new函数在sys_arch.c文件中实现,sys_arch.c是操作系统相关的接口文件,在有操作系统环境下移植lwip时要对sys_arch.c中的函数进行实现。
tcpip_thread任务的内容如下:

static void
tcpip_thread(void *arg)
{
  struct tcpip_msg *msg;
  LWIP_UNUSED_ARG(arg);

  LWIP_MARK_TCPIP_THREAD();

  LOCK_TCPIP_CORE();
  //tcpip_init_done是一个函数指针
  //tcpip_init_done_arg是一个变量指针
  //这两个可以由用户设置,如果想在while循环执行前做些什么
  if (tcpip_init_done != NULL) {
    tcpip_init_done(tcpip_init_done_arg);
  }

  while (1) {                          /* MAIN Loop */
  	//用户可以在opt.h文件中对下面这个宏进行函数定义
  	//可以根据这个宏使得用户在应用层知道tcpip_thread任务还活着
    LWIP_TCPIP_THREAD_ALIVE();
    /* wait for a message, timeouts are processed while waiting */
    //等待邮箱中出现消息、处理超时事件
    TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg);
    if (msg == NULL) {
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
      LWIP_ASSERT("tcpip_thread: invalid message", 0);
      continue;
    }
    //处理接收到的消息
    tcpip_thread_handle_msg(msg);
  }
}

tcpip_init_done函数指针和tcpip_init_done_arg变量指针在tcpip_init中被设置

void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
...
  tcpip_init_done = initfunc;
  tcpip_init_done_arg = arg;
...
}

而在用户可以修改的sys_arch.c文件中对TCPIP_Init函数的实现:

void TCPIP_Init(void)
{
...
  tcpip_init(NULL, NULL);
...
}

LWIP_TCPIP_THREAD_ALIVE宏在opt.h文件中被定义

#if !defined LWIP_TCPIP_THREAD_ALIVE || defined __DOXYGEN__
#define LWIP_TCPIP_THREAD_ALIVE()
#endif

如果用于想监测tcpip_thread任务是否一直活着可以在这里进行修改。

TCPIP_MBOX_FETCHtcpip_timeouts_mbox_fetch函数的带参宏定义,用于等待邮箱中是否有需要处理的消息,或者检查是否有超时事件,如果有则执行其回调函数。

static void
tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
{
  u32_t sleeptime, res;

again:
  LWIP_ASSERT_CORE_LOCKED();
  
  //获取距离最近超时事件的时间
  sleeptime = sys_timeouts_sleeptime();
  if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {//表示系统没有超时事件
    UNLOCK_TCPIP_CORE();
    //没有超时事件就无限期的一直等待邮箱中有消息到来
    sys_arch_mbox_fetch(mbox, msg, 0);
    LOCK_TCPIP_CORE();
    return;
  } else if (sleeptime == 0) {//表示有超时事件需要立刻执行
    //找出来超时事件并执行其回调函数
    sys_check_timeouts();
    /* We try again to fetch a message from the mbox. */
    //处理完需要立刻执行的超时事件后继续查看最近超时事件时间
    goto again;
  }
  
  //如果系统有超时事件但不还有一段时间触发,就执行以下代码
  UNLOCK_TCPIP_CORE();
  //等到邮箱中是否有消息,等待时间为sleeptime,也就是最近超时事件发生时间
  res = sys_arch_mbox_fetch(mbox, msg, sleeptime);
  LOCK_TCPIP_CORE();
  if (res == SYS_ARCH_TIMEOUT) {//表示邮箱等待超时了,没有等到邮箱中出现新的消息
    /* If a SYS_ARCH_TIMEOUT value is returned, a timeout occurred
       before a message could be fetched. */
    sys_check_timeouts(); //处理超时事件
    /* We try again to fetch a message from the mbox. */
    goto again; //继续查看是否有超时事件,没有就无限期等邮箱
  }
}

以上这个函数只有在收到邮箱中的一个消息之后才return返回,否则有超时事件就处理超时事件,没有则无限期等待邮箱中出现消息。
思考:似乎有个bug,如果一开始没有超时事件,在无限性等待邮箱中的消息时又出现超时事件了,那么如果一直等不到邮箱中的消息,新出现的超时事件就一直不会被处理了。可能这种情况不会发生,也可能lwip对这种情况有其他处理,还没看到。
其中等待邮箱中出现消息的sys_arch_mbox_fetch函数在sys_arch.c文件中,由用户移植时进行实现。

接下来调用tcpip_thread_handle_msg函数对消息进行分类处理。

static void
tcpip_thread_handle_msg(struct tcpip_msg *msg)
{
  switch (msg->type) {
    case TCPIP_MSG_API:
	...
      break;
    case TCPIP_MSG_API_CALL:
	...
      break;
    case TCPIP_MSG_INPKT:
	...
      break;
    case TCPIP_MSG_TIMEOUT:
	...
      break;
    case TCPIP_MSG_UNTIMEOUT:
	...
      break;
    case TCPIP_MSG_CALLBACK:
	...
      break;
    case TCPIP_MSG_CALLBACK_STATIC:
	...
      break;
    default:
	...
      break;
  }
}
<think>嗯,用户现在询问的是lwip协议中tcpip_thread函数的调用位置。这个问题其实很有深度,因为tcpip_threadlwIP的绝对核心线程。从之前的对话来看,用户似乎是个嵌入式开发工程师,正在深入调试lwIP的TCP/IP协议栈实现。用户引用了几篇技术博客的内容,但可能没找到直接答案。值得注意的是用户特别提到了端口绑定错误处理(ERR_USE)和IP选项处理机制,说明ta可能在调试具体网络问题时遇到了障碍。这种情况下,定位tcpip_thread的创建位置对理解整个协议栈运行机制确实很重要。我需要重点解释三个方面:首先是系统初始化阶段的操作系统适配层调用,然后是不同操作系统的具体实现差异,最后要提醒这个线程的特殊性——毕竟在嵌入式环境中,错误地操作核心线程会导致整个网络栈崩溃。考虑到用户引用了优快云的技术博客,说明ta可能习惯中文技术资料。在回答时需要特别注意:1)给出具体代码位置(如tcpip.c文件)2)说明RTOS适配层的差异3)强调绝对不能在中断中操作。用户之前已经接触过邮箱机制,可以适当关联sys_mbox_fetch的调用关系。特别要注意的是,用户展示的引用[3]提到netconn接口通过邮箱与tcpip_thread通信,这个细节很重要,应该在回答中体现应用层与核心线程的交互机制。另外引用[2]讨论的IP选项处理暗示用户可能遇到特殊数据包处理问题,需要强调tcpip_thread是所有IP报文的统一入口点。</think> 在 LwIP 协议栈中,`tcpip_thread()` 函数是 TCP/IP 协议栈的核心调度线程,其调用位置位于 **LwIP 初始化过程**中,通常通过操作系统适配层 (sys_arch) 创建线程来实现。具体调用逻辑如下: --- ### 调用位置分析 1. **系统初始化入口** 在 `lwip_init()` 全局初始化函数中,通过 `tcpip_init(tcpip_init_done_cb, arg)` 启动 TCP/IP 线程。 **相关代码**: ```c // lwip/src/api/tcpip.c void tcpip_init(tcpip_init_done_fn initfunc, void *arg) { tcpip_init_done = initfunc; // 保存初始化回调 sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE); // 创建核心邮箱 sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO); // ✅核心调用位置 } ``` 此处通过 **`sys_thread_new`(操作系统适配层函数)** 创建新线程,并将 `tcpip_thread` 作为线程入口函数。 2. **操作系统适配层实现** 在具体的操作系统移植层 (如 FreeRTOS/Linux) 中,通过创建线程或任务来调用 `tcpip_thread()`: ```c // lwip/src/arch/sys_arch.c (FreeRTOS 示例) sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio) { xTaskCreate(thread, name, stacksize, arg, prio, NULL); // FreeRTOS 任务创建 return SYS_THREAD_OK; } ``` 此处 `thread` 参数即 `tcpip_thread` 函数指针。 3. **应用层启动流程** 用户通常在系统初始化时调用 `lwip_init()`: ```c int main(void) { lwip_init(); // 触发tcpip_init() -> 创建tcpip_thread线程 netif_add(&netif, ...); // 添加网卡 netif_set_up(&netif); // 激活网卡 while (1) { /* 应用主循环 */ } } ``` --- ### 关键设计说明 - **核心作用**:`tcpip_thread` 作为单线程事件循环: - 通过 `sys_arch_mbox_fetch()` 阻塞等待邮箱消息(如网络包、API命令)[^1] - 处理定时器超时事件(如TCP重传、ARP老化) - 调用 `tcpip_handle_msg()` 分发消息 - **线程独占性**: 所有协议栈操作(如 `netconn_send()`, `tcp_bind()` 等)均需通过邮箱提交到该线程执行[^3],确保线程安全。 - **移植要求**:操作系统必须实现 `sys_thread_new()`,创建具有足够栈空间的实时线程。 --- ### 调用关系图 ```mermaid flowchart TB A[lwip_init()] --> B[tcpip_init()] B --> C[sys_thread_new()] C --> D[操作系统适配层:创建线程] D -->|启动线程| E[tcpip_thread()] --> F[核心事件循环] ``` --- ### 注意事项 1. **绝对禁止在中断中调用** 所有协议栈API需通过邮箱投递任务,中断中仅允许调用 `tcpip_callbackmsg_new()`。 2. **线程优先级要求** `TCPIP_THREAD_PRIO` 需高于应用线程(通常在FreeRTOS中设置为中高优先级,如 `configMAX_PRIORITIES-2`)。 3. **端口冲突时的处理** 如 `tcp_bind()` 返回 `ERR_USE`(端口被占用),需在 `tcp_close()` 后重置 `pcb->local_port=0`[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值