1.CPU内部有多个寄存器,如R0~R15,其中有3个寄存器需要熟知:
1.) R13 (SP - Stack Pointer) - 栈指针
-
作用:指向当前任务的栈顶(用于保存局部变量、函数调用返回地址、寄存器状态等)。
-
FreeRTOS 中的关键用途:
-
每个任务有独立的栈空间,任务切换时,R13 会被更新为新任务的栈指针(保存在任务的 TCB 中)。
-
中断发生时,硬件自动使用当前任务的栈保存上下文(部分架构可能使用独立的中断栈)。
-
注意:PUSH写入内存指令和POP读内存指令,读和写的位置均为SP指向的地址,例如
PUSH{R3,LR}(大括号内寄存器不分先后顺序,也可写成PUSH{LR,R3}),找到SP指向的地址,先将LR的内容写入,然后是SP=SP-4,再放入R3的内容,SP=SP-4(高标号的寄存器LR(R14)保存在高地址,低标号的寄存器(R3)保存在低地址)。
POP{R3,PC},找到SP指向的地址,将当前SP地址的内容读入R3,此时SP=SP+4,再将当前地址的内容(也就是上面存入的LR寄存器的内容)读入PC寄存器中,然后SP=SP+4.
2. )R14 (LR - Link Register) - 链接寄存器
-
作用:
-
存储函数调用的返回地址(当执行
BL或BLX指令时,CPU 自动将返回地址存入 R14)。 -
在异常(如中断)发生时,R14 会保存异常的返回地址(但需注意模式切换时的特殊行为)。
-
-
FreeRTOS 中的关键用途:
-
任务调度时,通过手动修改 R14 实现任务上下文的保存与恢复。
-
中断服务程序(ISR)中需谨慎处理 R14,确保正确返回到被中断的任务。
-
3. )R15 (PC - Program Counter) - 程序计数器
-
作用:指向当前正在执行的指令地址(CPU 根据 PC 取指执行)。
-
FreeRTOS 中的关键用途:
-
任务切换时,通过修改 PC 跳转到新任务的入口点。
-
中断或异常发生时,硬件自动更新 PC 到异常向量表地址。
-
2.参数传递规则(AAPCS)
在 ARM 架构(包括 Cortex-M、Cortex-A 等)中,函数调用时参数的传递遵循 AAPCS(ARM Architecture Procedure Call Standard)标准。
(1) 寄存器传递(快速、高效)
-
前 4 个参数(32位):依次通过 R0、R1、R2、R3 传递。
-
如果参数是 64 位(如
double或int64_t),则占用 两个寄存器(例如 R0+R1 或 R2+R3)。
-
-
返回值:通过 R0(32位)或 R0+R1(64位)返回。
(2) 栈传递(超出寄存器数量时)
-
第 5 个及以后的参数:通过 栈(Stack) 传递,按从右到左的顺序压栈。
-
栈对齐:通常需要 8 字节对齐(Cortex-M 等可能要求 4 字节对齐)。
3.任务是什么?
在 FreeRTOS(实时操作系统)中,任务(Task) 是最基本的执行单元,可以理解为一个独立运行的“线程”或“程序”。每个任务拥有自己的代码、栈空间、优先级和状态,由 FreeRTOS 内核调度器统一管理,实现多任务并发执行(在单核 MCU 上通过分时复用模拟并行)。
4.现场是什么?
在 FreeRTOS 中,现场(Context) 指的是任务或中断发生时,CPU 的 完整执行状态,即所有关键寄存器和程序状态的集合。保存和恢复现场是任务调度、中断处理的核心机制,确保任务切换后能继续正确执行。
5.栈是什么?
栈(Stack)是一种遵循 “后进先出”(LIFO, Last In First Out) 原则的线性数据结构,在计算机科学中广泛应用于 函数调用、局部变量存储、任务切换(RTOS)、中断处理 等场景。在 FreeRTOS 或嵌入式系统中,每个任务的独立栈存放在 系统的 RAM(内存) 中栈的概念尤为重要,因为它直接影响任务的运行和内存管理。
注:任务栈的大小取决于局部变量的大小和调用深度。
(1) 栈的操作
-
压栈(Push):将数据放入栈顶(栈指针减小)。
-
出栈(Pop):从栈顶取出数据(栈指针增大)。
-
栈指针(SP, Stack Pointer):始终指向栈的当前顶部位置。
(2) 栈的增长方向
-
递减栈(Descending Stack)(ARM 默认):栈向 低地址 增长,
PUSH操作使SP减小。 -
递增栈(Ascending Stack):栈向 高地址 增长(较少见)。
6.在 ARM Cortex-M 架构(如 FreeRTOS 常用平台)中,函数调用、硬件中断、任务切换 三种场景下需要保存的寄存器有所不同,主要取决于 调用约定(AAPCS) 和 硬件自动行为。以下是详细分析:
1)函数调用(Function Call)
需要保存的寄存器
根据 AAPCS(ARM Architecture Procedure Call Standard):
• 调用者保存(Caller-saved):
调用函数前,如果当前函数还需要使用这些寄存器,调用者必须保存它们(通常压栈):
◦ R0-R3:参数传递寄存器(被调用函数可自由修改)。
◦ R12(IP):临时寄存器。
◦ LR(R14):返回地址(但嵌套调用时需要手动保存)。
◦ PSR(程序状态寄存器):条件标志位可能被修改。
• 被调用者保存(Callee-saved):
被调用函数若使用这些寄存器,必须恢复它们的原始值(通常在函数开头压栈,结尾弹栈):
◦ R4-R11:通用寄存器。
◦ SP(R13):栈指针(通常由硬件或调用约定隐式管理)。
2)硬件中断(Hardware Interrupt)
需要保存的寄存器
ARM Cortex-M 中断发生时,硬件自动保存部分寄存器到当前栈(MSP 或 PSP),软件需保存其余寄存器:
-
硬件自动保存的寄存器(压入当前任务的栈):
-
R0-R3、R12、LR(R14)、PC(R15)、xPSR。 -
注意:
LR在中断中会被特殊修改为EXC_RETURN(用于中断返回)。
-
-
软件需手动保存的寄存器(若中断服务程序 ISR 中使用):
-
R4-R11:通用寄存器(如果 ISR 中用到)。 -
FPU 寄存器(如S0-S31,若使用浮点运算)。
-
3) 任务切换(Task Switching)
需要保存的寄存器
任务切换本质是 保存当前任务的所有现场(Context),并恢复新任务的现场。
-
必须保存的寄存器(完整任务上下文):
-
R0-R12:通用寄存器。 -
R13 (SP):栈指针(PSP 用于任务栈,MSP 用于内核/中断栈)。 -
R14 (LR):链接寄存器(可能包含特殊值如EXC_RETURN)。 -
R15 (PC):程序计数器。 -
xPSR:程序状态寄存器(条件标志、中断状态等)。 -
FPU 寄存器(若启用 FPU):
S0-S31/D0-D15。
-
综上所述,函数调用时,部分寄存器的内容无需保存如R0~R2;硬件中断时,硬件会自动保存一部分寄存器的内容,如R0~R2,自动压入栈中,另一部分寄存器需要用软件手动操作,将使用到的寄存器的内容进行保存;任务切换时,需要保存所有寄存器的内容。
7.任务的创建
任务是一个 无限循环的 C 函数,通常不返回(除非显式删除任务)。
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称
configSTACK_DEPTH_TYPE usStackDepth, // 栈大小(字)
void *pvParameters, // 任务参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t *pxCreatedTask // 任务句柄(可选)TCB结构体
);
示例:
xTaskCreate(
vTaskExample,
"DemoTask",
256,
NULL,
1,
NULL
);
注意:任务栈的大小取决于局部变量的大小和调用深度。
8.任务的TCB结构体的构建
在 FreeRTOS 中,任务控制块(Task Control Block, TCB) 是一个核心数据结构,用于存储和管理任务的所有运行时信息(如状态、栈指针、优先级等)。
TCB 的核心作用
-
任务身份标识:唯一标识一个任务。
-
资源管理:保存任务的栈指针、优先级、状态等。
-
调度依赖:调度器通过 TCB 决定任务切换。
以下是 FreeRTOS 中 TCB 的关键字段(实际定义可能因版本或配置略有不同):
typedef struct tskTaskControlBlock {
// 栈指针(硬件相关)
volatile StackType_t *pxTopOfStack; // 当前栈顶位置
StackType_t *pxStack; // 栈起始地址
// 任务状态和优先级
UBaseType_t uxPriority; // 当前优先级
UBaseType_t uxBasePriority; // 基础优先级(用于优先级继承)
volatile eTaskState eCurrentState; // 任务状态(就绪/阻塞/挂起等)
// 任务链表指针
ListItem_t xStateListItem; // 状态链表(如就绪列表、阻塞列表)
ListItem_t xEventListItem; // 事件链表(如队列、信号量等待)
// 栈和内存管理
configSTACK_DEPTH_TYPE usStackDepth; // 栈深度(单位:字)
char pcTaskName[ configMAX_TASK_NAME_LEN ]; // 任务名称(调试用)
// 调试和统计信息
UBaseType_t uxCriticalNesting; // 临界区嵌套计数
TickType_t xTaskTag; // 调试标签
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; // 任务唯一ID
#endif
// 其他扩展字段(依赖配置)
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; // MPU(内存保护单元)配置
#endif
} tskTCB;
关键字段详解
(1) 栈管理字段
| 字段 | 说明 |
|---|---|
pxTopOfStack | 当前栈顶指针(硬件上下文保存位置)。 |
pxStack | 栈的起始地址(用于检测栈溢出)。 |
usStackDepth | 栈大小(单位:StackType_t,通常为 4 字节/字)。 |
(2) 任务状态与调度
| 字段 | 说明 |
|---|---|
eCurrentState | 任务状态:eReady(就绪)、eBlocked(阻塞)、eSuspended(挂起)。 |
uxPriority | 当前优先级(数值越高优先级越高)。 |
uxBasePriority | 基础优先级(用于优先级继承,如互斥量)。 |
xStateListItem | 将任务链接到状态列表(如就绪列表、阻塞列表)。 |
xEventListItem | 将任务链接到事件列表(如等待队列或信号量)。 |
在任务创建时传入了任数指针
和任务参数
,但在上面的TCB的构建中并没有这两个内容。这是因为任务函数指针(也就是地址)赋值给PC寄存器,任务参数存放在R0寄存器
内存分配的空间如下:


1784

被折叠的 条评论
为什么被折叠?



