第五章 UCOSIII任务管理

/*

	  抄的正点原子的文档,我自己做学习笔记用的。

*/

多任务操作系统最主要的就是对 任务 的管理:
包括任务的 创建、删除,挂起、恢复,等

因此对于 UCOSIII操作系统中任务管理的理解就显得尤为重要。
本章我们就讲解 UCOSIII中的任务管理,

本章分为如下几个部分:
5.1 UCOSIII启动和初始化
5.2任务状态
5.3任务控制块
5.4任务堆栈
5.5任务就绪表
5.3任务调度和切换

5.1 UCOSIII启动和初始化

在使用UCOSIII的时候,我们需要按照一定的顺序去初始化并打开:、

(1)调用 OSInit() 初始化UCOSIII
(2)调用函数 OS_CRITICAL_ENTER() 进入临界区
. . . . . 创建任务
. . . . . 调用函数 OS_CRITICAL_EXIT() 退出临界区
(3)调用 OSStart() 函数开启UCOSIII

5.2任务状态

UCOSIII支持的是单核的CPU ,不支持多核CPU 。
这样某一个绝对的时间内只有一个任务正在使用CPU ,也就是说,CPU在绝对的时间内只能处理一个任务,那么其他的任务就需要进行等待或者其他的操作。
UCOSIII中有多种状态,如下表所示:

任务状态描述
运行态正在运行的任务称为运行态,当前正在使用CPU
等待态正在运行的任务需要等待某一个是件,比如信号量,消息,事件等,就会暂时的让出CPU的使用权,进入等待的状态,也就是说,任务还要继续执行,但是暂时让出CPU 执行其他的任务
就绪态任务在就绪表中已经进行了登记,等待获取CPU的使用权,也就是准备开始执行任务,但是还没有执行
休眠态任务只是以任务函数的方式存在,只是存储区中的一段代码,并未用OSTaskCreate()函数创建这个任务,不受UCOSII管理的。
中断服务态一个正在执行的任务被中断打断,cpu转而执行中断服务程序,这时这个任务就会被挂起,进入中断服务态。

在这里插入图片描述

5.3任务控制块

在这里插入图片描述
在这里插入图片描述
可以看到函数原型的第一个参数就是 任务控制块 : OS_TCB

在这里插入图片描述
继续跳转找到函数的原型。

在这里插入图片描述
是一个很长的结构体,下面来分析一下这个结构体。


/*
    时间: 2021年12月27日 23点22分
    姓名:陈旭
    说明: UCOSIII 源码阅读
*/

struct os_tcb {
    CPU_STK             *StkPtr;    /* 指向当前任务堆栈的栈顶 Pointer to current top of stack   */
    void                *ExtPtr;    /* 指向用户可定义的数据区 Pointer to user definable data for TCB extension       */
    CPU_STK             *StkLimitPtr;   /* 指向任务堆栈中的某个位置 Pointer used to set stack 'watermark' limit            */
    OS_TCB              *NextPtr;   /* NexPtr和  PrevPtr用于在任务就绪表建立   OS_TCB    Pointer to next     TCB in the TCB list*/
    OS_TCB              *PrevPtr;   /* 双向链表 Pointer to previous TCB in the TCB list*/
    OS_TCB              *TickNextPtr;  //TickNextPtr和 TickPrevPtr可把正在延时或在指定时   
    OS_TCB              *TickPrevPtr;  //间内等待某个事件的任务的 OS_TCB构成双向链表 
    OS_TICK_SPOKE       *TickSpokePtr;  /* 通过该指针可知道该任务在时钟节拍轮的哪个spoke上  Pointer to tick spoke if task is in the tick list      */
    CPU_CHAR            *NamePtr;       /* 任务名 Pointer to task name  */
    CPU_STK             *StkBasePtr;    /* 任务堆栈基地址 Pointer to base address of stack  */
    OS_TASK_PTR          TaskEntryAddr; /* 任务代码入口地址 Pointer to task entry point address */
    void                *TaskEntryArg;  /* 传递给任务的参数 Argument passed to task when it was created */
    OS_PEND_DATA        *PendDataTblPtr;    /* 指向一个表,包含有任务等待的所有事件对象的信息 Pointer to list containing objects pended on */
    OS_STATE             PendOn;    /* 任务正在等待的事件的类型 Indicates what task is pending on    */
    OS_STATUS            PendStatus;    /* 任务等待的结果 Pend status  */
    OS_STATE             TaskState; /* 任务的当前状态 See OS_TASK_STATE_xxx    */
    OS_PRIO              Prio;      /* 任务优先级 Task priority (0 == highest) */
    CPU_STK_SIZE         StkSize;   /* 任务堆栈大小 Size of task stack (in number of stack elements)       */
    OS_OPT               Opt;       /* 保存调用 OSTaskCreat()创建任务时的可选参数   Task options as passed by OSTaskCreate()*/
    OS_OBJ_QTY           PendDataTblEntries;    /* 任务同时等待的事件对象的数目   Size of array of objects to pend on   */
    CPU_TS               TS;        /* 存储事件发生时的时间戳  Timestamp    */
    OS_SEM_CTR           SemCtr;    /* 任务内建的计数型信号量的计数值  Task specific semaphore counter  */
    OS_TICK              TickCtrPrev;   /*  存储 OSTickCtr 之前的数值  Previous time when task was            ready           */
    OS_TICK              TickCtrMatch;  /* 任务等待延时结束时,当 TickCtrMatch和OSTickCtr的数值相匹配时,任务延时结束    Absolute time when task is going to be ready           */
    OS_TICK              TickRemain;    /* 任务还要等待延时的节拍数 Number of ticks remaining for a match (updated at ...  */                                            
    OS_TICK              TimeQuanta;    // TimeQuanta   和  TimeQuantaCtr与时间片有关
    OS_TICK              TimeQuantaCtr;

#if OS_MSG_EN > 0u
    void                *MsgPtr;    /* 指向任务接收到的消息     Message received */
    OS_MSG_SIZE          MsgSize;   //任务接收到消息的长度v        
#endif
#if OS_CFG_TASK_Q_EN > 0u
    OS_MSG_Q             MsgQ;  /* UCOSIII允许任务或  ISR向任务直接发送消息,MsgQ就为这个消息队列  Message queue associated with task   */
#if OS_CFG_TASK_PROFILE_EN > 0u
    CPU_TS               MsgQPendTime;  /* 记录一条消息到达所花费的时间 Time it took for signal to be received   */
    CPU_TS               MsgQPendTimeMax;   /*记录一条消息到达所花费的最长时间 Max amount of time it took for signal to be received */
#endif
#endif
#if OS_CFG_TASK_REG_TBL_SIZE > 0u
    OS_REG               RegTbl[OS_CFG_TASK_REG_TBL_SIZE];  /* 寄存器表,和 CPU寄存器不同 Task specific registers                                */
#endif
#if OS_CFG_FLAG_EN > 0u
    OS_FLAGS             FlagsPend; /* 任务正在等待的事件的标志位   Event flag(s) to wait on */
    OS_FLAGS             FlagsRdy;  /* 任务在等待的事件标志中有哪些已经就绪     Event flags that made task ready to run  */
    OS_OPT               FlagsOpt;  /* 任务等待事件标志组时的等待类型   Options (See OS_OPT_FLAG_xxx)    */
#endif
#if OS_CFG_TASK_SUSPEND_EN > 0u
    OS_NESTING_CTR       SuspendCtr;    /* 任务被挂起的次数 Nesting counter for OSTaskSuspend()  */
#endif
#if OS_CFG_TASK_PROFILE_EN > 0u
    OS_CPU_USAGE         CPUUsage;  /* CPU使用率  CPU Usage of task (0.00-100.00%)                       */
    OS_CPU_USAGE         CPUUsageMax;   /* CPU使用率峰值  CPU Usage of task (0.00-100.00%) - Peak                */
    OS_CTX_SW_CTR        CtxSwCtr;  /* 任务执行的频繁程度  Number of time the task was switched in                */
    CPU_TS               CyclesDelta;   /* 该成员被调试器或运行监视器利用  value of OS_TS_GET() - .CyclesStart                    */
    CPU_TS               CyclesStart;   /* 任务已经占用 CPU多长时间 Snapshot of cycle counter at start of task resumption  */
    OS_CYCLES            CyclesTotal;   /* 表示一个任务总的执行时间 Total number of # of cycles the task has been running  */
    OS_CYCLES            CyclesTotalPrev;   /* Snapshot of previous # of cycles                       */
    CPU_TS               SemPendTime;   /* 记录信号量发送所花费的时间 Time it took for signal to be received                 */
    CPU_TS               SemPendTimeMax;    /*记录信号量发送到一个任务所花费的最长时间  Max amount of time it took for signal to be received   */
#endif
#if OS_CFG_STAT_TASK_STK_CHK_EN > 0u
    CPU_STK_SIZE         StkUsed;   /* 任务堆栈使用量  Number of stack elements used from the stack */
    CPU_STK_SIZE         StkFree;   /* 任务堆栈剩余量  Number of stack elements free on   the stack */
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN
    CPU_TS               IntDisTimeMax; /* 该成员记录任务的最大中断关闭时间   Maximum interrupt disable time                         */
#endif
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
    CPU_TS               SchedLockTimeMax;  /* 该成员记录锁定调度器的最长时间 Maximum scheduler lock time*/
#endif

#if OS_CFG_DBG_EN > 0u
    OS_TCB              *DbgPrevPtr;  //下面 3个成语变量用于调
    OS_TCB              *DbgNextPtr;
    CPU_CHAR            *DbgNamePtr;
#endif
};




5.4任务堆栈

任务堆栈用来在切换任务和调用其他函数的时候,保存现场。因此每个任务应该都有自己的堆栈,我们可以按照下面的步骤创建一个堆栈:

1:定义一个 CPU_STK 变量,在 UCOSIII 中用 CPU_STK数据类型来定义任务堆栈,CPU_STK在 cpu.h中有定义,其实 CPU_STK就是 CPU_INT32U,可以看出一个 CPU_STK变量为 4字节,因此任务的实际堆栈大小应该为我们定义的 4倍。

下面代码就是我们定义了一个任务堆栈 TASK_STK,堆栈大小为 64*4=256字节。

CPU_STK    TASK_STK[64];    //定义一个任务堆栈

我们可以使用下面的方法定义一个堆栈,这样代码比较清晰,
我们所有例程都使用下面的方法定义堆栈。

#define	TASK_STK_SIZE	64			//任务堆栈大小
CPU_STK	TASK_STK[TASK_STK_SIZE];    //任务堆栈

我们使用 OSTaskCreat()函数创建任务的时候
就可以把创建的堆栈传递给任务,如下红色字体所示将创建的堆栈传递给任务,

将堆栈的基地址传递给 OSTaskCreate() 函数的参数 p_stk_base,
将堆栈深度传递给参数 stk_limit,堆栈深度通常为堆栈大小的十分之一,主要用来检测堆栈是否为空,
将堆栈大小传递给参数 stk_size。
在这里插入图片描述

创建任务的时候会初始化任务的堆栈,我们需要提前将CPU的寄存器保存在任务堆栈中,
完成这个任务的是 OSTaskStkIlnit() 函数,用户不能调用这个函数,
这个函数是被 OSTaskCreate() 函数在创建任务的时候调用的。

5.5任务就绪表

UCOSIII 中将已经将就绪的任务放到任务就绪表里,任务就绪表有两部分:
优先级位映射表: OSPrioTbl [ ]
就绪任务列表: OSRdyList [ ]

5.5.1优先级位映射表

当某一个任务就绪以后就会将优先级位映射表中相应的位置 1,优先级位映射表如下图所示,该表元素的位宽度可以是 8位,16位或 32位,根据 CPU_DATA(见 cpu.h)的不同而不同,在 STM32F103中我们定义 CPU_DATA为 CPU_INT32U类型的,即 32位宽。UCOSIII中任务数目由宏 OS_CFG_PRIO_MAX配置的(见 os_cfg.h)。

在这里插入图片描述

在图 5.5.1中从左到右优先级逐渐降低,但是每个 OSPrioTbl[]数组的元素最低位在右,最高为在左边,
比如 OSPrioTbl[0]的 bit31为最高优先级 0,bit0为优先级 31。
之所以这样做主要是为了支持一条特殊的指令“计算前导零(CLZ)”,使用这条指令可以快速的找到最高优先级任务。

有关于优先级的操作有 3个函数:

	OS_PrioGetHighest();  //获取就绪表中最高优先级任务
	OS_PrioInsert();      //将某个任务在就绪表中相对应的位置 1
	OS_PrioRemove();      //将某个任务在就绪表中相对应的位清零

在这里插入图片描述

OS_PRIO  OS_PrioGetHighest (void)
{
    CPU_DATA  *p_tbl;
    OS_PRIO    prio;
    prio  = (OS_PRIO)0;
    p_tbl = &OSPrioTbl[0];  // 从 OSPrioTbl[0]开始扫描映射表,一直到遇到非零项
    while (*p_tbl == (CPU_DATA)0) /* Search the bitmap table for the highest priority       */
        {                         
            prio += DEF_INT_CPU_NBR_BITS;   /* Compute the step of each CPU_DATA entry                */
            p_tbl++;
        }
    //一旦找到一个非零项,在加上该项的前导零数量就找到了最高优先级任务了。
    prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl);              /* Find the position of the first bit set at the entry    */
    return (prio);
}

//当数组 OSPrioTbl[] 中的某个元素为0的时候,就继续扫描下一个数组元素,           
// prio 加 DEF_INT_CPU_NBR_BITS 位,根据  CPU_DATA长度的不同  DEF_INT_CPU_NBR_BITS值不同,
//我们定义 CPU_DATA为  32位,那么  DEF_INT_CPU_NBR_BITS就为   32,prio就加  32。

从 OS_PrioGetHighest()函数可以看出,
计算前导零我们使用了函数 CPU_CntLeadZeros(),这个函数是由汇编编写的,
在 cpu_a.asm文件中,代码如下:

CPU_CntLeadZeros
CLZ	R0, R0		;计算前导零
BX  LR

函数 OS_PrioInsert() 和 OS_PrioRemove()分别为将指定优先级任务相对应的优先级映射表中的位置 1和清零,这两个函数代码如下:

void  OS_PrioInsert (OS_PRIO  prio)
{
    CPU_DATA  bit;
    CPU_DATA  bit_nbr;
    OS_PRIO   ix;
    
    ix             = prio / DEF_INT_CPU_NBR_BITS;
    bit_nbr        = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);
    bit            = 1u;
    bit          <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
    OSPrioTbl[ix] |= bit;
}


void  OS_PrioRemove (OS_PRIO  prio)
{
    CPU_DATA  bit;
    CPU_DATA  bit_nbr;
    OS_PRIO   ix;
    
    ix             = prio / DEF_INT_CPU_NBR_BITS;
    bit_nbr        = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);
    bit            = 1u;
    bit          <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
    OSPrioTbl[ix] &= ~bit;
}

5.5.2就绪任务列表

上一小节详细的讲解了优先级位映射表 OSPrioTbl[],
这个表主要是用来标记哪些任务就绪了的,
这一节我们要讲的就绪任务列表 OSRdyList[] 是用来记录每一个优先级下所有就绪的任务,
OSRdyList[] 在 os.h中有定义,
数组元素的类型为 OS_RDY_LIST,OS_RDY_LIST为一个结构体,结构体定义如下:

struct  os_rdy_list {
    OS_TCB              *HeadPtr;   /* 用于创建链表,指向链表头  Pointer to task that will run at selected priority     */
    OS_TCB              *TailPtr;   /* 用于创建链表,指向链表尾  Pointer to last task          at selected priority     */
    OS_OBJ_QTY           NbrEntries;    /*  此优先级下的任务数量 Number of entries             at selected priority     */
};

在这里插入图片描述

UCOSIII支持时间片轮转调度,因此在一个优先级下会有多个任务,那么我们就要对这些任务做一个管理,
这里使用 OSRdyList[ ] 数组管理这些任务。
OSRdyList[ ] 数组中的每个元素对应一个优先级,比如 OSRdyList[0] 就用来管理优先级0下的所有任务。

OSRdyList[0] 为 OS_RDY_LIST 类型,从上面 OS_RDY_LIST 结构体可以看到成员变量:
HeadPtr 和 TailPtr 分别指向 OS_TCB,
我们知道 OS_TCB是可以用来构造链表的,因此同一个优先级下的所有任务是通过链表来管理的,
HeadPtr和 TailPtr分别指向这个链表的头和尾,
NbrEntries用来记录此优先级下的任务数量,

图 5.5.2表示了优先级 4 现在有 3个任务时候的就绪任务列表。
在这里插入图片描述
图 5.5.2中展示了在优先级 4下面有 3个任务,
这三个任务组成一个链表,OSRdyList[4]的HeadPtr指向链表头,TailPtr指向链表尾,NbrEntries为 3,表示一共有 3个任务。

注意有些优先级只能有一个任务,比如 UCOSIII自带的 5个系统任务:
空闲任务 OS_IdleTask()、
时钟节拍任务 OS_TickTask()、
统计任务 OS_StatTask()、
定时任务 OS_TmrTask()
中断服务管理任务OS_IntQTask()。

针对任务就绪列表的操作有以下 6个函数,如表 5.5.1所示,这些函数都在 os_core.c这个文件中,
这几个函数是 UCOSIII内部使用的,用户程序不能使用。

函数描述
OS_RdyListInit()由 OSInit()调用用来初始化并清空任务就绪列表
OS_RdyListInsertHead()向某一优先级下的任务双向链表头部添加一个任务控制块 TCB
OS_RdyListInsertTail()向某一优先级下的任务双向链表尾部添加一个任务控制块 TCB
OS_RdyListRemove()将任务控制块 TCB从任务就绪列表中删除
OS_RdyListInsertTail()将一个任务控制块 TCB从双向链表的头部移到尾部一个任务控制块 TCB从双向链表的头部移到尾部
OS_RdyListInsert()在就绪表中添加一个任务控制块 TCB

5.6任务调度和切换

5.6.1可剥夺型调度

任务调度和切换就是让就绪表中优先级最高的任务获得 CPU的使用权,UCOSIII是可剥夺型,抢占式的,
可以抢了低优先级任务的 CPU使用权,任务的调度是由一个叫做任务调度器的东西来完成的,
任务调度器有两种:
一种是任务级调度器,
一种是中断级调度器。

1、任务级调度器

任务级调度器为 OSSched(),OSSched()函数代码在 os_core.c文件中,如下所示:

void  OSSched (void)
{
    CPU_SR_ALLOC();
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) {  /* ISRs still nested?  OSSched()为任务级调度器,如果是在中断服务函数中不能使用!  */
        return;                                             /* Yes ... only schedule when no nested ISRs              */
    }
    if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {        /* Scheduler locked?  调度器是否上锁*/
        return;                                             /* Yes                                                    */
    }
    CPU_INT_DIS();
    OSPrioHighRdy   = OS_PrioGetHighest();                  /* Find the highest priority ready                        */
    OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
    if (OSTCBHighRdyPtr == OSTCBCurPtr) {                   /* Current task is still highest priority task?           */
        CPU_INT_EN();                                       /* Yes ... no need to context switch                      */
        return;
    }
#if OS_CFG_TASK_PROFILE_EN > 0u
    OSTCBHighRdyPtr->CtxSwCtr++;                            /* Inc. # of context switches to this task                */
#endif
    OSTaskCtxSwCtr++;                                       /* Increment context switch counter                       */
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
    OS_TLS_TaskSw();
#endif
    OS_TASK_SW();                                           /* Perform a task level context switch                    */
    CPU_INT_EN();
}

在这里插入图片描述
(1)检查 OSSched() 函数是否在中断服务函数中调用,因为 OSSched()为任务级调度函数,因此不能用于中断级任务调度。
(2)检查调度器是否加锁,很明显,如果任务调度器加锁了就不能做任务调度和切换的!
(3)关中断
(4)获取任务就绪表中就绪了的最高优先级任务, OSPrioHighRdy 用来保存当前就绪表中就绪了的最高优先级。
(5)我们需要获取下一次任务切换是要运行的任务,因为 UCOSIII的一个优先级下可以有多个任务,
所以我们需要在这些任务中挑出任务切 换后要运行的任务,在这里可以看出获取的是就绪任务列表中的第一个任务,
OSTCBHighRdyPtr指向将要切换任务的 OS_TCB。
(6)判断要运行的任务是否是正在运行的任务,如果是的话就不需要做任务切换,
OSTCBCurPtr指向正在执行的任务的 OS_TCB。
(7)执行任务切换!
(8)开中断

在 OSSched() 中真正执行任务切换的是宏 OS_TASK_SW()(在 os_cpu.h中定义),
宏OS_TASK_SW()就是函数 OSCtxSW(),

OSCtxSW()是 os_cpu_a.asm中用汇编写的一段代码,
OSCtxSW()要做的就是将当前任务的 CPU寄存器的值保存在任务堆栈中,
也就是保存现场,保存完当前任务的现场后将新任务的 OS_TCB中保存的任务堆栈指针的值加载到 CPU的堆栈指
针寄存器中,最后还要从新任务的堆栈中恢复 CPU寄存器的值。

2中断级调度器

中断级调度器为 OSIntExit(),代码如下,调用 OSIntExit()时,中断应该是关闭的。

在这里插入图片描述
(1)判断 UCOSIII是否运行,如果 UCOSIII未运行的话就直接跳出。
(2) OSIntNestingCtr为中断嵌套计数器,进入中断服务函数后我们要调用 OSIntEnter()函数,
在这个函数中会将 OSIntNestingCtr加 1,用来记录中断嵌套的次数。这里检查 OSIntNestingCtr
是否为 0,确保在退出中断服务函数时调用 OSIntExit()后不会等于负数。
(3) OSIntNestingCtr减 1,因为 OSIntExit()是在退出中断服务函数时调用的,因此中断嵌套
计数器要减 1。
(4)如果 OSIntNestingCtr还大于 0,说明还有其他的中断发生,那么就跳回到中断服务程
序中,不需要做任务切换。
(5)检查调度器是否加锁,如果加锁的话就直接跳出,不需要做任务切换。
(6)接下来的 5行程序和任务级调度器 OSSechd()是一样的,从 OSRdyList[]中取出最高优
先级任务的控制块 OS_TCB。
(7)调用中断级任务切换函数 OSIntCtxSW()。
(8)开中断

在中断级调度器中真正完成任务切换的就是中断级任务切换函数 OSIntCtxSW(),与任务级
切换函数 OSCtxSW()不同的是,由于进入中断的时候现场已经保存过了,所以 OSIntCtxSW()
不需要像 OSCtxSW()一样先保存当前任务现场,只需要做 OSCtxSW()的后半部分工作,也就是
从将要执行的任务堆栈中恢复 CPU寄存器的值。

5.6.2时间片轮转调度

前面多次提到 UCOSIII支持多个任务同时拥有一个优先级,
要使用这个功能我们需要定义 OS_CFG_SCHED_ROUND_ROBIN_EN为 1,
这些任务的调度是一个值得考虑的问题,不过这不是我们要考虑的,
貌似说了一句废话。在 UCOSIII中允许一个任务运行一段时间(时间片)后让出 CPU的使用权,
让拥有同优先级的下一个任务运行,这种任务调度方法就是时间片轮转调度。

图 5.6.1展示了运行在同一优先级下的执行时间图,在优先级 N下有 3个就绪的任务,我们将时间片划分为 4个时钟节拍。
在这里插入图片描述
(1) 任务3正在运行,这时一个时钟节拍中断发生,但是任务3的时间片还没完成。
(2)任务 3的时钟片用完。
(3) UCOSIII切换到任务 1,任务 1是优先级 N下的下一个就绪任务。
(4)任务 1连续运行至时间片用完。
(5)任务 2运行。
(6)任务 3调用 OSSchedRoundRobinYield()(在 os_core.c文件中定义)函数放弃剩余的时间片,
从而使优先级 X下的下一个就绪的任务运行。
(7) UCOSIII切换到任务 1。
(8)任务 1执行完其时间片

我们前面讲了任务级调度器和中断级调度器,
这里我们要讲解的肯定是时间片轮转调度器,如果当前任务的时间片已经运行完,但是同一优先级下有多个任务,
那么 UCOSIII就会切换到该优先级对应的下一个任务,通过调用 OS_SchedRoundRobin() 函数来完成,这个函数由
OSTimeTick() 或者 OS_IntQTask() 调用,函数代码如下。
在这里插入图片描述
(1)首先检查时间片轮转调度是否允许。要允许时间片轮转调度的话需要使用 OSSchedRoundRobinCfg ()函数。
(2)获取某一优先级下就绪任务列表中的第一个任务。
(3)如果 p_tcb为 0,说明没有任务就绪那就直接返回
(4)如果 p_tcb为空闲任务的 TCB那么也就直接返回
(5)任务控制块 OS_TCB中的 TimeQuantaCtr字段表示当前任务的时间片还剩多少,在这里 TimeQuantaCtr减 1。
(6)在经过(5)将 TimeQuantaCtr减 1以后,判断这时的 TimeQuantaCtr是否大于 0,如果大于 0说明任务的时间片还没用完,那么就不能进行任务切换,直接返回。
(7)前面说过就绪任务列表中的 NbrEntries字段表示某一优先级下的任务数量,这里判断NbrEntries是否小于 2,如果任务数小于 2就不需要做任务切换,直接返回。
(8)判断调度器是否上锁,如果上锁的话就直接返回。
(9)当执行到这一步的时候说明当前任务的时间片已经用完,将当前任务的 OS_TCB从双向链表头移到链表尾。
(10)获取新的双向链表头,也就是下一个要执行的任务。
(11)我们要为下一个要执行的任务装载时间片值,一般我们在新建任务的时候会指定的,
这个指定的值被存放在任务控制块 OS_TCB的 TimeQuanta字段中,这里我们判断 TimeQuanta是否为0,
如果为0的话那么任务剩余的时间片TimeQuantaCtr就使用默认值OSSchedRoundRobinDfltTimeQuanta,
如果我们使能了 UCOSIII的时间片轮转调度功能的话,
OSSchedRoundRobinDfltTimeQuanta在我们调用OSInit()函数初始化 UCOSIII的时候就会被初始化为 OSCfg_TickRate_Hz / 10u,
比如 OSCfg_TickRate_Hz为 200的话那么默认的时间片就为20。
(12)如果 TimeQuanta不等于 0,也就是说我们定义了任务的时间片,那么 TimeQuantaCtr就等于 TimeQuanta,也就是我们设置的时间片值。
通过上面的讲解我们可以清晰的看到,如果某一优先级下有多个任务话,这些任务是如何
被调度和运行的,每次任务切换后运行的都是处于就绪任务列表 OSRdyList[]链表头的任务,
当这个任务的时间片用完后这个任务就会被放到链表尾,然后再运行新的链表头的任务。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值