UCOSⅢ 任务管理

前言

多任务操作系统最主要的就是对任务的管理,包括任务的创建、挂起、删除和调度等,因此对于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Ⅲ任务状态转换图
图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){1return;
}
CPU_CRITICAL_ENTER();
p_tcb=p_rdy_list->HeadPtr;2if(p_tcb==(OS_TCB*)0){3CPU_CRITICAL_EXIT();
return;
}
if(p_tcb==&OSIdleTaskTCB){
CPU_CRITICAL_EXIT();4return;
}
if(p_tcb->TimeQuantaCtr>(OS_TICK)0){5)
p_tcb->TimeQuantaCtr--;
}
if(p_tcb->TimeQuantaCtr>(OS_TICK)0){6CPU_CRITICAL_EXIT();
return;
}
if(p_rdy_list->NbrEntries<(OS_OBJ_QTY)2){7CPU_CRITICAL_EXIT();
return;
}
if(OSSchedLockNestingCtr>(OS_NESTING_CTR)0){8CPU_CRITICAL_EXIT();
return;
}
OS_RdyListMoveHeadToTail(p_rdy_list);9)
p_tcb=p_rdy_list->HeadPtr;10if(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、任务级调度器和中断级调度器的实现流程?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值