FreeRTOS第一个任务的创建和调度详解(SVC异常)

news2024/11/19 20:27:35

在上一篇文章中,我详细分析了FreeRTOS中上下文切换:基于Cortex-M的RTOS上下文切换详解及FreeRTOS实例

但是第一个任务没有上下文,它是怎么运行的呢?

1 创建任务

如果我们没有创建任务的话,系统也有一个空闲任务用来调度,这里不对这个进行分析。

首先,我们知道pxCurrentTCB指向当前运行任务的TCB,所以我们先看看哪里设置了pxCurrentTCB,流程如下

xTaskCreate
	/* 初始化TCB内容 */
	prvInitialiseNewTask
	/* 将TCB加入ReadyList */
	prvAddNewTaskToReadyList

prvAddNewTaskToReadyList的大概逻辑如下:

if( pxCurrentTCB == NULL )
{
    pxCurrentTCB = pxNewTCB;
    ...
}
else
{
    if( xSchedulerRunning == pdFALSE )
    {
        if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
        {
        pxCurrentTCB = pxNewTCB;
        }
    	...
    }
    ...
}
prvAddTaskToReadyList( pxNewTCB );

也就是说如果pxCurrentTCB为空,则直接将新创建的任务赋值给pxCurrentTCB,如果不为空且还没有开始任务调度,则判断当前创建任务的优先级是否比pxCurrentTCB中任务的优先级高,若是则更改pxCurrentTCB

  • 若任务已经开始调度,就将任务加入readyList中,交给Systick调度,这不属于本文讨论的范围

2 开始调度

接着就是任务调度了,来看看上电后的第一个任务具体是怎么调度的。

vTaskStartScheduler();
	/* 创建空闲任务 */
	xReturn = xTaskCreate(prvIdleTask,...)
	/* 关闭中断 */
	portDISABLE_INTERRUPTS();
	/* 开始任务调度 */
	xPortStartScheduler();
		/* 该函数中主要是初始化一些常量并打开PendSV和Systick中断:略 */
		/* 开始第一个任务 */
		vPortStartFirstTask();

可以看到最后进入到vPortStartFirstTask函数中:

  • 函数实现的具体内容见注释
vPortStartFirstTask
	/* 初始化NVIC的VTOR寄存器,来重定位中断向量表 */
	ldr r0, =0xE000ED08
	ldr r0, [r0]
	ldr r0, [r0]
	/* 中断向量表中的第一个字为MSP的初始值 */
	msr msp, r0
	/* 清除CONTROL寄存器,其中第三位FPCA表示FP扩展,将其关闭 */
	mov r0, #0
	msr control, r0
	/* Call SVC to start the first task. */
	/* 将PRIMASK设置为0,表示关闭NMI和Hardfault异常 */
	cpsie i
	/* 将FAULTMASK设置为0,表示关闭NMI异常 */
	cpsie f
	dsb
	isb
	/* 触发SVC异常程序,其中0在异常处理函数中没用到,随便传一个立即数即可 */
	svc 0

SVC异常处理函数如下:

vPortSVCHandler:
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB
	ldr r1, [r3]
	ldr r0, [r1]
	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}
	msr psp, r0
	isb
	mov r0, #0
	msr	basepri, r0
	bx r14

上面程序的意思就是将pxCurrentTCB的第一个参数,即第一个运行任务的堆栈指针pxTopOfStack加载到r0中,然后将任务中堆栈里的r4-r11r14出栈到系统的r4-r11r14寄存器中,然后把出栈后任务的堆栈地址赋值给psp,最后再开中断(前面调用了portDISABLE_INTERRUPTS()),切换到线程模式运行任务。在该异常处理程序退出时,还将由硬件从psppopr0-r3,r12,LR,PCxPSR到系统对应的寄存器中。这样系统就从第一个任务开始运行了。

问:为什么还要将r14(LR)寄存器出栈?或者说为什么要将它保存在栈中?
在创建任务时,每个任务的LRpxPortInitialiseStack函数初始化为:portINITIAL_EXC_RETURN0xFFFFFFFD,它表示退出异常时进入线程模式并使用PSP堆栈,这是通过最后的bx r14来实现的,它的作用是让硬件知道退出异常时要恢复什么状态。

实际上进入异常时硬件也自动保存了LR,但系统中的第一个任务,也就是第一次进入SVC异常时保存的LRvPortStartFirstTask()的下一跳指令return 0的地址,很明显系统不会执行到return 0。进入异常后,LR表示异常发生之前在使用的堆栈,FreeRTOS进入SVC异常时,它的值为0xFFFFFFF9,表示退出时进入线程模式并使用MSP堆栈(没运行操作系统默认使用MSP),当运行操作系统后,系统将使用PSP(FreeRTOS设置LR0xFFFFFFFD,对应SVC异常程序的ldmia r0!, {r4-r11, r14}中出栈给r14)。

一旦开始运行一个任务之后,每次进入异常硬件保存的LR都是0xFFFFFFFD了,因为FreeRTOS的任务都是在使用PSP堆栈,进入异常前的状态都是一样的。在后续任务的上下文切换的PendSV中断中也有压入r14

xPortPendSVHandler
	/* 进入时LR=0xFFFFFFFD,它是被SVC异常最后的bl r14修改的 */
	...
	stmdb r0!, {r4-r11, r14}
	...
	bl vTaskSwitchContext
	...
	ldmia r0!, {r4-r11, r14}
	...

这里将r14压栈再出栈的原因和SVC中的出栈不同,这里是因为后面调用了函数vTaskSwitchContext,会修改LR为其下一条指令的值,所以需要保存r14的值。


最后还有一个问题没有解决:r0-r15xPSR是何时保存到第一个任务的堆栈的呢?或者说每个创建的任务的初始堆栈是怎么设置的呢?不难发现,是在pxPortInitialiseStack中设置的:

xTaskCreate
	prvInitialiseNewTask
		/* 假设没打开StackOverflow检测和MPU */
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

现在来看看pxPortInitialiseStack具体做了什么事:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */

	/* Offset added to account for the way the MCU uses the stack on entry/exit
	of interrupts, and to ensure alignment. */
	pxTopOfStack--;

	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

	/* Save code space by skipping register initialisation. */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */

	/* A save method is being used that requires each task to maintain its
	own exec return value. */
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_EXC_RETURN;

	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

首先来看看任务的堆栈需要将寄存器按什么顺序保存在堆栈中:
在这里插入图片描述

  • 创建任务时,硬件堆栈需要自己初始化

这个函数中就是一个个来初始化这些寄存器的值并写入任务堆栈中,供SVCPendSV进行调度。

  • xPSRportINITIAL_XPSR宏为0x01000000,bit24位为1表示Thumb状态,其它的状态位为0即可
  • PCpxCode就是创建任务时传入的任务函数地址,其中portSTART_ADDRESS_MASK0xFFFFFFFE,根据Cortex-M的规范,PC地址是按字/半字对齐的,所以最低位总是为0。
    • 但使用bxblx跳转时,应该将最低位置1,表示使用Thumb指令
  • LR:硬件的LR设置为prvTaskExitError函数,但任务应该在一个死循环中不该返回,进入这个函数说明程序出错
  • r12~r4:没用到,写为任意值都行,保持默认值即可
  • r0pvParameters即为创建任务时传入的参数,这里可以在任务执行时传给任务
  • r14:前面有提到,设置为portINITIAL_EXC_RETURN(0xFFFFFFFD),表示退出异常时,进入线程模式并使用PSP堆栈

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

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

相关文章

【 java 集合】使用迭代器 Iterator 遍历集合

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

空气源热泵设备远程监控的优点

空气源热泵热水器是一种节能、环保、安全的新能源产品&#xff0c;也是家庭采暖、生活热水及空调制冷等方面的最佳选择。 在使用时&#xff0c;只需一次少量的电能就能实现连续制热和制冷&#xff0c;其制热效率是燃气热水器、电热水瓶和燃油热车等供热设备的数倍&#xff0c;因…

亿联会议系统操作使用全教程

环境&#xff1a; 终端VC800 亿联会议pc.v4.2.11 教程&#xff1a; 一、下载安装亿联会议PC/手机APP 官网下载 1.以下教程为Windows版演示 2.安装亿联会议软件&#xff08;具体操作按软件提示步骤进行&#xff09; 3.注册账号 3.1点击免费注册按钮&#xff0c;自动跳转至…

SCI投稿经验分享:成功发表一篇中科院2区智能物联网类SCI真的不难

我是如何用三个月成功发表一篇TOP 【本期推荐】本期小编分享点不一样的内容。关注小编三个月以上的铁粉可能会留意到七月份本协会新增了一本计算机智能物联网类1区TOP刊&#xff0c;该刊一经推出就受到了广大作者的纷纷投稿。奈何期刊合作期刊编辑严格控制版面&#xff0c;所以…

配电网络扩展规划:考虑使用概率性能源生产和消费概况的决策(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;主要研究方向是电力系统和智能算法、机器学…

Java开发学习(三十一)----Maven属性与版本管理

一、属性 1.1 问题分析 如下图所示 你会发现&#xff0c;如果现在想更新Spring的版本&#xff0c;你会发现依然需要更新多个jar包的版本&#xff0c;这样的话还是有可能出现漏改导致程序出问题&#xff0c;而且改起来也是比较麻烦。 问题清楚后&#xff0c;我们需要解决的话…

SpringBoot 打 jar包和打war 包配置

文章目录1. 前言2. SpringBoot 打 jar 包3. SpringBoot 打 war 包4.小结1. 前言 目前我们熟知的SpringBoot 打包方式&#xff0c;一共分为两种&#xff0c;一种是打jar 包&#xff08;内置tomcat 方式&#xff0c;yml 里的配置参数生效包括端口&#xff0c;和servlet 的contex…

EndNote参考文献导入

简介 本文介绍如何在Word作文过程中借助EndNote管理参考文献&#xff0c;关于如何将参考文献导入到EndNote中这里不做说明&#xff0c;假定需要的文献均已经导入。 具体步骤 为什么需要文献管理工具来生成参考文献呢&#xff1f;最核心的原因就是自动控制的&#xff0c;文献的…

JAVA SCRIPT设计模式--结构型--设计模式之ADAPTER适配器(6)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能&#xff0c;所以不可能像C&#xff0c;JAVA等面向对象语言一样严谨&#xff0c;大部分程序都附上了JAVA SCRIPT代码&#xff0c;代码只是实现了设计模式的主体功能&#xff0c;不代…

代码详细教程+文档+PPT+源码等]SSM框架网上书城全套含微信支付|电商购物计算机专业毕业论文java毕业设计网站

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 计算机毕业设计java毕设SSM框架实现的网上书城-升级版_哔哩哔哩_bilibili项目资料网址: http://itzygogogo.com软件下载地址:http://itzygogogo.co…

Spring的IOC是啥?有什么好处?

单一职责原则 依赖倒转原则 最小知识原则 接口隔离原则 合成/聚合复用原则 里氏代换原则&#xff0c;任何基类可以出现的地方&#xff0c;子类一定可以出现 依赖倒置 假设我们设计一辆汽车&#xff1a;先设计轮子&#xff0c;然后根据轮子大小设计底盘&#xff0c;接着根…

封装基板出厂100欧姆,测试85欧姆?

作者&#xff1a;一博科技高速先生成员 陈亮 封装基板&#xff08;Package Substrate&#xff09;是半导体芯片的载体。为芯片提供连接、保护、支撑、散热、组装等功效&#xff0c;以实现多引脚化&#xff0c;缩小产品体积、改善电性能及散热性、多芯片模块化等。我们生活中看…

构造HTTP请求 以及 关于HTTPS的加密

1.构造HTTP请求 构造HTTP请求有很多种方式.使用html,使用jQuery,使用socket…等等. 这里我们介绍两种. 使用html使用jQuery 1.1.使用html构造HTTP请求 使用html构造HTTP请求,要使用到 form 标签. 这里form标签中的属性有两个. action :填写数据要返送到的地址.(URL)meth…

【云原生 | Kubernetes 实战】08、零故障升级之 Pod 健康探测——启动、存活、就绪探测

目录 一、Pod容器健康探测 1.1 为什么要对容器做探测&#xff1f; 默认的健康检查 探测类型 检查机制 探测结果 Pod 探针相关的属性 两种探针区别 1.2 启动探测 startupprobe exec 模式 tcpsocket 模式 httpget 模式 1.3 存活性探测 livenessProbe 通过 exec …

C++const修饰成员函数

#include "iostream"using namespace std;//常函数class Person { public://this指针的本质 是指针常量 指针的指向是不可以修改的//const Person * const this//在成员函数后面加const&#xff0c;修饰的是this的指向&#xff0c;让指针指向的值也不可以修改void …

纤维二糖-聚乙二醇-羟基Cellobiose-PEG-OH羟基-PEG-纤维二糖

纤维二糖-聚乙二醇-羟基Cellobiose-PEG-OH羟基-PEG-纤维二糖 中文名称&#xff1a;纤维二糖-羟基 英文名称&#xff1a;Cellobiose-OH 别称&#xff1a;羟基修饰纤维二糖&#xff0c;羟基-纤维二糖 羟基&#xff08;oxhydryl&#xff09;是一种常见的极性基团&#xff0c;化…

深入场景痛点,制造业数据应用思考与实践

数字化转型是我国制造业进一步创新式发展的关键&#xff0c;决定了企业在未来生存和发展的态势。 但对于企业而言&#xff0c;如何以低耗能、低成本、高效率的方式加快制造业转型升级的步伐&#xff0c;仍然是众多制造企业需要解决的问题。 深入制造企业数字化转型的场景&…

#Spring-boot高级

一、SpringBoot 整合 Mybatis 1、SpringBoot 整合 Mybatis MyBatis 帮助我们快速集成 SpringBoot 提供的一个组件包&#xff08;mybatis-spring-boot-starter)&#xff0c;使用这个组件可以做到以下几点&#xff1a; 自动检测现有的DataSource将创建并注册SqlSessionFactory…

从工地转行网络安全工程师,工资翻了好几倍,我想和大家聊聊我的经历

成功的从工地转行到办公室办公也有一年了&#xff0c;楼主就来说说&#xff0c;从工地到白领的过渡吧&#xff0c;这其中历经艰辛&#xff0c;最终终达成目标。没错&#xff0c;楼主现在成为了一位网络安全工程师… 先说说为什么转行吧&#xff0c;身边很多做土木工程的都转行…

还在一张张打印CAD图纸吗

用CAD打印少些图纸可以一张张的打印&#xff0c;但是需要打印上百甚至上千张图纸的时候一张张打印就不知道需要用多久的打印时间了&#xff0c;那么在CAD梦想画图软件中有一个批量打印图纸的功能&#xff0c;能节约很大一部分打印时间 操作步骤 1.在网上下载CAD梦想画图 2.用…