FreeRTOS学习(4)——内存映射
文章目录前言什么是内存映射STM32F103系列中等密度芯片内存映射总地址空间Flash映射区域SRAM映射区域其余外设映射区域STM32的CPU内核寄存器内核寄存器的分类CPU与SRAM的数据交互上下文切换的原理什么是上下文上下文切换要做什么上下文切换的基本流程总结思考前言在之前的文章中我们对FreeRTOS中的任务是这样理解的任务是 FreeRTOS 进行资源分配的基本单元每一个任务都拥有一套相对独立的内存空间任务是 FreeRTOS 进行 CPU 调度的基本单元调度器通过在不同任务之间切换实现 CPU 的合理使用。想必你已经产生了很多疑惑任务所拥有的“独立内存空间”究竟包含哪些内容任务之间切换时上下文又是如何被保存与恢复的要想搞清楚这些问题我们就要从STM32的内存映射机制入手来看一看STM32的内存使用究竟是一个什么情况。什么是内存映射STM32 单片机是一台微型计算机麻雀虽小、五脏俱全CPU、存储器、外设等核心组成部分一应俱全。在整个系统中CPU 处于核心控制地位无论是程序的执行、数据的读写还是对 GPIO、USART、SPI 等外设的操作最终都必须由 CPU 来完成。那么问题就来了CPU 本身不可能直接“认识”外设CPU也不可能知道某一次操作到底是操作SRAM内存还是Flash闪存亦或者是操控某个外设CPU 唯一能够识别和使用的东西只有——地址。为了让 CPU 能够统一、有效地管理整个片上系统。STM32 引入了一个极其重要的核心机制内存映射Memory Mapping。所谓内存映射就是将 片上存储器和外设的寄存器统一映射到同一片连续地址空间中。使 CPU 可以通过访问指定地址的方式来完成对内存或外设的各种操作。从 CPU 的角度来看访问操作RAM 和Flash这种存储器亦或者访问操作外设寄存器都只是访问这段连续映射空间的某个地址罢了。CPU 并不需要区分这些地址背后连接的是哪一类硬件所有硬件层面上的差异都由芯片内部的构造和设计自动来完成解析。这正是 STM32 采用 内存映射机制 的核心意义。实际上不论是 STM32 这类单片机还是传统的通用计算机系统底层都采用了内存映射Memory-Mapped的设计机制。通过这种机制CPU 可以使用统一地址来访问 RAM、Flash 以及各类外设资源。可以说内存映射已经成为现代计算机体系结构中一种必不可少、且被广泛采用的资源管理机制。当然单片机毕竟要简陋简单很多。STM32的内存映射机制是直接暴露给程序员的程序员可以直接看到。而通用计算机这些底层细节被操作系统封装是看不见的。我们所使用的单片机是STM32F103C8T6打开《数据手册》找到内存映射章节。首先映入眼帘的就是下面这张图下面我们来详细解释一下这张图。STM32F103系列中等密度芯片内存映射从上面的内存映射图中可以看到STM32F103 系列中等密度芯片的整体内存映射布局以及各类存储区域和外设在地址空间中的分布情况。当然这张图还展示了STM32F103中等密度芯片所集成的外设。所以如果你想知道STM32F103C8T6具体有哪些片上外设除了查引脚定义表查这一张图也是可以的。总地址空间STM32是32位单片机这不仅意味着寄存器是32位的其地址空间也是32位的。从图中可以看到内存映射的地址范围是0x0000_0000 ~ 0xFFFF_FFFF每一个地址对应1个字节总共4GB的连续地址空间。当然这 4GB 的地址空间只是“逻辑上的映射空间”并不等同于芯片中真实存在的物理存储容量。实际上STM32F103C8T6只有64KB的Flash闪存容量以及20KB的RAM内存容量。也就是说在这整整 4GB 的地址空间中只有极小的一部分被实际的存储器和外设所占用。并且从图中我们可以明显看出这4G的映射地址空间一大部分都是保留空置的。Flash映射区域在内存映射图中可以看到如下地址范围0x0800_0000 ~ 0x0801_FFFF这属于单片机Flash闪存映射的内存区域。可以计算一下这段区域的大小是128KB上面已经讲过了这张图是STM32F103系列中等密度64~128KB芯片的内存映射所以图中展示的是128KB的Flash闪存容量。我们所使用的STM32F103C8T6只有64KB的Flash闪存容量实际映射区域是0x0800_0000 ~ 0x0800_FFFFFlash 闪存主要用于存储以下内容工程编译后生成的程序代码指令程序中使用的只读常量数据特点是掉电不丢失但不能像SRAM那样随意写擦除写入只能将1写成0单片机在运行程序时CPU 正是从 Flash 闪存中顺序读取下一条需要执行的程序指令并加以执行。SRAM映射区域STM32F103 系列芯片其 SRAM 的映射起始地址是0x2000_0000再加上STM32F103C8T6的实际SRAM容量是20KB。所以STM32F103C8T6 的 SRAM 实际有效映射范围是0x2000_0000 ~ 0x2000_4FFFSRAM可以理解成通用计算机的内存存储以下内容全局变量静态变量栈堆特点是断电即失速度快随意读写容量小。SRAM是程序运行时用于保存运行时数据的区域是程序运行的“施工现场”。无论是裸机应用还是基于FreeRTOS运行时产生的数据都需要放入SRAM中。从这个角度出发任务的内存分配必然是在SRAM中进行的。其余外设映射区域从图中以下面地址开头的映射区域0x4000_0000 ~就是各类外设的寄存器映射地址。在STM32阶段我们讲过基于寄存器的开发方式实际上已经使用过外设寄存器地址了。除此之外还有一块小区域也可以留意一下图中靠左上的一块区域起始地址是0xE000_0000 ~标注为Cortex-M3 Internal Peripherals这些地址区域就是内核外设的寄存器映射区域。比如NVIC、SysTick等组件的寄存器映射。STM32的CPU内核寄存器在前面学习 STM32 内存映射时我们已经明确了一点CPU 通过访问映射地址来使用 Flash、SRAM 和 各类外设。但需要注意的是CPU 并不仅仅是一个“访问内存的机器”。CPU 的核心职责是进行运算按照程序指令的顺序完成一系列计算与控制操作。在指令执行的过程中CPU 需要频繁地暂存正在参与运算的数据保存中间计算结果记录当前执行到哪一条指令…如果这些数据都通过访问 SRAM 来完成不仅效率低下也无法满足指令执行过程对速度和时序的要求。因此在 CPU 内部还存在着一组速度最快、距离运算单元最近的存储单元用于辅助指令的执行与运算过程这些存储单元便是 CPU 内核寄存器。简单来说内存Flash / SRAM解决的是“数据和程序放在哪”的问题而 CPU 内核寄存器解决的是“指令如何被高效执行”的问题。在 STM32 所采用的 Cortex-M 内核中这些寄存器由内核直接管理。既不属于 Flash也不属于 SRAM而是 CPU 内部结构中不可或缺的一部分。需要注意的是CPU内核寄存器属于CPU的一部分由CPU直接管理所以就不再需要映射地址空间了。内核寄存器的分类我们可以使用Keil5软件的调试模式随便调试一段普通代码不引入FreeRTOS如下图所示其中R0 ~ R12 这13个寄存器被称之为“通用寄存器”用作保存临时数据保存运算结果存放函数参数和返回值…在 C 语言层面所有的局部变量所有的中间计算结果最终都会被编译器安排到这些寄存器中使用。R13 ~ R15这三个属于特殊寄存器第一个特殊寄存器SP寄存器。SPStack Pointer栈指针SP 用来指向当前正在使用栈的栈顶地址。在不引入RTOS的情况下系统只有一条执行路径SP就始终指向SRAM中栈的栈顶也就是**主栈Main Stack**的栈顶。如果你仔细看图的话会发现下面还有一个MSP这就是MSPMain Stack Pointer主栈栈指针。可以看到图中它的值和SP是一样的因为当前工程只是一个标准库工程只存在主栈SP就是MSP。如果引入RTOS系统中存在多任务每个任务都有自身独立的任务栈。CPU 在运行某个任务时SP 会指向 当前正在执行任务的任务栈栈顶。此时SP将会和图中的“PSPProcess Stack Pointer”保持一致。当前PSP是空的因为系统中不存在任务PSP直译过来是“进程栈指针”但在RTOS场景下应该把它理解成TSPThread/Task Stack Pointer建议直接理解成任务栈指针。关于SP、MSP、PSP三个栈顶指针待到下一章节讲完任务的内存布局后我们再详谈。第二个特殊寄存器LR寄存器。LRLink Register链接寄存器是内核中的一个特殊寄存器。作用是在函数调用过程中保存返回地址。具体来说当发生函数调用时CPU 会将函数调用点之后的下一条指令地址保存到 LR 寄存器中被调用函数执行完毕后通过 LR 中保存的地址返回到调用点继续执行后续指令因此在函数调用的场景下LR 中保存的是一条程序指令的地址。而程序指令都存储在Flash闪存中正如图中所示LR寄存器存储的地址是0x0800_xxxx这正好位于 Flash 闪存的映射地址范围内说明 LR 指向的是一条存放在 Flash 中的程序指令。第三个特殊寄存器PC寄存器。PCProgram Counter程序计数器作用是在程序运行时保存下一条将要执行指令的地址。程序能顺序执行、跳转、循环本质上都是 PC 的变化。PC 决定了 CPU 下一步“干什么”。三个特殊寄存器的下面还有一个xPSR寄存器该寄存器用于保存 CPU 的运行状态信息可以通过该寄存器了解CPU是否正常工作。对于一般应用层程序员而言该寄存器不需要深入了解。CPU与SRAM的数据交互在程序运行过程中CPU 并不会直接在 SRAM 中对数据进行运算。例如intnum10;num;实际执行过程可以简单理解为CPU 先从 SRAM 中把变量 num 的值读入某个通用寄存器如 R0CPU 在通用寄存器中完成 1 运算CPU 再将运算后的结果从通用寄存器写回到 SRAM 中 num 对应的位置也就是说SRAM 负责存数据CPU 负责算数据而内核寄存器充当CPU运算过程中的“草稿纸”用于保存临时数据。上下文切换的原理经过前面的讲解大家已经对STM32 的内存映射机制以及CPU 内核寄存器有了基本认识。在此基础上我们就可以进一步深入来讲解上下文切换Context Switch的工作原理。什么是上下文首先我们要解释一下什么叫上下文Context。所谓 上下文Context也称为 CPU 的执行上下文指的是 某一时刻 CPU 内部整套寄存器所保存的数据状态。说得通俗一些CPU 内核寄存器中那一整套寄存器的取值就完整描述了 CPU 在某一时刻的执行状态。这一套通过寄存器快照所描述的CPU运行状态就是上下文。上下文切换要做什么以FreeRTOS为例在多任务系统中CPU 并不是只为某一个任务服务而是在多个任务之间不断切换执行。当CPU切换到另一个任务时考虑到可能重新回到该任务所以不可能直接不管不顾直接切换CPU。当 CPU 从一个任务切换到另一个任务时必须完成上下文的切换需要先保存旧任务的上下文状态然后再切换CPU并且在任务需要重新上CPU时恢复上下文状态。上下文切换的基本流程我们以两个任务 A 和 B 为例来看上下文切换是如何发生的。当 CPU 正在执行任务 A 时如果任务 A 因故进入阻塞状态CPU就需要切换到另一个可运行就绪态的任务B。此时需要做任务A的上下文保存将当前通用寄存器用到的部分、SP、LR、PC 等关键寄存器的值写入到SRAM中一块该任务的专用区域内。完成任务 A 的上下文保存后任务B上CPUCPU 的寄存器内容被切换为任务 B 对应的寄存器状态也就是将B任务保存的上下文信息恢复到CPU中。CPU 开始执行任务 B 的函数指令。如果任务 B 在执行过程中也进入阻塞状态同样需要将任务 B 当前的寄存器状态保存到 SRAM 中完成一次新的上下文保存。假如任务A此时结束阻塞进入就绪态于是调度器就会让任务A上CPUCPU 会从 SRAM 中将之前保存的任务 A 的寄存器数据读回覆盖当前 CPU 内核寄存器的内容CPU 从任务 A 上一次被切换的位置继续执行。这就是上下文恢复的过程。在系统运行过程中任务 A、任务 B、甚至更多任务会不断经历保存上下文 → 恢复上下文的过程从而实现 多任务“并发”运行的效果。总结上下文切换过程可以概括为三个核心步骤保存当前任务的上下文将当前任务运行时寄存器中的关键内容保存到SRAM中本质上就是把当前任务的运行现场保存到 SRAM 中。切换当前任务对象调度器根据就绪任务情况选择下一个要运行的任务这个过程由FreeRTOS任务调度器基于调度机制完成。恢复新任务的上下文从SRAM中找到新任务的上下文信息恢复到 CPU 寄存器中使 CPU 从该任务上次暂停的位置继续执行总之所谓上下文切换本质上就是总之所谓上下文切换本质上就是 CPU 在不同的寄存器快照之间来回切换而这些寄存器快照都保存在SRAM中。思考在 FreeRTOS 系统中任务往往不止一个。这也意味着系统在运行过程中可能需要在不同任务之间频繁进行上下文切换。而在上下文切换过程中每一个任务都需要将当下 CPU 寄存器中的瞬时状态快照保存到 SRAM 中以便后续能够准确恢复。那么问题就来了多个任务能否把各自的上下文状态 都保存到同一块 SRAM 区域中答案显然是否定的。如果所有任务都把寄存器快照保存在同一个位置那么一旦发生切换新任务的上下文状态就会直接覆盖掉旧任务的上下文信息。这样一来之前任务的执行状态将彻底丢失上下文保存也就失去了意义。因此一个非常明确的结论是每一个任务都必须拥有一块独立的、专属的 SRAM 区域用来保存与该任务相关的运行状态和工作数据。那么问题又来了任务在 SRAM 中的这块专属区域究竟长什么样这块专属区域究竟是如何工作的呢这些问题会在下一文章中讲解