前言
对于操作系统而言,最重要的就是任务的创建、挂起、删除和调度等,简单的创建任务可能大家都会,但是做大型项目的话,任务多了就可能需要对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 // 存放该函数错误时的返回值
);
参数:
-
p_tcb:指向任务控制块(Task Control Block, TCB)的指针。TCB是UCOSIII用于管理任务的一个数据结构,包含了任务的各种信息,如任务状态、优先级、堆栈指针等。
-
p_name:指向任务名称的指针。每个任务都有一个唯一的名称,用于调试和日志记录。
-
p_task:任务函数的指针。这是任务实际执行的代码入口点。任务函数通常具有
void func(void *p_arg)
的形式,其中p_arg
是传递给任务函数的参数。 -
p_arg:传递给任务函数的参数。这个参数在任务函数被调用时传递给它。如果任务函数不需要参数,这个值通常设置为
NULL
或0
。 -
prio:任务的优先级。UCOSIII支持多个任务优先级,优先级数值越小,任务越优先执行。但是,UCOSIII保留了一些优先级给系统内部任务使用,用户任务应避免使用这些优先级。
-
p_stk_base:任务堆栈的基地址。这是堆栈空间的最低地址,用于初始化任务堆栈。在UCOSIII中,堆栈的增长方向可以是向上(从低地址向高地址)或向下(从高地址向低地址),具体取决于CPU的配置。
-
stk_limit:任务堆栈的深度限位。这个值表示堆栈剩余空间的最低界限,当堆栈使用超过这个界限时,UCOSIII会报告堆栈溢出错误。但是,请注意,这个值在UCOSIII内部可能会根据堆栈的增长方向进行转换。
-
stk_size:任务堆栈的大小。这是堆栈空间的总大小,以堆栈元素(通常是CPU的字大小)为单位。
-
q_size:任务内部消息队列能够接收的最大消息数目。如果不需要内部消息队列,可以将此值设置为
0
。 -
time_quanta:当使能时间片轮转时的时间片长度。这个时间片长度决定了任务在执行完当前时间片后是否会被挂起,以便其他任务有机会执行。如果不需要时间片轮转,可以将此值设置为
0
。 -
p_ext:用户补充的存储区。这是一个指向用户定义的存储区的指针,可以用于存储任务相关的额外信息。如果不需要,可以设置为
NULL
。 -
opt:任务选项。这是一个位掩码,用于指定任务的特定选项,如是否检查堆栈、是否清除堆栈等。
-
p_err:存放该函数错误时的返回值。如果函数调用成功,
*p_err
将被设置为OS_ERR_NONE
;如果发生错误,则会被设置为相应的错误码。
三、任务堆栈
任务堆栈的作用
- 保存函数的执行路径信息:当任务中的函数调用发生时,堆栈用于保存函数的返回地址,以便在函数返回时能够正确地回到调用点。
- 保存CPU上下文:在任务切换时,堆栈用于保存当前任务的CPU寄存器状态(如程序计数器、状态寄存器等),以便在任务恢复时能够恢复到之前的状态。
- 传递参数:在函数调用时,参数通常通过堆栈传递给被调用的函数。
- 为临时变量分配空间:堆栈还用于存储任务执行过程中的临时变量。
任务堆栈的设定
- 堆栈大小:任务堆栈的大小取决于任务的需求,包括可能被调用的函数及其嵌套层数、相关局部变量的大小、中断服务程序所需要的空间等。设定堆栈大小时,需要考虑所有可能的情况,以确保堆栈不会溢出。
- 堆栈溢出检测:在有MMU(内存管理单元)或MPU(内存保护单元)的系统中,堆栈溢出的检测相对简单,因为这是MMU和MPU的必备功能之一。在没有这些硬件支持的系统中,UCOSIII提供了软件策略来检测堆栈溢出,如通过设置堆栈限制指针(StkLimitPtr)来监控堆栈的使用情况。
- 堆栈使用统计:UCOSIII提供了OSTaskStkChk()函数来统计任务堆栈的使用情况。这个函数可以帮助开发者了解每个任务的堆栈使用情况,以便在需要时调整堆栈大小。
注意事项
- 避免递归函数:在嵌入式系统中,由于资源有限,通常建议避免使用递归函数,因为它们可能会消耗大量的堆栈空间。
- 堆栈使用率:在程序设计调试阶段,最好谨慎地多查看任务堆栈的使用情况,以便在需要时做出调整。通常,堆栈使用率建议在50%到80%之间,太小会浪费空间,太大则可能不安全。
- 堆栈大小调整:如果任务堆栈的使用率接近或超过其限制,可能需要调整堆栈的大小。这可以通过修改任务创建时的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
}
}