本文由RT-Thread论坛用户@出出啊原创发布:https://club.rt-thread.org/as...
前言
很久之前就开始整理下面的优化项列表了,但是有很多问题研究不深,一时不敢冒失推出。
前不久,有人在论坛上提问,当时我给的答案比现在少,但是现在列出来的这些也不能保证是全部,以后再做补充吧。
lwip 协议栈、sal socket 抽象层使用了很多全局数组变量当作线程栈,可以修改成从内存堆动态申请的内存。
有些功能和特性在嵌入式设备里是用不到的,可以先去掉。
还有的是可有可无的特性,如果想用,也存在优化空间,可以自己实现。
以下说明不限于 lwip ,sal 部分也有涉及。
裁剪详解
sal 可裁剪优化项
SAL_INTERNET_CHECK
: 网络检测,使用到了 workqueue 。检测原理就是尝试连接 "link.rt-thread.org::8101",发送检测数据。
这个或者可以去掉检测,或者换成自家服务器。#define SAL_SOCKETS_NUM 4
: 这个可能是支持创建 socket 的最大数量。RT_USING_NETDEV
: 网络接口设备,没有终端操作的情况下可以优化掉。其中,NETDEV_USING_IFCONFIG
NETDEV_USING_PING
NETDEV_USING_NETSTAT
NETDEV_USING_AUTO_DEFAULT
分别可以单独增删。NETDEV_IPV6
: 目前支持还不普及的吧,可以关掉,如果需要才开启。
lwip 可裁剪优化项
RT_LWIP_IGMP
组播需要用到的,不用组播可能可以去掉RT_LWIP_ICMP
ping 命令使用的协议,没有 ping 也不需要这个协议。RT_LWIP_DNS
局域网不需要这个,或者说,直接使用 ip 地址进行连接而不是使用 url 链接地址,可以不使用 dns。RT_LWIP_TCP_WND
tcp 接收窗口,这个应该是申请内存大小。可以适当减小。不定义就是 1460 x 2 字节RT_LWIP_TCP_SND_BUF
tcp 发送缓存,同上,不定义就是 1460 x 2 字节LWIP_NO_TX_THREAD
和LWIP_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
源码里,这部分还有很大优化空间,具体见下文详解。
LWIP_NETIF_STATUS_CALLBACK
和前边的 SAL_INTERNET_CHECK 有关,这里设置网络连接回调。可以通知应用层连接上 INTERNET 了。LWIP_NETIF_LINK_CALLBACK
网卡连接状态,仅表示物理连接接入网络,有可能是和电脑直连,或者交换机、路由器等等。SO_REUSE
端口复用,这个在组播,而且是 UDP 协议才有用。不需要就定义成 0LWIP_SO_SNDTIMEO
LWIP_SO_RCVTIMEO
LWIP_SO_RCVBUF
这三个,如果 rtconf.h 里没有定义, lwipopts.h 会定义,所以不需要就定义成 0。
其中 LWIP_SO_RCVBUF 接收缓冲,涉及到接收缓冲上限。多数情况下不会有影响,只有网络数据多的时候才可能达到这个缓存上限。RT_LWIP_USING_PING
这个和前面的 NETDEV_USING_PING RT_LWIP_ICMP 有关。RT_LWIP_STATS
这是一组 stat 的总开关,详细细节查看 lwipopts.h 文件内的定义。或者取消 RT_LWIP_STATS 定义,关闭所有 stat 项,或者单独修改 lwipopts.h 文件中某些 stat 定义。- 修改 eth_rx_thread 和 eth_tx_thread ,启用 RT_USING_HEAP 后,添加动态创建线程。这两个线程被初始化在 INIT_PREV_EXPORT 阶段。片上内存堆和片外内地堆初始化注册都在 INIT_BOARD_EXPORT 阶段,可以申请使用动态内存。
erx etx 两个线程
以 etx 为例。ethernetif_linkoutput
函数主要操作如下:
if (rt_mb_send(ð_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(ð_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;
}
与使用 etx 线程唯一不同的是:使用线程时,发送数据操作 eth 驱动都在 etx 线程里进行的;如果去掉,就有可能多个应用线程同时发送数据,出现多个线程竞争 eth 驱动资源的现象。但是,这个可以经过优化应用层业务逻辑进行规避。
更多关于不使用 etx 和 erx 线程的修改,请移步我的 gitee 仓库。
本系列提到的所有代码更改已经提交到 gitee ,欢迎大家测试
https://gitee.com/thewon/rt_t...