前言
多任务操作系统最主要的就是对任务的管理,包括任务的创建、挂起、删除和调度等,因此对于UCOSⅢ操作系统中任务管理的理解就显得尤为重要。
一、UCOSⅢ启动和初始化
在使用UCOSⅢ的时候我们要按照一定的顺序初始化并打开UCOSⅢ:
1、最先肯定是要调用OSInit()初始化UCOSⅢ。
2、创建任务,一般我们在main()函数中只创建一个start_task任务,其他任务都在start_task任务中创建,在调用OSTaskCreate()函数创建任务的时候一定要调用OS_CRITICAL_ENTER()函数进入临界区,任务创建完以后调用OS_CRITICAL_EXIT()函数退出临界区。
3、最后调用OSStart()函数开启UCOSⅢ。
我们参考以下的代码:
int main(void)
{
OS_ERR err;
CPU_SR_ALLOC();
delay_init(168); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始化
OSInit(&err); //初始化ucosⅢ
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务
OSTaskCreate((OS_TCB* )&StartTaskTCB, //任务控制块
(CPU_CHAR* )”start task”, //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void* )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK* )&START_TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY)0, //任务内部消息队列能够接收的最大消息数目,为0时禁止 //接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度
(void* )0, //用户补充的粗存区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR* )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSⅢ
while(1);
}
首先是OSInit()初始化UCOSⅢ,然后创建一个start_task任务,最后调用OSStart()开启Ⅲ。
我们在调用OSStart()开启Ⅲ之前一定要至少创建一个任务,其实我们在调用OSInit()函数初始化UCOSⅢ的时候已经创建了一个空闲任务。
二、任务状态
UCOSⅢ支持的是单核cpu,不支持多核cpu,这样在某一时刻只有一个任务会获得cpu使用权进入运行态,其他任务就会进入其他状态,Ⅲ中的任务有多个状态,如表1-1所示:
任务状态 | 描述 |
---|---|
休眠态 | 休眠态就是任务只是以任务函数的方式存在,只是存储区中的一段代码,并未用OSTaskCreate()函数创建这个任务,不受UCOSⅢ管理。 |
就绪态 | 任务在就绪表中已经登记,等待获取cpu使用权。 |
运行态 | 正在运行的任务就处于运行态。 |
等待态 | 正在运行的任务需要等待某一事件,比如信号量、消息、事件标志组等,就会暂时让出CPU使用权,进入等待事件的状态。 |
中断服务态 | 一个正在执行的任务被中断打断,cpu转而执行中断服务程序,这时这个任务就会被挂起,进入中断服务态。 |
表1-1
在UCOSⅢ中任务可以在这5个状态中转换,转换关系如图1-1所示:
图1-1 UCOSⅢ任务状态转换图
三、任务控制块
UCOSⅢ有个重要的数据结构:任务控制块OS_TCB,其用来保存任务的信息,我们使用OSTaskCreate()函数来创建任务的时候就会给任务分配一个任务控制块。任务控制块是一个结构体,这个结构体如下,这里我们取掉了条件编译语句。
struct os_tcb{
CPU_STK *StkPtr; //指向当前任务堆栈的栈顶
void *ExtPtrr; //指向用户可定义的数据区
CPU_STK *StkLimitPtr; //可指向任务堆栈中的某个位置
OS_TCB *NextPtr; //NextPtr和PrevPtr用于在任务就绪表建立OS_TCB
OS_TCB *PrevPtr; //双向链表
OS_TCB *TickNextPtr; //TickNext和TickPrevPtr可把正在延时或在指定时间内
OS_TCB *TickPrevPtr; //等待某个事件的任务的OS_TCB构成双向链表
OS_TICK_SPOKE *TickSpokerPtr;//通过该指针可直到该任务在时钟节拍轮的哪个spoke上
CPU_CHAR *NamePtr; //任务名
CPU_STK *StkBasePtr; //任务堆栈基地址
OS_TASK_PTR TaskEntryAddr;//r任务代码入口地址
void *TaskEntryArg;//传递给任务的参数
OS_PEND_DATA *PendDataTblPtr;//指向一个表,包含有任务等待的所有事件对象的信息
OS_STATE PendOn; //任务正在等待的事件的类型
OS_STATUS PendStatus; //任务等待的结果
OS_STATE TaskState; //任务的当前状态
OS_PRIO Prio; //任务优先级
CPU_STK_SIZE StkSize; //任务堆栈的大小
OS_OPT Opt; //保存调用OSTaskCreat()创建任务时的可选参数options的值
OS_OBJ_QTY PendDataTblEntries;//任务同时等待的事件对象数目
CPU_TS TS; //存储事件发生时的时间戳
OS_SEM_CTR SemCtr; //任务内建的计数型信号量的计数值
OS_TICK TickCtrPrev; //存储OSTickCtr之前的数值
OS_TICK TickCtrMatch; //任务等待延时结束时,当TickCtrMatch和OSTickCtr的数值相匹配时,任务延时结束
OS_TICK TickRemain; //任务还要等待延时的节拍数
OS_TICK TimeQuanta; //TImeQuannta和TimeQuantaCtr与时间片有关
OS_TICK TimeQuantaCtr;//
void *MsgPtr; //指向任务接收到的消息
OS_MSG_SIZE MsgSize; //任务接收到的消息的长度
OS_MSG_Q MsgQ; //Ⅲ允许任务或者ISR向任务直接发送消息,MsgQ就为这个消息队列
CPU_TS MsgQPendTime;//记录一条消息到达所花费的时间
CPU_TS MsgQPendTimeMax;//记录一条消息到达所花费的最长时间
OS_REG RegTbl[OS_CFG_TASK_REG_TBL_SIZE];//寄存器表,和CPU寄存器不同
OS_FLAGS FlagsPend; //任务正在等待的事件的标志位
OS_FLAGS FlagsRdy; //任务在等待的事件标志中有哪些已经就绪
OS_OPT FlagsOpt; //任务等待事件标志组时的等待类型
OS_NESTING_CTR SuspendCtr; //任务被挂起的次数
OS_CPU_USAGE CPUUsage; //CPU使用率
OS_CPU_USAGE CPUUsageMax;//CPU使用率峰值
OS_CTX_SW_CTR CtxSwCtr; //任务执行的频繁程度
CPU_TS CyclesDelta; //改成员被调试器或运行监视器利用
CPU_TS CyclesStart; //任务已经占用CpU多长时间
OS_CYCLES CyclesTotal; //表示一个任务总的执行时间
OS_CYCLES CyclesTotalPrev;//
CPU_TS SemPendTime;//记录信号量发送所花费的时间
CPU_TS SemPendTimeMax;//记录信号量发送到一个任务所花费的最长时间
CPU_STK_SIZE StkUsed; //任务堆栈使用量
CPU_STK_SIZE StkFree; //任务堆栈剩余量
CPU_TS IntDisTimeMax;//该成员记录任务的最大中断关闭时间
CPU_TS SshedLockTimeMax;//该成员记录锁定调度器的最长时间
OS_TCB *DbgPrevPtr; //下面3个成语变量用于调试
OS_TCB *DbgNextPtr; //
CPU_CHAR *DbgNamePtr;//
};
四、任务堆栈
在UCOSⅢ中任务堆栈是一个非常重要的概念,任务堆栈用来在任务切换和调用其他函数的时候保存现场,因此每个任务都应该有自己的任务堆栈,我们可以按照下面的步骤创建一个堆栈:
定义一个CPU_ STK变量,在UCOSⅢ中用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[LED1_STK_SIZE];//任务堆栈
我们使用OSTaskCreat()函数创建任务的时候就可以把创建的堆栈传递给任务:
OSTaskCreate((OS_TCB* )&StartTaskTCB, //任务控制块
(CPU_CHAR* )”start task”, //任务名
(OS_TASK_PTP)start_task, //任务函数
(void* )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO,//任务优先级
(CPU_STK* )&TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)TASK_STK_SIZE/10,//任务堆栈深度限位
(CPU_STK_SIZE)TASK_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY)0,
(OS_TICK)0,
(void* )0, //用户补充的储存区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR
(OS_ERR* ),&err); //存放该函数错误时的返回值
创建任务的时候会初始化任务的堆栈,我们需要提前将CPU的寄存器保存在任务堆栈中,完成这个任务的时OSTaskStkInit()函数,用户不能调用这个函数,这个函数是被OSTaskCreate()函数在创建任务的时候调用的。
五、任务就绪表
UCOSⅢ中将已经就绪的任务放到任务就绪表里,任务就绪表有两部分:优先级位映射表OSPrioTbl[]和就绪任务列表OSRdyList[]。
1、优先级位映射表
当某一个任务就绪后就会将优先级位映射表中的位置1,优先级位映射表如图1-2所示,该表元素的宽度可以是8位,16位或者32位,根据CPU_DATA(见cpu.h)的不同而不同,在STM32F407中我们定义CPU_DATA位CPU_INT32U类型的,即32位宽。UCOSⅢ中任务数目由宏OS_CFG_PRIO_MAX配置的(见os_cfg.h)
图1-2 优先级位映射表
在图1-2中从左到右优先级逐渐减低,但是每个OSPrioTbl[]数组的元素最低位在右,最高位在左边,比如OSPrioTbl[0]的bit32为最高优先级0,bit0为优先级32。之所以这样做主要是为了支持一条特殊的指令“计算前导零(CLZ)”,使用这条指令可以快速的找到最高优先级任务。
有关于优先级的操作有三个函数:OS_PrioGetHighest(),OS_PrioInsert(),OS_PrioRemove()。分别为获取就绪表中最高优先级任务、将某个任务在就绪表中相应的位置1和将某个任务在就绪表中相对应的位清零,OS_PrioGetHightest()函数代码如下:
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){
//当数组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
prio += DEF_INT_CPU_NBR_BITS;
p_tbl++;//p_tbl加一,继续寻找OSPrioTbl[]数组的下一个元素
}
//一旦找到一个非零项,在加上该项的前导零数量就找到了最高优先级任务了
prio +=(OS_PRIO)CPU_CntLeadZeros(*p_tbl);
return(prio);
}
//从OS_PrioGetHightest()函数可以看出,计算前导零我们使用了函数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;
PUC_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;
}
2、就绪任务列表
上一小节详细的讲解了优先级位映射表OSPrioTbl[],这个表主要是用来标记哪些任务就绪了的,这一节我们要讲的就绪任务列表OSRdyList[]是用来记录每一个优先级下所有就绪的任务,OSRdyList[]在os.h中有定义,数组元素的类型为OS_RDY_LIST,OS_RDY_LIST为一个结构体,结构体定义如下:
struct os_rdy_list{
OS_TCB *HeadPtr;//用于创建链表,指向链表头
OS_TCB *TailPtr;//用于创建链表,指向链表尾
OS_OBJ_QTY NbrEntries;//此优先级下的任务数量
}
UCOSⅢ支持时间片轮转调度,因此在一个优先级下会有多个任务,那么我们就要对这些任务做一个管理,这里使用OSRdyList[]数组管理这些任务。OSRdyList[]数组中每个元素对应一个优先级,比如OSRdyList[0]就用来管理优先级0下的所有任务。OSRdyList[0]为OS_RDY_LIST类型,从上面OS_RDY_LIST结构体可以看到成员变量:HeadPtr和TailPtr分别指向OS_TCB,我们知道OS_TCB是可以用来构造链表的,因此同一个优先级下的所有任务是通过链表来管理的,HeadPtr和TailPtr分别指向这个链表的头和尾,NbrEntries用来记录此优先级下的任务数量,图1-3表示了优先级4现在有3个任务时候的就绪任务列表。
图1-3 优先级4就绪任务列表
图1-3中展示了在优先级4下面有3个任务,这三个任务组成一个链表,OSRdyList[4]的HeadPtr指向链表头,TailPtr指向链表尾,NbrEntries为3,表示一共有3个任务。注意有些优先级只能有一个任务,比如UCOSⅢ自带的5个系统任务:空闲任务OS_IdleTask()、时钟节拍任务OS_TickTask()、统计任务OS_StatTask()、定时任务OS_TmrTask()和中断服务管理任务OS_IntQTask()。
针对任务就绪列表的操作有以下6个函数,这些函数都在os_core.c这个文件中,这几个函数是UCOSⅢ内部使用的,用户程序不能使用,如下表:
函数 | 描述 |
---|---|
os_rdylistinit() | 由osinit()调用用来初始化并清空任务就绪列表 |
os_rdylistinserthead() | 向某一优先级下的任务双向链表头部添加一个任务控制块TCB |
os_rdylistnserttail() | 向某一优先级下的任务双向链表尾部添加一个任务控制块TCB |
os_rdylistremove() | 将任务控制块TCB从任务就绪列表中删除 |
os_rdylistinserttail() | 将一个任务控制块TCB从双向链表的头部移到尾部 |
os_rdylistinsert() | 在就绪表中添加一个任务控制块TCB |
表1-2 任务就绪表操作函数
六、任务调度和切换
1、可剥夺型调度
任务调度和切换就是让就绪表中优先级最高的任务获得cpu的使用权,UCOUSⅢ是可剥夺型,抢占式的,可以抢了低优先级任务的cpu使用权,任务的调度是由任务调度器完成的,分为两种。任务级调度器和中断级调度器。
①任务级调度器
其为OSSched(),OSSched()函数代码在os_core.c文件中,如下所示:
Void OSSched(void)
{
CPU_SR_ALLOC();
//OSSched()为任务级调度器,如果是在中断服务函数中不能使用!
if(OSIntNestingCtr>(OS_NESTING_CTR)0){ Ⅰ
return;
}
//调度器是否上锁
if(OSSchedLockNestingCtr>(OS_NESTING_CTR)0){ Ⅱ
return;
}
CPU_INT_DIS(); Ⅲ
OSPrioHighRdy =OS_PrioGetHighest(); Ⅳ
OSTCBHighRyPtr =OSRdyList[OSPrioHighRdy].HeadPtr; Ⅴ
if(OSTCBHightRdyPtr==OSTCBCurPtr){ Ⅵ
CPU_INT_EN();
return;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
OCTCBHighRdyPtr->CtxSwCtr++;
#endif
OSTaskCtxSwCtr++;
#if defined(OS_CFG_TLS_TBL_SIZE)&&(OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
OS_TASK_SW(); Ⅶ
CPU_INT_EN();} Ⅷ
Ⅰ检查OSSched函数是否在中断服务函数中调用,因为OSSched为任务级调度函数,因此不能用于中断级任务调度。
Ⅱ检查调度器是否加锁,如果任务调度器加锁了就不能做任务调度和切换
Ⅲ关中断
Ⅳ获取任务就绪表中就绪了的最高优先级任务,OSPrioHighRdy用来保存当前就绪表中就绪了的最高优先级
Ⅴ我们需要获取下一次任务切换要运行的任务
Ⅵ判断要运行的任务是否是正在运行的任务,如果是就不做任务切换,OSTCBCurPtr指向正在执行的任务的OS_TCB.
Ⅶ执行任务切换
Ⅷ开中断
在OSSched()中真正执行任务切换的是宏OS_TASK_SW()(在os_cpu.h中定义),宏OS_TASK_SW(),OSCtxSW()是os_cpu_a.asm中用汇编写的一段代码,OSCtxSW()要做的就是将当前任务的CPU寄存器的值保存在任务堆栈中,也就是保存现场,保存完当前任务的现场后将新任务的OS_TCB中保存的任务堆栈指针的值加载到CPU的堆栈指针寄存器中,最后还要从新任务的堆栈中恢复CPU寄存器的值。
②中断级调度器
中断级调度器为OSIntExit(),代码如下,调用OSIntExit()时,中断应该是关闭的。
void OSIntExit(void)
{
CPU_SR_ALLOC();
if(OSRunning!=OS_STATE_OS_RUNNING){ Ⅰ
return;
}
CPU_INT_DIS();
if(OSIntNestingCtr==(OS_NESTING_CTR)0){ Ⅱ
CPU_INT_EN();
return;
}
OSIntNestingCtr--; Ⅲ
if(OSIntNestingCtr>(OS_NESTING_CTR)0){ Ⅳ
CPU_INT_EN();
return;
}
if(OSSchedLockNestingCtr>(OS_NESTING_CTR)0){ Ⅴ
CPU_INT_EN();
return;
}
OSPrioHighRdy =OS_PrioGetHighest(); Ⅵ
OSTCBHighRdyPtr =OSRdyList[OSPrioHighRdy].HeadPtr;
if(OSTCBHighRdyPtr==OSTCBCurPtr){
CPU_INT_EN();
return;
}
#if defined(OS_CFG_TLS_TBL_SIZE)&&(OS_CFG_TLS_TBL_SIZE>0u)
OS_TLS_TaskSw();
#endif
OSIntCtxSw(); Ⅶ
CPU_INT_EN(); Ⅷ
}
Ⅰ判断Ⅲ是否运行,如果未运行的话就直接跳出
ⅡOSIntNestingCtr为中断嵌套计数器,进入中断服务函数后我们就要调用OSIntEnter()函数,在这个函数中会将OSIntNestingCtr加1,用来记录中断嵌套的次数。这里检查OSIntNestingCtr是否为0,确保在退出中断服务函数时调用OSIntExit()后不会等于负数。
ⅢOSIntNestingCtr减1,因为OSIntExit()是在退出中断服务函数时调用的,因此中断嵌套计数器要减1。
Ⅳ如果OSIntNestingCtr还大于0,说明还有其他的中断发生,那么就跳回到中断服务程序中,不需要做任务切换。
Ⅴ检查调度器是否加锁,如果加锁的话就直接跳出,不需要做任务切换
Ⅵ从OSRdyList[]中取出最高优先级任务的控制块OS_TCB
Ⅶ调用中断任务切换函数OSIntCtxSW()
Ⅷ开中断
在中断级调度器中真正完成任务切换的就是中断级任务切换函数OSIntCtxSW(),与任务级切换函数OSCtxSW()不同的是,由于进入中断的时候现场已经保存过了,所以OSIntCtxSW()不需要像OSCtxSW()一样先保存当前任务现场,只需要做到OSCtxSW()的后半部分工作,也就是从将要执行的任务堆栈中恢复cpu寄存器的值。
2、时间片轮转调度
前面多次提到UCOSⅢ支持多个任务同时拥有一个优先级,要使用这个功能我们需要定义os_cfg_sched_round_robin_en为1。在Ⅲ中允许一个任务运行一段时间(时间片)后让出CPU的使用权,让拥有同优先级的下一个任务运行,这种任务调度的方法就是时间片轮转调度。图1-4展示了运行在同一优先级下的执行时间图,在优先级N下有3个就绪的任务,我们将时间片划分为4个时钟节拍。
图1-4 轮转调度
①任务3正在运行,这时一个时钟节拍中断发生,但是任务3的时间片还没完成。
②任务3的时钟片用完。
③Ⅲ切换到任务1,任务1是优先级N下的下一个就绪任务。
④任务1连续运行至时间片用完。
⑤任务3运行。
⑥任务3调用OSSchedRoundRobinYield()(在os_core.c文件中定义)函数放弃剩余的时间片,从而使优先级X下的下一个就绪的任务运行。
⑦Ⅲ切换到任务1。
⑧任务1执行完其时间片。
如果当前任务的时间片已经运行完,但是同一优先级下有多个任务,那么Ⅲ就会切换到该优先级对应的下一个任务,通过调用OS_SchedRoundRobin()函数来完成,这个函数由SOTimeTick()或者OS_IntQTask()调用,函数代码如下:
void OS_SchedRoundRobin(OS_RDY_LIST *p_rdy_list)
{
OS_TCB *p_tcb;
CPU_SR_ALLOC();
if(OSSchedRoundRobinEn!=DEF_TRUE){ (1)
return;
}
CPU_CRITICAL_ENTER();
p_tcb=p_rdy_list->HeadPtr; (2)
if(p_tcb==(OS_TCB*)0){ (3)
CPU_CRITICAL_EXIT();
return;
}
if(p_tcb==&OSIdleTaskTCB){
CPU_CRITICAL_EXIT(); (4)
return;
}
if(p_tcb->TimeQuantaCtr>(OS_TICK)0){ (5)
p_tcb->TimeQuantaCtr--;
}
if(p_tcb->TimeQuantaCtr>(OS_TICK)0){ (6)
CPU_CRITICAL_EXIT();
return;
}
if(p_rdy_list->NbrEntries<(OS_OBJ_QTY)2){ (7)
CPU_CRITICAL_EXIT();
return;
}
if(OSSchedLockNestingCtr>(OS_NESTING_CTR)0){ (8)
CPU_CRITICAL_EXIT();
return;
}
OS_RdyListMoveHeadToTail(p_rdy_list); (9)
p_tcb=p_rdy_list->HeadPtr; (10)
if(p_tcb->TimeQuanta==(OS_TICK)0){ (11)
p_tcb->TimeQuantaCtr=p_tcb->TimeQuanta;
}else{
p_tcb->TimeQuantaCtr=p_tcb->TimeQuanta; (12)
}
CPU_CRITICAL_EXIT();
}
(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字段表示某一优先级下的任务数量。这里判断其是否小于2,如果任务数小于2就不需要做任务切换,直接返回。
(8)判断调度器是否上锁,如果上锁就直接返回。
(9)(到这一部说明,时间片已经用完)将当前任务的os_tcb从双向链表移到链表尾。
(10)获取新的双向链表头,也就是下一个要执行的任务。
(11)我们要为下一个要执行的任务装载时间片值,一般我们在新建任务的时候会指定的,这个指定的值被存放在任务控制块os_tcb的timeQUANTA字段中,这里我们判断其是否为0,如果为0的话那么任务剩余的时间片timequantactr就使用默认值osschedroundrobindflttimequanta,如果我们使用了Ⅲ的时间片轮转调度功能的话,osschedroundrobindflttimequanta在我们调用osinit函数初始化Ⅲ的时候就会被初始化为oscfg_tickrate_hz/10u,比如oscfg_tickrate_hz为200的话那么默认的时间片就为20。
(12)如果timequanta不等于0,也就是说我们定义了任务的时间片,那么timequantactr就等于timequanta,也就是我们设置的时间片值。
每次任务切换后运行的都是处于就绪任务列表OSRdyList[]链表头的任务,当这个任务的时间片用完后这个任务就会被放到链表尾,然后再运行新的链表头的任务。
思考
1、对任务的操作有哪几种?
2、任务状态有哪几种,有什么区别,相互间状态的跳转关系?
3、任务控制块和任务堆栈中的参数有哪些?
4、优先级位映射表和就绪任务列表如何储存?
5、任务级调度器和中断级调度器的实现流程?