在uc/os系统中,对于任务的描述和管理是通过任务控制快-OS_TCB来实现的,OS_TCB本质上是一些驻留在在RAM中的结构体。由以下内容构成
转载于: http://blog.csdn.net/mr_fjx_linux/article/details/6155391
1 typedef struct os_tcb { 2 OS_STK *OSTCBStkPtr;//OS_STK定义为32位无符号数据,该行定义当前任务所分配的堆栈的栈顶指针(该栈顶指针是指任务切换后自动保存的r0~r15等一系列数据后的堆栈指针),对于堆栈,uc/os可以对每一个任务分配一个任意大小的堆栈。 3 4 #if OS_TASK_CREATE_EXT_EN//如果使能勒扩展任务控制块,则定义以下数据 5 void *OSTCBExtPtr;//扩展任务块指针,扩展控制块的引入,使得可以在不改变uc/os源代码的前提下,加入扩展功能,此外如果需要使用该功能,则需在OS_CFG.H(uc/os配置文件)中将OS_TASK_CREATE_EXT_EN置一,允许建立任务函数的扩展 6 OS_STK *OSTCBStkBottom;指向任务堆栈的栈底(就是数据最后进入的地址)如果堆栈增长方式是递增的话,那么它指向堆栈的最高地址,反之指向最低地址,改参数在使用OSTaskStkChk()函数是需要使用 7 INT32U OSTCBStkSize;//该参数是任务堆栈大小,对于堆栈大小是指栈中所能容纳的指针数目,而不是字节数目。假设堆栈容量是1000,如果地址宽度是32位的,那么堆栈包含4000字节,但是其容量是1000 8 INT16U OSTCBOpt;//选择项,支持三个选择,OS_TASK_OPT_STK_CHK该参数用于告知TaskCreateExt()函数在建立任务时对堆栈进行检查(uc/os不会自动进行堆栈检查,必须使用改选项来设定),OS_TASK_OPT_STK_CLR该参数设定,则在任务建立的过程中将任务栈清零(只有在需要使用栈检验功能时才将栈清零)OS_TASK_OPT_SAVE_FP该参数是通知任务需要做浮点数运算 9 INT16U OSTCBId;用于存储任务的识别码,现在还没使用,感觉他会发展风linux中的PID。 10 #endif 11 12 struct os_tcb *OSTCBNext; 13 struct os_tcb *OSTCBPrev;//任务控制块的组成双向链表是所需的变量,分别指向该任务的后一个任务控制快和前一个任务控制块。 14 15 #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN 16 OS_EVENT *OSTCBEventPtr;//如果定义了队列,消息邮箱,信号量系统资源,则使用该指针指向事件控制块。 17 #endif 18 19 #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN 20 void *OSTCBMsg;//如果定义队列和邮箱,则指针指向所要传递的消息 21 #endif 22 23 INT16U OSTCBDly;//任务延时的时间,或者等待某事件发生的超时限制,在时钟没每生一次中断的时候,时钟节拍函数OSTimeTick()将通过任务控制块的链表访问该变量并将其减一从而刷新该变量 24 INT8U OSTCBStat;//该变量用于描述任务的状态 25 INT8U OSTCBPrio;//任务优先级 26 27 INT8U OSTCBX;//表示任务优先级在就绪表中的X轴的位置,相当说明该优先级是某一组中的哪一个位元素。 28 INT8U OSTCBY;//表示任务优先级在就绪表中的Y轴的位置,相当说明该优先级是处于第几个组元素 29 INT8U OSTCBBitX;//说明该优先级对应的OSTCBTBL[prio&0x7]中元素的赋值 30 INT8U OSTCBBitY;//说明该优先级对应的OSRdyGrp的元素,将该元素与OSRdyGrp相于就获得当前的OSRdyGrp所对应的值 31 32 #if OS_TASK_DEL_EN 33 BOOLEAN OSTCBDelReq;该参数表明该任务是否需要删除。只有在OS_FLAG_EN置为一是才会出现在OS_TCB中 34 #endif 35 } OS_TCB;
对于OS_TCB的管理,uc/os采用了两个链表进行管理,在任务初始化时所有的空闲OS_TCB被连接成单向的空任务链表。另外当任务建立时,空任务控制块指针OSTCBFreeList指向的控制块分配给该任务,当有多个任务建立时,所申请的任务控制块构成一个双向的任务控制块链表
- 对于任务就绪表的说明:在uc/os中最多支持64个任务,并为每一个任务指派一个优先级,对于任务的就绪查询,其使用任务就绪表。每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量OSRedyGrp和OSRdyTbl[]。在OSRdyGrp中,任务按优先级分组,8个任务为一组。OSRdyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置位。就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PR1O(见文件OS_CFG.H)。当用户的应用程序中任务数目比较少时,减少OS_LOWEST_PR1O的值可以降低μC/OS-Ⅱ对RAM(数据空间)的需求量。
- 任务堆栈的栈顶是指,将CPU全部寄存器压入堆栈后的SP,而不是说堆栈的起始地址。
OS_TCBInit()函数分析
函数名称: | OS_TCBInit() |
函数功能: | 任务控制块初始化 |
函数入口接口: | 任务优先级prio、任务堆栈栈顶ptos、任务堆栈栈底pbos、任务id、任务堆栈大小stk_size、任务扩展指针pext、任务选项opt |
函数出口接口: | 8位无符号数,表明是否初始化成功 |
主要调用函数: | 无 |
函数主要执行逻辑流程:
1 INT8U OS_TCBInit (INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT32U stk_size, void *pext, INT16U opt) 2 { 3 #if OS_CRITICAL_METHOD == 3 // 4 OS_CPU_SR cpu_sr; 5 #endif 6 OS_TCB *ptcb;//声明一个指向任务控制块的指针 7 8 OS_ENTER_CRITICAL();//防止其他任务抢占该TCB故要关中断 9 ptcb = OSTCBFreeList;//从空任务链表中申请一个新的控制块(空任务链表是单向链表,大小由OS_MASK_TASKS决定) 10 if (ptcb != (OS_TCB *)0) {//如果指针分配成功,即分配的tcb有实际地址 11 OSTCBFreeList =ptcb->OSTCBNext;//则空任务块指针OSTCBFreeList指向下一个空TCB块 12 OS_EXIT_CRITICAL();//此时该任务已经占有该任务控制块,开中断来初始化TCB中其余变元,减少关中断时间。 13 ptcb->OSTCBStkPtr =ptos; //初始话任务堆栈栈顶(参数传入) 14 ptcb->OSTCBPrio = (INT8U)prio;//初始化优先级(参数传入) 15 ptcb->OSTCBStat = OS_STAT_RDY;//任务状态为就绪 16 ptcb->OSTCBDly = 0;//设置任务延时为0,即不延时 17 #if OS_TASK_CREATE_EXT_EN > 0//如果在OS_FH.H中使能了OS_TASK_CREATE_EXT_EN额外的变元就被插入到TCB中 18 ptcb->OSTCBExtPtr = pext;//任务扩展块的指针 19 ptcb->OSTCBStkSize = stk_size;//任务对战大小 20 ptcb->OSTCBStkBottom = pbos;//堆栈的栈底指针 21 ptcb->OSTCBOpt = opt;//任务选项 22 ptcb->OSTCBId = id;//任务ID(无实际作用) 23 #else 24 pext = pext;//防止编译器报警 25 stk_size = stk_size; 26 pbos = pbos; 27 opt = opt; 28 id = id; 29 #endif 30 #if OS_TASK_DEL_EN > 0 31 ptcb->OSTCBDelReq = OS_NO_ERR;//布尔变量是否出现取决于OS_TASK_EN是否被设置(如果不打算删除任务的话,将节省一个布尔变量的空间) 32 #endif 33 ptcb->OSTCBY = prio >> 3;//提前计算任务就绪表所需要的一些变量 34 ptcb->OSTCBBitY = OSMapTbl[ptcb->OSTCBY]; 35 ptcb->OSTCBX = prio & 0x07; 36 ptcb->OSTCBBitX = OSMapTbl[ptcb->OSTCBX]; 37 #if OS_EVENT_EN > 0 38 ptcb->OSTCBEventPtr = (OS_EVENT *)0;//如果使用信号量,邮箱,队列则该指针指向事件控制块 39 #endif 40 #if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0) && (OS_TASK_DEL_EN > 0)//如果事件标志得到使用 41 ptcb->OSTCBFlagNode = (OS_FLAG_NODE *)0;//初始化指向事件标志节点的指针 42 #endif 43 #if (OS_MBOX_EN > 0) || ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) 44 ptcb->OSTCBMsg = (void *)0;//初始化指向邮箱、队列、信号量集所在消息的指针 45 #endif 46 #if OS_VERSION >= 204 47 OSTCBInitHook(ptcb);//钩子函数的初始化(具体内容由自己实现) 48 #endif 49 OSTaskCreateHook(ptcb);//调用钩子函数 50 OS_ENTER_CRITICAL();//关中断 51 OSTCBPrioTbl[prio] = ptcb;//将任务控制块优先级表中对应变量赋值为该TCB地址 52 ptcb->OSTCBNext = OSTCBList;//将初始化好的TCB加入到以用TCB双向链表中 53 ptcb->OSTCBPrev = (OS_TCB *)0;//TCB链表的每一次加入,总是加入到链表最前面的一个位置,故新加入的TCB前一个TCB应该为空 54 if (OSTCBList != (OS_TCB *)0) {//OSTCBList是双向链表的头结点,如果头结点是非空的话,证明该链表中非空了,故需将新申请的TCB块于前一个TCB连接起来 55 OSTCBList->OSTCBPrev = ptcb;//将前一个TCB控制块中的OSTCBPrev指向新申请的TCB,此时OSTCBList指向的是旧的TCB 56 } 57 OSTCBList = ptcb;//新TCB加入成功后,将双向链表头指针指向新加入的TCB(新的TCB总是加载到双向链表的链表头) 58 OSRdyGrp |= ptcb->OSTCBBitY//更新任务就绪表的内容(此次修改任务就绪组变量其中OSTCBBitY已经在前面计算过) 59 OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;//更新任务就绪位表 60 OS_EXIT_CRITICAL(); 61 return (OS_NO_ERR);返回TCB初始化成功 62 } 63 OS_EXIT_CRITICAL(); 64 return (OS_NO_MORE_TCB);//如果该TCB指针为零则返回OS_NO_MORE_TCB 65 } 66 该函数的基本过程:申请TCB->初始化TCB中的变量->更新任务控制块优先级表->将TCB加入到以用TCB链表->更新任务就绪表
OS_TaskStkInit()分析
函数名称: | OS_TaskStkInit() |
函数功能: | 任务堆栈初始化 |
函数入口接口: | 任务入口地址、任务参数、堆栈原始栈顶、选项 |
函数出口接口: | 初始化寄存器后的堆栈地址(堆栈栈顶) |
主要调用函数: | 无 |
逻辑流程
示意性代码:
OS_STK *OSTaskStkInit(void (*task)(void *pd),void *pdata,OS_STK *ptos,INT16U opt)
{
模拟带参数(pdata)的函数调用
模拟ISR向量
按照预先设计的寄存器值初始化堆栈结构
返回栈顶指针给调用该函数的函数
}
1 OSTaskStkInit()函数源码(针对ARM7的LPC2200,该源码根据不同的CPU有不同的形式) 2 OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt) 3 { 4 OS_STK *stk; 5 opt = opt; /* 'opt' 没有使用。作用是避免编译器警告 */ 6 stk = ptos; /* 获取堆栈指针 */ 7 /* 建立任务环境,使用满递减堆栈 */ 8 *stk = (OS_STK) task; /* pc */ 9 *--stk = (OS_STK) task; /* lr */ 10 11 *--stk = 0; /* r12 */ 12 *--stk = 0; /* r11 */ 13 *--stk = 0; /* r10 */ 14 *--stk = 0; /* r9 */ 15 *--stk = 0; /* r8 */ 16 *--stk = 0; /* r7 */ 17 *--stk = 0; /* r6 */ 18 *--stk = 0; /* r5 */ 19 *--stk = 0; /* r4 */ 20 *--stk = 0; /* r3 */ 21 *--stk = 0; /* r2 */ 22 *--stk = 0; /* r1 */ 23 *--stk = (unsigned int) pdata; /* r0,第一个参数使用R0传递 */ 24 *--stk = (USER_USING_MODE|0x00); /* spsr,允许 IRQ, FIQ 中断 */ 25 *--stk = 0; /* 关中断计数器OsEnterSum; */ 26 27 return (stk); 28 }
- 在任务第一次开始执行时,操作系统首先得到任务的任务控制块,然后从任务控制块得到任务的堆栈指针,再把这个堆栈指针送到R13(SP)。然后再OSCtxSw()函数中把初始化的各个寄存器的值送到对应的CPU寄存器,所以在OSTaskStkInit()函数里不用处理R13。在任务执行过一次之后,任务被中断切换到其它任务之前,就会从R13(SP)得到堆栈指针当前的位置,在OSCtxSw()函数中把CPU的R0-R12,R14等值压到R13(SP)指定的堆栈中。所以R13的内容是不需要初始化的。
- OSTaskStkInit()中数据进栈的顺序要和OSCtxSw()中数据出栈的顺序对应。
PC值是最后出栈,所以要最先进栈。把任务函数的地址压入堆栈。出栈后,任务函数地址送入PC后,就开始执行任务函数。 - R14是返回地址,但是任务函数是一个无限循环,只有在任务调度时才会退出,而在任务调度时切换到另一个任务时,会把另一个任务堆栈中存储的R14的值送到R14,保证程序在另一个任务被中断时的断点继续运行。所以在初始化堆栈时,R14的值是没有用的,可以随意赋值。
- R1-R12可随便赋值,但是赋值的形式最好是反映出堆栈增长方式
- 返回的地址是堆栈压入上述寄存器后的指针,而不是对战最初的指针
- CPSR在压入堆栈的时候确保其是允许IRQ,FIQ的
OS_Sched()函数分析
函数名称: | OS_Sched() |
函数功能: | 任务调度(该函数相当于判断是否需要进行任务切换,具体任务切换是由OS_TASK_SW()汇编宏来完成的 |
函数入口接口: | 无 |
函数出口接口: | 无 |
主要调用函数: | OS_TASK_SW()宏 |
函数逻辑流程
OSSched()函数源码
1 void OSSched (void) 2 { 3 INT8U y; 4 5 6 OS_ENTER_CRITICAL(); 7 if ((OSLockNesting | OSIntNesting) == 0) {//判断任务调度器是否上锁、该任务切换请求是否是在ISR中请求的 8 y = OSUnMapTbl[OSRdyGrp];//获得当前最高优先级任务处于就绪表的第几组 9 OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);//OSUNMapTbl【y】是获得其处于组元素中的第几位,将组元素左移三位相加位元素后强制类型转换得当前优先级最高的任务,这个表是uc/os提前设定好的,其实我也每台看明白他是怎么来的 10 if (OSPrioHighRdy != OSPrioCur) {//判断最高优先级任务是否是现在运行的任务 11 OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//根据当前最高优先级从任务控制块优先级表中获得当前最高优先级控制块的入口地址 12 OSCtxSwCtr++;//任务切换次数++,该变量是跟踪任务切换次数的 13 OS_TASK_SW();//宏调用,完成实际上的任务切换 14 } 15 } 16 OS_EXIT_CRITICAL(); 17 }
- 在任务切换的所有代码都是临界段代码,这样是防止ISR在将高优先级任务置位,干扰此次任务切换。
- 判断任务切换请求是否是ISR的请求的原因是OS_TASK_SW()应用在任务级,它只是简单的将处理器寄存器保存到被挂起的任务堆栈中,并且从堆栈中回复要运行的更高优先级任务。
OS_TASK_SW宏调用分析
函数名称: | OS_TASK_SW(实际上是调用软中断) |
函数功能: | 完成任务切换 |
函数入口接口: | 无 |
函数出口接口: | 无 |
主要调用函数: | 软中断 |
#define OS_TASK_SW() asm INT #080h //此处使用SWI指令进行分析
- 对于任务的切换uc/os使用软中断来实现
对于软中断将会在专门来说明一下,这点看了好久才看明白
OSTaskCreate()分析
前面说了那么多,终于该到OSTaskCreate()勒,在uc/os中,该函数的作用是创建一个任务(其实就是个线程)来完成一定的功能。
函数名称: | OSTaskCreate() |
函数功能: | 建立一个任务 |
函数入口接口: | void (*task)(void *pd)任务入口地址指针 void *pdata传给任务参数的指针(一般用不到) OS_STK *ptos任务堆栈栈顶指针(在初始化任务堆栈的时候使用) INT8U prio任务优先级,作用你懂得 |
函数出口接口: | INT8U 是否建立成功标志 |
主要调用函数: | OSTaskStkInit()任务堆栈初始化 OSTCBInit*()任务控制快初始化,说白了就是从空TCB取个空TCB然后该结构体内容赋值,再将该TCB加载到双向链表上 OSSched()任务调度 |
函数流程逻辑
OSTaskCreate()源码分析
1 INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio) 2 { 3 #if OS_CRITICAL_METHOD == 3 4 OS_CPU_SR cpu_sr; 5 #endif 6 OS_STK *psp; 7 INT8U err; 8 9 #if OS_ARG_CHK_EN > 0 10 if (prio > OS_LOWEST_PRIO) { //确保该优先级高于设定的最低优先级 11 return (OS_PRIO_INVALID); 12 } 13 #endif 14 OS_ENTER_CRITICAL(); 15 if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { //确保该优先级还未分配,如果该优先级已被分配,那么在任务控制块优先级表中存放的是实际分配任务控制块的入口地址,未分配则地址为空 16 OSTCBPrioTbl[prio] = (OS_TCB *)1; //只是简单的将该优先级对应数组元素置位一,表示该优先级现在被分配了,其不是赋值具体的TCB入口地址是为了减少关中断的时间,(具体入口地址是在OSTCBINI()函数中从空任务控制池中分配的) 17 / 18 OS_EXIT_CRITICAL(); 19 psp = (OS_STK *)OSTaskStkInit(task, pdata, ptos, 0);//堆栈初始化,返回的是压入一定寄存器后的SP 20 err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);//初始化TCB 21 if (err == OS_NO_ERR) { 22 OS_ENTER_CRITICAL(); 23 OSTaskCtr++; //该变量是用来追踪当前任务数目的 24 OS_EXIT_CRITICAL(); 25 if (OSRunning == TRUE) { //确保uc/os已经开始多任务调度了 26 OS_Sched();//找到当前最高优先级任务,开始任务调度 27 } 28 } else { 29 OS_ENTER_CRITICAL(); 30 OSTCBPrioTbl[prio] = (OS_TCB *)0;//如果TCB申请失败,那么将任务控制块优先级表对应元素清零,放弃此次任务建立 31 OS_EXIT_CRITICAL();