简要目录:
一、uCOS2中的中断处理过程。
二、在一指的情况下,要注意的问题。
三、在一指的情况下,代码可能的执行路径。
四、贴一些网上还好的关于ucos中断处理的帖子。
(提醒下,以下细节分析部分,是基于凌阳SPCE061A处理器,及移植的uCOS2.00的代码,可能有局限性。 且这里的OSEnterCritical()和O***itCritical是采用简单的关总中断和开总中断操作来实现的。)
一、uCOS2中的中断处理过程:
关总中断,保护现场,清中断标志(硬件自动清除的就不用了) (1)
禁止中断源使能(若中断源可能引起重复中断,必关之) (2)
OSIntEnter() (3)
中断服务代码(这个才是目的) (4)
OSIntExit() (5)
二、要注意两点:
1、上述步骤(1)中的保护现场。
一般是指通用寄存器入栈,但最终一定要与系统任务上下文内容切换恢复那些个动作对应,即是对应系统任务切换函数OSSched()最终调用的那个OSCtxSw()和OSIntCtxSw()两个函数做任务切换处理的动作相对应,具体是指恢复堆栈指针和能用寄存器等那些个部分。比如我这里任务切换最终的两段代码如下:
(声明一下,代码来自凌阳论坛某高手移植的源码。)
//宏定义,压栈、出栈
PUSHALL: .MACRO
push r1,r5 to [sp]
.ENDM
POPALL: .MACRO
pop r1,r5 from [sp]
.ENDM
_OSCtxSw:
PUSHALL
OSIntCtxSw_in:
r1=[_OSTCBCur] //获得当前TCB的指针
[r1]=sp //OSTCBCur->OSTCBStkPtr = Stack Pointer
call _OSTaskSwHook
r1=[_OSTCBHighRdy]
[_OSTCBCur]=r1
r2=[_OSPrioHighRdy]
[_OSPrioCur]=r2
sp=[r1] //得到需要恢复的任务的堆栈指针
POPALL
reti
_OSIntCtxSw:
r1=sp
r1+=7
sp=r1
jmp OSIntCtxSw_in //除了前面一点不同,OSIntCtxSw()的代码和OSCtxSw()是一样的
从代码中知道,最后就是POP ALL,恢复5个寄存器,然后再reti引起PC值弹栈返回,所以,我们在中断子程序中,保护现场应该就是PUSHALL,实际就是push r1,r5 to [sp]---对5个通用寄存器入栈,与其POP ALL对应。
2、上述步骤(2) - 依情况禁止中断源使能。
需要这么做的情况,前提条件是:
1、引起此次中断的中断源在清除中断标志后,在中断开启和中断源使能情况下,仍会连续触发中断。
2、使用的uCOS2操作系统的临界处理办法是简单地开关中断。即OSEnterCritical()和O***itCritical()函数,前者关总中断,后者开总中断。
3、你不希望瞬间重复中断---即中断处理后(即瞬间后),一旦开总中断,中断源仍处于不断发起中断中。如按键引起外部中断,只要总中断开,哪怕是瞬间一开一关,也会因为按键抖动而瞬间(ms级)引起许多次中断。
那么,此时必须禁止中断源使能,以防止重复中断。
当然,你会说,进入中断服务程序后,步骤(1)不是关总中断了吗?是的,但关键在于一旦在ISR中调用任何系统函数,如常用的OS***Post()函数,或者逃不掉的OSIntExit()函数,看其源码,发现这些函数都会执行OSEnterCritical()和O***itCritical()函数,而前提条件中说了,“使用的uCOS2的这两个临界函数是简单地开关中断。(即OSEnterCritical()和O***itCritical()函数,前者关总中断,后者开总中断。),那么这些系统函数执行过程中就已经通过O***itCritical()开启总中断了,那么,再满足上述第1个条件时,一定会重复中断,而再同时满足第3个条件时,你必须得按上述说的做了。
三、在一指的情况下,代码可能的执行路径。
1、一的代码中,假设禁止了所有中断源使能---即所禁止的中断源中,已经包含应用系统中所有可能引起中断的中断源。
那么,即使接下来再开总中断,也不会中断了,直到重新使能中断源。
那么,此时代码执行路径为步骤1至4依次执行,在步骤5中,调用OSIntExit()时,会执行任务切换,最终调用的函数为OSIntCtxSw(),如上述代码所示,而后reti跳到“任务”中执行。
当然,这个“任务”,可能是新任务,也可能是被中断的原任务,也可能是系统任务如IdleTask()。OSIntExit()后面的代码不可能会被执行 。
--- 另外,事实上还有个前提,步骤4中,你自己的服务代码,不含有,直接引起跳转后又不返回来的指令。
2、如果上述代码中未禁止中断源使能,则有可能在重复中断,至于重复中断的位置,可能是ISR中,OSIntExit()中,新任务、OSTimeTick()、OSIdleTask()都有可能。
因为只要是中断服务中一旦调用系统函数时,必通过其中调用的临界函数O***itCritical()开启总中断,就可能重复中断;
即使出了中断服务程序(OSIntExit()执行过后退出中断服务程序),因为总中断一定是被开了的(解释见二),所以此后调用新任务、OSTimeTick()、OSIdleTask()都有可能被重复中断。
四、网上一些关于ucos中断处理的帖子
4.1、嵌入式操作系统ucosII的中断处理过程(上篇)转
来源:http://puppypuppy2005.blog.163.com/blog/static/52048156200996105349904/
一. 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就获得了就绪表中优先级最高的任务
4.2、嵌入式操作系统ucosII的中断处理过程(下篇)
来源:http://puppypuppy2005.blog.163.com/blog/static/52048156200996105349904/
再看3处代码:
在uCOS_II.H中有如下定义:
OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1];//定义指向任务控制块的指针数//组,且每个优先级在同一时刻只对应一个任务
OS_EXT INT8U OSPrioCur;//用于保存目前任务的优先级
OS_EXT INT32U OSCtxSwCtr;//32位无符号全局整型变量,作为任务切换计数器
OS_EXT OS_TCB *OSTCBHighRdy;//指向最高优先级任务任务控制块的指针
if (OSPrioHighRdy != OSPrioCur)
//就绪态任务中的最高优先级已不是目前任务的优先级,则进行中断级的任务//切换
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
//将最高优先级任务控制块指针指向当前优先级最高的任务的任务控制块
OSCtxSwCtr++;//任务切换计数器加1
OSIntCtxSw();//调用中断级任务切换函数
}
此段代码体现出了可剥夺型实时操作系统内核的特点.
OSIntCtxSw()在80x86上的移植代码,此代码在OS_CPU_A.ASM中,代码如下:
_OSIntCtxSw PROC FAR
;
CALL FAR PTR _OSTaskSwHook ; 调用OSTaskSwHook()函数,此函数在
;OS_CPU_C.C中只是个空函数,留给用户
;在代码移植时自定义
;
MOV AX, SEG _OSTCBCur ;由于发生了段转移,恢复刚才(当前任务)数
MOV DS, AX; 据段
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ;AH=_OSTCBHighRdy+3
;AL=_OSTCBHighRdy+2
MOV DX, WORD PTR DS:_OSTCBHighRdy ;DH=_OSTCBHighRdy+1
;DL=_OSTCBHighRdy
MOV WORD PTR DS:_OSTCBCur+2, AX ;_OSTCBCur+3=AH
;_OSTCBCur+2=AL
MOV WORD PTR DS:_OSTCBCur, DX ;_OSTCBCur+1=DH
;_OSTCBCur=DL
;OSTCBCur=OSTCBHighRdy
MOV AL, BYTE PTR DS:_OSPrioHighRdy ;
MOV BYTE PTR DS:_OSPrioCur, AL;OSPrioCur= OSPrioHighRdy
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ;取址指令
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX] ;
;SS:SP=OSTCBHighRdy->OSTCBStkPtr
POP DS ;DS出栈
POP ES ;ES出栈
POPA ;CPU其余寄存器出栈
;
IRET ; 中断返回
;
_OSIntCtxSw ENDP
以上汇编代码在移植时根据处理器不同要作修改
四.在ISR中通知任务做事的理解(以OSSemPost()为例)
在理解OSSemPost(),先要理解事件,如下是事件的数据结构:
typedef struct {
INT8U OSEventType;//事件类型,这里是OS_EVENT_TYPE_SEM即信号量
INT8U OSEventGrp; //等待任务所在的组
INT16U OSEventCnt; //当事件是信号量时,使用此计数器
void *OSEventPtr; //信号量时不使用
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//等待任务列表
} OS_EVENT;
其中OSEventGrp与OSEventTbl[]构成等待事件的任务列表,前面所讲的OSRdyGrp与OSRdyTbl[]具有同样的功能,划分也一模一样.
在ISR中调用函数OSSemPost(),给任务发信息,此函数在OS_SEM.C中:
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif //定义开关中断类型
#if OS_ARG_CHK_EN > 0//如果启用了函数参数检查功能则进行参数检查
if (pevent == (OS_EVENT *)0) {
return (OS_ERR_PEVENT_NULL);//检查是否有事件发生,如果没有则报错
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {
return (OS_ERR_EVENT_TYPE);//检查当前事件是不是信号量,不是则出错
}
#endif
OS_ENTER_CRITICAL();//关中断
if (pevent->OSEventGrp != 0x00) { //如果等待事件发生的任务列表不为空,
//即有任务处于等待状态,则进入if
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
//使对应事件的任务从等待变为就绪
OS_EXIT_CRITICAL();//开中断
OS_Sched(); //进行任务调度
return (OS_NO_ERR);
}
if (pevent->OSEventCnt < 65535) { //如果等待事件发生的任务列表为空,且信号量计数
//器的值小于65535,则信号量计数器加1,否则不执
//行if,而报信号量益出
pevent->OSEventCnt++;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
OS_EXIT_CRITICAL();
return (OS_SEM_OVF);}
附:
uCOS_II 大致的启动过程:
main()
{
......
OSInit();
......
OSTaskCreate();//此函数在OS_TASK.C中,用于创建任务,调用了三个重要的系统函数
//它们是OSTaskInit();OS_TCBInit();OS_Sched();
......
OSStart();
}
OSTaskCreate()//此函数只能在main()及任务中调用,中断服务子程序不能调用
{
......
OSTaskStkInit();//此函数在OS_CPU_C.C中,用于创建任务堆栈,在移植过程中可根据
//具体情况做修改
OS_TCBInit();//此函数在OS_CORE.C中,用于初始化任务控制块,及就绪表
......
OS_Sched()();//此函数在OS_CORE.C中,是任务级调度函数,作用是获得最高优先级任务
...... //并进行调度,此函数包含一个重要函数OS_TASK_SW()
}
OSStart()
{
......
If(没有任务启动)
{ 获取最高优先级任务
OSStartHighRdy();//此函数在OS_CPU_A.ASM中,用于启动任务,在移植过程中随处理器
//不同要作修改
}
}
OS_TASK_SW()//在OS_CPU.H中它是一个宏定义,用于产生任务切换的中断,移植中要作修改
#define uCOS 0x80
#define OS_TASK_SW() asm INT uCOS
为什么在OSTaskCreate()中调用OS_Sched()后还要调用OSStart()来启用任务呢?
事实上在从main()中创建的任务是不执行OS_Sched()函数的,因为此时的任务并未启动,OSRunning的值为0。任务启动要通过OSStart()才行。相反,当在一个已启动的任务中调用OSTaskCreate()就通过OS_Sched()函数(OSRunning==1),而不用OSStart(),OSStart()只在操作系统启动时调用,任务中不调用
一.任务调度中的几个函数的区别:
----------------------------------------uCOSII启动时的任务调度--------------------------------------
OSStartHighRdy():该函数在OS_CPU_A.ASM中原形如下:
_OSStartHighRdy PROC FAR
MOV AX, SEG _OSTCBHighRdy ;
MOV DS, AX ;获得要运行任务的任务控制块所在段的段址
CALL FAR PTR _OSTaskSwHook ;调用用户自定义的任务切换接口函数
MOV AL, 1 ;0
MOV BYTE PTR DS:_OSRunning, AL ;置任务运行标志
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ;
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX+0] ;从任务控制块首指的四个8位内存单元获得该任务的任务堆栈的地址
;
POP DS ;DS出栈至任务堆栈
POP ES ;ES出栈至任务堆栈
POPA ;将其余CPU寄存器出栈
;
IRET ;恢复代码段及指令寄存器内容,运行任务
_OSStartHighRdy ENDP
该函数由OSStart()调用
void OSStart (void)//在OS_CORE.C中
{
INT8U y;
INT8U x;
if (OSRunning == FALSE) {
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy = (INT8U)((y << 3) + x);
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy();
}//如果多任务环境还没有启动,则先获得就绪表中任务的最高优先级,再获得该优先级下任务的任务控制块的地址(通过OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy],
OSTCBPrioTbl[]是一个指针数组,用于存放各优先级任务的任务控制块的地址,它在OS_TCBInit()中被赋值),然后调用OSStartHighRdy()启动任务。
}
-------------------------------任务级的任务切换--------------------------------
OSCtxSw():函数在OS_CPU_A.ASM中的原形如下:
_OSCtxSw PROC FAR
;
PUSHA ;将所有CPU寄存器压栈
PUSH ES ;经附加段寄存器压栈
PUSH DS ;将数据段寄存器压栈
MOV AX, SEG _OSTCBCur ;获取当前任务所在段的段址,放入DS
MOV DS, AX ;
LES BX, DWORD PTR DS:_OSTCBCur ;获取当前任务任务控制块的段地址及偏移地址
MOV ES:[BX+2], SS ;将当前任务堆栈的断址保存在当前任务的任务控制块中
MOV ES:[BX+0], SP ;将当前任务堆栈的偏移地址保存在当前的任务控制块中
CALL FAR PTR _OSTaskSwHook ;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ; OSTCBCur = OSTCBHighRdy
MOV DX, WORD PTR DS:_OSTCBHighRdy
MOV WORD PTR DS:_OSTCBCur+2, AX
MOV WORD PTR DS:_OSTCBCur, DX
MOV AL, BYTE PTR DS:_OSPrioHighRdy ; OSPrioCur = OSPrioHighRdy
MOV BYTE PTR DS:_OSPrioCur, AL
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ;将最高优先级任务的堆栈
MOV SS, ES:[BX+2] ;指针放回CPU的堆栈段寄存
MOV SP, ES:[BX] ;器及堆栈指针寄存器中
;SS:SP= OSTCBHighRdy->OSTCBStkPtr此时的任务堆栈已改变,变为最高优先级任务(已是当前任务)的任务堆栈
POP DS ;
POP ES ;
POPA ;
IRET ;
_OSCtxSw ENDP
该函数并非由OS_Sched()直接调用而是通过软中断指令INT 0x80(CPU为80x86),
产生中断,到中断向量表中找的OSCtxSw()的入口地址,然后跳转到该函数并执行的。
OS_Sched() 函数原形如下:
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
INT8U y;
OS_ENTER_CRITICAL();
if ((OSIntNesting == 0) && (OSLockNesting == 0))
//已是中断前套最外层且无任务锁定则执行以下代码
{
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
//从就绪表中获得任务的最高优先级
if (OSPrioHighRdy != OSPrioCur)
//如果当前任务的优先级已不是最高,则将行任务切换
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
//将OSTCBHighRdy指向具有最高优先级任务的任务控制块
OSCtxSwCtr++; //任务切换计数器加一
OS_TASK_SW(); //此函数是一个宏,见OS_CPU.H中的定义:
//#define uCOS 0x80
//#define OS_TASK_SW() asm INT uCOS
//执行中断,调用OSCtxSw()
}
}
OS_EXIT_CRITICAL();
}
-------------------------------中断级的任务切换--------------------------------
OSIntCtwSw()已经在中断处理过程中解释过,它是通过OSIntExit()来调用的.
二.关于操作系统源文件系统结构的认识
uCOSII源文件已将各文件作了很好的归类,以方便用户在其他处理器上以移植它的代码,其中OS_CPU.H,OS_CPU_A.ASM,OS_CPU_C.C是与用户具体使用的处理器相关的,在移植时
要根据处理器对其中的代码作相应修改,这就是所谓的HAL(硬件抽象层)。
另外OS_CFG.H,INCLUDES.H与用户具体的应用程序相关,包括决定任务的最低优先级,用户应用程序所能拥有的最大任务数等。
对于其余文件,用户在移植时一般不用考虑修改。
三.关于中断
1.中断嵌套:中断嵌套只能发生在中断服务子程序中,在中断服务子程序运行过程中,
当有更高优先级的中断发生且此时中断是打开的,则将发生中断嵌套
2.中断服务程序通知任务做事是通过事件使任务处于就绪状态,而并非立即进行任务切换,因为它们都是调用OS_Sched(),而函数只能在所有中断服务程序结束运行后才进行切换任务(因为if ((OSIntNesting == 0) && (OSLockNesting == 0)){…OS_TASK_SW();…}),所以任何任务切换都不能发生在ISR中,而必须等到所有ISR
结束运行后(因为if ((OSIntNesting == 0) && (OSLockNesting == 0)){ …}),在OSIntExit()中进行.所以任何时刻uCOSII只能有一个任务处于运行态.
四.关于任务调度
在任务中调用OSTaskCreate ()与在任务中调用OSSemPost(),OSMboxPost(),OSFlagPost()
,OSQPost(),OSQPostFront()进行任务调度的区别是,前者任务处于睡眠态(尚未创建任务),而后者的任务处于等待状态(任务已将创建)。
五.OSSemPost(),OSMboxPost(),OSFlagPost(),OSQPost(),OSQPostFront()的相同点
在中断服务子程序中,都只是使任务从等待状态进入就绪状态,而不调用OS_Sched()
而在任务中调用这些函数进行任务切换,都是通过OS_Sched