QK内核调度与中断处理机制解析
1. QK调度器函数与中断锁定策略
QK调度器函数的使用依赖于所采用的中断锁定策略,而该策略由
QF_INT_KEY_TYPE
决定。具体情况如下:
- 当未定义
QF_INT_KEY_TYPE
时,采用简单的“无条件中断锁定和解锁”策略。此时,QK调度器
QK_schedule_()
无需参数,扩展QK调度器
QK_scheduleExt_()
同样无需参数。
- 当定义了
QF_INT_KEY_TYPE
时,采用“保存和恢复中断状态”策略。此时,QK调度器
QK_schedule_()
和扩展QK调度器
QK_scheduleExt_()
都需要将中断状态键作为参数。
此外,宏
QK_SCHEDULE_()
用于调用QK调度器,同时隐藏实际使用的中断策略。
2. 中断处理
2.1 中断处理概述
中断处理通常与具体应用相关,无法以平台无关的方式进行通用编程。但理解中断处理对于掌握QK内核的工作原理至关重要。在任何抢占式内核(包括QK)中,内核必须知晓中断的进入和退出。具体而言,每个中断在退出时都必须调用QK调度器,以便内核处理异步抢占。
2.2 中断服务例程(ISR)伪代码
以下是QK中ISR的伪代码:
void interrupt YourISR(void) {
/* typically entered with interrupts locked */
Clear the interrupt source, if necessary
++QK_intNest_;
/* account for one more interrupt nesting level */
Unlock interrupts (depending on the interrupt policy used)
Execute ISR body, including calling QF services, such as:
Q_NEW(), QActive_postFIFO(), QActive_postLIF(), QF_publish(), or QF_tick()
Lock interrupts, if they were unlocked in step (4)
Send the EOI instruction to the interrupt controller
–QK_intNest_;
/* account for one less interrupt nesting level */
if (QK_intNest_ == (uint8_t)0) {
/* coming back to the task level? */
QK_schedule_();
/* handle potential asynchronous preemption */
}
}
2.3 ISR执行步骤说明
-
ISR定义
:通常需要使用特殊的扩展关键字(如
interrupt)来定义ISR。进入ISR时,中断通常是锁定的,但某些处理器架构(如ARM Cortex - M3)可能不会锁定中断,需查看设备数据手册。 - 清除中断源 :若需要,应立即清除中断源。
-
增加中断嵌套级别
:通过递增全局中断嵌套级别
QK_intNest_告知QK正在处理ISR。若处理器在进入ISR时未自动锁定中断,需在递增嵌套级别前显式锁定中断。 -
解锁中断
:根据所使用的中断锁定策略和中断控制器的情况,可能需要在此处解锁中断。步骤3和4构成了QK特定的中断进入操作,可封装在宏
QK_ISR_ENTRY()中。 - 执行ISR主体 :执行ISR主体,包括调用QF服务。需注意,这些服务内部使用临界区,若中断锁定策略不支持临界区嵌套,必须确保中断未锁定。
- 锁定中断 :若在步骤4中解锁了中断,需在此处锁定中断,以确保后续代码原子执行。
- 发送EOI指令 :向中断控制器发送EOI指令,通知硬件停止对该中断级别的优先级处理。
-
减少中断嵌套级别
:递减中断嵌套级别
QK_intNest_,表示离开中断。 -
检查是否返回任务级别
:若中断嵌套级别为0,说明返回任务级别,调用QK调度器处理潜在的异步抢占。步骤6 - 10构成了QK特定的中断退出操作,可封装在宏
QK_ISR_EXIT()中。
2.4 中断处理时间线
中断处理和异步抢占的时间线具有以下特点:
-
中断响应速度
:QK内核下的中断响应速度与其他抢占式内核相当,主要受系统中最长临界区和硬件将中断上下文保存到堆栈所需时间的影响。
-
高优先级任务响应速度
:高优先级任务的任务级响应通常比传统抢占式内核更快,因为无需从堆栈完全恢复中断上下文,也无需执行中断返回指令来启动高优先级任务,而是通过函数调用实现,通常比恢复整个寄存器集并执行IRET指令快得多。
以下是中断处理流程的mermaid流程图:
graph TD;
A[进入ISR] --> B[清除中断源];
B --> C[增加中断嵌套级别];
C --> D[解锁中断];
D --> E[执行ISR主体];
E --> F[锁定中断];
F --> G[发送EOI指令];
G --> H[减少中断嵌套级别];
H --> I{嵌套级别是否为0};
I -- 是 --> J[调用QK调度器];
I -- 否 --> K[退出ISR];
J --> K;
3. qk_sched.c源文件(QK调度器)
3.1 QK调度器概述
qk_sched.c
源文件实现了QK调度器,这是QK内核的核心部分。QK调度器在两种情况下被调用:一是当事件被发布到活动对象的事件队列时(同步抢占);二是在ISR处理结束时(异步抢占)。
3.2 QK调度器的工作原理
QK调度器是一个普通的C函数
QK_schedule_()
,其任务是高效地找到准备运行的最高优先级活动对象,并在其优先级高于当前服务的QK优先级时执行该对象。为完成此任务,QK调度器依赖两个数据元素:
-
QK_readySet_
:表示准备运行的任务集合,类型为
QPSet64
,最多可表示64个编号为1到64的元素。
QK_readySet_
中的每个位代表一个QF活动对象,若优先级为n的活动对象的事件队列不为空,则
QK_readySet_
中第n位为1;反之则为0。
-
QK_currPrio_
:表示当前服务的优先级,类型为
uint8_t
。
3.3 QK调度器实现代码
#include "qk_pkg.h"
/* Public-scope objects -------------------------------------------------*/
QPSet64 volatile QK_readySet_;
/* QK ready-set */
/* start with the QK scheduler locked */
uint8_t volatile QK_currPrio_ = (uint8_t)(QF_MAX_ACTIVE + 1);
uint8_t volatile QK_intNest_;
/* start with nesting level of 0 */
/*......................................................................*/
/* NOTE: the QK scheduler is entered and exited with interrupts LOCKED */
#ifndef QF_INT_KEY_TYPE
void QK_schedule_(void) {
#else
void QK_schedule_(QF_INT_KEY_TYPE intLockKey_) {
#endif
uint8_t p;
/* the QK scheduler must be called at task level only */
Q_REQUIRE(QK_intNest_ == (uint8_t)0);
if (QPSet64_notEmpty(&QK_readySet_)) {
/* determine the priority of the highest-priority task ready to run */
QPSet64_findMax(&QK_readySet_, p);
if (p > QK_currPrio_) {
/* do we have a preemption? */
uint8_t pin = QK_currPrio_;
/* save the initial priority */
QActive *a;
#ifdef QK_TLS
/* thread-local storage used? */
uint8_t pprev = pin;
#endif
do {
QEvent const *e;
a = QF_active_[p];
/* obtain the pointer to the AO */
QK_currPrio_ = p;
/* this becomes the current task priority */
#ifdef QK_TLS
/* thread-local storage used? */
if (p != pprev) {
/* are we changing threads? */
QK_TLS(a);
/* switch new thread-local storage */
pprev = p;
}
#endif
QK_INT_UNLOCK_();
/* unlock the interrupts */
e = QActive_get_(a);
/* get the next event for this AO */
QF_ACTIVE_DISPATCH_(&a->super, e);
/* dispatch to the AO */
QF_gc(e);
/* garbage collect the event, if necessary */
QK_INT_LOCK_();
/* determine the highest-priority AO ready to run */
if (QPSet64_notEmpty(&QK_readySet_)) {
QPSet64_findMax(&QK_readySet_, p);
}
else {
p = (uint8_t)0;
}
} while (p > pin);
/* is the new priority higher than initial? */
QK_currPrio_ = pin;
/* restore the initial priority */
#ifdef QK_TLS
/* thread-local storage used? */
if (pin != (uint8_t)0) {
/* no extended context for idle loop */
a = QF_active_[pin];
QK_TLS(a);
/* restore the original TLS */
}
#endif
}
}
}
3.4 QK调度器代码说明
-
包含头文件
:包含
qk_pkg.h头文件,该文件包含平台特定的QK端口头文件qk_port.h,并定义了一些仅在QK内部共享的内部宏和对象。 -
全局变量
:
-
QK_readySet_:维护所有活动对象事件队列的全局状态。 -
QK_currPrio_:表示当前运行任务或中断的全局系统优先级。 -
QK_intNest_:表示全局系统中断嵌套级别。
-
-
函数签名
:根据
QF_INT_KEY_TYPE的定义情况,QK_schedule_()函数的签名有所不同。未定义时无需参数,定义时需要中断状态键作为参数。 - 调用条件 :QK调度器应仅在任务级别调用。
-
处理事件
:若
QK_readySet_不为空,说明QK内核有事件需要处理,通过优先级集快速找到最高优先级的非空事件队列。 - 抢占条件 :只有新优先级高于当前执行任务的优先级时,QK调度器才能抢占当前运行的任务。
- 递归特性 :QK调度器是间接递归函数,但递归仅在任务优先级不断增加时继续,向较低或相等优先级任务(包括向自身)发布事件会停止递归。
-
处理抢占
:为处理抢占,先将当前QK优先级保存到栈变量
pin中,然后提高当前优先级。 -
线程本地存储(TLS)
:若定义了宏
QK_TLS,QK内核管理线程本地存储。在任务优先级变化时,切换TLS。 - 执行任务 :解锁中断,获取活动对象的下一个事件,将事件分派给活动对象,并进行垃圾回收。
-
检查新优先级
:锁定中断,再次检查最高优先级的活动对象。若
QK_readySet_不为空,找到新的最高优先级;若为空,将p设为0以终止循环。 - 恢复优先级和TLS :循环结束后,将当前QK优先级恢复到初始级别,若有任务被抢占,恢复TLS。
- 返回状态 :QK调度器始终在中断锁定状态下返回。
3.5 QK调度器工作流程总结
| 步骤 | 操作 |
|---|---|
| 1 |
检查
QK_readySet_
是否为空
|
| 2 | 若不为空,找到最高优先级非空事件队列 |
| 3 | 判断新优先级是否高于当前优先级 |
| 4 | 若高于,保存当前优先级,进入循环处理高优先级任务 |
| 5 | 循环中,切换TLS(若启用),解锁中断,执行任务,锁定中断,检查新优先级 |
| 6 | 循环结束,恢复优先级和TLS |
| 7 | 返回 |
4. qk.c源文件(QK启动和空闲循环)
4.1 源文件概述
qk.c
源文件定义了QK的初始化、清理、启动和空闲循环等操作。
4.2 主要函数及功能
4.2.1
QF_init()
函数
void QF_init(void) {
/* nothing to do for the QK preemptive kernel */
QK_init();
/* might be defined in assembly */
}
-
功能
:对于QK抢占式内核,此函数本身无实际操作,但会调用
QK_init()函数,该函数可能在汇编中定义,用于让QK内核进行初始化。
4.2.2
QF_stop()
函数
void QF_stop(void) {
QF_onCleanup();
/* cleanup callback */
/* nothing else to do for the QK preemptive kernel */
}
-
功能
:停止QF框架的执行。对于QK抢占式内核,仅调用
QF_onCleanup()回调函数进行清理操作,无其他额外操作。
4.2.3
QF_run()
函数
void QF_run(void) {
QK_INT_LOCK_KEY_
QK_INT_LOCK_();
QK_currPrio_ = (uint8_t)0;
/* set the priority for the QK idle loop */
QK_SCHEDULE_();
/* process all events produced so far */
QK_INT_UNLOCK_();
QF_onStartup();
/* startup callback */
for (;;) {
/* the QK idle loop */
QK_onIdle();
/* invoke the QK on-idle callback */
}
}
-
功能
:该函数实现了QK内核的启动和空闲循环。具体步骤如下:
- 锁定中断。
-
将
QK_currPrio_设置为0,对应QK空闲循环的优先级。初始时QK_currPrio_为QF_MAX_ACTIVE + 1,此值会锁定QK调度器,设置为0后调度器可正常工作。 -
调用
QK_SCHEDULE_()处理在活动对象初始化期间可能发布的所有事件,调用时中断处于锁定状态。 - 解锁中断。
-
调用
QF_onStartup()回调函数,通常在应用层(如BSP)实现,用于配置和启动中断。 -
进入QK空闲循环,不断调用
QK_onIdle()回调函数,该函数也通常在应用层实现,可用于将CPU置于低功耗睡眠模式或进行其他处理(如软件跟踪输出)。
4.2.4
QActive_start()
函数
void QActive_start(QActive *me, uint8_t prio,
QEvent const *qSto[], uint32_t qLen,
void *tls,
uint32_t flags,
QEvent const *ie)
{
Q_REQUIRE(((uint8_t)0 < prio) && (prio <= (uint8_t)QF_MAX_ACTIVE));
QEQueue_init(&me->eQueue, qSto, (QEQueueCtr)qLen);
me->prio = prio;
QF_add_(me);
/* make QF aware of this active object */
#if defined(QK_TLS) || defined(QK_EXT_SAVE)
me->osObject = (uint8_t)flags;
/* osObject contains the thread flags */
me->thread = tls;
/* contains the pointer to the thread-local storage */
#else
Q_ASSERT((tls == (void *)0) && (flags == (uint32_t)0));
#endif
QF_ACTIVE_INIT_(&me->super, ie);
/* execute initial transition */
}
-
功能
:启动一个活动对象。具体步骤如下:
-
检查优先级
prio是否在有效范围内(大于0且小于等于QF_MAX_ACTIVE)。 -
初始化活动对象的事件队列
me->eQueue。 -
设置活动对象的优先级
me->prio。 -
调用
QF_add_()函数,让QF框架知晓该活动对象。 -
若定义了
QK_TLS或QK_EXT_SAVE,将线程标志存储在me->osObject中,将线程本地存储指针存储在me->thread中;否则,断言tls和flags为0。 -
调用
QF_ACTIVE_INIT_()函数执行活动对象的初始转换。
-
检查优先级
4.2.5
QActive_stop()
函数
void QActive_stop(QActive *me) {
QF_remove_(me);
/* remove this active object from the QF */
}
-
功能
:停止一个活动对象,通过调用
QF_remove_()函数将该活动对象从QF框架中移除。
4.3 QK启动和运行流程总结
以下是QK启动和运行的mermaid流程图:
graph TD;
A[调用QF_init()] --> B[调用QK_init()];
B --> C[调用QF_run()];
C --> D[锁定中断];
D --> E[设置QK_currPrio_为0];
E --> F[调用QK_SCHEDULE_()];
F --> G[解锁中断];
G --> H[调用QF_onStartup()];
H --> I[进入QK空闲循环];
I --> J[调用QK_onIdle()];
J --> I;
| 步骤 | 操作 |
|---|---|
| 1 |
调用
QF_init()
进行初始化,调用
QK_init()
|
| 2 |
调用
QF_run()
启动QK内核
|
| 3 | 锁定中断 |
| 4 |
设置
QK_currPrio_
为0,解锁QK调度器
|
| 5 |
调用
QK_SCHEDULE_()
处理初始化期间的事件
|
| 6 | 解锁中断 |
| 7 |
调用
QF_onStartup()
配置和启动中断
|
| 8 |
进入QK空闲循环,不断调用
QK_onIdle()
|
综上所述,QK内核的调度器和中断处理机制紧密配合,通过合理的中断锁定策略、高效的调度算法和完善的启动与空闲循环机制,确保系统能够稳定、高效地处理各种事件和任务。在实际应用中,开发者可以根据具体需求灵活配置和使用这些机制,以满足不同场景的要求。
超级会员免费看
51

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



