FreeRTOS任务切换

news/2025/3/22 0:40:06

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;PRESERVE8mrs r0, psp  //读取进程栈指针,保存到寄存器R0isbldr r3, =pxCurrentTCB //获取当前任务的任务控制块ldr r2, [ r3 ]//将任务控制块的地址保存到R2stmdb r0 !, { r4 - r11 } //保存R4-R11寄存器的值str r0, [ r2 ] //将寄存器R0的值写入R2所保存的地址中去,也就是新的栈顶保存到任务控制块的第一个字段stmdb sp !, { r3, r14 }//将R3和R14压入栈中,R3保存了当前任务的任务控制块mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITYmsr basepri, r0//关闭中断,进入临界区dsbisbbl vTaskSwitchContext//获取下一个要运行的任务mov r0, #0msr 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的值isbbx 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_VALUEportALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );#elseulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();#endifif( 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;}#endiftaskSELECT_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 ] ) ); //获取列表中下一个的列表项,并把列表项所对应的任务控制块赋值给pxCurrentTCBuxTopReadyPriority = 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();		  					//初始化LEDLCD_Init();							//初始化LCDPOINT_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,此函数不会引起任务调度}
}

https://dhexx.cn/news/show-4626923.html

相关文章

操作系统复习4.2.0-磁盘组织和管理

磁盘的结构 磁盘、磁道、扇区 磁盘划分n圈磁道&#xff0c;每条磁道划分为多个扇区 磁盘读写 磁头移动到需要读写的扇区所在的磁道来完成读写 磁盘转起来让目标扇区在磁头下面划过 盘面和柱面 分类 按磁头分类&#xff1a;磁头可伸缩移动、不可伸缩移动(同一盘面上有多个…

详细解析MariaDB与MySQL两个数据库的区别

主要区别介绍 ● 发行版&#xff1a;MariaDB 是 MySQL 的一个分支&#xff0c;MySQL是 Oracle 公司的产品。 ● 开发公司&#xff1a;MariaDB 由 MariaDB 基金会和社区维护&#xff0c;MySQL 由 Oracle 公司维护。 ● 开发重点&#xff1a;MariaDB是功能改进和增强&#…

MoviePy介绍

MoivePy是一个用于视频编辑的Python库&#xff0c;可以&#xff1a;剪切、拼接、标题插入、视频合成、视频处理和创建自定义效果。它支持Windows、Linux、Mac&#xff0c;源码地址&#xff1a;https://github.com/Zulko/moviepy&#xff0c;最新发布版本v1.0.3&#xff0c;lice…

C++入门——关键字|命名空间|输入输出

前言&#xff1a; 今天我们又开启了一个崭新的大门——C面向对象编程语言&#xff0c;C是怎么来的呢&#xff1f;答案是&#xff1a;因为C语言的有很多不足&#xff0c;我们的祖师爷用着不爽&#xff0c;就不断更改&#xff0c;就改出来了一门新的语言&#xff0c;C。C语言兼容…

基于Springboot的社区论坛系统(源代码+数据库)055

部分代码地址 https://gitee.com/ynwynwyn/forum-public 基于Springboot的社区论坛系统(源代码数据库) 一、系统介绍 前台&#xff1a; 话题列表&#xff0c;搜索话题&#xff0c;发布话题通过标签筛选话题个人设置&#xff1a;修改个人信息&#xff0c;查看发布话题记录&a…

QT实现 WebsocketServer端与WebsocketClient 端通信

概 述 WebSockets 是一种通过单个 TCP 连接提供全双工通信信道的 web 技术。2011年&#xff0c;IETF 将 WebSocket 协议标准化为 RFC 6455 。Qt 提供的 QWebSocket 既可以用于客户端应用程序&#xff0c;也可以用于服务端应用程序&#xff0c;接口大部分和 QTcpSocket 一致。 …

《Apollo 智能驾驶进阶课程》

来自 &#xff1a; https://www.bilibili.com/video/BV1G341117NQ/ https://apollo.baidu.com/ 主要学习资源如下&#xff1a; Apollo社区公众号&#xff0c;直接有整个视频教程的微信推文教程&#xff1a;链接一个CSDN博主记录的笔记&#xff1a; https://blog.csdn.net/qq_45…

Python遍历网格中每个点

遍历网格中每个点 1. 问题描述2. Python实现2.1 网格参数初始化2.2 遍历赋值2.3 矩阵赋值1. 问题描述 最近需要实现一个对矩阵赋值并对矩阵表示的网格参数进行测试的任务,写了一段代码提供参考。 假设网格的长宽均为 2. Python实现 2.1 网格参数初始化 首先定义好需要划分…

【AI】Stable-Diffusion-WebUI使用指南

注&#xff1a;csdn对图片有审核&#xff0c;审核还很奇葩&#xff0c;线稿都能违规&#xff0c;为保证完整的阅读体验建议移步至个人博客阅读 最近AI绘画实现了真人照片级绘画水准&#xff0c;导致AI绘画大火&#xff0c;公司也让我研究研究&#xff0c;借此机会正好了解一下…

Vue- Treeselect组件使用(下拉框树形结构)

前言 最近在开发时遇到一个问题&#xff0c;是在输入框里面放一个树形组件。查看饿了吗之后发现都不太适合 最后在网上搜了一下&#xff0c;真的是有相关的组件Treeselect&#xff0c;确实有相关文章说明&#xff0c;但是比较乱。 在这里记录一下&#xff0c;Treeselect全局使…