7-时间片轮转调度

UCOSIII 时间片轮转调度

 

 

在UCOS中,任务调度可以理解为任务切换,当心跳时钟到来时,系统就会把当前任务的现场保存起来,放到本任务的堆栈中,这个步骤和进中断前的push压栈很相似,不过现场状态是保存在本任务的堆栈中而不是中断堆栈中。然后CPU去执行高优先级的就绪任务。比如有两个任务,任务A和任务B。B的优先级比A的高,现在正在运行任务A,而任务B在等待延时结束。心跳时钟来了,系统会更新各个延时时间参数,更新完延时参数后发现B的延时到了,就把B进入就绪太。因为任务B的优先级比任务A的优先级高,所以系统会切换任务,这个切换在心跳时钟到来了后在心跳时钟中断里切换。切换任务的大概过程还是挺简单的,首先系统会把任务A的现场保存到任务A的堆栈中,也就是保存各个CPU寄存器的值,然后从任务B的堆栈中把数据拷贝到CPU寄存器中,切换到任务B执行。任务B堆栈中的数据从哪里来?其实在任务B调用延时函数进入挂起态的时候,系统已经把任务B的现场(也就是各个CPU寄存器的值)拷贝到任务B的堆栈中了。这也是为什么每个任务都需要有自己专用堆栈的原因。

 

系统会给每个任务运行一定的时间,时间的长度由心跳频率决定,比如心跳时钟为200hz,那么就是5ms来一个心跳时钟,然后系统在心跳时钟到来时切换任务,所以很明显,一个任务运行的时间就是5ms,也是一个任务运行一次可用的时间。一个时间片(5ms)用完后,如果没有更高优先级 的任务出现,那本任务还会在获得一个(5ms)的时间片,一直到更高优先级的任务出现为止。

 

任务切换过程中的保存现场和恢复现场肯定会占用CPU的时间,一个心跳时钟切换一次任务,所以任务切换不要太频繁,不然系统整天忙于切换任务,哪有多少时间去处理任务中的事情。但是也不能太慢,如果太慢的话系统的实时性就不高了。比如心跳时钟是50hz的话,那么20ms才会切换一次任务,延时函数的误差最大为20ms,如果是时间要求比较严格的程序的话,那还怎么玩。所以。感觉系统切换有点像动态扫描数码管吧,扫描太快的时候虽然数码管看起来舒服但是会占用CPU太多资源,而扫描太慢又会导致数码管闪烁,这个例子并不恰当,也就将就着看吧。在原子的栗子中,用的是72Mhz的CPU,系统心跳为200hz,也就是5ms切换一次任务,系统切换任务占用多少CPU时间我也不知道,反正72Mhz下系统心跳频率为200hz是没问题的。

 

任务调度分为任务级和中断级,上面所说的是任务级的任务调度,也就是除了心跳中断外没有其他的中断到来的情况下的任务切换,在心跳时钟到来时由系统正常的切换任务。还有一种是中断级的任务调度。

可剥夺型内核,当一个优先级高的任务就绪时,就算心跳时钟没到来,那么高优先级任务可以直接从低优先级任务手中把CPU使用权抢过来,而不需要管低优先级任务时间片是否已经用完。还是举个栗子吧,比如有两个任务,任务A和任务B。B的优先级比A的高,现在正在运行任务A,而任务B正在等待一个外部中断,所以现在任务B不是就绪太。突然间任务B所等待的外部中断来了,那么任务B就会进入就绪太,而现在心跳时钟还没来,任务A的时间片也没用完,那么问题来了,任务A是运行态,任务B是就绪太,而任务B的优先级比任务A要高,系统该运行任务A呢还是该运行任务B呢,当然是运行任务B呀,所以系统会在退出中断时切换到任务B运行,而任务A的时间片虽然没用完,但是也只能眼巴巴地看着任务B把自己的时间片抢走,任务B剥夺了任务A的CPU使用权,这就是可剥夺型内核。这样做的好处就是系统实时响应比较好,如果等到心跳时钟来的时候才切换到任务B运行的话,估计黄花菜都凉了。

 

时间片轮转调度,据说这是UCOSIII新增的功能,UCOSII是没有的,本人并没有用过UCOSII,也就不纠结UCOSII的功能了。要使用时间片轮转调度得打开宏OS_CFG_SCHED_ROUND_ROBIN_EN。

时间片轮转调度,就是允许不同的任务拥有相同的优先级,既然优先级一样,那相同优先级的任务怎么运行,只能是把系统分配给本优先级的时间轮流用了。老规矩举栗子,有三个任务,A、B和C,A的优先级是5,B和C的优先级都是6,任务B的时间片是,3,任务C的时间片是1,系统心跳时钟是200hz,时间片长度为1个系统时钟节拍,也就是1*5=5ms。系统并不会因为优先级6上面的任务多而会给这个优先级更长的时间片,在没有中断级任务调度的情况下,系统会轮流运行第5和第6优先级的任务,也就是每个优先级的任务各运行5ms。比如在40ms的时间内,任务会切换8次,在8次的任务切换当中,优先级5的任务运行4次,因为优先级5只有一个任务,所以任务A运行了4次,一共运行20ms。而优先级6的任务运行就比较奇葩了,优先级6的任务也有4个时间片,假设在优先级6里先运行任务B吧,因为任务B占用了3个时间片,所以任务C就只有一个时间片了。也就是说在40ms时间内,任务A一共运行了4次,而任务B和任务C只是各运行了一次而已。所以我们在使用时间片轮转调度(不同任务使用相同优先级)的时候,每个任务的时间片尽量不要太多,比如上面的栗子中,如果把任务B的时间片分配为20,那么任务C要等任务B运行完了之后才轮到自己运行,那任务C运行的频率会非常低。UCOS作者给这个问题留了个解决方案,就是一个任务可以放弃自己剩余的时间片,把时间送给本优先级下其他的任务,注意是给同优先级的其他任务,而不是给其他优先级的任务,比如上面栗子的任务B拥有3个时间片,在任务B使用的第一个时间片的时候,时间片本来有5个ms,但是B只运行2ms就不想运行了,那么B可以放弃本时间片剩余的3个ms,包括剩余的未使用的2个时间片也放弃了。在B放弃剩余时间的时候会切换到任务C而不是任务A运行,因为任务C和任务B同优先级,而A不是。当然,当下一个心跳时钟到来时还是会切换到任务A运行滴。这个放弃时间片是手动放弃时间片切换任务的,而不是系统正常的任务切换。

反正我是不喜欢使用时间片轮转调度这个功能,如果系统不是非常庞大的话,一个任务分配一个优先级是没问题的,而且不使用时间片轮转调度的话还可以减少代码量,节省芯片空间。

任务切换相关函数

  1. OSSched (void)
  2. OSSchedRoundRobinCfg()
  3. OSSchedRoundRobinYield()

 

各个函数用法及参数说明

  1. OSSched ()

任务切换函数,如果手动调用这个函数的话会直接引起任务切换,其实很多函数都会调用这个函数从而进行任务切换,比如系统延时函数、任务挂起函数等。

函数原型

void  OSSched (void)

使能开关

永远有效

入口参数

 

注意事项

如果调度器已经上锁的话,这个函数是不起作用的

 

 

 

  1. OSSchedRoundRobinCfg()

时间片轮转调度初始化配置

函数原型

void  OSSchedRoundRobinCfg (CPU_BOOLEAN   en,

                            OS_TICK       dflt_time_quanta,

                            OS_ERR       *p_err)

使能开关

OS_CFG_SCHED_ROUND_ROBIN_EN

入口参数

CPU_BOOLEAN   en,

其实就是时间片轮转调度的使能

DEF_ENABLED 使能

DEF_DISABLED 不使能

 

OS_TICK       dflt_time_quanta,

默认的时间片长度,如果系统创建任务时初值为0,那么将会用这个值代替。如果这个值也被设置为0,则系统会用系统节拍的1/10作为时间轮转片节拍。比如系统心跳是1000hz,而dflt_time_quanta=0,那么时间片的频率就是200hz,每个时间片就是5ms

 

OS_ERR       *p_err

返回的错误码

注意事项

时间轮转片的频率和系统心跳频率是两回事,可以相同也可以不同

 

 

  1. OSSchedRoundRobinYield()

该任务放弃剩余时间片

函数原型

void  OSSchedRoundRobinYield (OS_ERR  *p_err)

使能开关

OS_CFG_SCHED_ROUND_ROBIN_EN

入口参数

OS_ERR  *p_err

返回的错误码

注意事项

这个函数是要手动调用的,只有任务中才能调用本函数

 

 

写了个代码,功能很简单,还是新建三个任务,第一个任务用来创建第二和第三个任务,然后第一个任务就删除自己,系统里剩下任务2和任务3在运行。任务2 是LED闪烁任务,优先级是5;任务3是串口输出任务,优先级也是5,然后让这两个优先级相同的任务一起运行,这个工程只是让两个优先级相同的任务同时运行而已,并没有使用放弃时间片等功能。

代码如下
Main.c文件
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"

#include "task.h"
/************************************************
 ALIENTEK战舰STM32开发板UCOS实验
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/

//UCOSIII中以下优先级用户程序不能使用,ALIENTEK
//将这些优先级分配给了UCOSIII的5个系统内部任务
//优先级0:中断服务服务管理任务 OS_IntQTask()
//优先级1:时钟节拍任务 OS_TickTask()
//优先级2:定时任务 OS_TmrTask()
//优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
//优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com  
//广州市星翼电子科技有限公司  
//作者:正点原子 @ALIENTEK


//*****************************************************************
//开始任务
OS_TCB  starTaskTCB;	//任务控制块
#define STAR_TASK_PRIO      3		//任务优先级
#define STAR_TASK_STK_SIZE  128	//任务堆栈总大小
CPU_STK STAR_TASK_STK[ STAR_TASK_STK_SIZE ];	//任务堆栈数组


int main()
{
	OS_ERR myErr;			//os的错误码
	CPU_SR_ALLOC();	//使用临街保护就得添加这玩意,不然会报错
	
	delay_init();  //时钟初始化
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
	uart_init(115200);   //串口初始化
	LED_Init();        	 //LED初始化	
	
	OSInit(&myErr);				 //os初始化
	
	//创建任务的时候进入临街保护比较好,但是这个函数非用户的API函数,用户使用是否合适以后再讨论
	OS_CRITICAL_ENTER(); 
	
	//创建开始任务,在开始任务里创建其他任务,为每个函数入口参数都强制转换类型吧,以防万一
	OSTaskCreate (		(OS_TCB       *)&starTaskTCB,						//任务控制块,
                    (CPU_CHAR     *)"star task",						//任务名字
                    (OS_TASK_PTR   )starTaskFunc,						//任务函数
                    (void         *)0,											//任务函数入口参数
                    (OS_PRIO       )STAR_TASK_PRIO,					//任务优先级
                    (CPU_STK      *)&STAR_TASK_STK[0],			//堆栈数组基地址
                    (CPU_STK_SIZE  ) STAR_TASK_STK_SIZE/10,	//堆栈溢出限位
                    (CPU_STK_SIZE  ) STAR_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       *)&myErr);								//错误码
	
 OS_CRITICAL_EXIT();	//任务创建完了就该退出临界保护了									
 OSStart(&myErr);				//任务创建完成后就该运行了
 while(1);						//运行系统后就一直在系统里面跑了,应该运行不到这里
}





Task.c文件
#include "task.h"
#include "sys.h"
#include "led.h"
#include "includes.h" //想要使用UCOS系统,得包含这个头文件



//LED秒闪任务,提示系统正在运行
OS_TCB  ledTaskTCB;
#define LED_TASK_PRIO 5
#define LED_TASK_STK_SIZE 128
CPU_STK LED_TASK_STK[LED_TASK_STK_SIZE];

//串口输出任务1
OS_TCB  uartTask_1_TCB;
#define UART_TASK_1_PRIO 5
#define UART_TASK_1_STK_SIZE 128
CPU_STK UART_TASK_1_STK[ UART_TASK_1_STK_SIZE ];

void starTaskFunc()
{	
	OS_ERR myErr;
	
	//时间片轮转初始化,时间片长度为一个心跳节拍,1*5ms=5ms
	OSSchedRoundRobinCfg(DEF_ENABLED,1,&myErr);  
	
	OSTaskCreate (	  (OS_TCB       *)&ledTaskTCB,
                    (CPU_CHAR     *)"led task",
                    (OS_TASK_PTR   )ledTaskFunc,
                    (void         *)0,
                    (OS_PRIO       )LED_TASK_PRIO,
                    (CPU_STK      *)&LED_TASK_STK[0],
                    (CPU_STK_SIZE  ) LED_TASK_STK_SIZE/10,
                    (CPU_STK_SIZE  ) LED_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       *)&myErr);

										
	OSTaskCreate (	  (OS_TCB       *)&uartTask_1_TCB,
                    (CPU_CHAR     *)"uart task 1 ",
                    (OS_TASK_PTR   )uartTask_1_Func,
                    (void         *)0,
                    (OS_PRIO       )UART_TASK_1_PRIO,
                    (CPU_STK      *)&UART_TASK_1_STK[0],
                    (CPU_STK_SIZE  ) UART_TASK_1_STK_SIZE/10,
                    (CPU_STK_SIZE  ) UART_TASK_1_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       *)&myErr);

//这个任务只是用来创建其他任务的,其他任务创建完成了这个任务也就没用了,删除掉		
	OSTaskDel((OS_TCB*)0,&myErr);
}	

//LED任务的任务函数
//LED每隔500ms翻转一次,提示系统正在运行
void ledTaskFunc()
{
	OS_ERR myErr;
	while(1)		//注意,每个任务都是一个死循环
	{
		OSTimeDlyHMSM (		 (CPU_INT16U  ) 0,		//时
											 (CPU_INT16U  ) 0,		//分
											 (CPU_INT16U  ) 0,		//秒
											 (CPU_INT32U  ) 100,	//毫秒
											 (OS_OPT      ) OS_OPT_TIME_HMSM_STRICT,	//选项
											 (OS_ERR      *)&myErr);	//错误码
		LED = !LED;	//LED状态翻转
	}	
}

//串口任务1的任务函数
//功能;每隔1秒向电脑串口助手发送一次数据
void uartTask_1_Func()
{
	OS_ERR myErr;
	static u16 uartTaskCount=0;
	
	while(1)
	{	
		uartTaskCount++;
		if(uartTaskCount > 65534)
		{
			uartTaskCount=0;
		}
		printf("uartTask 运行次数 :%d \r\n",uartTaskCount);
		
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_HMSM_STRICT,&myErr);//延时1秒
	}
}

	


Task.h文件
#ifndef __TASK_H
#define __TASK_H	 

	 				    
void starTaskFunc();	
void ledTaskFunc();
void uartTask_1_Func();
							
#endif

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值