PendSV异常
SVC 用于产生系统函数的调用请求。例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个 SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。类似于操作系统中的特权请求,陷入内核,通过操作系统去执行用户无法完成的任务。
SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高, 或是其它原因使之无法立即响应, 将上访成硬 fault)
PendSV(可悬起的系统调用),它是一种CPU系统级别的异常,它可以像普通外设中断一样被悬起,而不会像SVC服务那样,因为没有及时响应处理,而触发Fault。如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。
在嵌入式OS中,处理时间被划分为了多个时间片,任务会交替进行(并发,虚拟化CPU,会有一种任务同时完成的错觉),而滴答计时器就是这个时间片的计时器,每隔一段时间就会开启中断,让操作系统进行上下文切换。为了避免操作系统的上下文切换抢占了某些中断,可以使用PendSV,这样就会让其他中断先完成,再进行系统调用,实现上下文切换。
FreeRTOS任务切换场合
- 执行一个系统调用(SVC、PendSV)
- 系统滴答定时器(Sys Tick)中断
执行系统调用
任务切换函数taskYIELD()
#define taskYIELD() portYIELD()
#define portYIELD()
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; //向中断控制和状态寄存器ICSR的bit28写1挂起PendSV来启动PendSV中断
__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 ) do { if( xSwitchRequired != pdFALSE ) portYIELD(); } while( 0 )//调用 portYIELD()
系统滴答定时器中断
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
}
}
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();//关闭中断
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;//向中断控制和状态寄存器ICSR的bit28写1挂起PendSV来启动PendSV中断
}
}
vPortClearBASEPRIFromISR();//打开中断
}
PendSV中断服务函数
#define xPortPendSVHandler PendSV_Handler
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp //读取进程栈指针,保存到寄存器R0
isb
ldr r3, =pxCurrentTCB //获取当前任务的任务控制块
ldr r2, [ r3 ]//将任务控制块的地址保存到R2
stmdb r0 !, { r4 - r11 } //保存R4-R11寄存器的值
str r0, [ r2 ] //将寄存器R0的值写入R2所保存的地址中去,也就是新的栈顶保存到任务控制块的第一个字段
stmdb sp !, { r3, r14 }//将R3和R14压入栈中,R3保存了当前任务的任务控制块
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0//关闭中断,进入临界区
dsb
isb
bl vTaskSwitchContext//获取下一个要运行的任务
mov r0, #0
msr basepri, r0//打开中断,退出临界区
ldmia sp !, { r3, r14 }//出栈,恢复R3和R14的值,R3变成了下一个要运行的任务的控制块
ldr r1, [ r3 ]
ldr r0, [ r1 ]//获取新的要运行的的任务的任务堆栈栈顶,保存到R0
ldmia r0 !, { r4 - r11 } //R4-R11出栈,也就是即将运行的任务的现场
msr psp, r0//更新进程psp的值
isb
bx r14//硬件自动恢复寄存器R0-R3,R12,LR,PC,xPSP。返回原模式,新的任务开始,切换任务完成。
nop
}
查找下一个要运行的任务
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )//如果调度器挂起就不能执行任务切换
{
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
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif
taskCHECK_FOR_STACK_OVERFLOW();
#if ( configUSE_POSIX_ERRNO == 1 )
{
pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
}
#endif
taskSELECT_HIGHEST_PRIORITY_TASK(); //获取下一个要运行的任务
traceTASK_SWITCHED_IN();
#if ( configUSE_POSIX_ERRNO == 1 )
{
FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
}
#endif
#if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) )
{
configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock );
}
#endif
}
}
获取下一个任务的通用方法:
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority = uxTopReadyPriority;
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) //pxReadyTasksLists[]处于就绪态的最高优先级列表,uxTopPriority是最高优先级。listLIST_IS_EMPTY()判断这个列表是否为空,从高到低直到找到就绪列表不为空的列表
{
configASSERT( uxTopPriority );
--uxTopPriority;
}
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); //获取列表中下一个的列表项,并把列表项所对应的任务控制块赋值给pxCurrentTCB
uxTopReadyPriority = uxTopPriority;
}
获取下一个任务的硬件方法:
#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 ] ) ); //获取列表中下一个的列表项,并把列表项所对应的任务控制块赋值给pxCurrentTCB
}
FreeRTOS时间片调度
在FreeRTOS中允许一个任务运行一个时间片(一个时钟节拍的长度)后让出CPU的使用权,让拥有同优先级的下一任务运行。
void xPortSysTickHandler( void )//引发任务调度函数
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )//当返回值不为pdFALSE的时候就会进行任务调度
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE; traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
prvAddTaskToReadyList( pxTCB );
#if ( configUSE_PREEMPTION == 1 )//时间片调度条件
{
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )//判断当前任务所对应的优先级下是否有其他任务
{
xSwitchRequired = pdTRUE;//如果有就返回padTrue,进行一次任务切换
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
}
}
时间片调度实验
实验设计
start_task:创建任务
task1_task:控制LED0闪烁,并通过串口打印task1_task运行次数
task2_task:控制LED1灯闪烁,并通过串口打印task2_task运行次数
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "timer.h"
#include "lcd.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 2
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
LCD_Init(); //初始化LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F103/407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 9-1");
LCD_ShowString(30,50,200,16,16,"FreeRTOS Round Robin");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2016/11/25");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务1执行次数加1 注意task1_num1加到255的时候会清零!!
LED0=!LED0;
taskENTER_CRITICAL(); //进入临界区
printf("任务1已经执行:%d次\r\n",task1_num);
taskEXIT_CRITICAL(); //退出临界区
delay_xms(10); //延时10ms,模拟任务运行10ms,此函数不会引起任务调度,因为两个任务是同一个优先级,会按时间片交替调度
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task2_num1加到255的时候会清零!!
LED1=!LED1;
taskENTER_CRITICAL(); //进入临界区
printf("任务2已经执行:%d次\r\n",task2_num);
taskEXIT_CRITICAL(); //退出临界区
delay_xms(10); //延时10ms,模拟任务运行10ms,此函数不会引起任务调度
}
}