怎么做别人可以上的网站,网页设计实验报告模板,杭州开发网站,企业官网设计目录
一、程序存储分析
1.1 CM3内核寻址空间映射
1.2 程序静态存储和动态执行
二、CM3内核相关知识
2.1 操作模式和特权极别
2.2 环境相关寄存器
2.2.1 通用寄存器组#xff0c;
2.2.2 状态寄存器组
2.2.3 模式切换环境自动保存
2.2.4 函数调用形参位置
2.3 …
目录
一、程序存储分析
1.1 CM3内核寻址空间映射
1.2 程序静态存储和动态执行
二、CM3内核相关知识
2.1 操作模式和特权极别
2.2 环境相关寄存器
2.2.1 通用寄存器组
2.2.2 状态寄存器组
2.2.3 模式切换环境自动保存
2.2.4 函数调用形参位置
2.3 中断
2.3.1 PendSV(可悬起软中断)
2.3.2 SysTick(系统定时器),
三、RT_Thread线程结构
四、RT_Thread启动分析
五、RT_Thread上线文切换
5.1 上下文切换本质
5.2 上下文切换场景
5.3 上下文切换源码实现
5.3.1 启动第一个线程
5.3.2 调度中上下文切换
5.3.3 上下文切换源码分析
5.3.4 上下文切换逻辑图
5.4 上下文切换实例分析 对于实时操作系统来说上下文切换是线程抢占或轮询的根本要想吃透操作系统必需先搞明白上下文切换的过程若要搞明白上线文切换必然少不了与CPU内核打交道本文基于STM32F10X系列单片机( 内核为Cortex-M3核简称CM3)讲解RT_Thread Nano实时操作系统的上下文切换。 本章基于RT_Thread Nano V3.1.5版本分析 本章基于Cortex-M3内核和Keil5编译器分析,详细内核知识参考《CM3权威指南CnR2 宋岩 译》 一、程序存储分析
1.1 CM3内核寻址空间映射 如下图所示 对于STM32F103ZE型号的单片机Code区采用容量为512kB总线接口的NorFlashSRAM区为64kB的SRAM存储器暂不考虑扩展SRAM。
1.2 程序静态存储和动态执行 使用keil5编译STM32程序后程序存储分析如下图所示 各存储区说明 存储区 属性 存储位置 运行位置 存储内容 CODE 只读 NorFlash NorFlash 代码区:所有程序指令。 (起始为MSP初始化值Reset向量) RO-DATA 只读 NorFlash NorFlash 常量区const修饰的常量、字符串。 RW-DATA 读/写 NorFlash SRAM 变量区初始化为非0的全局、静态变量。 ZI-DATA 变量区 读/写 无 SRAM (运行中分配) 变量区未初始化和初始化为0的全局、静态变量。 heap 读/写 无 SRAM (运行中分配) 堆区使用编译器微库用于malloc申请动态内存。 stack 读/写 无 SRAM (运行中分配) 栈区特权级线程(Thread)、中断(异常)模式的局部变量。
1单片机内部FLASH启动模式下ICode总线只能从NorFLASH取指令代码段只能在NorFlash运行。
2总线接口的NorFlash可以用作只读内存RO-DATA(只读数据)可以不搬移至内存。
2heap段使用需要启动keil的微库才能通过malloc等操作进行动态内存申请操作系统可以使用该方式分配总的动态内存区但不要使用该方式分配任务栈任务栈申请使用操作系统自带的方式。
3stack段默认给特权级线程、中断(异常)模式使用由MSP寄存器控制出入栈如果切换到用户模式自动切换为进程栈指针寄存器PSP用户模式需要重新申请栈区PSP指向用户栈区操作系统中的动态内存区可以根据需求设置在RW-DATA段、ZI-DATA堆区、ZI-DATA变量区。
4ZI-DATA区该类变量初始值全为0故不体现在静态存储中在运行态由指令进行内存分配分配代码由Keil编译器自动生成我们只需要在工程配置和启动文件中将存储器参数和堆栈申请空间配置好就行如下所示 Keil编译器存储器设置NorFLASH、SRAM 启动文件(.s)中主程序堆(heap)空间分配: 启动文件(.s)中主程序栈(stack)空间分配: 编译结果: Map文件查看: 二、CM3内核相关知识
2.1 操作模式和特权极别
CM3有2种操作模式 处理者模式(或中断(异常)模式 handler mode )、线程(Thread mode)模式。CM3有2种权利级别 特权级、用户级特权级使用MSP栈指针寄存器用户级使用PSP栈指针寄存器。两种级别的栈相互独立。处理者模式(handler mode)只能运行在特权级别。用户级对系统控制空间SCS的访问将被阻止——该空间包含了配置寄存器组以及调试组件的寄存器组。还禁止使用 MRS/MSR 访问除了 APSR 之外的特殊功能寄存器。如果以身试法则对于访问特殊功能寄存器的访问操作被忽略而对于访问 SCS 空间的将触发fault异常。软件触发中断寄存器可以在用户级下访问以产生软件中断(利用这个特性实现用户模式到特权模式转变)。 各种模式之间的切换 模式切换 触发条件 栈指针 特权级Thread 模式 上电启动 MSP 特权级Thread 模式-特权级handler模式 中断、异常触发 R14(LD)更新为0XFFFFFFF9 MSP 特权级Thread 模式-用户级Thread 模式 操作寄存器CONTROL[0]置1 MSP切换为PSP 特权级handler模式-特权级Thread 模式 调用指令“BX R14” 其中R140XFFFFFFF9 MSP 特权级handler模式-用户级Thread 模式 调用指令“BX R14” 其中R140XFFFFFFFD MSP切换为PSP 用户级Thread 模式-特权级handler模式 中断、异常触发 R14更新为0XFFFFFFFD PSP切换为MSP 用户级Thread 模式-特权级Thread 模式 主动触发异常(SVCall异常) 调用指令“BX R14”返回异常 其中R140XFFFFFFF9 PSP切换为MSP
2.2 环境相关寄存器 CM3用于上下文切换的寄存器主要包括通用寄存器组、状态寄存器组。
2.2.1 通用寄存器组 如下图所示 1通用寄存器 R0-R12 都是 32 位通用寄存器用于数据操作、暂存。绝大多数 16 位 Thumb 指令只能访问 R0-R7而 32 位 Thumb-2 指令可以访问所有寄存器。
2堆栈指针SP R13用于栈指针(SP)指向栈区(stack)通过入栈出栈分配和释放临时变量。比如一个函数中定义了一些临时变量调整SP指针(入栈)分配临时变量的存储空间函数返回时调整SP指针(出栈)释放掉堆栈空间所以临时变量初始值是个不确定的值因为出栈仅是调整SP指针并不对栈空间进行归零等操作且临时变量不宜过多防止栈空间溢出。 CM3内核拥有两个堆栈指针 主堆栈指针MSP特权级别下使用复位后缺省使用的堆栈指针初始值指向内存ZI-DATA栈区(Stack)的栈底(一般是高地址)代码区第一条指令就是MSP的初始化值装置上电会先更新MSP然后在执行reset。 进程堆栈指针PSP用户级别下使用在操作系统中指向任务的栈区任务栈区一般从操作系统动态内存区分配操作系统动态内存区可以根据可以根据需求设置在RW-DATA段、ZI-DATA堆区、ZI-DATA变量区。
3连接寄存器LD R14用作连接寄存器LD连接寄存器当调用一个子程序时,R14存储指令返回地址如果只有 1 级子程序调用的代码无需访问内存堆栈内存从而提高了子程序调用的效率。如果多于 1 级则需要把前一级的 R14 值压到堆栈里。 程序进入handler模式(中断(异常))时R14自动入栈保存保存后R14更新为0xFFFFFFFX通过指令”BX R14”进行异常返回。X的bit0为1表示返回thumb状态bit1和bit2表示返回后sp用msp还是psp及返回到特权级别还是用户级别。合法的返回值如下所示
0xFFFF_FFF1返回handler模式【应用于中断嵌套的场景】0xFFFF_FFF9返回线程模式并使用主堆栈(SPMSP)【返回特权级线程(Thread)模式】0xFFFF_FFFD返回线程模式并使用线程堆栈(SPPSP)【返回用户级线程(Thread)模式】
4 程序计数寄存器(PC) R15用作程序计数寄存器(PC)程序计数寄存器指向NorFLASH代码区。正常运行取指令完成PC自动加1既PC指向下一条指令如果执行跳转指令或者直接修改PC寄存器的值 就能改变程序的执行流。
2.2.2 状态寄存器组 状态字寄存器组包括应用程序 PSRAPSR、 中断号 PSRIPSR、执行 PSREPSR环境保存时是三个寄存器会合并为一个32位xPSR进行保存。 2.2.3 模式切换环境自动保存 与一些高端内核不同CM3由线程模式(Thread)进入handler模式会自动进行部分寄存器的入栈保存,下图参考自《CM3权威指南CnR2 宋岩 译》 2.2.4 函数调用形参位置 ARM系列平台函数形参按从左向右顺序存放在寄存器r0,r1,r2,r3里超过4个参数值传递则放栈里。 比如函数
void test(int iv1,int iv2,int iv3,int iv4,int iv5,int iv6); 形参iv1、iv2、iv3、iv4分别存入寄存器r0、r1、r2、r3进行传递。 形参iv5、iv6则入栈传递。
2.3 中断 RT_Thread操作系统调度器涉及CM3的2个中断PendSV用于上下文切换SysTick提供时间片如下图 2.3.1 PendSV(可悬起软中断) 该中断可以在高优先级中断(异常)中设置为悬起等所有高优先级中断返回后再执行PendSV俗称“缓期执行”所以PendSV的优先级一般会设置为最低。 也可以在用户级或特权级线程模式调用该服务进入handler模式。 在操作系统中用于上下文切换悬起 PendSV 的方法是手工往 NVIC 的 PendSV 悬起寄存器中写 1。
2.3.2 SysTick(系统定时器), 可编程的定时中断(自检)为操作系统提供时间片进行轮询和抢占式调度。设置方法可以参考《CM3权威指南CnR2 宋岩 译》。
三、RT_Thread线程结构 RT_Thread操作系统每个线程有独立的线程控制块(TCB)和线程栈线程栈主要用于分配临时变量在线程切换和初始化时存储线程环境(通用寄存器状态寄存器)寄存器排列顺序固定如下图所示可分为自动出入栈部分和手动出入栈部分自动出入栈部分在CM3内核模式切换时由硬件自动实现见2.2.3章节手动出入栈部分则需要上下文切换代码实现。 四、RT_Thread启动分析 在分析操作系统上下文切换之前,先从宏观上分析操作系统启动和运行的过程。
1程序运行先进入特权级Thread模式系统初始化完成后取最高优先级线程作为第一个线程运行执行启动第一个线程接口触发PendSv中断进入特权级Handler模式寄存器LD强制为0XFFFFFFF9执行上下文切换代码将第一个线程中的环境(通用寄存器组状态寄存器组)出栈并通过执行BX 0XFFFFFFFD,跳转到用户级线程模式执行第一个线程的代码此时使用的堆栈指针为PSP指向第一个线程的线程栈。
2启动第一个线程接口同时会启动时间片中断(SysTick)为操作系统提供心跳和定时器周期轮询线程的功能这样操作系统正常运行。
3由图中可以看出操作系统正常运行后只在用户级Thread模式和特权级Handler模式之间切换如果不出现异常是不会在返回特权级Thread模式的。 五、RT_Thread上线文切换
5.1 上下文切换本质 RT_Thread操作系统进行上下文切换方法是挂起PendSv中断在中断中将线程模式下的寄存器(通用寄存器状态寄存器)入栈到当前线程栈从另一线程(高优先级)的线程栈中出栈环境保存的寄存器数值然后恢复到线程模式本质是保存和切换寄器(通用寄存器状态寄存器)数值。
5.2 上下文切换场景 RT_Thread操作系统支持抢占式调度在线程运行过程中会涉及到上下文切换其场景可分为2类
被动切换当有更高优先级线程就绪时调度器强制将当前线程寄存器状态入栈将高优先级线程寄存器状态出栈实现任务切换此过程一般在定时器线程或SysTick中断中触发。
主动切换当前任务主动进入阻塞(vTaskDelay)、接收消息阻塞、接收信号阻塞、释放新线程等会主动触发上下文切换。
5.3 上下文切换源码实现 上下文切换接口和相关变量如下所示
// 相关函数接口/************【1】启动第一个线程********************************************/
void rt_hw_context_switch_to(rt_ubase_t to);
/************【2】线程中上线文切花*******************************************/
void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to);
/************【3】中断中上下文切换*******************************************/
void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to);// 相关变量
rt_uint32_t rt_interrupt_from_thread; // 指向原线程栈顶指针
rt_uint32_t rt_interrupt_to_thread; // 指向目的线程栈栈顶指针
rt_uint32_t rt_thread_switch_interrupt_flag; // 上线文正在切换中标志
5.3.1 启动第一个线程 操作系统启动时会选择最高优先级的线程作为第一个线程启动调用方式如下形参为线程的栈顶指针地址根据2.2.4章节描述CM3内核会自动使用R0寄存器传递形参(rt_uint32_t)to_thread-sp。
rt_hw_context_switch_to((rt_uint32_t)to_thread-sp); 第一个线程汇编启动接口如下所示设置好目的线程rt_interrupt_to_thread和上下文切换标志rt_thread_switch_interrupt_flag后触发PendSv中断在PendSv中断中执行上下文切换。
;函数声明
rt_hw_context_switch_to PROC
EXPORT rt_hw_context_switch_to;【1】目的线程rt_interrupt_to_thread指向启动线程栈指针,即(rt_uint32_t)to_thread-spLDR r1, rt_interrupt_to_threadSTR r0, [r1];【2】原线程rt_interrupt_from_thread指向空设置为0LDR r1, rt_interrupt_from_thread MOV r0, #0x0STR r0, [r1];【3】上下文切换标志rt_thread_switch_interrupt_flag 设置为1LDR r1, rt_thread_switch_interrupt_flag MOV r0, #1STR r0, [r1];【4】设置中断PendSV和中断SysTick优先级LDR r0, NVIC_SYSPRI2 LDR r1, NVIC_PENDSV_PRILDR.W r2, [r0,#0x00] ; readORR r1,r1,r2 ; modifySTR r1, [r0] ; write-back;【5】挂起PendSv (上下文切换)LDR r0, NVIC_INT_CTRL LDR r1, NVIC_PENDSVSETSTR r1, [r0];【6】恢复 MSPLDR r0, SCB_VTOR LDR r0, [r0]LDR r0, [r0]MSR msp, r0;【6】在处理器级别启用中断中断启用后由于PendSv中断已经挂起,代码会跳转到PendsV中断执行CPSIE F CPSIE I;【7】跳转PendsV中断后系统在用户级Thread模式和特权级handler模式间切换永远运行不到此处!ENDP
5.3.2 调度中上下文切换 操作系统调度正常运行后时间片中断或线程可能会启动上下文切换调用接口函数如下所示根据2.2.4章节描述CM3内核会自动使用R0寄存器传递形参(rt_uint32_t)from_thread-spR1寄存器传递形参(rt_uint32_t)to_thread-sp。 rt_hw_context_switch((rt_ubase_t)from_thread-sp, (rt_ubase_t)to_thread-sp);rt_hw_context_switch_interrupt((rt_ubase_t)from_thread-sp, (rt_ubase_t)to_thread-sp); 2种函数汇编代码虽然相同但是内核模式不同线程中执行接口是在用户级Thread模式而时间片中断执行接口是在特权级Handler模式。 执行接口之前均对高优先级中断进行了屏蔽所以以下汇编执行过程不会中断。 汇编启动接口如下所示 设置好如下参数 rt_uint32_t rt_interrupt_from_thread; // 原线程栈指针 rt_uint32_t rt_interrupt_to_thread; // 目的线程栈指针 rt_uint32_t rt_thread_switch_interrupt_flag; // 上线文正在切换中标志 触发PendSv中断在PendSv中断中执行上下文切换。
rt_hw_context_switch_interruptEXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch PROCEXPORT rt_hw_context_switch;【1】上下文切换标志rt_thread_switch_interrupt_flag判断LDR r2, rt_thread_switch_interrupt_flagLDR r3, [r2];【1.1】上下文切换标志rt_thread_switch_interrupt_flag为1,跳至_reswitch CMP r3, #1BEQ _reswitch ;【1.2】上下文切换标志rt_thread_switch_interrupt_flag不为1,赋值为1MOV r3, #1STR r3, [r2] ;【2】rt_interrupt_from_thread 指向源线程栈指针LDR r2, rt_interrupt_from_threadSTR r0, [r2]
_reswitch;【3】rt_interrupt_to_thread指向目标线程栈指针LDR r2, rt_interrupt_to_thread STR r1, [r2];【4】触发PendSv异常(进行上下文切换)LDR r0, NVIC_INT_CTRL LDR r1, NVIC_PENDSVSETSTR r1, [r0];【5】跳出BX LRENDP
5.3.3 上下文切换源码分析 从5.3.1章节和5.3.2章节可见不管是启动第一个线程还是系统运行中线程切换最终执行的位置是PendSv中断并且在执行中断前已经通过全局变量传输参数。 rt_uint32_t rt_interrupt_from_thread; // 原线程栈指针 rt_uint32_t rt_interrupt_to_thread; // 目的线程栈指针 rt_uint32_t rt_thread_switch_interrupt_flag; // 上线文正在切换中标志 如果是启动第一个线程CM3内核会从特权级Thread模式进入特权级Handler模式R14数值更新为0XFFFFFFF9执行完上下文切换后 R14数值更新为0XFFFFFFFD通过指令BX 0xFFFFFFFD返回到用户级Thread模式。 如果操作系统调度启动后线程中进行上下文切换会先屏蔽中断再挂起PendSv等恢复中断后PendSv才能响应CM3内核会从特权级Thread模式进入特权级Handler模式R14数值更新为0XFFFFFFFD寄存器R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSR 自动通过指针PSP入栈堆栈指针切换为MSP。 如果时间片执行上下文切换等时间片中断完全执行完成后才能响应PendSv中断。 上下文切换汇编源码分析如下
PendSV_Handler PROC
EXPORT PendSV_Handler; 【0.1】如果用户级线程模式进入中断,R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSR; 自动通过指针PSP入栈堆栈指针切换为MSP; 【1】屏蔽除NMI和Fault外的中断MRS r2, PRIMASK ;中断屏蔽寄存器PRIMASK暂存,用于恢复CPSID I ;屏蔽除NMI和Fault中断; 【2】判断上下文切换标志标志rt_thread_switch_interrupt_flagLDR r0, rt_thread_switch_interrupt_flag LDR r1, [r0] ; 【2.1】上下文切换未进行,正常切换,否则执行pendsv_exit,退出切换 CBZ r1, pendsv_exit ; 【3】清除上下文切换标志标志rt_thread_switch_interrupt_flagMOV r1, #0x00 STR r1, [r0] ; 【4】判断原线程rt_interrupt_from_thread,LDR r0, rt_interrupt_from_thread LDR r1, [r0]; 【4.1】原线程rt_interrupt_from_thread无效,跳至switch_to_thread,直接恢复目的线程CBZ r1, switch_to_thread ; 【4.2】原线程rt_interrupt_from_thread有效,对原线程进行环境保存MRS r1, psp ;R1用作原线程栈指针STMFD r1!, {r4 - r11} ;向原线程栈入栈R4--R11同时调整R1LDR r0, [r0] ;取原线程控制块栈指针*sp,即rt_interrupt_from_threadSTR r1, [r0] ;sp更新为最新的栈指针,即完成入栈操作的R1;【5】对目的线程进行环境恢复
switch_to_threadLDR r1, rt_interrupt_to_threadLDR r1, [r1]LDR r1, [r1] ; R1用作目的线程栈指针LDMFD r1!, {r4 - r11} ; 从目的线程栈出栈 R4-R11同时调整R1MSR psp, r1 ; 线程模式堆栈指针psp更换为目的线程栈指针,即完成出栈操作的R1;【6】完成手动部分,恢复屏蔽进行自动出栈
pendsv_exitMSR PRIMASK, r2 ; 恢复屏蔽寄存器ORR lr, lr, #0x04 ; 出栈默认寄存器BX lr ; 执行BX 0xFFFFFFFD返回用户级线程模式根据堆栈指针psp; 自动出栈R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSRENDP5.3.4 上下文切换逻辑图 5.4 上下文切换实例分析 假设线程A由运行态切换到了阻塞态而线程B是当前最高优先级的就绪态任务线程A主动启动PendSv中断上下文切换过程如下所示 由上图可见对比切换前和切换后线程A和线程B的栈状态正好相反线程A进行了环境保存线程B进行了环境恢复。