FreeRTOS 为了任务启动和任务切换使用了三个异常:SVC、PendSV 和 SysTick。
SVC(系统服务调用)用于任务启动,有些操作系统不允许应用程序直接访问硬件,而是通过提供一些系统服务函数,通过 SVC 来调用;
PendSV(可挂起系统调用)用于完成任务切换,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV 会推迟执行,直到高优先级中断执行完毕;
SysTick 用于产生系统节拍时钟,提供一个时间片,如果多个任务共享同一个优先级,则每次 SysTick 中断,下一个任务将获得一个时间片。
一、任务创建
1.1 TCB控制块
创建任务的时候,首先申请堆内存,再申请任务控制块的内存。
注意:
heap_3使用的内存分配方法是直接调用C库malloc()和free()的简单封装,固需要对启动文件里的Heap_Size修改合适的值。
其他内存分配方法:heap_1、heap_2、heap_4、heap_5所分配的内存,均来自即初始化时分配的全局静态数组ucHeap[],大小由configTOTAL_HEAP_SIZE决定。
1.2 SVC,启动第一个任务
由上可知,SVC与SWI一样,产生一个软中断。
BaseType_t xPortStartScheduler( void ) //启动调度器
{
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //设置PendSV中断为最低优先级
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //设置Systick中断为最低优先级
vPortSetupTimerInterrupt(); //使能滴答定时器
。。。。。。
prvStartFirstTask();
。。。。。。
}
__asm void prvStartFirstTask( void ) //启动第一个任务
{
PRESERVE8
ldr r0, =0xE000ED08 //向量表偏移量寄存器的地址
ldr r0, [r0] //将R0保存的地址的值(主堆栈指针MSP初始值)给R0
ldr r0, [r0] //获取主堆栈指针MSP初始值
msr msp, r0 //将主堆栈指针MSP初始值赋值给MSP
mov r0, #0
msr control, r0
cpsie i //使能全局中断
cpsie f
dsb
isb
svc 0 //触发SVC软中断
nop
nop
}
__asm void vPortSVCHandler( void ) //SVC中断服务函数
{
PRESERVE8
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB //R3=最高优先级任务的TCB控制块的地址
ldr r1, [r3] //R1=R3保存地址的值
ldr r0, [r1] //R0=R1保存地址的值
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14} //出栈
msr psp, r0 //进程栈指针PSP设置为该任务的堆栈
isb //指令同步
mov r0, #0 //R0赋值0
msr basepri, r0 //basepri赋值0,开中断
bx r14 //硬件会自动恢复R0~R3,R12,LR.PC,xPSR寄存器的值
}
二、任务切换
2.1 上下文切换(任务切换)就是触发PendSV中断。切换上下文的方法有这两种:调用系统函数、SysTick中断(相当时间片切换)。
//给中断控制及状态寄存器ICSR的Bit 28写1.即触发PendSV悬起中断
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
//调用系统函数执行上下文切换
#define taskYIELD() portYIELD()
#define portYIELD()
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; //触发PendSV中断
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE );
}
//SysTick中断服务函数执行上下文切换
void SysTick_Handler (void)
{
SysTick->CTRL; //Clear overflow flag
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler(); //Call tick handler
}
}
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; //触发PendSV悬挂起中断
}
}
vPortClearBASEPRIFromISR();
}
2.2 上面说到,切换任务都是触发PendSV中断,而PendSV中断服务函数原型有必要了解下:
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp //获取当前任务的栈指针(R13)到R0
isb
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB //获取当前任务的TCB地址到R3,
ldr r2, [r3] //同时将R3的值加载到R2
/* Is the task using the FPU context? If so, push high vfp registers. */
tst r14, #0x10 //如果使用了FPU,即(r14 & 0x10)是否为1
it eq //若成立,则需要将高寄存器S16~S31保存到栈
vstmdbeq r0!, {s16-s31}
/* Save the core registers. */
stmdb r0!, {r4-r11, r14} //保存r4~r11,r14寄存器的值
/* Save the new top of stack into the first member of the TCB. */
str r0, [r2] //这里是将R0的值写入R2保存的地址里,
//此时,R0保存当前任务最新的栈顶指针值,
//而第2步已经将任务控制块的首地址(首地
//址即任务栈顶地址)写到R2。
stmdb sp!, {r0, r3} //临时保存R0, R3的值,入栈操作
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //关中断,进入临界区
msr basepri, r0
dsb
isb //等待数据同步
bl vTaskSwitchContext //获取下一个需要运行的任务
mov r0, #0 //这两行是开中断
msr basepri, r0 //这两行是开中断,退出临界区
ldmia sp!, {r0, r3} //恢复R0, R3的值,出栈操作
//注意:
//调用vTaskSwitchContext后,pxCurrentTCB
//的值已经被改变,此时R3保存的地址对应的数
//即pxCurrentTCB已经变成了下一个要运行的
//任务的任务控制块。
/* The first item in pxCurrentTCB is the task top of stack. */
ldr r1, [r3] //将pxCurrentTCB的值加载的r1
ldr r0, [r1] //将r1加载打r0,即新任务堆栈栈顶保存到r0
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}
/* Is the task using the FPU context? If so, pop the high vfp registers
too. */
tst r14, #0x10
it eq
vldmiaeq r0!, {s16-s31} //再次判断是否使用FPU,如果是恢复s16~s31
msr psp, r0 //
isb //这两条更新进程堆栈指针
#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
#if WORKAROUND_PMU_CM001 == 1
push { r14 }
pop { pc }
nop
#endif
#endif
bx r14 //硬件自动恢复寄存器 R0~R3、R12、LR、PC
//和 xPSR 的值
}
三、切换上下文时选择哪个任务优先执行
3.1 查找下一个最高优先级运行的任务
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
该定义为1,则使用硬件的方法查找下一个需要运行的任务,否则使用通用方法。
/*-----------------------------------------------------------*/
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) //如果调度器被挂起,则不能切换
{
/* The scheduler is currently suspended - do not allow a context
switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
/* Add the amount of time the task has been running to the
accumulated time so far. The time the task started running was
stored in ulTaskSwitchedInTime. Note that there is no overflow
protection here so count values are only valid until the timer
overflows. The guard against negative values is to protect
against suspect run time stat counter implementations - which
are provided by the application, not the kernel. */
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif /* configGENERATE_RUN_TIME_STATS */
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW(); //检查堆栈溢出
/* Before the currently running task is switched out, save its errno. */
#if( configUSE_POSIX_ERRNO == 1 )
{
pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
}
#endif
/* Select a new task to run using either the generic C or port
optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK(); //获取下一个需要运行的任务
traceTASK_SWITCHED_IN();
/* After the new task is switched in, update the global errno. */
#if( configUSE_POSIX_ERRNO == 1 )
{
FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to this task. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
}
}
3.2 通用查找方法
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority = uxTopReadyPriority;
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )
{
configASSERT( uxTopPriority );
--uxTopPriority;
}
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
uxTopReadyPriority = uxTopPriority;
}
//pxReadyTasksLists
3.3 硬件查找方法
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority;
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
}
版权声明:本文为CSDN博主「小书包VIP」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xqw19891201/article/details/122575294
暂无评论