前言

这篇文章是 LwIP应用笔记(二):无操作系统支持下的RAW API移植 的后续,以下所有内容都是建立在已经完成RAW API移植的前提下。本文可能不会太纠结于代码细节,因为本文的目标并不是演示移植过程中每一行代码该怎么写,而是希望在讲清大体框架的基础上,给出移植的主要流程,即在移植过程中,我们需要做什么事。

一、RTOS环境下的运行优势与劣势

在非RTOS环境下,用户程序是通过回调类接口,也就是RAW API与LwIP协议栈进行交互的。用户通过注册回调函数的方式告诉协议栈,当某些事件发生时需要做什么,可以理解为用户将自己所写的部分代码嵌入了协议栈的代码执行流程中,这样做会带来一个很大的问题,即用户注册的回调函数会影响协议栈的运行效率。设想这样一个场景,在非RTOS环境下,用户编写了若干个不同的网络功能模块,它们通过回调的方式与协议栈进行交互,现在假设用户注册了一个需要执行较长时间的回调函数,则每次协议栈执行到这个回调函数就都需要等待一个较长的时间去让回调执行完成,之后才能处理其他事务,注册这个回调函数的网络功能模块成功的以一已之力拖慢了整个网络协议栈以及其他网络功能模块的执行效率,不管其他网络功能模块写的多么高效,它们都必须受制于这个最慢的回调函数。所有发生的事情就如同下图所示。

同时我们也可以预见,随着网络功能模块注册的回调函数越来越多,协议栈需要耗费更多的时间去逐个处理回调函数。
解决上述问题的方法就是使用RTOS,通过将LwIP的协议栈与用户代码放置于不同的线程中执行,来规避用户代码拖慢协议栈执行的问题。用户代码和协议栈之间通过邮箱来进行数据传输。在RTOS环境下,LwIP提供NETCONN API以及类Socket API给用户使用,用户线程通过这两套API与协议栈线程进行交互。这样对于协议栈来说,它再也不用管何时去执行用户代码了,其只需要处理用户代码发送过来的数据,然后将需要用户代码处理的数据丢给对应的用户处理线程。如果很不幸有一个执行效率低下的用户线程,其可能会由于自身的低效丢失本该处理的数据,但是不会拖累其他的用户线程与协议栈本身的运行效率。用户代码和协议栈的关系此时如下所示。

但是我们也要注意到,在RTOS中用户程序和LwiP协议栈之间要借助邮箱等手段进行线程间交互,这会导致额外的系统开销以及一定的效率损失。在对性能有极高要求,同时需求比较简单的情况下,基于回调函数的RAW API接口可能会更加适用。

二、RTOS相关的配置项

需要修改用户配置文件lwipopts.h,添加一些操作系统相关的配置项,如下图所示。

需要重点关注操作系统相关注释下的配置项,如下几个配置项是需要特别关注的:

  • LWIP_COMPAT_MUTEX以及LWIP_CMPAT_MUTEX_ALLOWED,这两个配置使能后便可以在移植操作系统抽象层时不用移植互斥锁相关的接口,这时LwIP会借助二值信号量来代替互斥锁的功能,但是这样便没办法应对线程优先级翻转的问题了,所以需要谨慎考虑是否启用。这个功能,可能是为了照顾一些没有提供互斥锁接口的操作系统。
  • xxx_RECVMBOX_SIZE以及DEFAULT_ACCEPTMBOX_SIZE, 诸如这样的配置项是用于配置各个连接所使用的邮箱大小,邮箱接收的只是指向实际数据的指针地址,所以每项的大小为指针大小。这些项的值会在创建邮箱时传送给操作系统抽象层的邮箱创建API,如果不配置这些值的话,也可以在移植邮箱创建API时忽略传入的邮箱大小参数而使用固定的数值。

三、操作系统抽象层移植

由于LwIP并不知道自己将会运行于什么操作系统,故而为了提高可移植性,LwIP提供了操作系统抽象层,用于与操作系统的函数接口进行对接。总体而言,抽象层需要实现的函数接口分为以下几类:

  • 信号量类函数接口,此类函数接口用于提供信号量相关的操作接口;
  • 互斥锁类函数接口,此类函数接口用于提供互斥锁相关的操作接口,如果在前面配置时,设置了LWIP_COMPAT_MUTEX以及LWIP_CMPAT_MUTEX_ALLOWED为1,则这类函数接口则无需实现;
  • 邮箱类函数接口,此类函数接口用于提供邮箱相关的操作接口,如果操作系统不提供邮箱功能,可以考虑使用队列来实现;
  • 线程管理类函数接口,此类函数接口用于提供线程创建相关的操作接口;
  • 其他类函数接口,包括初始化操作系统抽象层以及获取当前的系统滴答计数时间;

下图为移植过程中需要移植的各类函数接口,函数的具体注释以及操作系统抽象层提供的所有函数接口可以参考LwIP内的sys.h文件,其中的注释详细说明了每个接口的功能以及移植的注意事项,在移植过程中需要频繁参考此文件。同时另一个很有参考价值的资料是官方基于doxygen生成的文档,其中有一节专门介绍操作系统抽象层的相关内容,官方链接如下:http://www.nongnu.org/lwip/2_1_x/group__sys__os.html。

笔者是基于rtthread nano进行的操作系统抽象层移植,可以参考笔者开源仓库中的移植文件sys_arch.c以及sys_arch.h

四、操作系统下的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资源的过多占用。上面一堆文字,其实就是下面一张图所表达的。

具体完整的代码实现可以参考https://gitee.com/water_zhang/enc28j60_arduino_shield_board/blob/master/software/stm32f412g_discovery_board_lwip/Middlewares/lwip/lwip_user/lwip_user_init.c

以下对文件中的几个关键函数进行说明:

  • static void Lwip_User_IRQ_Callback(void *param)
    此函数对应上图中的网卡中断,会在网卡中断发生时被调用,唯一做的是就是释放信号量通知网卡数据包接收线程,网卡有事情发送,该干活了;
  • void Lwip_User_Process(void *param)
    此函数就是前面提到的网卡数据包接收线程eth_recv的执行函数,此线程会堵塞在信号量获取函数上,直到收到中断释放的信号量,收到信号量后先判断中断是否为接收到数据包导致(笔者使用的enc28j60的中断为多源中断,需要查询寄存器得知触发中断的原因),如果是数据包接收中断,则调用Lwip_User_PollEthernetPacket函数,此函数会读取网卡中的所有数据包并最终将这些数据包提交到LwIP线程的tcpip_mbox邮箱等待处理;
  • int Lwip_User_Init(void)
    此函数实现对整个协议栈的初始化,具体在后面的初始化流程一节说明;

五、初始化流程

裸机环境下的初始化流程稍微改动后就可以了,这里将两份代码做一下对比,并给出差异点,没什么好说的其实。为了方便对比差异,这里删去了一些帮助调试的相关语句,只保留有价值的内容。

六、后续可优化点

其实通过全文可以得知,我们并没有对网络发送部分进行改造,也就是说,此时底层网卡的发送部分还是和LwIP线程在同一个线程中执行的,后期可以考虑将发送部分也独立为单独线程,LwIP将需要发送的数据包通过邮箱给到发送线程,发送线程堵塞在邮箱上,有需要发送的数据包时进行发送。这部分的修改需要涉及到此文件:https://gitee.com/water_zhang/enc28j60_arduino_shield_board/blob/master/software/stm32f412g_discovery_board_lwip/Middlewares/lwip/port/eth/eth0.c

LwIP应用笔记(三):在RTOS环境下运行LwIP协议栈相关推荐

  1. Redis学习笔记~Redis在windows环境下的安装

    Redis是一个key-value的存储系统,它最大的特点就是可以将数据序列化到文件中. redis存储在服务器的内存或者文件中,它不是session,不是cookies,它只是个更安全,更稳定,更可 ...

  2. python 需要在什么环境下运行_进入Python 环境进行编程并运行程序的三种方式

    越来越多人开始学习Python了,本篇介绍进入Python 环境进行编程并运行程序的三种方式,适合刚入门的小白参考.进入Python的前提是先下载安装Python软件,如下所示,建议下载最新版,安装过 ...

  3. 《ESP32 学习笔记》 之Arduino环境下 使用DAC模拟输出(是真的DAC哦!)完成两路呼吸灯

    在 Arduino 环境下经常使用的 analogWrite(PIN, arg) 语法并不是真正的DAC模拟输出,也只是1kHZ的PWM 波模拟而成! 支持DAC功能的引脚请查看:引脚定义 本次ESP ...

  4. linux如何运行java程序,Linux环境下运行简单java程序

    一.安装java 1.下载jdk8 选择对应jdk版本下载.(Tips:可在Windows下载完成后,通过FTP或者SSH到发送到Linux上) 2. 登录Linux,切换到root用户 su roo ...

  5. MAC OX 10.8 环境下运行TLD算法(MATLAB版)

    关于TLD算法,就不具体阐述了,谷歌百度搜索一下,介绍的文章相当多. 概述: 一.安装和编译高性能OpenCV 2.4.6 二.安装xcode和command line tool 三.MATLAB安装 ...

  6. 在Linux环境下运行你的第一个C语言程序

    在Linux环境下运行你的第一个C语言程序 1.前言 2.环境配置 1.1 安装编辑器 1.2 安装gcc编译器 3.编写第一个C程序 3.1 新建文件夹 3.2 编辑该文件 3.3 编译mian.c ...

  7. win10环境下 运行debug程序

    百度网盘:链接:https://pan.baidu.com/s/1y6omgW6fI-gT3Dp-0hutOg    提取码:iw4l CSDN0积分下载:https://download.csdn. ...

  8. sublime text3 怎么配置、运行python_【IT专家】Sublime Text3配置在可交互环境下运行python快捷键...

    本文由我司收集整编,推荐下载,如有疑问,请与我司联系 Sublime Text3 配置在可交互环境下运行 python 快捷键 2015/06/04 19131 安装插件 在 Sublime Text ...

  9. 成功解决Windows10环境下运行Linux系统下的.sh文件

    成功解决Windows10环境下运行Linux系统下的.sh文件 目录 解决问题 解决方法 解决问题 Windows10环境下运行Linux系统下的.sh文件 解决方法 .sh是shell scrip ...

最新文章

  1. 桌面应用程序 azure_如何开始使用Microsoft Azure-功能应用程序,HTTP触发器和事件队列...
  2. java search 不能使用方法_elasticsearch(七)java 搜索功能Search Request的介绍与使用...
  3. JS 简单实现公告消息自动逐条切换
  4. python---memcache使用操作
  5. QT的QNoDraw类的使用
  6. javax.servlet.http.HttpServlet was not found
  7. 关于微信小程序使用获取用户信息getUserProfile的问题:TypeError: wx.getUserProfile is not a function
  8. 百度大脑EasyDL专业版挑战赛正式开启,挑战没有做不到的模型!
  9. 精选|2018年12月R新包推荐
  10. UICollectionView实现带头视图和组的头视图同时存在实现
  11. CTD数据库(Comparative Toxicogenomics Database)介绍与使用
  12. 非对称加密算法RSA加密解密流程
  13. Vue 使用 Echarts 显示热力地图信息
  14. 月薪20k+的Android面试都问些什么?完整PDF
  15. pythoncad标注教程_CAD 2014二维三维建模渲染标注基础与提升视频教程
  16. 神经风格转移:Anaconda快速搭建DL框架学梵高(Van Gogh)绘画Ubuntu16.04 CPUGPU版
  17. CocoaPods制作
  18. Gunicorn-配置参数
  19. Word的样式库在 选项卡中_Word|表格的设置
  20. 红石外汇|每日分享:0.667是澳元多头在FOMC会议前的最后一道防线

热门文章

  1. opencv-双边滤波
  2. white-space: pre-wrap 会出现首行缩进的解决办法
  3. 报错-Error creating bean with name xxx: Unsatisfied dependency expressed through field xxx
  4. 安卓手机”应用未安装“解决办法
  5. Word中插入上标、下标快捷键
  6. 高德地图获取当前屏幕中心点的经纬度
  7. pythonurllib登录微博什么意思_登录微博详解-爬虫的第一站
  8. Go语言实现的API-Gateway
  9. 题解-表达式括号匹配
  10. python编程从入门到实践 第18章Django入门 2022年最新