| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> LwIP应用笔记(三):在RTOS环境下运行LwIP协议栈 -> 正文阅读 |
|
[系统运维]LwIP应用笔记(三):在RTOS环境下运行LwIP协议栈 |
欢迎来我的个人博客转转:https://www.codinglover.top 前言这篇文章是 LwIP应用笔记(二):无操作系统支持下的RAW API移植 的后续,以下所有内容都是建立在已经完成RAW API移植的前提下。本文可能不会太纠结于代码细节,因为本文的目标并不是演示移植过程中每一行代码该怎么写,而是希望在讲清大体框架的基础上,给出移植的主要流程,即在移植过程中,我们需要做什么事。 一、RTOS环境下的运行优势与劣势在非RTOS环境下,用户程序是通过回调类接口,也就是RAW API与LwIP协议栈进行交互的。用户通过注册回调函数的方式告诉协议栈,当某些事件发生时需要做什么,可以理解为用户将自己所写的部分代码嵌入了协议栈的代码执行流程中,这样做会带来一个很大的问题,即用户注册的回调函数会影响协议栈的运行效率。设想这样一个场景,在非RTOS环境下,用户编写了若干个不同的网络功能模块,它们通过回调的方式与协议栈进行交互,现在假设用户注册了一个需要执行较长时间的回调函数,则每次协议栈执行到这个回调函数就都需要等待一个较长的时间去让回调执行完成,之后才能处理其他事务,注册这个回调函数的网络功能模块成功的以一已之力拖慢了整个网络协议栈以及其他网络功能模块的执行效率,不管其他网络功能模块写的多么高效,它们都必须受制于这个最慢的回调函数。所有发生的事情就如同下图所示。 同时我们也可以预见,随着网络功能模块注册的回调函数越来越多,协议栈需要耗费更多的时间去逐个处理回调函数。 但是我们也要注意到,在RTOS中用户程序和LwiP协议栈之间要借助邮箱等手段进行线程间交互,这会导致额外的系统开销以及一定的效率损失。在对性能有极高要求,同时需求比较简单的情况下,基于回调函数的RAW API接口可能会更加适用。 二、RTOS相关的配置项需要修改用户配置文件lwipopts.h,添加一些操作系统相关的配置项,如下图所示。 需要重点关注操作系统相关注释下的配置项,如下几个配置项是需要特别关注的:
三、操作系统抽象层移植由于LwIP并不知道自己将会运行于什么操作系统,故而为了提高可移植性,LwIP提供了操作系统抽象层,用于与操作系统的函数接口进行对接。总体而言,抽象层需要实现的函数接口分为以下几类:
下图为移植过程中需要移植的各类函数接口,函数的具体注释以及操作系统抽象层提供的所有函数接口可以参考LwIP内的sys.h文件,其中的注释详细说明了每个接口的功能以及移植的注意事项,在移植过程中需要频繁参考此文件。同时另一个很有参考价值的资料是官方基于doxygen生成的文档,其中有一节专门介绍操作系统抽象层的相关内容,官方链接如下:http://www.nongnu.org/lwip/2_1_x/group__sys__os.html。 四、操作系统下的LwIP执行方式首先来回忆一下在裸机环境中我们是怎么运行LwIP协议栈的,初始化完成协议栈后,我们在主循环中一遍遍的查询网卡是否接收到报文,如果接收到报文就将报文传送给协议栈进行处理。同时我们还要定期执行负责处理超时事件的sys_check_timeouts函数。整个裸机环境下的执行流程如下所示: 在操作系统环境下,由于LwIP独自运行于一个线程中(线程名字由TCPIP_THREAD_NAME宏定义决定,这里为"tcpip_thread"),要将报文传送给LwIP就只能通过邮箱方式了,为此LwIP实现了tcpip_input函数。不同于netif_input函数直接将收到的报文丢入协议栈处理,tcpip_input函数会将收到的报文传入LwIP的报文接收邮箱等待LwIP线程读取并处理,在初始化流程中调用netif_add函数添加报文接收接口时,用这个函数代替ethernet_input函数传入即可。 如果追踪下LwIP内部的函数调用话,可以发现底层链路层为使用ARP协议的以太网情况下,最终处理报文的方法都是通过调用ethernet_input函数。不同之处在于,当处于裸机环境下,我们可以理解为以太网报文读取和报文处理都处于同一个线程下,函数调用为netif_input->ethernet_input;而实时操作系统下,负责以太网报文读取的线程调用流程为:tcpip_input->tcpip_inpkt->sys_mbox_trypost,到此报文最终被发送到了LwIP内部的tcpip_mbox邮箱(这个邮箱的长度就是由前文的TCPIP_MBOX_SIZE宏定义决定),LwIP线程会在tcpip_mbox邮箱中存在待处理报文时,调用ethernet_input处理这些报文。 这样一来,我们可以建立一个线程专门用于从网卡读取报文(称这个线程为"eth_recv"吧),然后在收到报文时调用eth0_input函数将报文从网卡中读出,然后最终通过tcpip_input函数发送到LwIP线程的接收邮箱。在编写eth_recv线程时,难道我们也要像裸机一样不停轮询是否有报文到来吗?这样未免有点浪费资源了,同时为了确保报文的及时接收,我们肯定会给这个线程分配比较高的优先等级,如果不停轮询一直占用cpu资源,其他低优先级线程便无法执行了。好点的方法是借助网卡芯片的中断硬件,当收到报文时触发外部中断,在中断中通过信号量通知eth_recv线程有报文到来,这时eth_recv线程便开始从网卡中读取报文并发送给LwIP所在线程,完成这一切后,eth_recv线挂起自己,等待下一次信号量的到来。这样就避免了轮询方式对CPU资源的过多占用。上面一堆文字,其实就是下面一张图所表达的。 以下对文件中的几个关键函数进行说明:
五、初始化流程裸机环境下的初始化流程稍微改动后就可以了,这里将两份代码做一下对比,并给出差异点,没什么好说的其实。为了方便对比差异,这里删去了一些帮助调试的相关语句,只保留有价值的内容。 六、后续可优化点其实通过全文可以得知,我们并没有对网络发送部分进行改造,也就是说,此时底层网卡的发送部分还是和LwIP线程在同一个线程中执行的,后期可以考虑将发送部分也独立为单独线程,LwIP将需要发送的数据包通过邮箱给到发送线程,发送线程堵塞在邮箱上,有需要发送的数据包时进行发送。这部分的修改需要涉及到此文件:https://gitee.com/water_zhang/enc28j60_arduino_shield_board/blob/master/software/stm32f412g_discovery_board_lwip/Middlewares/lwip/port/eth/eth0.c |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 11:27:31- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |