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

FreeRTOS 任务调度及相关函数详解(一)

文章目录

  • 一、任务调度器开启函数 vTaskStartScheduler()
  • 二、内核相关硬件初始化函数 xPortStartScheduler()
  • 三、启动第一个任务 prvStartFirstTask()
  • 四、中断服务函数 xPortPendSVHandler()
  • 五、空闲任务


一、任务调度器开启函数 vTaskStartScheduler()

这个函数的功能就是开启任务调度器的,这个函数在文件 tasks.c中有定义,缩减后的函数代码如下:

void vTaskStartScheduler( void )
{BaseType_t xReturn;xReturn = xTaskCreate( prvIdleTask, (1)"IDLE", configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),&xIdleTaskHandle );#if ( configUSE_TIMERS == 1 ) //使用软件定时器使能{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask(); (2)}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_TIMERS */if( xReturn == pdPASS ) //空闲任务和定时器任务创建成功。{portDISABLE_INTERRUPTS(); (3)#if ( configUSE_NEWLIB_REENTRANT == 1 ) //使能 NEWLIB{_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE; (4)xTickCount = ( TickType_t ) 0U;portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); (5)if( xPortStartScheduler() != pdFALSE ) (6){//如果调度器启动成功的话就不会运行到这里,函数不会有返回值的}else{//不会运行到这里,除非调用函数 xTaskEndScheduler()。}}else{//程序运行到这里只能说明一点,那就是系统内核没有启动成功,导致的原因是在创建//空闲任务或者定时器任务的时候没有足够的内存。configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}//防止编译器报错,比如宏 INCLUDE_xTaskGetIdleTaskHandle 定义为 0 的话编译器就会提//示 xIdleTaskHandle 未使用。( void ) xIdleTaskHandle;
}

(1)、创建空闲任务,如果使用静态内存的话使用函数 xTaskCreateStatic()来创建空闲任务,优先级为 tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 为 0,也就是说空闲任务的优先级为最低。

(2)、如果使用软件定时器的话还需要通过函数xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务的具体创建过程是在函数 xTimerCreateTimerTask()中完成的。

(3)、关闭中断,在 SVC 中断服务函数 vPortSVCHandler()中会打开中断。

(4)、变量 xSchedulerRunning 设置为 pdTRUE,表示调度器开始运行。

(5)、当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能,此时需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器。

(6)、调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、FPU 单元和 PendSV 中断等等。

二、内核相关硬件初始化函数 xPortStartScheduler()

FreeRTOS 系统时钟是由滴答定时器来提供的,而且任务切换也会用到 PendSV 中断,这些硬件的初始化由函数 xPortStartScheduler()来完成,缩减后的函数代码如下:

BaseType_t xPortStartScheduler( void )
{/******************************************************************//****************此处省略一大堆的条件编译代码**********************//*****************************************************************/portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; (1)portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; (2)vPortSetupTimerInterrupt(); (3)uxCriticalNesting = 0; (4)prvStartFirstTask(); (5)//代码正常执行的话是不会到这里的!return 0;
}

(1)、设置 PendSV 的中断优先级,为最低优先级。

(2)、设置滴答定时器的中断优先级,为最低优先级。

(3)、调用函数 vPortSetupTimerInterrupt()来设置滴答定时器的定时周期,并且使能滴答定时器的中断,函数比较简单,大家自行查阅分析。

(4)、初始化临界区嵌套计数器。

(5)、调用函数 prvStartFirstTask()开启第一个任务。

三、启动第一个任务 prvStartFirstTask()

经过上面的操作以后我们就可以启动第一个任务了,函数 prvStartFirstTask()用于启动第一个任务,这是一个汇编函数,函数源码如下:

__asm void prvStartFirstTask( void )
{PRESERVE8ldr r0, =0xE000ED08 ;R0=0XE000ED08 (1)ldr r0, [r0] ;取 R0 所保存的地址处的值赋给 R0 (2)ldr r0, [r0] ;获取 MSP 初始值 (3)msr msp, r0 ;复位 MSP (4)cpsie I ;使能中断(清除 PRIMASK) (5)cpsie f ;使能中断(清除 FAULTMASK) (6)dsb ;数据同步屏障 (7)isb ;指令同步屏障 (8)svc 0 ;触发 SVC 中断(异常) (9)nopnop
}

(1)、将 0XE000ED08 保存在寄存器 R0 中。一般来说向量表应该是从起始地址(0X00000000)开始存储的,不过,有些应用可能需要在运行时修改或重定义向量表,Cortex-M 处理器为此提供了一个叫做向量表重定位的特性。向量表重定位特性提供了一个名为向量表偏移寄存器(VTOR)的可编程寄存器。VTOR 寄存器的地址就是 0XE000ED08,通过这个寄存器可以重新定义向量表,比如在 STM32F103 的 ST 官方库中会通过函数 SystemInit()来设置 VTOR 寄存器,

代码如下:

SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //VTOR=0x08000000+0X00

通过上面一行代码就将向量表开始地址重新定义到了0X08000000,向量表的起始地址存储的就是 MSP 初始值。

(2)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取寄存器 VTOR中的值,并将其保存在 R0 寄存器中。这一行代码执行完就以后 R0 的值应该为 0X08000000。

(3)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取地址 0X08000000处存储的数据,并将其保存在 R0 寄存器中。我们知道向量表的起始地址保存的就是主栈指针MSP 的初始值,这一行代码执行完以后寄存器 R0 就存储 MSP 的初始值。现在来看(1)、(2)、(3)这三步起始就是为了获取 MSP 的初始值而已!

(4)、复位 MSP,R0 中保存了 MSP 的初始值,将其赋值给 MSP 就相当于复位 MSP。

(5)和(6)、使能中断。

(7)和(8)、数据同步和指令同步屏障。

(9)、调用 SVC 指令触发 SVC 中断,SVC 也叫做请求管理调用,SVC 和 PendSV 异常对于OS 的设计来说非常重要。SVC 异常由 SVC 指令触发。在 FreeRTOS 中仅仅使用 SVC 异常来启动第一
个任务,后面的程序中就再也用不到 SVC 了。

四、中断服务函数 xPortPendSVHandler()

在函数 prvStartFirstTask()中通过调用 SVC 指令触发了 SVC 中断,而第一个任务的启动就是在 SVC 中断服务函数中完成的,SVC 中断服务函数应该为 SVC_Handler(),但是
FreeRTOSConfig.h 中通过#define 的方式重新定义为了 xPortPendSVHandler(),如下:

#define xPortPendSVHandler PendSV_Handler

函数 vPortSVCHandler()在文件 port.c 中定义,这个函数也是用汇编写的,函数源码如下:

__asm void vPortSVCHandler( void )
{PRESERVE8ldr r3, =pxCurrentTCB ;R3=pxCurrentTCB 的地址 (1)ldr r1, [r3] ;取 R3 所保存的地址处的值赋给 R1 (2)ldr r0, [r1] ;取 R1 所保存的地址处的值赋给 R0 (3)ldmia r0!, {r4-r11, r14} ;出栈 ,R4~R11 和 R14 (4)msr psp, r0 ;进程栈指针 PSP 设置为任务的堆栈 (5)isb ;指令同步屏障mov r0, #0 ;R0=0 (6)msr basepri, r0 ;寄存器 basepri=0,开启中断 (7)orr r14, #0xd ; (8)bx r14 (9)
}

(1)、获取 pxCurrentTCB 指针的存储地址,pxCurrentTCB 是一个指向 TCB_t 的指针,这个指针永远指向正在运行的任务。这里先获取这个指针存储的地址,比如我现在的代码测试出来这个指针是存放在 0X20000044,如下图所示。
在这里插入图片描述
(2)、取 R3 所保存的地址处的值赋给 R1。通过这一步就获取到了当前任务的任务控制块的存储地址。比如当前我的程序中这个地址就为 0X20000EE8,如下图所示:
在这里插入图片描述
(3)、取 R3 所保存的地址处的值赋给 R0,我们知道任务控制块的第一个字段就是任务堆栈的栈顶指针 pxTopOfStack 所指向的位置,所以读取任务控制块所在的首地址(0X20000EE8)得到
的就是栈顶指针所指向的地址,当前我的程序中这个栈顶指针(pxTopOfStack)所指向的地址为0X20000E98,如下图所示
在这里插入图片描述
可以看出(1)、(2)和(3)的目的就是获取要切换到的这个任务的任务栈顶指针,因为任务所对应的寄存器值,也就是现场都保存在任务的任务堆栈中,所以需要获取栈顶指针来恢复这些寄存器值!

(4)、R4~R11,R14 这些寄存器出栈。这里使用了指令 LDMIA,LDMIA 指令是多加载/存储指令,不过这里使用的是具有回写的多加载/存储访问指令,用法如下:

LDMIA Rn! , {reg list}

表示从 Rn 指定的存储器位置读取多个字,地址在每次读取后增加(IA),Rn 在传输完成以后写回。对于 STM32 来说地址一次增加 4 字节,比如如下代码:

LDR R0, =0X800
LDMIA R0!, {R2~R4}

上面两行代码就是将 0X800 地址的数据赋值给寄存器 R2,0X804 地址的数据赋值给寄存器 R3,0X8008 地址的数据赋值给 R4 寄存器,然后,重点来了!此时 R0 为 800A!通过这一步我们就从任务堆栈中将 R4~R11 这几个寄存器的值给恢复了。这里有朋友就要问了,R0~R3,R12,PC,xPSR 这些寄存器怎么没有恢复?这是因为这些寄存器会在退出中断的时候 MCU 自动出栈(恢复)的,而 R4~R11 需要由用户手动出栈。这个我们在分析 PendSV 中断服务函数的时候会讲到。到这步以后我们来看一下堆栈的栈顶指针指到哪
里了?如下图所示:
在这里插入图片描述
从上图可以看出恢复 R4~R11 和 R14 以后堆栈的栈顶指针应该指向地址 0X20000EB8,也就是保存寄存器 R0 值的存储地址。退出中断服务函数以后进程栈指针 PSP 应该从这个地址开始恢复其他的寄存器值。

(5)、设置进程栈指针 PSP,PSP=R0=0X20000EB8,如下图所示:
在这里插入图片描述
(6)、设置寄存器 R0 为 0。

(7)、设置寄存器 BASEPRI 为 R0,也就是 0,打开中断!

(8)、R14 寄存器的值与 0X0D 进行或运算,得到的结果就是 R14 寄存器的新值。表示退出异常以后 CPU 进入线程模式并且使用进程栈!

(9)、执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值,堆栈使用进程栈 PSP,然后执行寄存器 PC 中保存的任务函数。至此,FreeRTOS 的任务调度器正式开始运行!

五、空闲任务

在前面讲解函数 vTaskStartScheduler()说过,此函数会创建一个名为“IDLE”的任务,这个任务叫做空闲任务。顾名思义,空闲任务就是空闲的时候运行的任务,也就是系统中其他的任务由于各种原因不能运行的时候空闲任务就在运行。空闲任务是 FreeRTOS 系统自动创建的,不需要用户手动创建。任务调度器启动以后就必须有一个任务运行!但是空闲任务不仅仅是为了满足任务调度器启动以后至少有一个任务运行而创建的,空闲任务中还会去做一些其他
的事情,如下:
1、判断系统是否有任务删除,如果有的话就在空闲任务中释放被删除任务的任务堆栈和任务控制块的内存。

2、运行用户设置的空闲任务钩子函数。

3、判断是否开启低功耗 tickless 模式,如果开启的话还需要做相应的处理空闲任务的任务优先级是最低的,为 0,任务函数为 prvIdleTask()。

相关文章:

FreeRTOS 任务调度及相关函数详解(一)

文章目录 一、任务调度器开启函数 vTaskStartScheduler()二、内核相关硬件初始化函数 xPortStartScheduler()三、启动第一个任务 prvStartFirstTask()四、中断服务函数 xPortPendSVHandler()五、空闲任务 一、任务调度器开启函数 vTaskStartScheduler() 这个函数的功能就是开启…...

飞桨paddlespeech语音唤醒推理C实现

上篇(飞桨paddlespeech 语音唤醒初探)初探了paddlespeech下的语音唤醒方案,通过调试也搞清楚了里面的细节。因为是python 下的,不能直接部署,要想在嵌入式上部署需要有C下的推理实现,于是我就在C下把这个方…...

04-Mysql常用操作

1. DDL 常见数据库操作 # 查询所有数据库 show databases; # 查询当前数据库 select databases();# 使用数据库 use 数据库名;# 创建数据库 create database [if not exits] 数据库名; # []代表可选可不选# 删除数据库 drop database [if exits] 数据库名; 常见表操作 创建…...

TensorFlow 2 和 Keras 高级深度学习:1~5

原文:Advanced Deep Learning with TensorFlow 2 and Keras 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象&#x…...

UML类图

一、UML 1、什么是UML? UML——Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果。UML本身是一套符号的规定,就像数学符号和化学符号一样&…...

【Python】【进阶篇】二十六、Python爬虫的Scrapy爬虫框架

目录 二十六、Python爬虫的Scrapy爬虫框架26.1 Scrapy下载安装26.2 创建Scrapy爬虫项目1) 创建第一个Scrapy爬虫项目 26.3 Scrapy爬虫工作流程26.4 settings配置文件 二十六、Python爬虫的Scrapy爬虫框架 Scrapy 是一个基于 Twisted 实现的异步处理爬虫框架,该框架…...

PyTorch 深度学习实用指南:6~8

原文:PyTorch Deep Learning Hands-On 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只关心如何实现目…...

数据湖 Hudi 核心概念

文章目录 什么是 Hudi ?Hudi 是如何对数据进行管理的?Hudi 表结构Hudi 核心概念 什么是 Hudi ? Hudi 是一个用于处理大数据湖的开源框架。 大数据湖是指一个大规模的、中心化的数据存储库,其中包含各种类型的数据,如结构化数据、半结构化…...

爬虫请求头Content-Length的计算方法

重点:使用node.js 环境计算,同时要让计算的数据通过JSON.stringify从对象变成string。 1. Blob size var str 中国 new Blob([str]).size // 6 2、Buffer.byteLength # node > var str 中国 undefined > Buffer.byteLength(str, utf8) 6 原文…...

Open Inventor 2023.1 Crack

发行说明 Open Inventor 2023.1(次要版本) 文档于 2023 年 4 月发布。 此版本中包含的增强功能和新功能: Open Inventor 10 版本编号更改体积可视化 单一分辨率的体绘制着色器中与裁剪和 ROI 相关的新功能MeshVizXLM 在 C 中扩展的剪辑线提…...

【华为OD机试真题】查找树中元素(查找二叉树节点)(javaC++python)100%通过率

查找树中元素 知识点树BFSQ搜索广搜 时间限制:1s空间限制:256MB限定语言:不限 题目描述: 已知树形结构的所有节点信息,现要求根据输入坐标(x,y)找到该节点保存的内容 值;其中: x表示节点所在的层数,根节点位于第0层,根节点的子节点位于第1层,依次类推; y表示节…...

常用设计模式

里氏替换原则:子类可以扩展父类的功能,但是不要更改父类的已经实现的方法子类对父类的方法尽量不要重写和重载。(我们可以采用final的手段强制来遵循)创建型模式 单例模式:维护线程数据安全 懒汉式 public class Test{ 饿汉式 private static final Test…...

时序分析 49 -- 贝叶斯时序预测(一)

贝叶斯时序预测(一) 时序预测在统计分析和机器学习领域一直都是一个比较重要的话题。在本系列前面的文章中我们介绍了诸如ARIMA系列方法,Holt-Winter指数平滑模型等多种常用方法,实际上这些看似不同的模型和方法之间都具有千丝万缕…...

从传统管理到智慧水务:数字化转型的挑战与机遇

概念 智慧水务是指利用互联网、物联网、大数据、人工智能等技术手段,将智能化、信息化、互联网等技术与水务领域相结合,通过感知、传输、处理水质、水量、水价等数据信息,对水资源进行全面监测、综合管理、智能调度和优化配置的智能化水务系…...

ROS学习第十八节——launch文件(详细介绍)

1.概述 关于 launch 文件的使用已经不陌生了,之前就曾经介绍到: 一个程序中可能需要启动多个节点,比如:ROS 内置的小乌龟案例,如果要控制乌龟运动,要启动多个窗口,分别启动 roscore、乌龟界面节点、键盘控制节点。如果…...

javaweb在校大学生贷款管理系统ns08a9

1系统主要实现:学生注册、填写详细资料、申请贷款、学校审核、银行审核、贷后管理等功能, (1) 学生注册:学生通过注册用户,提交自己的详细个人资料,考虑现实应用中的安全性,资料提交后不可修改;…...

分布式之搜索解决方案es

一 ES初识 1.1 概述 ElasticSearch:是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,每个字段都被索引并可被搜索,可以快速存储、搜索、分析海量的数据。是ELK的一个组成,是一个产品,而且是非常完善的产品,ELK代表…...

CSDN 编程竞赛四十六期题解

地址:CSDN 编程竞赛四十六期 思路:通过找规律可以知道,在周期第一个位置的数的下标都有一个规律:除以三的余数为 1 。而第二个位置,第三个位置的余数分别为 2 , 0 。 因此可以开一个长度为 3 的总和数组&am…...

Linux——进程

进程介绍及其使用 1、认识冯诺依曼体系2、操作系统如何理解操作系统对硬件做管理? 3、进程如何创建进程进程状态 1、认识冯诺依曼体系 在计算机的硬件结构中,有着图灵和冯诺依曼俩位举足轻重的人物。对于计算机的发展来说有着十分重要的意义。冯诺依曼结…...

计及氢能的综合能源优化调度研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)​现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

基于Springboot+Vue的办公管理系统

角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...

Kafka主题运维全指南:从基础配置到故障处理

#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...