In the previous section, I specified that a task is either an infinite loop function or a function that deletes itself when it is done executing. Note that the task code is not actually deleted — µC/OS-II simply doesn’t know about the task anymore, so that code will not run. A task looks just like any other C function, containing a return type and an argument, but it must never return. The return type of a task must always be declared void. The functions described in this chapter are found in the file OS_TASK.C
. To review, a task must have one of the two structures:
This chapter describes the services that allow your application to create a task, delete a task, change a task’s priority, suspend and resume a task, and allow your application to obtain information about a task.
µC/OS-II can manage up to 64 tasks, although µC/OS-II reserves the four highest priority tasks and the four lowest priority tasks for its own use. However, at this time, only two priority levels are actually used by µC/OS-II: OSTaskCreate
and OS_LOWEST_PRIO-1
(see OS_CFG.H
). This leaves you with up to 56 application tasks. The lower the value of the priority, the higher the priority of the task. In the current version of µC/OS-II, the task priority number also serves as the task identifier.
Table of Contents | ||
---|---|---|
|
Creating a Task, OSTaskCreate()
In order for µC/OS-II to manage your task, you must create it. You create a task by passing its address and other arguments to one of two functions: OSTaskCreate()
or OSTaskCreateExt()
. OSTaskCreate()
is backward compatible with µC/OS, and OSTaskCreateExt()
is an extended version of OSTaskCreate()
, providing additional features. A task can be created using either function. A task can be created prior to the start of multitasking or by another task. You must create at least one task before you start multitasking [i.e., before you call OSStart()
]. A task cannot be created by an ISR.
The code for OSTaskCreate()
is shown in Listing 4.1. As can be seen, OSTaskCreate()
requires four arguments. task is a pointer to the task code, pdata is a pointer to an argument that is passed to your task when it starts executing, ptos is a pointer to the top of the stack that is assigned to the task (see section 4.02, Task Stacks), and prio is the desired task priority.
Creating a Task, OSTaskCreateExt()
Creating a task using OSTaskCreateExt()
offers more flexibility, but at the expense of additional overhead. The code for OSTaskCreateExt()
is shown in Listing 4.2.
As can be seen, OSTaskCreateExt()
requires nine arguments! The first four arguments (task, pdata, ptos, and prio) are exactly the same as in OSTaskCreate()
, and they are located in the same order. I did this to make it easier to migrate your code to use OSTaskCreateExt()
.
id | establishes a unique identifier for the task being created. This argument has been added for future expansion and is otherwise unused by µC/OS-II. This identifier will allow me to extend µC/OS-II beyond its limit of 64 tasks. For now, simply set the task’s ID to the same value as the task’s priority. |
pbos | is a pointer to the task’s bottom-of-stack and this argument is used to perform stack checking. |
stk_size | specifies the size of the stack in number of elements. This means that if a stack entry is four bytes wide, then a stk_size of 1000 means that the stack will have 4,000 bytes. Again, this argument is used for stack checking. |
pext | is a pointer to a user-supplied data area that can be used to extend the |
opt | specifies options to |
Task Stacks
Each task must have its own stack space. A stack must be declared as being of type OS_STK
and must consist of contiguous memory locations. You can allocate stack space either statically (at compile time) or dynamically (at run time). A static stack declaration is shown in Listings 4.3 and 4.4. Either declaration is made outside a function
or
You can allocate stack space dynamically by using the C compiler’s malloc() function as shown in Listing 4.5. However, you must be careful with fragmentation. Specifically, if you create and delete tasks, your memory allocator may not be able to return a stack for your task(s) because the heap eventually becomes fragmented.
Because µC/OS-II supports processors with stacks that grow either from high to low memory or from low to high memory, you must know how the stack grows when you call either OSTaskCreate()
or OSTaskCreateExt()
because you need to pass the task’s top-of-stack to these functions. When OS_STK_GROWTH
is set to 0 in OS_CPU.H
, you need to pass the lowest memory location of the stack to the task create function as shown in Listing 4.6.
When OS_STK_GROWTH
is set to 1 in OS_CPU.H
, you need to pass the highest memory location of the stack to the task create function as shown in Listing 4.7.
This requirement affects code portability. If you need to port your code from a processor architecture that supports a downward-growing stack to one that supports an upward-growing stack, you may need to make your code handle both cases. Specifically, Listings 4.6 and 4.7 are rewritten as shown in Listing 4.8.
The size of the stack needed by your task is application specific. When sizing the stack, however, you must account for nesting of all the functions called by your task, the number of local variables that will be allocated by all functions called by your task, and the stack requirements for all nested interrupt service routines. In addition, your stack must be able to store all CPU registers.
Stack Checking, OSTaskStkChk()
Sometimes it is necessary to determine how much stack space a task actually uses. This allows you to reduce the amount of RAM needed by your application code by not overallocating stack space. µC/OS-II provides OSTaskStkChk()
, which provides you with this valuable information.
In order to use the µC/OS-II stack-checking facilities, you must do the following.
- Set
OS_TASK_CREATE_EXT
to 1 inOS_CFG.H
. - Create a task using
OSTaskCreateExt()
and give the task much more space than you think it really needs. You can callOSTaskStkChk()
for any task, from any task. - Set the opt argument in
OSTaskCreateExt()
toOS_TASK_OPT_STK_CLR + OS_TASK_OPT_STK_CLR
. Note that if your startup code clears all RAM and you never delete tasks once they are created, you don’t need to set theOS_TASK_OPT_STK_CLR
option. This reduces the execution time ofOSTaskCreateExt()
. - Call
OSTaskStkChk()
from a task by specifying the priority of the task you want to check. You can inquire about any task stack not just the running task.
You need to run the application long enough and under your worst case conditions to get proper numbers. Once OSTaskStkChk()
provides you with the worst case stack requirement, you can go back and set the final size of your stack. You should accommodate system expansion, so make sure you allocate between 10 and 100 percent more stack than what OSTaskStkChk()
reports. What you should get from stack checking is a ballpark figure; you are not looking for an exact stack usage.
The code for OSTaskStkChk()
is shown in Listing 4.9. The data structure OS_STK_DATA
(see uCOS_II.H
) is used to hold information about the task stack. I decided to use a data structure for two reasons. First, I consider OSTaskStkChk()
to be a query-type function, and I wanted to have all query functions work the same way — return data about the query in a data structure. Second, passing data in a data structure is efficient and allows me to add additional fields in the future without changing the API (Application Programming Interface) of OSTaskStkChk()
. For now, OS_STK_DATA
only contains two fields: OSFree
and OSUsed
. As you can see, you invoke OSTaskStkChk()
by specifying the priority of the task you want to perform stack checking on.
Deleting a Task, OSTaskDel()
Sometimes it is necessary to delete a task. Deleting a task means that the task will be returned to the DORMANT state (see section 3.02, Task States) and does not mean that the code for the task will be deleted. The task code is simply no longer scheduled by µC/OS-II. You delete a task by calling OSTaskDel()
(Listing 4.10).
Requesting to Delete a Task, OSTaskDelReq()
Sometimes, a task owns resources such as memory buffers or a semaphore. If another task attempts to delete this task, the resources are not freed and thus are lost. This would lead to memory leaks which is not acceptable for just about any embedded system. In this type of situation, you somehow need to tell the task that owns these resources to delete itself when it’s done with the resources. You can accomplish this with the OSTaskDelReq()
function. Both the requestor and the task to be deleted need to call OSTaskDelReq()
. The requestor code is shown in Listing 4.11.
The pseudocode for the task that needs to delete itself is shown in Listing 4.12. This task basically polls a flag that resides inside the task’s OS_TCB
. The value of this flag is obtained by calling OSTaskDelReq(OS_PRIO_SELF)
.
The code for OSTaskDelReq()
is shown in Listing 4.13. As usual, OSTaskDelReq()
needs to check for boundary conditions.
Changing a Task’s Priority, OSTaskChangePrio()
When you create a task, you assign the task a priority. At run time, you can change the priority of any task by calling OSTaskChangePrio()
. In other words, µC/OS-II allows you to change priorities dynamically. The code for OSTaskChangePrio()
is shown in Listing 4.14.
Suspending a Task, OSTaskSuspend()
Sometimes it is useful to explicitly suspend the execution of a task. This is accomplished with the OSTaskSuspend()
function call. A suspended task can only be resumed by calling the OSTaskResume()
function call. Task suspension is additive. This means that if the task being suspended is also waiting for time to expire, the suspension needs to be removed and the time needs to expire in order for the task to be ready to run. A task can suspend either itself or another task.
The code for OSTaskSuspend()
is shown in Listing 4.15.
Resuming a Task, OSTaskResume()
As mentioned in the previous section, a suspended task can only be resumed by calling OSTaskResume()
. The code for OSTaskResume()
is shown in Listing 4.16.
Getting Information about a Task, OSTaskQuery()
Your application can obtain information about itself or other application tasks by calling OSTaskQuery()
. In fact, OSTaskQuery()
obtains a copy of the contents of the desired task’s OS_TCB
. The fields available to you in the OS_TCB
depend on the configuration of your application (see OS_CFG.H
). Indeed, because µC/OS-II is scalable, it only includes the features that your application requires.
To call OSTaskQuery()
, your application must allocate storage for an OS_TCB
, as shown in Listing 4.17. This OS_TCB
is in a totally different data space from the OS_TCBs
allocated by µC/OS-II. After calling OSTaskQuery()
, this OS_TCB
contains a snapshot of the OS_TCB
for the desired task. You need to be careful with the links to other OS_TCBs
(i.e., .OSTCBNext
and .OSTCBPrev
); you don’t want to change what these links are pointing to! In general, only use this function to see what a task is doing — a great tool for debugging.
The code for OSTaskQuery()
is shown in Listing 4.18.
...
Code Block | ||
---|---|---|
| ||
void YourTask (void *pdata)
{
for (;;) {
/* USER CODE */
Call one of uC/OS-II's services:
OSFlagPend();
OSMboxPend();
OSMutexPend();
OSQPend();
OSSemPend();
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimeDlyHMSM();
/* USER CODE */
}
}
or,
void YourTask (void *pdata)
{
/* USER CODE */
OSTaskDel(OS_PRIO_SELF);
}
|
This chapter describes the services that allow your application to create a task, delete a task, change a task’s priority, suspend and resume a task, and allow your application to obtain information about a task.
µC/OS-II can manage up to 64 tasks, although µC/OS-II reserves the four highest priority tasks and the four lowest priority tasks for its own use. However, at this time, only two priority levels are actually used by µC/OS-II: OSTaskCreate
and OS_LOWEST_PRIO-1
(see OS_CFG.H
). This leaves you with up to 56 application tasks. The lower the value of the priority, the higher the priority of the task. In the current version of µC/OS-II, the task priority number also serves as the task identifier.
Table of Contents | ||
---|---|---|
|
Creating a Task, OSTaskCreate()
In order for µC/OS-II to manage your task, you must create it. You create a task by passing its address and other arguments to one of two functions: OSTaskCreate()
or OSTaskCreateExt()
. OSTaskCreate()
is backward compatible with µC/OS, and OSTaskCreateExt()
is an extended version of OSTaskCreate()
, providing additional features. A task can be created using either function. A task can be created prior to the start of multitasking or by another task. You must create at least one task before you start multitasking [i.e., before you call OSStart()
]. A task cannot be created by an ISR.
The code for OSTaskCreate()
is shown in Listing 4.1. As can be seen, OSTaskCreate()
requires four arguments. task is a pointer to the task code, pdata is a pointer to an argument that is passed to your task when it starts executing, ptos is a pointer to the top of the stack that is assigned to the task (see section 4.02, Task Stacks), and prio is the desired task priority.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
void *psp;
INT8U err;
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { (1)
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2)
OSTCBPrioTbl[prio] = (OS_TCB *)OS_TCB_RESERVED; (3)
OS_EXIT_CRITICAL(); (4)
psp = (void *)OSTaskStkInit(task, pdata, ptos, 0); (5)
err = OS_TCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0); (6)
if (err == OS_ERR_NONE) { (7)
if (OSRunning == OS_TRUE) { (8)
OS_Sched(); (9)
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0; (10)
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
} |
Panel | ||
---|---|---|
| ||
(1) If the configuration constant (2) Next, (3) If the desired priority is free, µC/OS-II reserves the priority by placing a non-NULL pointer in (4) This allows (5) (6) Once (7) If the stack frame and the task's TCB are properly initialized ... (8) ... if multitasking has already started then ... (9) The scheduler is called to determine whether the newly created task has a higher priority than the task that called OSTaskCreate(). Creating a higher priority task results in a context switch to the new task. If the task was created before multitasking has started [i.e., you did not call (10) If |
Creating a Task, OSTaskCreateExt()
Creating a task using OSTaskCreateExt()
offers more flexibility, but at the expense of additional overhead. The code for OSTaskCreateExt()
is shown in Listing 4.2.
As can be seen, OSTaskCreateExt()
requires nine arguments! The first four arguments (task, pdata, ptos, and prio) are exactly the same as in OSTaskCreate()
, and they are located in the same order. I did this to make it easier to migrate your code to use OSTaskCreateExt()
.
id | establishes a unique identifier for the task being created. This argument has been added for future expansion and is otherwise unused by µC/OS-II. This identifier will allow me to extend µC/OS-II beyond its limit of 64 tasks. For now, simply set the task’s ID to the same value as the task’s priority. |
pbos | is a pointer to the task’s bottom-of-stack and this argument is used to perform stack checking. |
stk_size | specifies the size of the stack in number of elements. This means that if a stack entry is four bytes wide, then a stk_size of 1000 means that the stack will have 4,000 bytes. Again, this argument is used for stack checking. |
pext | is a pointer to a user-supplied data area that can be used to extend the |
opt | specifies options to |
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskCreateExt (void (*task)(void *pd),
void *pdata,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_STK *psp;
INT8U err;
#if OS_ARG_CHK_EN > 0
if (prio > OS_LOWEST_PRIO) { (1)
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2)
OSTCBPrioTbl[prio] = (OS_TCB *)OS_TCB_RESERVED; (3)
OS_EXIT_CRITICAL(); (4)
psp = (OS_STK *)OSTaskStkInit(task, pdata, ptos, opt); (5)
err = OS_TCBInit(prio, psp, pbos, id, stk_size, pext, opt); (6)
if (err == OS_ERR_NONE) { (7)
if (OSRunning == TRUE) { (8)
OS_Sched(); (9)
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0; (10)
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
} |
Panel | ||
---|---|---|
| ||
(1) (2) Next, (3) If the desired priority is free, then µC/OS-II reserves the priority by placing a non-NULL pointer in (4) This allows (5) (6) Once (10) If (7) & (8) & (9) Finally, if |
Task Stacks
Each task must have its own stack space. A stack must be declared as being of type OS_STK
and must consist of contiguous memory locations. You can allocate stack space either statically (at compile time) or dynamically (at run time). A static stack declaration is shown in Listings 4.3 and 4.4. Either declaration is made outside a function
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
static OS_STK MyTaskStack[stack_size]; |
or
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_STK MyTaskStack[stack_size]; |
You can allocate stack space dynamically by using the C compiler’s malloc() function as shown in Listing 4.5. However, you must be careful with fragmentation. Specifically, if you create and delete tasks, your memory allocator may not be able to return a stack for your task(s) because the heap eventually becomes fragmented.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_STK *pstk;
pstk = (OS_STK *)malloc(stack_size);
if (pstk != (OS_STK *)0) { /* Make sure malloc() has enough space */
Create the task;
} |
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) Figure 4.1 illustrates a heap containing 3Kb of available memory that can be allocated with malloc(). For the sake of discussion, you create three tasks (tasks A, B, and C), each requiring 1Kb. (2) Assume that the first 1Kb is given to task A, the second to task B, and the third to task C. (3) Your application then deletes task A and task C and relinquishes the memory to the heap using free(). Your heap now has 2Kb of memory free, but it’s not contiguous. This means that you cannot create another task (i.e., task D) that requires 2 Kb because your heap is fragmented. If, however, you never delete a task, the use of malloc() is perfectly acceptable. |
Because µC/OS-II supports processors with stacks that grow either from high to low memory or from low to high memory, you must know how the stack grows when you call either OSTaskCreate()
or OSTaskCreateExt()
because you need to pass the task’s top-of-stack to these functions. When OS_STK_GROWTH
is set to 0 in OS_CPU.H
, you need to pass the lowest memory location of the stack to the task create function as shown in Listing 4.6.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_STK TaskStk[TASK_STK_SIZE];
OSTaskCreate(task, pdata, &TaskStk[0], prio); |
When OS_STK_GROWTH
is set to 1 in OS_CPU.H
, you need to pass the highest memory location of the stack to the task create function as shown in Listing 4.7.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_STK TaskStk[TASK_STK_SIZE];
OSTaskCreate(task, pdata, &TaskStk[TASK_STK_SIZE-1], prio); |
This requirement affects code portability. If you need to port your code from a processor architecture that supports a downward-growing stack to one that supports an upward-growing stack, you may need to make your code handle both cases. Specifically, Listings 4.6 and 4.7 are rewritten as shown in Listing 4.8.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_STK TaskStk[TASK_STK_SIZE];
#if OS_STK_GROWTH == 0
OSTaskCreate(task, pdata, &TaskStk[0], prio);
#else
OSTaskCreate(task, pdata, &TaskStk[TASK_STK_SIZE-1], prio);
#endif |
The size of the stack needed by your task is application specific. When sizing the stack, however, you must account for nesting of all the functions called by your task, the number of local variables that will be allocated by all functions called by your task, and the stack requirements for all nested interrupt service routines. In addition, your stack must be able to store all CPU registers.
Stack Checking, OSTaskStkChk()
Sometimes it is necessary to determine how much stack space a task actually uses. This allows you to reduce the amount of RAM needed by your application code by not overallocating stack space. µC/OS-II provides OSTaskStkChk()
, which provides you with this valuable information.
In order to use the µC/OS-II stack-checking facilities, you must do the following.
- Set
OS_TASK_CREATE_EXT
to 1 inOS_CFG.H
. - Create a task using
OSTaskCreateExt()
and give the task much more space than you think it really needs. You can callOSTaskStkChk()
for any task, from any task. - Set the opt argument in
OSTaskCreateExt()
toOS_TASK_OPT_STK_CLR + OS_TASK_OPT_STK_CLR
. Note that if your startup code clears all RAM and you never delete tasks once they are created, you don’t need to set theOS_TASK_OPT_STK_CLR
option. This reduces the execution time ofOSTaskCreateExt()
. - Call
OSTaskStkChk()
from a task by specifying the priority of the task you want to check. You can inquire about any task stack not just the running task.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) In Figure 4.2, I assume that the stack grows from high memory to low memory (i.e., (2) To perform stack checking, µC/OS-II requires that the stack be filled with zeros when the task is created. (3) & (4) Also, µC/OS-II needs to know the location of the bottom-of-stack (BOS) and the size of the stack you assigned to the task. These two values are stored in the task’s (5) (6) & (8) The amount of stack space used is obtained by subtracting the number of zero-value entries from the stack size you specified in (7) Note that at any given time, the stack pointer for the task being checked may be pointing somewhere between the initial top-of-stack (TOS) and the deepest stack growth. (5) Also, every time you call |
You need to run the application long enough and under your worst case conditions to get proper numbers. Once OSTaskStkChk()
provides you with the worst case stack requirement, you can go back and set the final size of your stack. You should accommodate system expansion, so make sure you allocate between 10 and 100 percent more stack than what OSTaskStkChk()
reports. What you should get from stack checking is a ballpark figure; you are not looking for an exact stack usage.
The code for OSTaskStkChk()
is shown in Listing 4.9. The data structure OS_STK_DATA
(see uCOS_II.H
) is used to hold information about the task stack. I decided to use a data structure for two reasons. First, I consider OSTaskStkChk()
to be a query-type function, and I wanted to have all query functions work the same way — return data about the query in a data structure. Second, passing data in a data structure is efficient and allows me to add additional fields in the future without changing the API (Application Programming Interface) of OSTaskStkChk()
. For now, OS_STK_DATA
only contains two fields: OSFree
and OSUsed
. As you can see, you invoke OSTaskStkChk()
by specifying the priority of the task you want to perform stack checking on.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskStkChk (INT8U prio, OS_STK_DATA *pdata)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_TCB *ptcb;
OS_STK *pchk;
INT32U free;
INT32U size;
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { (1)
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
if (p_stk_data == (OS_STK_DATA *)0) {
return (OS_ERR_PDATA_NULL);
}
#endif
pdata->OSFree = 0u;
pdata->OSUsed = 0u;
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { (2)
prio = OSTCBCur->OSTCBPrio;
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { (3)
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if ((ptcb->OSTCBOpt & OS_TASK_OPT_STK_CHK) == 0u) { (4)
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_OPT);
}
free = 0u; (5)
size = ptcb->OSTCBStkSize;
pchk = ptcb->OSTCBStkBottom;
OS_EXIT_CRITICAL();
#if OS_STK_GROWTH == 1u
while (*pchk++ == (OS_STK)0) {
free++;
}
#else
while (*pchk-- == (OS_STK)0) {
free++;
}
#endif
pdata->OSFree = free * sizeof(OS_STK); (6)
pdata->OSUsed = (size - free) * sizeof(OS_STK);
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) If (2) If you specify (3) Obviously, the task must exist. Simply checking for the presence of a non-NULL pointer in (4) To perform stack checking, you must have created the task using (5) If all the proper conditions are met, (6) Finally, the information that is stored in |
Deleting a Task, OSTaskDel()
Sometimes it is necessary to delete a task. Deleting a task means that the task will be returned to the DORMANT state (see section 3.02, Task States) and does not mean that the code for the task will be deleted. The task code is simply no longer scheduled by µC/OS-II. You delete a task by calling OSTaskDel()
(Listing 4.10).
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskDel (INT8U prio)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
#if OS_EVENT_EN > 0
OS_EVENT *pevent;
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FLAG_NODE *pnode;
#endif
OS_TCB *ptcb;
if (OSIntNesting > 0) { (1)
return (OS_ERR_TASK_DEL_ISR);
}
if (prio == OS_TASK_IDLE_PRIO) { (2)
return (OS_ERR_TASK_DEL_IDLE);
}
#if OS_ARG_CHK_EN > 0
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (3)
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { (4)
prio = OSTCBCur->OSTCBPrio;
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb != (OS_TCB *)0) { (5)
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_DEL);
}
OSRdyTbl[ptcb->OSTCBY] &= (OS_PRIO)~ptcb->OSTCBBitX; (6)
if (OSRdyTbl[ptcb->OSTCBY] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
#if (OS_EVENT_EN)
if (ptcb->OSTCBEventPtr != (OS_EVENT *)0) { (7)
OS_EventTaskRemove(ptcb, ptcb->OSTCBEventPtr); /
}
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) {
OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
}
#endif
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
pnode = ptcb->OSTCBFlagNode; (8)
if (pnode != (OS_FLAG_NODE *)0) {
OS_FlagUnlink(pnode);
}
#endif
ptcb->OSTCBDly = 0u; (9)
ptcb->OSTCBStat = OS_STAT_RDY; (10)
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
if (OSLockNesting < 255u) { (11)
OSLockNesting++;
}
OS_EXIT_CRITICAL(); (12)
OS_Dummy(); (13)
OS_ENTER_CRITICAL();
if (OSLockNesting > 0u) { (14)
OSLockNesting--;
}
OSTaskDelHook(ptcb); (15)
#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
OS_TLS_TaskDel(ptcb);
#endif
#endif
OSTaskCtr--; (16)
OSTCBPrioTbl[prio] = (OS_TCB *)0; (17)
if (ptcb->OSTCBPrev == (OS_TCB *)0) { (18)
ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *)0;
OSTCBList = ptcb->OSTCBNext;
} else {
ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext;
ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev;
}
ptcb->OSTCBNext = OSTCBFreeList; (19)
OSTCBFreeList = ptcb;
#if OS_TASK_NAME_EN > 0u
ptcb->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched(); (20)
}
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) (2) (3) You are allowed to delete the statistic task ( (4) The caller can delete itself by specifying (5) Once all conditions are satisfied, the (6) First, if the task is in the ready list, it is removed. (7) If the task is in a list waiting for a mutex, mailbox, queue, or semaphore, it is removed from that list. (8) If the task is in a list waiting for an event flag, it is removed from that list. (9) Next, (10) (11) At this point, the task to delete cannot be made ready to run by another task or an ISR because it’s been removed from the ready list, it’s not waiting for an event to occur, it’s not waiting for time to expire, and it cannot be resumed. For all intents and purposes, the task is DORMANT. Because of this, (12) At this point, (13) Note also that I call the dummy function (14) (15) (16) Next, (17) (18) (19) The (20) Last, but not least, the scheduler is called to see if a higher priority task has been made ready to run by an ISR that would have occurred when |
Requesting to Delete a Task, OSTaskDelReq()
Sometimes, a task owns resources such as memory buffers or a semaphore. If another task attempts to delete this task, the resources are not freed and thus are lost. This would lead to memory leaks which is not acceptable for just about any embedded system. In this type of situation, you somehow need to tell the task that owns these resources to delete itself when it’s done with the resources. You can accomplish this with the OSTaskDelReq()
function. Both the requestor and the task to be deleted need to call OSTaskDelReq()
. The requestor code is shown in Listing 4.11.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void RequestorTask (void *pdata)
{
INT8U err;
pdata = pdata;
for (;;) {
/* Application code */
if ('TaskToBeDeleted()' needs to be deleted) { (1)
while (OSTaskDelReq(TASK_TO_DEL_PRIO) != OS_TASK_NOT_EXIST) { (2)
OSTimeDly(1); (3)
}
}
/* Application code */ (4)
}
} |
Panel | ||
---|---|---|
| ||
(1) The task that makes the request needs to determine what conditions would cause a request for the task to be deleted. In other words, your application determines what conditions lead to this decision. (2) If the task needs to be deleted, call (3) You can do this by delaying the requestor for a certain amount of time, as I did in. I decided to delay for one tick, but you can certainly wait longer if needed. (4) When the requested task eventually deletes itself, the return value in L4.11(2) is |
The pseudocode for the task that needs to delete itself is shown in Listing 4.12. This task basically polls a flag that resides inside the task’s OS_TCB
. The value of this flag is obtained by calling OSTaskDelReq(OS_PRIO_SELF)
.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void TaskToBeDeleted (void *pdata)
{
INT8U err;
pdata = pdata;
for (;;) {
/* Application code */
if (OSTaskDelReq(OS_PRIO_SELF) == OS_TASK_DEL_REQ) { (1)
Release any owned resources; (2)
De-allocate any dynamic memory;
OSTaskDel(OS_PRIO_SELF); (3)
} else {
/* Application code */
}
}
} |
Panel | ||
---|---|---|
| ||
(1) When (2) & (3) In this case, the task to be deleted releases any resources owned and calls |
The code for OSTaskDelReq()
is shown in Listing 4.13. As usual, OSTaskDelReq()
needs to check for boundary conditions.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskDelReq (INT8U prio)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
BOOLEAN stat;
INT8U err;
OS_TCB *ptcb;
if (prio == OS_IDLE_PRIO) { (1)
return (OS_ERR_TASK_DEL_IDLE);
}
#if OS_ARG_CHK_EN > 0
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (2)
return (OS_ERR_PRIO_INVALID);
}
#endif
if (prio == OS_PRIO_SELF) { (3)
OS_ENTER_CRITICAL();
stat = OSTCBCur->OSTCBDelReq;
OS_EXIT_CRITICAL();
return (stat);
}
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { (4)
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); (6)
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_DEL);
}
ptcb->OSTCBDelReq = OS_ERR_TASK_DEL_REQ; (5)
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) First, (2) Next, it must ensure that the caller is not trying to request to delete an invalid priority. (3) If the caller is the task to be deleted, the flag stored in the (4) & (5) If you specified a task with a priority other than (6) If the task does not exist, |
Changing a Task’s Priority, OSTaskChangePrio()
When you create a task, you assign the task a priority. At run time, you can change the priority of any task by calling OSTaskChangePrio()
. In other words, µC/OS-II allows you to change priorities dynamically. The code for OSTaskChangePrio()
is shown in Listing 4.14.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
#if OS_EVENT_EN > 0
OS_EVENT *pevent;
#endif
OS_TCB *ptcb;
INT8U x;
INT8U y;
INT8U bitx;
INT8U bity;
#if OS_ARG_CHK_EN > 0u
if (oldprio >= OS_LOWEST_PRIO) { (1)
if (oldprio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
if (newprio >= OS_LOWEST_PRIO) {
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[newprio] != (OS_TCB *)0) { (2)
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
if (oldprio == OS_PRIO_SELF) {
oldprio = OSTCBCur->OSTCBPrio;
}
ptcb = OSTCBPrioTbl[oldprio]; (3)
if (ptcb == (OS_TCB *)0) {
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO);
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
#if OS_LOWEST_PRIO <= 63u
y_new = (INT8U)(newprio >> 3u); (4)
x_new = (INT8U)(newprio & 0x07u);
#else
y_new = (INT8U)((INT8U)(newprio >> 4u) & 0x0Fu);
x_new = (INT8U)(newprio & 0x0Fu);
#endif
bity_new = (OS_PRIO)(1uL << y_new);
bitx_new = (OS_PRIO)(1uL << x_new);
OSTCBPrioTbl[oldprio] = (OS_TCB *)0;
OSTCBPrioTbl[newprio] = ptcb;
y_old = ptcb->OSTCBY;
bity_old = ptcb->OSTCBBitY;
bitx_old = ptcb->OSTCBBitX;
if ((OSRdyTbl[y_old] & bitx_old) != 0u) { (5)
OSRdyTbl[y_old] &= (OS_PRIO)~bitx_old;
if (OSRdyTbl[y_old] == 0u) {
OSRdyGrp &= (OS_PRIO)~bity_old;
}
OSRdyGrp |= bity_new;
OSRdyTbl[y_new] |= bitx_new;
}
#if (OS_EVENT_EN)
pevent = ptcb->OSTCBEventPtr; (6)
if (pevent != (OS_EVENT *)0) {
pevent->OSEventTbl[y_old] &= (OS_PRIO)~bitx_old;
if (pevent->OSEventTbl[y_old] == 0u) {
pevent->OSEventGrp &= (OS_PRIO)~bity_old;
}
pevent->OSEventGrp |= bity_new;
pevent->OSEventTbl[y_new] |= bitx_new;
}
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) {
pevents = ptcb->OSTCBEventMultiPtr;
pevent = *pevents;
while (pevent != (OS_EVENT *)0) {
pevent->OSEventTbl[y_old] &= (OS_PRIO)~bitx_old;
if (pevent->OSEventTbl[y_old] == 0u) {
pevent->OSEventGrp &= (OS_PRIO)~bity_old;
}
pevent->OSEventGrp |= bity_new;
pevent->OSEventTbl[y_new] |= bitx_new;
pevents++;
pevent = *pevents;
}
}
#endif
#endif
ptcb->OSTCBPrio = newprio; (7)
ptcb->OSTCBY = y_new;
ptcb->OSTCBX = x_new;
ptcb->OSTCBBitY = bity_new;
ptcb->OSTCBBitX = bitx_new;
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched();
}
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) You cannot change the priority of the idle task. You can change either the priority of the calling task or another task. To change the priority of the calling task, either specify the old priority of that task or specify (2) Because µC/OS-II cannot have multiple tasks running at the same priority, (3) Here we are making sure that the priority we are changing does indeed exist. (4) (5) If the task that we are changing for is ready to run then we need to remove the task from the ready list at the current priority and insert it in the ready list at the new priority. (6) If the task is not ready, it could be waiting on a semaphore, mailbox, or queue. (7) Pre-computed are then saved in the task's TCB. After |
Suspending a Task, OSTaskSuspend()
Sometimes it is useful to explicitly suspend the execution of a task. This is accomplished with the OSTaskSuspend()
function call. A suspended task can only be resumed by calling the OSTaskResume()
function call. Task suspension is additive. This means that if the task being suspended is also waiting for time to expire, the suspension needs to be removed and the time needs to expire in order for the task to be ready to run. A task can suspend either itself or another task.
The code for OSTaskSuspend()
is shown in Listing 4.15.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskSuspend (INT8U prio)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
BOOLEAN self;
OS_TCB *ptcb;
#if OS_ARG_CHK_EN > 0
if (prio == OS_IDLE_PRIO) { (1)
return (OS_ERR_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (2)
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { (3)
prio = OSTCBCur->OSTCBPrio;
self = OS_TRUE;
} else if (prio == OSTCBCur->OSTCBPrio) { (4)
self = OS_TRUE;
} else {
self = OS_FALSE;
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { (5)
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_SUSPEND_PRIO);
}
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; (6)
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; (7)
OS_EXIT_CRITICAL();
if (self == OS_TRUE) {
OS_Sched(); (8)
}
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) (2) Next, you must specify a valid priority. Remember that the highest valid priority number (i.e., lowest priority) is (3) Next, (4) You could also decided to suspend the calling task by specifying its priority. In both of these cases, the scheduler needs to be called. This is why I created the local variable self, which will be examined at the appropriate time. If you are not suspending the calling task, then (5) (6) If so, it is removed from the ready list. Note that the task to suspend may not be in the ready list because it could be waiting for an event or for time to expire. In this case, the corresponding bit for the task to suspend in (7) Now (8) Finally, |
Resuming a Task, OSTaskResume()
As mentioned in the previous section, a suspended task can only be resumed by calling OSTaskResume()
. The code for OSTaskResume()
is shown in Listing 4.16.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskResume (INT8U prio)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_TCB *ptcb;
#if OS_ARG_CHK_EN > 0
if (prio >= OS_LOWEST_PRIO) { (1)
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { (2)
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_RESUME_PRIO);
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) != OS_STAT_RDY) { (3)
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_SUSPEND;
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) == OS_STAT_RDY) { (4)
if (ptcb->OSTCBDly == 0u) { (5)
OSRdyGrp |= ptcb->OSTCBBitY; (6)
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched(); (7)
}
} else {
OS_EXIT_CRITICAL();
} else {
OS_EXIT_CRITICAL();
}
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_SUSPENDED);
} |
Panel | ||
---|---|---|
| ||
(1) Because (2) & (3) The task to resume must exist because you will be manipulating its (4) (5) For the task to be ready to run, the (6) The task is made ready to run only when both conditions are satisfied. (7) Finally, the scheduler is called to see if the resumed task has a higher priority than the calling task. |
Getting Information about a Task, OSTaskQuery()
Your application can obtain information about itself or other application tasks by calling OSTaskQuery()
. In fact, OSTaskQuery()
obtains a copy of the contents of the desired task’s OS_TCB
. The fields available to you in the OS_TCB
depend on the configuration of your application (see OS_CFG.H
). Indeed, because µC/OS-II is scalable, it only includes the features that your application requires.
To call OSTaskQuery()
, your application must allocate storage for an OS_TCB
, as shown in Listing 4.17. This OS_TCB
is in a totally different data space from the OS_TCBs
allocated by µC/OS-II. After calling OSTaskQuery()
, this OS_TCB
contains a snapshot of the OS_TCB
for the desired task. You need to be careful with the links to other OS_TCBs
(i.e., .OSTCBNext
and .OSTCBPrev
); you don’t want to change what these links are pointing to! In general, only use this function to see what a task is doing — a great tool for debugging.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void MyTask (void *pdata)
{
OS_TCB MyTaskData;
pdata = pdata;
for (;;) {
/* User code */
err = OSTaskQuery(10, &MyTaskData);
/* Examine error code .. */
/* User code */
}
} |
The code for OSTaskQuery()
is shown in Listing 4.18.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSTaskQuery (INT8U prio, OS_TCB *pdata)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_TCB *ptcb;
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { (1)
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
if (p_task_data == (OS_TCB *)0) {
return (OS_ERR_PDATA_NULL);
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { (2)
prio = OSTCBCur->OSTCBPrio;
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { (3)
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO);
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
OS_MemCopy((INT8U *)p_task_data, (INT8U *)ptcb, sizeof(OS_TCB)); (4)
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) Note that I allow you to examine ALL the tasks, including the idle task. You need to be especially careful not to change what (2) & (3) As usual, (4) All fields are copied using the assignment shown instead of field by field. |