实时计算内核的中断处理、栈利用及QK内核实现解析
1. 中断处理与异步抢占
在中断处理过程中,执行EOI(End of Interrupt)指令并调用RTC调度器后,中断会被解锁,中断控制器允许所有中断级别,这符合任务级别的预期行为。不过,部分处理器架构(如ARM Cortex - M3)会将EOI和IRET(Interrupt Return)指令硬连线在一起,意味着EOI不能独立于IRET发出。在这种情况下,必须合成一个额外的虚拟中断栈帧,以确保EOI/IRET指令执行后原中断栈帧仍留在栈上,但这类CPU架构较为少见。
高优先级任务在中断解锁的状态下运行,因此它也可能被中断异步抢占,包括与低优先级任务相同级别的中断。若中断向更高优先级的任务发布事件,高优先级任务会被异步抢占,该场景会在更高的嵌套级别上递归重复。
2. 栈利用情况分析
2.1 同步抢占场景下的栈利用
同步抢占场景下的栈利用情况如下:
1. 初始时,栈指针指向低优先级任务栈帧。
2. 正常执行期间,低优先级任务向高优先级任务发布事件,调用RTC调度器,调度器栈帧被压入栈中。
3. 调度器检测到高优先级任务准备好运行,调用高优先级任务,高优先级任务栈帧被压入栈中。
4. 高优先级任务执行并向低优先级任务发布事件。
5. 事件发布触发RTC调度器,另一个调度器栈帧被压入栈中。若没有更高优先级任务准备好运行,调度器立即返回。
6. 高优先级任务执行完毕。
7. 高优先级任务返回至步骤2中调用的RTC调度器,其栈帧从栈中弹出。
8. 调度器再次检查是否有更高优先级任务,若没有则返回低优先级任务,其栈帧从栈中弹出。
9. 低优先级任务继续执行。
以下是该过程的mermaid流程图:
graph LR
A[初始:栈指针指向低优先级任务栈帧] --> B[低优先级任务发布事件,调用调度器]
B --> C[调度器检测高优先级任务,调用高优先级任务]
C --> D[高优先级任务执行并发布事件]
D --> E[事件发布触发调度器]
E --> F{是否有更高优先级任务}
F -- 否 --> G[高优先级任务执行完毕]
G --> H[高优先级任务返回调度器,栈帧弹出]
H --> I[调度器检查,返回低优先级任务,栈帧弹出]
I --> J[低优先级任务继续执行]
F -- 是 --> C
2.2 异步抢占场景下的栈利用
异步抢占场景下的栈利用情况如下:
1. 初始时,栈指针指向低优先级任务栈帧。
2. 异步事件中断处理器,硬件将中断栈帧压入栈中,中断服务例程(ISR)开始执行,可能会将更多上下文压入栈中。
3 - 5. ISR执行完毕,向中断控制器发送EOI命令。
6. 调用RTC调度器,其栈帧被压入栈中。
7. 调度器检测到高优先级任务准备好运行,启用中断并调用高优先级任务,高优先级任务栈帧被压入栈中。
8. 高优先级任务执行完毕并返回调度器,其栈帧从栈中弹出。
9. 调度器恢复并检查是否有更多高优先级任务,若没有则返回,其栈帧从栈中弹出。
10. ISR栈帧从栈中弹出,硬件执行IRET指令,中断栈帧弹出。
11. 中断返回后,低优先级任务恢复执行。
以下是该过程的mermaid流程图:
graph LR
A[初始:栈指针指向低优先级任务栈帧] --> B[异步事件中断,压入中断栈帧,ISR执行]
B --> C[ISR执行完毕,发送EOI命令]
C --> D[调用RTC调度器]
D --> E[调度器检测高优先级任务,调用高优先级任务]
E --> F[高优先级任务执行完毕,返回调度器,栈帧弹出]
F --> G[调度器检查,返回,栈帧弹出]
G --> H[ISR栈帧弹出,执行IRET指令]
H --> I[低优先级任务恢复执行]
在这两种场景中,所有上下文(中断和任务上下文)都保存在单个栈中,这使得内核必须是非阻塞的。调度器只能访问栈顶上下文,因此只能在启动新任务或恢复栈顶保存的任务上下文之间进行选择。
3. 与传统抢占式内核的比较
传统抢占式内核为每个运行的任务维护单独的栈空间,跟踪这些上下文细节并进行切换需要大量的记录工作和复杂的机制。在传统内核中,ISR将中断上下文存储在一个任务的栈上,并从另一个任务的栈中恢复上下文,中断上下文通常会在中断处理程序执行完毕后仍然保留在被抢占任务的栈上,因此难以定义中断的持续时间。
而RTC内核(如QK)将中断上下文存储在所有任务和中断共享的栈上。ISR处理后,会向中断控制器发送EOI指令并调用RTC调度器。若没有更高优先级任务准备好运行,ISR从栈中恢复上下文并执行IRET指令返回原任务;否则,调度器解锁中断并调用更高优先级任务。中断上下文同样保留在栈上。
RTC内核将ISR定义为从存储中断上下文到发出EOI指令并在RTC调度器中启用中断的时间段,这种定义更精确和通用。此外,RTC内核在以下方面具有优势:
| 比较项 | RTC内核 | 传统抢占式内核 |
| ---- | ---- | ---- |
| RAM使用 | 可以使用比典型阻塞内核少得多的RAM,因为任务没有私有栈,避免了暂停任务时的未使用私有栈空间 | 需要更多RAM,为每个任务维护单独的栈空间 |
| 上下文切换 | 上下文切换(尤其是同步抢占)涉及的栈空间和CPU开销比传统内核少,异步抢占也通常使用更少的栈空间和CPU周期 | 上下文切换需要保存所有CPU寄存器,且通常按严格顺序一次性保存到私有任务栈上,开销较大 |
| 编译器优化 | 可以利用C编译器的能力,优化中断栈帧,只保存实际被修改的寄存器 | 基本ISR入口和出口序列通常无法满足需求,编译器难以进行有效优化 |
4. QK内核的实现
QK是一个轻量级、基于优先级的RTC内核,专为QF实时框架提供抢占式多任务处理能力。它不是独立的内核,而是QF的一个附加组件。
4.1 QK源代码组织
QK的C语言版本源代码组织如下:
<qp>\qpc\
- QP/C root directory (<qp>\qpcpp for QP/C++)
|
+-include\
- QP platform-independent header files
| +-qk.h
- QK platform-independent interface
| +-. . .
|
+-qk\
- QK preemptive kernel
| +-source\
- QK platform-independent source code (*.C files)
| | +-qk_pkg.h
- internal, interface for the QK implementation
| | +-qk.c
- definitionofQK_getVersion()andQActive_start()
| | +-qk_sched.c
- definition of QK_schedule_()
| | +-qk_mutex.c
- definition of QK_mutexLock()/QK_mutexUnlock()
| | +-qk_ext.c
- definition of QK_scheduleExt_()
| |
| +-lint\
- QK options for lint
|
+-opt_qk.lnt
- PC-lint options for linting QK
|
+-ports\
- Platform-specific QP ports
| +-80x86\
- Ports to the 80x86 processor
| | +-qk\
- Ports to the QK preemptive kernel
| | | +-tcpp101\
- Ports with the Turbo C++ 1.01 compiler
| | |
+-l\
- Ports using the Large memory model
| | |
+-dbg\
- Debug build
| | |
| +-qf.lib
– QF library
| | |
| +-qep.lib
– QEP library
| | |
+-rel\
- Release build
| | |
+-spy\
- Spy build (with software instrumentation)
| | |
+-make.bat
– batch script for building the QP libraries
| | |
+-qep_port.h
– QEP platform-dependent include file
| | |
+-qf_port.h
– QF platform-dependent include file
| | |
+-qk_port.h
– QK platform-dependent include file
| | |
+-qs_port.h
– QS platform-dependent include file
| | |
+-qp_port.h
– QP platform-dependent include file
| +-cortex-m3\
- Ports to the Cortex-M3 processor
| | +-qk\
- Ports to the QK preemptive kernel
| | | +-iar\
- Ports with the IAR compiler
| |
+-examples\
- Platform-specific QP examples
| +-80x88\
- Examples for the 80x86 processor
| | +-qk\
- Examples for the QK preemptive kernel
| | | +- . . .
| +-cortex-m3\
- Examples for the Cortex-M3 processor
| | +-qk\
- Examples for the QK preemptive kernel
| | | +- . . .
| +- . . .
4.2 qk.h头文件解析
qk.h头文件将QK内核与QF框架集成,它使用了QF的一些基本构建块,包括原生QF事件队列、内存池和优先级集。以下是qk.h头文件的部分关键内容:
#ifndef qk_h
#define qk_h
(1)
#include "qequeue.h"
/* The QK kernel uses the native QF event queue */
(2)
#include "qmpool.h"
/* The QK kernel uses the native QF memory pool */
(3)
#include "qpset.h"
/* The QK kernel uses the native QF priority set */
/* public-scope objects */
(4)
extern QPSet64 volatile QK_readySet_;
/**< QK ready-set */
(5)
extern uint8_t volatile QK_currPrio_;
/**< current task/interrupt priority */
(6)
extern uint8_t volatile QK_intNest_;
/**< interrupt nesting level */
/***************************************************************************************/
/* QF configuration for QK */
(7)
#define QF_EQUEUE_TYPE
QEQueue
#if defined(QK_TLS) || defined(QK_EXT_SAVE)
(8)
#define QF_OS_OBJECT_TYPE
uint8_t
(9)
#define QF_THREAD_TYPE
void *
#endif
/* QK_TLS || QK_EXT_SAVE */
/* QK active object queue implementation...................................*/
(10)
#define QACTIVE_EQUEUE_WAIT_(me_) \
Q_ASSERT((me_)->eQueue.frontEvt != (QEvent *)0)
(11)
#define QACTIVE_EQUEUE_SIGNAL_(me_) \
(12)
QPSet64_insert(&QK_readySet_, (me_)->prio); \
(13)
if (QK_intNest_ == (uint8_t)0) { \
(14)
QK_SCHEDULE_(); \
} \
else ((void)0)
(15)
#define QACTIVE_EQUEUE_ONEMPTY_(me_) \
QPSet64_remove(&QK_readySet_, (me_)->prio)
/* QK event pool operations...............................................*/
(16)
#define QF_EPOOL_TYPE_
QMPool
(17)
#define QF_EPOOL_INIT_(p_, poolSto_, poolSize_, evtSize_) \
QMPool_init(&(p_), poolSto_, poolSize_, evtSize_)
(18)
#define QF_EPOOL_EVENT_SIZE_(p_)
((p_).blockSize)
(19)
#define QF_EPOOL_GET_(p_, e_)
((e_) = (QEvent *)QMPool_get(&(p_)))
(20)
#define QF_EPOOL_PUT_(p_, e_)
(QMPool_put(&(p_), (e_)))
(21)
void QK_init(void);
/* QK initialization */
(22)
void QK_onIdle(void);
/* QK idle callback */
(23)
char const Q_ROM * Q_ROM_VAR QK_getVersion(void);
(24)
typedef uint8_t QMutex;
/* QK priority-ceiling mutex */
(25)
QMutex QK_mutexLock(uint8_t prioCeiling);
(26)
void QK_mutexUnlock(QMutex mutex);
/* QK scheduler and extended scheduler */
(27)
#ifndef QF_INT_KEY_TYPE
(28)
void QK_schedule_(void);
(29)
void QK_scheduleExt_(void);
/* QK extended scheduler */
(30)
#define QK_SCHEDULE_()
QK_schedule_()
#else
(31)
void QK_schedule_(QF_INT_KEY_TYPE intLockKey);
(32)
void QK_scheduleExt_(QF_INT_KEY_TYPE intLockKey); /* extended scheduler */
(33)
#define QK_SCHEDULE_()
QK_schedule_(intLockKey_)
#endif
#endif
/* qk_h */
QK内核使用QEQueue作为活动对象的事件队列,QMPool作为QF事件池。QK内核从不阻塞,调度器仅在确定事件队列至少包含一个事件时才调用QActive_get_()。QK的一些关键宏和函数的作用如下:
-
QACTIVE_EQUEUE_WAIT_()
:断言事件队列不为空。
-
QACTIVE_EQUEUE_SIGNAL_()
:当事件发布到空队列时调用,将活动对象的优先级插入到就绪集QK_readySet_中,并在任务级别调用QK调度器。
-
QACTIVE_EQUEUE_ONEMPTY_()
:当队列变空时,从就绪集QK_readySet_中移除活动对象的优先级。
-
QK_init()
:QK初始化,在QF_init()中调用,在QK端口级别定义。
-
QK_onIdle()
:QK空闲循环调用的回调函数,用于自定义空闲处理。
-
QK_getVersion()
:获取QK内核的当前版本。
-
QK_mutexLock()
和
QK_mutexUnlock()
:分别用于互斥锁的加锁和解锁操作。
QK内核使用与QF实时框架相同的临界区机制,通过锁定和解锁中断来保护代码的临界区。QK调度器总是从临界区调用,但可能需要在内部解锁中断。
5. QK内核的关键数据元素及宏定义详解
QK内核有几个关键的全局变量和宏定义,它们在整个内核的运行中起着重要作用。
-
全局变量
:
-
QK_readySet_
:是一个优先级集,用于维护所有活动对象事件队列的全局状态。由于它可能在ISR中异步更改,所以被声明为
volatile
。
-
QK_currPrio_
:代表当前正在运行的任务或中断的全局系统优先级,同样因为可能在ISR中异步更改而被声明为
volatile
。
-
QK_intNest_
:表示全局系统的中断嵌套级别,也被声明为
volatile
。
-
宏定义
:
-
QF_EQUEUE_TYPE
:定义QK内核使用
QEQueue
作为活动对象的事件队列。
-
QF_OS_OBJECT_TYPE
和
QF_THREAD_TYPE
:在特定条件下(
QK_TLS
或
QK_EXT_SAVE
被定义)有特定的类型定义,用于相关的数据成员。
-
QACTIVE_EQUEUE_WAIT_
:确保事件队列不为空,因为QK内核调度器只有在确定队列中有事件时才会调用
QActive_get_()
。
-
QACTIVE_EQUEUE_SIGNAL_
:当事件被发布到空队列时,将活动对象的优先级插入到
QK_readySet_
中。如果事件发布发生在任务级别,会调用QK调度器来处理潜在的同步抢占。
-
QACTIVE_EQUEUE_ONEMPTY_
:当队列变空时,从
QK_readySet_
中移除活动对象的优先级。
-
QF_EPOOL_TYPE_
、
QF_EPOOL_INIT_
、
QF_EPOOL_EVENT_SIZE_
、
QF_EPOOL_GET_
和
QF_EPOOL_PUT_
:这些宏定义了QK内核使用
QMPool
作为QF事件池的相关操作。
6. QK内核的调度机制
QK内核的调度机制是其实现抢占式多任务处理的核心。调度器的主要工作是根据任务的优先级和事件队列的状态来决定哪个任务应该运行。
-
调度器的调用时机
:
- 当事件被发布到空队列时(通过
QACTIVE_EQUEUE_SIGNAL_
宏),如果是在任务级别,会调用QK调度器。
- 在ISR执行完毕,发送EOI命令后,会调用RTC调度器。
-
调度器的工作流程
:
1. 检查
QK_readySet_
,确定哪些任务准备好运行。
2. 选择优先级最高的任务。
3. 如果有更高优先级的任务准备好运行,调度器会解锁中断并调用该任务。
4. 任务执行完毕后,返回调度器,调度器再次检查是否有其他高优先级任务。
以下是一个简化的mermaid流程图来展示调度器的工作流程:
graph TD
A[开始] --> B[检查QK_readySet_]
B --> C{是否有任务准备好}
C -- 是 --> D[选择最高优先级任务]
D --> E{是否有更高优先级任务}
E -- 是 --> F[解锁中断,调用任务]
F --> G[任务执行完毕,返回调度器]
G --> B
E -- 否 --> H[继续当前任务]
H --> B
C -- 否 --> I[无任务可运行,等待]
I --> B
7. QK内核的互斥锁机制
QK内核提供了优先级天花板互斥锁机制,通过
QK_mutexLock()
和
QK_mutexUnlock()
函数来实现。
-
QK_mutexLock()
:用于获取互斥锁,参数
prioCeiling
指定了互斥锁的优先级天花板。当一个任务获取互斥锁时,它的优先级会被提升到优先级天花板,以防止低优先级任务持有互斥锁时被高优先级任务抢占,从而避免优先级反转问题。
-
QK_mutexUnlock()
:用于释放互斥锁,释放后任务的优先级会恢复到原来的级别。
以下是一个简单的表格来对比互斥锁的加锁和解锁操作:
| 操作 | 函数 | 作用 |
| ---- | ---- | ---- |
| 加锁 |
QK_mutexLock(uint8_t prioCeiling)
| 获取互斥锁,提升任务优先级到优先级天花板 |
| 解锁 |
QK_mutexUnlock(QMutex mutex)
| 释放互斥锁,恢复任务原来的优先级 |
8. 总结
综上所述,QK内核作为一个轻量级、基于优先级的RTC内核,为QF实时框架提供了强大的抢占式多任务处理能力。它在中断处理、栈利用、与传统内核的比较以及自身的实现上都有独特的优势。
- 在中断处理方面,通过合理的EOI和调度器调用,实现了高效的中断响应和任务调度。
- 栈利用上,将所有上下文保存在单个栈中,减少了RAM的使用,同时降低了上下文切换的开销。
- 与传统抢占式内核相比,QK内核在RAM使用、上下文切换和编译器优化等方面表现更优。
- 在实现上,QK内核通过合理的源代码组织、头文件定义和关键函数的实现,提供了一个稳定且高效的多任务处理解决方案。
对于开发者来说,理解QK内核的这些特性和机制,能够更好地利用它来开发实时应用程序,提高系统的性能和可靠性。在实际应用中,可以根据具体的需求,合理配置QK内核的参数和使用其提供的功能,以达到最佳的开发效果。
超级会员免费看
58

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



