ucos2源码阅读之主要函数功能分析

来源:互联网 发布:打简谱的软件 编辑:程序博客网 时间:2024/06/06 02:24

     最近用了一周时间,将ucos2.00 for 51的源码阅读了一遍。ucos2源码不长,3000多行,90%使用C写的,其余部分汇编编写,但为了读懂它还是不容易的。为了读懂它,我网上阅读了很多博客,查阅了一些51单片机的教程,还有keil C安装目录下的帮助文档。关于其在51单片机上的移植大家可以阅读杨屹《uCOS51移植心得》下载地址http://www.360doc.com/content/11/1130/18/7503466_168689698.shtml。总之,要读懂ucos2,除了要学习c和汇编,还要懂混合编程,中断处理,堆栈,编译器,可重入函数的知识。本人的测试环境为keilC+proteus7.7+ucos2.00 for 51,采用的完整工程源码下载地址:http://download.csdn.net/detail/hb2008hahaha/7297475。先给个简单的例子:

//可用keil直接仿真
//可用proteus仿真观察串口输出 ,晶振11.0592M,波特9600
#include "../ucos2/includes.h"
#include<stdio.h>
void TaskStartyya(void *yydata) reentrant;
void TaskStartyyb(void *yydata) reentrant;
void com_init(INT16U baud);
OS_STK TaskStartStkyya[MaxStkSize];//任务a堆栈空间
OS_STK TaskStartStkyyb[MaxStkSize];//任务b堆栈空间


void main(void)
{
    OSInit();    //操作系统初始化
    InitTimer0();  //定时器初始化
    com_init(9600);//串口初始化
    OSTaskCreate(TaskStartyya, (void *)0, &TaskStartStkyya[0],2);//创建第一个任务
    OSTaskCreate(TaskStartyyb, (void *)0, &TaskStartStkyyb[0],3);//创建第二个任务
    puts("There are 2 tasks");//输出提示信息
    OSStart();//多任务启动
 
}

void TaskStartyya(void *yydata) reentrant
{
  
yydata=yydata;
    printf("task1 begin\n");
    for(;;){
       OSTimeDly(OS_TICKS_PER_SEC);  //延2s
    printf("  task1 is running\n");
           
    }    
}

void TaskStartyyb(void *yydata) reentrant
{

yydata=yydata; 
   
    printf("task2 begin\n");
    for(;;){
        OSTimeDly(OS_TICKS_PER_SEC);  
printf("  TASK2 IS RUNNING\n");  
    }    
}

void com_init(INT16U baud)
{
  #define fosc 11059200
  SCON = 0x50;    //串口工作方式为1,串行允许接受   
  TMOD&=0x0f;
  TMOD|=0x20; //定时器1工作在方式2 
  if(baud<=9600) TH1=256-fosc/32/12/baud;
  else
   {PCON = 0x80;   //SMOD = 1; 波特率加倍
   TH1=256-fosc/32/12/(baud/2);
}
    TR1  = 1;            //允许定时器1工作
 // ES   = 1; //开串口中断,必须注释掉该句puts才能输出
  EA   = 1;             //开总中断
  TI=1;//要加上此句,才能用puts输出
}

这个程序的功能很简单,就是创建两个任务,两个任务每隔一段时间向屏幕输出字符串。在keil4调试状态下运行结果为:


这个程序本身也会很简单,通过分析main函数,分为操作系统初始化、相关硬件初始化、创建任务、多任务运行与调度四步。

一、操作系统初始化

我们在OS_CORE.C中找到OSInit的定义如下:

void OSInit (void) reentrant{    INT16U i;//系统变量初始化    OSTime        = 0L;                                    /* Clear the 32-bit system clock            */    OSIntNesting  = 0;                                     /* Clear the interrupt nesting counter      */    OSLockNesting = 0;                                     /* Clear the scheduling lock counter        */#if OS_TASK_CREATE_EN  || OS_TASK_CREATE_EXT_EN || OS_TASK_DEL_EN    OSTaskCtr     = 0;                                     /* Clear the number of tasks                */#endif    OSRunning     = FALSE;                                 /* Indicate that multitasking not started   */    OSIdleCtr     = 0L;                                    /* Clear the 32-bit idle counter            */#if OS_TASK_STAT_EN && OS_TASK_CREATE_EXT_EN    OSIdleCtrRun  = 0L;    OSIdleCtrMax  = 0L;    OSStatRdy     = FALSE;                                 /* Statistic task is not ready              */#endif    OSCtxSwCtr    = 0;                                     /* Clear the context switch counter         */    OSRdyGrp      = 0;                                     /* Clear the ready list                     */    for (i = 0; i < OS_RDY_TBL_SIZE; i++) {        OSRdyTbl[i] = 0;    }                    OSPrioCur     = 0;    OSPrioHighRdy = 0;                                               OSTCBHighRdy  = (OS_TCB *)0;                                 /* TCB Initialization                 */    OSTCBCur      = (OS_TCB *)0;    OSTCBList     = (OS_TCB *)0;    for (i = 0; i < (OS_LOWEST_PRIO + 1); i++) {                 /* Clear the priority table           */        OSTCBPrioTbl[i] = (OS_TCB *)0;    }    for (i = 0; i < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1); i++) {  /* Init. list of free TCBs            */        OSTCBTbl[i].OSTCBNext = &OSTCBTbl[i + 1];    }    OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS - 1].OSTCBNext = (OS_TCB *)0;    /* Last OS_TCB             */    OSTCBFreeList                                         = &OSTCBTbl[0];//系统其他功能初始化,如消息队列、邮箱、信号量、内存管理等,均为可不选。#if OS_MAX_EVENTS >= 2    for (i = 0; i < (OS_MAX_EVENTS - 1); i++) {            /* Init. list of free EVENT control blocks  */        OSEventTbl[i].OSEventPtr = (OS_EVENT *)&OSEventTbl[i + 1];    }    OSEventTbl[OS_MAX_EVENTS - 1].OSEventPtr = (OS_EVENT *)0;    OSEventFreeList                          = &OSEventTbl[0];    #endif#if OS_Q_EN && (OS_MAX_QS >= 2)    OSQInit();                                             /* Initialize the message queue structures  */#endif#if OS_MEM_EN && OS_MAX_MEM_PART >= 2    OSMemInit();                                           /* Initialize the memory manager            */#endif    //创建空闲任务#if OS_STK_GROWTH == 1    #if OS_TASK_CREATE_EXT_EN    OSTaskCreateExt(OSTaskIdle,                     (void *)0,                                 /* No arguments passed to OSTaskIdle()  */                    &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1], /* Set Top-Of-Stack                     */                    OS_IDLE_PRIO,                              /* Lowest priority level                */                    OS_TASK_IDLE_ID,                    &OSTaskIdleStk[0],                         /* Set Bottom-Of-Stack                  */                    OS_TASK_IDLE_STK_SIZE,                     (void *)0,                                 /* No TCB extension                     */                    OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack  */    #else    OSTaskCreate(OSTaskIdle, (void *)0, &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1], OS_IDLE_PRIO);    #endif#else    #if OS_TASK_CREATE_EXT_EN    OSTaskCreateExt(OSTaskIdle,                     (void *)0,                                 /* No arguments passed to OSTaskIdle()  */                    &OSTaskIdleStk[0],                         /* Set Top-Of-Stack                     */                    OS_IDLE_PRIO,                              /* Lowest priority level                */                    OS_TASK_IDLE_ID,                    &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1], /* Set Bottom-Of-Stack                  */                    OS_TASK_IDLE_STK_SIZE,                     (void *)0,                                 /* No TCB extension                     */                    OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack  */    #else    OSTaskCreate(OSTaskIdle, (void *)0, &OSTaskIdleStk[0], OS_IDLE_PRIO);    #endif#endif//创建统计任务(可不选)#if OS_TASK_STAT_EN     #if OS_TASK_CREATE_EXT_EN        #if OS_STK_GROWTH == 1        OSTaskCreateExt(OSTaskStat,                         (void *)0,                                /* No args passed to OSTaskStat()    */                        &OSTaskStatStk[OS_TASK_STAT_STK_SIZE - 1],/* Set Top-Of-Stack                  */                        OS_STAT_PRIO,                             /* One higher than the idle task     */                        OS_TASK_STAT_ID,                        &OSTaskStatStk[0],                        /* Set Bottom-Of-Stack               */                        OS_TASK_STAT_STK_SIZE,                         (void *)0,                                /* No TCB extension                  */                        OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);  /* Enable stack checking + clear  */        #else        OSTaskCreateExt(OSTaskStat,                         (void *)0,                                /* No args passed to OSTaskStat()    */                        &OSTaskStatStk[0],                        /* Set Top-Of-Stack                  */                        OS_STAT_PRIO,                             /* One higher than the idle task     */                        OS_TASK_STAT_ID,                        &OSTaskStatStk[OS_TASK_STAT_STK_SIZE - 1],/* Set Bottom-Of-Stack               */                        OS_TASK_STAT_STK_SIZE,                         (void *)0,                                /* No TCB extension                  */                        OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);  /* Enable stack checking + clear  */        #endif    #else        #if OS_STK_GROWTH == 1        OSTaskCreate(OSTaskStat,                      (void *)0,                                   /* No args passed to OSTaskStat()    */                     &OSTaskStatStk[OS_TASK_STAT_STK_SIZE - 1],   /* Set Top-Of-Stack                  */                     OS_STAT_PRIO);                               /* One higher than the idle task     */        #else        OSTaskCreate(OSTaskStat,                      (void *)0,                                   /* No args passed to OSTaskStat()    */                     &OSTaskStatStk[0],                           /* Set Top-Of-Stack                  */                     OS_STAT_PRIO);                               /* One higher than the idle task     */        #endif    #endif#endif



初步一看代码很长,其实OSInit的功能分为下面三个:

1.系统变量初始化

1)状态变量清零

将系统时间OSTime、中断计数OSIntNesting、调度锁计数任务计数OSTaskCtr、空闲计数OSIdleCtr、系统运行状态标志OSRunning等清零。

2)控制块变量初始化

ucos2用任务控制块OS_TCB来描述每个任务。与任务控制块相关的变量有:

OS_EXT idata OS_TCB   *OSTCBCur;                        /* Pointer to currently running TCB              */
OS_EXT  OS_TCB        *OSTCBFreeList;                   /* Pointer to list of free TCBs                  */
OS_EXT idata OS_TCB   *OSTCBHighRdy;                    /* Pointer to highest priority TCB ready to run  */
OS_EXT  OS_TCB        *OSTCBList;                       /* Pointer to doubly linked list of TCBs         */
OS_EXT  OS_TCB        *OSTCBPrioTbl[OS_LOWEST_PRIO + 1];/* Table of pointers to created TCBs

上述变量在ucosii.h中定义,都是指针或指针数组类型,因为所有的c文件都包含该头文件。所以上述变量为全局变量。在OSCore.c中有数组定义如下:static  OS_TCB       OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];  

OSTCBTbl才是真正保存所有进程控制板信息的地方,上述的指针最终直接或间接的指向OSTCBTbl。
OSInit初始化时会将OSTCB连接成一个单链表,末端指向0,然后其他任务控制块指针清0;

3)优先级变量初始化

优先级相关的全局变量有:

OS_EXT idata INT8U   OSPrioCur;                /* Priority of current task                             */
OS_EXT idata INT8U   OSPrioHighRdy;            /* Priority of highest priority task                    */

OSPrioCur记录当前正在运行的任务的优先级,OSPrioHighRdy表示就绪表中优先级最高的任务优先级。OSInit执行时上述变量都会清零。

4)就绪表变量初始化

OS_EXT  INT8U        OSRdyGrp;                        /* Ready list group                              */
OS_EXT  INT8U        OSRdyTbl[OS_RDY_TBL_SIZE];       /* Table of tasks which are ready to run 

这两个变量用于记录就绪的任务。关于就绪表的应用可以说是ucos2的一大特色之一,除了记录哪些任务进入就绪态,还能快速计算出当前优先级最高的任务优先级。OSInit执行时上述变量都会清零。

 2.系统其他功能初始化

指从#if OS_MAX_EVENTS >= 2  到#if OS_STK_GROWTH == 1这段代码。这段代码主要用于控制系统的可选功能如:消息队列、邮箱、信号量、内存管理等的初始化。都可以修改os_cfg.h中相应的宏开启或关闭。

3.创建空闲和统计任务

系统会创建空闲任务OSTaskIdle,这个任务功能优先级最低,用于统计CPU的使用情况。代码如下:

void OSTaskIdle (void *ppdata) reentrant
{
    ppdata = ppdata;                               /* Prevent compiler warning for not using 'pdata'     */
    for (;;) {
        OS_ENTER_CRITICAL();
        OSIdleCtr++;
        OS_EXIT_CRITICAL();
    }

空闲任务制作一件事,就是给OSIdleCtr加1.当系统没有创建其他任务时,系统一直执行该任务, OSIdleCtr就会增加很快。而系统有其他任务时,OSTaskIdle由于优先级最低,就会被剥夺CPU使用权。其他任务负载越重,OSTaskIdle获得执行的机会就越少。因此通过计算OSIdelCtr在一定时间内的增加量便可算出CPU的使用率。
接着创建统计任务OSTaskStat。该任务用于计算CPU的使用率,该任务可以选择性的创建。

二、相关硬件初始化

1.定时器初始化

执行InitTimer0()会对定时器T0进行初始化,代码如下:

void InitTimer0(void) reentrant
{
    TMOD=TMOD&0xF0;
    TMOD=TMOD|0x01;    //模式1(16位定时器),仅受TR0控制
    TH0=0x70;    //定义Tick=50次/秒(即0.02秒/次) 22.1184M,定时20ms
    TL0=0x00;    //OS_CPU_A.ASM  和  OS_TICKS_PER_SEC
    ET0=1;       //允许T0中断
    TR0=1;  
}

上述的意思是设置定时器T0每隔20ms中断一次。定时器是多任务的基石,没有定时器的中断是不可能完成多任务之间的调度的。

2.串口初始化

 执行com_init(9600)会对串口进行初始化,并设置波特率。设置好后我们便可调用printf在串口上输出信息,发便调试。

三、任务创建

INT8U OSTaskCreate (void (*task)(void *pd), void *ppdata, OS_STK *ptos, INT8U prio) reentrant{    void   *psp;    INT8U   err;    if (prio > OS_LOWEST_PRIO) {             /* 判断优先级是否有效           */        return (OS_PRIO_INVALID);    }    OS_ENTER_CRITICAL();    if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* 确保该优先级的任务未被创建  */        OSTCBPrioTbl[prio] = (OS_TCB *)1;    /* Reserve the priority to prevent others from doing ...  */                                             /* ... the same thing until task is created.              */        OS_EXIT_CRITICAL();        psp = (void *)OSTaskStkInit(task, ppdata, ptos, 0); /* 初始化任务堆栈            */        err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0);  //初始化任务控制块               if (err == OS_NO_ERR) {            OS_ENTER_CRITICAL();            OSTaskCtr++;                                   /* Increment the #tasks counter             */            OSTaskCreateHook(OSTCBPrioTbl[prio]);          /* Call user defined hook                   */            OS_EXIT_CRITICAL();            if (OSRunning) {                 /* 判断多任务是否已运行 */                OSSched();   //如果已运行,例如进入调度            }        } else {            OS_ENTER_CRITICAL();            OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others                 */            OS_EXIT_CRITICAL();        }        return (err);    } else {        OS_EXIT_CRITICAL();        return (OS_PRIO_EXIST);    }} 
先判断将优先级与系统定义的最低优先级进行比较,判断是否有效。然后通过数组OSTCBPrioTbl[]的值判断该优先级的任务是否以创键,如果未创建,则创建任务。

创建任务时,先初始化堆栈,然后初始化任务控制块。最后根据OSRunning的值判断操作系统是否已运行,如果已运行,则调用 OSSched()进行任务调度。

下面重点讲解下堆栈初始化函数OSTaskStkInit和控制块初始化函数OSTCBInit。

1.堆栈初始化OSTaskStkInit

1)C51的可重入函数

C51中跟ANSI C不同,默认情况下函数内部定义的局部变量不是放在堆栈中,要加reentrant关键字才能将函数内部定义的局部变量保存到栈。关于可重入函数的详解请参照:http://blog.sina.com.cn/s/blog_633c761e01012ulf.html 。这个栈我们称之为模拟栈,用指针C_XBP表示栈指针(其他的还有C_IBP,C_PBP,这个与编译器设置的存储模式有关,具体参照http://masust.blog.163.com/blog/static/146954084201282473543891/)

2)模拟栈、系统栈

 到此为止,51单片机中会产生两个栈,一个是系统栈,指针为SP,主要用于保存系统保存和恢复寄存器的值,用push入栈和pop出栈;另一个是模拟栈C—XBP,用于保存函数运行时的局部变量。寄存器和局部变量的值都是任务切换时务必保存的,却保存在两个栈中, 如何将这两个栈统一起来呢,请参照下面的任务栈结够。

3)ucos2_51的任务栈结构

任务堆栈结构:

ucos2创建时指定一块内存区域作为用户栈,用户栈的下半部分保存系统栈SP的内容,上半部分直接作为模拟栈。

uco2创建任务时
            

2.控制块初始化函数OSTCBInit

通过阅读OSTCBInit的源码我们可以看出任务栈的变量入栈顺序:

void *OSTaskStkInit (void (*task)(void *pd), void *ppdata, void *ptos, INT16U opt) reentrant{        OS_STK *stk;    ppdata = ppdata;    opt    = opt;                               //opt没被用到,保留此语句防止告警产生        stk    = (OS_STK *)ptos;                    //用户堆栈最低有效地址    *stk++ = 15;                                //用户堆栈长度    *stk++ = (INT16U)task & 0xFF;               //任务地址低8位    *stk++ = (INT16U)task >> 8;                 //任务地址高8位        *stk++ = 0x00;                              //PSW    *stk++ = 0x0A;                              //ACC    *stk++ = 0x0B;                              //B    *stk++ = 0x00;                              //DPL    *stk++ = 0x00;                              //DPH    *stk++ = 0x00;                              //R0    //R3、R2、R1用于传递任务参数ppdata,其中R3代表存储器类型,R2为高字节偏移,R1为低字节位移。//通过分析KEIL汇编,了解到任务的void *ppdata参数恰好是用R3、R2、R1传递,不是通过虚拟堆栈。    *stk++ = (INT16U)ppdata & 0xFF;             //R1    *stk++ = (INT16U)ppdata >> 8;               //R2    *stk++ = 0x01;                              //R3  因为我用的全是XDATA,所以存储器类型固定为1,见C51.PDF第178页说明。    *stk++ = 0x04;                              //R4    *stk++ = 0x05;                              //R5    *stk++ = 0x06;                              //R6    *stk++ = 0x07;                              //R7                                                //不用保存SP,任务切换时根据用户堆栈长度计算得出。        *stk++ = (INT16U) (ptos+MaxStkSize) >> 8;   //?C_XBP 仿真堆栈指针高8位    *stk++ = (INT16U) (ptos+MaxStkSize) & 0xFF; //?C_XBP 仿真堆栈指针低8位            return ((void *)ptos);}

其中ptos指向我们定义的任务堆栈数组的首地址,(INT16U) (ptos+MaxStkSize)为任务栈的栈顶,也是模拟栈的C_XBP的栈底。当该任务对应的任务函数执行的时候,会先弹出寄存器变量,然后弹出任务指针C—XBP,接着该函数的局部变量就保存在该任务栈中了。


 

四、多任务运行与调度

源码分析

void OSStart (void) reentrant{    INT8U y;    INT8U x;    if (OSRunning == FALSE) {        y             = OSUnMapTbl[OSRdyGrp];        //查找就绪表中最高优先级的任务        x             = OSUnMapTbl[OSRdyTbl[y]];        OSPrioHighRdy = (INT8U)((y << 3) + x);        OSPrioCur     = OSPrioHighRdy;           //OSPrioCur保存任务的优先级        OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy]; //OSTCBHighRdy指向即将运行的最高优先级的任务        OSTCBCur      = OSTCBHighRdy;        OSStartHighRdy();                            /* 执行最高优先级的任务     */    }}
1)查表
当系统中有多个就绪的任务时,UCOS2直接通过两次查表和两次数学运算就计算出了当前优先级最高的任务,时间复杂度为O(1),算法非常精妙。大家可参考博客

http://www.360doc.com/content/11/0420/17/2379862_111068673.shtml 和http://blog.csdn.net/yuan1125/article/details/7585619中的详细介绍。

OSStartHighRdy()的定义在OS_CPU.ASM中,用汇编实现。
</pre><pre class="cpp" name="code">2)任务切换
</pre><pre class="cpp" name="code"><1>保存任务堆栈
<2>从当前优先级最高的任务中恢复堆栈


0 0
原创粉丝点击