Skip to content

代码讲解

lab1中对例外的处理实现(中断异常)

(1) 外设基本初始化设置

Lab1实现了中断初始化和对串口、时钟外设进行中断处理。串口的初始化函数serial_init(位于/kern/driver/console.c)中涉及中断初始化工作的很简单:

......

// 使能串口1接收字符后产生中断

outb(COM1 + COM_IER, COM_IER_RDI);

......

// 通过中断控制器使能串口1中断

pic_enable(IRQ_COM1);

时钟是一种有着特殊作用的外设,其作用并不仅仅是计时。在后续章节中将讲到,正是由于有了规律的时钟中断,才使得无论当前CPU运行在哪里,操作系统都可以在预先确定的时间点上获得CPU控制权。这样当一个应用程序运行了一定时间后,操作系统会通过时钟中断获得CPU控制权,并可把CPU资源让给更需要CPU的其他应用程序。时钟的初始化函数clock_init(位于kern/driver/clock.c中)完成了对时钟控制器的初始化:

    ......

unsigned long timer_config;

unsigned long period = 200000000;

period = period / HZ;

timer_config = period & LISA_CSR_TMCFG_TIMEVAL;

timer_config |= (LISA_CSR_TMCFG_PERIOD | LISA_CSR_TMCFG_EN);

__lcsr_csrwr(timer_config, LISA_CSR_TMCFG);

pic_enable(TIMER0_IRQ);

这里关于CSR中的TCFG寄存器具体含义需要参阅龙芯架构32位精简版参考手册_v1.02.pdf的P64(PDF中的76页)。

(2) 例外初始化设置

对于LoongArch32架构的计算机来说,操作系统初始化中断首先需要完成例外处理程序的基地址设置。这一部分程序写在了kern/init/init.c中的setup_exception_vector函数。

其中,该函数中使用的__exception_vector地址位于文件kern/trap/vectors.S。为了简化,它直接跳转到了kern/trap/exception.S中的ramExcHandle_general处。

(3) 例外的处理过程

trap函数(定义在trap.c中)是对例外进行处理的过程,所有的例外在经过中断入口函数ramExcHandle_general预处理后 (定义在 exception.S中) ,都会跳转到这里。在处理过程中,根据不同的例外类型,进行相应的处理。在相应的处理过程结束以后,trap将会返回,被中断的程序会继续运行。整个中断处理流程大致如下:

1)产生例外后, CPU硬件完成了如下操作:

  • 将CSR.CRMD的PLV、IE分别存到CSR.PRMD的PPLV和IE中,然后将CSR.CRMD的PLV置为0,IE置为0。

  • 将触发例外指令的PC值记录到CSR.ERA中

  • 跳转到例外入口处取值。(如果是TLB相关例外跳转到CSR.RFBASE,否则为CSR.EBASE)

2)经过例外向量的跳转,进入exception.S中的ramExcHandle_general

在这里会完成例外现场的保存操作,切换到内核栈,将当前处理器的所有通用寄存器压入内核栈中,并使用CSR中的KS0和KS1寄存器用来辅助保存数据(否则保存过程中必然导致一些特定寄存器的修改)。

保存的数据按照trapfame结构进行,位于kern/trap/loongarch_trapframe.h

3) 然后跳转进入kern/trap/trap.c中的loongarch_trap函数,开始了C语言程序的内核的处理。

这个函数中,会根据例外的类型完成例外的分类并进行处理,具体见kern/trap/trap.c

4) 当loongarch_trap这一函数处理完毕后(处理过程可能包含对trapframe的修改),会返回到之前的汇编程序(exception.S中),完成寄存器状态的还原,然后使用ertn指令结束例外处理,恢复程序的执行。

至此,对整个lab1中的主要部分的背景知识和实现进行了阐述。请大家能够据此完成实验任务。


Last update: 2024年5月14日