····写本文的目的是项目中使用了FreeRTOS,带来了太多的HardFault问题,定位这些问题需要内核基础知识,本文是我看《Cortex-M3 权威指南》侧重于操作系统的一些总结。后面会跟随一些解决HardFault问题的记录。
····ARM指令和Thumb指令不能共存,Thumb-2指令和Thumb指令可以共存。CM3中放弃了ARM指令,使用的是Thumb-2指令集
····凡是打断程序顺序执行的事件,都被称为异常(exception),除了外部中断外,当有指令
执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。程序代码也可以主动请求进入异常状态,用于系统调用。
操作模式:处理者模式和线程模式,handler mode 和 thread mode。
特权分级:特权级和用户级
我的理解:
····普通应用程序代码,如main函数刚进入执行的代码,是处于thread mode下执行的。中断服务函数的代码是处于handler mode模式下执行的。需要注意:准确的来说,handler mode执行的是异常下的代码,中断如定时中断只是异常的一种
····CM3 运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服
务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。在特权级下,程序
可以访问所有范围的存储器(如果有 MPU,还要 在 MPU 规定的禁地之外),并且可以执行所有指令。
两个分类的组合:
····组合切换
16位Thumb指令集可以访问低位寄存器
32位Thumb-2指令集可以访问所有通用寄存器
以下为特殊功能寄存器
只有在特权级下,才允许访问PRIMASK, FAULTMASK 以及 BASEPRI,3 个寄存器
baserpi可以关闭Fault吗?
为什么要使用双堆栈?在OS环境下, OS内核仅在handler模式下运行,为了不破坏OS Kernel 运行环境,故将内核堆栈和线程堆栈分离。
对齐访问字,地址要对齐4字节。
对齐访问半字,地址要对齐2字节。
以下是图例:
CM3中,常规的数据传送指令如 LDR/LDRH/LDRSH 支持非对齐,以下仍然不支持
CM3内部会把非对齐访问转换为对齐访问,这个操作对编程人员是透明的。编程用户不需要考虑,但是这确实影响效率,需要更多的总线周期。
|
异常
编号 | 类型 | 优先级 | 简介 |
---|---|---|---|
0 | NA | NA | 没有异常 |
1 | 复位 | -3(最高) | 复位 |
2 | NMI | -2 | 不可屏蔽中断(来自外部 NMI 输入脚) |
3 | 硬(hard)fault | -1 | 所有被除能的 fault,都将“上访”(escalation)成硬 fault。只要 FAULTMASK 没有置位,硬 fault 服务例程就被强制执行。Fault 被除能的原因包括被禁用,或者被 PRIMASK/BASEPRI 被掩蔽。 若 FAULTMASK 也置位,则硬 fault 也被除能,此时彻底“关中 |
4 | MemManage fault |
可编程 | 存储器管理 fault,MPU 访问违例以及访问非法位置均可引发。 企图在“非执行区”取指也会引发此 fault |
5 | 总线 fault | 可编程 | 从总线系统收到了错误响应,原因可以是预取流产(Abort)或 数据流产,企图访问协处理器也会引发此 fault |
6 | 用法(usage) fault |
可编程 | 由于程序错误导致的异常。通常是使用了一条无效指令,或者是 非法的状态转换,例如尝试切换到 ARM 状态 |
7-10 | 保留 | ||
11 | SVCall | 可编程 | 执行系统服务调用指令(SVC)引发的异常 |
12 | 调试监视器 | 可编程 | 调试监视器(断点,数据观察点,或者是外部调试请求) |
13 | 保留 | ||
14 | PendSV | 可编程 | 为系统设备而设的“可悬挂请求”(pendable request) |
15 | SysTick | 可编程 | 系统滴答定时器 |
Fault 类异常: |
AHB接口正在传输数据的时候吗,如果回复了一个错误信号,则触发Faults
什么情况下会在APB传输数据的时候回复错误信号
MemManage fault,多与MPU有关。如果没有MPU但是在不可执行的存储器区域试图取指,也会触发MemManage fault
最常见原因就是试图切入 ARM 状态,只要在加载 PC 时使用了 LSB 为零的数(也就是偶数),就被视作试图切入 ARM 状态
····先说PendSV,PendSV称为可悬起系统调用。悬起状态是已经设置了中断或者异常标志,但是不可以立即执行的状态。可能的原因是当前系统中有更高优先级的中断或者异常。PendSV会等待所有的中断都执行完成之后,才会执行。为什么我们需要这个特性呢?如果没有PendSV,利用定时中断执行上下文切换就会出现如下问题
在PendSV中执行中断切换,就不会延误中断
····SVC 用于产生系统函数的调用请求。例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就要产生一个SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。
····FreeRTOS只在启动第一个进程的过程中使用SVC,之后进程切换都使用PendSV。原因如下,以下是FreeRTOS论坛里的一个问答
Why SVC is used in FreeRTOS
Posted by niekiran on January 21, 2017
Hello All,
I went through the xTaskStartSechudler() function, which ends up triggering
the SVC instruction. and i came to know that it used in only in this function.
If i am not wrong , is SVC used to launch the first Task ?
So, in my understanding SVC is used only one time, and subsequent task
switching in and out is carried out in the PendSV handler. am i right ?
Why can cant we use pendsv itself instead of SVC ? Thanks
Posted by rtel on January 21, 2017
Yes you are correct, unless you are using the version that supports the
MPU the SVC instruction is only used once.
Old versions don’t use the SVC, and instead use the PendSV, and there
was a really good reason for changing that I can’t recall the exact
details of but it was something to do with having to add extra code into
the PendSV instruction so it knew whether it had to save a context or
not, and also loosing a bit of stack space. If you use SVC to start the
first task then the SVC handler resets the stack to the start of the
stack and just has to restore the context of the first task to run. If
you are using the PendSV handler then you can’t reset the stack (because
you are using it?), and you have to test whether or not a task was
running when the PendSV handler was called (so the context is not saved
if it is the first call because there is no task context to save).
大致意思:启动第一个进程只存在恢复现场的动作,进程切换的时候存在保存现场和回复现场两套动作。老版本FreeRTOS就是全部使用PendSV,但这样的PendSV代码很多,因为需要判断是不是在启动第一个进程进而判断需不需要保存现场。新版本SVC负责启动第一个进程,PendSV负责进程切换。这样的话PendSV代码量少一点,切换进程更快。