任务创建函数定义以下:html
1 INT8U OSTaskCreate (void (*task)(void *p_arg), 2 void *p_arg, 3 OS_STK *ptos, 4 INT8U prio) 5 { 6 OS_STK *psp; 7 INT8U err; 8 #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ 9 OS_CPU_SR cpu_sr = 0u; 10 #endif 11 12 13 14 #ifdef OS_SAFETY_CRITICAL_IEC61508 15 if (OSSafetyCriticalStartFlag == OS_TRUE) { 16 OS_SAFETY_CRITICAL_EXCEPTION(); 17 } 18 #endif 19 20 #if OS_ARG_CHK_EN > 0u 21 if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */ 22 return (OS_ERR_PRIO_INVALID); 23 } 24 #endif 25 OS_ENTER_CRITICAL(); 26 if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */ 27 OS_EXIT_CRITICAL(); 28 return (OS_ERR_TASK_CREATE_ISR); 29 } 30 if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */ 31 OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */ 32 /* ... the same thing until task is created. */ 33 OS_EXIT_CRITICAL(); 34 psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */ 35 err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u); 36 if (err == OS_ERR_NONE) { 37 if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */ 38 OS_Sched(); 39 } 40 } else { 41 OS_ENTER_CRITICAL(); 42 OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */ 43 OS_EXIT_CRITICAL(); 44 } 45 return (err); 46 } 47 OS_EXIT_CRITICAL(); 48 return (OS_ERR_PRIO_EXIST); 49 }
21~23行,判断咱们传递进来的参数优先级是否合法,若是不知足,直接退出(当前系统支持最大64个任务,所以优先级必须小于64)。编程
26~29行,判断当前系统的中断状态,变量OSIntNesting的意义以前讲过,若是它大于0,那就表明目前处于中断服务程序中,在中断中系统是不容许创建新任务的。数组
30行,首先判断任务是否已经存在,若是已经存在则跳出,数组OSTCBPrioTbl[prio]的下标是优先级,内容是任务的相关的信息,若是该优先级的任务已创建,那么其内容必然不等于0.数据结构
31行,当任务不存在时,随便赋个值给这个任务的管理数组,至关因而在这个元素上作个标志,告诉系统,这个位置要保留下来不容许别人动,直到这个任务真正创建成功。ide
34行所调用的函数是初始化任务的堆栈,咱们如今详细看看内部的处理,究竟是如何进行初始化的。函数
函数OSTaskStkInit定义以下:this
1 OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt) 2 { 3 OS_STK *stk; 4 5 6 (void)opt; /* 'opt' is not used, prevent warning */ 7 stk = ptos; /* Load stack pointer */ 8 9 /* Registers stacked as if auto-saved on exception */ 10 *(stk) = (INT32U)0x01000000L; /* xPSR */ 11 *(--stk) = (INT32U)task; /* Entry Point */ 12 *(--stk) = (INT32U)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/ 13 *(--stk) = (INT32U)0x12121212L; /* R12 */ 14 *(--stk) = (INT32U)0x03030303L; /* R3 */ 15 *(--stk) = (INT32U)0x02020202L; /* R2 */ 16 *(--stk) = (INT32U)0x01010101L; /* R1 */ 17 *(--stk) = (INT32U)p_arg; /* R0 : argument */ 18 19 /* Remaining registers saved on process stack */ 20 *(--stk) = (INT32U)0x11111111L; /* R11 */ 21 *(--stk) = (INT32U)0x10101010L; /* R10 */ 22 *(--stk) = (INT32U)0x09090909L; /* R9 */ 23 *(--stk) = (INT32U)0x08080808L; /* R8 */ 24 *(--stk) = (INT32U)0x07070707L; /* R7 */ 25 *(--stk) = (INT32U)0x06060606L; /* R6 */ 26 *(--stk) = (INT32U)0x05050505L; /* R5 */ 27 *(--stk) = (INT32U)0x04040404L; /* R4 */ 28 29 return (stk); 30 }
这个函数该怎么解释呢?spa
它其实并非真正的去初始化了CPU的栈空间,只是对一个模拟的栈进行了初始化。操作系统
作过单片机程序的同窗都很清楚,在程运行中总遇到中断发生,当这个时候就必需要跳进中断中去运行,但在跳进中断服务函数以前,咱们必需要作一些处理,好比保存现场,将当前的数据和寄存器值押入栈中,而后在去执行中断函数,由于只有这样作了,在执行完中断函数之后才能找回原点继续执行剩下的代码(汇编语言须要本身压栈和出栈,C语言由系统自动完成)。线程
单片机裸机程序是单线程,代码的执行逻辑始终处于一条时间线,就算是发生了中断嵌套,但在某一个具体的时间点,它也是单线的,所以只须要保存一个现场就能够回到原点。
但在嵌入式操做系统中同时执行了多个任务,每一个任务之间都有可能发生切换,任务与任务之间不存在调用的关系,是相对独立的存在,任务的这个切换能够理解为单片机中发生的中断,并且切换的顺序并非线性的,一旦在任务过多的状况下,若是依然使用CPU本身的栈空间,那这样就会致使栈空间不够用,栈溢出,并且并且很是不方便。
因此,UCOSII系统对于每个任务都建立了一个数组来模拟它的栈空间,好比这个空闲任务,咱们跟踪它的参数能够知道:
OS_EXT OS_STK OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE]; /* Idle task stack */
#define OS_TASK_IDLE_STK_SIZE 128u /* Idle task stack size (# of OS_STK wide entries) */
系统定义好了一个128长度的数组来模拟空闲任务的栈空间,专门用来存在和这个任务有关的寄存器数据,当咱们从空闲任务跳出去的时候,就会把相应的信息保存在里面,等须要跳回的时候,就会从里面取出相应的信息,功能和CPU的栈空间彻底同样。
咱们能够用keil仿真一下。
首先在下面进入栈初始化以前打上断点:
这个时候咱们能够看看空闲任务的栈数组,也就是OSTaskIdleStk[],他此刻的状态以下:
这个数组的长度是128个字节,我不可能所有截完,不过它其中的内容都如上所示,所有是0x0000。
如今咱们在函数的出口后打上断点,而后全速运行,观察一下空闲任务的栈初始化完成之后是什么样子的,代码以下:
栈空间初始化完后结果以下:
咱们能看出来有些空间被填充了,因为M3的栈空间是由高往低增加,所以数组的最后几个字节被填充上的内容,其中第126个元素保存的是空闲任务的任务函数地址,第120个元素保存的传递进来的参数,剩下的那些当即数是模拟的CPU的寄存器,至于那些当即数的内容不用关心,基本是用于开发人员调试用的。
在咱们之前进行单片机C语言的编程中,在一个函数被调用以前,它的堆栈信息的初始化,压栈,出栈都是mcu自行进行的,不须要工程师干预,而在UCOSII系统中,因为MCU的栈空间只有一个,而咱们的任务又太多,所以便须要由人工来进行管理,实际上OSTaskStkInit函数即是模拟了这种动做。
空闲任务的栈空间初始化完成之后,接下来便须要对任务自己的一些信息进行处理,请接着看下一个函数:OS_TCBInit()
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
其简化后,定义以下:
1 INT8U OS_TCBInit (INT8U prio, 2 OS_STK *ptos, 3 OS_STK *pbos, 4 INT16U id, 5 INT32U stk_size, 6 void *pext, 7 INT16U opt) 8 { 9 OS_TCB *ptcb; 10 #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ 11 OS_CPU_SR cpu_sr = 0u; 12 #endif 13 #if OS_TASK_REG_TBL_SIZE > 0u 14 INT8U i; 15 #endif 16 17 18 OS_ENTER_CRITICAL(); 19 ptcb = OSTCBFreeList; /* Get a free TCB from the free TCB list */ 20 if (ptcb != (OS_TCB *)0) { 21 OSTCBFreeList = ptcb->OSTCBNext; /* Update pointer to free TCB list */ 22 OS_EXIT_CRITICAL(); 23 ptcb->OSTCBStkPtr = ptos; /* Load Stack pointer in TCB */ 24 ptcb->OSTCBPrio = prio; /* Load task priority into TCB */ 25 ptcb->OSTCBStat = OS_STAT_RDY; /* Task is ready to run */ 26 ptcb->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */ 27 ptcb->OSTCBDly = 0u; /* Task is not delayed */ 28 30 pext = pext; /* Prevent compiler warning if not used */ 31 stk_size = stk_size; 32 pbos = pbos; 33 opt = opt; 34 id = id; 35 36 #if OS_TASK_DEL_EN > 0u 37 ptcb->OSTCBDelReq = OS_ERR_NONE; 38 #endif 39 40 ptcb->OSTCBY = (INT8U)(prio >> 3u); 41 ptcb->OSTCBX = (INT8U)(prio & 0x07u); 42 43 ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY); 44 ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX); 45 46 #if OS_TASK_REG_TBL_SIZE > 0u /* Initialize the task variables */ 47 for (i = 0u; i < OS_TASK_REG_TBL_SIZE; i++) { 48 ptcb->OSTCBRegTbl[i] = 0u; 49 } 50 #endif 51 52 OSTCBInitHook(ptcb); 53 54 OSTaskCreateHook(ptcb); /* Call user defined hook */ 55 56 OS_ENTER_CRITICAL(); 57 OSTCBPrioTbl[prio] = ptcb; 58 ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */ 59 ptcb->OSTCBPrev = (OS_TCB *)0; 60 if (OSTCBList != (OS_TCB *)0) { 61 OSTCBList->OSTCBPrev = ptcb; 62 } 63 OSTCBList = ptcb; 64 OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run */ 65 OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; 66 OSTaskCtr++; /* Increment the #tasks counter */ 67 OS_EXIT_CRITICAL(); 68 return (OS_ERR_NONE); 69 } 70 OS_EXIT_CRITICAL(); 71 return (OS_ERR_TASK_NO_MORE_TCB); 72 }
第19行,ptcb只是一个局部指针,暂时尚未指向任何地址,所以首先须要从刚才创建的空闲链表中取出一个实际地址。
第20行,判断该地址是否有效(不等于0)。
第21行,因为咱们从空闲链表取出了一块地址,那么这个空闲链表的首地址就不能用了,因此须要把它的首地址指向下一个位置,以便从此其余任务的创建。
第23~27行,这几句话是设定任务的参数,好比栈地址,优先级,任务状态,延时参数等等。
第40~44行,这几句话和优先级策略以及从此的任务切换有关,详细可参考:http://www.cnblogs.com/han-bing/p/8882375.html
第52~53行,这是钩子函数的调用,无需多言。
第57行,这是将已经初始化完成的任务链表TCB赋值给全局的任务数组,这个数组一共有64个元素,其中是按照优先级从高到低来排序。
第58~59行,更新任务链表的下一个元素和上一个元素,也就是把新建的这个任务块,连接到总的任务控制连接里面。
第60~62行,判断当前任务链表的状态(因为空闲任务是第一个建立的任务,所以任务控制链表仍是空的,上一个连接天然也不存在,因此并不会执行if判断里面代码,ptcb->OSTCBPrev被赋值为0)。
第63行,把咱们新建的这个局部任务块,连接到总任务链表里面,从这时开始,OSTCBList就不在是空的了,里面有了第一个成员。
第64~65行,这两句话是任务优先级的管理表更新,详细可参考:http://www.cnblogs.com/han-bing/p/8882375.html
第66行,任务个数管理变量加1,表明系统创建了多少个任务。
----------------------------------------------------------------------------------------------------------------------
任务创建的过程整体而言不算简单也不算复杂,若是你已经掌握了链表等数据结构的知识,那么对于这段应该很容易掌握,不过如果之前对链表之类的彻底不了解,那就须要多花费一点时间了。
对于新手而言,上面出现的各式各样的链表和数组可能看起来彷佛有些眼花缭乱,以为UCOSII系统太耗费资源,那么大的结构体做成一个64个元素的数组,并且还有好几个……其实,链表和数组不一样,无论所管理的空间有多大,链表并不会占据实际的数据结构内存(固然,指针自己仍是要占一点内存的,只不过仅仅是一个指针的地址,并不会占据数据结构那么大的内存),别看咱们上面提到了那么多种链表,但不论是任务管理链表(OSTCBList),空闲链表(OSTCBFreeList)其实他们管理的都是同一段内存,也就是数组OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]。
就像一条街道拥有的不一样叫法,老年人叫“爱国路”,年轻人叫“欢乐路”,小朋友叫“玩耍路”……他们所知的都是同一条街道,只是名字不一样罢了。
若是不明白,请看看代码中的定义:
1 OS_EXT OS_TCB *OSTCBCur; /* Pointer to currently running TCB */ 2 OS_EXT OS_TCB *OSTCBFreeList; /* Pointer to list of free TCBs */ 3 OS_EXT OS_TCB *OSTCBHighRdy; /* Pointer to highest priority TCB R-to-R */ 4 OS_EXT OS_TCB *OSTCBList; /* Pointer to doubly linked list of TCBs */ 5 OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u]; /* Table of pointers to created TCBs */ 6 OS_EXT OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]; /* Table of TCBs */
前面5个数据定义的类型都是指向别的数据的指针,只有最后一个数组定义的才是占据实际内存的数据,UCOSII系统中所谓的任务管理,其实也就是把这几个链表指向的地址根据须要反复的修改,可是永远限定在OSTCBTbl数组中,就像孙猴子不管怎么飞永远也逃不出如来佛的手掌心,这样从而达到管理任务的目的。
首先是那个数组:OSTCBTbl
这是它的内存分部状态,它是实际占据内存的数据。
在系统刚刚初始化开始,尚未新建任何任务的时候,咱们用这个数组和空闲链表作一个比较。
数组:OSTCBTbl 空闲链表:OSTCBFreeList
由以上两张地址信息对比,能够看出,空闲链表中指向下一个结构的地址,其实就是数组元素的地址,所以它管理的是数组元素。
这个时候(没有任何任务创建,包括空闲任务),别的链表都是空:
首先是那个数组:OSTCBTbl
这个数组的内容固然是不会变的,无论任务是否创建,无论任务是否存在,它元素的地址永远都不会变。
在系统建完成空闲任务后,咱们用这个数组和空闲链表作一个比较。
数组:OSTCBTbl 空闲链表:OSTCBFreeList
两张图一对比便不难发现,空闲链表的数据内容发生了变化,具体即是,它全部的地址都往前缩进了一位,数组OSTCBTbl的首地址:0x20000A48,已经没法在空闲链表中找到了,空闲链表的首地址变成了,数组的第二个元素的地址。
此时此刻空闲任务已经创建完成,所以,空闲链表最开始的那个地址已经不空闲了,名花有主了,因此它的首地址只能指向像一个尚未被分配出去的地址,若是咱们再创建一个任务,那么空闲链表的首地址还会继续向上缩进一个,由于它所指向的,必须是这个数组中尚未被分配出去的那段空间。
咱们再继续看看别的链表。
好比说任务管理链表的状态:
数组:OSTCBTbl 任务管理链表:OSTCBList
由于咱们已经创建了一个任务,所以任务管理链表的首地址,天然就指向了那个仅存的空闲任务,至关于从空闲链表中拿出去,放进了任务管理链表中,每创建一个任务都是如此,不停的从空闲链表中拿出去,而后放进任务管理链表,任务的删除即是反向操做,从任务管理链表中拿出去,放进空闲链表。
至于别的两个链表,此刻依然是空,由于虽然创建了一个任务,但这个任务还没跑起来,等它跑起来后这两个链表里确定就不是空了。
优先级链表:OSTCBPrioTbl
因为空闲任务的创建,优先级管理链表中也有数据了,因为空闲任务的优先级是63,因此按照它的排序规则,放进最后一个元素中,能够看到它的地址正是那个空闲任务的地址,这也就意味着,他们管理的是同一段数据。
小结:在UCOSII中,系统所占据的内存其实并不大,虽然里面的各类链表比较多,但都管理的同一段内存,只是根据须要和意义赋予了不一样的名字。
铁打的数组,流水的链表,数组就像一棵巍然不动的千年老松,任凭风吹雨打,那些链表就像一堆迎风就倒的墙头小草,它们所管理的范围是变更的。
任务管理其实也很简单,它的本质,就是对任务数组的操做,增长、删除、排序……等等。
任务数组:OSTCBTbl,它里面是按照任务创建的时间来存听任务。
优先级数组:OSTCBPrioTbl,它里面是按照任务的优先级,对任务数组进行了一次排序。
所以,空闲任务的创建过程,首先是把空闲任务的信息放进数组OSTCBTbl的第1个位置(创建时间最先),再把空闲任务的信息放进数组OSTCBPrioTbl的最后一个位置(优先级最低)。