...
From a µC/OS-III user point of view, a task can be in any one of five states as shown in the figure below. Internally, µC/OS-III does not need to keep track of the dormant state and the other states are tracked slightly differently. The actual µC/OS-III states will be discussed after a discussion on task states from the user’s point of view. The figure below also shows which µC/OS-III functions are used to move from one state to another. The diagram is actually simplified as state transitions are a bit more complicated than this.
Internally, µC/OS-III keeps track of task states using the state machine shown in the figure below. The task state is actually maintained in a variable that is part of a data structure associated with each task, the task’s TCB. The task state diagram was referenced throughout the design of µC/OS-III when implementing most of µC/OS-III’s services. The number in parentheses is the state number of the task and thus, a task can be in any one of eight (8) states (see os.h, OS_TASK_STATE_???
).
Note that the diagram does not keep track of a dormant task, as a dormant task is not known to µC/OS-III. Also, interrupts and interrupt nesting is tracked differently as will be explained further in the text.
This state diagram should be quite useful to understand how to use several functions and their impact on the state of tasks. In fact, I’d highly recommend that the reader bookmark the page of the diagram.
Task Control Blocks
A task control block (TCB) is a data structure used by kernels to maintain information about a task. Each task requires its own TCB and, for µC/OS-III, the user assigns the TCB in user memory space (RAM). The address of the task’s TCB is provided to µC/OS-III when calling task-related services (i.e., OSTask???()
functions). The task control block data structure is declared in os.h
as shown in the listing below. Note that the fields are actually commented in os.h
, and some of the fields are conditionally compiled based on whether or not certain features are desired. Both are not shown here for clarity.
Also, it is important to note that even when the user understands what the different fields of the OS_TCB
do, the application code must never directly access these (especially change them). In other words, OS_TCB
fields must only be accessed by µC/OS-III and not the code.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) The Dormant state corresponds to a task that resides in memory but has not been made available to µC/OS-III. A task is made available to µC/OS-III by calling a function to create the task, When it is no longer necessary for µC/OS-III to manage a task, your code can call the task delete function, (2) A task is in the Ready state when it is ready-to-run. There can be any number of tasks ready and µC/OS-III keeps track of all ready tasks in a (3) The most important ready-to-run task is placed in the Running state. On a single CPU, only one task can be running at any given time. The task selected to run on the CPU is switched in by µC/OS-III when the application code calls As previously discussed, tasks must wait for an event to occur. A task waits for an event by calling one of the functions that brings the task to the pending state if the event has not occurred. (4) Tasks in the Pending state are placed in a special list called a pend-list (or wait list) associated with the event the task is waiting for. When waiting for the event to occur, the task does not consume CPU time. When the event occurs, the task is placed back into the ready list and µC/OS-III decides whether the newly readied task is the most important ready-to-run task. If this is the case, the currently running task will be preempted (placed back in the ready list) and the newly readied task is given control of the CPU. In other words, the newly readied task will run immediately if it is the most important task. Note that the (5) Assuming that CPU interrupts are enabled, an interrupting device will suspend execution of a task and execute an Interrupt Service Routine (ISR). ISRs are typically events that tasks wait for. Generally speaking, an ISR should simply notify a task that an event occurred and let the task process the event. ISRs should be as short as possible and most of the work of handling the interrupting devices should be done at the task level where it can be managed by µC/OS-III. ISRs are only allowed to make “Post” calls (i.e., As the state diagram indicates, an interrupt can interrupt another interrupt. This is called |
Internally, µC/OS-III keeps track of task states using the state machine shown in the figure below. The task state is actually maintained in a variable that is part of a data structure associated with each task, the task’s TCB. The task state diagram was referenced throughout the design of µC/OS-III when implementing most of µC/OS-III’s services. The number in parentheses is the state number of the task and thus, a task can be in any one of eight (8) states (see os.h, OS_TASK_STATE_???
).
Note that the diagram does not keep track of a dormant task, as a dormant task is not known to µC/OS-III. Also, interrupts and interrupt nesting is tracked differently as will be explained further in the text.
This state diagram should be quite useful to understand how to use several functions and their impact on the state of tasks. In fact, I’d highly recommend that the reader bookmark the page of the diagram.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) A task is in State 0 when a task is ready-to-run. Every task “wants” to be ready-to-run as that is the only way it gets to perform their duties. (2) A task can decide to wait for time to expire by calling either (3) A task can wait for an event to occur by calling one of the pend (i.e., wait) functions ( (4) A task can wait for an event to occur as indicated, but specify that it is willing to wait a certain amount of time for the event to occur. If the event is not posted within that time, the task is readied, then the task is notified that a timeout occurred. Again, the pend terminates when the event occurs (i.e., a task or an ISR performs a “post”), the object awaited is deleted or, another task decides to abort the pend. (5) A task can suspend itself or another task by calling (6) A delayed task can also be suspended by another task. In this case, the effect is additive. In other words, the delay must complete (or be resumed by (7) A task waiting on an event to occur may be suspended by another task. Again, the effect is additive. The event must occur and the suspension removed (by another task) in order for the task to be able to run. Of course, if the object that the task is pending on is deleted or, the pend is aborted by another task, then one of the above two condition is removed. The suspension, however, must be explicitly removed. (8) A task can wait for an event, but only for a certain amount of time, and the task could also be suspended by another task. As one might expect, the suspension must be removed by another task (or the same task that suspended it in the first place), and the event needs to either occur or timeout while waiting for the event. |
Task Control Blocks
A task control block (TCB) is a data structure used by kernels to maintain information about a task. Each task requires its own TCB and, for µC/OS-III, the user assigns the TCB in user memory space (RAM). The address of the task’s TCB is provided to µC/OS-III when calling task-related services (i.e., OSTask???()
functions). The task control block data structure is declared in os.h
as shown in the listing below. Note that the fields are actually commented in os.h
, and some of the fields are conditionally compiled based on whether or not certain features are desired. Both are not shown here for clarity.
Also, it is important to note that even when the user understands what the different fields of the OS_TCB
do, the application code must never directly access these (especially change them). In other words, OS_TCB
fields must only be accessed by µC/OS-III and not the code.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
struct os_tcb {
CPU_STK *StkPtr;
void *ExtPtr;
CPU_CHAR *NamePtr;
CPU_STK *StkLimitPtr;
OS_TCB *NextPtr;
OS_TCB *PrevPtr;
OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;
OS_TICK_LIST *TickListPtr;
CPU_STK *StkBasePtr;
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE]
OS_TASK_PTR TaskEntryAddr;
void *TaskEntryArg;
OS_TCB *PendNextPtr;
OS_TCB *PendPrevPtr;
OS_PEND_OBJ *PendObjPtr;
OS_STATE PendOn;
OS_STATUS PendStatus;
OS_STATE TaskState;
OS_PRIO Prio;
OS_PRIO BasePrio;
OS_MUTEX *MutexGrpHeadPtr;
CPU_STK_SIZE StkSize;
OS_OPT Opt;
CPU_TS TS;
CPU_INT08U SemID;
OS_SEM_CTR SemCtr;
OS_TICK TickRemain;
OS_TICK TickCtrPrev;
OS_TICK TimeQuanta;
OS_TICK TimeQuantaCtr;
void *MsgPtr;
OS_MSG_SIZE MsgSize;
OS_MSG_Q MsgQ;
CPU_TS MsgQPendTime;
CPU_TS MsgQPendTimeMax;
OS_REG RegTbl[OS_TASK_REG_TBL_SIZE];
OS_FLAGS FlagsPend;
OS_FLAGS FlagsRdy;
OS_OPT FlagsOpt;
OS_MON_DATA MonData;
OS_NESTING_CTR SuspendCtr;
OS_CPU_USAGE CPUUsage;
OS_CPU_USAGE CPUUsageMax;
OS_CTX_SW_CTR CtxSwCtr;
CPU_TS CyclesDelta;
CPU_TS CyclesStart;
OS_CYCLES CyclesTotal;
OS_CYCLES CyclesTotalPrev;
CPU_TS SemPendTime;
CPU_TS SemPendTimeMax;
CPU_STK_SIZE StkUsed;
CPU_STK_SIZE StkFree;
CPU_TS IntDisTimeMax;
CPU SchedLockTimeMax;
OS_TCB DbgPrevPtr;
OS_TCB DbgNextPtr;
CPU_CHAR DbgNamePtr;
CPU_INT08U TaskID;
}; |
.StkPtr
This field contains a pointer to the current top-of-stack for the task. µC/OS-III allows each task to have its own stack and each stack can be any size. .StkPtr
should be the only field in the OS_TCB
data structure accessed from assembly language code (for the context-switching code). This field is therefore placed as the first entry in the structure making access easier from assembly language code (it will be at offset zero in the data structure).
...
This field contains a pointer to a user-definable data area used to extend the TCB as needed. This pointer is provided as an argument passed in OSTaskCreate()
. This pointer is easily accessible from assembly language since it always follows the .StkPtr
. .ExtPtr
can be used to add storage for saving the context of a FPU (Floating-Point Unit) if the processor you are using has a FPUhas a FPU.
.NamePtr
This pointer allows a name (an ASCII string) to be assigned to each task. Having a name is useful when debugging, since it is user friendly compared to displaying the address of the OS_TCB
. Storage for the ASCII string is assumed to be in user space, either in code memory (ASCII string declared as a const
) or in RAM.
.StkLimitPtr
The field contains a pointer to a location in the task’s stack to set a watermark limit for stack growth and is determined from the value of the “stk_limit
” argument passed to OSTaskCreate()
. Some processors have special registers that automatically check the value of the stack pointer at run-time to ensure that the stack does not overflow. .StkLimitPtr
may be used to set this register during a context switch. Alternatively, if the processor does not have such a register, this can be “simulated” in software. However, this is not as reliable as a hardware solution. If this feature is not used then you can set the value of “stk_limit
” can be set to 0
when calling OSTaskCreate()
. See also Detecting Task Stack Overflows).
.NextPtr and .PrevPtr
...
This pointer points to one of two tick lists: either the list of tasks delayed waiting for time to expire (OSTickListDly
) or tasks pending on an object with a timeout (OSTickListTimeout
). This field is used to easily remove the OS_TCB
from the list.
.NamePtr
This pointer allows a name (an ASCII string) to be assigned to each task. Having a name is useful when debugging, since it is user friendly compared to displaying the address of the OS_TCB
. Storage for the ASCII string is assumed to be in user space, either in code memory (ASCII string declared as a const
) or in RAMthe list.
.StkBasePtr
This field points to the base address of the task’s stack. The stack base is typically the lowest address in memory where the stack for the task resides. A task stack is declared as follows:
...
void MyTask (void *p_arg);
.PendNextPtr and .PendDataTblPtr
...
PendPrevPtr
These two pointers are used to doubly link the OS_TCB in a wait list for a kernel object such as a semaphore, event flag group, mutex or message queue. This is further described in Chapter 10, “Pend Lists”.
.PendObjPtr
Is a pointer to the kernel object pended on. This is further described in Chapter 10, “Pend Lists”.
This field indicates what the task is pending on and contains one of the following values declared in os.h
:
OS_TASK_PEND_ON_NOTHING
OS_TASK_PEND_ON_FLAG
OS_TASK_PEND_ON_TASK_Q
OS_TASK_PEND_ON_MULTIMUTEX
OS_TASK_PEND_ON_MUTEXQ
OS_TASK_PEND_ON_QSEM
OS_TASK_PEND_ON_TASK_SEM
OS_TASK_PEND_ON_TASKCOND_SEM VAR
.PendStatus
This field indicates the outcome of a pend and contains one of the values declared in os.h
:
...
This field is used to store a “time stamp” of when an event that the task was waiting on occurred. When the task resumes execution, this time stamp is returned to the caller.
.SemID
This field is used by third-party tools such TraceAlyzer by Percepio.
.SemCtr
This field contains a semaphore counter associated with the task. Each task has its own built-in semaphore. An ISR or another task can signal a task using this semaphore. .SemCtr
is therefore used to keep track of how many times the task is signaled. .SemCtr
is used by OSTaskSem???()
services.
...
You can also ‘add’ OS_OPT_PEND_FLAG_CONSUME
and either OS_OPT_PEND_BLOCKING
or OS_OPT_PEND_NON_BLOCKING
to the above options.
.MonData
Monitor data associated with a task.
.SuspendCtr
This field is used by OSTaskSuspend()
and OSTaskResume()
to keep track of how many times a task is suspended. Task suspension can be nested. When .SuspendCtr
is 0, all suspensions are removed. This field only exists in a TCB if task suspension is enabled at compile time (OS_CFG_TASK_SUSPEND_EN
is set to DEF_ENABLED
in os_cfg.h
).
...
This field contains a pointer to the name of the object that the task is pending on when the task is pending on either an event flag group, a semaphore, a mutual exclusion semaphore or a message queue. This information is quite useful during debugging and thus, this field is only present if if OS_CFG_DBG_EN
is is set to to DEF_ENABLED
in in os_cfg.h
.
.TaskID
This field is used by third-party tools such as TraceAlyzer by Percepio.