UCOSIII任务创建
UCOSIII是多任务系统,那么肯定要能创建任务,创建任务就是将任务控制块、任务堆栈、任务代码等联系在一起,并且初始化任务控制块的相应字段。在UCOSIII中我们通过函数OSTaskCreate()来创建任务,OSTaskCreate()函数原型如下(在os_task.c中有定义)。
OSTaskCreate()的函数定义为:
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, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息 OS_TICK time_quanta, //当使能时间片轮转时的时间片长度,为0时为默认长度 void *p_ext, //用户补充的存储区 OS_OPT opt, //任务选项 OS_ERR *p_err) //存放该函数错误时的返回值 { ... //函数省略 OSSched(); }
对于任务创建函数有几点解释的:
- 参数*p_arg:传递给任务的参数。这就要求我们在定义任务函数的时候,需要给它赋予任务参数;但是我们一般在创建任务的时候,很少会传递这个参数,一般为0。比如,任务start_task的任务说明:
void start_task(void *p_arg);
- 参数q_size:UCOSIII中每个任务都有一个可选的内部消息队列,我们要定义宏OS_CFG_TASK_Q_EN>0,这是才会使用这个内部消息队列;
- 参数opt:包含任务的特定选项,有如下选项可以设置:
选项 | 说明 |
OS_OPT_TASK_NONE | 表示没有任何选项 |
OS_OPT_TASK_STK_CHK | 指定是否允许检测该任务的堆栈 |
OS_OPT_TASK_STK_CLR | 指定是否清除该任务的堆栈 |
OS_OPT_TASK_SAVE_FP | 指定是否存储浮点寄存器 |
另外注意!不能在中断服务程序中调用OSTaskCreat()函数来创建任务。
UCOSIII任务删除
OSTaskDel()函数用来删除任务,当一个任务不需要运行的话,我们就可以将其删除掉,删除任务不是说删除任务代码,而是UCOSIII不再管理这个任务。
void OSTaskDel (OS_TCB *p_tcb, OS_ERR *p_err) { ... //函数省略 OSSched(); /* Find new highest priority task */ }
对于任务删除函数有几点说明的:
- 调用OSTaskDel()删除一个任务后,这个任务的任务堆栈、OS_TCB所占用的内存并没有释放掉,因此我们可以利用他们用于其他的任务,当然我们也可以使用内存管理的方法给任务堆栈和OS_TCB分配内存,这样当我们删除掉某个任务后我们就可以使用内存释放函数将这个任务的任务堆栈和OS_TCB所占用的内存空间释放掉。
- 尽管UCOSIII允许在系统运行中删除任务,但是应该尽量避免这种操作,如果这个任务可能占有与其他任务共享的资源,在删除此任务之前这个被占有的资源没有被释放就有可能导致奇怪的结果。
UCOSIII任务挂起
有时候有些任务因为某些原因需要暂停运行,但是以后还要运行,因此我们就不能删除掉任务,这里我们可以使用OSTaskSuspend()函数挂起这个任务,以后再恢复运行,函数OSTaskSuspend()的原型如下:
void OSTaskSuspend (OS_TCB *p_tcb, OS_ERR *p_err) { ... //函数省略 OS_TaskSuspend(p_tcb, p_err); }
我们可以多次调用OSTaskSuspend ()函数来挂起一个任务,因此我们需要调用同样次数的OSTaskResume()函数才可以恢复被挂起的任务,这一点非常重要。
UCOSIII任务恢复
OSTaskResume()函数用来恢复被OSTaskSuspend()函数挂起的任务,OSTaskResume()函数是唯一能恢复被挂起任务的函数。函数OSTaskResume()的原型如下:
void OSTaskSuspend (OS_TCB *p_tcb, OS_ERR *p_err) { ... //函数省略 OS_TaskSuspend(p_tcb, p_err); }
如果被挂起的任务还在等待别的内核对象,比如事件标志组、信号量、互斥信号量、消息队列等,即使使用OSTaskResume()函数恢复了被挂起的任务,该任务也不一定能立即运行,该任务还是要等相应的内核对象,只有等到内核对象后才可以继续运行。
UCOSIII时钟片轮转调度
我们说过UCOSIII是支持多个任务拥有同一个优先级的,这些任务采用时间片轮转调度方法进行任务调度。在os_cfg.h文件中有个宏OS_CFG_SCHED_ROUND_ROBIN_EN,我们要想使用时间片轮转调度就需要将OS_CFG_SCHED_ROUND_ROBIN_EN定义为1,这样UCOSIII中有关时间片轮转调度的代码才会被编译,否则不能使用时间片轮转调度,这点特别重要!
OS_SchedRoundRobin()函数
时间片轮转调度器用于时间片轮转调度,为函数OS_SchedRoundRobin(),此函数由OSTimeTick或者OS_IntQTask()调用,函数在文件os_core.c中定义。
该函数在【UCOSIII】UCOSIII的任务调度和切换中已经讲解到,这里就不赘述了。
OSSchedRoundRobinCfg()函数
OSSchedRoundRobinCfg()函数用来使能或失能UCOSIII,如果我们要使用时间片轮转调度功能的话不仅要将宏OS_CFG_SCHED_ROUND_ROBIN_EN定 义 为1,还需要调用OSSchedRoundRobinCfg()函数来使能UCOSIII,OSSchedRoundRobinCfg()函数原型如下:
void OSSchedRoundRobinCfg (CPU_BOOLEAN en, //用于设置打开或关闭时间片轮转调度机制 OS_TICK dflt_time_quanta, //设置默认的时间片长度,就是系统时钟节拍个数 OS_ERR *p_err) { CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif CPU_CRITICAL_ENTER(); if (en != DEF_ENABLED) { OSSchedRoundRobinEn = DEF_DISABLED; } else { OSSchedRoundRobinEn = DEF_ENABLED; } if (dflt_time_quanta > (OS_TICK)0) { OSSchedRoundRobinDfltTimeQuanta = dflt_time_quanta; } else { OSSchedRoundRobinDfltTimeQuanta = (OS_TICK)(OSCfg_TickRate_Hz / (OS_RATE_HZ)10); } CPU_CRITICAL_EXIT(); *p_err = OS_ERR_NONE; }
对于OSSchedRoundRobinCfg()函数还有几点说明:
- 参数en:用于设置打开或关闭时间片轮转调度机制,如果为DEF_ENABLED表示打开时间片轮转调度,为DEF_DISABLED表示关闭时间片轮转调度;
- 参数dflt_time_quanta:设置默认的时间片长度,就是系统时钟节拍个数,比如我们设置系统时钟频率OSCfg_TickRate_Hz为200Hz,那么每个时钟节拍就是5ms。当我们设置dflt_time_quanta为n时,时间片长度就是(5*n)ms长,如果我们设置dflt_time_quanta为0的话,UCOSIII就会使用系统默认的时间片长度:OSCfg_TickRate_Hz / 10,比如如果OSCfg_TickRate_Hz为200,那么时间片长度为:200/10*5=100ms。
OSSchedRoundRobinYield()函数
当一个任务想放弃本次时间片,把CPU的使用权让给同优先级下的另外一个任务就可以使用OSSchedRoundRobinYield()函数,函数原型如下:
void OSSchedRoundRobinYield (OS_ERR *p_err) { OS_RDY_LIST *p_rdy_list; OS_TCB *p_tcb; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Can't call this function from an ISR */ *p_err = OS_ERR_YIELD_ISR; return; } #endif if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't yield if the scheduler is locked */ *p_err = OS_ERR_SCHED_LOCKED; return; } if (OSSchedRoundRobinEn != DEF_TRUE) { /* Make sure round-robin has been enabled */ *p_err = OS_ERR_ROUND_ROBIN_DISABLED; return; } CPU_CRITICAL_ENTER(); p_rdy_list = &OSRdyList[OSPrioCur]; /* Can't yield if it's the only task at that priority */ if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) { CPU_CRITICAL_EXIT(); *p_err = OS_ERR_ROUND_ROBIN_1; return; } OS_RdyListMoveHeadToTail(p_rdy_list); /* Move current OS_TCB to the end of the list */ p_tcb = p_rdy_list->HeadPtr; /* Point to new OS_TCB at head of the list */ if (p_tcb->TimeQuanta == (OS_TICK)0) { /* See if we need to use the default time slice */ p_tcb->TimeQuantaCtr = OSSchedRoundRobinDfltTimeQuanta; } else { p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta; /* Load time slice counter with new time */ } CPU_CRITICAL_EXIT(); OSSched(); /* Run new task */ *p_err = OS_ERR_NONE; }
该函数的返回错误码通常为:
错误码 | 说明 |
OS_ERR_NONE | 调用成功 |
OS_ERR_ROUND_ROBIN_1 | 当前优先级下没有其他就绪任务 |
OS_ERR_ROUND_ROBIN_DISABLED | 未使能时间片轮转调度功能 |
OS_ERR_YIELD_ISR | 在中断调用了本函数 |