9.任务调度

一、开启任务调度器

在这里插入图片描述

1.函数 vTaskStartScheduler()

在这里插入图片描述
函数 vTaskStartScheduler()用于启动任务调度器,任务调度器启动后,FreeRTOS 便会开始
进行任务调度,除非调用函数 xTaskEndScheduler()停止任务调度器,否则不会再返回。函数
vTaskStartScheduler()的代码如下所示:

void vTaskStartScheduler( void )
{BaseType_t xReturn;/* 如果启用静态内存管理,则优先使用静态方式创建空闲任务 */
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{StaticTask_t * pxIdleTaskTCBBuffer = NULL;StackType_t * pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize;vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,&pxIdleTaskStackBuffer,&ulIdleTaskStackSize);xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,configIDLE_TASK_NAME,ulIdleTaskStackSize,( void * ) NULL,portPRIVILEGE_BIT,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer);if( xIdleTaskHandle != NULL ){xReturn = pdPASS;}else{xReturn = pdFAIL;}
}
#else/* 未启用静态内存管理,则使用动态方式创建空闲任务 */
{xReturn = xTaskCreate( prvIdleTask,configIDLE_TASK_NAME,configMINIMAL_STACK_SIZE,( void * ) NULL,portPRIVILEGE_BIT,&xIdleTaskHandle);}
#endif/* 如果启用软件定时器,则需要创建定时器服务任务 */
#if ( configUSE_TIMERS == 1 )
{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();}else{mtCOVERAGE_TEST_MARKER();}
}
#endifif( xReturn == pdPASS ){
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{/* 此函数用于添加一些附加初始化,不用理会 */freertos_tasks_c_additions_init();
}
#endif/* FreeRTOS 关闭中断,* 以保证在开启任务任务调度器之前或过程中,SysTick 不会产生中断,* 在第一个任务开始运行时,会重新打开中断。*/portDISABLE_INTERRUPTS();#if ( configUSE_NEWLIB_REENTRANT == 1 )
{/* Newlib 相关 */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif/* 初始化一些全局变量* xNextTaskUnblockTime: 下一个距离取消任务阻塞的时间,初始化为最大值* xSchedulerRunning: 任务调度器运行标志,设为已运行* xTickCount: 系统使用节拍计数器,宏 configINITIAL_TICK_COUNT 默认为 0* */xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;/* 为任务运行时间统计功能初始化功能时基定时器* 是否启用该功能,可在 FreeRTOSConfig.h 文件中进行配置*/portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();/* 调试使用,不用理会 */traceTASK_SWITCHED_IN();/* 设置用于系统时钟节拍的硬件定时器(SysTick)* 会在这个函数中进入第一个任务,并开始任务调度* 任务调度开启后,便不会再返回*/if( xPortStartScheduler() != pdFALSE ){}else{}}else{/* 动态方式创建空闲任务和定时器服务任务(如果有)时,因分配给 FreeRTOS 的堆空间* 不足,导致任务无法成功创建 */configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}/* 防止编译器警告,不用理会 */( void ) xIdleTaskHandle;/* 调试使用,不用理会 */( void ) uxTopUsedPriority;
}

从上面的代码可以看出,函数 vTaskStartScheduler()主要做了六件事情。

  1. 创建空闲任务,根据是否支持静态内存管理,使用静态方式或动态方式创建空闲任务。
  2. 创建定时器服务任务,创建定时器服务任务需要配置启用软件定时器,创建定时器服务
    任务,同样是根据是否配置支持静态内存管理,使用静态或动态方式创建定时器服务任务。
  3. 关闭中断,使用 portDISABLE_INTERRUPT()关闭中断,这种方式只关闭受 FreeRTOS 管
    理的中断。关闭中断主要是为了防止 SysTick 中断在任务调度器开启之前或过程中,产生中断。
    FreeRTOS 会在开始运行第一个任务时,重新打开中断。
  4. 初始化一些全局变量,并将任务调度器的运行标志设置为已运行。
  5. 初始化任务运行时间统计功能的时基定时器,任务运行时间统计功能需要一个硬件定时
    器提供高精度的计数,这个硬件定时器就在这里进行配置,如果配置不启用任务运行时间统计
    功能的,就无需进行这项硬件定时器的配置。
  6. 最后就是调用函数 xPortStartScheduler()。

2.函数xPortStartScheduler()

在这里插入图片描述
函数 xPortStartScheduler()完成启动任务调度器中与硬件架构相关的配置部分,以及启动第
一个任务,具体的代码如下所示:

BaseType_t xPortStartScheduler( void )
{
#if ( configASSERT_DEFINED == 1 )
{/* 检测用户在 FreeRTOSConfig.h 文件中对中断相关部分的配置是否有误,代码省略 */
}
#endif/* 设置 PendSV 和 SysTick 的中断优先级为最低优先级 */portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;/* 配置 SysTick* 清空 SysTick 的计数值* 根据 configTICK_RATE_HZ 配置 SysTick 的重装载值* 开启 SysTick 计数和中断*/vPortSetupTimerInterrupt();/* 初始化临界区嵌套次数计数器为 0 */uxCriticalNesting = 0;/* 使能 FPU* 仅 ARM Cortex-M4/M7 内核 MCU 才有此行代码* ARM Cortex-M3 内核 MCU 无 FPU*/prvEnableVFP();/* 在进出异常时,自动保存和恢复 FPU 相关寄存器* 仅 ARM Cortex-M4/M7 内核 MCU 才有此行代码* ARM Cortex-M3 内核 MCU 无 FPU*/*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;/* 启动第一个任务 */prvStartFirstTask();/* 不会返回这里 */return 0;
}

函数 xPortStartScheduler()的解析如下所示:

  1. 在启用断言的情况下,函数 xPortStartScheduler()会检测用户在 FreeRTOSConfig.h 文件
    中对中断的相关配置是否有误,感兴趣的读者请自行查看这部分的相关代码。
  2. 配置 PendSV 和 SysTick 的中断优先级为最低优先级,请参考 4.3.1 小节。
  3. 调用函数 vPortSetupTimerInterrupt()配置 SysTick,函数 vPortSetupTimerInterrupt()首先会
    将 SysTick 当 前 计 数 值 清 空 , 并 根 据 FreeRTOSConfig.h 文件中配置的
    configSYSTICK_CLOCK_HZ(SysTick 时钟源频率)和 configTICK_RATE_HZ(系统时钟节拍
    频率)计算并设置 SysTick 的重装载值,然后启动 SysTick 计数和中断。
  4. 初始化临界区嵌套计数器为 0。
  5. 调用函数 prvEnableVFP()使能 FPU,因为 ARM Cortex-M3 内核 MCU 无 FPU,此函数
    仅在 ARM Cortex-M4/M7 内核 MCU 平台上被调用,执行改函数后 FPU 被开启。
  6. 接下来将 FPCCR 寄存器的[31:30]置 1,这样在进出异常时,FPU 的相关寄存器就会自
    动地保存和恢复,同样地,因为 ARM Cortex-M3 内核 MCU 无 FPU,此当代码仅在 ARM Cortex-M4/M7 内核 MCU 平台上被调用。
  7. 调用函数 prvStartFirstTask()启动第一个任务。

二、FreeRTOS 启动第一个任务

在这里插入图片描述

1.函数 prvStartFirstTask()

函数 prvStartFirstTask()用于初始化启动第一个任务前的环境,主要是重新设置 MSP 指针,
并使能全局中断,具体的代码如下所示:

__asm void prvStartFirstTask( void )
{/* 8 字节对齐 */PRESERVE8ldr r0, =0xE000ED08 /* 0xE000ED08 为 VTOR 地址 */ldr r0, [ r0 ] /* 获取 VTOR 的值 */ldr r0, [ r0 ] /* 获取 MSP 的初始值 *//* 初始化 MSP */msr msp, r0/* 使能全局中断 */cpsie icpsie fdsbisb/* 调用 SVC 启动第一个任务 */svc 0nopnop
}

从上面的代码可以看出,函数 prvStartFirstTask()是一段汇编代码,解析如下所示:

  1. 首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,栈在任何时候都是需要 4 字
    节对齐的,而在调用入口得 8 字节对齐,在进行 C 编程的时候,编译器会自动完成的对齐的操
    作,而对于汇编,就需要开发者手动进行对齐。

  2. 接下来的三行代码是为了获得 MSP 指针的初始值,那么这里就能够引出两个问题:
    (1) 什么是 MSP 指针?
    程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,
    MCU 会自动更新 SP 指针,使 SP 指针指向最后一个入栈的元素,那么程序就可以根据 SP 指针
    来从栈中存取信息。对于正点原子的 STM32F1、STM32F4、STM32F7 和 STM32H7 开发板上使
    用的 ARM Cortex-M 的 MCU 内核来说,ARM Cortex-M 提供了两个栈空间,这两个栈空间的堆
    栈指针分别是 MSP(主堆栈指针)和 PSP(进程堆栈指针)。在 FreeRTOS 中 MSP 是给系统栈
    空间使用的,而 PSP 是给任务栈使用的,也就是说,FreeRTOS 任务的栈空间是通过 PSP 指向
    的,而在进入中断服务函数时,则是使用 MSP 指针。当使用不同的堆栈指针时,SP 会等于当
    前使用的堆栈指针。
    在这里插入图片描述

(2) 为什么是 0xE00ED08?
0xE00ED08 是 VTOR(向量表偏移寄存器)的地址,VTOR 中保存了向量表的偏移地址。
一般来说向量表是从其实地址 0x00000000 开始的,但是在有情况下,可能需要修改或重定向向
量表的首地址,因此 ARM Corten-M 提供了 VTOR 对向量表进行从定向。而向量表是用来保存
中断异常的入口函数地址,即栈顶地址的,并且向量表中的第一个字保存的就是栈底的地址,
在 start_stm32xxxxxx.s 文件中有如下定义:

__Vectors DCD __initial_sp ; 栈底指针DCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ; Hard Fault HandlerDCD MemManage_Handler ; MPU Fault Handler

以上就是向量表(只列出前几个)的部分内容,可以看到向量表的第一个元素就是栈指针
的初始值,也就是栈底指针。

在了解了这两个问题之后,接下来再来看看代码。首先是获取 VTOR 的地址,接着获取
VTOR 的值,也就是获取向量表的首地址,最后获取向量表中第一个字的数据,也就是栈底指
针了。
在这里插入图片描述

  1. 在获取了栈顶指针后,将 MSP 指针重新赋值为栈底指针。这个操作相当于丢弃了程序
    之前保存在栈中的数据,因为FreeRTOS从开启任务调度器到启动第一个任务都是不会返回的,
    是一条不归路,因此将栈中的数据丢弃,也不会有影响。
  2. 重新赋值 MSP 后,接下来就重新使能全局中断,因为之前在函数 vTaskStartScheduler()
    中关闭了受 FreeRTOS 的中断。
  3. 最后使用 SVC 指令,并传入系统调用号 0,触发 SVC 中断。

2.函数 vPortSVCHandler()

在这里插入图片描述
在这里插入图片描述

当使能了全局中断,并且手动触发 SVC 中断后,就会进入到 SVC 的中断服务函数中。SVC
的中断服务函数为 vPortSVCHandler(),该函数在 port.c 文件中有定义,具体的代码如下所示
:

__asm void vPortSVCHandler( void )
{/* 8 字节对齐 */PRESERVE8/* 获取任务栈地址 */ldr r3, = pxCurrentTCB /* r3 指向优先级最高的就绪态任务的任务控制块 */ldr r1, [ r3 ] /* r1 为任务控制块地址 */ldr r0, [ r1 ] /* r0 为任务控制块的第一个元素(栈顶) *//* 模拟出栈,并设置 PSP */ldmia r0 !, { r4 - r11 } /* 任务栈弹出到 CPU 寄存器 */msr psp, r0 /* 设置 PSP 为任务栈指针 */isb/* 使能所有中断 */mov r0, # 0msr basepri,/* 使用 PSP 指针,并跳转到任务函数 */orr r14, # 0xdbx r14
}

从上面代码中可以看出,函数 vPortSVCHandler()就是用来跳转到第一个任务函数中去的,
该函数的具体解析如下:

  1. 首先通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就
    绪态任务就是系统将要运行的任务。pxCurrentTCB 是一个全局变量,用于指向系统中优先级最
    高的就绪态任务的任务控制块,在前面创建 start_task 任务、空闲任务、定时器处理任务时自动
    根据任务的优先级高低进行赋值的,具体的赋值过程在后续分析任务创建函数时,会具体分析。
    这里举个例子,在《FreeRTOS 移植实验》中,start_task 任务、空闲任务、定时器处理任务
    的优先级如下表所示:
    在这里插入图片描述
    在这里插入图片描述
    从上表可以看出,在《FreeRTOS 移植实验》中,定时器处理任务的任务优先级为 31,是系
    统中优先级最高的任务,因此当进入 SVC 中断时,pxCurrentTCB 就是指向了定时器处理任务
    的任务控制块。

接着通过获取任务控制块中的第一个元素,得到该任务的栈顶指针,任务控制块的相关内
容,请查看第 5.5 小节《FreeRTOS 任务控制块》。

  1. 接下来通过任务的栈顶指针,将任务栈中的内容出栈到 CPU 寄存器中,任务栈中的内
    容在调用任务创建函数的时候,已经初始化了。然后再设置 PSP 指针,那么,这么一来,任务
    的运行环境就准备好了。
  2. 通过往 BASEPRI 寄存器中写 0,允许中断。
  3. 最后通过两条汇编指令,使 CPU 跳转到任务的函数中去执行,代码如下所示:
orr r14, # 0xdbx r14

要弄清楚这两条汇编代码,首先要清楚 r14 寄存器是干什么用的。通常情况下,r14 为链接
寄存器(LR),用于保存函数的返回地址。但是在异常或中断处理函数中,r14 为 EXC_RETURN
(关于 r14 寄存器的相关内容,感兴趣的读者请自行查阅相关资料),EXC_RETURN 各比特位
的描述如下表所示:
在这里插入图片描述
因为此时是在 SVC 的中断服务函数中,因此此时的 r14 应为 EXC_RETURN,将 r14 与 0xd
作或操作,然后将值写入 r14,那么就是将 r14 的值设置为了 0xFFFFFFED 或 0xFFFFFFED(具
体看是否使用了浮点单元),即返回后进入线程模式,并使用 PSP。这里要注意的是,SVC 中断
服务函数的前面,将 PSP 指向了任务栈。

说了这么多,FreeRTOS 对于进入中断后 r14 为 EXC_RETURN 的具体应用就是,通过判断
EXC_RETURN 的 bit4 是否为 0,来判断任务是否使用了浮点单元。

最后通过 bx r14 指令,跳转到任务的任务函数中执行,执行此指令,CPU 会自动从 PSP 指
向的栈中出栈 R0、R1、R2、R3、R12、LR、PC、xPSR 寄存器,并且如果 EXC_RETURN 的
bit4 为 0(使用了浮点单元),那么 CPU 还会自动恢复浮点寄存器。

三、任务切换

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

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

相关文章

Centos修改系統語言

一、使用命令行修系统语言 1、显示系统当前语言环 [rootkvm-suma ~]# localectl System Locale: LANGen_US.utf8 VC Keymap: cn X11 Layout: cn 2、查看系统支持字符集 [rootkvm-suma ~]# locale -a 2、设置系统语言环境 [rootkvm-suma ~]# localectl set-locale LANGz…

【GESP试卷】2024年03月Scratch四级试卷

2024年GESP03月认证Scratch四级试卷 分数:100 题数:27 一、单选题(共15题,每题2分,共30分) 010203040506070809101112131415CDBBACBCDCDADBA 1、小杨的父母最近刚刚给他买了一块华为手表,他说手表上跑的是鸿蒙&…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【00】补充

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【00】补充 插件IDEAVsCode MavenvagrantDocker解决MySQL连接慢问题启动(自动)Docker注意切换到root用户远程访问MySQL MyBatisPlus代码地址参考 插件 IDEA Mybati…

【数据挖掘】四分位数识别数据中的异常值(附代码)

写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 路虽远,行则将至&#…

STM32F1之OV7725摄像头

目录 1. 摄像头简介 2. OV7725 摄像头简介 3. OV7725 引脚 4. OV7725 功能框架图 5. SCCB时序 5.1 SCCB 的起始、停止信号及数据有效性 5.2 SCCB 数据读写过程 1. 摄像头简介 在各类信息中,图像含有最丰富的信息,作为机…

SVM原问题与对偶问题

目的:求出我们的f(X),它代表着我们X映射到多维的情况,能够帮我们在多维中招到超平面进行分类。 1.优化问题: 1.1推荐好书: 1.2 优化理论中的原问题: 原问题和限制条件如下: 这是一个泛化性…

【漏洞复现】英飞达医学影像存档与通信系统 WebJobUpload 任意文件上传漏洞

0x01 产品简介 英飞达医学影像存档与通信系统 Picture Archiving and Communicaton System,它是应用在医院影像科室的系统,主要的任务就是把日常产生的各种医学影像(包括核磁,CT,超声,各种X光机,各种红外仪…

LeetCode - 数组 - 四数之和

题目地址 描述 给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复)&#x…

cs与msf权限传递以及mimikatz抓取win2012明文密码

简单的介绍 cs与msf的简单介绍 我查找过资料得出,Cobalt Strike 的前身是 Armitage,而 Armitage 又可以理解为 Metasploit Framework 的图形界面版,因此 Cobalt Strike 与 Metasploit Framework 在很多地方都是兼容的,所以我们便…

人工智能 框架 paddlepaddle 飞桨 使用指南 使用例子 线性回归模型demo 详解

安装过程&使用指南&线性回归模型 使用例子 本来预想 是安装 到 conda 版本的 11.7的 但是电脑没有gpu 所以 安装过程稍有变动,下面简单讲下 conda create -n paddle_env117 python=3.9 由于想安装11.7版本 py 是3.9 所以虚拟环境名称也是 paddle_env117 activa…

下载和安装AD19 - Altium Designer 19.1.9 Build 167

虽然有AD24 的安装资源,但是我比较喜欢19 这个数字[doge] 下载 仍然是从毛子网站源头进货:https://rutracker.net/forum/viewtopic.php?t5754276,网盘: https://pan.baidu.com/s/1ic31N4h7HS2FBu7JFll0YQ?pwdvjum 提取码: vjum 安装 压…

【DevOps】深入了解RabbitMQ:AMQP协议基础、消息队列工作原理和应用场景

目录 一、核心功能 二、优势 三、核心概念 四、工作原理 五、交换机类型 六、消息确认 七、持久性和可靠性 八、插件和扩展 九、集群和镜像队列 十、客户端库 十一、管理界面 十二、应用场景 RabbitMQ是一个基于AMQP协议的消息队列中间件,提供高可用、可…

[MRCTF2020]Xor

32位程序 主要逻辑 flagMSAWB~FXZ:J:tQJ"N bpdd}8g for i in range(len(flag)):print(chr(ord(flag[i])^i),end)

react 权限树形结构实现

项目背景 react ant design 实现效果 1 将后台返回的平铺数据 , 转成树形结构 const [roleId, setRoleId] useState() //存储角色id// 弹权限弹窗const empowerHandle async record > {setRoleId(record.roleId)//获取单独的权限const res1 await getPermission({ role…

力扣96. 不同的二叉搜索树

Problem: 96. 不同的二叉搜索树 文章目录 题目描述思路复杂度Code 题目描述 思路 一个数字做根节点的话可能的结果为:其左边数字做子树的组合数字乘以其右边数字做子树的个数之积 1.创建备忘录memo; 2.递归分别求取当前数字左边和右边数字做子树的数量&…

Vue 中 diff 算法原理

1. Diff 概念 vue 基于虚拟 DOM 做更新 。diff 的核心就是比较两个虚拟节点的差异 。Vue 的 diff 算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。 2. Vue2 Diff 比较流程. 1.1先比较是否是相同节点 key tag 1.2相同节点比较属性,并…

【数据结构与算法 刷题系列】移除链表元素

💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:数据结构与算法刷题系列(C语言) 期待您的关注 目录 一、问题描述 二、解题思路 三、源代码实现 一、问题…

基于trunk、yew构建web开发脚手架

trunk 构建、打包 rust wasm 程序;yewweb 前端开发库; 项目仓库yew-web trunk 之前已经简单介绍了trunk,全局安装: $> cargo install --locked trunk常用命令: trunk build 基于wasm-bindgen构建 wasm 程序。trunk watch …

vue17:v-bind对css样式的控制增强

代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><styl…