当前位置: 首页> 科技> 能源 > 查企企官网_建站专家_杭州seo培训_核心关键词和长尾关键词举例

查企企官网_建站专家_杭州seo培训_核心关键词和长尾关键词举例

时间:2025/7/13 1:02:39来源:https://blog.csdn.net/Dr_chaser/article/details/145036524 浏览次数:0次
查企企官网_建站专家_杭州seo培训_核心关键词和长尾关键词举例

文章目录

  • 前言
  • 1. 预备知识
    • 1.1 是谁决定了CPU运行哪段代码?
    • 1.2 函数调用与返回机制
    • 1.3 任务调度和上下文切换
    • 1.4 堆栈指针(SP/MSP/PSP)
  • 2. 代码流程分析
    • 2.1 启动任务调度器
    • 2.2 执行调度
  • 3. 参考链接

前言

我们知道在使用FreeRTOS时,当我们在main函数中调用了vTaskStartScheduler();之后,FreeRTOS的任务调度器就接管了main()了,且不会运行main中vTaskStartScheduler();之后的代码了。之前我一直认为FreeRTOS中有类似于while(1)地方,所以调用了vTaskStartScheduler();之后就不会再往下继续运行了。

然后带着这种想法我去查看了FreeRTOS的代码,可是走进vTaskStartScheduler()的实现,并没有找到有任何wihle(1)的地方。

于是为了找到答案,我重新梳理了下FreeRTOS的任务调度器是如何接管main()函数的,这里向大家分享一下,如果有不对的地方希望大家能够评论说出来一起讨论下。

1. 预备知识

前面一直以为FreeRTOS的任务调度器是通过while(1)的方式接管的main()函数归咎其原因还是自己对代码到底是怎么运行的这块理解的不深,所以这里我们需要先了解一些基本知识,了解了这些之后后面再理解如何被接管的就更加轻松了。

1.1 是谁决定了CPU运行哪段代码?

我们在探讨FreeRTOS的任务调度器是如何接管了main()函数的运行前,需要首先了解决定CPU接下来到底运行哪段程序的因素到底是什么?是函数调用吗?还是将某个函数指针赋值到某个特定的寄存器?还是其他?

程序计数器(PC)决定了CPU的执行流程
程序计数器(PC, Program Counter)是决定 CPU 执行哪段代码的核心机制。每条指令执行时,CPU 会读取 PC 中保存的地址,并根据该地址从内存中取出指令进行执行。执行一条指令后,PC 会根据指令类型的不同进行相应的更新,通常是递增的,指向下一条指令的地址。

  • PC 递增:程序按顺序执行时,PC 会递增到下一条指令的地址。

  • 跳转指令:例如 goto、if、while、for 等控制结构会修改 PC 的值,指向条件判断或跳转目标。

  • 函数调用:在函数调用时,当前指令的地址(即返回地址)会被压入栈中,PC 会跳转到被调用函数的地址。函数执行完毕后,PC 会通过栈恢复到函数调用处的下一条指令。

1.2 函数调用与返回机制

函数调用和返回是程序流控制的基本方式。当一个函数被调用时,CPU 会执行以下操作:

  • 保存当前地址:将当前的 PC 值(即调用指令的地址)压入栈(LR)中,以便函数执行完后能够返回。
  • 修改 PC:将 PC 修改为被调用函数的起始地址,执行该函数。
  • 函数返回:函数执行完成后,PC 会从栈中弹出返回地址,跳转回函数调用处继续执行。

1.3 任务调度和上下文切换

在操作系统中,任务调度器决定了哪个任务运行、停止,这个决策依赖于操作系统的调度策略(优先级、时间片)

当从一个任务切换到另外一个任务时,最关键的是上下文切换,指的是保存当前任务的状态(PC、寄存器、栈指针),然后将控制权转交给另一个任务。这个过程通常由时钟中断触发,操作系统会在每次中断时检查是否需要切换任务。

  • 任务控制块(TCB):每个任务都有一个 TCB,其中包含了该任务的状态信息,包括 PC 和其他寄存器的值。
  • 上下文保存与恢复:当任务调度发生时,当前任务的 PC 和其他寄存器的值会被保存到 TCB 中,并且下一任务的 PC 和寄存器的值会被恢复,从而实现任务的切换。

1.4 堆栈指针(SP/MSP/PSP)

堆栈指针(Stack Pointer,SP)作为处理器中的一个关键寄存器,用于管理堆栈操作。堆栈是一个特殊的内存区域,用于存储函数调用时的局部变量、返回地址、保存的寄存器等信息。STM32 中的堆栈指针有两个主要类型:主堆栈指针(MSP) 和 进程堆栈指针(PSP)。

SP、MSP和PSP的关系
SP、MSP和PSP都是与堆栈指针相关的寄存器,但它们在 STM32(基于 ARM Cortex-M 核心的微控制器)中的作用和使用方式有所不同

  • SP:它实际上是 MSP 或 PSP 的别名,表示当前使用的堆栈指针。
  • MSP(Main Stack Pointer,主堆栈指针):它在异常模式(如中断或者系统调用)下用于堆栈操作,是默认的堆栈指针(main函数使用的就是MSP)。
  • PSP(Process Stack Pointer,进程堆栈指针):它通常用于线程模式下,即应用程序或者RTOS中的用户任务。当应用程序运行时,通常使用 PSP 进行堆栈操作,特别是在多任务操作系统中,每个任务会有自己的 PSP。

执行时的堆栈指针

  • 默认情况下: 如果没有执行任务切换或终端,SP指向MSP,执行的是主堆栈。
  • 中断发生时:处理器会自动使用MSP。
  • 任务切换时:RTOS会切换到PSP,执行对应任务的堆栈

代码示例:切换堆栈指针

// 切换到 PSP 模式
__set_PSP(0x20002000);  // 设置 PSP 指向新的堆栈地址
__set_CONTROL(__get_CONTROL() | 0x02);  // 切换到线程模式(使用 PSP)// 切换回 MSP 模式
__set_CONTROL(__get_CONTROL() & ~0x02);  // 切换到异常模式(使用 MSP)
__set_MSP(0x20001000);  // 设置 MSP 指向主堆栈地址

堆栈指针内容

在这里插入图片描述

在理解堆栈指针时,大家可以这么想,每个函数都有一个属于自己的栈帧,该栈帧就是该函数的堆栈指针,其中包含了函数的返回地址以及R0~R11这些寄存器中要填充的值。

当进行函数调用或者任务切换时,实际上就是将正在使用的函数的栈信息先存下来,然后挑战转新函数或者新任务的地址去执行,同时将新函数或者任务的栈帧取出来填充到实际的R0~R11、PC、LR这些寄存器中。这就是任务的切换与还原。

2. 代码流程分析

2.1 启动任务调度器

vTaskStartScheduler
我们一般是在main()中调用vTaskStartScheduler()启动线程。我们看下该函数,进入该函数后首先它会创建一个idle线程,这个idle线程一般是优先级最低的。idle线程是必须要有的,因为这样当没有任务需要执行时就能够执行idle线程。否则我们想一下如果任何任务都没有那不就没有任务能够接管main()了吗

在这里插入图片描述
xPortStartScheduler
vTaskStartScheduler 又会调用到prvStartFirstTask()。
该函数主要是做一些任务调度器启动前的准备工作,例如将要用到的中断优先级拉低。然后开始启动第一个FreeRTOStask
在这里插入图片描述
prvStartFirstTask
在这里插入图片描述

该函数的作用:

  1. 恢复主堆栈指针 (MSP),确保系统的堆栈指针指向正确的位置。
    当操作系统(如 FreeRTOS)开始调度任务时,通常会将任务切换的堆栈指针切换为 进程堆栈指针 (PSP)。这是因为每个任务需要独立的堆栈空间,以避免不同任务之间的数据冲突或覆盖。但是在启动启动阶段没有任何任务,所以在启动第一个任务之前,必须确保MSP被正确恢复,如果不恢复系统将无法正常执行切换。
  2. 清除控制寄存器中的 FPU 使用位,以便于在启动任务前不占用不必要的空间。
  3. 启用中断,确保系统可以响应中断。
  4. 启动第一个任务,通过一个系统调用(svc 0

另外需要注意msr control, r0
ARM Cortex-M 处理器有一个 CONTROL 寄存器,它控制系统当前使用的是 MSP 还是 PSPCONTROL 寄存器的第 1 位表示当前是否使用 PSP,如果为 1,则使用 PSP,如果为 0,则使用 MSP

执行svc中断(svc 0)vPortSVCHandler
在这里插入图片描述
我们需要先了解下SVC中断,在arm架构中SVC是具备特权模式的中断,一般用来从应用模式切换到特权模式(内核模式)。在特权模式下处理器有更高的权限,可以执行一些用户模式下不能执行的操作。
msr psp, r0 这种只有在特权模式下才能被执行。

该中断的含义就是将第一个任务的的堆栈指针取出来,获取要执行任务的地址以及恢复任务的现场。
通过 msr psp, r0 指令可以恢复堆栈指针为 PSP(任务堆栈)并执行相应的任务。

这之后FreeRTOS的第一个任务就接管了main()函数,当前运行的SP也从MSP转变为了PSP。

2.2 执行调度

这块的内容可以参考我之前的一篇博客《聊一聊 - FreeRTOS的任务调度实现》
调度主要是利用tick中断以及pendSV中断来实现的。
tick中断中执行xPortSysTickHandler,该函数会调用xTaskIncrementTick()增加RTOStick计数,同时检查当前是否有高优先级的任务处于就绪状态了,以及是否有任务的时间片到了可以出发任务切换了。
在这里插入图片描述

xTaskIncrementTick返回为pdTRUE(即需要进行任务切换时),会通过

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;

打开pendSV中断。然后在pendSV中断中执行任务的切换。
在这里插入图片描述

3. 参考链接

FreeRTOS学习笔记:FreeRTOS启动方式及流程
FreeRTOS的启动流程

关键字:查企企官网_建站专家_杭州seo培训_核心关键词和长尾关键词举例

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: