UCOSIII的任务管理详解

前言

对于操作系统而言,最重要的就是任务的创建、挂起、删除和调度等,简单的创建任务可能大家都会,但是做大型项目的话,任务多了就可能需要对UCOSIII的任务管理做更深层次的一些理解。

一、任务状态

UCOSIII是单核系统,一个时刻只有一个任务是运行状态,其它任务都是别的状态,状态一共分五种,分别为:休眠态、就绪态、运行态、等待态、中断服务态,下图是对于这几种状态的描述。

 

二、创建任务

代码如下:

void OSTaskCreate(
  OS_TCB         *p_tcb,       // 任务控制块
  CPU_CHAR       *p_name,       // 任务名称
  OS_TASK_PTR     p_task,       // 任务函数
  void           *p_arg,        // 传递给任务函数的参数
  OS_PRIO         prio,         // 任务优先级
  CPU_STK        *p_stk_base,   // 任务堆栈基地址
  CPU_STK_SIZE    stk_limit,    // 任务堆栈深度限位
  CPU_STK_SIZE    stk_size,     // 任务堆栈大小
  OS_MSG_QTY      q_size,       // 任务内部消息队列能够接收的最大消息数目
  OS_TICK         time_quanta,  // 当使能时间片轮转时的时间片长度
  void           *p_ext,        // 用户补充的存储区
  OS_OPT          opt,          // 任务选项
  OS_ERR         *p_err         // 存放该函数错误时的返回值
);

参数:

  1. p_tcb:指向任务控制块(Task Control Block, TCB)的指针。TCB是UCOSIII用于管理任务的一个数据结构,包含了任务的各种信息,如任务状态、优先级、堆栈指针等。

  2. p_name:指向任务名称的指针。每个任务都有一个唯一的名称,用于调试和日志记录。

  3. p_task:任务函数的指针。这是任务实际执行的代码入口点。任务函数通常具有void func(void *p_arg)的形式,其中p_arg是传递给任务函数的参数。

  4. p_arg:传递给任务函数的参数。这个参数在任务函数被调用时传递给它。如果任务函数不需要参数,这个值通常设置为NULL0

  5. prio:任务的优先级。UCOSIII支持多个任务优先级,优先级数值越小,任务越优先执行。但是,UCOSIII保留了一些优先级给系统内部任务使用,用户任务应避免使用这些优先级。

  6. p_stk_base:任务堆栈的基地址。这是堆栈空间的最低地址,用于初始化任务堆栈。在UCOSIII中,堆栈的增长方向可以是向上(从低地址向高地址)或向下(从高地址向低地址),具体取决于CPU的配置。

  7. stk_limit:任务堆栈的深度限位。这个值表示堆栈剩余空间的最低界限,当堆栈使用超过这个界限时,UCOSIII会报告堆栈溢出错误。但是,请注意,这个值在UCOSIII内部可能会根据堆栈的增长方向进行转换。

  8. stk_size:任务堆栈的大小。这是堆栈空间的总大小,以堆栈元素(通常是CPU的字大小)为单位。

  9. q_size:任务内部消息队列能够接收的最大消息数目。如果不需要内部消息队列,可以将此值设置为0

  10. time_quanta:当使能时间片轮转时的时间片长度。这个时间片长度决定了任务在执行完当前时间片后是否会被挂起,以便其他任务有机会执行。如果不需要时间片轮转,可以将此值设置为0

  11. p_ext:用户补充的存储区。这是一个指向用户定义的存储区的指针,可以用于存储任务相关的额外信息。如果不需要,可以设置为NULL

  12. opt:任务选项。这是一个位掩码,用于指定任务的特定选项,如是否检查堆栈、是否清除堆栈等。

  13. p_err:存放该函数错误时的返回值。如果函数调用成功,*p_err将被设置为OS_ERR_NONE;如果发生错误,则会被设置为相应的错误码。

三、任务堆栈

任务堆栈的作用

  1. 保存函数的执行路径信息:当任务中的函数调用发生时,堆栈用于保存函数的返回地址,以便在函数返回时能够正确地回到调用点。
  2. 保存CPU上下文:在任务切换时,堆栈用于保存当前任务的CPU寄存器状态(如程序计数器、状态寄存器等),以便在任务恢复时能够恢复到之前的状态。
  3. 传递参数:在函数调用时,参数通常通过堆栈传递给被调用的函数。
  4. 为临时变量分配空间:堆栈还用于存储任务执行过程中的临时变量。

任务堆栈的设定

  1. 堆栈大小:任务堆栈的大小取决于任务的需求,包括可能被调用的函数及其嵌套层数、相关局部变量的大小、中断服务程序所需要的空间等。设定堆栈大小时,需要考虑所有可能的情况,以确保堆栈不会溢出。
  2. 堆栈溢出检测:在有MMU(内存管理单元)或MPU(内存保护单元)的系统中,堆栈溢出的检测相对简单,因为这是MMU和MPU的必备功能之一。在没有这些硬件支持的系统中,UCOSIII提供了软件策略来检测堆栈溢出,如通过设置堆栈限制指针(StkLimitPtr)来监控堆栈的使用情况。
  3. 堆栈使用统计:UCOSIII提供了OSTaskStkChk()函数来统计任务堆栈的使用情况。这个函数可以帮助开发者了解每个任务的堆栈使用情况,以便在需要时调整堆栈大小。

注意事项

  1. 避免递归函数:在嵌入式系统中,由于资源有限,通常建议避免使用递归函数,因为它们可能会消耗大量的堆栈空间。
  2. 堆栈使用率:在程序设计调试阶段,最好谨慎地多查看任务堆栈的使用情况,以便在需要时做出调整。通常,堆栈使用率建议在50%到80%之间,太小会浪费空间,太大则可能不安全。
  3. 堆栈大小调整:如果任务堆栈的使用率接近或超过其限制,可能需要调整堆栈的大小。这可以通过修改任务创建时的stk_size参数来实现。

四、任务调度

UCOSIII(μC/OS-III)的任务调度是其内核的一个重要功能,它负责决定接下来哪个任务将获得CPU的控制权。以下是对UCOSIII任务调度的详细解释:

1. 调度器的类型

UCOSIII是一个抢占式的、基于优先级的内核。这意味着当有更高优先级的任务就绪时,当前正在执行的任务的CPU控制权将被剥夺,转交给更高优先级的任务。

2. 优先级就绪位图

UCOSIII使用就绪优先级位图来快速查找最高优先级的就绪任务。就绪优先级位图是一个按位表示的结构,每个位代表一个优先级。当某个优先级上有任务就绪时,相应位被置位。UCOSIII通过API如OS_PrioGetHighest()来获取最高优先级,并使用OS_PrioInsert()和OS_PrioRemove()来管理优先级位图,这些操作允许UCOSIII在O(1)时间复杂度内完成优先级任务的管理和调度。

3. 就绪队列

UCOSIII使用就绪队列来管理所有处于就绪状态的任务。每个优先级对应一个就绪队列,所有具有相同优先级的任务被链入该队列中。任务控制块(TCB)是任务管理的基本单位,包含了任务的所有状态信息。通过API如OS_RdyListInsert()、OS_RdyListInsertHead()、OS_RdyListInsertTail()等,可以将TCB插入到相应的就绪队列中,实现任务的调度。

4. 调度时机

任务调度通常发生在以下情况下:

  • 一个任务向另一个任务发送信号或信息,且接收方任务优先级高于当前任务。
  • 中断服务程序(ISR)向一个更高优先级的任务发布了消息或释放了信号量。
  • 任务调用时间延迟函数(如OSTimeDly())后超时。
  • 任务调用任务挂起函数(如OSTaskSuspend())挂起自身或其他任务,并因此改变了最高优先级任务的候选者。
  • 调用OSSched()显式请求任务调度。
  • 中断处理函数结束时,如果最高优先级的任务不是当前任务,则调用OSIntExit()进行任务切换。

5. 时间片轮转调度

当两个或多个任务拥有相同的优先级时,UCOSIII允许这些任务执行时间片轮转调度。每个时钟节拍到来时,当前运行任务的时间片减一,减到0则进行任务切换。如果一个任务不需要使用完整的时间片,它可以主动放弃CPU控制权,让给下一个同优先级的任务执行。

6. 调度策略

UCOSIII的调度策略基于优先级,并且允许用户在运行时动态调整任务的优先级和时间片大小。此外,用户还可以控制时间片轮转调度的开启和关闭。

UCOSIII通过就绪优先级位图和就绪队列的结合,实现了高效的任务调度机制。这种机制不仅保证了实时操作系统的高效性,还提供了灵活的任务管理方式。在实际应用中,通过优化任务优先级和队列管理,可以进一步提升系统性能,满足各种复杂的实时需求。

五、完整代码

下面是一个UCOSIII创建跑马灯任务的完整代码,可以参考:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"

//任务优先级
#define START_TASK_PRIO		3
//任务堆栈大小	
#define START_STK_SIZE 		512
//任务控制块
OS_TCB StartTaskTCB;
//任务堆栈	
CPU_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *p_arg);

//任务优先级
#define LED0_TASK_PRIO		4
//任务堆栈大小	
#define LED0_STK_SIZE 		128
//任务控制块
OS_TCB Led0TaskTCB;
//任务堆栈	
CPU_STK LED0_TASK_STK[LED0_STK_SIZE];
void led0_task(void *p_arg);

//任务优先级
#define LED1_TASK_PRIO		5
//任务堆栈大小	
#define LED1_STK_SIZE       128
//任务控制块
OS_TCB Led1TaskTCB;
//任务堆栈	
CPU_STK LED1_TASK_STK[LED1_STK_SIZE];
//任务函数
void led1_task(void *p_arg);


int main(void)
{
    OS_ERR err;
	CPU_SR_ALLOC();
    
    Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz   
    HAL_Init();                     //初始化HAL库
    delay_init(180);                //初始化延时函数
    uart_init(115200);              //初始化USART
    LED_Init();                     //初始化LED 
	OSInit(&err);		//初始化UCOSIII
	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_OPT_TASK_SAVE_FP, //任务选项,为了保险起见,所有任务都保存浮点寄存器的值
                 (OS_ERR 	* )&err);				//存放该函数错误时的返回值
	OS_CRITICAL_EXIT();	//退出临界区	 
	OSStart(&err);      //开启UCOSIII
    while(1)
    {
	} 
}

//开始任务函数
void start_task(void *p_arg)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	p_arg = p_arg;

	CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
   OSStatTaskCPUUsageInit(&err);  	//统计任务                
#endif
	
#ifdef CPU_CFG_INT_DIS_MEAS_EN		//如果使能了测量中断关闭时间
    CPU_IntDisMeasMaxCurReset();	
#endif

#if	OS_CFG_SCHED_ROUND_ROBIN_EN  //当使用时间片轮转的时候
	 //使能时间片轮转调度功能,设置默认的时间片长度s
	OSSchedRoundRobinCfg(DEF_ENABLED,10,&err);  
#endif		
	
	OS_CRITICAL_ENTER();	//进入临界区
	//创建LED0任务
	OSTaskCreate((OS_TCB 	* )&Led0TaskTCB,		
				 (CPU_CHAR	* )"led0 task", 		
                 (OS_TASK_PTR )led0_task, 			
                 (void		* )0,					
                 (OS_PRIO	  )LED0_TASK_PRIO,     
                 (CPU_STK   * )&LED0_TASK_STK[0],	
                 (CPU_STK_SIZE)LED0_STK_SIZE/10,	
                 (CPU_STK_SIZE)LED0_STK_SIZE,		
                 (OS_MSG_QTY  )0,					
                 (OS_TICK	  )0,					
                 (void   	* )0,					
                 (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR|OS_OPT_TASK_SAVE_FP,
                 (OS_ERR 	* )&err);				
				 
	//创建LED1任务
	OSTaskCreate((OS_TCB 	* )&Led1TaskTCB,		
				 (CPU_CHAR	* )"led1 task", 		
                 (OS_TASK_PTR )led1_task, 			
                 (void		* )0,					
                 (OS_PRIO	  )LED1_TASK_PRIO,     	
                 (CPU_STK   * )&LED1_TASK_STK[0],	
                 (CPU_STK_SIZE)LED1_STK_SIZE/10,	
                 (CPU_STK_SIZE)LED1_STK_SIZE,		
                 (OS_MSG_QTY  )0,					
                 (OS_TICK	  )0,					
                 (void   	* )0,				
                 (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR|OS_OPT_TASK_SAVE_FP, 
                 (OS_ERR 	* )&err);
				 
	OS_CRITICAL_EXIT();	//进入临界区				 
	OS_TaskSuspend((OS_TCB*)&StartTaskTCB,&err);		//挂起开始任务			 
}

//led0任务函数
void led0_task(void *p_arg)
{
	OS_ERR err;
	p_arg = p_arg;
	while(1)
	{
		LED0=0;    //LED0打开
		OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_HMSM_STRICT,&err); //延时200ms
		LED0=1;    //LED0关闭
		OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms
	}
}

//led1任务函数
void led1_task(void *p_arg)
{
	p_arg = p_arg;
	while(1)
	{
		LED1=!LED1;
        delay_ms(500);//延时500ms
	}
}

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千千道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值