手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务(一)

时间:2021-11-23 20:05:46

 

  做过软件的同学都知道,任何一个程序都必须要有初始化的过程,在初始化过程中,我们会对外围硬件以及CPU的奔跑环境进行初步的设置,以便接下来的使用和调度。

  以前在写单片机逻辑程序之时,系统初始化过程大概分为两种:

  ①外围硬件的初始化(比如MCU寄存器,时钟,看门狗,串口,IO口,SPI等等)

  ②代码内参数的初始化(比如堆栈,变量,结构体等等)

  UCOSII操作系统想要跑起来,当然也需要一系列的初始化,比如中断模式、延时模块、外围硬件等等,但本文不讲硬件相关,只对操作系统本身的初始化进行一些讲解。

  首先请看一段熟悉的代码:

 1 #include "sys.h"
 2 #include "delay.h"
 3 #include "led.h"
 4 #include "includes.h"
 5 
 6 
 7 /////////////////////////UCOSII任务设置///////////////////////////////////
 8 //START 任务
 9 //设置任务优先级
10 #define START_TASK_PRIO                 10 //开始任务的优先级设置为最低
11 //设置任务堆栈大小
12 #define START_STK_SIZE                  64
13 //任务堆栈
14 OS_STK START_TASK_STK[START_STK_SIZE];
15 //任务函数
16 void start_task(void *pdata);
17 
18 //LED0任务
19 //设置任务优先级
20 #define LED0_TASK_PRIO                  7
21 //设置任务堆栈大小
22 #define LED0_STK_SIZE                   64
23 //任务堆栈
24 OS_STK LED0_TASK_STK[LED0_STK_SIZE];
25 //任务函数
26 void led0_task(void *pdata);
27 
28 
29 //LED1任务
30 //设置任务优先级
31 #define LED1_TASK_PRIO                  6
32 //设置任务堆栈大小
33 #define LED1_STK_SIZE                   64
34 //任务堆栈
35 OS_STK LED1_TASK_STK[LED1_STK_SIZE];
36 //任务函数
37 void led1_task(void *pdata);
38 
39 int main(void)
40 {
41     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
42     delay_init();       //延时函数初始化
43     LED_Init();         //初始化与LED连接的硬件接口
44     OSInit();
45     OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
46     OSStart();
47 }
48 
49 //开始任务
50 void start_task(void *pdata)
51 {
52     OS_CPU_SR cpu_sr=0;
53     pdata = pdata;
54     OS_ENTER_CRITICAL();            //进入临界区(无法被中断打断)
55     OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO);
56     OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO);
57     OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
58     OS_EXIT_CRITICAL();             //退出临界区(可以被中断打断)
59 }
60 
61 //LED0任务
62 void led0_task(void *pdata)
63 {
64     while(1)
65     {
66         LED0=0;
67         delay_ms(80);
68         LED0=1;
69         delay_ms(920);
70     };
71 }
72 
73 //LED1任务
74 void led1_task(void *pdata)
75 {
76     while(1)
77     {
78         LED1=0;
79         delay_ms(300);
80         LED1=1;
81         delay_ms(300);
82     };
83 }

  以上的代码大家都不陌生,这几乎便是UCOSII系统初始化的标准格式,首先是定义任务的基本信息,优先级,堆栈大小,堆栈空间,任务函数等等。

  然后由main函数开始执行具体的初始化过程,分别是中断模式设定,延时功能设定,以及外围硬件设定,等这些东西都设定完成以后,便进入了操作系统的设定,最后起始任务执行完毕,程序会跳进应用任务中执行。

  而main函数中的OSInit()这个函数便是我们本次讲解的重点。

  UCOSII操作系统在初始化的过程中,到底做了一些什么?或者说函数OSInit()中到底有些什么处理?

  废话不多说,直接进入这个函数的定义:

 1 void  OSInit (void)
 2 {
 3     OSInitHookBegin();                                           /* Call port specific initialization code   */
 4 
 5     OS_InitMisc();                                               /* Initialize miscellaneous variables       */
 6 
 7     OS_InitRdyList();                                            /* Initialize the Ready List                */
 8 
 9     OS_InitTCBList();                                            /* Initialize the free list of OS_TCBs      */
10 
11     OS_InitEventList();                                          /* Initialize the free list of OS_EVENTs    */
12 
13 #if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
14     OS_FlagInit();                                               /* Initialize the event flag structures     */
15 #endif
16 
17 #if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
18     OS_MemInit();                                                /* Initialize the memory manager            */
19 #endif
20 
21 #if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
22     OS_QInit();                                                  /* Initialize the message queue structures  */
23 #endif
24 
25     OS_InitTaskIdle();                                           /* Create the Idle Task                     */
26 #if OS_TASK_STAT_EN > 0u
27     OS_InitTaskStat();                                           /* Create the Statistic Task                */
28 #endif
29 
30 #if OS_TMR_EN > 0u
31     OSTmr_Init();                                                /* Initialize the Timer Manager             */
32 #endif
33 
34     OSInitHookEnd();                                             /* Call port specific init. code            */
35 
36 #if OS_DEBUG_EN > 0u
37     OSDebugInit();
38 #endif
39 }

  以上便是系统初始化函数中的处理,看得出来,它只是一个接口,一个容器,真正的处理还在它内部调用的那些函数里,接下来我们开始一句一句的理解。(简单的函数用黑色,复杂的函数用红色)

 

    一:函数OSInitHookBegin(); 

  其定义如下:

1 #if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
2 void  OSInitHookBegin (void)
3 {
4 #if OS_TMR_EN > 0
5     OSTmrCtr = 0;
6 #endif
7 }
8 #endif

 

     这个函数俗称钩子函数,它里面本身不带任何处理,是专门留给用户扩展的,当用户需要在初始化里执行某些处理的时候,可以在这里把自己的代码添加进去。

     比如我想在初始化时点亮一个LED小灯或者让喇叭叫起来,那么就可以在里面写个点灯的代码(注意这个函数不能被意外打断)。

     我们可以看到在它的头上有两个宏开关,只有都满足才会执行,第一个开关OS_CPU_HOOKS_EN是使能位,如果我们不需要在这里执行任何处理,可以直接把OS_CPU_HOOKS_EN宏定位为0(不建议定义为0,因为编译会出问题,需要修改的地方不少)。

1 #define OS_CPU_HOOKS_EN              0u                /* uC/OS-II hooks are found in the processor port files         */

  第二个宏开关是系统版本,只有系统在203版本以上才能用这个功能。

1 #define  OS_VERSION                 291u                /* Version of uC/OS-II (Vx.yy mult. by 100)    */

我现在的版本是291,所以当然没问题啦!

 

  二:函数OS_InitMisc()  

  其定义如下:

 1 static  void  OS_InitMisc (void)
 2 {
 3 #if OS_TIME_GET_SET_EN > 0u
 4     OSTime                    = 0uL;                       /* Clear the 32-bit system clock            */
 5 #endif
 6 
 7     OSIntNesting              = 0u;                        /* Clear the interrupt nesting counter      */
 8     OSLockNesting             = 0u;                        /* Clear the scheduling lock counter        */
 9 
10     OSTaskCtr                 = 0u;                        /* Clear the number of tasks                */
11 
12     OSRunning                 = OS_FALSE;                  /* Indicate that multitasking not started   */
13 
14     OSCtxSwCtr                = 0u;                        /* Clear the context switch counter         */
15     OSIdleCtr                 = 0uL;                       /* Clear the 32-bit idle counter            */
16 
17 #if OS_TASK_STAT_EN > 0u
18     OSIdleCtrRun              = 0uL;
19     OSIdleCtrMax              = 0uL;
20     OSStatRdy                 = OS_FALSE;                  /* Statistic task is not ready              */
21 #endif
22 
23 #ifdef OS_SAFETY_CRITICAL_IEC61508
24     OSSafetyCriticalStartFlag = OS_FALSE;                  /* Still allow creation of objects          */
25 #endif
26 }

  这个函数的作用,是对系统中的某些全局变量进行初始化:

  OSTime是系统中滴答时钟的存储变量,如果想要使用这个变量的话,那么宏开关OS_TIME_GET_SET_EN必须要设置为1。

  ※这个变量还是挺有用的,比如当我需要判断系统时间是否经过了50ms,那么就可以调用OSTimeGet()函数读一下这个变量,然后过一会儿再读一下,只要两次读数相差达到50,那就是经过了50ms,这样就不用专门再开一个定时器了。

  OSIntNesting是中断嵌套计数变量,进中断时加1,出中断时减1,当变量为0的时候表示没有进入过中断,当等于1的时候表示进入了1重中断,当等于2的时候表示进入了2重中断……以此类推。

  OSLockNesting是调度器上锁次数变量,当调用函数OSSchedLock()便会对这个变量进行加1处理,当调用函数OSSchedUnlock()便会对它减1处理,只有在变量OSLockNesting等于0时,系统才会进行任务调度(当执行某些不能被别的任务打断的处理时,可以用这个功能)。

  OSTaskCtr是记录系统中一共有多少个任务,比如我上面的那段代码自己创建了3个任务,那等任务建立完毕以后,这个变量至少是大于3了,由于UCOSII系统还保留了一些系统任务(空闲任务,统计任务等),所以这个变量肯定比3大。

       OSRunning是记录操作系统当前的状态,操作系统跑起来是TRUE,没跑起来是FALSE,现在还在初始化,肯定是FALSE。

  OSCtxSwCtr是记录任务切换的次数,当从优先级0的任务切换到优先级1的任务之时,它就会加1,在切换回来,它又会加1,等加到极限以后,重新变成0。

  OSIdleCtr是记录空闲任务执行的次数,每执行一次空闲任务,它就加1,这个变量可以配合统计任务来计算CPU的使用率。

 

1 #if OS_TASK_STAT_EN > 0u
2     OSIdleCtrRun              = 0uL;
3     OSIdleCtrMax              = 0uL;
4     OSStatRdy                 = OS_FALSE;                  /* Statistic task is not ready              */
5 #endif

  这三句代码有宏开关,和统计任务相关,专门用来计算CPU的使用率,如果不需要这个数据,直接把宏开关设0便可。

1 #ifdef OS_SAFETY_CRITICAL_IEC61508
2     OSSafetyCriticalStartFlag = OS_FALSE;                  /* Still allow creation of objects          */
3 #endif

  这段代码我没有找到是用来做什么的,如果有哪位同学了解,还希望不吝赐教。

  三:函数OS_InitRdyList()

  其定义如下:

 1 static  void  OS_InitRdyList (void)
 2 {
 3     INT8U  i;
 4 
 5 
 6     OSRdyGrp      = 0u;                                    /* Clear the ready list                     */
 7     for (i = 0u; i < OS_RDY_TBL_SIZE; i++) {
 8         OSRdyTbl[i] = 0u;
 9     }
10 
11     OSPrioCur     = 0u;
12     OSPrioHighRdy = 0u;
13 
14     OSTCBHighRdy  = (OS_TCB *)0;
15     OSTCBCur      = (OS_TCB *)0;
16 }

  该函数的作用是用来初始化任务、以及任务优先级相关的变量。

  OSRdyGrp 是记录当前所有任务组的就绪状态,是任务调度算法的重要变量。

  OSRdyTbl[]是记录当前所有任务的就绪状态,是任务调度算法的重要变量。

  OSPrioCur是记录当前正在执行的任务。

  OSPrioHighRdy是记录当前就绪任务中,优先级最高的那个任务的优先级。

  OSTCBHighRdy这是一个结构体,里面记录优先级最高的那个任务的信息,初始化时没有任务执行,其值为0。

  OSTCBCur也是一个结构体,里面记录当前正在执行的那个任务的信息,初始化时没有任务执行,其值为0。

  

  四:函数OS_InitTCBList()

  定义如下:

 1 static  void  OS_InitTCBList (void)
 2 {
 3     INT8U    ix;
 4     INT8U    ix_next;
 5     OS_TCB  *ptcb1;
 6     OS_TCB  *ptcb2;
 7 
 8 
 9     OS_MemClr((INT8U *)&OSTCBTbl[0],     sizeof(OSTCBTbl));      /* Clear all the TCBs                 */
10     OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl));  /* Clear the priority table           */
11     for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++) {    /* Init. list of free TCBs     */
12         ix_next =  ix + 1u;
13         ptcb1   = &OSTCBTbl[ix];
14         ptcb2   = &OSTCBTbl[ix_next];
15         ptcb1->OSTCBNext = ptcb2;
16 #if OS_TASK_NAME_EN > 0u
17         ptcb1->OSTCBTaskName = (INT8U *)(void *)"?";             /* Unknown name                       */
18 #endif
19     }
20     ptcb1                   = &OSTCBTbl[ix];
21     ptcb1->OSTCBNext        = (OS_TCB *)0;                       /* Last OS_TCB                        */
22 #if OS_TASK_NAME_EN > 0u
23     ptcb1->OSTCBTaskName    = (INT8U *)(void *)"?";              /* Unknown name                       */
24 #endif
25     OSTCBList               = (OS_TCB *)0;                       /* TCB lists initializations          */
26     OSTCBFreeList           = &OSTCBTbl[0];
27 }

 

在讲解函数之前,首先看一下任务信息结构体的定义:

 1 typedef struct os_tcb {
 2     OS_STK          *OSTCBStkPtr;           /* Pointer to current top of stack                         */
 3 
 4 #if OS_TASK_CREATE_EXT_EN > 0u
 5     void            *OSTCBExtPtr;           /* Pointer to user definable data for TCB extension        */
 6     OS_STK          *OSTCBStkBottom;        /* Pointer to bottom of stack                              */
 7     INT32U           OSTCBStkSize;          /* Size of task stack (in number of stack elements)        */
 8     INT16U           OSTCBOpt;              /* Task options as passed by OSTaskCreateExt()             */
 9     INT16U           OSTCBId;               /* Task ID (0..65535)                                      */
10 #endif
11 
12     struct os_tcb   *OSTCBNext;             /* Pointer to next     TCB in the TCB list                 */
13     struct os_tcb   *OSTCBPrev;             /* Pointer to previous TCB in the TCB list                 */
14 
15 #if (OS_EVENT_EN)
16     OS_EVENT        *OSTCBEventPtr;         /* Pointer to          event control block                 */
17 #endif
18 
19 #if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
20     OS_EVENT       **OSTCBEventMultiPtr;    /* Pointer to multiple event control blocks                */
21 #endif
22 
23 #if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
24     void            *OSTCBMsg;              /* Message received from OSMboxPost() or OSQPost()         */
25 #endif
26 
27 #if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
28 #if OS_TASK_DEL_EN > 0u
29     OS_FLAG_NODE    *OSTCBFlagNode;         /* Pointer to event flag node                              */
30 #endif
31     OS_FLAGS         OSTCBFlagsRdy;         /* Event flags that made task ready to run                 */
32 #endif
33 
34     INT32U           OSTCBDly;              /* Nbr ticks to delay task or, timeout waiting for event   */
35     INT8U            OSTCBStat;             /* Task      status                                        */
36     INT8U            OSTCBStatPend;         /* Task PEND status                                        */
37     INT8U            OSTCBPrio;             /* Task priority (0 == highest)                            */
38 
39     INT8U            OSTCBX;                /* Bit position in group  corresponding to task priority   */
40     INT8U            OSTCBY;                /* Index into ready table corresponding to task priority   */
41     OS_PRIO          OSTCBBitX;             /* Bit mask to access bit position in ready table          */
42     OS_PRIO          OSTCBBitY;             /* Bit mask to access bit position in ready group          */
43 
44 #if OS_TASK_DEL_EN > 0u
45     INT8U            OSTCBDelReq;           /* Indicates whether a task needs to delete itself         */
46 #endif
47 
48 #if OS_TASK_PROFILE_EN > 0u
49     INT32U           OSTCBCtxSwCtr;         /* Number of time the task was switched in                 */
50     INT32U           OSTCBCyclesTot;        /* Total number of clock cycles the task has been running  */
51     INT32U           OSTCBCyclesStart;      /* Snapshot of cycle counter at start of task resumption   */
52     OS_STK          *OSTCBStkBase;          /* Pointer to the beginning of the task stack              */
53     INT32U           OSTCBStkUsed;          /* Number of bytes used from the stack                     */
54 #endif
55 
56 #if OS_TASK_NAME_EN > 0u
57     INT8U           *OSTCBTaskName; 58 #endif 59 60 #if OS_TASK_REG_TBL_SIZE > 0u 61  INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE]; 62 #endif 63 } OS_TCB;

 这个结构体乍一看还是挺大的,不过我们现在只关注核心数据,去掉那些被宏开关包围的成员,加上一些注释,再看一下:

typedef struct os_tcb {
    OS_STK          *OSTCBStkPtr;           /* 指向任务堆栈的指针                   */
 struct os_tcb *OSTCBNext; /* 指向下一个节点的指针 */ struct os_tcb *OSTCBPrev; /* 指向上一个节点的指针 */ INT32U OSTCBDly; /* 任务的延时参数 */ INT8U OSTCBStat; /* 任务的状态 */ INT8U OSTCBStatPend; /* 任务的阻塞状态 */ INT8U OSTCBPrio; /* 任务的优先级 */                           /* 下面4个参数是有关优先级算法的,作用三两句说不清楚,可以参考我上一篇讲任务调度的文章 */ INT8U OSTCBX; /* Bit position in group corresponding to task priority */ INT8U OSTCBY; /* Index into ready table corresponding to task priority */ OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */ OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */ } OS_TCB;

简化后明了多了,我们由定义的那两个成员可知,这是一个双向链表(如果对链表这种数据结构还不是很清楚,建议先去百度了解一下)。

因此,上面那个函数的作用也就是建立一个链表(空闲链表),长度根据自己的配置。

#define OS_MAX_TASKS             10u   /* Max. number of tasks in your application, MUST be >= 2       */
#define  OS_N_SYS_TASKS          2u    /* Number of system tasks  

我这里把宏定义为10(最大支持10个用户任务),再加上系统本身的保留任务2个(空闲任何、统计任务),那么这个链表的长度就是12。

这个链表有什么用?

我认为是为了预留出将来的空间,先建立一个空表在内存中占地方,等今后需要建立任务的时候,就直接从里面拿取空间,以避免内存不够的尴尬情况。

这个函数的作用,是对任务相关的数据进行初始化,最重要的生成一个名为空闲链表的链表(这个空闲链表链接事先定义好的任务数组)。

UCOSI操作系统里面有好几个结构完全一样的链表,比如任务链表,优先级链表,空闲链表等等,也是由这几个链表组合管理任务的信息。

※至于这几个链表的具体应用,暂时不用纠结,会在后面详细解释。

 

  五、函数OS_InitEventList()

  定义如下:

static  void  OS_InitEventList (void)
{
#if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0u)
#if (OS_MAX_EVENTS > 1u)
    INT16U     ix;
    INT16U     ix_next;
    OS_EVENT  *pevent1;
    OS_EVENT  *pevent2;


    OS_MemClr((INT8U *)&OSEventTbl[0], sizeof(OSEventTbl)); /* Clear the event table                   */
    for (ix = 0u; ix < (OS_MAX_EVENTS - 1u); ix++) {        /* Init. list of free EVENT control blocks */
        ix_next = ix + 1u;
        pevent1 = &OSEventTbl[ix];
        pevent2 = &OSEventTbl[ix_next];
        pevent1->OSEventType    = OS_EVENT_TYPE_UNUSED;
        pevent1->OSEventPtr     = pevent2;
#if OS_EVENT_NAME_EN > 0u
        pevent1->OSEventName    = (INT8U *)(void *)"?";     /* Unknown name                            */
#endif
    }
    pevent1                         = &OSEventTbl[ix];
    pevent1->OSEventType            = OS_EVENT_TYPE_UNUSED;
    pevent1->OSEventPtr             = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
    pevent1->OSEventName            = (INT8U *)(void *)"?"; /* Unknown name                            */
#endif
    OSEventFreeList                 = &OSEventTbl[0];
#else
    OSEventFreeList                 = &OSEventTbl[0];       /* Only have ONE event control block       */
    OSEventFreeList->OSEventType    = OS_EVENT_TYPE_UNUSED;
    OSEventFreeList->OSEventPtr     = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
    OSEventFreeList->OSEventName    = (INT8U *)"?";         /* Unknown name                            */
#endif
#endif
#endif
}

  此函数主要用来对系统的事件机制(邮箱、队列、信号量等等)做一些初始化,里面处理和任务初始化差不多,作用也是事先占领一些内存空间,以备今后使用。

  管理各种消息的也是链表(定义就不贴出来了),事件最大的个数也是通过宏定义来实现,如果没有使用的话,把宏定义关掉可以节约一部分空间(不建议)。

 

  六、七、八、函数OS_FlagInit(),OS_MemInit(),OS_QInit()

  这三个函数就是对具体的消息机制、内存管理功能进行初始化,内部的处理也都大同小异,都是根据系统的配置,然后先占领一些空间,以备今后使用(每个事件的链表的结构体不同)。

  函数定义这里不贴出来,具体可以查看源代码,在今后专门会推出讲解消息量的文章,到时候再详细说。

 

  九、函数OS_InitTaskIdle()

  定义如下:

 1 static  void  OS_InitTaskIdle (void)
 2 {
 3 #if OS_TASK_NAME_EN > 0u
 4     INT8U  err;
 5 #endif
 6 
 7 
 8 #if OS_TASK_CREATE_EXT_EN > 0u
 9     #if OS_STK_GROWTH == 1u
10     (void)OSTaskCreateExt(OS_TaskIdle,
11                           (void *)0,                                 /* No arguments passed to OS_TaskIdle() */
12                           &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Top-Of-Stack                     */
13                           OS_TASK_IDLE_PRIO,                         /* Lowest priority level                */
14                           OS_TASK_IDLE_ID,
15                           &OSTaskIdleStk[0],                         /* Set Bottom-Of-Stack                  */
16                           OS_TASK_IDLE_STK_SIZE,
17                           (void *)0,                                 /* No TCB extension                     */
18                           OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack  */
19     #else
20     (void)OSTaskCreateExt(OS_TaskIdle,
21                           (void *)0,                                 /* No arguments passed to OS_TaskIdle() */
22                           &OSTaskIdleStk[0],                         /* Set Top-Of-Stack                     */
23                           OS_TASK_IDLE_PRIO,                         /* Lowest priority level                */
24                           OS_TASK_IDLE_ID,
25                           &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Bottom-Of-Stack                  */
26                           OS_TASK_IDLE_STK_SIZE,
27                           (void *)0,                                 /* No TCB extension                     */
28                           OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack  */
29     #endif
30 #else
31     #if OS_STK_GROWTH == 1u
32     (void)OSTaskCreate(OS_TaskIdle,
33                        (void *)0,
34                        &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
35                        OS_TASK_IDLE_PRIO);
36     #else
37     (void)OSTaskCreate(OS_TaskIdle,
38                        (void *)0,
39                        &OSTaskIdleStk[0],
40                        OS_TASK_IDLE_PRIO);
41     #endif
42 #endif
43 
44 #if OS_TASK_NAME_EN > 0u
45     OSTaskNameSet(OS_TASK_IDLE_PRIO, (INT8U *)(void *)"uC/OS-II Idle", &err);
46 #endif
47 }

  这个函数的作用是建立一个空闲任务,所谓的空闲任务就是啥也不干的任务,为什么必须要有这么一个吃干饭的家伙呢?

  试想一下,等UCOSII操作系统跑起来以后,如果我现在所有任务都处在延时状态中,也就是未就绪的状态,那么系统应该执行什么代码呢?

       CPU的动力来源于晶振,只要晶振不停,那CPU也是不会停下来的啊。

  这就是空闲任务的作用,当所有的任务都没有执行的时候,系统就会执行它,虽然空闲任务啥也不干,但可以避免CPU一脸懵逼加茫然无措。

 

  这个函数我们需要重点分析,因为所有任务的建立原理和过程都是一样的,只要完全理解了空闲任务的建立过程后,那么建立别的任务也就明白了。

 

  因为我们创建任务使用的是标准的create函数,并未使用拓展的create函数(宏定义OS_TASK_CREATE_EXT_EN == 0),所以我们只需要关注下面那个部分:

  OSTaskCreateExt()为OSTaskCreate()拓展版本,功能大同小异,只不过多了一些设置,这些设置基本上不会用到。

 1 #else
 2     #if OS_STK_GROWTH == 1u
 3     (void)OSTaskCreate(OS_TaskIdle,
 4                        (void *)0,
 5                        &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
 6                        OS_TASK_IDLE_PRIO);
 7     #else
 8     (void)OSTaskCreate(OS_TaskIdle,
 9                        (void *)0,
10                        &OSTaskIdleStk[0],
11                        OS_TASK_IDLE_PRIO);
12     #endif
13 #endif

 

#define  OS_STK_GROWTH        1      /* Stack grows from HIGH to LOW memory on ARM    */

 

  由于我们使用mcu是m3内核,它堆栈的增长方向是由高到低,所以只需要看第一个宏里面的代码。

  看看函数的定义和形参:

INT8U  OSTaskCreate (void   (*task)(void *p_arg),
                     void    *p_arg,
                     OS_STK  *ptos,
                     INT8U    prio)

   看看我们传递进去的实参:

    (void)OSTaskCreate(OS_TaskIdle,
                       (void *)0,
                       &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
                       OS_TASK_IDLE_PRIO);

 

void   (*task)(void *p_arg):任务函数名               赋值=OS_TaskIdle(空闲任务函数名,系统已经实现了这个任务,不需要自己添加)

void    *p_arg                    :给任务传入的参数   赋值= 0

OS_STK  *ptos                 :任务的堆栈空间       赋值= OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u](空闲任务的堆栈空间和大小也是系统定义的)

INT8U    prio                     :任务的优先级          赋值= OS_TASK_IDLE_PRIO(既然都叫空闲任务,那优先级肯定是最低的,这个宏的值是63,由系统定义)

大家快来看啊,原来创建一个任务这么简单,只需要给4个参数就可以了。

传递了4个参数进去后,那里面具体又是怎么实现处理的呢?

 

待续……