• Lwip之定时机制


    这里主要介绍Lwip的定时机制

    Lwip的设计是基于进程模型,整个协议栈的处理是由一个进程来处理的,通常也说是一个任务。在Lwip中没有使用任何操作系统相关的函数,取而代之的是提供了一个操作系统仿真层,操作系统仿真层提供唯一的一个接口给操作系统服务,这些服务包括定时器,进程同步以及消息传递机制。进程的同步使用信号量来实现,消息传递使用邮箱实现,而对于定时器的实现则是这一部分需要介绍的。

    Lwip通过维护一个任务相关的定时链表来管理相应的定时事件。该链表的基本结构如下图所示。

    定时事件总体结构:

    通过上图可以看出,系统通过全局指针threads指向链表的开始,左边一列是与任务相关的,每个任务对应一个控制块,所有的任务通过next指针连在一起,右边则是该任务对应的定时事件,该结构体中包含了定时时间,对应捕获函数的参数以及地址,各个定时事件同样通过next指针域连在一起。所以,通过threads指针我们可以找到每个任务的每个定时事件。要添加一个定时事件到任务的定时链表中,则需要在任务的上下文环境中调用函数sys_timeout,关于该函数要做的工作在下面介绍:

    sys_timeout函数的实现

    @1:首先为当前定时事件timeout结构体分配内存并赋值 (timeout结构体)

    @2:调用sys_arch_timeouts函数获得当前任务的定时队列(我们所添加的定时事件都是任务相关的,即该定时事件属于那个任务是确定的)

    @3:如果当前任务的定时队列为空(如上图中的2与4),则将刚才分配的定时事件挂到任务的定时队列上,返回

    @4:进入到这一步说明当前任务的定时队列如上图的1和3两种情况。此时,如果当前任务定时队列中的第一个定时事件的触发时间大于刚才新产生的定时事件的触发时间,则将其触发时间减去新产生的定时事件的触发时间,并将新的定时事件加入到其后;否则,进入下一步(比如,如果当前的定时为7->4->2,到达的定时事件的触发时间为5,则新的定时队列就为5->2->4->2,这样各个事件的新的触发时间就都是相对值了,相对的对象就是其前一事件的触发时间值)

    @5:遍历当前任务的定时队列,每进入一次循环,执行下面的操作:

    修改A的(A/B/C等的含义见后面说明)Atime,即减去Btime,如果没到队列头并且Ctime不大于Atime,则继续进行下次循环。否则,如果队列到头了,则直接将A添加到B之后,如果是相对值小于C与B的相对值,则说明Atime介于二者之间,此时就将其插入到二者之间,并进行Ctime - Atime操作。最后跳出循环。

    (新产生定时事件——A; 当前指向的任务的定时事件(循环开始时为任务的定时队列上的第一个定时事件)——B; B之后的定时事件——C; A的触发时间Atime; B的触发时间Btime; C的触发时间Ctime)

    对于第四步所举的实例,如果Atime为8,则最终的排序为5->2->1->3->2,如果Atime为7,则为5->2->2->4->2,若Atime为6,则排序为5->1->2->4->2,若以实际时间值排序,则三种情况下的对应的结果分别为:5->7->8->11->13; 5->7->7(Atime)->11->13; 5->6->7->11->13。

    从上述结果可以看出,整个过程是一个按照升序进行排序的过程,只不过绝对的时间值的比较变成了相对时间值的比较。这里,sys_timeout函数不仅做了定时事件的添加,而且还有排序工作,这样就为后续的操作减轻了负担。如果我们使用绝对时间的话,该函数的实现是可以简单一些,但是,一旦第一个定时事件的触发时间到达后,则还需要遍历队列去修改后续定时事件的触发时间,这会影响到整个系统的效率。

    这里介绍了定时事件的结构以及如何被添加到任务的定时队列中去,但是,在这里并没有按照我们所想象的那样有任何关于触发时间如何消耗以及定时捕获函数的执行等相关操作。我们将在后面通过对下面三个函数的介绍来对这一问题进行说明。

    sys_mbox_fetch函数的实现及对定时的影响

    该函数的执行流程如下:

    首先,调用sys_arch_timeouts获取当前任务的定时队列

    如果当前任务没有定时队列或者定时队列为空,则调用sys_arch_mbox_fetch函数获取消息,此时,该函数的时间参数设置为0,也就是说系统将处于死等状态,如果此时邮箱为空,系统将一直等待,直到邮箱中有消息。

    否则,也就是说任务的定时队列不为空,将进行下面的所有操作:

    如果Btime(含义见上)大于零,同样调用函数sys_arch_mbox_fetch函数获取消息,不过此时该函数的时间参数并不设置为0,而是Btime。同时,用一个变量保存其返回值。否则,说明应该触发该定时事件,执行其相应的捕获函数,因此,我们给时间变量赋值为全F。

    对于sys_arch_mbox_fetch函数的调用,前面已经说明,如果时间参数为0,则就是死等,而如果参数设置为Btime,则我们至多等待Btime的时间,如果在次期间函数取得消息返回,则返回的时间为实际等待的时间,否则,就是Btime。

    通过上面的操作,我们完成了对时间变量的设置,下面的操作就基于该时间变量的值进行。如果定时事件的触发时间到了,则将其从定时队列中取出,并执行其捕获函数。执行完后返回到开始,继续取消息。否则,说明在定时时间到达之前取得了消息,此时只需将Btime减去时间变量保存的返回值即可。

    通过上面对从邮箱取消息操作的说明可以看出,只有当前的定时事件的定时还没有到达,并且从邮箱中取得了消息,该函数才会返回。

    sys_sem_wait函数的实现及对定时的影响

    该函数的操作与sys_mbox_fetch类似,只不过取邮箱操作变成了等待信号量,对应底层的操作由sys_arch_mbox_fetch变成了sys_arch_sem_wait。

    sys_msleep函数的实现及对定时的影响

    该函数的作用是使得系统进行毫秒级的等待,同时允许其他任务有执行的机会。基本的执行过程如下:

    调用sys_sem_new创建一个信号量

    调用sys_sem_wait_timeout进行sleep操作

    调用函数sys_sem_free释放之前创建的信号量资源

    下面对sys_sem_wait_timeout函数的实现进行简单的说明:

    首先判断等待的时间,如果不大于零,则一直等待信号量,否则,添加一个用于释放信号量的定时事件,触发时间就是sleep时间。之后等待信号量的操作则如对sys_sem_wait函数的描述。从之前的描述中我们可以看出该操作会消耗系统时间。

    根据以上描述,如果一个任务在其执行的整个过程中,如果没有直接或者间接的调用以上三个函数或其一,那么在其上下文中调用sys_timeout所添加的定时事件将永远不会得到执行。

    另外,该任务还必须是通过调用操作系统仿真层提供的任务创建函数创建的才行,否则,必须调用相应的任务添加函数将其任务id添加到lwip维护和管理的任务链中。

  • 相关阅读:
    科大讯飞分类算法挑战赛2023的一些经验总结
    构建煤矿物联网大数据平台思路(1)
    Java项目:SSM游戏点评网站
    面向对象编程(高级部分)——final关键字
    2022-04-10-Docker
    ethercat EOE arp
    JavaEE Bean作用域与生命周期
    企业数据流动安全管理软件(深度解析文章)
    基于jsp+ssm的网上图书商城-计算机毕业设计
    Visual Studio Code 配置 C/C++ 开发环境的最佳实践(VSCode + Clangd + CMake)
  • 原文地址:https://blog.csdn.net/wwwyue1985/article/details/125470415