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架构为例进行讲解,在汇编代码层面和ARM架构不一样,但是整体框架是一样的侧重任务调度底层机制讲解,讲解代码只保留了基本功能,可配置的功能基本都已经删除本文是以可抢占式调度机制进行讲解RISC-V架构只支持…...

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

C语言——深入理解指针(3)
目录 1. 字符指针 2. 数组指针 2.1 数组指针变量 2.2 数组指针变量的初始化 3.二维数组传参(本质) 4. 函数指针 4.1 函数指针变量的创建 4.2 函数指针的使用 4.3 typedef 5. 函数指针数组 6. 转移表(函数指针数组的使用ÿ…...

图书管理系统源码,图书管理系统开发,图书借阅系统源码配置和运行图解源码已附加
目录 配置简介和软件条件 数据库附件配置 vs应用程序web.config配置数据库链接字符串 数据库文件脚本代码 配置简介和软件条件 所需要的软件是Vs2017以上数据库是Sqlserver2012以上,如果数据库附件不了可以使用数据库脚本附件数据库脚本会在文章末尾写出来。可以…...

FFmpeg介绍
官方网站:http://www.ffmpeg.org/ 项目组成 libavformat 封装模块,封装了Protocol层和Demuxer、Muxer层,使得协议和格式对于开发者来说是透明的。FFmpeg能否支持一种封装格式的视频的封装与解封装,完全取决于这个库,…...
修改网卡PHY的灯-RK3568
文章目录 前言1.定制PHY的灯2.通过命令修改LED状态3.修改驱动效果前言 前面我们已经移植了网卡到开发板上面,也能够正常的进行通信,但是,我们会发现座子上面的灯并没有全部亮起来,而且这些灯的含义是什么,并没有讲解到,这里,就此问题,展开学习。 PHY 有一个重要的功能…...

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

【从删库到跑路 | MySQL总结篇】表的增删查改(进阶下)
个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】🎈 本专栏旨在分享学习MySQL的一点学习心得,欢迎大家在评论区讨论💌 目录 一、联合…...

【机器学习 | 可视化系列】可视化系列 之 决策树可视化
🤵♂️ 个人主页: AI_magician 📡主页地址: 作者简介:CSDN内容合伙人,全栈领域优质创作者。 👨💻景愿:旨在于能和更多的热爱计算机的伙伴一起成长!!&…...
配置阿里云的yum仓库
目录 配置阿里云的yum源 清理官方yum源 下载阿里云的yum源 centos7下载阿里云yum源: 清理yum缓存:yum cleam all 生成自己的yum缓存:yum makecache 使用centos自带的官方yum源下载很慢,那今天来跟大家说说配置阿里云的yum仓…...
Kubernetes之kubeadm日志展示篇—K8S私有云worker节点gluster安装部署
文章目录 一. 服务器信息1.1 环境准备1.2 配置hosts解析记录 二. 安装与部署2.1 配置仓库 (所有节点)2.2 安装服务 (所有节点)2.3 启动服务 (所有节点)2.4 配置资源池 (主节点)2.5 创…...

P3368 【模板】树状数组 2 (区间修改,单点查询)
本题链接:【模板】树状数组 2 - 洛谷 题目: 输入 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 思路: 根据题意,这里是需要区间添加值,单点查询值。如果区间添加值中暴力去一个个加值,肯定…...

智慧城市运营管理平台解决方案:PPT全文61页,附下载
关键词:智慧城市建设方案,智慧城市解决方案,智慧城市的发展前景和趋势,智慧城市建设内容,智慧城市运营管理平台 一、智慧城市运营平台建设背景 随着城市化进程的加速,城市面临着诸多挑战,如环…...

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

关于网站的favicon.ico图标的设置需要注意的几点
01-必须在网页的head标签中放上对icon图标的说明语句: 比如下面这样的语句: <link rel"shortcut icon" href"/favicon.ico">否则,浏览器虽然能读到图标,但是不会把图标显示在标签上。 02-为了和本地开…...

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

EMA训练微调
就是取前几个epoch的weight的平均值,可以缓解微调时的灾难性遗忘(因为新数据引导,模型权重逐渐,偏离训练时学到的数据分布,忘记之前学好的先验知识) 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. 交叉开发 交叉编译: 在电脑把程序编写 编译 调试好 再下载到嵌入式产品中运行 编译: gcc 之前编译环境和运行环境是一样的 交叉编译: 编译 把编译代码和运行分开 编译代码在虚拟机中 运行…...
数据结构与算法之递归: LeetCode 46. 全排列 (Typescript版)
全排列 https://leetcode.cn/problems/permutations/ 描述 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

UE5 音效系统
一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类,将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix,将上述三个类翻入其中,通过它管理每个音乐…...
大模型真的像人一样“思考”和“理解”吗?
Yann LeCun 新研究的核心探讨:大语言模型(LLM)的“理解”和“思考”方式与人类认知的根本差异。 核心问题:大模型真的像人一样“思考”和“理解”吗? 人类的思考方式: 你的大脑是个超级整理师。面对海量信…...

【Qt】控件 QWidget
控件 QWidget 一. 控件概述二. QWidget 的核心属性可用状态:enabled几何:geometrywindows frame 窗口框架的影响 窗口标题:windowTitle窗口图标:windowIconqrc 机制 窗口不透明度:windowOpacity光标:cursor…...
【R语言编程——数据调用】
这里写自定义目录标题 可用库及数据集外部数据导入方法查看数据集信息 在R语言中,有多个库支持调用内置数据集或外部数据,包括studentdata等教学或示例数据集。以下是常见的库和方法: 可用库及数据集 openintro库 该库包含多个教学数据集&a…...