中断处理过程示意图_嵌入式操作系统ucosII的中断处理过程(上篇)

一.   UCOSII的中断过程简介

系统接收到中断请求后,如果CPU处于开中断状态,系统就会中止正在运行的当前任务,而按中断向量的指向去运行中断服务子程序,当中断服务子程序运行完成后,系统会根据具体情况返回到被中止的任务继续运行,或转向另一个中断优先级别更高的就绪任务。

由于UCOS II是可剥夺型的内核,所以中断服务程序结束后,系统会根据实际情况进行一次任务调度,如果有优先级更高的任务,就去执行优先级更高的任务,而不一定要返回被中断了的任务。

二.UCOSII的中断过程的示意图

三.具体中断过程

1.中断到来,如果被CPU识别,CPU将查中断向量表,根据中断向量表,获得中断服务子程序的入口地址。

2.将CPU寄存器的内容压入当前任务的任务堆栈中(依处理器的而定,也可能压入被压入被中断了的任务堆栈中。

3.通知操作系统将进入中断服务子程序。即:调用OSIntEnter()或OSIntNesting直接

加1。

4.If(OSIntNesting==1) {OSTCBCur->OSTCBStrPtr=SP;} //如果是第一层中断,则将堆栈指针保存到被中断任务的任务控制块中

5.清中断源,否则在开中断后,这类中断将反复的打入,导致系统崩贵

6.执行用户ISR

7.中断服务完成后,调用OSIntExit().如果没有高优先级的任务被中断服务子程序激活而进入就绪态,那么就执行被中断了的任务,且只占用很短的时间.

8.恢复所有CPU寄存器的值.

9.执行中断返回指令.

四.相关代码

与编译器相关的数据类型:

typedef unsigned char BOOLEAN;

typedef unsigned char INT8U;

typedef unsigned int OS_STK; //堆栈入口宽度为16 位

(一) void  OSIntEnter (void)的理解

uCOS_II.H中定义:

#ifdef   OS_GLOBALS

#define  OS_EXT

#else

#define  OS_EXT  extern

#endif     //定义全局宏OS_EXT

#ifndef  TRUE

#define  TRUE   1

#endif

OS_EXT  BOOLEAN   OSRunning; //定义外部BOOLEAN类型全局变量,用来指示

//核是否在运行

OS_EXT  INT8U   OSIntNesting;//定义外部8位无符号整型数全局变量,用来表

//示中断嵌套层数

OS_CORE.C中的OSIntEnter()函数原型:

void  OSIntEnter (void)

{

if (OSRunning == TRUE) //如果内核正在运行则进入if

{

if (OSIntNesting < 255) //如果嵌套层数小于255,则可以继//续

{

OSIntNesting++; //嵌套层数加1

}

}

}

(二)在中断服务子程序中加if ( OSIntNesting == 1){…}的原因

uCOS_II.H中定义:

typedef struct os_tcb {

OS_STK    *OSTCBStkPtr;//声明指向任务堆栈栈顶的16位指针

………………

} OS_TCB;//定义名为OS_TCB的结构体数据类型,即任务控制块的数据结构

OS_EXT  OS_TCB   *OSTCBCur;//声明一个指向任务控制块的全局指针变量

//用于指向当前任务的任务控制块

中断服务程序中添加的代码:

if ( OSIntNesting == 1)

{

OSTCBCur->OSTCBStkPtr = SP; // 如果是第一层中断,则将被中断任务

//的堆栈指针保存在被中断任务的任务

//任务控制块中

}

关于uCOS-II的中断服务程序(ISR)中必须加“OSIntNesting == 1”的原因 ==避免调整堆栈指针.

出现这个问题的根源是当低优先级的任务被中断,当中断完成后由于有高优先级的任务就绪,则必须调度高优先级的任务,原来的低优先级任务继续被中断着,但是此时的低优先级任务的堆栈已经被破坏,已不能被调度程序直接调度了,要想被调度而必须调整堆栈指针。如下图所示的场景:

问题分析:   要想理解加上上面两句的原因,不妨假设有下面场景出现:void MyTask(void)

{

...

}该任务在执行过程中被中断打断,下面是它的服务子程序

void MyISR(void)

{保存现场(PUSHA)

OSIntEnter();

//此时的堆栈指针是正确的,再往下就不对了,应该在此处保存用户任务堆栈指针 OSIntExit();恢复现场(POPA)    中断返回}

OSIntExit(),大体如下:

OSIntExit()

{

OS_ENTER_CRITICAL();

if( OSIntNesting==0 && OSLockNesting == 0 ) {找到目前系统中就绪表中优先级最的任务      如果不是当前任务,则调度它执行OSIntCtxSw();

}

OS_EXIT_CRITICAL();

}

综上所述,任务调用链如下:MyTask --> MyISR -->

①     OSIntExit -->

②        OS_ENTER_CRITICAL(); ③

OSIntCtxSw();        ④

然而在实际的移植过程中,需要调整的指针偏移量是与编译器相关的,如果想要避免调整,显然一个简单的方法就是在调用OSIntExit之前先把堆栈指针保存下来,以后调度该用户任务时,直接从此恢复堆栈指针,而不再管实际的堆栈内容了(因为下面的内容相对于调度程序来说已经没有用处了)

(三) void OSIntExit (void)的理解

OS_CPU.H中的宏定义:

typedef unsigned short OS_CPU_SR;      //定义OS_CPU_SR为16位的CPU状态寄存器

#if      OS_CRITICAL_METHOD == 1

#define  OS_ENTER_CRITICAL()  asm  CLI // OS_ENTER_CRITICAL()即为将处理器标志

//寄存器的中断标志为清0,不允许中断

#define  OS_EXIT_CRITICAL()   asm  STI // OS_ENTER_CRITICAL()即为将处理器标志

//寄存器的中断标志为置1,允许中断

#endif                                 //此一整段代码定义为开关中断的方式一

#if      OS_CRITICAL_METHOD == 2

#define  OS_ENTER_CRITICAL()  asm {PUSHF; CLI} //将当前任务的CPU的标志寄存器入

//然后再将中断标志位清0

#define  OS_EXIT_CRITICAL()   asm  POPF  //将先前压栈的标志寄存器的值出栈,恢复

//到先前的状态,如果先前允许中断则现在

//仍允许,先前不允许现在仍不允许

#endif                                 //此一整段代码定义为开关中断的方式二

#if      OS_CRITICAL_METHOD == 3

#define  OS_ENTER_CRITICAL()  (cpu_sr = OSCPUSaveSR()) //保存CPU的状态寄存器到

//变量cpu_sr中,cpu_sr

//为OS_CPU_SR型变量

#define  OS_EXIT_CRITICAL()   (OSCPURestoreSR(cpu_sr))// 从cpu_sr中恢复状态寄存

//器

#endif                                  //此一整段代码定义为开关中断的方式三,

//此段代码只是示意代码,OSCPUSaveSR()及

//OSCPURestoreSR(cpu_sr)具体什么函数由

//用户编译器所提供的函数决定.

//以上宏定义非常重要,在使用不同处理器时要使用相应处理器的开关中断指令,在代码移//植时很有用

uCOS_II.H中定义:

OS_EXT  INT8U   OSLockNesting; //8位无符号全局整数,表示锁定嵌套计数器

void  OSIntExit (void)

{

#if OS_CRITICAL_METHOD == 3

OS_CPU_SR  cpu_sr;

#endif   //采用开关中断方式三

if (OSRunning == TRUE) //如果内核正在运行,则进入if

{

OS_ENTER_CRITICAL();//进入临界段,关中断

if (OSIntNesting > 0) //判断最外层中断任务是否已完成

{

OSIntNesting--;//由于此层中断任务已完成,中断嵌套计数器减//一

}

if ((OSIntNesting == 0) && (OSLockNesting == 0))

// OSIntNesting==0表示程序的最外层中断任务以完成, OSLockNesting == 0

//表示是否存在任务锁定,整句代码的意思是如果全部中断处理完了且没有其他

//任务锁定任务调度则执行下列任务调度代码

{

OSIntExitY  = OSUnMapTbl[OSRdyGrp];                  //1

OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]);  //2

if (OSPrioHighRdy != OSPrioCur)                  //3

{

OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

OSCtxSwCtr++;

OSIntCtxSw();

}

}

OS_EXIT_CRITICAL();//开中断

}

}

要理解1,2,3处的代码含义.首先要理解任务是如何调度的,所以先讲一下任务调度的核心算法:

a.数据结构:

1.就绪表:就绪表包含两个变量,他们分别是OSRdyGrp(在uCOS_II.H中为OS_EXT  INT8U  OSRdyGrp;即8位无符号整型的全局变量)和OSRdyTb1[](在uCOS_II.H中为OS_EXT  INT8U   OSRdyTbl[OS_RDY_TBL_SIZE];)

先分析 OS_EXT INT8U  OSRdyTbl[OS_RDY_TBL_SIZE];是怎么回事

#define OS_LOWEST_PRIO  12 //在OS_CFG.H中

这个宏定义了任务所能具有的最低优先级,那么此处共有从0到12共13个优先级,用户在代码移植时可以修改它,自定义所需要的优先级个数,但max(OS_LOWEST_PRIO)==63

#define  OS_RDY_TBL_SIZE   ((OS_LOWEST_PRIO) / 8 + 1) //在uCOS_II.中

OS_RDY_TBL_SIZE用于确定数组OSRdyTbl[]的大小,如果OS_LOWEST_PRIO==63,则上述宏实际上为#define  OS_RDY_TBL_SIZE  8,由于每个数组元素为8位,如果每一位表示一个优先级,则共有8*8=64个优先级

现在回到就绪表,操作系统将优先级分为8组,优先级从0到7分为第一组,对应于OSRdyGrp的第0位,从8到15分为第二组,对应于OSRdyGrp的第1位,以此类推,64个优先级就有下面的对应关系(OSRdyTb1[]每组元素的每一位代表一个优先级):

OSRdyTb1[0]--------------优先级从0到7--------------OSRdyGrp第0位

OSRdyTb1[1]--------------优先级从8到15-------------OSRdyGrp第1位

OSRdyTb1[2]--------------优先级从16到23-------------OSRdyGrp第2位

OSRdyTb1[3]--------------优先级从24到31-------------OSRdyGrp第3位

OSRdyTb1[4]--------------优先级从32到39-------------OSRdyGrp第4位

OSRdyTb1[5]--------------优先级从40到47-------------OSRdyGrp第5位

OSRdyTb1[6]--------------优先级从48到55-------------OSRdyGrp第6位

OSRdyTb1[7]--------------优先级从55到63-------------OSRdyGrp第7位

现在再做如下对应:

当OSRdyTbl[0]中的任何一位是1时,OSRdyGrp的第0位置1,

当OSRdyTbl[1]中的任何一位是1时,OSRdyGrp的第1位置1,

当OSRdyTbl[2]中的任何一位是1时,OSRdyGrp的第2位置1,

当OSRdyTbl[3]中的任何一位是1时,OSRdyGrp的第3位置1,

当OSRdyTbl[4]中的任何一位是1时,OSRdyGrp的第4位置1,

当OSRdyTbl[5]中的任何一位是1时,OSRdyGrp的第5位置1,

当OSRdyTbl[6]中的任何一位是1时,OSRdyGrp的第6位置1,

当OSRdyTbl[7]中的任何一位是1时,OSRdyGrp的第7位置1,

如果置1表示有任务进入就绪态,那么上面的表可以理解为:OSRdyGrp的第N位(0<=N<=7)为1,那么在OSRdyTb1[N]中至少有一位是1,也就是说在OSRdyTb1[N]对应的任务中至少有一个任务处于就绪态

该表在OS_CORE.C中定义如下:

INT8U  const  OSMapTbl[]={0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

//8位无符号整型常量数组

3.表(数组)OSUnMapTb1[]:用于求出一个8位整型数最低位为1的位置

该数组在OS_CORE.C中定义如下:

INT8U  const  OSUnMapTbl[] = {

0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F */

5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F */

6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F */

5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F */

7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F */

5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF */

6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF */

5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF */

4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF */

};

理解: 我把问题转化为:

“一个无符号的8位整数,如何确定最低位为1的位的位置?”

即对于任意一个8位整型数,比如4,考虑它的二进制位中所有为1的位,确定最低位为1的位置(相对第0位的偏移),一般来讲首先想到的方法是移位的方法.如:

pos=0;//pos用于统计相对于第0位的偏移

while( !(num & 0x01) )//与00000001按位于,如果最低位为1,退出循环,即找到最低位//为1的位

{

num=num>>1;//将二进制数右移一位

pos++;//进行一次移位,则pos加一

}

最后得到的pos就是所有位中为1的最低位的偏移量,但这样计算需要时间,尽管最多右移7次。为了节省时间,使用的方法是“空间换时间”的办法,即把8位无符号数,所有可能的情况的都列了出来,共有256个数字,把每个数字的最低为1位的位置都预先计算好。比如4对应二进制数为100,最低为1位相对第0位偏移量为2,则查表时,以4为索引,马上就得到2这个数字。(即:OSUnMapTb1[4]==2)

b.构建OSRdyGrp和OSRdyTb1[]算法:

代码原型在OS_CORE.C中,实际代码大致如下:(prio为任务优先级)

INT8U  OS_TCBInit (INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT32U stk_size, void *pext, INT16U opt)

{

…………

ptcb->OSTCBY         = prio >> 3;

ptcb->OSTCBBitY      = OSMapTbl[ptcb->OSTCBY];

ptcb->OSTCBX         = prio & 0x07;

ptcb->OSTCBBitX      = OSMapTbl[ptcb->OSTCBX];

…………..

OSRdyGrp               |= ptcb->OSTCBBitY;

OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

…………..

}//此函数在创建任务时被调用,即OSTaskCreate(..)中,用于初始化任务控制块

以上代码可以等效于:

OSRdyGrp  |=  OSMapTbl[prio>>3];

OSRdyTb1[prio>>3] |= OSMapTbl[prio&0x07];

此处 prio >> 3 是右移3位,就相当于连续除以3个2,因此相当于:prio / 8 ;

prio & 0x07 是求低3位的值,而由高5位构成的值正好是8的整数倍,因此是取余运算,

即:prio % 8

因此又可将算法等效为:

OSRdyGrp  |=  OSMapTbl[prio / 8];

OSRdyTb1[prio / 8] |= OSMapTbl[prio % 8];

算法的作用相当于如下流程:(假定prio=28)

就这样把任务的优先级放入了就绪表.在此我产生一个疑问,”prio>>3与prio&0x07并不直观,为什么不用prio/8与prio%8呢?”我做如下解释:

处理器一般具有如下结构

累加器是具有移位功能的,prio>>3可以在累加器中完成而不必进入ALU,而prio/8则不同,要进入ALU,ALU处理速度不如累加器,如果采用prio/8将降低操作系统的实时性,同样prio&0x07只是一个间单的位与操作,而prio%8则还要经过ALU,如采用prio%8也将降低实时性.

现在回到OSIntExit()处,看1,2,3处的代码:

OSIntExitY  = OSUnMapTbl[OSRdyGrp];                //1

OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]);//2

if (OSPrioHighRdy != OSPrioCur)                    //3

{

OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

OSCtxSwCtr++;

OSIntCtxSw();

}

在uCOS_II.H中有如下定义:

OS_EXT  INT8U   OSIntExitY;//8位无符号全局整型变量,用于存放就绪表中就绪的任务组

OS_EXT  INT8U   OSPrioHighRdy;// 8位无符号全局整型变量,用于存放具有最高优先级任务的优先级

OSUnMapTbl[]:用于计算偏移量,偏移量即为优先级最高的就绪任务组在OSRdyGrp中的位置

以及优先级最高的任务在最高优先级任务组OSRdyTbl[N](N表示最高优先级任务组,0<=N<=7)中的位置.

OSIntExitY  = OSUnMapTbl[OSRdyGrp];//表示获得具有最高优先级的组

例如OSRdyGrp值为01101000(0x68),则第3,5,6组中有任务就绪,查表OSUnMapTbl[0x68]==3,即优先级最高任务组为第3组.

OSUnMapTbl[OSRdyTbl[OSIntExitY]] //表示获得最高优先级任务组中的优先级最高的任务

例如OSRdyTbl[3]的值为01110000(0x70),则第4,5,6位中有任务绪,OSUnMapTbl[0x70]==4,即优先级最高的任务在组中位于第4位.

OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]);

//就是3*8+4==28,再经强制类型转换成INT8U型,赋给OSPrioHighRdy

这样OSPrioHighRdy就获得了就绪表中优先级最高的任务

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页

打赏作者

weixin_40009026

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值