龙芯架构 32 位精简版下的中断采用线中断的形式。每个处理器核内部可记录 12 个线中断,分别是:1 个核间中断(IPI),1 个定时器中断(TI),8 个硬中断(HWI0 ~ HWI7),2 个软中断(SWI0~SWI1)。所有的线中断都是电平中断,且都是高电平有效。
核间中断的中断输入来自于核外的中断控制器,其被处理器核采样记录在 CSR.ESTAT.IS[12]位。
定时器中断的中断源来自于核内的恒定频率定时器。当恒定频率定时器倒计时至全 0 值时,该中断被置起。置起后的定时器中断被处理器核采样记录在 CSR.ESTAT.IS[11]位。清除定时器中断需要通过软件向CSR.TICLR 寄存器的 TI 位写 1 来完成。
硬中断的中断源来自于处理器核外部,其直接来源通常是核外的中断控制器。8 个硬中断 HWI[7:0]被处理器核采样记录在 CSR.ESTAT.IS[9:2]位。
软中断的中断源来自于处理器核内部,软件通过 CSR 指令对 CSR.ESTAT.IS[1:0]写 1 则置起软中断,写
0 则清除软中断。
中断在 CSR.ESTAT.IS 域中记录的位置的索引值也被称为中断号(Int Number)。SWI0 的中断号等于 0,
SWI1 的中断号等于 1,……,IPI 的中断号等于 12。
同一时刻多个中断的响应采用固定优先级仲裁基址,中断号越大优先级越高。因此 IPI 的优先级最高,
TI 次之,……,SWI0 的优先级最低。
中断被处理器硬件标记到指令上以后就被当作一种例外进行处理,因此中断入口的计算遵循普通例外入口的计算规则。有关普通例外入口的计算规则请参看 6.2.1 节的介绍。
各中断源发来的中断信号被处理器采样至 CSR.ESTAT.IS 域中,这些信息与软件配置在 CSR.ECFG.LIE 域中的局部中断使能信息按位与,得到一个多位中断向量 int_vec。当 CSR.CRMD.IE=1 且 int_vec 不为全 0时,处理器认为有需要响应的中断,于是从执行的指令流中挑选出一条指令,将其标记上一种特殊的例外——中断例外。
TLB 重填例外的入口来自于 CSR.TLBRENTRY。
除上述例外之外的所有普通例外入口相同,来自于 CSR.EENTRY。此时需要软件通过 CSR.ESTA 中的
Ecode、IS 域的信息来判断具体的例外类型。
例外优先级遵循两个基本原则:其一,中断的优先级高于例外;其二,对于例外,取指阶段检测出的优先级最高,译码阶段检测出的优先级次之,执行阶段检测出的优先级再次之。
对于取指阶段检测出的例外:取指地址错例外优先级最高,取指 TLB 相关例外优先级次之。
译码阶段可检测出的例外彼此互斥,故无需考虑其间的优先级。
执行阶段仅访存指令或同时触发多种例外,其优先级从高到低依次为:要求地址对齐的访存指令因地址不对齐而产生的地址对齐错例外(ALE) > TLB 相关的例外。
不同的例外,处理器硬件在处理时可能存在一些细节上的差异,这里对所有例外共有的通用处理过程进行描述。
当触发例外时,处理器硬件会进行如下操作:
❖将 CSR.CRMD 的 PLV、IE 分别存到 CSR.PRMD 的 PPLV、PIE 中,然后将 CSR.CRMD 的 PLV 置为 0,IE 置为 0;
❖将触发例外指令的 PC 值记录到 CSR.ERA 中;
❖跳转到例外入口处取指。当软件执行 ERTN 指令从例外执行返回时,处理器硬件会完成如下操作:
❖将 CSR.PRMD 中的 PPLV、PIE 值恢复到 CSR.CRMD 的 PLV、IE 中;
❖跳转到 CSR.ERA 所记录的地址处取指。
针对上述硬件实现,软件在例外处理过程的中途如果需要开启中断,需要保存 CSR.PRMD 中的 PPLV、
PIE 等信息,并在例外返回前,将所保存的信息恢复到 CSR.PRMD 中。
复位将重新处理器核中的所有逻辑,将电路置于确定的状态。这里将给出复位后处理器的状态的定义。
复位后第一条指令的 PC 是 0x1C000000。由于复位撤销后 MMU 一定处于直接地址翻译模式,所以复
位后所取的第一条指令的物理地址也是 0x1C000000。
复位撤销后,处于确定状态的寄存器内容有:
❖CSR.CRMD 的 PLV=0,IE=0,DA=1,PG=0,DATF=0,DATM=0;
❖CSR.EUEN 的 FPUen 为 0;
❖CSR.ECFG 中的 LIE 为 0;
❖CSR.ESTAT 中 IS[1:0]均为 0;
❖CSR.TCFG 的 En=0;
❖CSR.LLBCTL 的 KLO=0;
❖所有实现的 CSR.DMW 中的 PLV0、PLV3 均为 0;
除了上述指定的内容外,复位撤销后,处理器中其它软件可见的寄存器的值都是不确定的,软件在使
用前都要将其状态置于确定状态。
TLB 和 Cache 在复位期间是否进行硬件复位由实现决定,若未实现则需要进行软件复位。
在计算机系统层次结构中,应用层3在操作系统层之上,只能看到和使用指令系统的一个子集,即指令系统的用户态部分。每个应用程序都有自己的寄存器、内存空间以及可执行的指令。现代计算机的指令系统在用户态子集之外还定义了操作系统核心专用的特权态部分,我们称之为特权指令系统。
现代计算机的操作系统都实现了保护模式,至少需要用户态和核心态两种运行模式。应用运行在用户态模式下,操作系统运行在核心态模式下。因此,指令系统必须有相应的运行模式以做区分。LoongArch定义了PLV0~PLV3四种模式。
刚开机时,CPU初始化为操作系统核心态对应的运行模式,执行引导程序加载操作系统。操作系统做完一系列初始化后,控制CPU切换到操作系统用户态对应的运行模式去执行应用程序。应用程序执行过程中,如果出现用户态对应的运行模式无法处理的事件,则CPU会通过异常或中断回到核心态对应的运行模式,执行操作系统提供的服务程序。操作系统完成处理后再控制CPU返回用户态对应的运行模式,继续运行原来的应用程序或者调度另一个应用程序。在LoongArch指令系统中,CPU当前所处的运行模式由当前模式信息控制状态寄存器(CSR.CRMD)的PLV域的值确定,其值为0~3分别表示CPU正处于PLV0~PLV3四种运行模式(见图3.1)。
图 3.1: LoongArch当前模式信息控制状态寄存器格式
异常与中断是一种打断正常的软件执行流,切换到专门的处理函数的机制。它在各种运行模式的转换中起到关键的纽带作用。比如用户态代码执行过程中,当出现对特权空间的访问,或者访问了虚实地址映射表未定义的地址,或者需要调用操作系统服务等情况时,CPU通过发出异常来切换到核心态,进入操作系统定义的服务函数。操作系统完成处理后,返回发生异常的代码并同时切换到用户态。
控制状态寄存器位于一个独立的地址空间,是支撑前面3种机制的具体实现,不同的指令系统差别较大。下面以LoongArch指令系统为例,列出其控制状态寄存器的功能。在LoongArch指令系统中,就定义了CSRRD和CSRWR指令来完成控制状态寄存器的读写操作。例如,指令“csrrd t 0 , C S R C R M D 4 ”将控制状态寄存器 C R M D 的值读出,然后写入通用寄存器 t0, CSR_CRMD4”将控制状态寄存器CRMD的值读出,然后写入通用寄存器 t0,CSRCRMD4”将控制状态寄存器CRMD的值读出,然后写入通用寄存器t0中;指令“csrwr t 0 , C S R C R M D ”将通用寄存器 t0, CSR_CRMD”将通用寄存器 t0,CSRCRMD”将通用寄存器t0中的值写入到控制状态寄存器CRMD中,同时将控制状态寄存器CRMD的旧值写入通用寄存器$t0中。
从来源来看,异常可分为以下6种。
1)外部事件:来自CPU核外部5的事件,来自处理器内部其他模块或者处理器外部的真实物理连线也称为中断。中断的存在使得CPU能够异步地处理多个事件。在操作系统中,为了避免轮询等待浪费CPU时间,与IO相关的任务通常都会用中断方式进行处理。中断事件的发生往往是软件不可控制的,因此需要一套健全的软硬件机制来防止中断对正常执行流带来影响。
2)指令执行中的错误:执行中的指令的操作码或操作数不符合要求,例如不存在的指令、除法除以0、地址不对齐、用户态下调用核心态专有指令或非法地址空间访问等。这些错误使得当前指令无法继续执行,应当转到出错处进行处理。
3)数据完整性问题:当使用ECC等硬件校验方式的存储器发生校验错误时,会产生异常。可纠正的错误可用于统计硬件的风险,不可纠正的错误则应视出错位置进行相应处理。
4)地址转换异常:在存储管理单元需要对一个内存页进行地址转换,而硬件转换表中没有有效的转换对应项可用时,会产生地址转换异常。
5)系统调用和陷入:由专有指令产生,其目的是产生操作系统可识别的异常,用于在保护模式下调用核心态的相关操作。
6)需要软件修正的运算:常见的是浮点指令导致的异常,某些操作和操作数的组合硬件由于实现过于复杂而不愿意处理,寻求软件的帮助。
LoongArch指令系统的异常一览表
异常处理的流程包括异常处理准备、确定异常来源、保存执行状态、处理异常、恢复执行状态并返回等。主要内容是确定并处理异常,同时正确维护上下文环境。异常处理是一个软硬件协同的过程,通常CPU硬件需要维护一系列控制状态寄存器(域)以用于软硬件之间的交互。LoongArch指令系统中与异常(含中断)处理相关的控制状态寄存器格式如图3.2所示。
下面对异常处理流程的五个阶段进行介绍。
1)异常处理准备。当异常发生时,CPU在转而执行异常处理前,硬件需要进行一系列准备工作。
首先,需要记录被异常打断的指令的地址(记为EPTR)。这里涉及精确异常的概念,指发生任何异常时,被异常打断的指令之前的所有指令都执行完,而该指令之后的所有指令都像没执行一样。在实现精确异常的处理器中,异常处理程序可忽略因处理器流水线带来的异常发生位置问题。异常处理结束后将返回EPTR所在地址,重新执行被异常打断的指令6,因此需要将EPTR记录下来。EPTR存放的位置因不同指令集而不同,LoongArch存于CSR.ERA
其次,调整CPU的权限等级(通常调整至最高特权等级)并关闭中断响应。在LoongArch指令系统中,当异常发生时,硬件会将CSR.CRMD的PLV域置0以进入最高特权等级,并将CSR.CRMD的IE域置0以屏蔽所有中断输入。
再次,硬件保存异常发生现场的部分信息。在LoongArch指令系统中,异常发生时会将CSR.CRMD中的PLV和IE域的旧值分别记录到CSR.PRMD的PPLV和PIE域中,供后续异常返回时使用。
最后,记录异常的相关信息。异常处理程序将利用这些信息完成或加速异常的处理。最常见的如记录异常编号以用于确定异常来源。在LoongArch指令系统中,这一信息将被记录在CSR.ESTAT的Ecode和EsubCode域,前者存放异常的一级编号,后者存放异常的二级编号。除此以外,有些情况下还会将引发异常的指令的机器码记录在CSR.BADI中,或是将造成异常的访存虚地址记录在CSR.BADV中。
2)确定异常来源。LoongArch将不同的异常进行编号,其异常处理程序入口地址采用“入口页号与页内偏移进行按位逻辑或”的计算方式,入口页号通过CSR.EENTRY配置,每个普通异常处理程序入口页内偏移是其异常编号乘以一个可配置间隔(通过CSR.ECFG的VS域配置)。通过合理配置EENTRY和ECFG控制状态寄存器中相关的域,可以使得不同异常处理程序入口地址不同。当然,也可以通过配置使得所有异常处理程序入口为同一个地址,但是实际使用中通常不这样处理。
3)保存执行状态。在操作系统进行异常处理前,软件要先保存被打断的程序状态,通常至少需要将通用寄存器和程序状态字寄存器的值保存到栈中。
4)处理异常。跳转到对应异常处理程序进行异常处理。
5)恢复执行状态并返回。在异常处理返回前,软件需要先将前面第3个步骤中保存的执行状态从栈中恢复出来,在最后执行异常返回指令。之所以要采用专用的异常返回指令,是因为该指令需要原子地完成恢复权限等级、恢复中断使能状态、跳转至异常返回目标等多个操作。在LoongArch中,异常返回的指令是ERTN,该指令会将CSR.PRMD的PPLV和PIE域分别回填至CSR.CRMD的PLV和IE域,从而使得CPU的权限等级和全局中断响应状态恢复到异常发生时的状态,同时该指令还会将CSR.ERA中的值作为目标地址跳转过去。X86的IRET指令有类似效果。
在LoongArch指令系统中,异常嵌套时被打断的异常处理程序的状态的保存和恢复主要交由软件处理,这就需要保证异常处理程序在完成当前上下文的保存操作之前,不会产生新的异常,或者产生的新异常不会修改当前需要保存的上下文。这两方面要求仅通过异常处理程序开发人员的精心设计是无法完全保证的,因为总有一些异常的产生原因是事先无法预知的,如中断、机器错、TLB重填等。为此需要设计硬件机制以保证这些情况发生时不至于产生嵌套异常,或即使产生嵌套异常也能保证软件可以获得所要保存上下文的正确内容。例如,可以在跳转到异常入口的过程中关闭全局中断使能以禁止中断异常发生,还可以在发生嵌套异常的时候将可能被破坏而软件又来不及保存的上下文信息由硬件暂存到指定的控制状态寄存器或内存区域。
异常处理的流程是通用的,但有两类异常出现的机会确实比其他类型大很多。一类是地址转换异常,当片内从虚地址到物理地址的地址转换表不包含访问地址时,就会产生缺页异常,在3.3节中我们将进行详细介绍。另一类常见的异常就是中断,中断在外部事件想要获得CPU注意时产生。由于外部事件的不可控性,中断处理所用的时间较为关键。在嵌入式系统中,CPU的主要作用之一就是处理外设相关事务,因此中断发生的数量很多且非常重要。本节以LoongArch指令系统为例介绍中断相关的重要内容。
LoongArch指令系统支持中断线的中断传递机制,共定义了13个中断,分别是:1个核间中断(IPI),1个定时器中断(TI),1个性能监测计数溢出中断(PMI),8个外部硬中断(HWI0HWI7),2个软中断(SWI0SWI1)。其中所有中断线上的中断信号都采用电平中断,且都是高电平有效。当有中断发生时,这种高电平有效中断方式输入给处理器的中断线上将维持高电平状态直至中断被处理器响应处理。无论中断源来自处理器核外部还是内部,是硬件还是软件置位,这些中断信号都被不间断地采样并记录到CSR.ESTAT中IS域的对应比特位上。这些中断均为可屏蔽中断,除了CSR.CRMD中的全局中断使能位IE外,每个中断各自还有其局部中断使能控制位,在CSR.ECFG的LIE域中。当CSR.ESTAT中IS域的某位为1且对应的局部中断使能和全局中断使能均有效时,处理器就将响应该中断,并进入中断处理程序入口处开始执行。
LoongArch指令系统默认支持向量化中断8,其13个线中断各自具有独立的中断处理程序入口地址。在LoongArch指令系统中,中断被视作一类特殊的异常进行处理,因此在具体计算中断处理程序入口地址时将SWI0~IPI这13个中断依次“视作”异常编号64~76的异常,用异常处理程序入口地址的统一计算方式进行计算。向量化中断的好处之一是省去了中断处理程序开头处识别具体中断源的开销,可以进一步加速中断的处理。
在支持多个中断源输入的指令系统中,需要规范在多个中断同时触发的情况下,处理器是否区别不同来源的中断的优先级。当采用非向量中断模式的时候,处理器通常不区别中断优先级,此时若需要对中断进行优先级处理,可以通过软件方式予以实现,其通常的实现方案是:
1)软件随时维护一个中断优先级(IPL),每个中断源都被赋予特定的优先级。
2)正常状态下,CPU运行在最低优先级,此时任何中断都可触发。
3)当处于最高中断优先级时,任何中断都被禁止。
4)更高优先级的中断发生时,可以抢占低优先级的中断处理过程。
当采用向量中断模式的时候,处理器通常不可避免地需要依照一套既定的优先级规则来从多个已生效的中断源中选择一个,跳转到其对应的处理程序入口处。LoongArch指令系统实现的是向量中断,采用固定优先级仲裁机制,具体规则是硬件中断号越大优先级越高,即IPI的优先级最高,TI次之,…,SWI0的优先级最低。
在中断处理程序中,经常会涉及中断使能控制位的修改,如关闭、开启全局中断使能。在大多数指令系统中,这些中断使能控制位位于控制状态寄存器中,因此软件在进行中断使能控制调整时,必须关注修改的原子性问题。以LoongArch指令系统为例,全局中断使能控制位IE位于CRMD控制寄存器的第2位。如果仅用CSRRD和CSRWR指令访问CRMD控制寄存器,那么需要通过下面的一段程序才能完成开启中断使能的功能:
li $t1, IE_BITMASK
csrrd $t0, CSR_CRMD
1:
andn $t0, $t0, $t1
or $t0, $t0, $t1
2:
csrwr $t0, CSR_CRMD
这段程序本身也可能被中断,若在标号1和2之间被中断且中断处理程序修改了CRMD控制寄存器的值,则在返回时该中断处理程序对CRMD控制寄存器的改写会被这段程序覆盖。若不想让这种情况发生,就需要保证这段程序不会被打断,更正式地说是保证这段程序的原子性。保证原子性的方法有很多种,例如添加专门的位原子修改指令、在程序执行时禁用中断、不允许中断处理程序修改SR,或者使用通用的方法保证程序段的原子性,即将被访问的控制寄存器作为临界区来考虑。LoongArch指令系统中定义了按位掩码修改控制寄存器的指令CSRXCHG。使用该指令时,上述开启全局中断使能的代码改写如下:
li $t0, IE_BITMASK
csrxchg $t0, $t0, CSR_CRMD
上面的例子中,CRMD寄存器的IE位置1的操作仅通过csrxchg一条指令完成,所以自然确保了修改的原子性。