目录
前言
一、任务的创建
二、任务的栈空间
三.任务的挂起和恢复
1.挂起任务
2.恢复任务
四.删除任务
前言
前面几篇文章我们了解了实时操作系统的主要概念以及内核结构,接下来我们一起看下操作系统的精髓《任务管理》,本章所讲的内容包括如何在用户的应用程序中建立任务、删除任务、改变任务的优先级、挂起和恢复任务,以及获得有关任务的信息。
一、任务的创建
μC/OS-Ⅱ可以管理多达 64 个任务,并从中保留了四个最高优先级和四个最低优先级的任务供自己使用,所以用户可以使用的只有 56 个任务。任务的优先级越高,反映优先级的值则越低。在最新的μC/OS-Ⅱ版本中,任务的优先级数也可作为任务的标识符使用。
μC/OS-I是通过任务控制块来管理任务的。因此,创建任务的工作实质上是创建一个任务控制块,并通过任务控制块把任务代码和任务堆栈关联起来形成个完整的任务。当然,还要使刚创建的任务进入就绪状态,并接着引发一次任务调度。c/os-i有两个用来创建任务的函数: OSTaskCreate和 OSTaskCreateExt(其中OSTaskCreateExt()是 OSTaskCreate()的扩展,并提供了一些附加功能。用户可根据需要使用这两个函数之一来完成任务的创建工作。
任务的创建有以下三个要点
- 任务可以在多任务调度开始 (即调用OSStart())之前创建,也可以在其它任务的执行过程中被创建。
- 在OSStart()被调用之前,用户必须创建至少一个任务;
- 不能在中断服务程序(ISR)中创建新任务
下面是OSTaskCreate的源码
INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U
prio)
{
void *psp;
INT8U err;
if (prio > OS_LOWEST_PRIO) {
return (OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {
OSTCBPrioTbl[prio] = (OS_TCB *)1;
OS_EXIT_CRITICAL();
psp = (void *)OSTaskStkInit(task, pdata, ptos, 0);
err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0);
if (err == OS_NO_ERR) {
OS_ENTER_CRITICAL();
OSTaskCtr++;
OSTaskCreateHook(OSTCBPrioTbl[prio]);
OS_EXIT_CRITICAL();
if (OSRunning) {
OSSched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;
OS_EXIT_CRITICAL();
}
return (err);
} else {
OS_EXIT_CRITICAL();
return (OS_PRIO_EXIST);
}
}
从上面的源码中,我们可以看到创建任务的过程如下:
- 任务优先级检查 :该优先级是否在0到OS_LOWSEST_PRIO之间?该优先级是否空闲?
- 调用OSTaskStkInit(),创建任务的栈帧;
- 调用OSTCBInit(),从空闲的OS_TCB池(即OSTCBFreeList链表)中获得一个TCB并初始化其内容,然后把它加入到 OSTCBList链表的开头,并把它设定为就绪状态;
- 任务个数OSTaskCtr加1;
- 调用用户自定义的函数OSTaskCreateHook();
- 判断是否需要调度(调用者是正在执行的任务)
返回值
- OS_NO_ERR:函数调用成功;
- OS_PRIO_EXIT:任务优先级已经存在;
- OS_PRIO_INVALID:任务优先级无效。
二、任务的栈空间
每个任务都有自己的栈空间(Stack),栈必须声明为OS_STK类型,并且由连续的内存空间组成;
- 栈空间的分配方法静态分配:在编译的时候分配
- static OS_STK MyTaskStack[stack_size]; OS_STK MyTaskStack[stack_size];
- 动态分配:在任务运行的时候使用malloc()函数来动态申请内存空间;
- OS_STK *pstk;
- pstk = (OS_STK *)malloc(stack_size);
- /* 确认malloc()能得到足够的内存空间 */
- if (pstk != (OS_STK *)0)
- {
- Create the task;
- }
在动态分配中,用户要时刻注意内存碎片问题。特别是当用户反复地建立和删除任务时,内存堆中可能会出现大量的内存碎片,导致没有足够大的一块连续内存区域可用作任务堆栈,这时 malloc()便无法成功地为任务分配堆栈空间。
上图表示了一块能被 malloc()动态分配的 3K 字节的内存堆 。为了讨论问题方便,假定用户要建立三个任务(任务 A,B 和C),每个任务需要 1K 字节的空间。设第一个 1K 字节给任务 A, 第二个 1K 字节给任务 B, 第三个 1K 字节给任务 C。然后,用户的应用程序删除任务 A 和任务 C,用 free()函数释放内存到内存堆中。现在,用户的内存堆虽有 2K 字节的自由内存空间,但它是不连续的,所以用户不能建立另一个需要 2K 字节内存的任务(即任务 D)。如果用户并不会去删除任务,使用 malloc()是非常可行的
栈的增长方向的设置
从低地址到高地址:在OS_CPU.H中,将常量OS_STK_GROWTH设定为 0;
从高地址到低地址:在OS_CPU.H中,将常量OS_STK_GROWTH设定为 1;
OS_STK TaskStack[TASK_STACK_SIZE]; OSTaskCreate(task, pdata,&TaskStack[TASK_STACK_SIZE-1],prio);
三.任务的挂起和恢复
1.挂起任务
所谓挂起一个任务,就是停止这个任务的运行。在uC/OS-Ⅱ中,用户任务可通过调用系统提供 OSTaskSuspend()函数来挂起自身或者除空闲任务之外的其他任务。用函数OSTaskSuspend)挂起的任务,只能在其他任务中通过调用恢复函数 OSTaskResume()使其恢复为就绪状态。任务在运行状态、就绪状态和等待状态之间的转移关系下图所示。
挂起任务函数 OSTaskSuspend()的原型为INT8U OSTaskSuspend (INT8U prio);函数的参数prio为待挂起任务的优先级别。如果调用函数 OSTaskSuspend()的任务要挂起自身,则参数必须为常数s_pIO_SELF(该常数在文件uCOS_II.H中被定义为OxFF)
当函数调用成功时,返回信息 OSNOERR;否则根据出错的具体情况返回OS_TASKSUSPEND_IDLE、OS_PRIO_INVALID和OS_TASK_SUSPEND_PRIO
该函数在一系列的判断中主要是判断待要挂起的任务是否调用这个函数的任务本身。如果是任务本身,则必须删除任务在任
务就绪表中的就绪标志,并在任务控制块成员 OSTCBStat中做了挂起记录之后,引发一次任务调度,以使CPU去运行就绪的其他任务。如果待挂起的任务不是调用函数的任务本身而是其他任务,那么只要删除任务就绪表中被挂起任务的就绪标志,并在任务控制块成员OSTCBStat中做了挂起记录即可。2.恢复任务
OSTaskResume():
- 恢复一个任务恢复被OSTaskSuspend()挂起的任务;
- 清除TCB中OSTCBStat字段OS_STAT_SUSPEND位
函数的参数为待恢复任务的优先级别。若函数调用成功,则返回信息OS_NO_ERR;否则,据出错的具体情况返回OS_PRIO_INVALID、OS_TSK_ RESUME_PRIO和OS_TASK_NOT_SUSPEND等。OSTaskResume()函数在判断任务确实是一个已存在的挂起任务,同时它又不是一个等待任务(任务控制块成员 OSTCBDly=0)时,就清除任务制块成员 OSTCBStat中的挂起记录并使任务就绪,最后调用调度器 OSSched()进行任务调度,并返回函数调用成功的信息OS_NO_ERR
四.删除任务
所谓删除一个任务,就是把该任务置于睡眠状态。具体做法是,把被删除任务的任务控制块从任务控制块链表中删除,并归还给空任务控制块链表,然后在任务就绪表中把该任务的就绪状态位置0,于是该任务就不能再被调度器所调用了。简单地说,就是把它的身份证给吊销了。在任务中,可以通过调用函数 OSTaskDel来删除任务自身或者除了空闲任务之外的其他任务。
如果一个任务调用这个函数是为了删除任务自己,则应在调用函数时令函数的参数pri为OS_PRIO_SELF有时,任务会占用一些动态分配的内存或信号量之类的资源。这时,如果有其他任务把这个任务删除了,那么被删除任务所占用的一些资源就会因为没有被释放而丢失,这是任何系统都无法接受的。
因此,在删除一个占用资源的任务时,一定要谨慎。具体的办法是,提出删任务请求的任务只负责提出删除任务请求,而删除工作则由被删除任务自己来完成。这样,被删除任务就可以根据自身的具体情况来决定何时删除自身,同时也有机会在删除自身之前占用的资源释放掉。
显然,如果想使提出删除任务请求的任务和被删除任务之间,能够像上述方式来执行删工作,则它们双方必须有某种通信方法μC/OS-II利用被删除任务的任务控制块成员OSTCBDelReq作为请求删除方的被删除方的联络信号,同时提供了一个双方都能调用的函数请求删除任务函数 OSTaskDelReq()。这样,提出删除任务请求的任务和被删除任务的双方就都使用这个函数来访问 OSTCBDelReq这个信号,从而可以根据这个信号的状态来决定各自的行为。
删除任务请求方要用被删除任务的优先级别prio作为参数来调用这个函数。从图中可以看出,删除任务请求方调用这个函数的目的就是要查看被删除的任务控制块是否还在如果还在,则令被删除任务的任务控制块成员 OSTCBDelReq的值为 OSTaskDelReq且通知该任务:“已经有任务要求在合适的时候要删除自己”;如果不在,认为被删除任务经被删除了。