QK-nano内核:嵌入式系统的高效解决方案
1. 引言
在嵌入式系统开发中,实时性和资源利用效率是至关重要的。QK-nano内核作为一种轻量级的实时内核,能够在资源受限的微控制器单元(MCU)上提供高效的任务调度和管理。本文将详细介绍QK-nano内核的相关特性、接口、调度器、中断处理以及优先级天花板互斥锁机制,并通过PELICAN交叉路口示例展示其实际应用。
2. MCU的栈内存与任务嵌套
MCU需要有足够的栈内存来支持至少两级的任务嵌套,这样高优先级的活动对象才能至少一次抢占低优先级的活动对象。如果栈内存不足,就无法充分利用抢占式内核的优势。此外,C或C++编译器需要能够生成可重入代码,特别是要能在栈上分配自动变量。一些较旧的CPU架构,如8位PIC MCU,虽然可能有足够的RAM,但由于其栈架构不友好,很难运行QK-nano。
3. QK-nano接口qkn.h
要将QP-nano应用配置为使用QK-nano内核,只需在qpn_port.h头文件的末尾包含QK-nano接口qkn.h。以下是qkn.h头文件的内容:
#ifndef qkn_h
#define qkn_h
(1)
#define QK_PREEMPTIVE
1
(2)
void QK_init(void);
(3)
void QK_schedule_(void) Q_REENTRANT;
(4)
void QK_onIdle(void);
(5)
extern uint8_t volatile QK_currPrio_;
/* current QK priority */
#ifndef QF_ISR_NEST
(6)
#define QK_SCHEDULE_() \
(7)
if (QF_readySet_ != (uint8_t)0) { \
QK_schedule_(); \
} else ((void)0)
#else
(8)
extern uint8_t volatile QK_intNest_;
/* interrupt nesting level */
#define QK_SCHEDULE_() \
(9)
if ((QF_readySet_ != (uint8_t)0) && (QK_intNest_ == (uint8_t)0)) { \
QK_schedule_(); \
} else ((void)0)
#endif
#ifdef QK_MUTEX
(10)
typedef uint8_t QMutex;
(11)
QMutex QK_mutexLock
(uint8_t prioCeiling);
(12)
void
QK_mutexUnlock(QMutex mutex);
#endif
/* QK_MUTEX */
#endif
/* qkn_h */
3.1 宏和函数说明
- QK_PREEMPTIVE :该宏用于配置QF-nano实时框架使用QK-nano抢占式内核,而非协作式的“普通”内核。
- QK_init() :该函数用于执行CPU特定的初始化(如果需要)。此函数是可选的,并非所有QK-nano端口都需要实现它。但如果提供了该函数,应用程序必须在BSP初始化期间调用它。
- QK_schedule_() :该函数实现了QK-nano调度器。调度器总是在临界区被调用,但可能会在内部解锁中断以启动任务。
- QK_onIdle() :该回调函数允许应用程序自定义空闲处理。
- QK_currPrio_ :全局变量,代表当前运行任务的全局系统优先级。由于它可能在ISR中异步更改,因此被声明为volatile。
- QK_SCHEDULE_() :该宏封装了在中断退出时调用QK-nano调度器的操作,以处理异步抢占。
- QK_intNest_ :全局变量,代表全局系统的中断嵌套级别。由于它可能在ISR中异步更改,因此被声明为volatile。
- QK_MUTEX :当定义该宏时,qkn.h定义了优先级天花板互斥锁接口。
4. 启动活动对象和QK-nano空闲循环
以下是启动活动对象和QK-nano空闲循环的代码:
/* Global-scope objects -------------------------------------------------*/
(1)
uint8_t volatile QK_currPrio_ = (uint8_t)(QF_MAX_ACTIVE + 1);
#ifdef QF_ISR_NEST
(2)
uint8_t volatile QK_intNest_;
/* start with nesting level of 0 */
#endif
(3)
extern QActiveCB const Q_ROM * Q_ROM_VAR QF_pCB_; /* ptr to AO control block */
/* local objects --------------------------------------------------------*/
(4)
static QActive *l_act;
/* pointer to AO */
/*......................................................................*/
void QF_run(void) {
/* trigger initial transitions in all registered active objects... */
(5)
static uint8_t p;
/* declared static to save stack space */
(6)
for (p = (uint8_t)1; p <= (uint8_t)QF_MAX_ACTIVE; ++p) {
l_act = (QActive *)Q_ROM_PTR(QF_active[p].act);
l_act->prio = p;
#ifndef QF_FSM_ACTIVE
QHsm_init((QHsm *)l_act);
/* initial transition */
#else
QFsm_init((QFsm *)l_act);
/* initial transition */
#endif
}
QF_INT_LOCK();
(7)
QK_currPrio_ = (uint8_t)0;
/* set the priority for the QK idle loop */
(8)
QK_SCHEDULE_();
/* process all events produced so far */
QF_INT_UNLOCK();
(9)
QF_onStartup();
/* invoke startup callback
*/
(10)
for (;;) {
/* enter the QK idle loop */
(11)
QK_onIdle();
/* invoke the on-idle callback */
}
}
4.1 代码说明
- QK_currPrio_ :初始化为高于任何活动对象的优先级,以在整个初始过渡期间锁定QK-nano调度器。
- QK_intNest_ :仅在允许中断嵌套时需要,初始嵌套级别为0。
- QF_pCB_ :指向活动对象控制块的指针。
- l_act :指向活动对象的指针。
-
QF_run()
:
- 触发所有注册活动对象的初始转换。
- 将QK-nano优先级降低到空闲循环级别。
- 调用QK-nano调度器处理所有已产生的事件。
- 调用QF_onStartup()回调函数配置并启动中断。
- 进入QK-nano空闲循环,不断调用QK_onIdle()回调函数,使应用程序能够将CPU置于低功耗睡眠模式。
4.2 空闲处理差异
作为抢占式内核,QK-nano的空闲处理与非抢占式的普通内核不同。QK_onIdle()回调函数总是在中断解锁的情况下被调用,不需要解锁中断。此外,在QK_onIdle()内过渡到低功耗睡眠模式不需要在中断锁定的情况下进行,因为只要有事件可处理,抢占式内核就不会将上下文切换回空闲循环,因此这种过渡是安全的,不会导致竞态条件。
5. QK-nano调度器
调度器是QK-nano内核最重要的部分。QK调度器在以下两个时刻被调用:
1. 当事件被发布到活动对象的事件队列时(同步抢占)。
2. 在ISR处理结束时(异步抢占)。
以下是QK-nano调度器的实现代码:
(1)
void QK_schedule_(void) Q_REENTRANT {
static uint8_t const Q_ROM Q_ROM_VAR log2Lkup[] = {
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4
};
static uint8_t const Q_ROM Q_ROM_VAR invPow2Lkup[] = {
0xFF, 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F
};
(2)
uint8_t p;
/* the new highest-priority active object ready to run */
/* determine the priority of the highest-priority AO ready to run */
#if (QF_MAX_ACTIVE > 4)
if ((QF_readySet_ & 0xF0) != 0) {
/* upper nibble used? */
(3)
p = (uint8_t)(Q_ROM_BYTE(log2Lkup[QF_readySet_ >> 4]) + 4);
}
else
/* upper nibble of QF_readySet_ is zero */
#endif
{
(4)
p = Q_ROM_BYTE(log2Lkup[QF_readySet_]);
}
(5)
if (p > QK_currPrio_) {
/* is the new priority higher than the current? */
(6)
uint8_t pin = QK_currPrio_;
/* save the initial priority */
(7)
do {
(8)
QK_currPrio_ = p;
/* new priority becomes the current priority */
(9)
QF_INT_UNLOCK();
/* unlock interrupts to launch the new task */
/* dispatch to HSM (execute the RTC step) */
#ifndef QF_FSM_ACTIVE
(10)
QHsm_dispatch((QHsm *)Q_ROM_PTR(QF_active[p].act));
#else
(11)
QFsm_dispatch((QFsm *)Q_ROM_PTR(QF_active[p].act));
#endif
(12)
QF_INT_LOCK();
/* set cb and a again, in case they change over the RTC step */
(13)
QF_pCB_ = &QF_active[p];
(14)
l_act = (QActive *)Q_ROM_PTR(QF_pCB_->act);
(15)
if ((--l_act->nUsed) == (uint8_t)0) {/*is queue becoming empty? */
/* clear the ready bit */
(16)
QF_readySet_ &= Q_ROM_BYTE(invPow2Lkup[p]);
}
else {
(17)
Q_SIG(l_act) =
((QEvent *)Q_ROM_PTR(QF_pCB_->queue))[l_act->tail].sig;
#if (Q_PARAM_SIZE != 0)
(18)
Q_PAR(l_act) =
((QEvent *)Q_ROM_PTR(QF_pCB_->queue))[l_act->tail].par;
#endif
(19)
if (l_act->tail == (uint8_t)0) {
/* wrap around? */
(20)
l_act->tail = Q_ROM_BYTE(QF_pCB_->end); /* wrap the tail */
}
(21)
--l_act->tail;
/* always decrement the tail */
}
/* determine the highest-priority AO ready to run */
(22)
if (QF_readySet_ != (uint8_t)0) {
#if (QF_MAX_ACTIVE > 4)
(23)
if ((QF_readySet_ & 0xF0) != 0) {
/* upper nibble used? */
(24)
p = (uint8_t)(Q_ROM_BYTE(log2Lkup[QF_readySet_ >> 4])+ 4);
}
else
/* upper nibble of QF_readySet_ is zero */
#endif
{
(25)
p = Q_ROM_BYTE(log2Lkup[QF_readySet_]);
}
}
else {
(26)
p = (uint8_t)0;
/* break out of the loop */
}
(27)
} while (p > pin);
/* is the new priority higher than initial? */
(28)
QK_currPrio_ = pin;
/* restore the initial priority */
}
(29)
}
5.1 调度器说明
- QK_schedule_() :该函数必须是可重入的。调度器总是在中断锁定的情况下被调用,但可能需要在内部解锁中断以启动任务。
- p :栈变量,用于保存新的最高优先级活动对象的优先级。由于每个抢占级别都需要计算一个独立的最高优先级活动对象,因此该变量必须分配在栈上。
- 查找最高优先级活动对象 :通过二进制对数查找表快速找到具有非空事件队列的最高优先级活动对象。
- 启动任务条件 :只有当新优先级高于当前执行任务的保存优先级时,QK调度器才能启动任务。
-
处理抢占
:
- 保存当前优先级到栈变量pin。
- 进入do-while循环,只要调度器找到优先级高于初始优先级pin的就绪任务,就继续循环。
- 将当前QK-nano优先级提高到即将启动的最高优先级任务的级别。
- 解锁中断以启动新的RTC任务。
- 将当前事件分发到活动对象状态机。
- 锁定中断以更新活动对象事件队列的状态。
- 检查队列是否为空,如果为空则清除QF_readySet_中的相应位。
- 复制下一个事件到状态机内的当前事件。
- 检查尾索引是否需要回绕,并始终递减尾索引。
- 找到下一个最高优先级的就绪活动对象。
- 如果QF_readySet_为空,将p设置为0以终止循环。
- 循环结束后,将当前QK-nano优先级恢复到初始级别。
- 调度器总是在中断锁定的情况下返回。
5.2 递归特性
QK-nano调度器是一个间接递归函数。调度器调用任务函数,任务函数可能会向其他任务发布事件,从而再次调用调度器(同步抢占)。调度器也可能被ISR抢占,ISR在退出时也会调用调度器(异步抢占)。然而,这种递归只有在任务优先级不断提高时才会继续。向较低或相等优先级的任务发布事件(向自身发布)会因为第(5)行的if语句而停止递归。
6. 中断处理
QK-nano通常可以与C编译器合成的ISR一起工作,大多数嵌入式C交叉编译器都支持这种方式。但与非抢占式的普通内核不同,抢占式的QK-nano内核必须在每次ISR进入和退出时得到通知,以处理潜在的异步抢占。具体的操作取决于ISR的中断锁定策略。
6.1 允许中断嵌套
当允许中断嵌套时(在qpn_port.h中定义了QF_ISR_NEST宏),QK-nano的中断处理与完整版QK相同。具体来说,中断嵌套计数器QK_intNest_必须在中断进入时递增,在中断退出时递减。只有当中断嵌套计数器为0时,才能调用QK-nano调度器,这意味着调用的ISR没有嵌套在另一个ISR之上。
6.2 不允许中断嵌套
当不允许中断嵌套时(在qpn_port.h中未定义QF_ISR_NEST宏),QK-nano的中断处理比完整版QK更简单。具体来说,当不能嵌套中断时,不需要在ISR进入时递增中断嵌套计数器(QK_intNest_),也不需要在ISR退出时递减它。实际上,当未定义QF_ISR_NEST宏时,QK_intNest_计数器甚至不可用。这种简化是因为QP-nano使用了特殊的ISR版本的事件发布函数QActive_postISR(),该函数不会调用QK-nano调度器。因此,不需要防止ISR内的同步抢占。
以下是不允许中断嵌套时的简化中断处理代码:
void interrupt YourISR(void) {
/* typically entered with interrupts locked */
/* QK_ISR_ENTRY() - empty */
Clear the interrupt source, if necessary
Execute ISR body, including calling QP-nano services, such as:
QActive_postISR() or QF_tick()
Send the EOI instruction to the interrupt controller, if necessary
QK_SCHEDULE_();
/* QK_ISR_EXIT() */
}
7. 优先级天花板互斥锁
QK-nano以与完整版QK完全相同的方式支持优先级天花板互斥锁机制。在QK-nano中,该功能默认禁用,但可以通过在qpn_port.h头文件中定义QK_MUTEX宏来启用它。
8. PELICAN交叉路口示例
“Fly ‘n’ Shoot”游戏示例展示了QP-nano的大多数特性,但未能展示QP-nano在真正小型MCU上的运行情况。PELICAN交叉路口示例应用则展示了一个非平凡的分层状态机、活动对象和ISR之间的事件交换、时间事件,甚至QK-nano抢占式内核。该应用可以运行在仅具有128字节RAM和2KB闪存ROM的MSP430-F2013超低功耗MCU上。
8.1 示例说明
该示例的代码在Texas Instruments的eZ430-F2013 USB棒上进行了测试。eZ430-F2013是一个完整的开发工具,在一个小的USB棒封装中提供了基于USB的调试器和MSP430-F2013开发板。
8.2 问题规格
PELICAN交叉路口的初始状态是汽车通行(绿灯),行人禁止通行(“禁止行走”信号)。行人必须按下路口的按钮,产生PEDS_WAITING事件,以触发交通灯的变化。响应此事件,迎面而来的汽车会得到黄灯,几秒钟后变为红灯。然后,行人会得到“行走”信号,不久后变为闪烁的“禁止行走”信号。当“禁止行走”信号停止闪烁时,汽车再次得到绿灯。在这个周期之后,交通灯不会立即响应PEDS_WAITING按钮的按下,尽管按钮会“记住”它已被按下。交通灯控制器总是在重复交通灯变化周期之前,给汽车至少几秒的绿灯时间。此外,操作员可以随时通过提供OFF事件使PELICAN交叉路口离线。在“离线”模式下,汽车得到闪烁的红灯,行人得到闪烁的“禁止行走”信号。操作员可以随时通过提供ON事件将交叉路口重新上线。
8.3 代码版本
代码包含了针对MSP430、Cortex-M3和80x86/DOS的多个版本的PELICAN交叉路口应用。对于每个目标CPU,都提供了非抢占式配置(使用普通内核)和抢占式QK-nano配置两个版本。
9. 总结
QK-nano内核是一种轻量级的实时内核,适用于资源受限的嵌入式系统。它提供了高效的任务调度、灵活的中断处理和优先级天花板互斥锁机制。通过PELICAN交叉路口示例,我们可以看到QK-nano内核在实际应用中的强大功能和优势。在开发嵌入式系统时,可以根据具体需求选择合适的内核配置,以实现最佳的性能和资源利用效率。
9.1 关键特性总结
| 特性 | 说明 |
|---|---|
| 任务嵌套 | 支持至少两级任务嵌套,高优先级活动对象可抢占低优先级活动对象 |
| 可重入代码 | C或C++编译器需生成可重入代码,在栈上分配自动变量 |
| 接口qkn.h | 配置QP-nano应用使用QK-nano内核,定义宏和函数 |
| 调度器 | 高效查找最高优先级就绪活动对象,处理同步和异步抢占 |
| 中断处理 | 根据中断嵌套情况简化处理,确保安全的异步抢占 |
| 优先级天花板互斥锁 | 支持该机制,可通过宏启用 |
9.2 流程图
graph TD;
A[开始] --> B[初始化活动对象];
B --> C[降低QK-nano优先级到空闲循环级别];
C --> D[调用调度器处理事件];
D --> E[调用QF_onStartup()启动中断];
E --> F[进入空闲循环];
F --> G[调用QK_onIdle()];
G --> F;
9.3 操作步骤总结
- 配置QP-nano应用使用QK-nano内核:在qpn_port.h头文件末尾包含qkn.h。
- 初始化活动对象:在QF_run()函数中触发所有注册活动对象的初始转换。
- 降低优先级:将QK-nano优先级降低到空闲循环级别。
- 处理事件:调用QK-nano调度器处理所有已产生的事件。
- 启动中断:调用QF_onStartup()回调函数配置并启动中断。
- 进入空闲循环:不断调用QK_onIdle()回调函数,将CPU置于低功耗睡眠模式。
- 处理中断:根据中断嵌套情况进行相应处理。
- 启用优先级天花板互斥锁:在qpn_port.h头文件中定义QK_MUTEX宏。
10. 深入分析QK-nano内核的优势
10.1 资源高效利用
QK-nano内核在资源受限的MCU上表现出色。它对栈内存的要求相对较低,只要MCU有足够的栈内存支持至少两级任务嵌套,就能实现高优先级活动对象对低优先级活动对象的抢占。这种设计使得在资源有限的情况下,也能充分发挥内核的功能,提高系统的实时性和响应能力。例如,在一些小型的传感器节点中,资源非常有限,QK-nano内核可以很好地适应这种环境,高效地处理各种任务。
10.2 灵活性与可配置性
通过qkn.h接口文件,QK-nano内核提供了丰富的配置选项。开发者可以根据具体的应用需求,选择是否使用抢占式内核、是否支持中断嵌套以及是否启用优先级天花板互斥锁等功能。这种灵活性使得QK-nano内核可以适应不同的应用场景,从简单的单任务系统到复杂的多任务实时系统都能胜任。
10.3 安全可靠的中断处理
在中断处理方面,QK-nano内核根据中断嵌套的情况进行了优化。当允许中断嵌套时,通过中断嵌套计数器QK_intNest_确保调度器的正确调用,避免了中断嵌套带来的潜在问题。当不允许中断嵌套时,简化了中断处理流程,减少了不必要的开销。这种设计保证了系统在中断处理时的安全性和可靠性,避免了竞态条件的发生。
10.4 高效的调度算法
QK-nano调度器采用了高效的算法,能够快速找到最高优先级的就绪活动对象。通过二进制对数查找表,大大提高了查找效率,减少了调度时间。同时,调度器的递归特性使得它能够处理复杂的任务抢占情况,确保系统的实时性和稳定性。
11. 实际应用中的注意事项
11.1 栈内存管理
在使用QK-nano内核时,栈内存的管理至关重要。由于任务嵌套和递归调用的存在,需要确保MCU有足够的栈内存。在设计应用程序时,要合理分配栈空间,避免栈溢出的问题。可以通过对任务的优先级和复杂度进行评估,优化栈的使用。
11.2 中断处理的一致性
在处理中断时,要确保中断处理代码的一致性。特别是在允许中断嵌套的情况下,要严格按照规定的流程进行操作,正确更新中断嵌套计数器。同时,要注意中断处理代码的执行时间,避免过长的中断处理时间影响系统的实时性。
11.3 优先级天花板互斥锁的使用
当启用优先级天花板互斥锁时,要正确使用相关的函数。在获取和释放互斥锁时,要确保优先级的正确性,避免出现优先级反转的问题。同时,要注意互斥锁的使用范围,避免过度使用导致系统性能下降。
12. 与其他内核的比较
12.1 与非抢占式内核的比较
与非抢占式的普通内核相比,QK-nano内核具有更高的实时性。非抢占式内核在任务执行过程中,即使有高优先级的任务就绪,也必须等到当前任务执行完毕才能切换。而QK-nano内核支持任务抢占,高优先级任务可以立即抢占低优先级任务的执行,大大提高了系统的响应速度。
12.2 与其他抢占式内核的比较
与其他抢占式内核相比,QK-nano内核的优势在于其轻量级的设计。它对资源的要求较低,适合在资源受限的MCU上运行。同时,QK-nano内核提供了丰富的配置选项,开发者可以根据具体需求进行定制,具有较高的灵活性。
13. 未来发展趋势
13.1 更广泛的应用场景
随着物联网和嵌入式系统的不断发展,对轻量级实时内核的需求越来越大。QK-nano内核凭借其高效的性能和灵活的配置,有望在更多的领域得到应用,如智能家居、工业自动化、医疗设备等。
13.2 性能优化
未来,QK-nano内核可能会在性能方面进行进一步的优化。例如,优化调度算法,减少调度时间;提高中断处理的效率,降低中断延迟。这些优化将使得QK-nano内核在实时性要求更高的场景中表现更加出色。
13.3 与新技术的融合
随着新技术的不断涌现,QK-nano内核可能会与人工智能、机器学习等技术进行融合。例如,利用机器学习算法优化任务调度,提高系统的智能化水平。
14. 总结与展望
14.1 总结
QK-nano内核是一种功能强大、轻量级的实时内核,适用于资源受限的嵌入式系统。它提供了高效的任务调度、灵活的中断处理和优先级天花板互斥锁机制,通过PELICAN交叉路口示例,我们可以看到它在实际应用中的强大功能和优势。
14.2 展望
在未来的嵌入式系统开发中,QK-nano内核有望发挥更大的作用。开发者可以根据具体需求,灵活配置内核,实现最佳的性能和资源利用效率。同时,随着技术的不断发展,QK-nano内核也将不断完善和优化,为嵌入式系统的发展提供更强大的支持。
14.3 操作步骤回顾
| 步骤 | 操作 |
|---|---|
| 1 | 配置QP-nano应用使用QK-nano内核:在qpn_port.h头文件末尾包含qkn.h |
| 2 | 初始化活动对象:在QF_run()函数中触发所有注册活动对象的初始转换 |
| 3 | 降低优先级:将QK-nano优先级降低到空闲循环级别 |
| 4 | 处理事件:调用QK-nano调度器处理所有已产生的事件 |
| 5 | 启动中断:调用QF_onStartup()回调函数配置并启动中断 |
| 6 | 进入空闲循环:不断调用QK_onIdle()回调函数,将CPU置于低功耗睡眠模式 |
| 7 | 处理中断:根据中断嵌套情况进行相应处理 |
| 8 | 启用优先级天花板互斥锁:在qpn_port.h头文件中定义QK_MUTEX宏 |
14.4 流程图回顾
graph TD;
A[开始] --> B[初始化活动对象];
B --> C[降低QK-nano优先级到空闲循环级别];
C --> D[调用调度器处理事件];
D --> E[调用QF_onStartup()启动中断];
E --> F[进入空闲循环];
F --> G[调用QK_onIdle()];
G --> F;
QK-nano内核:嵌入式系统高效解决方案
超级会员免费看
144

被折叠的 条评论
为什么被折叠?



