什么是任务?
生活中我们处理一个大问题的时候通常都是将这个问题“分而治之”,把大问题分成多个小问题,小问题被逐步的解决掉,大问题也就随之解决了。那么这些小问题就可以看成是很多个小任务。在我们设计复杂、大型程序的时候也是一样的,将这些负责的程序分割成许多个简单的小程序,这些小程序就是单个的任务,所有的小任务和谐的工作,最终完成复杂的功能。在操作系统中这些小任务可以并发执行,从而提高CPU的使用效率。
UCOSIII就是一个可剥夺的多任务系统,我们使用UCOSIII的一个重要的原因就是它的多任务处理能力。
UCOSIII中的任务
在UCOSIII中任务是以何种面貌存在的呢?在UCOSIII中任务就是程序实体,UCOSIII能够管理和调度这些小任务(程序)。
UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
任务堆栈: 上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
任务控制块:任务控制块用来记录任务的各个属性。
任务函数: 由用户编写的任务处理代码,是实实在在干活的,一般写法如下:
UCOSIII中的任务函数模板:
void XXX_task(void *p_arg)
{
while(1)
{
。。。。。//任务处理过程
}
}
可以看出用任务函数通常是一个无限循环,当然了,也可以是一个只执行一次的任务。任务的参数是一个void类型的,这么做的目的是可以可以传递不同类型的数据甚至是函数。
可以看出任务函数其实就是一个C语言的函数,但是在使用UCOIII的情况下这个函数不能有用户自行调用,任务函数何时执行执行,何时停止完全有操作系统来控制。
UCOSIII系统任务:
UCOSIII默认有5个系统任务:
1、空闲任务:UCOSIII创建的第一个任务,UCOSIII必须创建的任务,此任务有UCOSIII自动创建,不需要用户手动创建。
2、时钟节拍任务:此任务也是必须创建的任务。
3、统计任务:可选任务,用来统计CPU使用率和各个任务的堆栈使用量。此任务是可选任务,由宏OS_CFG_STAT_TASK_EN控制是否使用此任务。
4、定时任务:用来向用户提供定时服务,也是可选任务,由宏OS_CFG_TMR_EN控制是否使用此任务。
5、中断服务管理任务:可选任务,由宏OS_CFG_ISR_POST_DEFERRED_EN控制是否使用此任务。
从用户的角度看,UCOSIII的任务一共有5种状态:
1、休眠态:任务已经在CPU的flash中了,但是还不受UCOSIII管理。
2、就绪态:系统为任务分配了任务控制块,并且任务已经在就绪表中登记,这时这个任务就具有了运行的条件,此时任务的状态就是就绪态。
3、运行态:任务获得CPU的使用权,正在运行。
4、等待态:正在运行的任务需要等待一段时间,或者等待某个事件,这个任务就进入了等待态,此时系统就会把CPU使用权转交给别的任务。
5、中断服务态:当发送中断,当前正在运行的任务会被挂起,CPU转而去执行中断服务函数,此时任务的任务状态叫做中断服务态。
任务堆栈的创建:
#define START_STK_SIZE 512 //堆栈大小
CPU_STK START_TASK_STK[START_STK_SIZE]; //定义一个数组来作为任务堆栈
任务堆栈的大小是多少呢?
CPU_STK为CPU_INT32U类型,也就是unsigned int类型,为4字节的,那么任务堆栈START_TASK_STK的大小就为:512 X 4=2048字节!
任务堆栈初始化:
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。不同的CPU对于的寄存器和对堆栈的操作方式不同,因此在移植UCOSIII的时候需要用户根据各自所选的CPU来编写任务堆栈初始化函数。
怎么使用创建的任务堆栈?
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)
堆栈增长方式:
向上增长:堆栈的增长方向从低地址向高地址增长。
向下增长:堆栈的增长方向从高地址向低地址增长。
任务控制块结构:
OS_TCB StartTaskTCB; //创建一个任务控制块
OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。
struct os_tcb
{
CPU_STK *StkPtr;
void *ExtPtr;
CPU_STK *StkLimitPtr
OS_TCB *NextPtr;
OS_TCB *Prev
…… //此处省略N个成员变量
#if OS_CFG_DBG_EN > 0u
OS_TCB *DbgPrevPtr;
OS_TCB *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
}
任务控制块初始化:
函数OS_TaskInitTCB()用与初始化任务控制块。用户不需要自行初始化任务控制块。
优先级:
1、优先级位映射表OSPrioTbl[]:用来记录哪个优先级下有任务就绪。
2、就绪任务列表OSRdyList[]:用来记录每一个优先级下所有就绪的任务。
struct os_rdy_list {
OS_TCB *HeadPtr //用于创建链表,指向链表头
OS_TCB *TailPtr; //用于创建链表,指向链表尾
OS_OBJ_QTY NbrEntries; //此优先级下的任务数量
};
同一优先级下如果有多个任务的话最先运行的永远是HeadPtr所指向的任务!
可剥夺型任务调度:
UCOSIII是可剥夺型内核,因此当一个高优先级的任务准备就绪,并且此时发生了任务调度,那么这个高优先级的任务就会获得CPU的使用权!
UCOSIII中的任务调度是由任务调度器来完成的,任务调度器有2种:任务级调度器和中断级调度器。
任务级调度器为函数OSSched()。
中断级调度器为函数OSIntExit(),当退出外部中断服务函数的时候使用中断级任务调度。
任务调度点:
2、使用延时函数OSTimeDly()或者OSTimeDlyHMSM()。
3、任务等待的事情还没发生(等待信号量,消息队列等)。
4、任务取消等待。
5、创建任务。
6、删除任务。
7、删除一个内核对象。
8、任务改变自身的优先级或者其他任务的优先级。
9、任务通过调用OSTaskSuspend()将自身挂起。
10、任务解挂某个挂起的任务。
11、退出所有的嵌套中断。
13、任务调用OSSchedRoundRobinYield()放弃其执行时间片。
14、用户调用OSSched()。
时间片轮转调度器为:OS_SchedRoundRobin()。
什么是任务切换?
任务级切换函数为:OSCtxSw()。
中断级切换函数为:OSIntCtxSw()。
OS_TCB StartTaskTCB; //创建一个任务控制块
OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。
int main(void)
{
OS_ERR err;
……
//其他函数,一般为外设初始化函数
……
OSInit(&err);
……
//其他函数,一般为创建任务函数
……
OSStart(&err);
}