什么是异常
在mips中,中断、陷阱、系统调用和任何可以中断程序正常执行流的情况都称异常
1. 外部事件 ——中断
2. 内存翻译异常
3. 其他不太常见的内核修改的程序条件
4. 程序或硬件探测到的错误
5. 数据完整性错误
6. 系统调用和陷入
精确异常
在运行流程中没有任何多余效应的异常。即当异常发生时,在受害指令之前的指令被完全执行,而受害指令及后面的指令还没开始执行(注:说受害指令及后面的指令还没做任何事情是不对的,实际上受害指令是处于其指令周期的第三阶段刚完成,即ALU阶段刚完成)。精确异常有有助于保证软件设计上不受硬件实现的影响。
CP0中的EPC寄存器用于指向异常发生时指令跳转前的执行位置,一般是受害指令地址。当异常时,是返回这个地址继续执行。但如果受害指令在分支延迟槽中,则会硬件自动处理使EPC往回指一条指令,即分支指令。在重新执行分支指令时,分支延迟槽中的指令会被再执行一次。
精确异常的实现对流水线的流畅性是有一定的影响的,如果异常太多,系统执行效率就会受到影响。
异常
常规异常一般为软件的异常,而中断一般为硬件异常,中断可以是芯片内部,也可以是芯片外部触发产生。
异常发生时,跳转前最后被执行的指令是其MEM阶段刚好被执行完的那条指令。受害指令是其ALU阶段刚好执行完的那条指令。
Ø 用户特权地址的TLB重填
TLB硬件上只存在存储一定数目的地址转换条目,在运行一个虚拟内存的OS的系统中,如果如果程序得以充分的运行,应用程序就会很容易碰到一个虚拟地址在TLB中没有的情况,一个TLB不匹配的事件就发生了。异常产生后硬件帮助异常处理程序在大约13个时钟周期内完成TLB重填。
Ø 64位地址空间的TLB重填
为了充分利用64位CPU的地址空间,地址转换用了一套略为不同的寄存器布局和不同的TLB重填例程,叫做XTLB重填。
Ø 非缓存的异常入口点
出于对异常处理性能的考虑,访问中断入口地址时都要经过缓存,但是在系统启动期间,上电或重启时缓存未经初始化不能使用。因此,在早期启动期间访问不需要经过缓存,CP0寄存器中有个模式位SR(BEV)将异常处理入口定位于非缓存的、启动安全的kseg1内存区域。
Ø 奇偶/ECC校验错误
Mips32cpu可以检查到数据错误,这是不管SR(BEV)的状态,缓存错误的异常入口都在非缓存区域kseg1内存区域。
Ø 重启
MIPS系统把重启看作一个不可回归的异常来处理。
冷启动:CPU硬件完全被重新配置,软件重新加载;
热启动:软件完全重新初始化;
Ø 中断
异常向量
所有的异常入口点都位于MIPS内存映射中不需要地址转换的区域——非缓存的kseg1段和缓存的kseg0段。当SR(BEV)置位时,非缓存的异常入口时固定的,但是当SR(BEV)清零时,EBase寄存器可以通过编程一起移动所有的异常入口到其他地址。
高优先级异常有:冷启动、热重启、非屏蔽中断,
TLB 重填(32 位模式),
xTLB 重填(64 位模式),cache 错误,其他异常。
高优先级异常入口地址有以下五个:
优先级异常入口
异常类型 |
正常运行(BEV 为 0) |
启动(BEV 为 1) |
冷启动、热重启、非屏蔽中断 |
0x BFC0 0000 |
0x BFC0 0000 |
TLB 重填 |
0x 8000 0000 |
0x BFC0 0200 |
xTLB 重填 |
0x 8000 0080 |
0x BFC0 0280 |
cache 错误 |
0x A000 0100 |
0x BFC0 0300 |
其他 |
0x 8000 0180 |
0x BFC0 0380 |
状态寄存器(SR)
BEV=1 :非缓存异常处理入口固定定位于非缓存的、启动安全的kseg1内存区域
BEV=0 :异常处理入口不固定,通过EBase寄存器可以编程移动,系统正常运行时为0
MIPS 下 TLB、Cache 都要 OS 参与管理,在其启动时 OS 尚未接管系统,这个时候不采用 TLB、Cache 机制是很重要的。
冷启动、 热重启、 非屏蔽中断的入口地址始终位于 0x BFC0 0000
其他异常类入口(一般称为通用异常入口) , 当 CPU 内部异常或者外部中断发生时, CPU 硬件设置 CAUSE 寄存器的 ExcCode( CAUSE6: 2) 位后, 就跳转到该异常入口。 ExcCode 位段用来描述通用异常类型, 共 5 位, 故而可 以描述 2^5 = 32 个异常类型。
1. 高优先级异常入口初始化
通用异常入口初始化, 位于:[ arch/mips/kernel/traps. c]
void __init trap_init( )
cache 错误入口初始化, 位于:[ arch/mips/mm/c-r4k. c]
void __init r4k_cache_init( )
2. 通用异常处理表初始化
CAUSE 寄存器的 ExcCode 索引一张通 用异常处理表 exception_handlers, 它定义于:
arch/mips/kernel/traps. c]
void __init trap_init( void)
异常初始化
1. EPC被置为被中断的PC
2. 如果中断模式是兼容模式的话(普便情况),则vector offset = 0x180.
3. 如果Status[EXL] == 1, 则offset = 0x180,并且EPC不变
4. 如果是TLB refill, 则offset = 0x0000000,如果是其它异常,则是0x180。这里强调一下是,mips R2后中断有几种模式,不同的模式它的入口还不同。
5. 如果Status[BEV] == 1, 则BASE会到0xBFC00200,否则就BASE会是0x80000000,当然在MIPS32 R2后,我们设置了EBASE寄存器,这可以让用户选择中断后跳转的指令,这与R1就不同了,它固定在0x80000000处。为了向后兼容,EBASE默认情况下,它就是0x80000000处。
6. 设置cause[extcode]为异常号,中断为0。
7. Status[EXL] = 0,这个值会产生很大的影响,它会无视Status[IE, KSU]位,而强制处于内核态和关中断状态。这样做的原因是为了我们有足够的时间去保存现场。
8. 最近PC会跑去base+offset的地方。
异常处理
异常产生
EPC指向异常位置
1. 设置EPC指向回归的位置;
2. 设置SR(EXL)强迫CPU进入kernel态,并禁止所有中断响应。
3. 设置Cause寄存器,以使软件可以得到异常的类型信息;还有其它一些寄存器在某些异常时 会被设置;
4. CPU开始从异常入口取指令,然后以后的所有事情都交由软件处理了。
k0和k1寄存器用于保存异常处理函数的地址。
异常处理函数执行完成后,会回到异常分配函数那去,在异常分配函数里,有一个eret指令,用于回归原来被中断的程序继续执行;eret指令会原子性地把中断响应打开(置SR(EXL)),并把状态级由kernel转到user级,并返回原地址继续执行
中断
MIPS CPU有8个独立的中断位(在Cause寄存器中),其中,6个为外部中断,2个为内部中断(可由软件访问)。一般来说,片上的时钟计数/定时器,会连接到一个硬件位上去。
Ø 全局中断使能位SR(IE)必须置1,否则没有中断响应。
Ø 设置SR(EXL)(异常级别)和SR(ERL)(错误级别)位(任何异常之后会立即设置这二者之一)将阻止中断。
Ø 状态寄存器里有8个单独的中断屏蔽位SR(IM),每个对应Cause寄存器的一个中断位。要使能某个中断,其对应的屏蔽位SR(IM)必须置为1。
中断处理程序也是用通用异常入口。
中断的响应:
handle_int ->plat_irq_dispatch->do_IRQ(irq)->generic_handle_irq -> __do_IRQ()
1. 假如Gpio中断,系统产生中断, 通过在 CPU 的中断引脚上,引起异常。
2. CPU 自动设置 CAUSE 的 ExcCode 位为 0, IP5 为x, 并跳转到通用异常入口0x 80000180
3. 位于通用异常入口处的简单异常处理程序, 根据 ExcCode 的值索引异常处理表(exception_handlers) , 获取到 0 号异常的处理程序是 handle_int, 并跳转过去
4. handle_int 根据 CAUSE 之 IP 位的值跳转到相关的中断plat_irq_dispatch;通过简单的计算得到中断号, 进而调 用 do_IRQ 进入相应的中断处理程序。
启动
CPU重启和异常几乎相同,但重启不会返回到被中断的程序。利用一般异常的机制,EPC就指向重启被侦测到时正在执行的那条指令,大多数寄存器的值会被保留。然而,重启打断了正常的操作、正在进行装载的一个寄存器,或者正在进行存储或重填的缓存单元,此时重启也许会被忽略。
利用重启时存储起来的状态可以做一些有用的死机后的调试。
启动顺序:
1. 跳转到主ROM代码。
2. 将状态寄存器设置成为某种已知的有意义的状态,从现在开始,可以对非缓存内存区域进行存取操作。
3. 在初始化并且对RAM空间的完整性进行快速自检前,启动代码只能使用寄存器。
4. 对控制台端口和诊断寄存器配置,以输出初始化过称中的问题
5. 分配堆栈,设置足够多的寄存器,通用标准的C代码
6. 初始化缓存