如何调整事件队列和事件池的大小?
我们只有一个目标,为了最小化这个内存。最小化带来的风险,队列溢出或者内存池被耗光,断言失败,这两种情况被归为第一类故障,同为第一类故障的还有堆栈溢出。
在开发初期使用超大型的事件队列和内存池,让你的开发更具有灵活性,调整事件队列和事件池放在开发的末尾。这并不意味着在设计过程中,完全不在乎队列和内存池。
在设计的时候一定要遵从一般性规则,什么是一般性规则,举个类比你就明白了:在我们的单片机的启动代码中需要定义一个堆栈,经典的0x400,如何避免栈溢出?
答:一般性规则,避免函数的深度嵌套和大的自动变量。
在我们QF中,同样定义了一个定量的事件队列和内存池,如何避免他们溢出?
答:一般性规则,避免不必要的事件产生或把较长的RTC步骤分解。
关于事件队列你需要理解一个事实:平均事件产生速率 不能超过 平均事件消耗速率,假如这一个规则被打破,再大的队列也会被撑爆,只是时间问题。但是偶尔产生率可以超过消耗速率,但不能持续很长时间类似我们CPU的超频功能。
传统的确认事件队列或内存池使用量有两种方式,第一种较为简单就是通过运行时观察系统动态实时开销来评估需要多少的内存,像QF在事件队列中提供nMin(数据成员)。
静态内存分析是我们提高软件设计能力的核心,动态分析往往作为辅助验证。
一下几种情况可以当做小故事来思考,帮你理解静态分析的核心:(出自QP原书实例):考虑某个 QF 应用程序运行在某个可抢占的,基于优先级的内核上 。假设最高优先级 的活动对象仅接受其他活动对象的事件(但是不结束从 ISR 发出的事件)。每当任何低优先级的活动对象发送或发行某个事件给最高优先级的对象,内核立即把 CPU赋给接收者。内核做这个上下文切换是因为,在这一刻,接收者是最高优先级的预报运行的线程。最高优先级的活动对象醒过来,并运行到结束, 消耗任何发送给它的事件。因此,最高优先级的活动对象实际不需要对事件排队(它的事件队列的最大 深度为 1 ) 当最高优先级的活动对象接受从 ISR 来的事件时,更多的事件会为了它排队。
在最常见的安排中, 某个 ISR 每次活动时仅产生一个事件。另外,实时要求的底线通常是,最高优先级的活动对象必须在下个中断前消耗完事件。这种情况下,活动对象的事件队列可以增长到最多 2 个事件:一个用于任务,另一个用于某个 ISR 。你可以把这个分析递归的扩展到较低优先级的活动对象。被排队的事件的最大数量是,在给定的底线内,所有可以为这个活动对象产生事件的较高优先级线程和 ISR 产生的所有事件的总和。底线时这个活动对象的最长的 RTC步骤,包括所有可能的来自较高优先级线程和 ISR 的抢占。例如,在 DPP应用程序中,所有 Philosoper活动对象执行非常少的处理(它们有短的 RTC步骤)。如果 CPU可以在一个时钟节拍内完成这些 RTC步骤, Philosopher 队列的最大长度将是 3 个事件:一个用于系统时钟ISR ,其它 2 个用于活动对象 Table ( Table 有时候在一个 RTC步骤内发行 2 个事件)。
对事件队列进行静态分析的经验参考如下:
1. 事件队列的大小取决于活动对象的优先级。一般的,优先级越高,必需的事件队列越短。特别的,系统里最高优先级活动对象立即消耗所有被其他活动对象发现的所有事件,仅需要对那些被 ISR 发行的事件进行排队。
2. 队列尺寸取决于最长的 RTC步骤持续的时间,包括所欲可能的(最差情况下的)来自较高优先级活动对象或 ISR 的抢占。处理越快,必需的事件队列越短。为了最小化队列尺寸, 你应该避免非常长的 RTC步骤。理想情况是,某个给定活动对象的所有 RTC步骤都只需要相同的 CPU周期来完成。
3.任何相关的事件生产都能增加队列的尺寸。例如,有时候 ISR 或活动对象在一个 RTC步骤内生产多个事件实例(例如, Table 活动对象偶尔生产 2 个就餐许可事件)。如果在你的应用程序中,最小的队列尺寸是关键性要求,你应该避免这类爆发性,例如,通过在许多RTC步骤上分散多个事件的产生。
简单的规则事件池的尺寸取决于你的事件队列和活动对象的和,实际取决于活着的动态事件的数量,真实的情况要比这复杂,只有在实战中慢慢理解。关于内存管理的讨论到这里就结束啦,这一篇站在应用角度上虽然少图少代码,但确实最重要,内功心法太难配图,先大体有个了解,当在实际应用时慢慢体会这些理论。