基于RT-Thread的lwip网卡优化笔记

news2024/11/18 22:25:24

一、RT-Thread的lwip框架

RT-Thread的网络框架主要是实现eth_device,并通过eth_device_init注册,下面是eth_device的定义。

struct eth_device
{
    /* inherit from rt_device */
    struct rt_device parent;

    /* network interface for lwip */
    struct netif *netif;
    struct rt_semaphore tx_ack;

    rt_uint16_t flags;
    rt_uint8_t  link_changed;
    rt_uint8_t  link_status;

    /* eth device interface */
    struct pbuf* (*eth_rx)(rt_device_t dev);
    rt_err_t (*eth_tx)(rt_device_t dev, struct pbuf* p);
};

网卡移植主要是要在驱动层实现eth_rx和eth_tx。eth_rx即网卡接收,eth_tx即网卡发送。一般来说,网卡驱动需要结合dma来使用,例如当需要发送数据时,lwip最终会调用到eth_tx函数指针进行发送,lwip将要发送的数据放到了pbuf中,我们需要从pbuf从读取数据放入网卡发送DMA中,启动发送;当网卡收到数据时,emac将产生中断,在中断里会唤醒接收线程,这个接收线程会调用eth_rx函数指针接收数据,我们需要申请pbuf,并将网卡DMA中的数据拷贝到pbuf中,最后给lwip返回这个pbuf。

lwip的ethernetif.c是一个供移植的文件,在RT-Thread上,RT-Thread已经全都定义好了,从下面的代码可以看出,eth_system_device_init_private函数创建了两个线程:erx和etx。一般来说,LWIP_NO_RX_THREAD宏都是没定义的,也就是erxmb信号量和erx线程会被创建,当emac接收数据并产生中断时,会调用eth_device_ready函数发送erxmb信号量唤醒erx接收线程,这个线程再去调用eth_rx接收函数。LWIP_NO_TX_THREAD宏可以定义也可以不定义,当定义这个宏时,发送数据时将会直接在lwip的tcpip线程调用eth_tx发送函数,当未定义这个宏时,发送的数据将会由tcpip线程通过邮箱的形式传给etx线程,由etx线程发出。

int eth_system_device_init_private(void)
{
    rt_err_t result = RT_EOK;

    /* initialize Rx thread. */
#ifndef LWIP_NO_RX_THREAD
    /* initialize mailbox and create Ethernet Rx thread */
    result = rt_mb_init(&eth_rx_thread_mb, "erxmb",
                        &eth_rx_thread_mb_pool[0], sizeof(eth_rx_thread_mb_pool)/4,
                        RT_IPC_FLAG_FIFO);
    RT_ASSERT(result == RT_EOK);

    result = rt_thread_init(&eth_rx_thread, "erx", eth_rx_thread_entry, RT_NULL,
                            &eth_rx_thread_stack[0], sizeof(eth_rx_thread_stack),
                            RT_ETHERNETIF_THREAD_PREORITY, 16);
    RT_ASSERT(result == RT_EOK);
    result = rt_thread_startup(&eth_rx_thread);
    RT_ASSERT(result == RT_EOK);
#endif

    /* initialize Tx thread */
#ifndef LWIP_NO_TX_THREAD
    /* initialize mailbox and create Ethernet Tx thread */
    result = rt_mb_init(&eth_tx_thread_mb, "etxmb",
                        &eth_tx_thread_mb_pool[0], sizeof(eth_tx_thread_mb_pool)/4,
                        RT_IPC_FLAG_FIFO);
    RT_ASSERT(result == RT_EOK);

    result = rt_thread_init(&eth_tx_thread, "etx", eth_tx_thread_entry, RT_NULL,
                            &eth_tx_thread_stack[0], sizeof(eth_tx_thread_stack),
                            RT_ETHERNETIF_THREAD_PREORITY, 16);
    RT_ASSERT(result == RT_EOK);

    result = rt_thread_startup(&eth_tx_thread);
    RT_ASSERT(result == RT_EOK);
#endif

    return (int)result;
}

下面的代码是注册lwip网卡驱动的过程。

	struct emac_device *edev = &g_emac_device[EMAC0_100M];	

	lwip_sys_init();

    edev->module = EMAC0_100M;
    edev->irq = VC0768_IRQ_EMAC0_DMA;
    edev->mac_base = VC0768_EMAC0_BASE;
    edev->dma_base = VC0768_EMAC0_BASE + REG_EMAC_DMA_OFFSET;
    edev->phy_type = PHY_TYPE_INT;
    edev->phy_addr = DEFAULT_INT_PHY_ADDR;
    edev->speed = SPEED100M;
    edev->duplex = FULLDUPLEX;
    edev->auto_nego = true;
    sprintf(edev->name, "%s", EMAC0_NAME);

    rt_memcpy(edev->dev_addr, mac_addr, sizeof(mac_addr));

    edev->tx_buf = (u32 *)rt_malloc_align(EMAC_TX_BUF_SIZE * TRANSMIT_DESC_SIZE, RT_CPU_CACHE_LINE_SZ);
    edev->rx_buf = (u32 *)rt_malloc_align(EMAC_RX_BUF_SIZE * RECEIVE_DESC_SIZE, RT_CPU_CACHE_LINE_SZ);

    memset(edev->tx_buf, 0, EMAC_TX_BUF_SIZE);
    memset(edev->rx_buf, 0, EMAC_RX_BUF_SIZE);

    emac_clk_init(edev);
    emac_phy_init(edev);
    emac_dma_init(edev);
    emac_mac_init(edev);

    emac_tx_sem = rt_sem_create("emac rx on sem", 0, RT_IPC_FLAG_FIFO);

    edev->parent.eth_rx = emac_rx;
    edev->parent.eth_tx = emac_tx;

	edev->parent.parent.init = emac_ops.init;
	edev->parent.parent.control = emac_ops.control;
	rt_kprintf("%s %d\n", __func__, __LINE__);
    eth_device_init(&(edev->parent), edev->name);

	request_irq(edev->irq, vmc_interrupt, 0, edev->name, edev);
	enable_irq(edev->irq);
	
    netif_set_link_up(edev->parent.netif);

二、网卡驱动

VC0768有两个EMAC,分别是EMAC0和EMAC1,其中EMAC0最高支持到100Mbps,EMAC1最高支持到1000Mbps,EMAC0内部自带PHY,外部只需加上网络变压器即可通信,EMAC1则需要外接千兆PHY,例如开发板上接的就是Realtek的RTL8211F。在软件配置上,EMAC0和EMAC1的配置是差不多的,但EMAC1需要额外配置相应的引脚。

网卡驱动移植最重要的就是配置网卡DMA,VC0768的网卡DMA分为发送DMA和接收DMA,以接收DMA举例。DMA的缓存的个数以及每个块的大小都是可以配置的,例如uboot中配置的DMA单个接收块有256个,每个块大小为2048字节(实际大于1518字节即可),DMA有链式(chain)模式和环形(ring)模式,以环形模式来说,在逻辑上,这就像是个环形的队列,队列的每个块大小是固定的。当EMAC接收到数据时,会自动放入到DMA的某块缓存中,下一次接收时,将自动放入下一块缓存中,即其本身具备地址自增功能。这就是一个最经典的生产者和消费者模型,需要确保DMA的数据及时读取,否则所有的DMA数据块被写满后,将会丢包。代码中可以判断对应块的status来判断这个块当前时属于CPU还是属于DMA,属于CPU即代码可以访问,属于DMA即这个块DMA正在使用。

三、网卡吞吐速率测试

网卡吞吐速率测试一般使用iperf工具,iperf有很多版本,最新的是iperf3,iperf3的功能比较齐全。但iperf和iperf3并不通用,有些设备只支持iperf,那么对端也只能使用iperf。最简单的就是服务器端执行iperf -s,客户端执行iperf -c xx:xx:xx:xx,xx为服务端的ip地址。iperf还可以设置发包数量、tcp窗口大小等参数。iperf是个命令行工具,Jperf则是基于iperf的图形化工具,可以简单轻松的对iperf进行配置,如下所示。
在这里插入图片描述

四、网卡吞吐速率优化

网卡吞吐速率优化是一个比较繁琐的性能优化过程,里面涉及到很多,包括TCP参数、lwip参数、内存拷贝效率、网卡收发逻辑等,下面是几个比较重要的优化点。

4.1 TCP参数优化

  1. TCP_MSS:即Maxitum Segment Size,TCP单次传输最大的有效字节数,这个值最大可以为1460字节,因此需要改成1460字节。
  2. TCP_WND:即TCP的滑动窗口大小,不过实测这个值对吞吐速率影响不大,窗口大小是会双方自动调节的。

4.2 lwip参数优化

lwip的opt.h文件有很多lwip的默认配置,如果想修改配置,修改lwipopt.h文件即可。lwip的参数优化主要就是修改lwipopt.h这个文件。

  1. 内存申请方式优化:lwip共有三种内存分配方式,第一种是使用C库的malloc,第二种是lwip自己实现的动态内存堆分配,第三种是使用lwip自己实现的动态内存池分配。C库的malloc是最不推荐使用的,内存大的情况下,推荐使用内存池分配。内存池相比于内存堆,优点是分配和释放速度快且不会产生内存碎片,缺点是容易浪费空间,因为内存池里的块大小是不变的。具体的改动如下。各种类型的pbuf的个数、大小也需要合理设置,不至于在吞吐速率高时,pbuf不够用。

    #define MEM_LIBC_MALLOC             0
    #define MEM_USE_POOLS               1
    #define MEMP_USE_CUSTOM_POOLS       1
    
  2. LWIP_NETIF_TX_SINGLE_PBUF宏需要改为0,虽然官方说这个宏是为了让多个pbuf尽可能合成一个,有利于DMA发送,但实际上这个宏定义了之后会有拷贝的操作,影响效率,需要斟酌是否需要改为0。

  3. LWIP_STATS宏设置为0,这个是lwip内部统计功能,关闭可以提高代码执行效率。

4.3 内存拷贝优化

4.3.1 rt_memcpy优化

系统主频为1.2GHz,但实测拷贝12.5MB(100Mbit)数据,使用原生的rt_memcpy需要600ms,这会验证影响网卡的收发效率,因为lwip里面有不少pbuf拷贝的操作,例如网卡发送时,需要将pbuf中的payload拷贝到网卡DMA中发送。看rt_memcpy的源码可以发现,4字节对齐时,以long为字长进行拷贝,当内存非4字节对齐时,直接使用单字节方式进行拷贝,没有考虑2字节对齐的情况。且4字节拷贝时,可以使用neon指令进行拷贝,速度会更快。因此可以对rt_memcpy做如下优化:

void *rt_memcpy(void *dst, const void *src, rt_ubase_t count)
{
#define UNALIGNED(X, Y) \
    (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1)))
#define BIGBLOCKSIZE    (sizeof (long) << 2)
#define LITTLEBLOCKSIZE (sizeof (long))
#define TOO_SMALL(LEN)  ((LEN) < BIGBLOCKSIZE)

    char *dst_ptr = (char *)dst;
    char *src_ptr = (char *)src;
    long *aligned_dst;
    long *aligned_src;
    int len = count;

	unsigned short *test_dst;
	unsigned short *test_src;

    /* 增加neon拷贝 */
	if((len > 63) && (!((long)dst_ptr & 3)) && !((long)src_ptr & 3))
	{
        /* 此处只做演示,可能会溢出 */
		if (len & 63)
			len = (len & -64) + 64;
        
		asm volatile (
		"NEONCopyPLD: \n"
				" VLDM %[src]!,{d0-d7} \n"
				" VSTM %[dst]!,{d0-d7} \n"
				" SUBS %[len],%[len],#0x40 \n"
				" BGT NEONCopyPLD \n"
		: [dst]"+r"(dst), [src]"+r"(src), [len]"+r"(len) : : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "cc", "memory");
		
		return dst_ptr+len;
	}

	/* 原生的4字节拷贝 */
    if (!TOO_SMALL(len) && !UNALIGNED(src_ptr, dst_ptr))
    {
        aligned_dst = (long *)dst_ptr;
        aligned_src = (long *)src_ptr;

        while (len >= BIGBLOCKSIZE)
        {
            *aligned_dst++ = *aligned_src++;
            *aligned_dst++ = *aligned_src++;
            *aligned_dst++ = *aligned_src++;
            *aligned_dst++ = *aligned_src++;
            len -= BIGBLOCKSIZE;
        }

        while (len >= LITTLEBLOCKSIZE)
        {
            *aligned_dst++ = *aligned_src++;
            len -= LITTLEBLOCKSIZE;
        }

        dst_ptr = (char *)aligned_dst;
        src_ptr = (char *)aligned_src;
    }


    /* 增加2字节对齐时的拷贝 */
	if( !((long)src_ptr & 0x01)  && !((long)dst_ptr & 0x01))
	{
		test_dst = (unsigned short*)dst_ptr;
		test_src = (unsigned short*)src_ptr;
		while (len > 1)
		{
			*test_dst++ = *test_src++;
			len -= 2;
		}	

		dst_ptr = (char *)test_dst;
        src_ptr = (char *)test_src;
	}
	
    /* 原生的单字节拷贝 */
    while (len--)
    {
        *dst_ptr++ = *src_ptr++;
    }

    return dst;
#undef UNALIGNED
#undef BIGBLOCKSIZE
#undef LITTLEBLOCKSIZE
#undef TOO_SMALL
#endif
}

优化的效果如下:

  • rt_memcpy优化2字节拷贝后udp发送速率从30Mbps提升到48Mbps,TCP发送速率从22Mbps提升到30Mbps,tcp接收仍然是6Mbps。
  • rt_memcpy4字节对齐时加入neon指令进行拷贝,udp发送仍然是48Mbps,TCP发送速率从30Mbps提升到36Mbps,TCP接收仍然是6Mbps。

优化2字节对齐的拷贝,性能有较明显的提升,原因是Ethernet II头部是6字节目的地址+6字节源地址+2字节长度,共14字节,后面紧跟着payload,如下图所示。当payload是4字节对齐时,目的地址所在的内存是2字节对齐的。lwip发送数据时,传下来的pbuf->payload就是这样的情况,pbuf->payload是一个2字节对齐的地址,而不是4字节对齐的,因此优化2字节对齐时,对lwip整体性能也有提升。

在这里插入图片描述

网上有一个开源的rt_memcpy加强版,使用汇编指令,实测效果比neon指令略差一些,但比原生的rt_memcpy要强得多。地址如下:https://github.com/mysterywolf/rt_memcpy_cm

4.3.2 使用uboot下的memcpy.S

实测同一段内存拷贝测试程序,在uboot下执行只需要8ms,而在melis下执行需要102ms,差了整整10倍多。因此将uboot下arch/arm/lib/memcpy.S直接拿来用,实测仍然是102ms,没有提升。可能是跳转到melis系统后,哪里没有设置对,待排查,TCP接收速率也明显不行,也需要排查。

4.4 网卡收发优化

4.3.1 lwip发送优化

前文提到了lwip在发送报文时,如果没有定义LWIP_NO_TX_THREAD宏,那么发送的数据会通过邮箱传递给etx线程,由etx线程代发,缺点是这里引入了一次操作系统的调度,这是需要时间上的开销的,优点是tcpip线程只需要专心处理数据,隔离性比较好。如果追求性能,可以考虑定义LWIP_NO_TX_THREAD这个宏。

4.4.2 网卡发送优化

前文提到,DMA有多个块,在中断里会判断发送是否完成,并释放发送完成的信号量。实际上网卡发送时,不必每次都等待这个信号量,因为当前发送可能正在进行,但是可以把数据装载到下一个DMA块里。所以发送时应直接取下一块DMA块,判断当前是否属于CPU,如果是属于CPU就直接使用,无需等待信号量;如果是属于DMA,说明所有的DMA发送块都写满了,这时候才需要等待发送完成。所以将DMA发送块的个数设置的稍大一些,在一定程度上也是可以提升性能的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/545115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis查询各种类型数据该如何处理才能得到数据

文章目录 1、前言2、查询一个实体类对象字段名和属性名无法映射处理方式一&#xff1a;起别名方式二&#xff1a;使用全局配置文件配置映射规则方式三&#xff1a;自定义resultmap 3、查询一个list集合4、查询单个数据5、查询一条数据为map集合6、 查询多条数据为map集合方式一…

课程分享:华清远见联合NXP推出i.MX8M Plus开发与实践课程,超干超实用!

​课程名称&#xff1a; i.MX8M Plus开发与实践课程 课程介绍&#xff1a; i.MX8M Plus应用处理器是NXP推出的一款致力于推动机器学习&#xff08;ML&#xff09;&#xff0c;机器视觉&#xff0c;多媒体与工业边缘物联网应用的工业人工智能芯片。拥有4个ARM Cortex-A53核心…

远程桌面连接黑屏怎么解决?方法大全

远程桌面连接是一种非常有用的技术&#xff0c;它可以让用户从任何位置远程访问到其它计算机。然而&#xff0c;当你尝试连接到一个计算机时&#xff0c;你有可能会遇到远程桌面连接黑屏的问题。这个问题很常见&#xff0c;但是它可能会给你带来很多麻烦。在本文中&#xff0c;…

ssRender Plugin 基础

ssRender Plugin 基础 一.什么是Plugin ​ 插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下&#xff08;可能同时支持多个平台&#xff09;&#xff0c;而不能脱离指定的平台单独…

Windows安装配置Tomcat服务器教程 -- 外网远程访问

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 转载自cpolar文章&#xff1a;外网访问本地Tomcat服务器【cpolar内网穿透】…

【SpringBoot】五:Web服务---SpringMVC---控制器

文章目录 1 控制器介绍2 控制器工作流程3 控制器中的方法4 匹配请求路径到控制器方法5 RequestMapping6 控制器方法参数类型与可用返回值类型7 接收请求参数8 验证参数8.1 Bean Validation8.2 分组校验8.3 ValidationAutoConfiguration 1 控制器介绍 &#xff08;1&#xff09…

【Linux Network】数据链路层

目录 认识以太网 以太网帧格式 认识MAC地址 对比理解MAC地址和IP地址 认识MTU MTU对IP协议的影响 MTU对UDP协议的影响 MTU对于TCP协议的影响 MSS和MTU的关系&#xff1a; 查看硬件地址和MTU ARP协议 ARP协议的作用 ARP协议的工作流程 ARP数据报的格式 DNS(Domain Name System) …

近期要做填报报表,使用Spreadsheet还是Finereport?

又是忙碌的五月呀~~近期接到一个项目&#xff0c;是一家商贸公司需要去采集销售部门的销售业绩据&#xff0c; 以往他们使用Excel表格线下去做报表填报&#xff0c;传统的报表体系效率低&#xff0c;文件杂&#xff0c;汇总难。下级部门上传数据需要以多个表格来上报&#xff0…

Python3安装

依赖安装 gcc是一个用于linux系统下编程的编译器&#xff0c;由于python3需要编译安装&#xff0c;因此&#xff0c;需要首先安装gcc。先查看一下系统中&#xff0c;是否安装了gcc。 gcc --versions 发现没有安装&#xff0c;则需要安装。参数-y的作用是在安装过程中自动确认…

在 Python 中制作偶数列表

文章目录 开始什么是偶数在 Python 中使用 for 循环创建偶数列表在 Python 中使用 while 循环制作偶数列表使用列表理解在 Python 中制作偶数列表使用 Lambda 表达式在 Python 中创建偶数列表 开始 我们将通过示例介绍偶数列表以及在 Python 中创建偶数列表的不同方法。 什么是…

今天公司来了个拿 30K 出来的测试,算是见识到了基础的天花板

今天上班开早会就是新人见面仪式&#xff0c;听说来了个很厉害的大佬&#xff0c;年纪还不大&#xff0c;是上家公司离职过来的&#xff0c;薪资已经达到中高等水平&#xff0c;很多人都好奇不已&#xff0c;能拿到这个薪资应该人不简单&#xff0c;果然&#xff0c;自我介绍的…

韩国访问学者签证D-2-5材料准备及签证流程

韩国的签证种类很多&#xff0c;对于申请访问学者签证来说&#xff0c;较常见的签证种类是D-2-5签证和E-3签证&#xff0c;本篇知识人网小编先介绍D-2-5签证。 签证的材料准备 根据韩国大使馆2023年4月12日最新发布的“签证申请与准备材料指导”内容, D-2-5签证的签发对象及准…

一文带你了解电信终端指南(详细篇)

​ 电信入库认证周期&#xff1a; 常规为 4-6 周&#xff0c;我公司可加急完成认证&#xff0c;请拨打免费服务热线 400 626 0709 或联系 蒋先生 13823213584&#xff08;微信同号&#xff09;了解详情。 中国电信集团公司是我国特大型国有通信企业、上海世博会全球合作伙伴&a…

Yolov5轻量化:EMO,结合 CNN 和 Transformer 的现代倒残差移动模块设计,性能优于EdgeViT、Mobile-former等网络

论文: https://arxiv.org/pdf/2301.01146.pdf 🏆🏆🏆🏆🏆🏆Yolo轻量化模型🏆🏆🏆🏆🏆🏆 重新思考了 MobileNetv2 中高效的倒残差模块 Inverted Residual Block 和 ViT 中的有效 Transformer 的本质统一,归纳抽象了 MetaMobile Block 的一般概念。受这…

深度学习4 -- 卷积神经网络(代码实现篇+付详细流程文件)

引言 本文是使用pytorch对卷积神经网络(Convolutional Neural Network, CNN)的代码实现&#xff0c;作为之前介绍CNN原理的一个代码补充。本文代码相关介绍相对较为详细&#xff0c;也为自己的一个学习过程&#xff0c;有错误的地方欢迎指正。本人介绍CNN原理的链接:CNN原理介…

【Ai工具合集,一定有你需要的!】

花费了一天的时间测试了市面上各大Ai工具&#xff0c;然后帮大家整理总结出来了这些工具&#xff0c;一定记得点赞收藏保存&#xff0c;后面肯定会用到&#xff01; 使用说明 1.部分Ai工具需要魔法上网&#xff0c;请自行解决&#xff1b;部分工具需要收费&#xff0c;可以尝…

图神经网络:(处理点云)PointNet++的实现

文章说明&#xff1a; 1)参考资料&#xff1a;PYG官方文档。超链。 2)博主水平不高&#xff0c;如有错误还望批评指正。 3)我在百度网盘上传了这篇文章的jupyter notebook和有关文献。超链。提取码8848。 文章目录 简单前置工作学习文献阅读PointNet的实现模型问题 简单前置工作…

convLSTM2D 层使用方法解析(Keras库)

最近在研究时序图像分类问题&#xff0c;需要用到convLSTM层提取特征&#xff0c;所以在此仔细分析一下keras.layers.ConvLSTM2D层的使用方法。深度学习框架是tensorflow 官方文档&#xff1a;recurrent/#convlstm2d - Keras 中文文档 下面这部分内容摘自官方文档 ConvLSTM2D…

Axure 轮播图如何制作

近来在学习axure&#xff0c;用的版本为Axure 9&#xff0c;给大家讲一下怎么使用轮播图&#xff0c;老规矩保姆式教学法 一、作图 1.创建新的页面&#xff0c;方便我们做图 2.在元件库搜索“动态面板”字样&#xff0c;设置一个动态面板&#xff0c;为什么要设置呢&#xff…

IIC总线通讯协议学习

​ IIC(最简单的总线通讯,简单意味着通用和普适性) iic通讯一般采用一主多从的方式.同一时间要么在发送信息,要么在读取信息(半双工通讯) ​​​​​​​​​​​​​​ ​​​​ ​​​ 标准的写数据帧&#xff08;主机向从机写数据) 解释以上的写数据帧 S:起始信号(在SCL…