UCOS-II 中断与时钟问题总结(以UCOS2.25自带的X86例子)

    这两天在看UCOS-II, uCOS中断和时钟问题做个小结,以UCOS2.25自带的X86例子、《嵌入式实时操作系统uCOS-II原理及应用》为基础,相信对网友们也有帮助,理解不对的地方,请高手指正:

      UCOS-II是抢占式、实时操作系统,这是核心。

  1.   uCOS的系统有任务级任务调度中断级任务调度:任务级任务调度靠常常在下面的代码中自动调用,

void main()

{

   OSinit();

    OSTaskCreate();

    OSTaskCreate();

   .......

  OSStart();


}

      当执行OSTaskCreate时,会将任务优先级较高的队列改变为就绪状态,同时OS_CORE.C中调度器OnSched()进行任务调度(内部调用ON_TASK_SW宏,该宏为INT 0x80,进行软件中断,调用在OS_CPU_A.ASM中需要实现的OSCtxSwOSCtxSw是宏调用通常含有软中断指令,切换是靠中断级代码完成的))。OnSched只调用优先级最高的任务,可以通过进程同步类的函数来释放CPU资源,达到任务并行进行和通信,这些函数包括对任务状态和优先级改变的函数(OSTaskSuspend, OSTaskResume, OSTaskChangePrio, OSTaskDel, OSTimeDly,ostimedlyhmsm,这几函数涉及到任务的状态迁移,见下面图), 信号量,消息队列,互斥信号量(用于防止任务优先级反转,很重要,可以另开一个话题)。

UCOS-II 中断与时钟问题总结(以UCOS2.25自带的X86例子)_第1张图片

UCOS-II 中断与时钟问题总结(以UCOS2.25自带的X86例子)_第2张图片UCOS-II 中断与时钟问题总结(以UCOS2.25自带的X86例子)_第3张图片


睡眠态:ROMRAM中,交给UCOS要调用下面两个函数之一:OSTaskCreate或者OSTaskCreateExt,调用之后告诉了UCOS任务的起始地址,优先级,要使用多少栈空间。

  就绪态:建立了任务之后,就进入就绪态。如果是由任务建立的新任务且优先级更高,那么新建的任务将立即得到CPU使用权。通过OSTaskDel将一个任务返回到睡眠态。

  运行态:调用OSStart可以启动多任务,该函数运行用户初始化代码中已建的优先级最高的任务。

  等待态:正在运行的任务通过两个函数将自己延迟即进入等待状态。OSTimeDly或者OSTimeDlyHMSM。这两个函数将会立即执行强制任务切换,让下一个优先级最高且进入就绪态的任务运行。当延时时间到后,系统服务函数OSTimeTick将会让等待的任务进入就绪态。在运行中的任务如果调用OSFlagPendOSSemPendOSMutexPendOSMboxPend或者OSQPend时时间并没有发生,那么该任务就进入等待态,此时最高优先级的就绪态任务进入运行态。当等待事件发生或者等待超时,该任务就从等待态进入就绪态。

  中断服务态:正在运行的任务可以被中断。被中断了的任务进入中断服务态。响应中断时该任务被挂起,中断服务子程序可能报告多个事件发生,从而使得更高优先级的任务进入就绪态,当中断返回时会重新判断优先级,让最高优先级的任务运行,如果由更高优先级的任务那么先执行,直到被挂起的任务的优先级最高才会返回到被中断挂起的任务。


2. 下面讲重点,中断级任务调度:

    当任务在运行的时候,执行完中断服务程序后,uCOS应该在任务列表中寻找已经就绪状态的任务(这些任务可能是因为调用等待资源或者调用OSTimeDly等函数进入等待状态,当资源已经具备或者OSTimeDlg时间超时),寻找最高优先级的任务执行。因此,在中断处理函数中要加入相应的代码,uCOS一般不提供中断注册的函数,中断服务程序需要实现的代码如下伪代码描述:

      void isr_task()

{

     保存全部CPU寄存器; (1)
   调用OSIntEnter或OSIntNesting直接加1; (2)
   执行用户代码做中断服务; (3)
   调用OSIntExit(); (4)
 

}

3. 《嵌入式实时操作系统uCOS-II原理及应用》第3章中了很多这样一些(1)(2)代码,(3)中的是其他地方看到的:

   (1) PC_Vectset(0x08, OSTickISR);  //安装时钟服务程序, 完成各任务中执行了OSTimeDly等函数的时间到期提供处理程序

   (2)  PC_SetTickRate(OS_TICKS_PER_SEC);   //   设置时钟频率,需要根据CPU,写汇编程序完成定时器的寄存器初始化工作

 (3)   PC_Vectset(0x80, OSCtxSw);  // 安装软件中断时候的任务切换工作,因为OSCtxSW重要执行寄存器的操作,常常使用进入软件中断模式特权模式。


(1): 跟踪了一下PC_VectSet(0x08, OSTickISR)发现,这个函数为:

void PC_VectSet (INT8U vect, void (*isr)(void))
{
#if OS_CRITICAL_METHOD == 3                      /* Allocate storage for CPU status register           */
    OS_CPU_SR  cpu_sr;
#endif    
    INT16U    *pvect;
    
    
    pvect    = (INT16U *)MK_FP(0x0000, vect * 4);     /* Point into IVT at desired vector location     */
    OS_ENTER_CRITICAL();
    *pvect++ = (INT16U)FP_OFF(isr);                   /* Store ISR offset                              */
    *pvect   = (INT16U)FP_SEG(isr);                   /* Store ISR segment                             */
    OS_EXIT_CRITICAL();
}

        分析了一下,该函数只适合于80X86 CPU,因为 8 *4 = 32,正好为80X86定时器0的中断向量入口地址,和ARM不一样,Arm跳转后是执行这里的指令,而X86是执行该处32位值所指向地址的指令,因此,该函数不适合与其他CPU,如果要使用需更改。

      另外,OSTickISR在移植时,一般需要在OS_CPU_A.asm中根据CPU不同用汇编实现,完成的功能主要有:

              void  OSTickISR(void)

{

       保存全部CPU寄存器; (1)
     调用OSIntEnter或OSIntNesting直接加1; (2)

     if(OSintNesting == 1)

{

  OSTCBCur->OSStkPtr = SP;    //在任务TCB中保存堆栈指针  (3)

}   

       调用OnTimeTick() ;   调用节拍处理函数该函数在OS_CORE.C中已经实现了,不需要自己实现(注意:与OS_CORE.C中同一级目录中的所有源程序都与CPU无关),节拍处理函数中自动完成了(1) OSTime ++; (2) 遍历任务控制块链表中所有控制块,把用来存放任务延时时限的OSTCBDly减1,如果OSTCBDly == 0唤醒suspend等等待线程,将其转为就绪态                                (4)
     调用OSIntExit(); (4)
    恢复所有CPU寄存器; (5)
     执行中断返回指令; (6)

}

再论,因为OS_CORE.C中OnTimeTick函数有如下一段代码:


void  OSTimeTick (void)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr;
#endif    
    OS_TCB    *ptcb;


    OSTimeTickHook();   // 在OS_CPU_C.C定义,该文件不在SOURCE自带的目录下,需要自己创建钩子函数
。。。。

}

在uCOS-II \ Ix86L \ BC45 \ OS_CPU_S.C文件中定义如下,可以自己定义钩子函数

#if OS_CPU_HOOKS_EN > 0
void  OSTimeTickHook (void)
{
}
#endif

(2):PC_SetTickRate(OS_TICKS_PER_SEC);  用户设置时钟频率,需要根据CPU,写汇编程序完成定时器的寄存器初始化工作。

(3): PC_Vectset(0x80, OSCtxSw);  用于任务调度切换的注册,80为X86 CPU的软件中断入口,如果是ARM,需要相应的改变,同时OSCtxSW需要在OS_CPU.A.ASM中根据处理器来操作寄存器进行实现。 OSCTxSW完成的任务有(以X86的这个例子为例,其他的类似):

               <1>把当前CPU软件中断前的PC指针、PSW(程序状态字)保存到当前任务控制块的栈中,压栈操作(与<6>相对应);

               <2>把当前CPU各寄存器压栈保存到当前任务控制块的栈中,压栈操作。(与<5>对应)

              <3> 在任务链中取得优先级最高,且就绪的任务控制块,复制到OSTCBCur中;

              <4> 把该任务优先级别赋值给OSPrioCur中;

              <5> 得到OSTCBCur中栈中所保存的上一次任务切换时CPU寄存器数据,恢复到相应的寄存器中,此过程出栈数据顺序与2>中正好相反,且对应

              <6>中断返回,读出OSTCBCur中栈中所保存PC指针数据和程序状态字PSW,此过程出栈数据顺序与1>中正好相反,且对应

4.    2中讲述的 在UCOS中断处理程序中,OSIntExit (OS_CORE.C中)调用的时候会触发 OSIntCtxSw()的调用,我们一般需要在OS_CPU_A.asm中实现针对具体CPU的汇编代码,完成如下中断任务级的调度,需要完成的和上面的OSCtxSw类似,不过少了(1)(2)动作,因为在中断的前部分已经执行了。


5. 要想移植一个实时操作系统,3中描述的PC_VectSet(0x08, OSTickISR)、PC_SetTickRate(OS_TICKS_PER_SEC)涉及到的时钟的几个函数需要针对CPU来具体实现,包括PC_SetTickRate中的时钟源和中断相关寄存器的设置, PC_VectSet针对不同的CPU的C语言实现,这里可以做一个通用的中断注册函数,当然写成汇编,在引导程序.s中也可以做,不过比较麻烦,另外,OSTickISR的具体实现,需要实现什么上面都有介绍。为了完成任务级的调度,还得实现ON_TASK_SW宏(默认在OS_CPU.h中定义),通常采用软件中断的方式,实现OSCtxSW在OS_CPU_A.asm的汇编代码,如果不是,改掉相应地方的代码。为了实现硬件中断级调度,在中断处理程序中所有的地方都需要按照      void isr_task()中的风格来做,同时需要加入通用的OSIntCtwSw在OS_CPU.asm中的汇编代码。

6. 因此,要想移植一个最简单的UCOS,需要在OS_CPU_A.asm中实现:

                      <1> OSCtxSw()

                      <2> OSIntCtxSw()

                      <3> OSTickISR() 以及针对该函数的时钟初始化

                      <4> OSStartHighRdy;本节没有介绍,主要在OSStart函数中被调用,应该实现如下的功能:

 (1): 并关闭IRQ,FIQ中断

 (2): 调用OSTaskSwHook(),这是钩子函数,一般为空,用户可以视需要自己添加内容。

 (4):把OSRunning标志置1。OSRunning是UCOS系统己经启动的一个标识,它在调用UCOS中的OSStart时被置为1。

 (5):SP指向最高优先级任务的堆栈。

(6):把最高优先级任务堆栈的内容保存到寄存器中

(7):开始执行最高优先级任务。

         <4> 在其他地方,如OS_CPU_C.C 中,需要实现 OSTaskStkInit函数,完成任务的堆栈初始化函数;类似;

OS_STK  *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
    INT16U *stk;


    opt    = opt;                           /* 'opt' is not used, prevent warning                      */
    stk    = (INT16U *)ptos;                /* Load stack pointer                                      */
    *stk-- = (INT16U)FP_SEG(pdata);         /* Simulate call to function with argument                 */
    *stk-- = (INT16U)FP_OFF(pdata);         
    *stk-- = (INT16U)FP_SEG(task);
    *stk-- = (INT16U)FP_OFF(task);
    *stk-- = (INT16U)0x0202;                /* SW = Interrupts enabled                                 */
    *stk-- = (INT16U)FP_SEG(task);          /* Put pointer to task   on top of stack                   */
    *stk-- = (INT16U)FP_OFF(task);
    *stk-- = (INT16U)0xAAAA;                /* AX = 0xAAAA                                             */
    *stk-- = (INT16U)0xCCCC;                /* CX = 0xCCCC                                             */
    *stk-- = (INT16U)0xDDDD;                /* DX = 0xDDDD                                             */
    *stk-- = (INT16U)0xBBBB;                /* BX = 0xBBBB                                             */
    *stk-- = (INT16U)0x0000;                /* SP = 0x0000                                             */
    *stk-- = (INT16U)0x1111;                /* BP = 0x1111                                             */
    *stk-- = (INT16U)0x2222;                /* SI = 0x2222                                             */
    *stk-- = (INT16U)0x3333;                /* DI = 0x3333                                             */
    *stk-- = (INT16U)0x4444;                /* ES = 0x4444                                             */
    *stk   = _DS;                           /* DS = Current value of DS                                */
    return ((OS_STK *)stk);
}

            
7.以上主要涉及到中断部分,还有一些函数需要具体实现,例如X86例子中的OS_ENTER_CRITICAL()

ON_EXIT_CRITICAL()开中断与关中断的实现


                     




你可能感兴趣的:(UCOS-II 中断与时钟问题总结(以UCOS2.25自带的X86例子))