当前位置: 首页 > news >正文

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空间

相关文章:

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

1、前言 本文是以RISC-V架构为例进行讲解&#xff0c;在汇编代码层面和ARM架构不一样&#xff0c;但是整体框架是一样的侧重任务调度底层机制讲解&#xff0c;讲解代码只保留了基本功能&#xff0c;可配置的功能基本都已经删除本文是以可抢占式调度机制进行讲解RISC-V架构只支持…...

深入了解Spring Boot中@Async注解的8大坑点

文章目录 1. 缺少EnableAsync注解2. 异步方法需独立3. 不同的异步方法间无法相互调用4. 返回值为void的异步方法无法捕获异常5. 外部无法直接调用带有Async注解的方法6. Async方法不适用于private方法7. 缺失异步线程池配置8. 异步方法与事务的兼容结语 &#x1f389;深入了解S…...

C语言——深入理解指针(3)

目录 1. 字符指针 2. 数组指针 2.1 数组指针变量 2.2 数组指针变量的初始化 3.二维数组传参&#xff08;本质&#xff09; 4. 函数指针 4.1 函数指针变量的创建 4.2 函数指针的使用 4.3 typedef 5. 函数指针数组 6. 转移表&#xff08;函数指针数组的使用&#xff…...

图书管理系统源码,图书管理系统开发,图书借阅系统源码配置和运行图解源码已附加

目录 配置简介和软件条件 数据库附件配置 vs应用程序web.config配置数据库链接字符串 数据库文件脚本代码 配置简介和软件条件 所需要的软件是Vs2017以上数据库是Sqlserver2012以上&#xff0c;如果数据库附件不了可以使用数据库脚本附件数据库脚本会在文章末尾写出来。可以…...

FFmpeg介绍

官方网站&#xff1a;http://www.ffmpeg.org/ 项目组成 libavformat 封装模块&#xff0c;封装了Protocol层和Demuxer、Muxer层&#xff0c;使得协议和格式对于开发者来说是透明的。FFmpeg能否支持一种封装格式的视频的封装与解封装&#xff0c;完全取决于这个库&#xff0c…...

修改网卡PHY的灯-RK3568

文章目录 前言1.定制PHY的灯2.通过命令修改LED状态3.修改驱动效果前言 前面我们已经移植了网卡到开发板上面,也能够正常的进行通信,但是,我们会发现座子上面的灯并没有全部亮起来,而且这些灯的含义是什么,并没有讲解到,这里,就此问题,展开学习。 PHY 有一个重要的功能…...

11月29日作业

作业&#xff1a; 自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show(…...

【从删库到跑路 | MySQL总结篇】表的增删查改(进阶下)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、联合…...

【机器学习 | 可视化系列】可视化系列 之 决策树可视化

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…...

配置阿里云的yum仓库

目录 配置阿里云的yum源 清理官方yum源 下载阿里云的yum源 centos7下载阿里云yum源&#xff1a; 清理yum缓存&#xff1a;yum cleam all 生成自己的yum缓存&#xff1a;yum makecache 使用centos自带的官方yum源下载很慢&#xff0c;那今天来跟大家说说配置阿里云的yum仓…...

Kubernetes之kubeadm日志展示篇—K8S私有云worker节点gluster安装部署

文章目录 一. 服务器信息1.1 环境准备1.2 配置hosts解析记录 二. 安装与部署2.1 配置仓库 &#xff08;所有节点&#xff09;2.2 安装服务 &#xff08;所有节点&#xff09;2.3 启动服务 &#xff08;所有节点&#xff09;2.4 配置资源池 &#xff08;主节点&#xff09;2.5 创…...

P3368 【模板】树状数组 2 (区间修改,单点查询)

本题链接&#xff1a;【模板】树状数组 2 - 洛谷 题目&#xff1a; 输入 5 5 1 5 4 2 3 1 2 4 2 2 3 1 1 5 -1 1 3 5 7 2 4 输出 6 10 思路&#xff1a; 根据题意&#xff0c;这里是需要区间添加值&#xff0c;单点查询值。如果区间添加值中暴力去一个个加值&#xff0c;肯定…...

智慧城市运营管理平台解决方案:PPT全文61页,附下载

关键词&#xff1a;智慧城市建设方案&#xff0c;智慧城市解决方案&#xff0c;智慧城市的发展前景和趋势&#xff0c;智慧城市建设内容&#xff0c;智慧城市运营管理平台 一、智慧城市运营平台建设背景 随着城市化进程的加速&#xff0c;城市面临着诸多挑战&#xff0c;如环…...

Vue性能优化方法

一、前言 1.1 为什么需要性能优化 用户体验&#xff1a;优化性能可以提升用户体验&#xff0c;降低加载时间和响应时间&#xff0c;让用户更快地看到页面内容。SEO优化&#xff1a;搜索引擎更喜欢快速响应的网站&#xff0c;优化性能可以提高网站的排名。节约成本&#xff1…...

关于网站的favicon.ico图标的设置需要注意的几点

01-必须在网页的head标签中放上对icon图标的说明语句&#xff1a; 比如下面这样的语句&#xff1a; <link rel"shortcut icon" href"/favicon.ico">否则&#xff0c;浏览器虽然能读到图标&#xff0c;但是不会把图标显示在标签上。 02-为了和本地开…...

PHP中关于func_get_args()方法

首先呢这个函数出现的是比较早的,大致应该是PHP4出现的, func_get_args — 返回一个包含函数参数列表的数组 说明 func_get_args(): array 获取函数参数列表的数组。 该函数可以配合 func_get_arg() 和 func_num_args() 一起使用&#xff0c;从而使得用户自定义函数可以接…...

EMA训练微调

就是取前几个epoch的weight的平均值&#xff0c;可以缓解微调时的灾难性遗忘&#xff08;因为新数据引导&#xff0c;模型权重逐渐&#xff0c;偏离训练时学到的数据分布&#xff0c;忘记之前学好的先验知识&#xff09; class EMA():def __init__(self, model, decay):self.…...

Kafka集群部署详细教程

版本说明 Ubuntu 18.04.6Zookeeper 3.5.9Kafka 2.7.0JDK8 集群配置 操作系统ip域名Zookeeper 端口Kafka 端口Ubuntu 18.04.6192.168.50.131kafka1.com21819092Ubuntu 18.04.6192.168.50.132kafka2.com21819092Ubuntu 18.04.6192.168.50.133kafka3.com21819092 安装 vim, cu…...

交叉编译

1. 交叉开发 交叉编译&#xff1a; 在电脑把程序编写 编译 调试好 再下载到嵌入式产品中运行 编译&#xff1a; gcc 之前编译环境和运行环境是一样的 交叉编译&#xff1a; 编译 把编译代码和运行分开 编译代码在虚拟机中 运行…...

数据结构与算法之递归: LeetCode 46. 全排列 (Typescript版)

全排列 https://leetcode.cn/problems/permutations/ 描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...