rt-thread|rt-thread 裁剪系列(一) 之 lwip

本文由RT-Thread论坛用户@出出啊原创发布:https://club.rt-thread.org/as...
前言 很久之前就开始整理下面的优化项列表了,但是有很多问题研究不深,一时不敢冒失推出。
前不久,有人在论坛上提问,当时我给的答案比现在少,但是现在列出来的这些也不能保证是全部,以后再做补充吧。
lwip 协议栈、sal socket 抽象层使用了很多全局数组变量当作线程栈,可以修改成从内存堆动态申请的内存。
有些功能和特性在嵌入式设备里是用不到的,可以先去掉。
还有的是可有可无的特性,如果想用,也存在优化空间,可以自己实现。
以下说明不限于 lwip ,sal 部分也有涉及。
裁剪详解
sal 可裁剪优化项

  1. SAL_INTERNET_CHECK: 网络检测,使用到了 workqueue 。检测原理就是尝试连接 "link.rt-thread.org::8101",发送检测数据。
    这个或者可以去掉检测,或者换成自家服务器。
  2. #define SAL_SOCKETS_NUM 4: 这个可能是支持创建 socket 的最大数量。
  3. RT_USING_NETDEV: 网络接口设备,没有终端操作的情况下可以优化掉。其中,NETDEV_USING_IFCONFIG NETDEV_USING_PING NETDEV_USING_NETSTAT NETDEV_USING_AUTO_DEFAULT 分别可以单独增删。
  4. NETDEV_IPV6: 目前支持还不普及的吧,可以关掉,如果需要才开启。
lwip 可裁剪优化项
  1. RT_LWIP_IGMP 组播需要用到的,不用组播可能可以去掉
  2. RT_LWIP_ICMP ping 命令使用的协议,没有 ping 也不需要这个协议。
  3. RT_LWIP_DNS 局域网不需要这个,或者说,直接使用 ip 地址进行连接而不是使用 url 链接地址,可以不使用 dns。
  4. RT_LWIP_TCP_WND tcp 接收窗口,这个应该是申请内存大小。可以适当减小。不定义就是 1460 x 2 字节
  5. RT_LWIP_TCP_SND_BUF tcp 发送缓存,同上,不定义就是 1460 x 2 字节
  6. LWIP_NO_TX_THREADLWIP_NO_RX_THREAD eth 线程,发送一个,接收一个。以下是几个相关宏定义,如果不定义堆栈大小,默认使用 1024
    #define RT_LWIP_ETHTHREAD_PRIORITY 12 #define RT_LWIP_ETHTHREAD_STACKSIZE 1024 #define RT_LWIP_ETHTHREAD_MBOX_SIZE 8 #define LWIP_NO_TX_THREAD #define LWIP_NO_RX_THREAD

    源码里,这部分还有很大优化空间,具体见下文详解。
  7. LWIP_NETIF_STATUS_CALLBACK 和前边的 SAL_INTERNET_CHECK 有关,这里设置网络连接回调。可以通知应用层连接上 INTERNET 了。
  8. LWIP_NETIF_LINK_CALLBACK 网卡连接状态,仅表示物理连接接入网络,有可能是和电脑直连,或者交换机、路由器等等。
  9. SO_REUSE 端口复用,这个在组播,而且是 UDP 协议才有用。不需要就定义成 0
  10. LWIP_SO_SNDTIMEO LWIP_SO_RCVTIMEO LWIP_SO_RCVBUF 这三个,如果 rtconf.h 里没有定义, lwipopts.h 会定义,所以不需要就定义成 0。
    其中 LWIP_SO_RCVBUF 接收缓冲,涉及到接收缓冲上限。多数情况下不会有影响,只有网络数据多的时候才可能达到这个缓存上限。
  11. RT_LWIP_USING_PING 这个和前面的 NETDEV_USING_PING RT_LWIP_ICMP 有关。
  12. RT_LWIP_STATS 这是一组 stat 的总开关,详细细节查看 lwipopts.h 文件内的定义。或者取消 RT_LWIP_STATS 定义,关闭所有 stat 项,或者单独修改 lwipopts.h 文件中某些 stat 定义。
  13. 修改 eth_rx_thread 和 eth_tx_thread ,启用 RT_USING_HEAP 后,添加动态创建线程。这两个线程被初始化在 INIT_PREV_EXPORT 阶段。片上内存堆和片外内地堆初始化注册都在 INIT_BOARD_EXPORT 阶段,可以申请使用动态内存。
erx etx 两个线程 以 etx 为例。ethernetif_linkoutput 函数主要操作如下:
if (rt_mb_send(e_tx_thread_mb, (rt_uint32_t) &msg) == RT_EOK) { /* waiting for ack */ rt_sem_take(&(enetif->tx_ack), RT_WAITING_FOREVER); }

发送了一个邮箱,然后等待一个信号量。这个信号量从哪儿来?看下面的 etx 线程入口函数。
static void eth_tx_thread_entry(void* parameter) { struct eth_tx_msg* msg; while (1) { if (rt_mb_recv(e_tx_thread_mb, (rt_ubase_t *)&msg, RT_WAITING_FOREVER) == RT_EOK) { struct eth_device* enetif; RT_ASSERT(msg->netif != RT_NULL); RT_ASSERT(msg->buf!= RT_NULL); enetif = (struct eth_device*)msg->netif->state; if (enetif != RT_NULL) { /* call driver's interface */ if (enetif->eth_tx(&(enetif->parent), msg->buf) != RT_EOK) { /* transmit eth packet failed */ } }/* send ACK */ rt_sem_release(&(enetif->tx_ack)); } } }

etx 等待 ethernetif_linkoutput 的邮件消息,然后调用 eth 驱动接口函数,完成后释放信号量给 ethernetif_linkoutput一个应答。
从这里看,用上这个线程,需要额外增加两次 ipc 消息。
去掉 etx 之后呢?ethernetif_linkoutput 变成下面的样子。
static err_t ethernetif_linkoutput(struct netif *netif, struct pbuf *p) { struct eth_device* enetif; RT_ASSERT(netif != RT_NULL); enetif = (struct eth_device*)netif->state; if (enetif->eth_tx(&(enetif->parent), p) != RT_EOK) { return ERR_IF; } return ERR_OK; }

【rt-thread|rt-thread 裁剪系列(一) 之 lwip】与使用 etx 线程唯一不同的是:使用线程时,发送数据操作 eth 驱动都在 etx 线程里进行的;如果去掉,就有可能多个应用线程同时发送数据,出现多个线程竞争 eth 驱动资源的现象。但是,这个可以经过优化应用层业务逻辑进行规避。
更多关于不使用 etx 和 erx 线程的修改,请移步我的 gitee 仓库。
本系列提到的所有代码更改已经提交到 gitee ,欢迎大家测试
https://gitee.com/thewon/rt_t...

    推荐阅读