Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Task States

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
Figure - Five basic states of a task
Figure - Five basic states of a task

Panel
borderWidth0
titleFigure - Five basic states of a task

Image Added


Panel
bgColor#f0f0f0

(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, OSTaskCreate(). The task code actually resides in code space but µC/OS-III needs to be informed about it.

When it is no longer necessary for µC/OS-III to manage a task, your code can call the task delete function, OSTaskDel()OSTaskDel() does not actually delete the code of the task, it is simply not eligible to access the CPU.

(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 ready list (discussed later). This list is sorted by priority.

(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 OSStart(), or when µC/OS-III calls either OSIntExit() or OS_TASK_SW().

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 OSTaskSuspend() function unconditionally blocks a task and this task will not actually wait for an event to occur but in fact, waits until another task calls OSTaskResume() to make the task ready-to-run.

(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., OSFlagPost()OSQPost()OSSemPost()OSTaskQPost() and OSTaskSemPost()). The only post call not allowed to be made from an ISR is OSMutexPost() since mutexes, as will be addressed later, are assumed to be services that are only accessible at the task level.

As the state diagram indicates, an interrupt can interrupt another interrupt. This is called interrupt nesting and most processors allow this. However, interrupt nesting easily leads to stack overflow if not managed properly.


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
Figure - µC/OS-III’s internal task state machine
Figure - µC/OS-III’s internal task state machine

Panel
borderWidth0
titleFigure - µC/OS-III’s internal task state machine

Image Added


Panel
bgColor#f0f0f0

(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 OSTimeDly() or OSTimeDlyHMSM(). When the time expires or the delay is cancelled (by calling OSTimeDlyResume()), the task returns to the ready state.

(3) A task can wait for an event to occur by calling one of the pend (i.e., wait) functions (OSFlagPend()OSMutexPend()OSQPend()OSSemPend()OSTaskQPend(), or OSTaskSemPend()), and specify to wait forever for the event to occur. The pend terminates when the event occurs (i.e., a task or an ISR performs a “post”), the awaited object is deleted or, another task decides to abort the pend.

(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 OSTaskSuspend(). The only way the task is allowed to resume execution is by calling OSTaskResume(). Suspending a task means that a task will not be able to run on the CPU until it is resumed. If a task suspends itself then it must be resumed by another task.

(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 OSTimeDlyResume()) and the suspension must be removed (by another task which would call OSTaskResume()) in order for the task to be able to run.

(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
Listing - OS_TCB Data Structure
Listing - OS_TCB Data Structure

Code Block
languagecpp
titleListing - OS_TCB Data Structure
linenumberstrue
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).

...

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).

Image Modified

.NextPtr and .PrevPtr

...