1.qk和qv调度的区别
qv是以轮询调度的方式派发事件;
qk是以抢占的方式调度,优先级高的活动对象关联的事件经系统调度后会优先得到派发
2.qk调度的过程
2.1中断级调度流程
2.2中断级调度过程
①系统中断
中断级调度是有SystickHandler发起的,在中断任务中查询是否有优先级更高的活动对象关联的时间需要处理,有则触发pendsv中断,没有则正常退出。
void SysTick_Handler(void)
{
uint32_t tmp;
(void)tmp;
QK_ISR_ENTRY();
systick_ticks += 1;
tmp = SysTick->CTRL; /* clear SysTick_CTRL_COUNTFLAG /
qs_tick_time += qs_tick_period; / account for the clock rollover */
QF_TICK_X(0, &obj_systick); // 处理所有的事件定时
QK_ISR_EXIT(); // 查询是否有更高优先级的活动对象有事件要处理,触发pendsv中断
}
②pendsv中断
void PendSV_Handler(void) {
__asm volatile (
; 为清除pendsv悬起状态准备,r1是写入的数据,r3是ICSR寄存器地址
" LDR r3,=" STRINGIFY(NVIC_ICSR) “\n”
" MOVS r1,#1 \n"
" LSLS r1,r1,#27 \n"
; 进入临界区
#if (__ARM_ARCH == 6)
" CPSID i \n" ; 关闭总中断
#else
#if (ARM_FP != 0) ; 如果内核支持浮点运算
" PUSH {r0,lr} \n"
#endif
" MOVS r0,#" STRINGIFY(QF_BASEPRI) “\n”
" CPSID i \n"
" MSR BASEPRI,r0 \n" ; 设置中断屏蔽寄存器,屏蔽部分中断
" CPSIE i \n"
#endif /* M3/M4/M7 /
" STR r1,[r3] \n" ; 设置ICSR寄存器,清除pendsv悬起状态
/ The QK activator must be called in a Thread mode, while this code
* executes in the Handler mode of the PendSV exception. The switch
* to the Thread mode is accomplished by returning from PendSV using
* a fabricated exception stack frame, where the return address is
* QK_activate().
*
* NOTE: the QK activator is called with interrupts DISABLED and also
* returns with interrupts DISABLED.
*/
" LSRS r3,r1,#3 \n" ; 左移3位(1<<24), 设置EPSR的T位,使用thumb指令集
" LDR r2,=QK_activate \n" ; 加载QK_activate_到r2
" SUBS r2,r2,#1 \n" ; 按照半字对齐thumb地址(新的pc指针)
" LDR r1,=Thread_ret \n" ; 设置lr寄存器,QK_activate_执行完后返回Thread_ret执行
" SUB sp,sp,#84 \n" ; 将当前的栈帧(xpsr,pc,lr,r12,r3-r0)压入栈中
" ADD r0,sp,#54 \n" ; r0指向栈中lr地址处
" STM r0!,{r1-r3} \n" ; 将xpsr,pc,lr写入栈中
" MOVS r0,#6 \n" ; r0 = 6
" MVNS r0,r0 \n" ; 取反r0 = 0xFFFFFFF9, 设置中断特殊返回值
" BX r0 \n" ; 退出中断返回到线程模式,自动出栈,sp每次自增4按照顺序依次出栈
);
}
③QK_activate_
从pendsv中断退出后进入到QK_activate_(),此函数主要作用是派发优先级最高的活动对象的事件。如果在派发期间产生了更高优先级的活动对象事件,则连续派发。在systick中断退出时执行QK_sched_()计算了当前所有活动对象中优先级最高的,在次函数中利用此变量(QK_attr.nextPrio)进行切换
void QK_activate_(void) {
uint_fast8_t const pin = (uint_fast8_t)QK_attr_.actPrio; /* save /
uint_fast8_t p = (uint_fast8_t)QK_attr_.nextPrio; / next prio to run */
QActive a;
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
uint_fast8_t pprev;
#endif / QK_ON_CONTEXT_SW || Q_SPY */
/* QK_attr_.actPrio and QK_attr_.nextPrio must be in ragne /
Q_REQUIRE_ID(500, (pin < Q_DIM(QF_active_))
&& (0U < p)
&& (p < Q_DIM(QF_active_)));
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
pprev = pin;
#endif / QK_ON_CONTEXT_SW || Q_SPY /
QK_attr_.nextPrio = 0U; / clear for the next time /
/ loop until no more ready-to-run AOs of higher prio than the initial /
do {
QEvt const e;
a = QF_active_[p]; // 根据优先级从活动对象列表中取出活动对象
QK_attr_.actPrio = (uint8_t)p; // p作为当前活动对象的优先级
QS_BEGIN_NOCRIT_PRE_(QS_SCHED_NEXT, QS_priv_.locFilter[AO_OBJ], a)
QS_TIME_PRE_(); / timestamp /
QS_2U8_PRE_(p, / priority of the scheduled AO /
pprev); / previous priority /
QS_END_NOCRIT_PRE_()
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
if (p != pprev) { // 发生了切换?执行上下文切换回调函数
#ifdef QK_ON_CONTEXT_SW
/ context-switch callback/
QK_onContextSw(((pprev != 0U)
? QF_active_[pprev]
: (QActive )0), a);
#endif / QK_ON_CONTEXT_SW /
pprev = p; / update previous priority /
}
#endif / QK_ON_CONTEXT_SW || Q_SPY /
QF_INT_ENABLE(); // 关闭中断
e = QActive_get_(a); // 从活动对象a的事件队列中取出一个事件e
QHSM_DISPATCH(&a->super, e); // 派发事件e到状态机函数并同步执行
QF_gc(e); // 事件回收(如果是动态分配的事件,需要将内存回收至内存池)
/ determine the next highest-priority AO ready to run… */
QF_INT_DISABLE(); // 打开中断
if (a->eQueue.frontEvt == (QEvt )0) { // 检查活动对象a的时间队列是否为空?
QPSet_remove(&QK_attr_.readySet, p); // 从活动对象就绪表中移除相应的优先级位
}
QPSet_findMax(&QK_attr_.readySet, p); // 计算最高的活动对象优先级p
if (p <= pin) { // p比当前活动对象优先级更低,清0退出
p = 0U;
}
else if (p <= (uint_fast8_t)QK_attr_.lockPrio) { // 低于锁定优先级,同样退出
p = 0U; / active object not eligible /
}
else {
Q_ASSERT_ID(510, p <= QF_MAX_ACTIVE);
}
} while (p != 0U); // 优先级有效,继续重复上述操作
QK_attr_.actPrio = (uint8_t)pin; // 恢复当前活动状态优先级
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
if (pin != 0U) { / resuming an active object? /
a = QF_active_[pin]; / the pointer to the preempted AO /
QS_BEGIN_NOCRIT_PRE_(QS_SCHED_RESUME, QS_priv_.locFilter[AO_OBJ], a)
QS_TIME_PRE_(); / timestamp /
QS_2U8_PRE_(pin, / priority of the resumed AO /
pprev); / previous priority /
QS_END_NOCRIT_PRE_()
}
else { / resuming priority==0 --> idle */
a = (QActive )0; / QK idle loop */
QS_BEGIN_NOCRIT_PRE_(QS_SCHED_IDLE, (void )0, (void )0)
QS_TIME_PRE_(); / timestamp /
QS_U8_PRE_(pprev); / previous priority /
QS_END_NOCRIT_PRE_()
}
#ifdef QK_ON_CONTEXT_SW
QK_onContextSw(QF_active_[pprev], a); / context-switch callback /
#endif / QK_ON_CONTEXT_SW /
#endif / QK_ON_CONTEXT_SW || Q_SPY /
}
④Thread_ret
从QK_active_退出后,执行Thread_ret。为什么执行Thread_ret函数? 请回看步骤2关于pendsv中断的分析。Thread_ret的内容非常简单,写ICSR寄存器触发NMI中断。
attribute ((naked))
void Thread_ret(void) {
__asm volatile (
#if (__ARM_FP != 0) / if VFP available… /
" MRS r0,CONTROL \n" / r0 := CONTROL /
" BICS r0,r0,#4 \n" / r0 := r0 & ~4 (FPCA bit) /
" MSR CONTROL,r0 \n" / CONTROL := r0 (clear CONTROL[2] FPCA bit) /
" ISB \n" / ISB after MSR CONTROL (ARM AN321,Sect.4.16) /
#endif / VFP available /
/ trigger NMI to return to preempted task…
* NOTE: The NMI exception is triggered with nterrupts DISABLED
/
" LDR r0,=0xE000ED04 \n" ; 加载ICSR寄存器地址到r0
" MOVS r1,#1 \n" ; r1 = 1
" LSLS r1,r1,#31 \n" ; r1 = 1 << 31
" STR r1,[r0] \n" ; 写ICSR寄存器的bit31悬起NMI中断
" B . \n" ; 原地等待进入NMI中断,相当于while(1);
);
}
⑤NMI_Handler
NMI中断是MCU内部复位中断之外优先级最高的中断。在此处的作用是恢复现场。从线程模式(可理解为主循环)中被pendsv中断打断时,有一个自动入栈的操作。执行完调度后需要恢复现场,将进入pendsv中断时压入的栈帧恢复,这样退出NMI中断后会继续从被pendsv打断处运行。
attribute ((naked))
void NMI_Handler(void) {
__asm volatile (
" ADD sp,sp,#(84) \n" ; 移除8个寄存器的栈帧
#if (__ARM_ARCH == 6) ; Cortex-M0/M0+/M1 (v6-M, v6S-M)内核无中断BASEPRI寄存器
" CPSIE i \n" ; 打开中断
" BX lr \n" ; 退出中断, 返回到被pendsv打断处继续执行
#else ; M3/M4/M7内核有BASEPRI寄存器
" MOVS r0,#0 \n" ; r0 = 0
" MSR BASEPRI,r0 \n" ; 清除中断屏蔽寄存器
#if (__ARM_FP != 0) ; 如果是带浮点运算的内核
" POP {r0,pc} \n" ; 将r0, pc先弹出
#else ; 无浮点运算的内核
" BX lr \n" ; 退出中断,返回到被pendsv打断处继续执行
#endif / no VFP /
#endif / M3/M4/M7 /
);
}
思考一个问题:
1). 进入NMI中断后为什么首先执行ADD sp,sp,#(84)这个出栈的操作?
答:程序是从Thread_ret函数进入NMI中断的,Thread_ret是在线程模式下运行的,进入NMI中断内核会自动压入8个寄存器的栈帧,所以进入NMI中断,首先要弹出栈帧,否则退出NMI中断时,程序会回到Thread_ret函数被打断处继续执行。
2.3任务级调度
任务级调度在线程中发送事件时调用,相关的函数有QActive_post_,QActive_postLIFO和QTicker_post_。该调度的触发函数是QACTIVE_EQUEUE_SIGNAL_