RTT小师弟 · 2021年09月02日

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(ð_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...
推荐阅读
关注数
8074
内容数
181
小而美的物联网操作系统,经过14年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过4亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息