ARM异常系(中断)
异常是有CPU处理器来控制的,外设是有外设控制器来控制的,然后外设控制器与CPU相连接
七种异常:
1.复位异常:
触发原因:①关机-》开机会产生异常
②reset按键会产生异常
2.未定义指令异常:
触发原因:指令没有定义的时候触发的异常,包括关键字不对,格式不正确,或者是将代码下载到错误地址上,导致取不出来指令
3.软中断:
swi指令触发,是主动触发,一般系统调用就会触发,用户模式触发软中断是用户模式切换到管理模式,然后可以执行系统的函数的调用,用户模式不可以执行
4.预取址异常:
4G地址空间,程序在0x20008000,现在MMU被激活,MMU缺页了,还要访问0x20008000,此时由于MMU缺页无法映射到0x20008000处,导致没有权限访问到实际存储空间0x20008000,这个时候还想进入到0x20008000,此时取不出来指令,程序就会卡在这,这个时候处理器会报告异常
5.数据异常:
mov r0,r1
ldr r3,[r0] //从r0中读取一个数,读到r3,此时从r0的内存地址上取数据出错,就造成数据异常,与内存操作相关的指令会产生异常
str ldr stm ldm
6.irq中断
硬件触发
7.firq中断
硬件触发
异常处理函数:异常发生的时候就会触发的函数,这个函数是解决这种异常的函数
七种异常有七种异常处理函数,是需要开发者完成的,那么异常触发的时候怎么知道异常处理函数在哪里?
1.异常发生的时候,跳转到固定的位置,这个固定地址处放一条跳转指令,再用这条转到我们的异常处理函数,这个固定的位置就是异常向量表
2.异常处理函数执行前,需要保存现场(工作在异常模式,操作的寄存器都是异常模式下的),用于异常处理函数处理完之后,回复运行正常代码
在异常处理过程中,软件与硬件的责任都有哪些
硬件的责任:
1.把pc寄存器更改了,从正常代码执行的pc值改成异常代码执行的pc值,
2.在pc的值改变之前,将pc的值保存到跳转之前的异常模式下的lr中,pc是取指的地址,根据三级流水线,取指地址是当前执行的地址+8
3.CPSR寄存器修改了
4.在异常前,保存CPSR寄存器中的内容到SPSR(根据异常存到相应的SPSR中,SPSR就是用来保存CPSR的值的)中,保证异常结束后,CPSR值跟异常之前是一致的
异常发生跳转到异常向量表示硬件做的
软件的责任:
1.写异常向量表,需要32个字节,每4字节是一条跳转指令,跳转到异常处理函数
2.明确基地址,不同异常对应的偏移地址
基地址有两个基地址:
基地址默认地址是0,关机到开机时从0地址开始,所以就明白复位异常就是从0地址开始,但是0地址是IROM固化的代码,所以需要MMU
基地址是0xffff0000,需要修改协处理器
偏移地址:
复位:偏移是0
未定义:偏移是4
软中断:偏移是8
预取指异常:偏移是12
数据异常:偏移是16
irq:偏移是20
firq:偏移是24
以上8条指令放到内存0地址,可以先放到代码最前端,通过连接脚本,从0地址开始运行
3.异常处理函数
①压栈,在正常模式下的寄存器与异常模式的有些寄存器是共用的,所以需要先将正常模式下的寄存器压入堆栈保存起来
②压栈之前要保证堆栈存在,所以在启动代码里,先分好栈,初始化堆栈
③根据具体的需求,写异常处理的代码
④弹栈,把之前压入的值弹出来
⑤恢复CPSR
⑥模式切换回原来的模式
⑦保存在lr中的值给回pc寄存器
sub r14,r14,#4
stmfd sp!,{r0~r3,r14}压栈
....
ldmfd sp!,{r0~r3,pc}^弹栈,^更新cpsr
一次中断得到流程
start.s
一般ARM,0地址是可映射的,整个程序在0地址处
.global start
start:
ldr pc,_reset_hdl @使用ldr对pc赋值来实现跳转,相比于b与bl的好处就是不受跳转范围的限制,这是条伪指令
ldr pc,_und_hdl
ldr pc,_swi_hdl
ldr pc,_pabt_hdl
ldr pc,_dataerr_hdl
b .
ldr pc,_irq_hdl
ldr pc,_firq_hdl
_reset_hdl:
.word reset @reset就是一个函数
reset:
bl uart_run
_swi_hdl:
.word swi
swi:
//确定返回到pc还是pc-4还是pc-8
stmfd sp!,{r0~r3,r14}
……
ldmfd sp!,{r0~r3,pc}^
trigger_swi: @trigger_swi是由C语言调用,触发软中断,然后硬件跳转到ldr pc,_swi_hdl,执行swi
swi 0
mov pc,lr
MMU原理&激活MMU
MMU具备通用性
作用:实现物理地址与虚拟地址的映射
物理地址:
CPU的寻址空间0~4G,规定了从哪到哪是nandflash,哪到哪是IROM,哪到哪是DRAM的,就是这4个G是真实存在的,固定不变的
虚拟地址:
UC的地址全部是虚拟地址,驱动也是虚拟地址,链接脚本指定的地址就是虚拟地址
虚拟地址空间是4个G的,但是哪个地址都不映射,或者都映射
虚拟地址中有个0xe2900000,这个地址指向哪里是不确定的,物理地址不知道现在指向哪,只能等待MMU来告诉物理地址,要操作哪一块内存
那么怎么通过MMU让虚拟地址找到对应的物理地址?
MMU会维护一张页表,通过页表去映射,一级查表,操作0xe2900000,MMU通过页表项找到具体与之对应的物理地址,页表项的值改变,下次使用的时候对应的物理地址就会变化
好处:多进程操作同一地址时,MMU就起作用了,多进程操作的同一地址是虚拟地址,然后MMU在一个进程运行这个地址的时候,会找一个单独的实际物理地址去运行,从而保证多进程运行没有问题