2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 【μc/OS内核分析】 任务管理

【μc/OS内核分析】 任务管理

时间:2021-03-08 16:36:22

相关推荐

【μc/OS内核分析】 任务管理

μc/OS 任务管理

2.1 任务管理数据结构

任务控制块、任务空闲链表和任务就绪链表、任务优先级指针表、任务堆栈等。

2.1.1 任务控制块

OSTCB结构体包含了任务控制信息。

typedef struct os_tcb {OS_STK*OSTCBStkPtr; /* Pointer to current top of stack*/#if OS_TASK_CREATE_EXT_EN > 0uvoid *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */OS_STK*OSTCBStkBottom; /* Pointer to bottom of stack*/INT32U OSTCBStkSize;/* Size of task stack (in number of stack elements) */INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */INT16U OSTCBId;/* Task ID (0..65535) */#endifstruct os_tcb *OSTCBNext; /* Pointer to nextTCB in the TCB list */struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */#if OS_TASK_CREATE_EXT_EN > 0u#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)OS_TLS OSTCBTLSTbl[OS_TLS_TBL_SIZE];#endif#endif#if (OS_EVENT_EN)OS_EVENT *OSTCBEventPtr; /* Pointer toevent control block */#endif#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)OS_EVENT **OSTCBEventMultiPtr; /* Pointer to multiple event control blocks*/#endif#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */#endif#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)#if OS_TASK_DEL_EN > 0uOS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node*/#endifOS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */#endifINT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */INT8U OSTCBStat; /* Taskstatus*/INT8U OSTCBStatPend; /* Task PEND status*/INT8U OSTCBPrio; /* Task priority (0 == highest) */INT8U OSTCBX;/* Bit position in group corresponding to task priority */INT8U OSTCBY;/* Index into ready table corresponding to task priority */OS_PRIOOSTCBBitX; /* Bit mask to access bit position in ready table*/OS_PRIOOSTCBBitY; /* Bit mask to access bit position in ready group*/#if OS_TASK_DEL_EN > 0uINT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */#endif#if OS_TASK_PROFILE_EN > 0uINT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */INT32U OSTCBCyclesStart;/* Snapshot of cycle counter at start of task resumption */OS_STK*OSTCBStkBase;/* Pointer to the beginning of the task stack */INT32U OSTCBStkUsed;/* Number of bytes used from the stack */#endif#if OS_TASK_NAME_EN > 0uINT8U *OSTCBTaskName;#endif#if OS_TASK_REG_TBL_SIZE > 0uINT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];#endif} OS_TCB;

OSTCBPrio,每个任务有唯一的优先级,用户任务的优先级为0~61,62和63倍统计任务和空闲任务占用。

μc/OS是以结构体数组的形式生成任务控制块的实体。

OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];

结构体数组OSTCBTbl中元素的个数为最大用户数量+系统任务数。

2.1.2 空闲链表和就绪链表

μc/OS将任务控制块分成两个链表来管理,即空闲任务链表和就绪任务链表。所谓空闲任务控制块,是指未分配给某个任务的任务控制块,创建一个新任务的前提就是系统中有这样的空闲任务块。就绪链表则是将所有的就绪任务栓在一起,如果有新的任务就绪,就要将其任务控制块从空闲链表中取出,加入到就绪链表中。

OS_TCB *OSTCBFreeList; //空闲链表OS_TCB *OSTCBList;//就绪链表

OSTCBTbl中的任务控制块为空闲链表和就绪链表的节点元素,其中的prev、next指针指向其在OSTCBFreeList或OSTCBList中的下一个或者前一个节点。当创建一个任务的时候,就在空闲链表中remove一个元素,然后为其赋值,再将该元素插入就绪链表。将一个TCB从空闲链表插入到就绪链表时,是从OSTCBFreeList的表头取出一个元素,插入OSTCBList的表头。即,OSTCBFreeList永远指向空闲链表的表头,OSTCBList永远指向就绪链表的表头。

2.1.3 任务优先级指针表

即任务优先级指针数组,用来获某优先级的任务的任务控制块地址。

OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1]

任务优先级指针数组的元素为指向OS_TCB的指针。可以快速地根据优先级访问对应任务控制块的内容。

2.1.4 任务堆栈

为了满足任务切换或者响应中断时保存CPU寄存器的内容,以及存储任务私有数据的需要,每个任务都有自己的堆栈。

#define TASK_STK_SIZE 512typedef unsigned int OS_STK;OS_STK TaskStk[OS_MAX_TASKS][TASK_STK_SIZE];

TaskStk中的元素类型为OS_STK,在32位PC系统中为32位无符号数。这里定义了最多用户任务数个用户堆栈,每个用户堆栈的大小为TASK_STK_SIZE。统计任务和空闲任务的堆栈是单独定义的,分别是OSTaskStatStkOSTaskIdleStk。它们作为全局变量定义在ucos_ii.h中。任务堆栈可以由用户自己定义。

OS_EXT OS_STK OSTaskStatStk[OS_TASK_STAT_STK_SIZE];/* Statistics task stack*/OS_EXT OS_STK OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE];/* Idle task stack*/

2.1.5 任务就绪表和就绪组

查找高优先级的任务,与正在运行的任务的优先级进行比较以确定是否进行任务切换时内核在每个时钟中断都需要做的事情。

typedef unsigned char INT8U;#define OS_RDY_TBL_SIZE ((OS_LOWEST_PRIO)/8 + 1)INT8U OSRdyGrp;INT8U OSRdyTbl[OS_RDY_TBL_SIZE];

OSRdyTbl是每个元素都为8位无符号数的数组,数组中的元素个数是OS_RDY_TBL_SIZE。

每个就绪的任务在就绪表中的对应位置为1,反之为0.只要就绪表OSRdyTbl[n]中有一位不为0,那么就绪组OSRdyGrp对应的位,值为1;只有当OSRdyTbl[n]中的所有位都为0,OSRdyGrp的第n位才为0。

OSRdyTbl为一个数组,假设数组元素数量为8(当最低优先级为63时),每个元素都为一个无符号8位整数,这样就可以把OSRdyTbl看做一个二维数组,行数为8,列数为8。每一行对应8个Task,当任务为就绪时,其在OSRdyTbl中对应的位置为1。

OSRdyTbl的每一行对应于OSRdyGrp的一个位。这一行全为1时对应OSRdyGrp的位为0,否则为1。

如何修改OSRdyGrp以及OSRdyTbl?

关键是要知道某一优先级应该对应OSRdyTbl的哪一行,假设位于n行,就将OSRdyGrp的第n位设置为1。

利用一个查找表OSMapTbl:

INT8U const OSMapTbl[]={0x01,0x02,0x03,0x04,0x08,0x10,0x20,0x40,0x80};

对于优先级prio,将其右移3位,得到其在OSMapTbl中的位置,然后用OSRdyGrp与对应位置元素进行异或操作即可。

比如prio为25,25>>3后,即25/8=3,即将OSRdyGrp的第三位设置为1,OSMapTbl[3]为00001000,异或后就完成了对OSRdyGrp的修改。对OSRdyTbl的修改也是相同的思想,主要是找一个映射关系。

OSRdyGrp |= OSMapTbl[prio>>3];OSRdyTbl[prio>>3] |= OSMapTbl[prio&0x07];

利用分组管理的方式,可以使OS在查找当前最高优先级时花费的时间是固定的。查找就绪任务中的最高优先级:

y=OSUnMapTbl[OSRdyGrp];OSPrioHighRdy=(INT8U) ((y<<3) + OSUnMapTbl[OSRdyTbl[y]);

原理:

假设OSRdyGrp中最低为1的位的y号位,决定了在就绪表中哪一行。再接下来是找列号。OSUnMapTbl就是为了这个目的设计的表。

直接查询优先级判定表OSUnMapTbl[n]就可以找到n的二进制表示法中最低为1的位的序号。在这里就是得到OSRdyTbl的元素序号(行号),再使用OSUnMapTbl[OSRdyTbl[y]]就是该行中最低的为1的位的号数,也就是就绪表中的列号。然后y<<3(==y*8)+列号就得到最高优先级。

2.2 任务控制块初始化

2.2.1 代码解析

任务控制块初始化函数OS_TCBInit,当任务被创建时用来初始化任务控制块。

INT8U OSTCBInit(INT8U prio,OS_STK *ptos,OS_TSK *pbos,INT16U id,INT32U stk_size,Void *pext,INT16U opt)

prio:被创建任务的优先级ptos:任务堆栈栈顶地址pbos:任务堆栈栈底的地址。id:任务的IDstk_size:堆栈的大小pext:任务控制块的扩展块的地址opt:其他选项

2.2.2 流程分析

OS_TCBInit中,首先查询空闲任务链表OSTCBFreeList,如果有空闲任务控制块,则取出空闲任务控制块,对该TCB中的各个字段完成赋值。注意,这里的OSTCBFreeList是一个全局变量,每当访问一次全局数据时都需要关闭系统中断

OS_ENTER_CRITICAL();···critical rigion···OS_EXIT_CRITICAL();

随后,调用两个钩子函数,这两个钩子函数允许用户自己填写代码,默认为空。随后对任务优先级指针表和就绪组及就绪表进行相关操作。

2.3 操作系统初始化

操作系统初始化函数OSInit是操作系统在开始运行时,对全局变量、任务控制块、就绪表、事件及消息队列等重要数据结构进行的初始化操作。并创建空闲任务、统计任务等系统任务。

2.3.1 代码解析

void OSInit (void){#if OS_TASK_CREATE_EXT_EN > 0u#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)INT8U err;#endif#endifOSInitHookBegin(); /* Call port specific initialization code */OS_InitMisc(); /* Initialize miscellaneous variables */OS_InitRdyList(); /* Initialize the Ready List*/OS_InitTCBList(); /* Initialize the free list of OS_TCBs*/OS_InitEventList(); /* Initialize the free list of OS_EVENTs */#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)OS_FlagInit(); /* Initialize the event flag structures*/#endif#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)OS_MemInit(); /* Initialize the memory manager */#endif#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)OS_QInit();/* Initialize the message queue structures */#endif#if OS_TASK_CREATE_EXT_EN > 0u #id defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)OS_TLS_Init(&err); /* Initialize TLS, before creating tasks */if (err != OS_ERR_NONE) {return;}#endif#endifOS_InitTaskIdle(); /* Create the Idle Task */#if OS_TASK_STAT_EN > 0uOS_InitTaskStat(); /* Create the Statistic Task*/#endif#if OS_TMR_EN > 0uOSTmr_Init(); /* Initialize the Timer Manager */#endifOSInitHookEnd();/* Call port specific init. code */#if OS_DEBUG_EN > 0uOSDebugInit();#endif}}

OS_InitMisc实现对一些杂项全局变量的初始化。

OS_InitRdyList将就绪表和就绪组全部清零,然后对四个重要的任务相关的全局变量进行了初始化,这四个全局变量定义在μcos_ii.h中。

static void OS_InitRdyList (void){INT8U i;OSRdyGrp= 0u;/* Clear the ready list */for (i = 0u; i < OS_RDY_TBL_SIZE; i++) {OSRdyTbl[i] = 0u;}OSPrioCur= 0u;OSPrioHighRdy = 0u;OSTCBHighRdy = (OS_TCB *)0;OSTCBCur= (OS_TCB *)0;}

OS_InitTCBListTCB的两个链表——空闲任务链表和就绪任务链表进行初始化,此时系统中没有就绪用户任务,所以就绪链表为空,空闲链表长度为最长。

OS_InitEventList

OS_FlagInit

OS_MemInit

os_QInit

OS_InitTaskIdle将会创建操作系统的第一个任务——空闲任务。这一部分的解释放在任务创建部分作为例子讲解。

OS_InitTaskStat创建一个统计任务。

2.4 任务的创建

2.4.1 OSTaskCreate代码解析

主要是对任务运行需要的数据结构进行赋值。

task指向任务代码,p_arg是一个指针,指向用来给task任务传值的数据结构,ptos指针指向任务栈顶。prio表示任务优先级。

#if OS_TASK_CREATE_EN > 0uINT8U OSTaskCreate (void (*task)(void *p_arg),void *p_arg,OS_STK *ptos,INT8U prio){OS_STK*psp;INT8U err;#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register*/OS_CPU_SR cpu_sr = 0u;#endif#ifdef OS_SAFETY_CRITICAL_IEC61508if (OSSafetyCriticalStartFlag == OS_TRUE) {OS_SAFETY_CRITICAL_EXCEPTION();return (OS_ERR_ILLEGAL_CREATE_RUN_TIME);}#endif#if OS_ARG_CHK_EN > 0uif (prio > OS_LOWEST_PRIO) {/* Make sure priority is within allowable range */return (OS_ERR_PRIO_INVALID);}#endifOS_ENTER_CRITICAL();if (OSIntNesting > 0u) {/* Make sure we don't create the task from within an ISR */OS_EXIT_CRITICAL();return (OS_ERR_TASK_CREATE_ISR);}if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {/* Make sure task doesn't already exist at this priority */OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... *//* ... the same thing until task is created. */OS_EXIT_CRITICAL();psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);if (err == OS_ERR_NONE) {OS_TRACE_TASK_CREATE(OSTCBPrioTbl[prio]);if (OSRunning == OS_TRUE) {/* Find highest priority task if multitasking has started */OS_Sched();}} else {OS_TRACE_TASK_CREATE_FAILED(OSTCBPrioTbl[prio]);OS_ENTER_CRITICAL();OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */OS_EXIT_CRITICAL();}return (err);}OS_EXIT_CRITICAL();return (OS_ERR_PRIO_EXIST);}#endif

创建任务时,需要给任务分配堆栈空间。堆栈是在操作系统变量声明中已经分配好了的。因此要将该堆栈空间分配给该任务。执行这个任务的是OSTaskSktInit函数,

OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt){/*task是函数指针,指向任务代码的地址*//*p_arg:任务代码的参数*//*ptos:任务堆栈栈顶指针*//*opt:初始化选项*/OS_TASK_STK *p_stk;/* Load stack pointer */p_stk= (OS_TASK_STK *)((char *)ptos - sizeof(OS_TASK_STK));p_stk->TaskArgPtr = p_arg;p_stk->TaskOpt = opt;p_stk->Task= task;p_stk->ThreadHandle = NULL;p_stk->ThreadID= 0u;p_stk->TaskState= STATE_NONE;p_stk->SignalPtr= NULL;p_stk->InitSignalPtr = NULL;p_stk->Terminate= DEF_FALSE;p_stk->OSTCBPtr= NULL;return ((OS_STK *)p_stk);}

这里是用一个OS_TASK_STK结构体来保存任务堆栈的信息

typedef struct os_task_stk {void *TaskArgPtr;INT16U TaskOpt;void (*Task)(void*);HANDLE ThreadHandle;DWORD ThreadID;volatile OS_TASK_STATE TaskState;HANDLE SignalPtr; /* Task synchronization signal.*/HANDLE InitSignalPtr; /* Task created signal.*/CPU_BOOLEANTerminate; /* Task terminate flag.*/OS_TCB*OSTCBPtr;} OS_TASK_STK;

在任务堆栈初始化的过程中,任务代码的地址被压入任务堆栈。

在任务堆栈初始化完成以后,OS_TCBInit对任务的TCB进行初始化。

OS_TCBInit函数在前面已经有了分析,主要就是判断是否有空闲TCB,如果有的话就从TCBFreeList中取出一个TCB然后对其中的各个字段进行初始化。在任务创建过程中已经初始化了任务堆栈,所以任务TCB中将会记录任务堆栈的位置等信息。

ptcb->OSTCBStkSize = stk_size;/* Store stack size*/ptcb->OSTCBStkBottom= pbos; /* Store pointer to bottom of stack */

2.4.2 OSTaskCreate流程分析

2.4.3 OSTaskCreateExt代码解析

2.5 任务的删除

2.5.1 任务删除代码解析

2.5.2 任务删除流程分析

任务删除代码较为复杂,主要是需要将任务涉及到的方方面面都要进行处理。但总结来说,无外乎先进行参数的检查,然后如果该任务在等待某些事件,就删除等待的标志,之后对TCB中的值进行修改,对就绪表、就绪组、任务优先级指针表、任务就绪链表、空闲任务链表等重要的数据结构进行与创建任务相反的操作。

2.5.3 请求删除任务代码解析

2.6 任务挂起和恢复

任务在创建后,将从睡眠态转移到就绪态,就绪的任务如果调用OSTaskSuspend将会被阻塞,也就是被剥夺CPU使用权,转到阻塞状态。被阻塞的任务不能运行,除非其他任务以该任务的优先级作为参数调用OSTaskResume来恢复它,才能将该任务的状态重新设置为就绪态。

2.7 任务的调度和多任务的启动

2.7.1 任务调度器

在启动多任务以后,每个时钟中断都要执行任务的调度。执行任务调度的函数就是OSTimeTick

void OSTimeTick (void){OS_TCB *ptcb;BOOLEAN step;OSTimeTickHook(); /*调用用户钩子函数,默认是空函数 */#if OS_TIME_GET_SET_EN > 0uOS_ENTER_CRITICAL();/* Update the 32-bit tick counter*/OSTime++;OS_EXIT_CRITICAL();#endifif (OSRunning == OS_TRUE) {#if OS_TICK_STEP_EN > 0uswitch (OSTickStepState) {/* Determine whether we need to process a tick */case OS_TICK_STEP_DIS:/* Yes, stepping is disabled*/step = OS_TRUE;break;case OS_TICK_STEP_WAIT: /* No, waiting for uC/OS-View to set ... */step = OS_FALSE;/*.. OSTickStepState to OS_TICK_STEP_ONCE */break;case OS_TICK_STEP_ONCE: /* Yes, process tick once and wait for next ... */step = OS_TRUE;/*... step command from uC/OS-View */OSTickStepState = OS_TICK_STEP_WAIT;break;default: /* Invalid case, correct situation */step = OS_TRUE;OSTickStepState = OS_TICK_STEP_DIS;break;}if (step == OS_FALSE) {/* Return if waiting for step command */return;}#endifptcb = OSTCBList; /* Point at first TCB in TCB list*/while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {/* Go through all TCBs in TCB list */OS_ENTER_CRITICAL();if (ptcb->OSTCBDly != 0u) {/* No, Delayed or waiting for event with TO*/ptcb->OSTCBDly--;/* Decrement nbr of ticks to end of delay */if (ptcb->OSTCBDly == 0u) {/* Check for timeout */if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;/* Yes, Clear status flag */ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */} else {ptcb->OSTCBStatPend = OS_STAT_PEND_OK;}if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {/* Is task suspended? */OSRdyGrp|= ptcb->OSTCBBitY; /* No, Make ready*/OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;}}}ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list*/OS_EXIT_CRITICAL();}}}

ptcb指向就绪链表表头。就绪任务链表的最后一个元素是空闲任务,当ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO时,说明就绪队列中有其他就绪任务。

OSTimeTick函数做的事就是遍历就绪队列,对延时的任务修改延时时间,然后将延时时间用完的任务设置为就绪。并没有真正执行任务切换。OSTimeTick在每个时钟中断时被调用。

2.7.2 任务切换函数

任务切换函数最重要的两个步骤是:

保存当前进程的运行上下文加载下一个进程的运行上下文

2.7.3 中断中的任务调度

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。