uCOS-III 时间片轮转调度

参考https://blog.csdn.net/baidu_39514357/article/details/122901648

0 时间片轮转调度的意义

我们现在实现的 uCOS 内核,包含了就绪列表和时基列表。就绪列表的插入规则与优先级有关,而时基列表的插入规则与时基计数器和延时时间有关。上一篇文章中,已经实现了时基列表的任务调度。但是我们写的内核还有一些缺陷,当不进行任务调度时(注意:任务调度的实质是切换到高优先级的任务去执行),系统只能执行就绪列表下的双向链表头指针对应的任务,那么相同优先级的其他任务就执行不到了。因此,我们希望相同优先级的多个任务都可以执行到,其中一个办法就是每隔一段时间就切换到相同优先级上的任务,这样这些任务都有机会被运行到。

时间片轮转调度用于解决相同优先级下多个任务的运行问题。现在假设 A 优先级下有任务 1、任务 2、任务 3,B 优先级有任务 4 且被阻塞 6 个时间片的长度,所以现在要运行 A 优先级下的任务。

如果不实现时间片轮转,那么 A 优先级排在最前面的任务 1 将运行 6 个时间片,即独占了这 6 个时间片。
如果实现时间片轮转,那么 A 优先级下的任务可以商量好,大家这样来:任务 1 先运行 2 个时间片,任务 2 再运行 2 个时间片,最后任务 3 也运行 2 个时间片,很公平,大家都被运行了。当然,也不一定平均分配,比如任务 1 先运行 1 个时间片,任务 2 再运行 2 个时间片,最后任务 3 运行 3 个时间片,这样也可以。
相同优先级的任务,谁都可以被运行,这就是时间片轮转调度带来的好处了。现在,我们就来实现这个机制。

1 修改任务控制块 TCB(os.h)

在实现轮转调度之前,先添加 TCB 的成员,有两个:

TimeQuanta:用于记录该任务需要多少个时间片,这个值设置好后一般是不动的。
TimeQuantaCtr:用于时间片计数,表示任务剩余的时间片个数。一旦数到了零,说明该任务已经用完了时间片,需要切换其他任务了。

struct os_tcb{
	CPU_STK			*StkPtr;
	CPU_STK_SIZE	StkSize;
	
	OS_PRIO			Prio;				/* 任务优先级 */
	
	OS_TCB			*NextPtr;			/* 就绪列表双向链表的下一个指针 */
	OS_TCB			*PrevPtr;			/* 就绪列表双向链表的前一个指针 */
	
	OS_TCB			*TickNextPtr;		/* 指向链表的下一个 TCB 节点 */
	OS_TCB			*TickPrevPtr;		/* 指向链表的上一个 TCB 节点 */
	OS_TICK_SPOKE	*TickSpokePtr;		/* 用于回指到链表根部 */
	OS_TICK			TickCtrMatch;		/* 该值等于时基计数器 OSTickCtr 的值加上 TickRemain 的值 */
	OS_TICK			TickRemain;			/* 设置任务还需要等待多少个时钟周期 */
	
	OS_TICK			TimeQuanta;			/* 任务需要多少个时间片 */
	OS_TICK			TimeQuantaCtr;		/* 任务剩余的时间片个数 */
};

在这里插入图片描述

2 时间片轮转调度函数 OS_SchedRoundRobin()(os_core.c)

该函数实现了时间片轮转调度的功能,完成的步骤是:

传入的参数是就绪列表的一个数组元素,进而可以获知对应的双向链表头指针,即链表的第一个 TCB 节点。
如果双向链表为空,或者获得的是最低优先级的双向链表(是空闲任务 TCB 所在的地方),那么不进行调度。
否则就是普通情况了,把第一个 TCB 的时间片计数器减一。
如果减一后发现还未归零,则说明该任务的时间片未用完,不进行调度。
如果减一后发现还归零了,则说明该任务的时间片已用完,将第一个 TCB 放到链表最后。
第一个 TCB 换成了新的任务 TCB,设置好时间片计数器。

/* 时间片调度函数 */
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
void OS_SchedRoundRobin (OS_RDY_LIST *p_rdy_list)
{
	OS_TCB *p_tcb;
	
	CPU_SR_ALLOC();
	CPU_CRITICAL_ENTER();		/* 进入临界段 */
	
	p_tcb = p_rdy_list->HeadPtr;
	
	/* 如果链表为空,或是空闲任务,则退出 */
	if ((p_tcb == (OS_TCB *)0) || (p_tcb == &OSIdleTaskTCB))
	{
		CPU_CRITICAL_EXIT();	/* 退出临界段 */
		return;
	}
	
	/* 如果时间片未用完,时间片个数减一 */
	if (p_tcb->TimeQuantaCtr > (OS_TICK)0)
	{
		p_tcb->TimeQuantaCtr--;
	}
	
	/* 如果减一后,时间片仍未用完,则退出 */
	if (p_tcb->TimeQuantaCtr > (OS_TICK)0)
	{
		CPU_CRITICAL_EXIT();	/* 退出临界段 */
		return;
	}
	
	/* 如果链表只有一个节点,则退出 */
	if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2)
	{
		CPU_CRITICAL_EXIT();	/* 退出临界段 */
		return;
	}
	
	/* 运行到此处时,意味着当前任务已经用完了时间片,将任务放到链表最后 */
	OS_RdyListMoveHeadToTail (p_rdy_list);
	
	/* 重设下一个任务的时间片计数 */
	p_tcb = p_rdy_list->HeadPtr;
	p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta;

	CPU_CRITICAL_EXIT();		/* 退出临界段 */
}
#endif

需要注意的是,时间片轮转调度功能可以开启,也可以关闭,在宏定义 OS_CFG_SCHED_ROUND_ROBIN_EN 中(位于 os_cfg.h)可以设置。

3 修改相关代码

接下来,修改有关时间片的代码部分。

3.1 SysTick 中断发起后调用 OSTimeTick()(os_time.c)

当 SysTick 发起一次中断时,说明一个时间片已经过去,需调用 OS_SchedRoundRobin(),更新 TCB 中的时间片计数器,同时检查有无 TCB 的时间片用完。

void OSTimeTick (void)
{
	/* 更新时基列表 */
	OS_TickListUpdate();
	
	/* 时间片调度 */
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
	OS_SchedRoundRobin (&OSRdyList[OSPrioCur]);
#endif
	
	/* 任务调度 */
	OSSched();  
}

3.2 任务创建函数 OSTaskCreate()(os_task.c)

需要加入初始化时间片成员的代码。现在,创建一个任务时,用户还需要指定该任务的时间片个数。

/* 任务创建函数 */
void OSTaskCreate( 	OS_TCB 			*p_tcb,  		/* TCB指针 */
					OS_TASK_PTR 	p_task,  		/* 任务函数名 */
					void 			*p_arg,  		/* 任务的形参 */
					OS_PRIO			prio,			/* 任务优先级 */
					CPU_STK 		*p_stk_base, 	/* 任务栈的起始地址 */
					CPU_STK_SIZE 	stk_size,		/* 任务栈大小 */
					OS_TICK			time_quanta,	/* 时间片个数 */
					OS_ERR 			*p_err )		/* 错误码 */
{
	CPU_STK		*p_sp;
	CPU_SR_ALLOC();
	
	OS_TaskInitTCB (p_tcb);
	
	p_sp = OSTaskStkInit ( 	p_task,
							p_arg,
							p_stk_base,
							stk_size );  /* 任务栈初始化函数 */
	p_tcb->Prio		= prio;		/* 任务优先级保存在 TCB 的 prio 中 */
	p_tcb->StkPtr 	= p_sp;    	/* 剩余栈的栈顶指针 p_sp 保存到任务控制块 TCB 的 StkPtr 中 */
	p_tcb->StkSize 	= stk_size; /* 将任务栈的大小保存到任务控制块 TCB 的成员 StkSize 中 */
	
	p_tcb->TimeQuanta = time_quanta;
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
	p_tcb->TimeQuantaCtr = time_quanta;
#endif
	
	OS_CRITICAL_ENTER();	/* 进入临界段 */
	
	/* 将任务添加到就绪列表 */
	OS_PrioInsert (p_tcb->Prio);
	OS_RdyListInsertTail (p_tcb);
	
	OS_CRITICAL_EXIT();		/* 退出临界段 */
	
	*p_err = OS_ERR_NONE;		/* 函数执行到这里表示没有错误 */
}

3.3 空闲任务初始化函数 OS_IdleTaskInit()(os_core.c)

将空闲任务的时间片分配为 0,因为空闲任务位于最低优先级,而且是独占了最低优先级,因此不需要分配时间片。

/* 空闲任务初始化函数 */ 
void OS_IdleTaskInit (OS_ERR *p_err)
{
	OSIdleTaskCtr = (OS_IDLE_CTR) 0;		/* 计数器清零 */
	
	OSTaskCreate ((OS_TCB*)      &OSIdleTaskTCB, 
	              (OS_TASK_PTR)  OS_IdleTask, 
	              (void *)       0,
				  (OS_PRIO)		(OS_CFG_PRIO_MAX - 1u),
	              (CPU_STK *)    OSCfg_IdleTaskStkBasePtr,
	              (CPU_STK_SIZE) OSCfg_IdleTaskStkSize,
				  (OS_TICK)		 0,
	              (OS_ERR *)     &p_err);		/* 创建空闲任务 */
}

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些可能的uCOS-III多任务创建实验思考题及其答案,供您参考: 1. uCOS-III提供了哪些函数和数据类型来创建任务? 答:uCOS-III提供了以下函数和数据类型来创建任务: - OS_TASK_CREATE_EXT()函数:用于创建任务,并指定任务的优先级、栈空间、入口函数等参数。 - OS_PRIO数据类型:表示任务的优先级。 - OS_TCB数据类型:表示任务控制块,用于存储任务的信息。 - OSTaskCreateExtHook函数指针:可以在任务创建时执行一些额外的操作,如设置任务名称、堆栈检查等。 2. 如何理解任务的优先级和时间片轮转? 答:任务的优先级表示任务在系统中的重要程度或执行顺序,优先级越高的任务将优先执行。uCOS-III采用的是优先级抢占式调度,即当一个优先级更高的任务就绪时,将立即抢占当前任务的执行权。时间片轮转是指当多个任务优先级相同时,系统会为每个任务分配一个时间片,并按照轮流执行的方式来切换任务。这样可以保证所有任务都能得到执行,避免优先级低的任务长时间得不到执行。 3. 在多任务环境下,如何保证共享资源的安全性? 答:在多任务环境下,多个任务可能同时访问同一个共享资源,如全局变量、队列、信号量等。为了保证共享资源的安全性,可以使用以下方法: - 禁止中断:在访问共享资源时,可以关闭中断,这样可以防止其他任务干扰当前任务的执行,但会影响系统的实时性。 - 使用信号量:信号量是一种同步机制,可以用于多个任务之间的同步和互斥。当一个任务需要访问共享资源时,可以申请一个信号量,当访问完成后释放信号量,这样可以保证同一时间只有一个任务访问共享资源。 - 使用互斥量:互斥量是一种特殊的信号量,可以用于实现临界区保护。当一个任务进入临界区时,可以申请一个互斥量,在退出临界区时释放互斥量,这样可以保证同一时间只有一个任务进入临界区。 4. 如何处理任务间的通信和同步? 答:在多任务环境下,任务之间需要进行通信和同步,以实现共同的任务目标。可以使用以下方法: - 队列:队列是一种常用的通信机制,可以用于任务间的数据传输。发送任务将数据发送到队列中,接收任务从队列中取出数据。队列可以实现任务间的异步通信,也可以使用信号量来实现同步通信。 - 信号量:信号量是一种同步机制,可以用于任务间的同步和互斥。发送任务将信号量的值减一,接收任务将信号量的值加一。当信号量的值为0时,发送任务会被阻塞,直到有接收任务将信号量的值加一。 - 事件标志组:事件标志组可以用于任务间的事件通知和同步。发送任务将事件标志组的某个标志位设置为1,接收任务等待相应的标志位被设置为1。当某个标志位被设置为1时,接收任务被唤醒并执行相应的操作,可以实现任务间的同步和事件通知。 - 信箱:信箱是一种特殊的队列,可以用于任务间的数据传输和同步。发送任务将数据发送到信箱中,接收任务从信箱中取出数据。当信箱为空时,接收任务会被阻塞,直到有发送任务将数据发送到信箱中。 5. 实验中可能会遇到哪些问题,如何解决? 答:在实验中可能会遇到以下问题: - 堆栈溢出:如果任务的堆栈空间不足,可能会导致堆栈溢出,影响系统的稳定性。可以通过设置堆栈空间大小、堆栈检查等方法来避免堆栈溢出。 - 优先级反转:当一个低优先级任务正在占用一个共享资源时,一个高优先级任务需要访问该共享资源,这时可能会导致优先级反转,影响系统的实时性。可以使用优先级继承、优先级反转解决方案等方法来避免优先级反转。 - 死锁:当多个任务互相等待对方释放资源时,可能会导致死锁,影响系统的稳定性。可以通过避免多个任务同时申请多个资源、使用超时机制等方法来避免死锁。 - 系统资源耗尽:当系统资源(如堆内存、任务控制块等)耗尽时,可能会导致系统不可用。可以通过增加系统资源、优化系统资源的使用等方法来避免系统资源耗尽的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值