记LWIP调试http server的Out of memory问题

本文记录了作者在使用LWIP HTTP Server进行IOT控制时遇到的Out of memory问题及其解决过程。通过分析LWIP源码,作者发现了问题出在TCP连接的内存管理和POLL函数的配置上,特别是`tcp_poll`的轮询间隔和最大重试次数设置,这两个参数直接影响页面显示和内存使用。最后,作者分享了对嵌入式平台通用dump_stack工具的想法,并提供了部分源码链接。
摘要由CSDN通过智能技术生成

最近在做IOT控制,主要通过LWIP的http server来做控制,实现手机和电脑浏览器控制查看数据,其中用web server做实时的数据传输,遇到了切换网页是有时会卡在跳转处很久,有时会直接跳转失败,只能重新进入web,于是打开LWIP的调试发现卡死时输出Out of memory的输出,于是我就重新过了变LWIP,看看是什么原因导致的。
下面我们来看LWIP的HTTPD的源码。

void
httpd_init(void)
{
#if HTTPD_USE_MEM_POOL
  LWIP_ASSERT("memp_sizes[MEMP_HTTPD_STATE] >= sizeof(http_state)",
     memp_sizes[MEMP_HTTPD_STATE] >= sizeof(http_state));
  LWIP_ASSERT("memp_sizes[MEMP_HTTPD_SSI_STATE] >= sizeof(http_ssi_state)",
     memp_sizes[MEMP_HTTPD_SSI_STATE] >= sizeof(http_ssi_state));
#endif
  LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n"));

  httpd_init_addr(IP_ADDR_ANY);
}

先判断是否使用内存池分配内存,在进行初始化,本周就是开启一个TCP连接,和SOCKET建立的TCP类似,

static void
httpd_init_addr(ip_addr_t *local_addr)
{
  struct tcp_pcb *pcb;
  err_t err;

  pcb = tcp_new();
  LWIP_ASSERT("httpd_init: tcp_new failed", pcb != NULL);
  tcp_setprio(pcb, HTTPD_TCP_PRIO);
  /* set SOF_REUSEADDR here to explicitly bind httpd to multiple interfaces */
  err = tcp_bind(pcb, local_addr, HTTPD_SERVER_PORT);
  LWIP_ASSERT("httpd_init: tcp_bind failed", err == ERR_OK);
  pcb = tcp_listen(pcb);
  LWIP_ASSERT("httpd_init: tcp_listen failed", pcb != NULL);
  /* initialize callback arg and accept callback */
  tcp_arg(pcb, pcb);
  tcp_accept(pcb, http_accept);
}

这里面做TCP的建立,写个socket的应该都比较熟悉,先bind、在listen、然后accept,只是这里是accept回调函数,在LWIP里面SOCKET也是用回调函数实现的,在linux里面是使用system call来实现socket调用的,我在看下accept在什么时候被回调执行。

#define TCP_EVENT_ACCEPT(pcb,err,ret)                          \
  do {                                                         \
    if((pcb)->accept != NULL)                                  \
      (ret) = (pcb)->accept((pcb)->callback_arg,(pcb),(err));  \
    else (ret) = ERR_ARG;                                      \
  } while (0)

在TCP_imple.h里面实现了多种event事件,用于实现回调,看下那个代码调用了accept事件。

static err_t
tcp_process(struct tcp_pcb *pcb)
{
  struct tcp_seg *rseg;
  u8_t acceptable = 0;
  err_t err;

  err = ERR_OK;

  XXX
#if LWIP_CALLBACK_API
        LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL);
#endif
        /* Call the accept function. */
        TCP_EVENT_ACCEPT(pcb, ERR_OK, err);
  XXX
  return ERR_OK;
}

在这个process函数里面其实做了很多事情,不止调用了accept函数,还有很多连接等信息,基本所有的TCP包都要经过他的处理,里面用状态机写的,大家可以看下源码,所以这个函数肯定是被TCP接口函数调用的。

void
tcp_input(struct pbuf *p, struct netif *inp){
    /* If there is data which was previously "refused" by upper layer */
    if (pcb->refused_data != NULL) {
      if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
        ((pcb->refused_data != NULL) && (tcplen > 0))) {
        /* pcb has been aborted or refused data is still refused and the new
           segment contains data */
        TCP_STATS_INC(tcp.drop);
        snmp_inc_tcpinerrs();
        goto aborted;
      }
    }
    tcp_input_pcb = pcb;
    err = tcp_process(pcb);
   }

这个函数也异常的长,主要是对tcp的各种包做解析,在做处理,比如是不是broadcast的包等等,

err_t
ip_input(struct pbuf *p, struct netif *inp){
#endif /* LWIP_UDP */
#if LWIP_TCP
    case IP_PROTO_TCP:
      snmp_inc_ipindelivers();
      tcp_input(p, inp);
      break;
#endif /* LWIP_TCP */
}

TCP处理肯定是经过ip包传上来的,所以ip_put就是做这个操作的,里面也是用状态机写的,包括了各种类型的包解析,比如udp,icmp等等,ip包要经过arp解析后再传输的,这个是另个内容了,我这边是使用freertos,所以ip包经过一个线程来实时接收。

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

  if (tcpip_init_done != NULL) {
    tcpip_init_done(tcpip_init_done_arg);
  }

  LOCK_TCPIP_CORE();
  while (1) {                          /* MAIN Loop */
    UNLOCK_TCPIP_CORE();
    LWIP_TCPIP_THREAD_ALIVE();
    /* wait for a message, timeouts are processed while waiting */
    sys_timeouts_mbox_fetch(&mbox, (void **)&msg);
    LOCK_TCPIP_CORE();
    switch (msg->type) {
#if LWIP_NETCONN
    case TCPIP_MSG_API:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
      break;
#endif /* LWIP_NETCONN */

#if !LWIP_TCPIP_CORE_LOCKING_INPUT
    case TCPIP_MSG_INPKT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));

      if(msg->msg.inp.p != NULL && msg->msg.inp.netif != NULL) {
#if LWIP_ETHERNET
        if (msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
          ethernet_input(msg->msg.inp.p, msg->msg.inp
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值