1、任务的创建、调度、其他任务管理API
这些API都在os_task.c中实现。
1.1 任务的创建
任务的创建其实质是创建一个任务控制块,并把它与任务程序代码(C函数)和任务堆栈(私有堆栈)关联起来形成一个完整的任务;并使刚刚创建的任务进入就绪状态,引发一次任务调度。
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg,OS_STK *ptos,NT8U prio)
这个函数用于创建一个任务。
创建任务的时机:1、在多任务系统启动前创建;2、由一个运行的任务创建;
注意:任务不能由中断服务程序ISR创建。
分析各个参数:task 是一个指向任务程序代码,程序代码类型 void taskFun(void *p_arg);
p_arg 是一个指向在任务首次执行时传递给任务的参数area区域;
ptos 是一个指向任务堆栈栈顶。如果OS_STK_GROWTH设置为1,则栈是向下生长的,ptos就指向内存的最高地址;如果OS_STK_GROWTH设置为0,则栈是向上生长的,ptos指向内存的最低地址。
prio 是任务的优先级。唯一的优先级,数字越小,优先级越高。
返回值:成功则返回OS_ERR_NONE
失败则根据不同失败的原因有不同的返回值:
优先级已经存在——OS_PRIO_EXIT;
优先级无效——OS_ERR_PRIO_INVALID
从中断中创建任务——OS_ERR_TASK_CREATE_ISR
OSTaskCreate 这个任务创建函数内部逻辑: 检查任务的优先级是否合法-->检查优先级表看该优先级是否被使用。如果没被使用,初始化任务堆栈,获得并初始化任务控制块----》上述均没有问题后,就在任务计数器加1,如果多任务调度(uC/OS-II的核存于运行状态)已经开始,则进行一次任务调度。
先初始化堆栈,再err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
任务控制块--->私有堆栈----->任务代码
私有堆栈初始化,我们看看堆栈里存了什么数据
OS_STK *OSTaskStkInit(void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
如果使用了FPU的话就保存FPU寄存器,否则只保存通用寄存器。注意入栈顺序
如果开启了FPU并且使用Lazy Stacking特性,首先把FPSCR S0~S15 入栈;再把xPSR PC LR R12 R0~R3入栈;接着是是剩下的S16~S31入栈,最后R4~R11手动入栈。
再来看看扩展版本的创建任务----这和OSTaskCreate功能很类似,只是需要指定额外的任务信息。
INT8U OSTaskCreateExt (void (*task)(void *p_arg),void *p_arg,OS_STK *ptos,INT8U prio,
INT16U id,OS_STK *pbos,INT32U stk_size,void *pext,INT16U opt)多指定了 任务的标识;任务堆栈栈底的指针;任务堆栈的容陵;指向附加数据域的指针;用于设定操作的选项
红色部分就是与OSTaskCreate相比要多指定的任务参数。
操作的选项有
OS_TASK_OPT_STK_CHK----为任务检查堆栈
OS_TASK_OPT_STK_CLR-----当任务创建时清除堆栈
OS_TASK_OPT_SAVE_FP-----任务切换时保存FPU的寄存器的值。
1.2 任务的挂起和恢复
挂起任务——就是停止这个任务的运行,使得任务切换到等待状态。
恢复任务——就是使任务恢复到就绪状态。
下面是uC/OS-II实时系统中任务挂起、恢复、运行三态的转移关系的示意图。
在uC/OS-II实时系统中,用户任务可以通过调用系统提供的OSTaskSuspend()函数来挂起自己或者是除空闲任务之外的其他任务。
被OSTaskSuspend()挂起的任务,只能在其他任务中通过调用恢复函数OSTaskResume()使其恢复为就绪状态。
1.2.1 挂起任务API
INT8U OSTaskSuspend (INT8U prio)
这个API用来挂起任务,通过传递的参数优先级prio来挂起指定优先级prio的任务;或者挂起任务本身OS_PRIO_SELF。
参数prio:待挂起的任务的优先级,挂起自身就指定参数为OS_PRIO_SELF.
返回值: 成功挂起----OS_ERR_NONE
不允许挂起空闲任务-----OS_ERR_TASK_SUSPEND_IDLE
指定的优先级无效------OS_ERR_PRIO_INVALID
想挂起的任务不存在------OS_ERR_TASK_SUSPEND_PRIO
想挂起的任务指定了互斥锁----OS_ERR_TASK_NOT_EXITS
挂起任务实质上:对于挂起的是任务本身,则必须删除任务在就绪表的就绪标志,并在任务控制块成员OSTCBState中做挂起记录,再引发一次任务调度,以使CPU去运行其他任务;
对于挂起的是其他任务时,只需要删除任务就绪表中被挂起任务的就绪标志,并在任务控制块成员OSTCBStat中做挂起记录。
1.2.2 恢复任务
INT8U OSTaskResume (INT8U prio)
根据优先级prio确定这是一个已存在的挂起任务,同时它不是一个等待任务(任务控制块成员OSTCBDly=0)时,就清除任务控制块成员OSTCBStat中的挂起记录并使任务就绪,最后调用调度器OSSched()进行一次任务调度,成功则返回OS_NO_ERR。
挂起任务恢复时,确保它不是处于阻塞态,否则不会从等待态转为就绪态。
1.3 任务优先级切换
INT8U OSTaskChangePrio (INT8U oldprio,INT8U newprio)
这个API可以用来动态切换任务的优先级。注意新的任务优先级必须可用
返回值: OS_ERR_NONE----切换成功
OS_ERR_PRIO_INVALID---指定的优先级无效
OS_ERR_PRIO_EXIST----新的优先级已经存在了
OS_ERR_PRIO-----OLD任务不存在
OS_ERR_TASK_NOT_EXIST---该任务已经加了线程锁
1.4 查询任务的信息
INT8U OSTaskQuery (INT8U prio, //待查询任务的优先级
OS_TCB *p_task_data //存储任务信息的结构
)
在应用程序运行过程中需要了解一个任务的指针、堆栈等信息,调用上述API函数OSTaskQuery()来获取选定的任务信息 ---任务控制块的指针,成功则返回OS_NO_ERR。
1.5 任务删除
所谓删除一个任务,就是把该任务置于睡眠状态。具体而言,就是把被删除任务的任务控制块从任务控制块链表中删除,并归还给空任务控制块链表,然后在任务就绪表中把该任务的就绪状态位置0,这样该任务鱼就不能再被调度器所调用。即任务的身份证被吊销了。
INT8U OSTaskDel (INT8U prio)
参数prio-----是待删除任务的优先级;
如果在运行的任务中去删除其他任务,有可能其他任务还占用了一些动态分配的内存或信号量之类的资源,这样可能导致内存泄露。
因此比较安全的做法是 提出删除任务请求的任务 和 删除任务本身两者之间通过删除任务的任务控制块成员OSTCBDelReq作为双方的联络新信号,同时给出双方都能调用的函数-------请求删除任务函数OSTaskDelReq(INT8U prio)
在OSTaskDelReq(INT8U prio)中根据信号OSTCBDelReq的状态 提出删除任务请求的任务 和 删除任务本身要有不同的行为。
删除任务本身调用请求删除任务函数时,参数为OS_PRIO_SELF对自己做删除信号请求,从而获取OSTCBDelReq成员的状态;
提出删除任务请求的任务调用请求删除任务函数时,参数为要删除任务的优先级,如果该优先级的任务存在(除空闲任务外),则将要删除任务的任务控制块的OSTCBDelReq置为OS_TASK_DEL_REQ;
在删除任务本身通过OSTaskDelReq(INT8U prio)查询到OSTCBDelReq==OS_TASK_DEL_REQ,即其他任务已经发送要任务在合适的时候删除本身,此时可以调动资源释放函数和OSTaskDel (OS_PRIO_SELF)对自己进行删除。
因此更加合适的接口时
INT8U OSTaskDelReq(INYuU prio); //待删除任务的优先级
1.6uC/OS-II的初始化和任务的启动
1.6.1 uC/OS-II的初始化
要想用uC/OS-II的所有服务之前,第一步必须调用uC/OS-II的初始化函数OSInit(),对uC/OS-II自身的运行环境进行初始化。看看代码.
void OSInit (void)
{
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
OS_QInit(); /* Initialize the message queue structures */
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
#if OS_TMR_EN > 0u
OSTmr_Init(); /* Initialize the Timer Manager */
#endif
OSInitHookEnd(); /* Call port specific init. code */
#if OS_DEBUG_EN > 0u
OSDebugInit();
#endif
}
根据注释,俺们知道OSInit() 完成了就绪表、空白任务控制块链表、空白事件链表、其他全局变量进行初始化。还根据配置初始化事件标志结构体变量、初始化内存管理、初始化信息队列;并创建空闲任务OSTaskIdle(),并赋之以最低优先级和永久的就绪状态。还根据需要创建优先级为OS_LOWEST_PRIO-1的统计任务。初始化定时器管理。
系统任务初始化,主要还是全局变量和数据结构初始化;
全局变量: OSPrioCur-----当前运行任务的优先级;
OSPrioHighRdy----就绪表中具有最高优先级的优先级;
OSTCBCur----指向正在运行任务的任务控制
OSRunning-----uC/OS-II核是否正在运行的标志。
数据结构:5个空数据缓冲区-------空白任务控制块链表、任务就绪表OSRdyTbl[] OSRdyGrp、OSTCBPrioTbl[]数组等。
uC/OS-II自身的运行环境
1.6.2 uC/OS-II的启动
uC/OS-II进行任务的管理由调用启动函数OSStart()开始,在此之前,必须先至少创建一个用户任务。uC/OS-II立即进入了多任务管理阶段。
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew(); /* Find highest priority's task priority number */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
调用OSStart()之后,各个变量的变化。