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

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数+1const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;//更改系统的tick数,如果溢出则等于0xTickCount = 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.mzph.cn/news/182365.shtml

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

相关文章

【ZEDSLAM】Ubuntu18.04系统ZED 2i双目相机SDK安装、联合标定、SLAM测试

0.设备、环境和说明 笔记本电脑i5-8300H、GTX 1060、32GRAM 因为后面要测试Vins-Fusion和ORB-SLAM3&#xff0c;所以推荐安装Ubuntu 18.04&#xff08;或者Ubuntu 20.04&#xff09; ROS 1&#xff08;不建议用比Ubuntu18更低的版本&#xff09; ROS一键安装命令&#xff1a;…

智能监控平台/视频共享融合系统EasyCVR接入RTSP协议视频流无法播放原因是什么?

视频集中存储/云存储/视频监控管理平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。AI智能/大数据视频分析EasyCVR平台已经广泛应用在工地、工厂、园区、楼…

网络入门---网络编程预备知识

目录标题 ifconfigip地址和mac地址的区别端口号pid和端口号UDP和TCP的初步了解网络字节序socket套接字 ifconfig 通过指令ifconfig便可以查看到两个网络接口&#xff1a; 我们当前使用的是一个linux服务器并是一个终端设备&#xff0c;所以他只需要一个接口用来入网即可&…

JVM

图来自JavaGuide 程序计数器 程序计数器是线程私有的&#xff0c;每个线程一份&#xff0c;是线程安全的&#xff1b;内部保存的字节码的行号&#xff0c;用于记录正在执行的字节码指令的地址。 java堆 java堆是线程共享的区域&#xff08;线程不安全&#xff09;&#xff…

ECRS生产工时分析软件:工业效率提升的隐形引擎

降本增效往往是企业开工规划的第一步。那到底降什么本&#xff0c;增什么效呢&#xff0c;对于很多企业来说&#xff0c;都是从采购成本入手&#xff0c;结果采购成本是降下来了&#xff0c;但是整体品质却下降了。实际上&#xff0c;要降本增效&#xff0c;优化现场管理才是企…

leetcode 611. 有效三角形的个数(优质解法)

代码&#xff1a; class Solution {public int triangleNumber(int[] nums) {Arrays.sort(nums);int lengthnums.length;int n0; //三元组的个数//c 代表三角形最长的那条边for (int clength-1;c>2;c--){int left0;int rightc-1;while (left<right){if(nums[left]nums[r…

【JavaEE初阶】 HTTP响应报文

文章目录 &#x1f332;序言&#x1f38d;200 OK&#x1f340;404 Not Found&#x1f384;403 Forbidden&#x1f334;405 Method Not Allowed&#x1f38b;500 Internal Server Error&#x1f333;504 Gateway Timeout&#x1f332;302 Move temporarily&#x1f38d;301 Move…

序列号管理

序列号管理&#xff0c;将从以下方面进行学习和阐述 WHY 为什么需要序列号&#xff0c;有什么作用 HOW sap如何进行管理序列号 WHEN 什么情况下适合进行序列号管理 1、 什么是序列号 首先简单介绍一个序列号是什么东西&#xff0c;我们使用的手机、电脑或者大家…

Spring Framework远程代码执行漏洞 CVE-2022-22965 漏洞复现

Spring Framework远程代码执行漏洞 CVE-2022-22965 漏洞复现和相关利用工具 名称: Spring Framework 远程命令执行漏洞 描述: Spring core是Spring系列产品中用来负责发现、创建并处理bean之间的关系的一个工具包&#xff0c;是一个包含Spring框架基本的核心工具包&#xff0…

【SparkSQL】基础入门(重点:SparkSQL和Hive的异同、SparkSQL数据抽象)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Spark SQL的定义、特点、发展历史、与hive的区别、数据抽象、SparkSession对象。 后续会继续分享其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一…

远程工具无法连接VMware虚拟机 (Network error: Connection timed out)

windowr输入&#xff1a;services.msc ①检查window相关的Vmmare服务是否开启&#xff1a; 确保上面这个几个启动类型是自动&#xff0c;状态是正在运行。 ②排查虚拟网卡是否禁用&#xff1a; 设置->网络->更改适配器选项&#xff1a; ③检查虚拟网络编辑器以及虚拟机…

Java数据结构之《栈实现括号匹配的检验》问题

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我写完…

CPU虚拟化的过程

VMCS 是Virtual Machine Control Structure。是 Intel 实现 CPU 虚拟化&#xff0c;记录 vCPU 状态的一个关键数据结构。VMCS 数据结构主要包含以下信息。 Guest-state area&#xff0c;即 vCPU 的状态信息&#xff0c;包括 vCPU 的基本运行环境&#xff0c;例如寄存器等。Hos…

4G5G防爆执法记录仪、防爆智能安全帽赋能智慧燃气,可视化巡检巡线,安全生产管控

随着燃气使用的普及&#xff0c;燃气安全问题日益突出。传统应急安全问题处理方式暴露出以下问题&#xff1a; 应急预案不完善&#xff1a;目前一些燃气企业的应急预案存在实用性不高、流程不清晰等问题&#xff0c;导致在紧急情况下难以迅速启动和有效执行。 部门协同不流畅…

[Python入门系列之十二]安装Jupyter notebook与代码运行

引言 Jupyter Notebook将代码、图片和文本完美结合在一起&#xff0c;为编程学习带来了前所未有的便捷性。本文旨在为初学者提供一个关于Jupyter Notebook的入门指南。 什么是Jupyter Notebook Jupyter Notebook是一个开源的Web应用程序&#xff0c;允许你创建和共享包含代码…

wvp如果确认音频udp端口开放成功

用到工具 在服务器上开启端口监听 选中udp server&#xff0c;点击创建按钮 设置服务器监听端口 在客户端连接服务器端口 选中udp客户端&#xff0c;点击创建 输入服务器地址 远程端口和本地端口&#xff0c;本地端口只要没被占用都可以使用 &#xff0c;点击确认 发送数据 …

使用凌鲨管理本地git仓库

把本地git仓库添加到凌鲨后&#xff0c;可以更方便的获取git仓库的信息&#xff0c;比如查看commit记录&#xff0c;统计代码提交量&#xff0c;获取远程仓库的issue等功能。 功能 查看提交/分支/标记列表 查看提交差异 查看远程仓库和相关issue 每天代码量统计 添加本地仓库…

注解(概念、分类、自定义注解)

注解基本概念 注解(元数据)为我们在代码中添加信息提供一种形式化的方法&#xff0c;我们可以在某个时刻非常方便的使用这些数据。将的通俗一点&#xff0c;就是为这个方法增加的说明或功能。 作用&#xff1a; 编写文档&#xff1a;通过代码里标识的注解生成文档【生成doc文…

S7-1200PLC和KEPserver OPC通信

KEPserver属于OPCserver商用软件,在国内的市场占有率还是比较高的,这篇博客介绍S71200PLC和KEPserver通信配置,首先我们看下我们的PLC的IP地址。 1、S7-1200PLC IP地址 接下来我们在KEPserver新建通道 2、新建通道 3、选择通道网卡 4、添加PLC IP地址 5、指定扫描模式 6、…

【用unity实现100个游戏之16】Unity中程序化生成的2D地牢4(附项目源码)

文章目录 最终效果前言素材按程序放置物品放置玩家和敌人控制主角移动参考源码完结 最终效果 前言 本期紧跟着上期内容&#xff0c;主要实现在地牢中生成物品、放置玩家和敌人。 素材 物品素材&#xff1a; https://itch.io/c/1597630/super-retro-world 按程序放置物品 …