跳转至

处理器工作模式与特权级别

学习目标

完成本文学习后,你将能够:

  • 理解ARM Cortex-M的两种工作模式:线程模式和处理器模式
  • 掌握特权级别和非特权级别的区别
  • 了解模式切换的触发条件和机制
  • 掌握CONTROL寄存器的配置方法
  • 理解处理器模式在系统安全中的作用
  • 能够编写代码实现模式切换
  • 了解操作系统如何利用特权级别保护系统

前置要求

在开始本文学习之前,你需要:

知识要求: - 了解ARM Cortex-M架构基础 - 熟悉C语言编程 - 了解中断和异常的基本概念 - 掌握寄存器操作基础

技能要求: - 能够阅读和编写简单的C代码 - 了解位操作和寄存器配置 - 熟悉基本的调试方法

推荐但非必需: - 有嵌入式系统开发经验 - 了解操作系统基础概念 - 熟悉汇编语言

概述

ARM Cortex-M处理器设计了两种工作模式和两种特权级别,这种设计为嵌入式系统提供了基本的安全保护机制。通过合理使用这些模式和特权级别,可以隔离关键系统代码和用户应用代码,防止错误的用户代码破坏系统稳定性。

为什么需要处理器模式

  1. 系统保护
  2. 保护关键系统资源不被意外修改
  3. 防止用户代码破坏系统配置
  4. 提高系统稳定性和可靠性

  5. 权限管理

  6. 区分系统代码和应用代码
  7. 限制应用代码的访问权限
  8. 实现基本的安全隔离

  9. 操作系统支持

  10. 为RTOS提供基础支持
  11. 实现任务隔离
  12. 支持系统调用机制

  13. 错误隔离

  14. 限制错误代码的影响范围
  15. 便于故障诊断和恢复
  16. 提高系统鲁棒性

处理器模式概览

ARM Cortex-M处理器有两个维度的模式划分:

┌─────────────────────────────────────────┐
│         处理器工作模式                   │
├──────────────────┬──────────────────────┤
│   线程模式       │    处理器模式         │
│  (Thread Mode)   │   (Handler Mode)     │
│                  │                      │
│  执行应用代码    │   执行异常处理代码    │
│  可以是特权或    │   总是特权级别        │
│  非特权级别      │                      │
└──────────────────┴──────────────────────┘

┌─────────────────────────────────────────┐
│         特权级别                         │
├──────────────────┬──────────────────────┤
│   特权级别       │    非特权级别         │
│  (Privileged)    │   (Unprivileged)     │
│                  │                      │
│  可以访问所有    │   访问受限            │
│  系统资源        │   不能访问某些资源    │
│  可以执行所有    │   不能执行某些指令    │
│  指令            │                      │
└──────────────────┴──────────────────────┘

四种可能的组合

  1. 线程模式 + 特权级别:系统启动后的默认状态
  2. 线程模式 + 非特权级别:运行用户应用代码
  3. 处理器模式 + 特权级别:处理中断和异常(唯一组合)
  4. 处理器模式 + 非特权级别:不存在这种组合

第一部分:线程模式与处理器模式

线程模式(Thread Mode)

线程模式是处理器执行普通应用代码时的工作模式。

线程模式的特点

  1. 执行应用代码
  2. main函数及其调用的函数
  3. 任务代码(在RTOS中)
  4. 普通的应用逻辑

  5. 可配置特权级别

  6. 可以是特权级别
  7. 也可以是非特权级别
  8. 通过CONTROL寄存器配置

  9. 使用主栈或进程栈

  10. 可以使用主栈指针(MSP)
  11. 也可以使用进程栈指针(PSP)
  12. 由CONTROL寄存器控制

  13. 可以被中断打断

  14. 响应中断时切换到处理器模式
  15. 中断返回后继续线程模式

线程模式的典型应用

/**
 * @brief  线程模式示例
 * @note   main函数和普通函数都在线程模式下执行
 */
int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 应用代码(线程模式)
    while(1) {
        // 执行应用逻辑
        Process_Data();

        // 等待事件
        Wait_For_Event();

        // 处理任务
        Handle_Task();
    }
}

void Process_Data(void)
{
    // 这个函数也在线程模式下执行
    uint32_t data = Read_Sensor();
    Filter_Data(data);
    Send_Data(data);
}

处理器模式(Handler Mode)

处理器模式是处理器执行异常处理代码时的工作模式。

处理器模式的特点

  1. 执行异常处理代码
  2. 中断服务函数(ISR)
  3. 异常处理函数
  4. 系统异常处理

  5. 总是特权级别

  6. 不能配置为非特权级别
  7. 始终具有完全访问权限
  8. 可以访问所有系统资源

  9. 总是使用主栈

  10. 固定使用主栈指针(MSP)
  11. 不能使用进程栈指针(PSP)
  12. 确保异常处理的栈空间独立

  13. 异常嵌套

  14. 可以被更高优先级的异常打断
  15. 支持异常嵌套
  16. 自动保存和恢复上下文

处理器模式的典型应用

/**
 * @brief  SysTick中断处理函数
 * @note   在处理器模式下执行
 */
void SysTick_Handler(void)
{
    // 这个函数在处理器模式下执行
    // 总是特权级别
    // 使用主栈(MSP)

    // 更新系统滴答计数
    HAL_IncTick();

    // 可以访问所有系统资源
    SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;  // 触发PendSV
}

/**
 * @brief  外部中断处理函数
 * @note   在处理器模式下执行
 */
void EXTI0_IRQHandler(void)
{
    // 处理器模式,特权级别
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);

        // 处理中断事件
        Handle_Button_Press();
    }
}

模式切换机制

处理器在线程模式和处理器模式之间自动切换:

线程模式
  │ 发生异常/中断
处理器模式
  │ 异常返回
线程模式

切换到处理器模式

  • 当发生异常或中断时
  • 处理器自动切换到处理器模式
  • 自动保存上下文到栈
  • 跳转到异常处理函数

返回线程模式

  • 异常处理函数执行完毕
  • 执行异常返回指令(BX LR,LR包含特殊值)
  • 自动恢复上下文
  • 返回到被打断的代码

模式切换示例

/**
 * @brief  演示模式切换
 */
void Mode_Switch_Demo(void)
{
    // 当前在线程模式
    printf("在线程模式中\n");

    // 触发一个中断
    NVIC_SetPendingIRQ(EXTI0_IRQn);

    // 中断处理完成后,继续在线程模式
    printf("中断处理完成,返回线程模式\n");
}

/**
 * @brief  中断处理函数
 * @note   自动切换到处理器模式
 */
void EXTI0_IRQHandler(void)
{
    // 现在在处理器模式
    printf("在处理器模式中处理中断\n");

    // 清除中断标志
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);

    // 函数返回时自动切换回线程模式
}

检测当前模式

/**
 * @brief  检测当前处理器模式
 * @retval 0: 线程模式, 1: 处理器模式
 */
int Is_Handler_Mode(void)
{
    // 读取IPSR寄存器
    uint32_t ipsr = __get_IPSR();

    // IPSR为0表示线程模式,非0表示处理器模式
    return (ipsr != 0);
}

/**
 * @brief  打印当前模式
 */
void Print_Current_Mode(void)
{
    if (Is_Handler_Mode()) {
        printf("当前在处理器模式\n");
    } else {
        printf("当前在线程模式\n");
    }
}

第二部分:特权级别与非特权级别

特权级别(Privileged)

特权级别具有对系统的完全访问权限。

特权级别的权限

  1. 访问所有内存区域
  2. 可以访问所有SRAM
  3. 可以访问所有Flash
  4. 可以访问所有外设寄存器

  5. 执行所有指令

  6. 可以执行所有ARM指令
  7. 可以修改CONTROL寄存器
  8. 可以配置系统控制寄存器

  9. 访问系统控制空间

  10. 可以访问SCB(系统控制块)
  11. 可以配置NVIC(中断控制器)
  12. 可以配置MPU(内存保护单元)

  13. 切换到非特权级别

  14. 可以主动降低权限
  15. 通过修改CONTROL寄存器实现

特权级别的典型代码

/**
 * @brief  特权级别操作示例
 * @note   只能在特权级别下执行
 */
void Privileged_Operations(void)
{
    // 1. 配置系统控制寄存器
    SCB->VTOR = 0x08000000;  // 设置向量表地址

    // 2. 配置NVIC
    NVIC_SetPriority(SysTick_IRQn, 0);  // 设置中断优先级
    NVIC_EnableIRQ(EXTI0_IRQn);         // 使能中断

    // 3. 配置MPU
    MPU->CTRL = MPU_CTRL_ENABLE_Msk;    // 使能MPU

    // 4. 修改CONTROL寄存器
    __set_CONTROL(__get_CONTROL() | CONTROL_nPRIV_Msk);  // 切换到非特权
    __ISB();  // 指令同步屏障
}

非特权级别(Unprivileged)

非特权级别的访问权限受到限制,用于运行用户应用代码。

非特权级别的限制

  1. 不能访问某些内存区域
  2. 不能访问系统控制空间
  3. 不能访问某些外设(由MPU配置)
  4. 可能不能访问某些SRAM区域

  5. 不能执行某些指令

  6. 不能修改CONTROL寄存器
  7. 不能直接配置系统寄存器
  8. 不能禁用中断(除非使用PRIMASK)

  9. 需要通过系统调用访问系统服务

  10. 使用SVC指令请求系统服务
  11. 由特权级别的代码处理请求
  12. 类似于操作系统的系统调用

  13. 不能切换回特权级别

  14. 只能通过异常返回恢复特权级别
  15. 或通过系统调用请求切换

非特权级别的典型代码

/**
 * @brief  非特权级别操作示例
 * @note   在非特权级别下执行
 */
void Unprivileged_Operations(void)
{
    // 1. 可以执行普通的应用逻辑
    uint32_t data = Read_Sensor();
    Process_Data(data);

    // 2. 可以访问普通的外设
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

    // 3. 不能直接配置系统寄存器
    // SCB->VTOR = 0x08000000;  // ❌ 会触发错误

    // 4. 需要通过系统调用访问系统服务
    SVC_Request_System_Service();
}

/**
 * @brief  系统调用示例
 * @note   使用SVC指令请求系统服务
 */
void SVC_Request_System_Service(void)
{
    // 使用SVC指令触发系统调用
    __asm volatile ("SVC #0");
}

/**
 * @brief  SVC处理函数
 * @note   在特权级别下执行
 */
void SVC_Handler(void)
{
    // 现在在特权级别
    // 可以执行需要特权的操作

    // 处理系统调用请求
    Handle_System_Call();
}

特权级别切换

从特权切换到非特权

/**
 * @brief  切换到非特权级别
 * @note   只能在特权级别下调用
 */
void Switch_To_Unprivileged(void)
{
    // 读取CONTROL寄存器
    uint32_t control = __get_CONTROL();

    // 设置nPRIV位(bit 0)
    control |= CONTROL_nPRIV_Msk;

    // 写回CONTROL寄存器
    __set_CONTROL(control);

    // 指令同步屏障,确保修改生效
    __ISB();

    printf("已切换到非特权级别\n");
}

从非特权切换到特权

非特权级别不能直接切换回特权级别,必须通过以下方式:

  1. 通过异常返回

    /**
     * @brief  通过SVC切换回特权级别
     */
    void Request_Privileged_Mode(void)
    {
        // 触发SVC异常
        __asm volatile ("SVC #1");
    }
    
    /**
     * @brief  SVC处理函数
     */
    void SVC_Handler(void)
    {
        // 在处理器模式下,总是特权级别
        // 可以修改返回时的CONTROL寄存器值
    
        // 获取栈帧
        uint32_t *stack_frame;
        if (__get_CONTROL() & CONTROL_SPSEL_Msk) {
            stack_frame = (uint32_t*)__get_PSP();
        } else {
            stack_frame = (uint32_t*)__get_MSP();
        }
    
        // 修改CONTROL寄存器(在异常返回时生效)
        __set_CONTROL(__get_CONTROL() & ~CONTROL_nPRIV_Msk);
        __ISB();
    }
    

  2. 通过复位

    /**
     * @brief  通过复位恢复特权级别
     */
    void Reset_To_Privileged(void)
    {
        // 系统复位后默认为特权级别
        NVIC_SystemReset();
    }
    

第三部分:CONTROL寄存器

CONTROL寄存器结构

CONTROL寄存器是一个特殊功能寄存器,用于控制处理器的工作模式和特权级别。

CONTROL寄存器位定义

31                                    3    2    1    0
┌─────────────────────────────────────┬────┬────┬────┐
│           保留 (0)                  │FPCA│SPSEL│nPRIV│
└─────────────────────────────────────┴────┴────┴────┘

bit 0 - nPRIV: 特权级别控制
        0 = 特权级别 (Privileged)
        1 = 非特权级别 (Unprivileged)
        注意:只在线程模式下有效

bit 1 - SPSEL: 栈指针选择
        0 = 使用主栈指针 (MSP)
        1 = 使用进程栈指针 (PSP)
        注意:只在线程模式下有效

bit 2 - FPCA: 浮点上下文激活
        0 = 没有浮点指令执行
        1 = 浮点指令已执行
        注意:只在有FPU的处理器上有效

bit 3-31: 保留,必须为0

CONTROL寄存器的特点

  1. 只能在特权级别修改
  2. 非特权级别不能修改CONTROL寄存器
  3. 尝试修改会被忽略(不会产生异常)

  4. 只在线程模式下有效

  5. nPRIV和SPSEL位只在线程模式下有效
  6. 处理器模式下总是特权级别,总是使用MSP

  7. 需要指令同步屏障

  8. 修改后需要执行ISB指令
  9. 确保修改立即生效

CONTROL寄存器操作

读取CONTROL寄存器

/**
 * @brief  读取CONTROL寄存器
 * @retval CONTROL寄存器的值
 */
uint32_t Read_CONTROL(void)
{
    uint32_t control = __get_CONTROL();

    printf("CONTROL寄存器: 0x%08X\n", control);
    printf("  nPRIV (bit 0): %d (%s)\n", 
           (control & CONTROL_nPRIV_Msk) ? 1 : 0,
           (control & CONTROL_nPRIV_Msk) ? "非特权" : "特权");
    printf("  SPSEL (bit 1): %d (%s)\n", 
           (control & CONTROL_SPSEL_Msk) ? 1 : 0,
           (control & CONTROL_SPSEL_Msk) ? "PSP" : "MSP");
    printf("  FPCA  (bit 2): %d\n", 
           (control & CONTROL_FPCA_Msk) ? 1 : 0);

    return control;
}

修改CONTROL寄存器

/**
 * @brief  配置CONTROL寄存器
 * @param  npriv: 特权级别 (0=特权, 1=非特权)
 * @param  spsel: 栈选择 (0=MSP, 1=PSP)
 */
void Configure_CONTROL(uint32_t npriv, uint32_t spsel)
{
    uint32_t control = __get_CONTROL();

    // 清除nPRIV和SPSEL位
    control &= ~(CONTROL_nPRIV_Msk | CONTROL_SPSEL_Msk);

    // 设置新值
    if (npriv) {
        control |= CONTROL_nPRIV_Msk;
    }
    if (spsel) {
        control |= CONTROL_SPSEL_Msk;
    }

    // 写回CONTROL寄存器
    __set_CONTROL(control);

    // 指令同步屏障
    __ISB();

    printf("CONTROL寄存器已配置\n");
}

检查当前特权级别

/**
 * @brief  检查当前是否为特权级别
 * @retval 1: 特权级别, 0: 非特权级别
 */
int Is_Privileged(void)
{
    // 如果在处理器模式,总是特权级别
    if (Is_Handler_Mode()) {
        return 1;
    }

    // 在线程模式,检查CONTROL寄存器
    uint32_t control = __get_CONTROL();
    return !(control & CONTROL_nPRIV_Msk);
}

/**
 * @brief  打印当前特权级别
 */
void Print_Privilege_Level(void)
{
    if (Is_Privileged()) {
        printf("当前为特权级别\n");
    } else {
        printf("当前为非特权级别\n");
    }
}

栈指针选择

CONTROL寄存器的SPSEL位控制线程模式下使用哪个栈指针。

主栈指针(MSP)

  • 系统启动后默认使用
  • 处理器模式总是使用MSP
  • 通常用于系统代码和中断处理

进程栈指针(PSP)

  • 需要通过CONTROL寄存器切换
  • 只在线程模式下使用
  • 通常用于任务代码(RTOS)

切换栈指针示例

/**
 * @brief  切换到进程栈指针
 * @param  psp_value: PSP的初始值
 */
void Switch_To_PSP(uint32_t psp_value)
{
    // 设置PSP的值
    __set_PSP(psp_value);

    // 切换到PSP
    uint32_t control = __get_CONTROL();
    control |= CONTROL_SPSEL_Msk;
    __set_CONTROL(control);
    __ISB();

    printf("已切换到PSP,PSP = 0x%08X\n", psp_value);
}

/**
 * @brief  切换回主栈指针
 */
void Switch_To_MSP(void)
{
    // 切换到MSP
    uint32_t control = __get_CONTROL();
    control &= ~CONTROL_SPSEL_Msk;
    __set_CONTROL(control);
    __ISB();

    printf("已切换到MSP\n");
}

/**
 * @brief  获取当前使用的栈指针
 * @retval 当前栈指针的值
 */
uint32_t Get_Current_SP(void)
{
    uint32_t control = __get_CONTROL();

    if (Is_Handler_Mode()) {
        // 处理器模式总是使用MSP
        return __get_MSP();
    } else {
        // 线程模式,检查SPSEL位
        if (control & CONTROL_SPSEL_Msk) {
            return __get_PSP();  // 使用PSP
        } else {
            return __get_MSP();  // 使用MSP
        }
    }
}

第四部分:模式切换实战

完整的模式切换示例

以下是一个完整的示例,演示如何在不同模式和特权级别之间切换:

#include "stm32f1xx_hal.h"
#include <stdio.h>

// 定义进程栈
#define PSP_STACK_SIZE  1024
uint32_t psp_stack[PSP_STACK_SIZE];

/**
 * @brief  初始化进程栈
 */
void Init_PSP_Stack(void)
{
    // 设置PSP指向栈顶
    uint32_t psp_top = (uint32_t)&psp_stack[PSP_STACK_SIZE];
    __set_PSP(psp_top);

    printf("PSP栈已初始化,栈顶: 0x%08X\n", psp_top);
}

/**
 * @brief  打印当前状态
 */
void Print_Status(void)
{
    printf("\n=== 当前状态 ===\n");

    // 打印模式
    if (Is_Handler_Mode()) {
        printf("模式: 处理器模式\n");
    } else {
        printf("模式: 线程模式\n");
    }

    // 打印特权级别
    if (Is_Privileged()) {
        printf("特权级别: 特权\n");
    } else {
        printf("特权级别: 非特权\n");
    }

    // 打印栈指针
    uint32_t control = __get_CONTROL();
    if (Is_Handler_Mode() || !(control & CONTROL_SPSEL_Msk)) {
        printf("栈指针: MSP = 0x%08X\n", __get_MSP());
    } else {
        printf("栈指针: PSP = 0x%08X\n", __get_PSP());
    }

    // 打印CONTROL寄存器
    printf("CONTROL: 0x%08X\n", control);
    printf("================\n\n");
}

/**
 * @brief  特权级别的函数
 */
void Privileged_Function(void)
{
    printf("执行特权级别函数\n");
    Print_Status();

    // 可以访问系统寄存器
    uint32_t vtor = SCB->VTOR;
    printf("VTOR = 0x%08X\n", vtor);
}

/**
 * @brief  非特权级别的函数
 */
void Unprivileged_Function(void)
{
    printf("执行非特权级别函数\n");
    Print_Status();

    // 尝试访问系统寄存器(会失败)
    // uint32_t vtor = SCB->VTOR;  // ❌ 会触发错误

    // 只能执行普通操作
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}

/**
 * @brief  模式切换演示
 */
void Mode_Switch_Demo(void)
{
    printf("\n========== 模式切换演示 ==========\n\n");

    // 1. 初始状态:线程模式 + 特权级别 + MSP
    printf("1. 初始状态\n");
    Print_Status();
    Privileged_Function();

    // 2. 切换到PSP
    printf("\n2. 切换到PSP\n");
    Init_PSP_Stack();
    uint32_t control = __get_CONTROL();
    control |= CONTROL_SPSEL_Msk;
    __set_CONTROL(control);
    __ISB();
    Print_Status();

    // 3. 切换到非特权级别
    printf("\n3. 切换到非特权级别\n");
    control = __get_CONTROL();
    control |= CONTROL_nPRIV_Msk;
    __set_CONTROL(control);
    __ISB();
    Print_Status();
    Unprivileged_Function();

    // 4. 触发中断(自动切换到处理器模式)
    printf("\n4. 触发中断\n");
    NVIC_SetPendingIRQ(EXTI0_IRQn);

    // 5. 中断返回后,仍然是非特权级别
    printf("\n5. 中断返回后\n");
    Print_Status();

    // 6. 通过SVC切换回特权级别
    printf("\n6. 通过SVC切换回特权级别\n");
    __asm volatile ("SVC #0");
    Print_Status();
    Privileged_Function();

    printf("\n========== 演示结束 ==========\n\n");
}

/**
 * @brief  EXTI0中断处理函数
 */
void EXTI0_IRQHandler(void)
{
    printf("进入中断处理函数\n");
    Print_Status();  // 会显示:处理器模式 + 特权级别 + MSP

    // 清除中断标志
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);

    printf("退出中断处理函数\n");
}

/**
 * @brief  SVC处理函数
 */
void SVC_Handler(void)
{
    printf("进入SVC处理函数\n");
    Print_Status();  // 会显示:处理器模式 + 特权级别 + MSP

    // 切换回特权级别
    uint32_t control = __get_CONTROL();
    control &= ~CONTROL_nPRIV_Msk;
    __set_CONTROL(control);
    __ISB();

    printf("已切换回特权级别\n");
}

RTOS中的应用

在实时操作系统(RTOS)中,处理器模式和特权级别被广泛使用:

/**
 * @brief  RTOS任务切换示例
 * @note   简化的RTOS任务切换机制
 */

// 任务控制块
typedef struct {
    uint32_t *stack_ptr;  // 任务栈指针
    uint32_t priority;    // 任务优先级
    uint32_t state;       // 任务状态
} Task_TCB;

Task_TCB task1_tcb;
Task_TCB task2_tcb;
Task_TCB *current_task;

/**
 * @brief  创建任务
 * @param  tcb: 任务控制块
 * @param  task_func: 任务函数
 * @param  stack: 任务栈
 * @param  stack_size: 栈大小
 */
void Create_Task(Task_TCB *tcb, void (*task_func)(void), 
                 uint32_t *stack, uint32_t stack_size)
{
    // 初始化任务栈
    uint32_t *stack_top = &stack[stack_size - 16];

    // 设置初始栈帧(模拟异常返回)
    stack_top[15] = 0x01000000;  // xPSR (Thumb bit)
    stack_top[14] = (uint32_t)task_func;  // PC
    stack_top[13] = 0xFFFFFFFD;  // LR (返回线程模式,使用PSP)
    // ... 其他寄存器

    // 保存栈指针
    tcb->stack_ptr = stack_top;
}

/**
 * @brief  启动第一个任务
 */
void Start_First_Task(void)
{
    // 切换到PSP
    __set_PSP((uint32_t)current_task->stack_ptr);

    uint32_t control = __get_CONTROL();
    control |= CONTROL_SPSEL_Msk;  // 使用PSP
    control |= CONTROL_nPRIV_Msk;  // 非特权级别
    __set_CONTROL(control);
    __ISB();

    // 触发PendSV进行任务切换
    SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}

/**
 * @brief  PendSV处理函数(任务切换)
 * @note   在处理器模式下执行,特权级别
 */
void PendSV_Handler(void)
{
    // 保存当前任务上下文
    __asm volatile (
        "MRS R0, PSP\n"           // 获取PSP
        "STMDB R0!, {R4-R11}\n"   // 保存R4-R11到栈
        "STR R0, [%0]\n"          // 保存新的栈指针
        :
        : "r" (&current_task->stack_ptr)
        : "r0"
    );

    // 选择下一个任务
    // current_task = next_task;

    // 恢复新任务上下文
    __asm volatile (
        "LDR R0, [%0]\n"          // 加载新任务栈指针
        "LDMIA R0!, {R4-R11}\n"   // 恢复R4-R11
        "MSR PSP, R0\n"           // 设置PSP
        "BX LR\n"                 // 返回
        :
        : "r" (&current_task->stack_ptr)
        : "r0"
    );
}

/**
 * @brief  任务1
 */
void Task1(void)
{
    while(1) {
        printf("Task1 running\n");
        // 任务在非特权级别运行
        // 使用PSP
        HAL_Delay(1000);
    }
}

/**
 * @brief  任务2
 */
void Task2(void)
{
    while(1) {
        printf("Task2 running\n");
        // 任务在非特权级别运行
        // 使用PSP
        HAL_Delay(1000);
    }
}

第五部分:安全性考虑

使用特权级别保护系统

合理使用特权级别可以提高系统的安全性和稳定性:

/**
 * @brief  系统初始化(特权级别)
 */
void System_Init(void)
{
    // 在特权级别下初始化系统
    HAL_Init();
    SystemClock_Config();

    // 配置MPU保护关键区域
    Configure_MPU();

    // 配置关键外设
    Configure_Critical_Peripherals();

    // 初始化用户栈
    Init_User_Stack();

    // 切换到非特权级别运行用户代码
    Switch_To_Unprivileged();

    // 现在用户代码在非特权级别运行
    User_Application();
}

/**
 * @brief  配置MPU保护系统区域
 */
void Configure_MPU(void)
{
    // 禁用MPU
    MPU->CTRL = 0;

    // 配置区域0:保护系统代码(只读)
    MPU->RBAR = 0x08000000 | MPU_REGION_NUMBER0;
    MPU->RASR = MPU_RASR_ENABLE_Msk | 
                MPU_RASR_SIZE_64KB |
                MPU_RASR_AP_RO;  // 只读

    // 配置区域1:保护系统数据(特权读写)
    MPU->RBAR = 0x20000000 | MPU_REGION_NUMBER1;
    MPU->RASR = MPU_RASR_ENABLE_Msk | 
                MPU_RASR_SIZE_4KB |
                MPU_RASR_AP_PRIV_RW;  // 特权读写

    // 配置区域2:用户数据(用户读写)
    MPU->RBAR = 0x20001000 | MPU_REGION_NUMBER2;
    MPU->RASR = MPU_RASR_ENABLE_Msk | 
                MPU_RASR_SIZE_16KB |
                MPU_RASR_AP_FULL;  // 完全访问

    // 使能MPU
    MPU->CTRL = MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk;
}

/**
 * @brief  用户应用程序(非特权级别)
 */
void User_Application(void)
{
    // 用户代码在非特权级别运行
    // 不能访问系统保护的区域

    while(1) {
        // 执行用户任务
        User_Task();

        // 需要系统服务时,通过SVC调用
        if (Need_System_Service()) {
            Request_System_Service();
        }
    }
}

/**
 * @brief  系统服务请求
 */
void Request_System_Service(void)
{
    // 使用SVC指令请求系统服务
    __asm volatile ("SVC #1");
}

/**
 * @brief  SVC处理函数(特权级别)
 */
void SVC_Handler(void)
{
    // 获取SVC号
    uint32_t *stack_frame;
    if (__get_CONTROL() & CONTROL_SPSEL_Msk) {
        stack_frame = (uint32_t*)__get_PSP();
    } else {
        stack_frame = (uint32_t*)__get_MSP();
    }

    uint32_t pc = stack_frame[6];  // 返回地址
    uint8_t svc_number = ((uint8_t*)pc)[-2];  // SVC号

    // 根据SVC号执行相应的系统服务
    switch(svc_number) {
        case 0:
            // 切换回特权级别
            __set_CONTROL(__get_CONTROL() & ~CONTROL_nPRIV_Msk);
            __ISB();
            break;

        case 1:
            // 执行系统服务
            Execute_System_Service();
            break;

        default:
            // 未知的SVC号
            break;
    }
}

错误处理

当非特权代码尝试执行特权操作时,会触发错误:

/**
 * @brief  MemManage错误处理函数
 * @note   当MPU检测到非法访问时触发
 */
void MemManage_Handler(void)
{
    printf("MemManage错误!\n");

    // 检查错误类型
    uint32_t cfsr = SCB->CFSR;

    if (cfsr & SCB_CFSR_IACCVIOL_Msk) {
        printf("指令访问违规\n");
    }
    if (cfsr & SCB_CFSR_DACCVIOL_Msk) {
        printf("数据访问违规\n");

        // 获取违规地址
        uint32_t mmfar = SCB->MMFAR;
        printf("违规地址: 0x%08X\n", mmfar);
    }
    if (cfsr & SCB_CFSR_MSTKERR_Msk) {
        printf("入栈错误\n");
    }
    if (cfsr & SCB_CFSR_MUNSTKERR_Msk) {
        printf("出栈错误\n");
    }

    // 清除错误标志
    SCB->CFSR = cfsr;

    // 错误处理
    Error_Handler();
}

/**
 * @brief  UsageFault错误处理函数
 * @note   当执行非法指令时触发
 */
void UsageFault_Handler(void)
{
    printf("UsageFault错误!\n");

    // 检查错误类型
    uint32_t cfsr = SCB->CFSR;

    if (cfsr & SCB_CFSR_UNDEFINSTR_Msk) {
        printf("未定义指令\n");
    }
    if (cfsr & SCB_CFSR_INVSTATE_Msk) {
        printf("无效状态\n");
    }
    if (cfsr & SCB_CFSR_INVPC_Msk) {
        printf("无效PC\n");
    }
    if (cfsr & SCB_CFSR_NOCP_Msk) {
        printf("无协处理器\n");
    }
    if (cfsr & SCB_CFSR_UNALIGNED_Msk) {
        printf("未对齐访问\n");
    }
    if (cfsr & SCB_CFSR_DIVBYZERO_Msk) {
        printf("除零错误\n");
    }

    // 清除错误标志
    SCB->CFSR = cfsr;

    // 错误处理
    Error_Handler();
}

总结

本文详细介绍了ARM Cortex-M处理器的工作模式和特权级别,主要内容包括:

核心要点

  1. 两种工作模式
  2. 线程模式:执行应用代码
  3. 处理器模式:执行异常处理代码

  4. 两种特权级别

  5. 特权级别:完全访问权限
  6. 非特权级别:受限访问权限

  7. CONTROL寄存器

  8. nPRIV位:控制特权级别
  9. SPSEL位:选择栈指针
  10. 只能在特权级别修改

  11. 模式切换

  12. 异常触发:线程模式 → 处理器模式
  13. 异常返回:处理器模式 → 线程模式
  14. 特权切换:需要通过SVC或异常

  15. 安全应用

  16. 使用MPU保护关键区域
  17. 用户代码运行在非特权级别
  18. 通过SVC提供系统服务

最佳实践

  1. 系统设计
  2. 系统代码运行在特权级别
  3. 用户代码运行在非特权级别
  4. 使用MPU保护关键资源

  5. RTOS应用

  6. 任务使用PSP和非特权级别
  7. 内核使用MSP和特权级别
  8. 通过PendSV进行任务切换

  9. 错误处理

  10. 使能MemManage和UsageFault
  11. 提供详细的错误信息
  12. 实现错误恢复机制

应用场景

  1. 裸机应用
  2. 简单应用可以一直使用特权级别
  3. 复杂应用可以使用非特权级别保护系统

  4. RTOS应用

  5. 必须使用不同的模式和特权级别
  6. 实现任务隔离和保护

  7. 安全应用

  8. 使用MPU和特权级别
  9. 保护关键代码和数据
  10. 防止恶意代码攻击

延伸阅读

推荐进一步学习的内容:

  1. 内存保护
  2. MPU内存保护单元配置实战
  3. 内存访问权限管理
  4. 安全启动技术

  5. RTOS相关

  6. FreeRTOS任务管理
  7. 任务切换机制详解
  8. 系统调用实现

  9. 高级主题

  10. TrustZone安全技术
  11. 安全固件设计
  12. 代码保护技术

参考资料

  1. ARM Cortex-M3权威指南 - Joseph Yiu
  2. ARM Architecture Reference Manual
  3. STM32 Cortex-M3编程手册 - ST Microelectronics
  4. The Definitive Guide to ARM Cortex-M0 and Cortex-M0+ Processors

练习题

  1. 解释线程模式和处理器模式的区别,并说明何时会发生模式切换。

  2. 编写代码实现以下功能:

  3. 在特权级别下初始化系统
  4. 切换到非特权级别运行用户代码
  5. 通过SVC切换回特权级别

  6. 分析以下代码的执行结果:

    void Test_Function(void)
    {
        // 当前在线程模式,特权级别
        printf("1. 当前状态\n");
        Print_Status();
    
        // 切换到非特权级别
        __set_CONTROL(__get_CONTROL() | CONTROL_nPRIV_Msk);
        __ISB();
    
        printf("2. 切换后状态\n");
        Print_Status();
    
        // 尝试修改CONTROL寄存器
        __set_CONTROL(__get_CONTROL() & ~CONTROL_nPRIV_Msk);
        __ISB();
    
        printf("3. 尝试切换后状态\n");
        Print_Status();
    }
    
    说明每一步的状态变化。

  7. 设计一个简单的系统,要求:

  8. 系统代码运行在特权级别
  9. 用户代码运行在非特权级别
  10. 使用MPU保护系统内存
  11. 提供系统调用接口

  12. 解释为什么RTOS需要使用PSP和非特权级别?如果不使用会有什么问题?

下一步:建议学习 寄存器操作与位操作技巧,掌握高效的寄存器操作方法。