异常入口
系统调用是用户态和内核态通信的一种方式,用户程序可以直接调用系统调用的接口陷入内核中执行相关任务,完成后返回用户态继续运行。
应用程序使用系统调用很简单,直接调用C库提供的系统调用接口即可。在C库中,对用户传入的参数进行分析和保存,然后通过syscall指令引发系统调用异常,之后便陷入内核。
内核处理根据系统调用号执行相应的处理函数,并将结果返回到用户态。
图1 系统调用大体流程
当发生异常时,协处理器0的Cause寄存器会记录发生了什么种类的异常。Cause寄存器的每个域如图2所示。其中bit6-2(ExcCode)位中保存了具体发生了什么异常,系统可以根据异常种类决定调用哪一个异常处理例程。
图2 Cause寄存器
所有的异常入口都位于mips内存映射中不需要地址转换的区域——非缓存的kseg1段和缓存的kseg0段。如图3所示,RAM中的异常入口点的起始地址为BASE+0x000,BASE表示EBase寄存器编程的异常基地址。一些特殊的异常的处理例程有单独的地址存放其异常处理例程,如缓存异常和TLB重填等,其他异常处理例程都放在BASE+0x180地址处。
图3 异常处理入口
BASE+0x180共存放了32种异常的入口函数地址,图4中显示了部分异常类型对应的ExcCode值,可以看到其中系统调用对应的ExcCode等于8。当发生系统调用时,内核就可以根据Cause寄存器查看异常类型,然后跳转到BASE+0x180地址处执行,执行的结果就是找到对应的处理函数并跳转到处理函数的地址去执行。
图4 异常类型
这些异常处理函数的注册在trap_init()函数中完成,该函数将上面所说的32个异常的处理函数地址放到一个全局数组exception_handlers中,这个全局变量定义为:
unsigned long exception_handlers[32];
这个全局变量是unsigned long型,每个元素的值就是一种异常向量处理函数的入口地址。
那BASE+0x180地址处的代码如何找到异常对应的处理函数呢。trap_init()函数中将except_vec3_generic拷贝到了BASE+0x180,这是一个函数,其实现如下:
NESTED(except_vec3_generic, 0, sp)
.set push
.set noat
mfc0 k1, CP0_CAUSE #读取协处理器0的cause寄存器保存到k1中。
andi k1, k1, 0x7c #取得k1的2-6位,即excCode
#取得exception_handlers[excCode]的值
PTR_L k0, exception_handlers(k1)
jr k0 #跳转到excCode对应的处理函数去执行
.set pop
END(except_vec3_generic)
由except_vec3_generic的实现可知,它负责读取Cause寄存器并跳转到异常处理函数。
产生异常时,MIPS CPU所要做的主要工作为:
设置EPC,指向异常返回的位置。
置Status寄存器的EXL位,迫使CPU进入内核模式(高特权级)并且禁用中断。
设置Cause寄存器,使得软件可以看到异常的原因。
CPU从异常处理入口点取指执行,即执行异常处理程序。
异常处理的流程主要包括以下步骤:
保护现场,将各个寄存器的值压栈,