全部博文(71)
分类: LINUX
2008-04-22 11:10:31
3.1中断概述
简单地说就是当前的工作被一个突发事件打断。例如,您正在看一本小说,突然电话铃响了,你放下小说去接电话,在接电话的过程中,送快递的人按响了您家的门铃,您又暂时放下电话去开门。这样一个事例已经蕴含了所有的中断相关的概念了。下面仔细的分析一下这个例子,看看它蕴含了那些中断概念。首先,看小说是一个正常工作,电话铃响表示一个中断发生,您放下手中的书,并在书上做上标签记号(保存现场),然后,去接电话(电话响的中断处理),门铃响起(中断嵌套了),再次放下电话(第二次保存现场),打开门(关于门铃响的中断处理),最后,回到电话旁继续接电话(中断恢复),打完电话继续从标签处读书(中断恢复)。
为什么需要中断呢?
回答这个问题只需回答中断的好处就行了?在中断出现以前的计算机系统中,计算机采用查询的方式监控外部事件,由于外部事件一般为非周期事件,且外部设备相对CPU来说速度较慢,从而导致了CPU浪费大量的时间查询或者等待I/O操作。中断的引入使CPU不需要在I/O上花费大量的时间,CPU只在需要的时候处理I/O操作,这大大提高了CPU的利用率。
随着计算机技术的发展,中断概念得到进一步的扩展,被定义为导致程序正常执行流程改变的事件。在实际的计算机系统中,中断又可细分为以下三种类型:
· 中断:相当于传统意义上的中断,由CPU外部原因而改变程序执行流程的过程,属于异步事件,又称为硬件中断;
· 陷入:表示通过处理器所拥有的软件指令,可预期地使处理器正在执行的程序流程发生变化,有些处理器还伴随着处理器运行模式的改变。在现代32位处理器核中基本上都带有陷入指令,比如Motorola 6800系列中Trap指令,ARM中SWI指令,和Intel80x86中的INT指令。陷入指令的执行是有意安排的,所以陷入操作属于显式事件。陷入在操作系统内核中有着广泛的应用,操作系统的系统调用一般都基于陷入;
· 异常:异常事件是指CPU在运行过程中遇到诸如被0除,执行非法指令,内存保护故障等操作。异常没有对应的处理器指令,当异常发生时,当前运行的程序被无条件挂起,处理器的PC指向一个特定的地址,并开始执行特定的程序。
对于实时嵌入式内核来讲,中断是必不可少的机制,中断有效地保证了具有时限要求的部分能够得到及时的响应。实时嵌入式内核都应该提供管理中断机制,该机制应该方便中断处理程序开发,并能使中断服务程序与任务有效地结合起来。
3.2 中断处理过程
当中断发生后,到底需要哪些处理步骤呢?典型的步骤如下(摘自William Stallings的《操作系统内核与设计原理》):
1. 中断事件给处理器发送一个中断请求信号;
2. 处理器在响应中断请求信号前挂起当前指令;
3. 处理器对中断进行判定,确定中断的存在,并给提交中断的设备发送确认信号;
4. 处理器为转移到中断处理程序做准备。首先,需要保存从中断点恢复所需的信息,这些信息至少包括程序状态字(PSW)和程序计数器(PC)的下一条指令位置;
5. 处理器把响应这个中断的中断处理程序入口地址装入程序寄存器。一旦完成对程序计数器的装入,处理器则继续下一个指令周期,这时中断服务程序开始执行;
到目前为止,完成了中断检测和现场保护阶段的工作,中断处理部分主要完成中断服务程序的执行,中断服务程序一般由用户编写。当中断服务程序结束时,中断处理进入中断恢复步骤,这主要细分为以下两步:
6. 恢复中断前被保存在相应栈中的寄存器内容;
7. 最后的动作从栈中恢复PSW和程序计数器的值。这时程序恢复到被中断前的状态,并开始运行。
3.3 中断管理的实现
对于应用程序而言,应用人员希望只需编写中断处理程序,对于其他的中断处理步骤都不需要了解和关心,比如中断现场保护,中断恢复等等,这应该算是中断管理的第一个目标;第二目标就是对于一个中断(如定时器中断),它可以有多余一个的中断处理程序。为了达到这个效果,内核应该怎样来实现呢?
在谈及实现之前,有必要先看看向量中断和非向量中断。多数处理器都提供向量中断和非向量中断,向量中断是指芯片为每个中断分配一个入口地址,当中断发生时直接就能进入相应的服务程序,因此有较快中断响应时间。非向量中断为一类中断分配一个入口地址,这类中断共享一个入口地址,当中断发生时,还需要确定具体的中断。例如,三星公司的S
下面可以看看ByCore的具体实现了。
ByCore对中断管理的实现很简洁(主要是复杂的我还设计不出来!~_~),由于中断管理与具体的硬件有关,所以在该小节中的论述是建立在这样一个前提下,该前提就是当前已经完成了中断现场保护,除此之外没有涉及到中断恢复,因为这些和处理器有关,所以将这些内容放到内核移植一章中叙述。
前面提到,中断实现的最终目的之一是让一个中断可以对应多个不同的中断处理程序。实现的基本思想为,首先将中断源按顺序编号,然后,当中断发生时,根据中断源调用相应的中断处理程序。为此,内核中设计了描述中断的数据类型如下所示:
typedef struct isr_desc{
list_t link;
void (*pisr)();
}isr_t;
这个结构体包括两个成员,link和(*pisr)( ),link主要起到连接作用,通过link域可以把ist_t类型连成双链表。(*pisr)( )是函数指针,它指向中断处理程序的入口地址。对于一个有n个中断源的处理器,首先,将n个中断源从0开始编号,初始化时创建一个list_t israrry [n]的数组(lsit_t[i]对应第i号中断)。该数组中的israrry [i]就是中断源为i编号相关中断处理程序队列的队头。其结构如图3.1所示。
图3.1 中断处理结构
有了图3.1的描述,就可以实现一个中断处理程序来回调每个isr_t结构中的(*pisr)( ),而(*pisr)( )所指向的正式用户编写的中断处理程序。ByCore中使用了isrHandler( )函数来实现这个回调。isrHandler( )的实现如下所示:
void isrHandler(void){
uword_t isrnum;
list_t *plist;
isr_t *phdl;
int_cnt++;
int_flag = TRUE;
isrnum = get_isr_num(); ①
plist = israrry[isrnum].prev; ②
do{
if(plist != &israrry[isrnum]){
phdl = mac_find_entry(plist,isr_t,link);
phdl->pisr();
plist = plist->prev;
}
}while(plist != israrry[isrnum].prev);
isr_clr_pending(isrnum); ③
scheduler();
}
在①处的get_isr_num()函数和具体的处理器有关,它的功能是得到中断源,在ARM中,实际上是查询I_ISPR寄存器,该寄存器记录了中断源。从②处开始,根据得到的中断号,检索israrry[]数组,并回调pisr()指向的用户中断处理函数。③处的isr_clr_pending()函数是清楚硬件的中断标志位,保证下次中断。下面的代码为get_isr_num()和isr_clr_pending()的实现,它们的实现与处理器有关。
uword_t get_isr_num(void){
uword_t tmp;
uword_t renum = 0;
tmp = rI_ISPR;
while(tmp != 0){
tmp >>= 1;
renum++;
}
return renum-1;
}
void isr_clr_pending(uword_t isr_num){
uword_t tmp = 1;
while(isr_num-- > 0)
tmp <<= 1;
rI_ISPC |= tmp;
}
现在留下最后一个问题没解决了,当一个中断处理程序编写完毕以后,怎样让它注册到内核中呢?具体的说就是在israrry[]中的适当位置添加一个节点。ByCore实现了一个这样的函数isrInstall(),它的原型与实现如下:
void isrInstall(uword_t IntNum, void (*phdl)()){
isr_t *ptmp;
ptmp = (isr_t*)kmalloc(sizeof(isr_t));
if(ptmp != NULL){
ptmp->pisr = phdl;
add_node_seque_rear(&israrry[IntNum],&ptmp->link);
}
}
isrInstall()需要两个参数,一个是IntNum和phdl指针,前者说明了中断编号,后者指向该中断号的处理程序。isrInstall()的实现非常简单,申请一个isr_t类型的空间,然后将isr_t的pisr指针指向phdl,然后,将此isr_t类型添加到israrry[]数组。
到此为止,中断部分也差不多结束了。最后需要说明的是中断编号问题,至于怎样去将一个处理器的中断编号,ByCore不会很在意,只需注意的是israrry[]数组的编号与实际的中断的编号应该一一对应。还有就是israrry[]数组的大小为实际处理器中断的个数。
OK!see you next time!