事件驱动框架(四)——实时框架
说明:
之前介绍的都是事件处理框架,是离应用最近的一层,主要思想就是分割对象,然后细化对象的状态,用状态机的方式进行处理。这里的处理方式很多,主要还是那两个接口的实现状态机。
这章介绍核心的实时框架部分。这部分是介于底层或者OS和时间处理框架之间的一层可重用的设计。(看完别人的事件驱动的框架思路就被直接带着走了= =)负责多个状态机间发送事件的处理策略。
下面这段是实施框架种类的介绍,我只是主要整理了一下。
基础
1.控制倒置
事件驱动框架和顺序式结构程序最大的区别在于控制的倒置。事件驱动框架是框架会根据不同的策略在内部主动调用对象,框架常常扮演协调和排列程序活动的主函数角色。顺序式结构需要程序员事先规划好应用的执行顺序,按照流程进行运行。
2.传统事件驱动系统
传统事件驱动系统有2部分组成:事件基础设施和应用程序。事件基础设施由事件循环,事件调度器,事件队列组成。应用程序则是用来共享共同数据的时间处理函数。
系统中从异步中断或者重时间处理函数被放入事件队列。控制权则停留在一个无限循环查询事件队列的调度器上。调度器负责事件的排序和事件的查询。这就要求时间处理函数执行时间短,并且要避免阻塞。
传统时间驱动系统有以下缺点:
1>.不可抢占。需要等待上一个事件处理完后才能接着处理下一个。
2>.不支持对应用程序上下文的管理
3>.全部的事件处理函数存取一样的全局数据,带来了并发性危机。
3.活动对象模型
这个模型的本质概念是在一个多任务环境里使用多个事件驱动型系统。一个应用程序包括了多个活动对象。每个对象都有它自己的控制线程:
活动对象=控制线程+事件队列+状态机
这种模型和状态机兼容。状态机的事件队列,事件循环和事件处理器是通用的,是实时框架的一部分。应用程序用状态机实现。框架通过调度接口调用它们。
传统的可抢占式内核
在活动对象的模型中,活动对象被映射为OS的一个线程。活动对象系统可以实现和传统的任务相同的任务层响应。
合作式内核(非抢占式内核)
一种非常简单的内核。活动对象合作分享CPU,并且在每个RTC步骤内无条件让出控制权。该种策略适合于低端的嵌入式系统。同时RTC时间需要被设计的短。
内核策略:
内核负责确定下一部分的运行代码。它的调度器在大循环中不断监视所有事件队列,挑选中间优先级最高的非空队列,派发事件调度。当调度时间完成后返回到主循环中,此时,运行权有交还给调度器来重新进行事件队列的监视。
当调度器检测到所有的事件都为空时,执行空闲处理。(这部分可被用户程序定制,输出追踪信息等,在嵌入式中也进入低功耗模式,和当有中断时唤醒)
可抢占式RTC内核
在RTC内核,任务和中断服务ISR 都是一次性,运行到完成(从队列中获得事件到调度dispatch结束)。中断被看做作一个“超高”优先级任务,整个过程非常类似一个被基于优先级的中断控制器管理的ISR。
只需要全部任务运行到完成,并强制固定优先级调度,一个RTC内核可以使用堆栈协议。当一个任务被一个更高优先级任务抢占, RTC内核使用一个常规的C函数调用,在被抢占任务堆栈上下文的顶部创建较高优先级任务的上下文。只要一个中断抢占了一个任务,内核再次使用一个常规C函数调用,使用已经建立的中断堆栈框架,在它的顶部建立较高优先级任务的上下文。这个简单的上下文管理的形式是适当的,因为每个任务,就像每个ISR 一样,运行到完成。因为抢占任务必须也运行到完成,较低优先级堆栈的上下文将不再需要,直到这个抢占任务完成为止—这时候,被抢占的任务将自然的在堆栈的顶部,准备被恢复。这个简单的机制使用和一个基于优先级的硬件中断控制器系统工作完全相同的原理工作。RTC内核最严重的局限性,就是任务不能被阻塞。。使用一个传统RTOS 唯一真实的原因是要和现有代码兼容。例如,传统的设备驱动程序,通讯堆栈(比如TCP/IP,USB,CAN等待)和其他legacy 子系统通常使用阻塞范型编写。传统的阻塞型RTOS 可以支持活动对象和传统的阻塞代码, RTOS 在实时框架外面执行它。(原理直接复制)
4.事件派发机制
事件的产生来源:系统的任何部分,活动对象等。消费事件只能是活动对象。
实时框架支持2 类事件派发机制:
1>.直接发送。将产生的事件直接放入事件队列。
2>.发行-订阅。生产的事件发送给框架,框架发送事件给所有订阅了这个事件的活动对象。
5.事件内存管理
这部分主要是对事件的使用方式。
事件管理的策略:
1.复制事件
将事件整个复制到事件队列中,使用时再将整个事件复制到内存缓冲区。这种方法在复制是消耗的时间比较多,并且复制的是整个事件,内存的开销也大。
2.零复制的事件派发
这种策略是由框架控制事件的产生和销毁。事件从内存池中进行分配,然后生产者将事件填充,在以指针的形式放入事件队列。活动对象读取队列中的指针,指针指向得到事件数据。然后用完的事件会被框架自动回收。
静态事件和动态事件
静态事件的参数不会变,而动态事件所带的参数会改变。所以动态事件需要动态分配。
多路传输事件和引用计数器算法
对于发送订阅这种模式下,实现对事件的回收可以通过使用标准的引用计数器算法。它的原理是,事件被创建时计数器从0开始。这个事件被插入队列,计数器加1。每次对这个事件的垃圾收集会将计数器减1。仅当事件的计数器为0 时才会被回收。
注意引用计数器在事件被从一个队列提取时没有递减,仅在稍后的垃圾收集步骤才会被递减。
内存池
使用标准函数malloc,free,new,delete等来管理动态分配堆会产生以下问题:
1>.大量的动态分配会把堆搞得支离破碎
2>.基于堆的内存管理是浪费的。所有的堆管理算法必须为每个被分配的块维护某些形式的头信息。
3>.动态分配不可重入
4>.容易造成内存泄露并且不好调试
内存池是一个块尺寸固定的堆。因为大小固定,它可以使不被碎片化,并且没有没了头信息,减小了开销
用内存池管理事件的策略:
由框架来管理内存池,为了满足不同大小的事件的需要,内存池分为不同的大小。并且在使用时事件需标明从哪块内存池中分配的事件。
时钟管理
在事件驱动的框架下,时钟管理是把时钟当做一类事件进行处理。