FreeRTOS任务切换

news2024/12/26 23:03:04

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/606017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(字符串 ) 459. 重复的子字符串——【Leetcode每日一题】

❓459. 重复的子字符串 难度&#xff1a;简单 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s “abab” 输出: true 解释: 可由子串 “ab” 重复两次构成。 示例 2: 输入: s “aba” 输出: false 示例 3: 输入: s “…

IDEA+Mysql调试常见异常解决办法_kaic

IDEA导入项目出现Error: java: 程序包javax.servlet.http不存在错误的解决办法 解决方法&#xff1a;打开File>Project Structure>Libraries&#xff0c;点击右侧加号&#xff0c;寻找到tomcat的lib文件夹&#xff0c;点击ok&#xff0c;IDEA会自动重新Rebuild Project&…

数据结构 | 图的深度优先遍历和广度优先遍历(C语言)

一、数据结构定义 1、图 #define MaxVertexNum 100 // 最大可存储的节点数目/*图*/ typedef char VexterType; typedef int EdgeType;typedef struct GraphMatrix {VexterType Vexs[MaxVertexNum]; //结点 EdgeType Edges[MaxVertexNum][MaxVertexNum]; //边int vexnum, a…

使用 MCSM 面板一键搭建我的世界服务器,并内网穿透公网远程联机

文章目录 前言1.Mcsmanager安装2.创建Minecraft服务器3.本地测试联机4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射内网端口 5.远程联机测试6. 配置固定远程联机端口地址6.1 保留一个固定TCP地址6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 转载自远程穿透文章&…

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

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

图像处理:GrapeCity Documents Imaging 6.1.2 Crack

适用于 .NET 6 的快速、强大的映像 API 库,在代码中应用高级图像处理&#xff0c;零依赖关系。 加载和保存图像文件&#xff0c;如BMP&#xff0c;JPEG&#xff0c;TIFF&#xff0c;GIF&#xff0c;ICO&#xff0c;SVG&#xff0c;WebP和PNG 对灰度和 RGB 图像应用抖动和阈值等…

Ribbon 负载均衡策略 —— 图解、源码级解析

文章目录 负载均衡策略RandomRuleRoundRobinRuleRetryRuleWeightedResponseTimeRuleBestAvailableRuleAvailabilityFilteringRuleZoneAvoidanceRule Ribbon 负载均衡策略源码RandomRule源码RoundRobinRule源码BestAvailableRule源码RetryRule源码 通过本文你可以学习到&#xf…

自学大语言模型之BERT

BERT 模型由 Jacob Devlin、Ming-Wei Chang、Kenton Lee 和 Kristina Toutanova在BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding中提出。它是一种双向变换器&#xff0c;使用掩码语言建模目标和对包含多伦多图书语料库和维基百科的大型语…

开源高星精选,10个2023企业级Python测试项目,再不学习时间就没了

纸上得来终觉浅&#xff0c;光学习理论知识是不够的。 想要学好软件测试必须要结合实战项目深入掌握&#xff0c;今天给大家分享十个2022最新企业级Python软件测试项目&#xff1a; ​ 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ▌Rank 1&#xf…

SEW-Movifit软件的调试步骤

首先安装软件&#xff08;名称和版本为SEW_Software_MotionStudio_V5-9-0-4-compact&#xff09;。安装完毕后打开软件&#xff0c;新建一个工程。 3、新建完成之后会进入如下画面。 4、点击红框内的图标进行设置 5、打开后会显示如下画面&#xff0c;在下拉菜单中选择serial这…

【Unity3D】高斯模糊特效

1 高斯模糊原理 边缘检测特效中使用了卷积运算进行了边缘检测&#xff0c;本文实现的高斯模糊特效同样使用了卷积运算&#xff0c;关于卷积核和卷积运算的概念&#xff0c;读者可以参考边缘检测特效。 本文完整资源见→Unity3D高斯模糊特效。 我们将用于模糊处理的卷积核称为模…

C++模拟牛顿力学(2D)

简介 如何用计算机来模拟真实世界呢&#xff1f;计算机最大的功能是计算&#xff0c;而物理学的种种公式就把现实世界中的物理规律以数学的语言描绘了出来&#xff0c;从而使我们可以通过计算大致模拟现实世界的物体运动。因此不难想到把物理学定律&#xff08;这里用的是牛顿…

SAP-MM-维护物料主数据的类(Class)和特性(Characteristic)

一&#xff0e;说明 物料主数据有千个左右条目&#xff0c;但仍不能满足各类物料自有特性的描述&#xff0c;为此SAP启用了类&#xff08;Class&#xff09;和特性&#xff08;Characteristic&#xff09;&#xff0c;并在物料主数据的分类视图&#xff08;Characteristic&…

推荐一款免费开源的代码质量分析工具

文章目录 一、简介二、环境安装三、使用说明四、其他报错UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe6 in position 29: ordinal not in range(128)**linux:****windos:** 五、安全编程规范 一、简介 Flawfinder是一款开源的关于C/C静态扫描分析工具&#xf…

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

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

黑客常用的十大工具(附工具安装包),你知道几款?

注&#xff1a;本文总结白帽黑客常用的十大工具。文档仅供参考&#xff0c;不得用于非法用途&#xff0c;否则后果自负。 1 Nmap nmap是一个网络连接端扫描软件&#xff0c;用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端&#xff0c;并且推断计算机运行哪个…

谈谈开源的利弊和国内的开源 ——《新程序员005:开源深度指南 新金融背后的科技力量》书评

感谢CSDN的送测 《新程序员005&#xff1a;开源深度指南 & 新金融背后的科技力量》 是一本以计算机编程和金融科技为主题的杂志书&#xff0c;由中国最大的开源社区之一的开源社主办&#xff0c;内容丰富多样&#xff0c;包括了众多知名开源项目和工具的介绍&#xff0c;同…

第 3 章:使用 Vue 脚手架

目录 具体步骤 模板项目的结构&#xff08;脚手架文件结构&#xff09; Vue脚手架报错 修改方案&#xff1a; 关于不同版本的Vue vue.config.js配置文件 ref属性 props配置项 mixin(混入) 插件 小结&#xff1a; scoped样式 小结&#xff1a; Todo-list 案例 小结…

kafka重点问题解答-----kafka 的设计架构

1. kafka 都有哪些特点&#xff1f; 高吞吐量&#xff0c;低延迟 可以热扩展 并发度高 具有容错性(挂的只剩1台也能正常跑) 可靠性高 2. 请简述你在哪些场景下会选择 kafka&#xff1f; kafka的一些应用 日志收集&#xff1a;一个公司可以用kafka可以收集各种服务的log&…

自学黑客(网络安全/web渗透),一般人我还是劝你算了吧

由于我之前写了不少网络安全技术相关的文章&#xff0c;不少读者朋友知道我是从事网络安全相关的工作&#xff0c;于是经常有人私信问我&#xff1a; 我刚入门网络安全&#xff0c;该怎么学&#xff1f; 要学哪些东西&#xff1f; 有哪些方向&#xff1f; 怎么选&a…