操作系统中的任务状态
在FreeRTOS中存在四种任务运行状态:
- 每一个状态下对应一个双向循环链表,里面存储了对应状态的任务列表。
- 任务状态的转换实际上就是将对应任务的任务控制块从一个队列转移到另一个队列的过程。
- 处于就绪队列中的任务将会被调度,在FreeRTOS中会选择优先级最高的任务让其运行。
- 当就绪队列中的任务被调度器调度执行后,任务状态便从就绪态转为运行态,如果最高优先级的任务有多个并且使能了时间片轮询,那么操作系统会为这些任务分配时间片交替执行。
- 当前任务正在运行的时间片用完或者有更高优先级的任务被唤醒时,操作系统会进行上下文切换,此时任务的状态便从运行态转换为就绪状态。
- 正在运行的任务如果调用了阻塞的函数,设置的阻塞时间不是portMAX_DELAY,那么该任务将会被放入延时队列中,进入阻塞状态,如果阻塞时间设置为portMAX_DELAY,该任务将会被放入挂起队列中,进入挂起状态,避免该任务被周期性的唤醒。
- 处于延时队列中的任务在条件满足之后任务将会重新添加到就绪队列中,等待被操作系统调度运行。
- 正在运行的任务调用vTaskSuspend函数,将会把任务从就绪队列转移到挂起队列中,下一次进行任务切换时,调度器从就绪队列中重新查找优先级最高的任务进行调度。
- 处于挂起队列中的任务在调用vTaskResume后将会重新添加到就绪队列中,等待被操作系统调度运行。
操作系统中的上下文切换
操作系统中的上下文切换分为如下几种情况:
- 第一种是任务调用portYIELD()将PendSV的挂起位置1。
- 第二种是处于阻塞状态的任务等待超时重新被唤醒或挂起的被任务恢复,并且他们的优先级足够高,在每个SysTick中断产生之后会检测是否需要进行上下文切换,如果需要则将PendSV的挂起位置1。
- 第三种是使用了时间片轮询,当一个任务的时间片用完之后,必须要切换到下一个任务,此时SysTick中断中也会将PendSV的挂起位置1。
从上面几种情况中可以看出当需要调度器切换上下文时,必须要将PendSV的挂起位置1,这是切换上下文的前提条件。PendSV的优先级设置为最低,当系统中没有其他中断产生时候,PendSV中断被CPU响应。
#define portYIELD() \
{ \
\
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
\
\
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
- 前面讲解过Cortex-M7处理器在带操作系统运行模式下,中断中使用的栈指针和线程中使用的栈指针是不同的,在中断中使用的是MSP,在线程中使用的是PSP,当前任务的栈顶值位于PSP中,如果直接使用push会把寄存器值放入MSP所指向的栈中,这样无法完成任务切换。
- 因此第一步是通过MRS加载PSP的值到R0中,然后再加载pxTopOfStack的地址到R2中,加载R0是为了后面使用stm指令保存任务状态到栈上,而pxTopOfStack则保存了当前任务的栈顶,可用于直接访问线程栈和溢出检测等用途。
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
ldr r3, =pxCurrentTCB
ldr r2, [r3]
- 然后将需要换出的任务状态保存到栈中,同时更新一下栈顶pxTopOfStack的值,这样换出工作便完成了。
tst r14, #0x10
it eq
vstmdbeq r0!, {s16-s31}
stmdb r0!, {r4-r11, r14}
str r0, [r2]
- 进入临近段跳转到vTaskSwitchContext函数中,在这个函数中根据对应的调度算法找出需要运行的下一个任务,将pxCurrentTCB指向新任务然后退出临界段。
stmdb sp!, {r0, r3}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
cpsid i
msr basepri, r0
dsb
isb
cpsie i
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
- 然后将新任务的栈顶指针加载到R0中,把对应的寄存器从栈中恢复,同时更新新任务的pxTopOfStack值,这样换入工作便完成了。
ldmia sp!, {r0, r3}
ldr r1, [r3]
ldr r0, [r1]
ldmia r0!, {r4-r11, r14}
tst r14, #0x10
it eq
vldmiaeq r0!, {s16-s31}
msr psp, r0
isb
- 在上下文切换成功之后通过LR进行返回,新任务从上次中断的位置继续往下运行。
#ifdef WORKAROUND_PMU_CM001
#if WORKAROUND_PMU_CM001 == 1
push { r14 }
pop { pc }
nop
#endif
#endif
bx r14
}
|