FreeRTOS 系统的任务切换最终都是在 PendSV 中断服务函数中完成的,uCOS 也是在 PendSV 中断中完成任务切换的。
【为什么用PendSV异常来做任务切换】
PendSV 可以像普通中断一样被 Pending(往 NVIC 的 PendSV 的 Pend 寄存器写 1),常用的场合是 OS 进行上下文切换;它可以手动拉起后,等到比他优先级更高的中断完成后,再执行;
假设,带 OS 系统的 CM3 中有两个就绪的任务,上下文切换可以发生在 SYSTICK 中断中:
这里展现的是两个任务 A 和 B 轮转调度的过程;但是,如果在产生 SYSTICK 异常时,系统正在响应一个中断,则 SYSTICK 异常会抢占其他 ISR。在这种情况下 OS 是不能执行上下文切换的,否则将使得中断请求被延迟;
而且,如果在 SYSTICK 中做任务切换,那么就会尝试切入线程模式,将导致用法 fault 异常;因为正常来说,即使SYSTICK优先级比IRQ高,当SYSTICK执行完后,也应该是回到低优先级的中断处理函数IRQ里而不是直接切换到任务去运行了,这时候IRQ都没运行完呢,怎么能中断函数都没处理完就去运行主程序呢?自然是不允许这样的。
为了解决这种问题,早期的 OS 在上下文切换的时候,检查是否有中断需要响应,没有的话,采取切换上下文,然而这种方法的问题在于,可能会将任务切换的动作拖延很久(如果此次的 SYSTICK 无法切换上下文,那么要等到下一次 SYSTICK 再来切换),严重的情况下,如果某 IRQ 来的频率和 SYSTICK 来的频率比较接近的时候,会导致上下文切换迟迟得不到进行;
引入 PendSV 以后,可以将 PendSV 的异常优先级设置为最低,在 PendSV 中去切换上下文,PendSV 会在其他 ISR 得到相应后,立马执行:
上图的过程可以描述为:
1、任务 A 呼叫 SVC 请求任务切换;
2、OS 收到请求,准备切换上下文,手动 Pending 一个 PendSV;
3、CPU 退出 SVC 的 ISR 后,发现没有其他 IRQ 请求,便立即进入 PendSV 执行上下文切换;
4、正确的切换到任务 B;
5、此刻发生了一个中断,开始执行此中断的 ISR;
6、ISR 执行一半,SYSTICK 来了,抢占了该 IRQ;
7、OS 执行一些逻辑,并手动 Pending PendSV 准备上下文切换;
8、退出 SYSTICK 的 ISR 后,由于之前的 IRQ 优先级高于 PendSV,所以之前的 ISR 继续执行;
9、ISR 执行完毕退出,此刻没有优先级更高的 IRQ,那么执行 PendSV 进行上下文切换;
10、PendSV 执行完毕,顺利切到任务 A,同时进入线程模式;
以上部分摘自:https://www.cnblogs.com/god-of-death/p/14856578.html
【如何设定PendSV优先级】
往地址为0xE000ED22的寄存器PRI_14写入PendSV优先级
NVIC_SYSPRI14 EQU 0xE000ED22
NVIC_PENDSV_PRI EQU 0xFF
LDR R1, =NVIC_PENDSV_PRI
LDR R0, =NVIC_SYSPRI14
STRB R1, [R0] ;将r1 中的 [7:0]存储到 r0 对应的内存
BX LR ;返回
【如何触发PendSV异常】
往ICSR第28位写1,即可将PendSV异常挂起。若是当前没有高优先级中断产生,那么程序将会进入PendSV handler
NVIC_INT_CTRL EQU 0xE000ED04
NVIC_PENDSVSET EQU 0x10000000
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
【测试PendSV异常handler实现任务切换】
如何实现任务切换?三个步骤:
步骤一:在进入中断前先设置PSP。
步骤二:将当前寄存器的内容保存到当前任务堆栈中。进入ISR时,cortex-m3会自动保存八个寄存器到PSP中,剩下的几个需要我们手动保存。
步骤三:在Handler中将下一个任务的堆栈中的内容加载到寄存器中,并将PSP指向下一个任务的堆栈。这样就完成了任务切换。
要在PendSV 的ISR中完成这两个步骤,我们先需了解下在进入PendSV ISR时,cortex-M3做了什么?
1,入栈。会有8个寄存器自动入栈。入栈内容及顺序如下:
在步骤一中,我们已经设置了PSP,那这8个寄存器就会自动入栈到PSP所指地址处。
2,取向量。找到PendSV ISR的入口地址,这样就能跳到ISR了。
3,更新寄存器内容。
做完这三步后,程序就进入ISR了。
进入ISR前,我们已经完成了步骤一,cortex-M3已经帮我们完成了步骤二的一部分,剩下的需要我们手动完成。
在ISR中添加代码如下:
MRS R0, PSP
保存PSP到R0。为什么是PSP而不是MSP。因为在OS启动的时候,我们已经把SP设置为PSP了。这样使得用户程序使用任务堆栈,OS使用主堆栈,不会互相干扰。不会因为用户程序导致OS崩溃。
STMDB R0!,{R4-R11}
保存R4-R11到PSP中。C语言表达是*(–R0)={R4-R11},R0中值先自减1,然后将R4-R11的值保存到该值所指向的地址中,即PSP中。
STMDB Rd!,{寄存器列表} 连续存储多个字到Rd中的地址值所指地址处。每次存储前,Rd先自减一次。
若是ISR是从从task0进来,那么此时task0的堆栈中已经保存了该任务的寄存器参数。保存完成后,当前任务堆栈中的内容如下(假设是task0)
左边表格是预期值,右边是keil调试的实际值。可以看出,是一致的。在任务初始化时(步骤一),我们将PSP指向任务0的栈顶0x20000080。在进入PendSV之前,cortex-M3自动入栈八个值,此时PSP指向了0x20000060。然后我们再保存R4-R11到0x20000040~0x2000005C。
这样很容易看明白,如果需要下次再切换到task0,只需恢复R4~R11,再将PSP指向0x20000060即可。
测试例程:
#define HW32_REG(ADDRESS) (*((volatile unsigned long *)(ADDRESS)))
void USART1_Init(void);
void task0(void) ;
uint32_t curr_task=0;
uint32_t next_task=1;
uint32_t task0_stack[17];
uint32_t task1_stack[17];
uint32_t PSP_array[4];
u8 task0_handle=1;
u8 task1_handle=1;
void task0(void)
{
while(1)
{
if(task0_handle==1)
{
printf("task0\n");
task0_handle=0;
task1_handle=1;
}
}
}
void task1(void)
{
while(1)
{
if(task1_handle==1)
{
printf("task1\n");
task1_handle=0;
task0_handle=1;
}
}
}
__asm void SetPendSVPro(void)
{
NVIC_SYSPRI14 EQU 0xE000ED22
NVIC_PENDSV_PRI EQU 0xFF
LDR R1, =NVIC_PENDSV_PRI
LDR R0, =NVIC_SYSPRI14
STRB R1, [R0]
BX LR
}
__asm void TriggerPendSV(void)
{
NVIC_INT_CTRL EQU 0xE000ED04
NVIC_PENDSVSET EQU 0x10000000
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
}
int main(void)
{
SetPendSVPro();
LED_Init();
uart_init(115200);
printf("OS test\n");
PSP_array[0] = ((unsigned int) task0_stack) + (sizeof task0_stack) - 16*4;
HW32_REG((PSP_array[0] + (14*4))) = (unsigned long) task0;
HW32_REG((PSP_array[0] + (15*4))) = 0x01000000;
PSP_array[1] = ((unsigned int) task1_stack) + (sizeof task1_stack) - 16*4;
HW32_REG((PSP_array[1] + (14*4))) = (unsigned long) task1;
HW32_REG((PSP_array[1] + (15*4))) = 0x01000000;
curr_task = 0;
__set_PSP((PSP_array[curr_task] + 16*4));
SysTick_Config(9000000);
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
__set_CONTROL(0x3);
__ISB();
task0();
while(1);
}
__asm void PendSV_Handler(void)
{
MRS R0, PSP
STMDB R0!,{R4-R11}
LDR R1,=__cpp(&curr_task)
LDR R3,=__cpp(&PSP_array)
LDR R4,=__cpp(&next_task)
LDR R4,[R4]
STR R4,[R1]
LDR R0,[R3, R4, LSL #2]
LDMIA R0!,{R4-R11}
MSR PSP, R0
BX LR
ALIGN 4
}
void SysTick_Handler(void)
{
LED0=!LED0;
if(curr_task==0)
next_task=1;
else
next_task=0;
TriggerPendSV();
}
串口输出:
可以看到在任务0和任务1之间来回切换。
【FreeRTOS任务切换源码】
上下文(任务)切换被触发的场合大致分为:
● 可以执行一个系统调用
● 系统滴答定时器(SysTick)中断。
执行系统调用就是执行 FreeRTOS 系统提供的相关 API 函数,比如任务切换函数taskYIELD(),这些 API 函数和任务切换函数
taskYIELD()都统称为系统调用。
函数 taskYIELD()其实就是个宏,在文件 task.h 中有如下定义:
#define taskYIELD() portYIELD()
函数 portYIELD()也是个宏,在文件 portmacro.h 中有如下定义
#define portYIELD() \
{ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
\
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
中断级的任务切换函数为 portYIELD_FROM_ISR(),定义如下:
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
#define portEND_SWITCHING_ISR( xSwitchRequired ) \
if( xSwitchRequired != pdFALSE ) portYIELD()
系统滴答定时器(SysTick)中断
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
xPortSysTickHandler()源码如下:
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
真正的任务切换代码在PendSV中断函数中,
FreeRTOS做了如下函数重定义
#define xPortPendSVHandler PendSV_Handler
xPortPendSVHandler函数如下 (汇编 port.c)
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
ldr r3, =pxCurrentTCB
ldr r2, [r3]
tst r14, #0x10
it eq
vstmdbeq r0!, {s16-s31}
stmdb r0!, {r4-r11, r14}
str r0, [r2]
stmdb sp!, {r3}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp!, {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
bx r14
}
参考:
https://www.cnblogs.com/WeyneChen/p/4891885.html
https://www.cnblogs.com/god-of-death/p/14856578.html
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)