MPU(内存保护单元)的规矩,完全可以用“势力范围”来理解。它管得宽不宽、让不让进,全看地盘归谁管。
1. 被 MPU 势力范围笼罩(Region 覆盖)
只要这块地盘被 MPU 划进了它的势力范围,访问就要看它定的“规矩”(AP 权限):
- 设成只读:你就只能读,敢写就触发 MemManage Fault。
- 设成只写(如果有这种配置):那就只能写,读就报错。
- 设成读写:随便访问,相安无事。
- 设成禁止访问:一碰就报错。
MPU(内存保护单元)的规矩,完全可以用“势力范围”来理解。它管得宽不宽、让不让进,全看地盘归谁管。
只要这块地盘被 MPU 划进了它的势力范围,访问就要看它定的“规矩”(AP 权限):
接下来就是要对mpu的覆盖范围进行设置,并且设置访问的权限
设置的话,只会涉及到两个寄存器
首先是基地址的设置寄存器
然后配置基地址处内存保护单元的大小,
PRIVDEFENA 开关控制:0 就是全封杀,1 就是特权放行,非特权封杀)。CPU有两种运行模式,一个是线程模式,一个是处理模式
对一些程序的权限呢,我们要做一些划分,权限高的就可以用一些比较底层的汇编指令
对这个特殊寄存器control register来进行编程可以控制当前的特权级别和栈
在下面的图片中可以看见,在普通的非特权模式下,要想从非特权模式到特权模式是没有反应的,没有效果的。
所以要在特权模式下触发从非特权模式到特权模式的转换
上面一讲有一张PPT里面就有说中断和异常都是特权模式,所以我们要在这里面来进行由非特权模式向特权模式的转换
这里的话,在执行完从特权级向非特权级的转变之后,我们在调试界面手动触发interrupt1,在中断里面进行特权级的切换
可以看见进入中断前呢,是非特权级,然后我们把这个NVIC控制器的这两个给它勾选上,
上一讲里面呢,我们是用中断来触发一次特权及切换的。但是这个中断呢,试的时候使用调试器手动触发的
中断是程序收到外部的信息,然后才去执行。但是我们这里呢,我们希望程序它能够自己的执行,不需要外界的输入。所以就使用异常。这里使用SVC异常
可以看见,我们先执行一次权限切换,切换到非特权级,然后准备进行触发SVC异常
在非特权级下,函数也可以访问特权级才能访问访问的资源->在异常处理函数里面访问
所以调用这个函数就相当于触发一次异常,在异常中处理访问资源
提供了几个接口,在受限的环境下,可以通过这几个接口来访问内核资源
#include <stdio.h>
#include "ARMCM3.h"
#define SYSCALL_ENABLE_IRQ 0
#define SYSCALL_PRINT_NUM 1
#define SYSCALL_SLEEP 2
// 用户态 API:申请睡眠
int sys_sleep(void) {
return syscall(SYSCALL_SLEEP, 0, 0);
}
// 用户态 API:申请开关中断
int sys_enable_irq(int enable) {
return syscall(SYSCALL_ENABLE_IRQ, enable, 0);
}
// 用户态 API:申请打印
int sys_print_num(char *fmt, int num) {
return syscall(SYSCALL_PRINT_NUM, (int)fmt, num);
}
// 应用入口
int main(void) {
NVIC_EnableIRQ(Interrupt0_IRQn); // 注意:此操作必须在进入用户模式前完成(特权态)
switch_user(); // 切换到非特权模式
int res = sys_sleep();
res = sys_enable_irq(1);
res = sys_print_num("%d\n", 32);
for (;;) {
sys_print_num("Hello, world\n", 0); // 用户态下不能直接 printf
}
return 0;
}
SCB寄存器分配图
BusFault 表示 总线访问发生错误。
常见情况:
UsageFault 主要用于报告 CPU 执行指令时出现的使用错误。
常见原因:
以除 0 为例,需要打开两层开关。
向量表是一个数组
数组里面存的是各个中断函数的地址,也就是函数指针数组
该数组规定了attribute字段为reset,也就是相当于在链接脚本里面,就把它放到地址零处
