/**
* I has illustrated a way to using exception handling mechanism to implement
* a operating system(multi task scheduler) on ARMv4 architecture in this article.
* 2015.8.7
*/
本文介绍了再ARMv4结构上实现一个小型操作系统的基本技术,希望对一些ARM爱好者有帮助,也希望高手能指出错误和不足之处,也欢迎拍砖。
对于设计一个操作系统而言,深入的了解目标CPU的特性是非常重要的,这里我们首先介绍一下ARMv4的一些基本知识,ARMv4体系的CPU有以下7种工作模式:
1、用户模式(Usr):用于正常执行程序。
2、快速中断模式(FIQ):用于高速数据传输。
3、外部中断模式(IRQ):用于通常的中断处理。
4、管理模式(svc):操作系统使用的保护模式。
5、数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储以及存储保护。
6、系统模式(sys):运行具有特权的操作系统任务;
7、未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持指令仿真。
Arm的工作模式切换有两种方法:
被动切换:在arm运行的时候产生一些异常或者中断来自动进行模式切换.
主动切换:通过软件改变,即软件设置寄存器来经行arm的模式切换,应为arm的工作模式都是可以通过相应寄存器的赋值来切换的。 除用户模式外,其余6种工作模式都属于特权模式。特权模式中除了系统模式以外的其余5种模式称为异常模式。大多数程序运行于用户模式。进入特权模式是为了处理中断、异常、或者访问被保护的系统资源。
ARMv4寄存器:
ARM有31个通用的32位寄存器,6个程序状态寄存器,共分为7组,有些寄存器是所有工作模式共用的,
还有一些寄存器专属于每一种工作模式;
R13——栈指针寄存器,用于保存堆栈指针;
R14——程序连接寄存器,当执行BL子程序调用指令时,R14中得到R15的备份,
而当发生中断或异常时,R14保存R15的返回值;
R15——程序计数器;
快速中断模式有7个备份寄存器R8—R14,这使得进入快速中断模式执行很大部分程序时,
甚至不需要保存任何寄存器;
其它特权模式都含有两个独立的寄存器副本R13、R14,这样可以令每个模式都拥有自己
的堆栈指针和连接寄存器。
我们的操作系统是一个多任务的操作系统,它具有如下特点:
(1)每个任务有独立的任务控制块.
(2)每个任务独立的享有CPU的寄存器上下文。
(3)每个任务在可以主动放弃或者被调度器强行剥夺CPU的使用权(一般在时钟中断下执行调度)。
(4)任务与内核分别运行在不同的处理器级别上。
(5)任务通过特定的系统调用获得内核的服务。
基于这些要求,结合ARM处理器的特点,我们可以得出以下方法。
(1)每个任务独立的享有R0-12,R13(SP),LR,PC,CPSR,它们都保存在任务控制块的相关域中,作为任务的运行环境。
(2)为了保护资源,任务对某些系统资源没有访问权限,需要通过系统调用由内核代其完成操作,那么用户程序可
以使用ARM的用户模式,而内核则使用特权模式,具体可以这样做:
<1> 系统上电时,默认处于管理模式(SVC),我们SVC中完成系统的初始化之后,可以切换到系统模式,加载用户任
务环境,然后直接切换到用户模式,这样就可以启动用户任务了。
<2> 当中断产生时,处理器进入IRQ模式,此时我们可以切换到系统模式(SYS)保存用户任务的上下文,然后切换到
SVC模式执行ISR(),执行完ISR()之后,我们再切换到系统模式加载用户任务上下文,这样就可以返回到用户任务了。
(3)为了支持用户态主动请求系统服务,ARMv4提供了软件中断(SWI)机制,我们的操作系统可以利用这个机制来实现系统调用。
当用户程序执行SWI指令时,导致CPUS异常而进入管理模式(SVC),这时我们需要切换到系统模式下获取并保存用户任务
上下文,然后返回管理模式执行具体的SWI处理程序,当SWI处理程序返回之后,程序控制流会有两条
<1> 用户执行的系统调用没有发生阻塞,这时我们可要切换到系统模式来加载用户任务上下文,从而恢复任务的运行,用户
任务表现为执行系统调用完成了。
<2> 用户执行系统调用发生了阻塞,例如IO资源不满足,为了处分利用处理器,我们不能停留在这里等待,这时需要我们
切换到其他就绪的任务执行。为了实现这个需求,要求我们在SWI()中发生阻塞时,要把当前的执行上下文(注意这个上下文
是管理模式)保存下来(这里,我们可以保存在用户任务的堆栈中),然后才加载下一个就绪的任务。直到某一时刻之前
由于IO资源阻塞的任务得到了资源而进入就绪状态(设备中断ISR()中修改任务状态,进入就绪队列),我们就可以恢复
它的运行。通过前面的分析可以,这种情况下,用户堆栈保存了两个环境,一个是用户任务在用户模式下的环境,
一个是用户任务在管理模式下的环境,我们恢复的时候,当然是按照栈的基本思想,先恢复管理模式下的环境,
这时回到了之前任务的SWI(),接着SWI()执行完毕,再从堆栈中恢复任务在用户模式下的环境,这时用户任务最终
就执行完一个系统调用了。
(4) 为了使任务之间能以公平的方式使用处理器,我们需要定时的进行任务切换,同时为了满足系统的软件定时系统运作的精确度, 因此可以使用ARM的一个定时器来产生时钟中断,为系统提供”节拍”。在用户模式运行任务时,响应时钟中断请求是毋庸置疑的,然而,在管理模式下时,响应中断与否是一个比较值得关注的问题
<1> 如果不响应,那么内核复杂化将会降低,比较容易实现,后果就是,如果管理模式运行时间超过一个时钟中断周期,那么就有可能丢失一个或者多个时钟中断事件,这对一般的任务来说不会造成什么严重的问题,但是会影响内核软件定时系统的精确度,而依赖于软件定时的任务可能会产生问题,同时对于一些紧急任务来说,例如网卡中断,可能会导致网卡的缓冲区满单来不及响应而丢失数据帧。
<2> 如果响应,那么内核的复杂度将会增大。在管理模式响应中断,需要保存当前管理模式的任务环境,在执行中断ISR() 之后,必须要回到之前保存的环境,而不能切换任务,即在管理模式可以响应中断,但是不能执行任务上下文切换,即使是系统时钟中断产生,我们也不会执行调度函数,这也保证,用户任务一旦执行一个系统调用进入管理模式之后,除非系统调用产生阻塞而导致任务上下文必须切换和中断抢占执行权之外,该系统调用执行不会被调度器抢占。这样虽然内核复杂度增加,也增加了栈空间的消耗(保存管理模式的上下文),但是,计时系统更准确,中断响应也比较及时。
综上所述,虽然在管理模式下响应中断请求会使内核复杂化,也增加堆栈消耗,但是优势也是比较明显的,因此,我们的 内核采用的第二种方案。
注意:这里需要说明的是,管理模式支持中断响应,指的是用户任务通过SWI进入管理模式的情况,而由中断进入中断模式后转入SVC模式执行ISR()的情况,是不支持响应中断的,即不支持中断嵌套。这是为了控制内核复杂度所采取的办法, 当内核经测试稳定之后,可以考虑支持中断嵌套。
上面的表述每次保存用户任务上下文和恢复任务上下文时都需要切换到系统模式,然后再由系统模式切换到用户模式,这是什么原因呢?是否多此一举?何不直接从管理模式恢复到用户模式?
其实,这个问题也困扰了我很久,刚开始学习ARM的时候,还因为ARM那么多的模式有点烦恼呢!何不如直接两个模式来的实在,
一个用户模式一个系统模式,完美解决操作系统的用户态和内核态,哈哈,是不是我的想法有点幼稚?其实这么多模式也是有个用处的。就拿用户模式和系统模式来说,这二者共用一套寄存器,区别就是系统模式可以直接切换系统模式。这有什么用处呢?首先就多任务来说,每个任务都可以有独立的ARM寄存器,这些寄存器在任务被切换的时候保存在自己的任务控制块
(TCB)中,待任务再次获得CPU执行权时,再从TCB中把这些被保存下来的寄存器值一一恢复到CPU的寄存器。问题在于,用户任务执行时
是处于用户模式,进入特权模式之后,要先完全保存任务上下文(CPSR,R0-R12,SP,LR,PC),这其中大部分寄存器很容易保存:
(1) CPSR可以直接从特权模式的SPSR中获取。
(2) R0-R12是所有模式共用的(除了快速中断模式),各个模式下都可以直接获取。
(3) R13(SP) 用作堆栈指针,每个任务都有自己独立的堆栈指针,保存在任务TCP中,也可方便获取。
(4) PC可以从异常特权模式的LR中获取做一定处理后得到。
唯一纠结的就是R14(LR),这个寄存器是链接寄存器,子过程调用时,这个寄存器用来保存返回地址。这个寄存器每个除了用户模式和系统模式共用一个之外,其他模式都有自己的LR。要实现多任务的切换,R14必须像(CPSR,R0-R12,SP,PC)一样保存到各自任务的TCB中,否则后一个任务会破坏前一个任务的LR,导致前一个任务恢复时程序乱套,因为它们都运行在用户模式。然后这个寄存器只在用户模式可见,其他异常模式获取保存用户模式的LR必须切换到用户模式,获得LR之后,又要回到特权模式执行内核函数,这就又引出一个问题,”用户模式无法切换模式”,所以这条路行不通。当时我在设计的时候,也遇到这样的问题,不保存用户模式的LR,就无法实现多任务切换的工作。这时候,系统模式就派上用场啦(我也是看了很多资料,研究了无数次ARM的技术参考手册,才发现系统模式可以用来解决这个问题,或许这就是系统模式存在的唯一理由吧)! 系统模式和用户模式共用一套相同的寄存器,同时系统模式具有切换模式的特权,看到这个提示,相信接下来的路就清晰了。要获取用户模式下的LR寄存器值,可以切换到系统模式。
同样地,要恢复用户模式,也可先切换到系统模式然后在恢复所有寄存器即可,感觉这样做就比较方便了。
然而,从系统模式返回用户模式这也带来一个问题,因为系统模式下没有SPSR寄存器,恢复到任务上下文时,我们不能通过下列指令序列还完成:
LDMFD SP!,{R0}
MSR SPSR, R0
LDMFD SP!,{R0-R12,LR,PC}^
因为系统模式没有SPSR寄存器,我们只能使用一下指令:
LDMFD SP!,{R0}
MSR CPSR,R0
LDMFD SP!,{R0-R12,LR,PC}
这些指令会引发一个问题,MSR CPSR,R0会导致ARM CPU开始响应中断,若来不及执行LDMFD SP!,{R0-R12,LR,PC}这条指令,却已经发生了中断请求,那么用户任务将不能恢复。如果一直产生中断的话,用户任务堆栈会一直在被压入数据,最后会导致堆栈溢出。中断是一个随机事件,我们不容许内核存在太多的不确定因素,所以,必须找到一个解决方法。
可能的解决办法是,从管理模式返回用户模式时不要经过系统模式而直接返回(总结这篇文章时,才想到的办法,回去试验再告知各位)。