目录
简介:
启动任务调度的流程图:
第一个任务调度启动相关程序注释:
主任务循环,启动调度器顶级封装
指定TCB
修改PendSV 和 SysTick 的中断优先级为最低
呼叫系统调用(手动触发SVC)
调用第一个任务的核心操作(使用SVC中断来实现)
任务启动后关于任务调度切换的相关程序注释:
主循环中的任务结束后切换任务的顶级封装
使用手动触发PendSV中断
多任务调度的核心操作(使用PendSV中断来实现)
简介:
本文是 [野火®]《FreeRTOS 内核实现与应用开发实战—基于STM32》 这本书第7章任务的定义与任务切换的实现的补充
启动任务调度的流程图:
虚线表示,这个线程程序完,触发的中断服务和中断服务好返回的线程程序位置。
第一个任务调度启动相关程序注释:
主任务循环,启动调度器顶级封装
vTaskStartScheduler() (main.c)
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 初始化与任务相关的列表开始,如就绪列表 */
/* 初始化与任务相关的列表结束,如就绪列表 */
/* 启动调度器,开始多任务调度,启动成功则不返回 */
vTaskStartScheduler();
for(;;)
{
/* 系统启动成功不会到达这里 */
}
}
指定TCB
void vTaskStartScheduler( void ) (task.c)
void vTaskStartScheduler( void )
{
/* 手动指定第一个运行的任务 */
pxCurrentTCB = &Task1TCB;
/* 启动调度器 */
if( xPortStartScheduler() != pdFALSE )
{
/* 调度器启动成功,则不会返回,即不会来到这里 */
}
}
修改PendSV 和 SysTick 的中断优先级为最低
BaseType_t xPortStartScheduler( void ) (port.c)
BaseType_t xPortStartScheduler( void )
{
/* 配置PendSV 和 SysTick 的中断优先级为最低 */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 启动第一个任务,不再返回 */
prvStartFirstTask();
/* 不应该运行到这里 */
return 0;
}
呼叫系统调用(手动触发SVC)
__asm void prvStartFirstTask( void ) (port.c)
__asm void prvStartFirstTask( void )
{
//8字节对齐
//12345678 87654321
//12345678 00000000
PRESERVE8
/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,
里面存放的是向量表的起始地址,即MSP的地址 */
//1)立即数 -> 寄存器地址
//2)寄存器的值 -> 向量表起始地址
//3) 向量表起始地址第一个值 -> MSP的值
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/* 设置主堆栈指针msp的值 */
//MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数
//给把r0的值赋值给msp寄存器
msr msp, r0
/* 使能全局中断 ,这个可以用作原语*/
// 中断开关指令
// CPSID CPSIE 用于快速的开关中断
// CPSID I PRIMASK=1 关中断
// CPSIE I PRIMASK=0 开中断
// CPSID F FAULTMASK=1 关异常
// CPSIE F FAULTMASK=0 开异常
// 隔离指令
// ISB 指令同步隔离,最严格:证所有它前面的指令都执行完毕之后,才执行它后面的指令
// DSB 数据同步隔离,比DMB 严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访问操作——译者注)
// DMB 数据存储隔离,DMB 指令保证仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作。
cpsie i
cpsie f
dsb
isb
/* 调用SVC去启动第一个任务 */
// 调用SVC的0号服务
// svc_handler 0号服务
// svc_handler_1 1号服务
svc 0
nop
nop
}
调用第一个任务的核心操作(使用SVC中断来实现)
__asm vPortSVCHandler(void)(port.c )
__asm void vPortSVCHandler( void )
{
//更新系统栈为pxCurrentTCB
//这边用于第一次启动任务的时候,将MSP切到PSP
extern pxCurrentTCB;
PRESERVE8
//R3是当前TCB指针的地址,R1是当前TCB指针的值,R0是TCB指针指向第一个成员的值
ldr r3, =pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */
ldr r1, [r3] /* 加载pxCurrentTCB到r1 */
ldr r0, [r1] /* 加载pxCurrentTCB指向的值到r0,目前r0的值等于第一个任务堆栈的栈顶 */
//将自建任务堆的R4-R11压入系统栈中,剩下其他寄存器的在函数返回自动压入,psp指向r0
ldmia r0!, {r4-r11} /* 以r0为基地址,将栈里面的内容加载到r4~r11寄存器,同时r0会递增 */
msr psp, r0 /* 将r0的值,即任务的栈指针更新到psp */
isb
// 设置 basepri 寄存器的值为 0,即打开所有中断。basepri 是一个中断屏蔽寄存器,大于等于此寄存器值的中断都将被屏蔽。
mov r0, #0 /* 设置r0的值为0 */
msr basepri, r0 /* 设置basepri寄存器的值为0,即所有的中断都没有被屏蔽 */
// 在进入异常服务程序后,将自动更新LR的值为特殊的EXC_RETURN。
// 这是一个高28位全为1的值,只有[3:0]的值有特殊含义,如表9.3所示。
// 当异常服务例程把这个值送往PC时,就会启动处理器的中断返回序列。
// 因为LR的值是由CM3自动设置的,所以只要没有特殊需求,就不要改动它。
//
// 表9.3 EXC_RETURN位段详解位段 含义
// [31:4] EXC_RETURN的标识:必须全为1
// 3 0=返回后进入Handler模式
// 1=返回后进入线程模式
// 2 0=从主堆栈中做出栈操作,返回后使用MSP,
// 1=从进程堆栈中做出栈操作,返回后使用PSP
// 1 保留,必须为0
//
// 0 0=返回ARM状态。
// 1=返回Thumb状态。在CM3中必须为1
//
// 表9.4 合法的EXC_RETURN值及其功能
// 数值 功能
// 0xFFFF_FFF1 返回handler模式
// 0xFFFF_FFF9 返回线程模式,并使用主堆栈(SP=MSP)
// 0xFFFF_FFFD 返回线程模式,并使用线程堆栈(SP=PSP)
//
// 使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作并返回后进入任务模式、返回 Thumb 状态。
// 在 SVC 中断服务里面,使用的是 MSP 堆栈指针,是处在 ARM 状态。
// 当 r14 为 0xFFFFFFFX,执行是中断返回指令,cortext-m3 的做法,X 的 bit0 为 1 表示返回 thumb 状态,
// bit1 和 bit2 分别表示返回后 sp 用 msp 还是 psp、以及返回到特权模式还是用户模式
//
// 这个是第一次任务是MSP进来的所以进入异常服务程序后,LR的为 0xFFFF_FFF9
orr r14, #0xd /* 当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,
使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回Thumb状态 */
//压入剩下的,并返回线程指针
//R13变为PSP,指向xPSR的上一格子
bx r14 /* 异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:
xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
同时PSP的值也将更新,即指向任务栈的栈顶 */
}
任务启动后关于任务调度切换的相关程序注释:
主循环中的任务结束后切换任务的顶级封装
taskYIELD() (main.c)
/* 软件延时 */
void delay (uint32_t count)
{
for(; count!=0; count--);
}
/* 任务1 */
void Task1_Entry( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay( 100 );
flag1 = 0;
delay( 100 );
/* 任务切换,这里是手动切换 */
taskYIELD();
}
}
/* 任务2 */
void Task2_Entry( void *p_arg )
{
for( ;; )
{
flag2 = 1;
delay( 100 );
flag2 = 0;
delay( 100 );
/* 任务切换,这里是手动切换 */
taskYIELD();
}
}
使用手动触发PendSV中断
taskYIELD()( porrmacro.h)
#define taskYIELD() portYIELD()
#define portYIELD() \
{ \
/* 触发PendSV,产生上下文切换 */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
多任务调度的核心操作(使用PendSV中断来实现)
__asm void xPortPendSVHandler( void )(port.c)
void vTaskSwitchContext( void )
{
/* 两个任务轮流切换 */
if( pxCurrentTCB == &Task1TCB )
{
pxCurrentTCB = &Task2TCB;
}
else
{
pxCurrentTCB = &Task1TCB;
}
}
__asm void xPortPendSVHandler( void )
{
//申明变量和函数
extern pxCurrentTCB;
extern vTaskSwitchContext;
//8字节对齐
PRESERVE8
/* 当进入PendSVC Handler时,上一个任务运行的环境即:
xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 */
/* 获取任务栈指针到r0 */
//MRS访问特殊寄存器
mrs r0, psp
isb
//r3是TCB这个指针的地址,r2是TCB指针的值
ldr r3, =pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */
ldr r2, [r3] /* 加载pxCurrentTCB到r2 */
// 弹出需要手动保存的r4-r11保存在当前tcb中
stmdb r0!, {r4-r11} /* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
// STR 把一个寄存器按字存储到存储器中
// STRH 把一个寄存器存器的低半字存储到存储器中
// STRB 把一个寄存器的低字节存储到存储器中
// r0是CB地址第一个变量的地址的值,即栈顶指针
// 上面r0是psp指向R0的起始指针,这个r0是指向r4的起始指针
str r0, [r2] /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */
//r3是存TCB指针的地址,后面切任务,指针会变,指针地址不变
//r14是存这个异常任务的返回值EXC_RETURN=0xFFFF_FFFD
//sp是压入系统栈中
stmdb sp!, {r3, r14} /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护 */
//屏蔽11以上编号的中断,systick即以下的函数中断,就是屏蔽systick和svc
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 进入临界段 */
msr basepri, r0
dsb
isb
//这边切函数了,之前函数的xPSR,PC,R14,R12,R3,R2,R1,R0自动保存;
//切换进程任务
bl vTaskSwitchContext /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */
//开启所有中断
mov r0, #0 /* 退出临界段 */
msr basepri, r0
//切换新任务了,r3切回当前TCB的地址(调用切换函数前,当前TCB地址存放的值是上一个任务的指针,调用切换函数后,存放的值是下一个任务的指针)
//R14是当前异常函数需要返回的值
ldmia sp!, {r3, r14} /* 恢复r3和r14 */
//r3是上一个任务的TCB地址,r1是上一个任务TCB地址第一个变量的地址,r0是上一个任务TCB地址第一个变量的地址的值,即栈顶指针
ldr r1, [r3]
ldr r0, [r1] /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
ldmia r0!, {r4-r11} /* 出栈 */
msr psp, r0
isb
bx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,
然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
nop
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)