实时操作系统UCOS学习笔记6----UCOSIII任务管理

多任务操作系统最主要的就是对任务的管理,包括任务的创建、挂起、删除和调度等,因此对于UCOSIII操作系统中任务管理的理解就显得尤为重要。本章就讲解UCOSIII中的任务管理。

1、UCOSIII启动和初始化

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

  • 最先肯定是要调用OSInit()初始化UCOSIII。
  • 创建任务,一般我们在main函数中只创建一个start_task任务,其他任务都在start_task任务中创建,在调用OSTaskCreate()函数创建任务的时候一定要调用OS_CRITICAL_ENTER()函数进入临界区,任务创建完以后调用OS_CRITICAL_EXIT()函数退出临界区。
  • 最后调用OSSTART()函数开启UCOSIII。
    在这里插入图片描述

从上面代码可以看出我们是按照前面提到的步骤来使用UCOSIII,首先是OSInit()初始化UCOSIII,然后创建以一个start_task()任务,最后调用OSStart()函数开启UCOSIII.
**注意:**在调用OSStart()开启UCOSIII之前一定要至少创建一个任务,其实我们在调用OSInit()函数初始化UCOSIII的时候已经创建了一个空闲任务。

2、任务状态

UCOSIII支持的是单核CPU,不支持多核CPU,这样在某一时刻只有一个任务会获得CPU使用权进入运行态,其他的任务就会进入其他状态,UCOSIII中的任务有多个状态,如图所示:
在这里插入图片描述

在UCOSIII中任务可以在5个状态中切换,转换关系如图:

在这里插入图片描述

3、任务控制块

在学习UCOSII的时候我们知道有个重要的数据结构:任务控制块OS_TCB,在UCOSIII中也有任务控制块OS_TCB。任务控制块TCB用来保存任务的信息,我们使用OSTaskCreate()函数来创建任务的时候就会给任务分配一个任务控制块。任务控制块时一个结构体,这个结构体如下,这里我们去掉了条件编译语句。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

从上面os_tcb结构体中可以看出UCOSIII的任务控制块要比UCOSII的要复杂的多,这也间接说明了UCOSIII要比UCOSII功能要强大的多。

4、任务堆栈

在UCOSIII中任务堆栈是一个非常重要的概念,任务堆栈用来在切换任务和调用其他函数的时候保存现场,因此每个任务都应该有自己的堆栈,我们可以按照下面的步骤创建一个堆栈:
定义一个CPU_STK变量,在UCOSIII中用CPU_STK数据类型来定义任务堆栈,CPU_STK在cpu.h中有定义,其实CPU_STK就是CPU_INT32U,可以看出一个CPU_STK变量为4字节,因此任务的实际堆栈大小应该为我们定义的4倍。下面代码就是我们定义了一个任务堆栈TASK_STK,堆栈大小为64*4=256字节。在这里插入图片描述

我们可以使用下面的方法定义一个堆栈,这样代码比较清晰。
在这里插入图片描述

我们使用OSTaskCreate()函数创建任务的时候就可以把创建的堆栈传递给任务,如下红色字体所示将创建的堆栈传递给任务,将堆栈的基地址传递给OSTaskCreate()函数的参数p_stk_base,将堆栈深度传递给参数stk_limit,堆栈深度通常为堆栈大小的十分之一,主要用来检测堆栈是否为空,将堆栈大小传递给参数stk_size
在这里插入图片描述

创建任务的时候会初始化任务的堆栈,我们需要提前将CPU的寄存器保存在任务堆栈中,完成这个任务的是OSTaskStkInit()函数,这个函数大家应该不陌生的,我们在移植UCOSIII的时候专门讲解过这个函数,用户不能调用这个函数,这个函数是被OSTaskCreate()函数在创建任务的时候调用的。

5、任务就绪表

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

1、优先级位映射表(标记哪些任务就绪)

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

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

有关于优先级的操作有3个函数:OS_PrioGetHighest()、OS_PrioInsert()和OS_PrioRemove()。分别为获取就绪表中最高优先级任务、将某个任务在就绪表中相对应的位 置1、将某个任务在就绪表中相对应的位清零,OS_PrioGetHighest()函数代码如下:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2、就绪任务列表(记录每一个优先级下所有就绪的任务)

上一小节详细的讲解了优先级位映射表OSPrioTbl[],这个表主要是用来标记哪些任务就绪了的,这一节我们要讲的就绪任务列表OSRdyList[]是用来记录每一个优先级下所有就绪的任务,OSRdyList[]在os.h中有定义,数组元素的类型为OS_RDY_LIST,OS_RDY_LIST为一个结构体,结构体定义如下:
在这里插入图片描述UCOSIII支持时间片轮转调度,因此在一个优先级下会有多个任务,那么我们就要对这些任务做一个管理,这里使用OSRdyList[]数组管理这些任务。OSRdyList[]数组中的每个元素对应一个优先级,比如OSRdyList[0]就用来管理优先级0下的所有任务。OSRdyList[0]为OS_RDY_LIST类型,从上面OS_RDY_LIST结构体可以看到成员变量:HeadPtr和TailPtr分别指向OS_TCB,我们知道OS_TCB是可以用来构造链表的,因此同一个优先级下的所有任务是通过链表来管理的,HeadPtr和TailPtr分别指向这个链表的头和尾,NbrEntries用来记录此优先级下的任务数量,下图表示了优先级4现在有3个任务时候的就绪任务列表。
在这里插入图片描述

上图展示了在优先级4下面有3个任务,这3个任务组成一个链表,OSRdyList[4]的HeadPtr指向链表头,TailPtr指向链表尾,NbrEntries为3,表示一共有3个任务。注意有些优先级只能有一个任务,比如UCOSIII自带的5个系统任务:空闲任务OS_IdleTask()、时钟节拍任务OS_TickTask()、统计任务OS_StatTask()、定时任务OS_TmrTask()和中断服务管理任务OS_IntQTask()。

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

在这里插入图片描述

6、任务调度和切换

1、可剥夺型调度

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

① 任务级调度器(OSSched())
任务级调度器为OSSched(),OSSched()函数代码在ox.core.c文件中,如下所示:
在这里插入图片描述
在这里插入图片描述

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

② 中断级调度器(OSIntExit())
中断级调度器为OSIntExit(),代码如下,调用OSIntExit()时,中断应该是关闭的。
在这里插入图片描述
在这里插入图片描述

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

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

2、时间片轮转调度
前面多次提到UCOSIII支持多个任务同时拥有一个优先级,要使用这个功能我们需要定义OS_CFG_SCHED_ROUND_ROBIN_EN为1,这些任务的调度是一个值得考虑的问题,不过这不是我们要考虑的,貌似说了一句废话。在UCOSIII中允许一个任务运行一段时间(时间片)后让出CPU的使用权,让拥有同优先级的下一个任务运行,这种任务调度方法就是时间片轮转调度。下图展示了运行在同一个优先级下的执行时间框图,在优先级N下有3个就绪的任务,我们将时间片划分为4个时钟节拍。
在这里插入图片描述

  1. 任务3正在运行,这时一个时钟节拍中断发生,但是任务3的时间片还没用完。
  2. 任务3的时间片用完。
  3. COSIII切换到任务1,任务1是优先级N下的下一个就绪任务。
  4. 任务1连续运行至时间片用完。
  5. 任务3运行。
  6. 任务3调用OSSchedRoundRobinYield()(在os_core.c文件中定义)函数放弃剩余的时间片,从而使优先级N下的下一个就绪的任务运行。
  7. UCOSIII切换到任务1.
  8. 任务1执行完其时间片。

我们前面讲了任务级调度器和中断级调度器,这里我们要讲解的肯定是时间片轮转调度器,如果当前任务的时间片已经运行完,但是同一个优先级下有多个任务,那么UCOSIII就会切换到该优先级对应的下一个任务,通过调用OSSchedRoundRobin()函数来完成,这个函数由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
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值