论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=93149
第9章 RTX5任务运行在特权级或非特权级模式
本章教程为大家讲解RTX5运行模式的一个重要知识点,特权级模式和非特权级模式,有些资料或者书籍将非特权级模式称为用户模式。
9.1 初学者重要提示
9.2 RTX5任务特权级知识点说明
9.3 RTX5任务特权等级深入认识
9.4 实验例程说明
9.5 总结
9.1 初学者重要提示
RTX5使用非特权级模式,千万不可以开Event Recorder,因为DWT调用需要特权级。
9.2 RTX5任务特权级知识点说明
对于初学者只需记住本小节的知识点即可,如果要深入的了解还是需要花些时间去研究下Cortex-M3/M4/M7权威指南。
对于使用Cortex-M3/M4/M7内核的芯片来说,RTX操作系统可以让任务运行在特权级或者非特权级模式,这两种模式是Cortex-M3/M4/M7内核本身所具有的特性。
在特权级模式下,用户可以访问和配置系统控制寄存器,比如NVIC中断控制器。然而,如果是在非特权级模式下,系统控制寄存器是不允许访问的,一旦访问将导致硬件异常。
- Unprivileged:
非特权级,起到保护用户任务的作用,防止用户可以在任意任务中访问和修改系统寄存器,操作不当会造成系统崩溃。
- Privileged:
特权级,这种模式下用户可以在任意任务中对系统控制寄存器的访问和修改。
有了这点基础的认识之后,还有以下四个疑问有待解决。
9.2.1 非特权级模式下那些寄存器不可访问
对于Cortex-M3/M4/M7内核来说,所有的核心外设寄存器都是只能在特权级下才可以访问,那些又是核心外设寄存器呢,对于STM32来说需要大家看编程手册,如下这些核心外设所有的寄存器都需要在特权级下才可以访问:
核心外设主要是MPU,NVIC,SCB和STK四个单元。一些初学者要问了,你又是如何知道这些内核外设的寄存器要在特权级下才可以访问?答案就在这里,我们可以任意打开一个寄存器:
关于MPU,NVIC,SCB和STK四个单元的其它寄存器是否需要在特权级下才可以访问,大家可以按照我上面说的方法进行查看。
除了核心外设寄存器以外,Cortex-M3/M4/M7内核的特殊功能寄存器也是不能在非特权级下访问的,特殊功能寄存器主要包括以下寄存器:
- 程序状态寄存器组(PSRs或曰xPSR)
- 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及BASEPRI)
- 控制寄存器(CONTROL)
对于参考手册上面所说的SPI,USART,USB等所有外设寄存器都是可以在非特权级下进行访问的。
9.2.2 非特权级模式下核心外设寄存器如何初始化
如果用户将RTX操作系统的任务设置在非特权级模式下运行,那么核心外设寄存器应该放在哪里进行初始化呢,主要有以下两种方法:
- 使用SVC(Supervisor Call)软中断,这个在后面章节会有有详细讲解。
- 在初始化和开启RTX多任务前做核心外设的初始化。
9.2.3 Cortex-M3/M4/M7内核如何切换两种模式
Cortex-M3/M4/M7中的特殊功能寄存器包括:
- 程序状态寄存器组(PSRs或曰xPSR)
- 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及BASEPRI)
- 控制寄存器(CONTROL)
其中控制寄存器CONTROL是用来设置特权级和非特权级切换的,CONTROL寄存器定义如下:
CONTROL[0]
仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个软中断,再由服务例程改写该位。
CONTROL寄存器也是通过MRS和MSR指令来操作的:
MRS R0, CONTROL
MSR CONTROL, R0
9.2.4 RTX5任务特权等级的设置方法
RTX任务特权等级的设置方法比较简单,查看RTX系统的配置向导,如下图9.1所示:
图9.1 RTX配置向导
- Run in privileged mode
此参数就是用来设置特权级和非特权级的,选上单选框表示使能任务工作在特权级模式,取消单选框表示任务工作在非特权级模式。
9.3 RTX5任务特权等级深入认识
本小节的知识点对于初学者来说比较难理解,需要积累了一定的经验后才能更好的理解,不过还是建议读一读。
深入了解Cortex-M3/M4/M7内核的特权等级就不得不说说两种操作模式,Cortex-M3/M4支持两种操作模式,两种操作模式分别是:
- Handler mode,中断模式,简单的说就是指的异常服务程序是处在中断模式。
- Thread mode,线程模式,简单的说就是异常服务程序以外的程序都是处在线程模式。
Cortex-M3/M4内核实现这两种操作的目的就是区分普通应用程序的代码和异常服务程序。下面是两种操作模式和两种特权等级的关系:
- 中断和异常的区别
在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。 除了外部中断外,当有指令执行了“非法操作”, 或者访问被禁的内存区间, 因各种错误产生的 fault, 以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的( 常用于系统调用)。
当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面,handler模式总是特权级的。在系统复位后,处理器进入线程模式+特权级。
在特权级下的代码可以通过置位CONTROL[0]来进入用户级。而不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后,系统将回到产生异常时所处的级别。用户级下的代码不能再试图修改CONTROL[0]来回到特权级。它必须通过一个异常handler,由那个异常handler来修改CONTROL[0],才能在返回到线程模式后拿到特权级。下图是特权级线程模式和用户级线程模式的切换图:
一些简单的应用程序是不需要用户级线程模式的,如下图所示:
把代码按特权级和用户级分开对待,有利于使CM3/CM4的架构更加安全和健壮。例如,当某个用户程序代码出问题时,不会让它成为害群之马,因为用户级的代码是禁止写特殊功能寄存器和NVIC中断寄存器的。另外,如果还配有MPU,保护力度就更大,甚至可以阻止用户代码访问不属于它的内存区域。
为了避免系统堆栈因应用程序的错误使用而毁坏,我们可以给应用程序专门配一个堆栈,不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用PSP,而异常服务例程则使用MSP。这两个堆栈指针的切换是智能全自动的,就在异常服务的始末由硬件处理。
如前所述,特权等级和堆栈指针的选择均由CONTROL负责。当CONTROL[0]=0时,在异常处理的始末,只发生了处理器模式的转换,如下图所示。
但若CONTROL[0]=1(线程模式+用户级),则在中断响应的始末,处理器模式和特权等极都要发生变化,如下图所示。
CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVC)”来触发“SVC异常”,该异常的服务例程可以视具体情况而修改CONTROL[0]。
关于操作模式和特权级就跟大家说这么多,后面讲解RTX任务切换时再详述(此章节在后期RTX5教程升级版本时再配套)。
9.4 实验例程说明
配套例子:
V5-405_RTX5 Task Unprivileged Mode
实验目的:
- 学习RTX的特权级和非特权级设置。
实验内容:
- K1键按下,串口打印。
- K2键按下,强行做NVIC操作,会进入硬件异常。
- 各个任务实现的功能如下:
AppTaskUserIF任务 : 按键消息处理。
AppTaskLED任务 : LED闪烁。
AppTaskMsgPro任务 : 消息处理。
AppTaskStart任务 : 启动任务,也是最高优先级任务,这里实现按键扫描。
osRtxTimerThread任务 : 定时器任务,暂未使用。
串口打印信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
RTX配置:
RTX配置向导详情如下:
System Configuration
- Global Dynamic Memory size
全局动态内存,这里设置为32KB。
- Kernel Tick Frequency
系统时钟节拍,这里设置为1KHz。
Thread Configuration
- Default Thread Stack size
默认的任务栈大小,这里设置为1024字节
RTX5任务调试信息:
程序设计:
任务栈大小分配:
全部独立配置,没有使用RTX5默认配置:
/* ********************************************************************************************************** 变量 ********************************************************************************************************** */ /* 任务的属性设置 */ const osThreadAttr_t ThreadStart_Attr = { /* 未使用 */ // .cb_mem = &worker_thread_tcb_1, // .cb_size = sizeof(worker_thread_tcb_1), // .stack_mem = &worker_thread_stk_1[0], // .stack_size = sizeof(worker_thread_stk_1), // .priority = osPriorityAboveNormal, // .tz_module = 0 .name = "osRtxStartThread", .attr_bits = osThreadDetached, .priority = osPriorityHigh4, .stack_size = 2048, }; const osThreadAttr_t ThreadMsgPro_Attr = { .name = "osRtxMsgProThread", .attr_bits = osThreadDetached, .priority = osPriorityHigh3, .stack_size = 1024, }; const osThreadAttr_t ThreadLED_Attr = { .name = "osRtxLEDThread", .attr_bits = osThreadDetached, .priority = osPriorityHigh2, .stack_size = 512, }; const osThreadAttr_t ThreadUserIF_Attr = { .name = "osRtxThreadUserIF", .attr_bits = osThreadDetached, .priority = osPriorityHigh1, .stack_size = 1024, };
系统栈大小分配:
RTX5初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* HAL库,MPU,Cache,时钟等系统初始化 */ System_Init(); /* 内核开启前关闭HAL的时间基准 */ HAL_SuspendTick(); /* 内核初始化 */ osKernelInitialize(); /* 创建启动任务 */ ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr); /* 开启多任务 */ osKernelStart(); while(1); }
RTX5任务创建:
/* ********************************************************************************************************* * 函 数 名: AppTaskCreate * 功能说明: 创建应用任务 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void AppTaskCreate (void) { ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr); ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr); ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr); }
四个RTX任务的实现:
/* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: 无 * 返 回 值: 无 * 优 先 级: osPriorityHigh1 (数值越小优先级越低,这个跟uCOS相反) ********************************************************************************************************* */ void AppTaskUserIF(void *argument) { uint8_t ucKeyCode; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下 */ case KEY_DOWN_K1: printf("K1键下载\r\n"); break; /* K2键按下 */ case KEY_DOWN_K2: printf("K2键按下,非特权级模式下设置NVIC分组,从而导致系统进入到硬件异常 HardFault_Handler\r\n"); HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); break; /* 其他的键值不处理 */ default: break; } } osDelay(20); } } /* ********************************************************************************************************* * 函 数 名: AppTaskLED * 功能说明: LED闪烁。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: osPriorityHigh2 ********************************************************************************************************* */ void AppTaskLED(void *argument) { const uint16_t usFrequency = 200; /* 延迟周期 */ uint32_t tick; /* 获取当前时间 */ tick = osKernelGetTickCount(); while(1) { bsp_LedToggle(2); /* 相对延迟 */ tick += usFrequency; osDelayUntil(tick); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 消息处理,暂时未用到。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: osPriorityHigh3 ********************************************************************************************************* */ void AppTaskMsgPro(void *argument) { while(1) { osDelay(10); } } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 启动任务,这里用作BSP驱动包处理。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: osPriorityHigh4 ********************************************************************************************************* */ void AppTaskStart(void *argument) { const uint16_t usFrequency = 1; /* 延迟周期 */ uint32_t tick; /* 初始化外设 */ HAL_ResumeTick(); bsp_Init(); /* 创建任务 */ AppTaskCreate(); /* 获取当前时间 */ tick = osKernelGetTickCount(); while(1) { /* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */ bsp_ProPer1ms(); /* 相对延迟 */ tick += usFrequency; osDelayUntil(tick); } }
9.5 总结
对于初学者来说,容易在特权级和非特权级的地方出错。如果以前没有这方面基础的话,理解的时候还稍困难些,不急,慢慢来,完全的理解也需要一个循序渐进的过程。