手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务(一)
做过软件的同学都知道,任何一个程序都必须要有初始化的过程,在初始化过程中,我们会对外围硬件以及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个参数进去后,那里面具体又是怎么实现处理的呢?
待续……