05-LWIP(内核框架)

目录

1、初始化过程

2、内核超时模块

2.1、内核超时链表数据结构

2.2、内核超时初始化

2.3、超时检查处理

2.4、周期定时实现

3、lwip中的消息

3.1、API消息

3.2、数据包消息

4、tcpip_thread线程

4.1、lwip内核主线程

4.2、等待消息/超时事件函数

4.3、tcpip_thread处理消息


1、初始化过程

协议栈初始化函数 lwip_init() init.c 文件中。(只有在NO_SYS模式下才能直接调用此函数进行初始化)

其初始化的核心组件如下所示:

cpp 复制代码
void
lwip_init(void)
{
  /* Modules initialization */
  stats_init();
#if !NO_SYS
  sys_init();
#endif /* !NO_SYS */
  mem_init();
  memp_init();
  pbuf_init();
  netif_init();
#if LWIP_IPV4
  ip_init();
#if LWIP_ARP
  etharp_init();
#endif /* LWIP_ARP */
#endif /* LWIP_IPV4 */
#if LWIP_RAW
  raw_init();
#endif /* LWIP_RAW */
#if LWIP_UDP
  udp_init();
#endif /* LWIP_UDP */
#if LWIP_TCP
  tcp_init();
#endif /* LWIP_TCP */
#if LWIP_IGMP
  igmp_init();
#endif /* LWIP_IGMP */
#if LWIP_DNS
  dns_init();
#endif /* LWIP_DNS */
#if PPP_SUPPORT
  ppp_init();
#endif

#if LWIP_TIMERS
  sys_timeouts_init();
#endif /* LWIP_TIMERS */
}

如果协议栈移植了操作系统,则必须调用 tcpip_init 对系统进行初始化操作:

cpp 复制代码
void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
  lwip_init();

  tcpip_init_done = initfunc; /* 初始化后的钩子函数 */
  tcpip_init_done_arg = arg; /* 初始化后的钩子函数的参数 */
  /* 创建内核邮箱 */
  if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
    LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
  }
#if LWIP_TCPIP_CORE_LOCKING
  /* 创建内核锁 */
  if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
    LWIP_ASSERT("failed to create lock_tcpip_core", 0);
  }
#endif /* LWIP_TCPIP_CORE_LOCKING */
  /* 创建内核线程 */
  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}

其中除了调用 lwip_init 对内核进行初始化外,还进行了如下操作:

  • 配置初始化后的钩子函数,在新内核线程 tcpip_thread 中跑;
  • 创建一个 tcpip_mbox 邮箱,成员个数为 TCPIP_MBOX_SIZE。主要用于接收从底层或者上层传递过来的消息;
  • 创建一个 lock_tcpip_core 内核锁;
  • 创建一个 tcpip_thread 线程。这个线程就是LwIP在操作系统中作为一个独立的线程运行,所有处理的数据都是这个线程处理;

2、内核超时模块

内核超时框架的源码主要在 timeouts.ctimeouts.h;

2.1、内核超时链表数据结构

内核超时链表:

cpp 复制代码
/** The one and only timeout list */
static struct sys_timeo *next_timeout;

它的数据结构如下:

cpp 复制代码
struct sys_timeo {
  struct sys_timeo *next; /* 下一个节点 */
  u32_t time; /* 被唤醒的时间 */
  sys_timeout_handler h; /* 回调 */
  void *arg; /* 回调的参数 */
#if LWIP_DEBUG_TIMERNAMES
  const char* handler_name; /* 当前超时事件的描述。调试使用 */
#endif /* LWIP_DEBUG_TIMERNAMES */
};

循环定时功能也是通过内核超时模块实现的,循环定时的数据结构如下:

cpp 复制代码
/** This struct contains information about a stack-internal timer function
 that has to be called at a defined interval */
struct lwip_cyclic_timer {
  u32_t interval_ms;    /* 循环定时周期 */
  lwip_cyclic_timer_handler handler;    /* 每周期到期时触发的动作 */
#if LWIP_DEBUG_TIMERNAMES
  const char* handler_name;
#endif 
};

2.2、内核超时初始化

注册超时事件:

注册超时事件使用 sys_timeout(),内部会调用 sys_timeout_abs()把超时事件插入到超时链表 next_timeout 中:

cpp 复制代码
void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
{
  u32_t next_timeout_time;

  next_timeout_time = (u32_t)(sys_now() + msecs); 

  sys_timeout_abs(next_timeout_time, handler, arg);
}

计算得出当前对象下一周期的超时时间,并调用 sys_timeout_abs 函数进行链表插入处理;

超时事件插入超时链表:

超时事件插入超时链表是调用 sys_timeout_abs 函数实现的。具体如下:

cpp 复制代码
static void
sys_timeout_abs(u32_t abs_time, sys_timeout_handler handler, void *arg)
{
  struct sys_timeo *timeout, *t;

  timeout = (struct sys_timeo *)memp_malloc(MEMP_SYS_TIMEOUT);
  if (timeout == NULL) {
    return;
  }

  timeout->next = NULL;
  timeout->h = handler;
  timeout->arg = arg;
  timeout->time = abs_time;

  if (next_timeout == NULL) {
    next_timeout = timeout;
    return;
  }
  if (TIME_LESS_THAN(timeout->time, next_timeout->time)) {
    timeout->next = next_timeout;
    next_timeout = timeout;
  } else {
    for (t = next_timeout; t != NULL; t = t->next) {
      if ((t->next == NULL) || TIME_LESS_THAN(timeout->time, t->next->time)) {
        timeout->next = t->next;
        t->next = timeout;
        break;
      }
    }
  }
}
复制代码
具体流程:
1、从 TIMEOUT 内存池中申请一个超时节点空间,并按传入参数初始化此节点;
2、按顺序插入此节点到 next_timeout 链表中;

注销超时事件:

cpp 复制代码
void
sys_untimeout(sys_timeout_handler handler, void *arg)
{
  struct sys_timeo *prev_t, *t;
  /* 确保在tcpip线程安全锁内 */
  LWIP_ASSERT_CORE_LOCKED();

  if (next_timeout == NULL) {
    return;
  }

  for (t = next_timeout, prev_t = NULL; t != NULL; prev_t = t, t = t->next) {
    if ((t->h == handler) && (t->arg == arg)) {
      /* 在链表中找到该事件 */
      /* 从超时链表中提除 */
      if (prev_t == NULL) {
        next_timeout = t->next;
      } else {
        prev_t->next = t->next;
      }
      /* 释放内存资源 */
      memp_free(MEMP_SYS_TIMEOUT, t);
      return;
    }
  }
  return;
}

将超时节点移出 next_timeout 超时链表,并从 TIMEOUT 内存池中释放此节点的内存资源;

初始化内核超时模块:

cpp 复制代码
/** Initialize this module */
void sys_timeouts_init(void)
{
  size_t i;
  /* tcp_tmr() 不用在初始化时就插入延时链表,因为还没用到 */
  for (i = (LWIP_TCP ? 1 : 0); i < LWIP_ARRAYSIZE(lwip_cyclic_timers); i++) {
    /* 把lwip_cyclic_timers数组保存的回调插入到超时链表中 */
    sys_timeout(lwip_cyclic_timers[i].interval_ms, lwip_cyclic_timer, LWIP_CONST_CAST(void *, &lwip_cyclic_timers[i]));
  }
}

将 lwip_cyclic_timers 数组里的每个类型元素,都插入到 timeout 链表中。其中插入顺序是按照 "当前时间 + 周期 = 到期时间" 来作为插入序号进行插入排列的。

其中 lwip_cyclic_timers 数组内容如下:

cpp 复制代码
/* 这个数组包含所有堆栈内部的循环计时器 */
const struct lwip_cyclic_timer lwip_cyclic_timers[] = {
#if LWIP_TCP
  /* TCP计时器是一种特殊情况:它不必总是运行,初始化内核超时机制时就不会将其插入超时链表。可使用tcp_timer_needed()触发从TCP启动。*/
  {TCP_TMR_INTERVAL, HANDLER(tcp_tmr)},
#endif /* LWIP_TCP */
#if LWIP_IPV4
#if IP_REASSEMBLY
  {IP_TMR_INTERVAL, HANDLER(ip_reass_tmr)},
#endif /* IP_REASSEMBLY */
#if LWIP_ARP
  {ARP_TMR_INTERVAL, HANDLER(etharp_tmr)},
#endif /* LWIP_ARP */
#if LWIP_DHCP
  {DHCP_COARSE_TIMER_MSECS, HANDLER(dhcp_coarse_tmr)},
  {DHCP_FINE_TIMER_MSECS, HANDLER(dhcp_fine_tmr)},
#endif /* LWIP_DHCP */
#if LWIP_ACD
  {ACD_TMR_INTERVAL, HANDLER(acd_tmr)},
#endif /* LWIP_ACD */
#if LWIP_IGMP
  {IGMP_TMR_INTERVAL, HANDLER(igmp_tmr)},
#endif /* LWIP_IGMP */
#endif /* LWIP_IPV4 */
#if LWIP_DNS
  {DNS_TMR_INTERVAL, HANDLER(dns_tmr)},
#endif /* LWIP_DNS */
#if LWIP_IPV6
  {ND6_TMR_INTERVAL, HANDLER(nd6_tmr)},
#if LWIP_IPV6_REASS
  {IP6_REASS_TMR_INTERVAL, HANDLER(ip6_reass_tmr)},
#endif /* LWIP_IPV6_REASS */
#if LWIP_IPV6_MLD
  {MLD6_TMR_INTERVAL, HANDLER(mld6_tmr)},
#endif /* LWIP_IPV6_MLD */
#if LWIP_IPV6_DHCP6
  {DHCP6_TIMER_MSECS, HANDLER(dhcp6_tmr)},
#endif /* LWIP_IPV6_DHCP6 */
#endif /* LWIP_IPV6 */
};
/* lwip_num_cyclic_timers表示lwip_cyclic_timers数组有多少个成员 */
const int lwip_num_cyclic_timers = LWIP_ARRAYSIZE(lwip_cyclic_timers);

2.3、超时检查处理

裸机中:

直接定时调用 sys_check_timeouts() 函数来实现超时检查处理。

cpp 复制代码
void
sys_check_timeouts(void)
{
  u32_t now;
  /* 确保在tcpip线程安全锁内 */
  LWIP_ASSERT_CORE_LOCKED();

  /* 获取当前时间 */
  now = sys_now();

  do { /* 把所有已经超时的事件都处理一遍 */
    struct sys_timeo *tmptimeout;
    sys_timeout_handler handler;
    void *arg;
    /* 检查释放已收到的无序报文的资源,提高资源空闲空间 */
    PBUF_CHECK_FREE_OOSEQ();

    tmptimeout = next_timeout;
    if (tmptimeout == NULL) { /* 没有事件就直接返回 */
      return;
    }

    if (TIME_LESS_THAN(now, tmptimeout->time)) { /* 没有到期事件也不要处理 */
      return;
    }

    /* 处理已超时事件 */
    next_timeout = tmptimeout->next; /* 先从超时链表中移除 */
    handler = tmptimeout->h; /* 获取回调函数 */
    arg = tmptimeout->arg; /* 获取回调函数参数 */
    current_timeout_due_time = tmptimeout->time; /* 获取当前时间 */
#if LWIP_DEBUG_TIMERNAMES
    if (handler != NULL) {
      LWIP_DEBUGF(TIMERS_DEBUG, ("sct calling h=%s t=%"U32_F" arg=%p\n",
                                 tmptimeout->handler_name, sys_now() - tmptimeout->time, arg));
    }
#endif /* LWIP_DEBUG_TIMERNAMES */
    /* 先释放内存资源 */
    memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
    if (handler != NULL) {
      handler(arg); /* 执行回调 */
    }
    LWIP_TCPIP_THREAD_ALIVE(); /* 该宏函数默认为空。其作用是用于类似看门狗的功能 */

  } while (1);
}

该函数中直到处理完所有超时的节点回调,函数才会退出;

系统中:

超时检查处理在 tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg);函数被调用。这个函数会在 tcpip_thread 周期性调用,主要内容是等待 tcpip_mbox消息,是可阻塞的,如果在等待 tcpip_mbox的过程中发生超时事件,则会同时执行超时事件处理。这个函数后面会介绍到。

2.4、周期定时实现

周期定时器机制数据结构:

cpp 复制代码
/* lwip周期定时机制数据结构 */
struct lwip_cyclic_timer {
  u32_t interval_ms; /* 周期值 */
  lwip_cyclic_timer_handler handler; /* 回调函数 */
#if LWIP_DEBUG_TIMERNAMES
  const char* handler_name; /* 周期定时事件文本描述 */
#endif /* LWIP_DEBUG_TIMERNAMES */
};

周期定时器机制基函数:

cpp 复制代码
void lwip_cyclic_timer(void *arg)
{
  u32_t now;
  u32_t next_timeout_time;
  /* 超时事件的回调参数就是周期定时事件 */
  const struct lwip_cyclic_timer *cyclic = (const struct lwip_cyclic_timer *)arg;

#if LWIP_DEBUG_TIMERNAMES
  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: %s()\n", cyclic->handler_name));
#endif
  cyclic->handler(); /* 执行周期定时回调函数 */

  now = sys_now(); /* 获取当前时间 */
  /* 计算下一个唤醒当前周期定时事件的时间,应该从上一个理应唤醒时间开始计 */
  next_timeout_time = (u32_t)(current_timeout_due_time + cyclic->interval_ms);
  if (TIME_LESS_THAN(next_timeout_time, now)) {
    /* 如果当前定时事件的下一个唤醒时间也过期了,那就重新计时,以当前时间为基准 */
#if LWIP_DEBUG_TIMERNAMES
    sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg, cyclic->handler_name);
#else
    /* 重新插入到超时链表,实现周期定时回调 */
    sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg);
#endif

  } else { /* 下一个唤醒时间还没到期 */
#if LWIP_DEBUG_TIMERNAMES
    sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg, cyclic->handler_name);
#else
    /* 重新插入到超时链表,实现周期定时回调 */
    sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg);
#endif
  }
}

1、一旦调用 lwip_cyclic_timer函数, 就证明存在周期性定时事件超时,所以直接执行回调函数;

2、计算下一唤醒当前周期定时器事件时间,从上一理应唤醒时间开始计时;

若当前定时事件的下一个唤醒事件也同时过期了,就重新计时,以当前时间为基准:

复制代码
      current_timeout_due_time 是定时器本应到期的时间。它在 sys_timeout_check 中调用 lwip_cyclic_timer 前调用,如果定时器查询滞时严重,当前定时事件的下个唤醒时间也过期了,就重新将其插入超时链表,并以当前时间为基准,防止超时事件的处理一直处于滞后状态;

若下个唤醒时间没有到期,则证明定时器检查没有滞后,此时就直接将事件插入到超时链表即可;


3、lwip中的消息

lwip消息就是其它线程把业务外包到lwip内核主线程 tcpip_thread()去执行。具体分为:

  • 数据包消息;
  • API消息;

lwip中的消息数据结构:

由于lwip中有多种消息类型,所以数据结构使用联合体。

cpp 复制代码
struct tcpip_msg {
  enum tcpip_msg_type type; /* msg类型 */
  union {
#if !LWIP_TCPIP_CORE_LOCKING
    struct {
      tcpip_callback_fn function; /* 需要内核执行的API */
      void* msg; /* API的参数指针,内含多个参数 */
    } api_msg; /* TCPIP_MSG_API。API消息 */
    struct {
      tcpip_api_call_fn function;  /* 需要内核执行的API */
      struct tcpip_api_call_data *arg;
      sys_sem_t *sem;
    } api_call; /* TCPIP_MSG_API_CALL。API回传消息 */
#endif /* LWIP_TCPIP_CORE_LOCKING */
#if !LWIP_TCPIP_CORE_LOCKING_INPUT
    struct {
      struct pbuf *p; /* 收到的数据包 */
      struct netif *netif; /* 来自哪个网卡的 */
      netif_input_fn input_fn; /* 需要传入哪个内核函数处理 */
    } inp; /* TCPIP_MSG_INPKT。网络数据包消息 */
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
    struct {
      tcpip_callback_fn function;
      void *ctx;
    } cb; /* TCPIP_MSG_CALLBACK、TCPIP_MSG_CALLBACK_STATIC。回调消息 */
#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
    struct {
      u32_t msecs;
      sys_timeout_handler h;
      void *arg;
    } tmo; /* TCPIP_MSG_TIMEOUT、TCPIP_MSG_UNTIMEOUT。注册注销超时消息 */
#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  } msg;
};

其中消息类型有:

cpp 复制代码
enum tcpip_msg_type {
#if !LWIP_TCPIP_CORE_LOCKING
  TCPIP_MSG_API, /* API消息类型。如用户调用应用层的接口时,需要tcpip_thread执行内核函数就用这个消息类型。需要往南供给协议栈。 */
  TCPIP_MSG_API_CALL, /* API回传消息类型。比如用户调用应用层有回传的接口时,就用这个消息类型。需要往南供给协议栈。 */
#endif /* !LWIP_TCPIP_CORE_LOCKING */
#if !LWIP_TCPIP_CORE_LOCKING_INPUT
  TCPIP_MSG_INPKT, /* 网络数据包消息类型。即是网卡上收到的数据。需要往北供给协议栈。 */
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
  TCPIP_MSG_TIMEOUT, /* 注册超时定时器消息类型。 */
  TCPIP_MSG_UNTIMEOUT, /* 注销超时定时器消息类型。 */
#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  TCPIP_MSG_CALLBACK, /* 回调消息类型。就是让tcpip_thread这个线程帮忙执行这个回调函数,这个回调函数就能肆无忌惮地访问内核函数了。 */
  TCPIP_MSG_CALLBACK_STATIC /* 静态的回调消息类型。 */
};

3.1、API消息

API消息是由用户线程发出,与内核进行交互。用户调用应用层接口,需要 tcpip_thread线程执行时,通过API消息告知 tcpip_thread线程执行。

API消息的数据结构如下所示:

struct api_msg 包含3个字段:

  1. 描述连接信息的struct netconn *conn;
  2. 内核执行的结果err_t err;
  3. API接口数据结构union msg;(``调用不同的API会使用不同的数据结构``)
cpp 复制代码
/* IP addresses and port numbers are expected to be in the same byte order as in the corresponding pcb. */
/* 这个数据结构包含了在另一个线程上下文中执行netconn函数所需的所有内容(主要用于处理tcpip_thread上下文中的netconn以确保线程安全)。 */
struct api_msg {
  /* 当前需要执行的API对应的连接 */
  struct netconn *conn;
  /* 在tcpip_thread中执行的函数的返回值 */
  err_t err;
  /* 用户调用不同的API,会使用不同的数据结构 */
  union {
    /* used for lwip_netconn_do_send */
    struct netbuf *b;
    /** used for lwip_netconn_do_newconn */
    struct {
      u8_t proto;
    } n;
    /** used for lwip_netconn_do_bind and lwip_netconn_do_connect */
    struct {
      API_MSG_M_DEF_C(ip_addr_t, ipaddr);
      u16_t port;
      u8_t if_idx;
    } bc;
    /** used for lwip_netconn_do_getaddr */
    struct {
      ip_addr_t API_MSG_M_DEF(ipaddr);
      u16_t API_MSG_M_DEF(port);
      u8_t local;
    } ad;
    /** used for lwip_netconn_do_write */
    struct {
      /** current vector to write */
      const struct netvector *vector;
      /** number of unwritten vectors */
      u16_t vector_cnt;
      /** offset into current vector */
      size_t vector_off;
      /** total length across vectors */
      size_t len;
      /** offset into total length/output of bytes written when err == ERR_OK */
      size_t offset;
      u8_t apiflags;
#if LWIP_SO_SNDTIMEO
      u32_t time_started;
#endif /* LWIP_SO_SNDTIMEO */
    } w;
    /* used for lwip_netconn_do_recv */
    struct {
      size_t len;
    } r;
#if LWIP_TCP
    /* used for lwip_netconn_do_close (/shutdown) */
    struct {
      u8_t shut;
#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER
      u32_t time_started;
#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
      u8_t polls_left;
#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
    } sd;
#endif /* LWIP_TCP */
#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
    /* used for lwip_netconn_do_join_leave_group */
    struct {
      API_MSG_M_DEF_C(ip_addr_t, multiaddr);
      API_MSG_M_DEF_C(ip_addr_t, netif_addr);
      u8_t if_idx;
      enum netconn_igmp join_or_leave;
    } jl;
#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
#if TCP_LISTEN_BACKLOG
    struct {
      u8_t backlog;
    } lb;
#endif /* TCP_LISTEN_BACKLOG */
  } msg;
#if LWIP_NETCONN_SEM_PER_THREAD
  sys_sem_t* op_completed_sem;
#endif /* LWIP_NETCONN_SEM_PER_THREAD */
};

3.2、数据包消息

消息类型为 TCPIP_MSG_INPKT

数据包消息是底层网卡接收到数据后需要交给协议栈处理时,需要构造的消息。

其在 tcpip_inpkt()函数中构造。

主要将收到的数据包传递到 tcpip_thread线程执行。并告知要传入哪个内核函数处理。

cpp 复制代码
/**
 * 将接收到的数据包打包给tcpip_thread执行
 */
err_t
tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
{
#if LWIP_TCPIP_CORE_LOCKING_INPUT /* 开放了tcpip内核锁给input业务处理,就不需要外包到tcpip_thread处理 */
  err_t ret;
  LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_inpkt: PACKET %p/%p\n", (void *)p, (void *)inp));
  LOCK_TCPIP_CORE(); /* 获取tcpip内核锁 */
  ret = input_fn(p, inp); /* 直接在本线程处理收到的数据包 */
  UNLOCK_TCPIP_CORE(); /* 释放tcpip内核锁 */
  return ret;
#else /* 如果没有开放tcpip内核锁给input业务处理,就需要外包到tcpip_thread处理 */
  struct tcpip_msg *msg;

  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(tcpip_mbox));
  /* 申请数据包消息资源 */
  msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
  if (msg == NULL) {
    return ERR_MEM;
  }
  /* 配置消息 */
  msg->type = TCPIP_MSG_INPKT; /* 数据包消息类型 */
  msg->msg.inp.p = p;
  msg->msg.inp.netif = inp;
  msg->msg.inp.input_fn = input_fn;
  if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK) { /* 投递消息,非阻塞 */
    memp_free(MEMP_TCPIP_MSG_INPKT, msg); /* 投递失败,释放资源 */
    return ERR_MEM;
  }
  return ERR_OK;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
}

tcpip_inpkt 分为两种实现:(下面指的 内核锁 其实是一个 mutex 互斥锁)

  1. 没有 tcpip 内核锁权限,就需要把 input 的业务通过消息转交给 tcpip_thread处理;
  2. 如果有 tcpip 内核锁权限,那就获取 tcpip 内核锁,在本线程处理即可;(其中 sys_mbox_trypost 函数是一个 "消息队列" ,如果队列没满发送消息进去就是 ERR_OK,反之则反)

lwip数据包收包流图:


4、tcpip_thread线程

LwIP内核是作为操作系统的一个线程运行的,在协议栈初始化的时候就会创建 tcpip_thread线程。

该线程主要处理超时检查和接收各种消息进行处理。

tcpip内核锁是维护tcpip内核函数的原子性。

tcpip_thread内核线程和其它线程对内核tcpip的函数存在竞争关系。

4.1、lwip内核主线程

lwIP主线程。(这个线程独占地访问lwIP核心函数)

其他线程使用消息队列与该线程通信。

该线程还启动了所有的计时器,以确保它们在正确的线程上下文中运行。

cpp 复制代码
static void
tcpip_thread(void *arg)
{
  struct tcpip_msg *msg;
  LWIP_UNUSED_ARG(arg);

  /* tcpip内核上锁 */
  LOCK_TCPIP_CORE();
  if (tcpip_init_done != NULL) {
    /* 执行用户插入的lwip内核初始化钩子函数 */
    tcpip_init_done(tcpip_init_done_arg);
  }

  while (1) {                          /* MAIN Loop */
    LWIP_TCPIP_THREAD_ALIVE(); /* 默认为空。这个可用于类似看门狗功能 */
    /* 等待消息或超时事件 */
    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);
  }
}

4.2、等待消息/超时事件函数

cpp 复制代码
#if !LWIP_TIMERS
/* wait for a message with timers disabled (e.g. pass a timer-check trigger into tcpip_thread) */
#define TCPIP_MBOX_FETCH(mbox, msg) sys_mbox_fetch(mbox, msg)
#else /* !LWIP_TIMERS */
/* wait for a message, timeouts are processed while waiting */
#define TCPIP_MBOX_FETCH(mbox, msg) tcpip_timeouts_mbox_fetch(mbox, msg)

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

1、调用 sys_timeouts_sleeptime 获取下一次超时时间差;

若没有任何超时事件:

只调用 sys_arch_mbox_fetch 函数进行消息接收,此时可以无限等待邮箱消息;(所以这之前要解锁,不然会导致任务无法切换)

存在超时节点超时:

调用 sys_check_timeouts 函数进行超时回调处理;

存在超时节点,但没有超时:

等待邮箱,但最多等待 sleeptime 时间。等待超时,就执行超时回调,因为此时下个事件已经超时了;

4.3、tcpip_thread处理消息

其它线程需要发消息到 tcpip_thread()线程来处理内核操作。

如果开启了 tcpip 内核锁,客户端对lwip的操作也可以不用外包 tcpip_thread()线程处理。

cpp 复制代码
/* 处理单个tcpip_msg
 * This is in its own function for access by tests only.
 */
static void
tcpip_thread_handle_msg(struct tcpip_msg *msg)
{
  switch (msg->type) {
#if !LWIP_TCPIP_CORE_LOCKING
    /* 没有开启tcpip内核锁,其它线程对lwip的操作需要外包到tcpip_thread线程操作 */
    case TCPIP_MSG_API:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      msg->msg.api_msg.function(msg->msg.api_msg.msg); /* 执行API */
      break;
    case TCPIP_MSG_API_CALL:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API CALL message %p\n", (void *)msg));
      msg->msg.api_call.arg->err = msg->msg.api_call.function(msg->msg.api_call.arg); /* 执行API,并返回结果码 */
      sys_sem_signal(msg->msg.api_call.sem); /* 释放信号量,告知用户线程执行API完毕 */
      break;
    case TCPIP_MSG_CALLBACK_STATIC_WAIT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK WAIT message %p\n", (void *)msg));
      msg->msg.cb_wait.function(msg->msg.cb_wait.ctx); /* 执行回调 */
      sys_sem_signal(msg->msg.cb_wait.sem); /* 释放信号量,告知插入回调的线程回调执行完毕 */
      break;
#endif /* !LWIP_TCPIP_CORE_LOCKING */

#if !LWIP_TCPIP_CORE_LOCKING_INPUT
    /* 没有开启LWIP_TCPIP_CORE_LOCKING_INPUT,所以tcpip_input()没有权限好的tcpip内核锁,只能外包回tcpip_tread线程处理 */
    case TCPIP_MSG_INPKT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
      if (msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif) != ERR_OK) { /* 处理收到的数据包 */
        pbuf_free(msg->msg.inp.p); /* 处理完毕后释放这个pbuf的资源 */
      }
      memp_free(MEMP_TCPIP_MSG_INPKT, msg); /* 释放消息资源 */
      break;
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */

#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
    /* 支持在tcpip_thread执行计时器功能 */
    case TCPIP_MSG_TIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
      sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg); /* 注册超时事件 */
      memp_free(MEMP_TCPIP_MSG_API, msg); /* 释放消息资源 */
      break;
    case TCPIP_MSG_UNTIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
      sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg); /* 注销超时事件 */
      memp_free(MEMP_TCPIP_MSG_API, msg); /* 释放消息资源 */
      break;
#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */

    case TCPIP_MSG_CALLBACK:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
      msg->msg.cb.function(msg->msg.cb.ctx); /* 执行回调 */
      memp_free(MEMP_TCPIP_MSG_API, msg); /* 释放消息资源 */
      break;

    case TCPIP_MSG_CALLBACK_STATIC:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
      msg->msg.cb.function(msg->msg.cb.ctx); /* 执行回调。静态,不需要释放消息资源 */
      break;

    default:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
      LWIP_ASSERT("tcpip_thread: invalid message", 0);
      break;
  }
}
相关推荐
星马梦缘3 小时前
数据库 第十三章 未完结版本
java·网络·数据库
co_wait3 小时前
不同VLAN间通信方法和配置
网络·智能路由器
非凡大爹3 小时前
实验十一 华为路由器和交换机实现单区域 OSPF 动态路由协议配置实验指导书
网络·华为
鼎讯信通3 小时前
从“盲挖”到“精准敲击”:能源管网维护的一种新思路
大数据·网络·能源
风风筝吖3 小时前
【华为】vlan+NAT(Easy-IP 与 NAT Server)+ OSPF 动态路由综合配置
网络·智能路由器
酉鬼女又兒3 小时前
零基础入门虚拟局域网VLAN:从广播域问题根源到802.1q帧格式、三大端口类型及实战例题全解析
网络·网络协议·计算机网络·网络安全·职场和发展·智能路由器·求职招聘
Promise微笑3 小时前
洞察无形:红外热像仪行业标准解析与深度选型指南
网络·人工智能·算法
va学弟3 小时前
Java 网络通信编程(9):从 BIO 到 NIO
java·运维·服务器·网络
rcms152702692183 小时前
Matrox Genesis 63039620241采集卡
网络