FreeRTOS---基础知识1---寄存器、堆和栈、任务

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-R3R12LR(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寄存器

内存分配的空间如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值