(七)FreeRTOS 任务切换

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

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,此函数不会引起任务调度
	}
}

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值