RTOS 的核心时任务管理,而任务管理的重中之重任务切换,系统中任务切换的过程决定了操作系统的运行效率和稳定性,尤其是对于实时操作系统。而对于深入了解和学习 FreeRTOS,FreeRTOS 的任务切换是必须要掌握的一个知识点。
PendSV 异常
PendSV(Pended Service Call,可挂起服务调用),是一个对 RTOS 非常重要的异常。PendSV的中断优先级是可以编程的,用户可以根据实际的需求,对其进行配置。PendSV 的中断由将中断控制状态寄存器(ICSR)中 PENDSVSET 为置一触发。PendSV 与 SVC 不同,PendSV 的中断是非实时的,即 PendSV 的中断可以在更高优先级的中断中触发,但是在更高优先级中断结束后才执行。利用 PendSV 的这个可挂起特性,在设计 RTOS 时,可以将 PendSV 的中断优先级设置为最低的中断优先级。这么一来,PendSV 的中断服务函数就会在其他所有中断处理完成后才执行。任务切换时,就需要用到 PendSV 的这个特性。
在典型的 RTOS 中,任务的处理时间被分为多个时间片,OS 内核的执行可以有两种触发方式,一种是通过在应用任务中通过 SVC 指令触发,例如在应用任务在等待某个时间发生而需要停止的时候,那么就可以通过 SVC 指令来触发 OS内核的执行,以切换到其他任务;第二种方式是,SysTick 周期性的中断,来触发 OS 内核的执行。下图演示了只有两个任务的 RTOS 中,两个任务交替执行的过程:
如果一个中断请求(IRQ)在 SysTick 中断产生之前产生,那么 SysTick 就可能抢占该中断
请求,这就会导致该中断请求被延迟处理,这在实时操作系统中是不允许的,因为这将会影响
到实时操作系统的实时性,如下图所示:

并且,当 SysTick 完成任务的上下文切换,准备返回任务中运行时,由于存在中断请求,
ARM Cortex-M 不允许返回线程模式,因此,将会产生用法错误异常(Usage Fault)。
PendSV 通过延迟执行任务切换,直到处理完所有的中断请求,以解决上述问题。为了达到
这样的效果,必须将 PendSV 的中断优先级设置为最低的中断优先等级。如果操作系统决定切
换任务,那么就将 PendSV 设置为挂起状态,并在 PendSV 的中断服务函数中执行任务切换,如下图所示:
1. 任务一触发 SVC 中断以进行任务切换(例如,任务一正等待某个事件发生)。
2. 系统内核接收到任务切换请求,开始准备任务切换,并挂起 PendSV 异常。
3. 当退出 SVC 中断的时候,立刻进入 PendSV 异常处理,完成任务切换。
4. 当 PendSV 异常处理完成,返回线程模式,开始执行任务二。
5. 中断产生,并进入中断处理函数。
6. 当运行中断处理函数的时候,SysTick 异常(用于内核时钟节拍)产生。
7. 操作系统执行必要的操作,然后挂起 PendSV 异常,准备进行任务切换。
8. 当 SysTick 中断处理完成,返回继续处理中断。
9. 当中断处理完成,立马进入 PendSV 异常处理,完成任务切换。
10. 当 PendSV 异常处理完成,返回线程模式,继续执行任务一。
PendSV在RTOS的任务切换中,起着至关重要的作用,FreeRTOS的任务切换就是在PendSV
中完成的。
PendSV 中断服务函数
FreeRTOS 在 PendSV 的中断中,完成任务切换,PendSV 的中断服务函数由 FreeRTOS 编
写,将 PendSV 的中断服务函数定义成函数 xPortPendSVHandler()。
针 对 ARM Cortex-M3 和针对 ARM Cortex-M4 和 ARM Cortex-M7 内 核 的 函 数
xPortPendSVHandler()稍有不同,其主要原因在于 ARM Cortex-M4 和 ARM Cortex-M7 内核具有浮点单元,因此在进行任务切换的时候,还需考虑是否保护和恢复浮点寄存器的值。
FreeRTOS 确定下一个要运行的任务
在 PendSV 的中断服务函数中,调用了函数 vTaskSwitchContext()来确定写一个要运行的任务。
PendSV 异常何时触发
PendSV 异常用于进行任务切换,当需要进行任务切换的时候,FreeRTOS 就会触发 PendSV
异常,以进行任务切换。
时间片调度简介
同等优先级任务轮流地享有相同的 CPU 时间(可设置), 叫时间片,在FreeRTOS中,一个时间片就等于SysTick 中断周期。
注意:使用时间片调度需把宏 configUSE_TIME_SLICING 和 configUSE_PREEMPTION 置1

1、同等优先级任务,轮流执行;时间片流转;
2、一个时间片大小,取决为滴答定时器中断频率;
3、注意没有用完的时间片不会再使用,下次任务Task3得到执行还是按照一个时间片的时钟节拍运行;
FreeRTOS 时间片调度实验
本实验设计3个任务:start_task、task1_task 和 task2_task,这四个任务的任务功能如下:
start_task:用来创建其他 2 个任务。
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 "stm32f10x_conf.h"
#include "key.h"
#include "lcd.h"
#include "chinese.h"
#include "chfont.h"
#include "FreeRTOS.h"
#include "exti.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
//创建开始任务
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("Task 1 has been executed: %d times\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("Task 2 has been executed: %d times\r\n",task2_num);
taskEXIT_CRITICAL(); //退出临界区
delay_xms(10); //延时10ms,模拟任务运行10ms,此函数不会引起任务调度
}
}


FreeRTOS 任务切换&spm=1001.2101.3001.5002&articleId=147513598&d=1&t=3&u=c641a83693b842fd872a94c041435c3b)
5758

被折叠的 条评论
为什么被折叠?



