
In the
previous article, we looked at the different types of scheduling supported by the RTOS and the corresponding features in Nucleus SE. This article will look at the additional scheduling options in Nucleus SE and the process of saving and restoring context.
Previous articles in the series:
Article # 9. Scheduler: implementationArticle # 8. Nucleus SE: Inside and DeploymentArticle # 7. Nucleus SE: introductionArticle # 6. Other RTOS servicesArticle # 5. Interaction between tasks and synchronizationArticle # 4. Tasks, context switching and interruptsArticle # 3. Tasks and planningArticle # 2. RTOS: Structure and Real Time
Article # 1. RTOS: introduction.
Optional Functions
When developing the Nucleus SE, I made the maximum number of functions optional, which saves on memory and / or time.
Suspending Tasks
As mentioned earlier in the
“Scheduler: Implementation” article , the Nucleus SE supports various options for suspending tasks, but this feature is optional and is enabled with the
NUSE_SUSPEND_ENABLE symbol in
nuse_config.h . If set to
TRUE , the data structure is defined as
NUSE_Task_Status [] . This type of suspension applies to all tasks. The array is of type
U8 , where 2 nibbles are used separately. The lower 4 bits contain the status of the task:
NUSE_READY, NUSE_PURE_SUSPEND ,
NUSE_SLEEP_SUSPEND ,
NUSE_MAILBOX_SUSPEND , etc. If a task is suspended by an API call (for example,
NUSE_MAILBOX_SUSPEND ), the upper 4 bits contain the index of the object on which the task is suspended. This information is used when the resource becomes available and to call the API you need to find out which of the suspended tasks you need to resume.
')
To perform the suspension of tasks, a pair of scheduler functions is used:
NUSE_Suspend_Task () and
NUSE_Wake_Task () .
The
NUSE_Suspend_Task () code is as follows:

The function saves the new task state (all 8 bits), received as the suspend_code parameter. When locking is enabled (see “API blocking calls” below), the return code
NUSE_SUCCESS is
preserved . Next,
NUSE_Reschedule () is called to transfer control to the next task.
The
NUSE_Wake_Task () code is quite simple:

The task status is set to
NUSE_READY . If the Priority Scheduler is not used, the current task continues to occupy the processor until it is time to release the resource. If the Priority Scheduler is used,
NUSE_Reschedule () is called with the task index as the execution indication, since the task may have a higher priority and must be immediately put to execution.
API blocking calls
Nucleus RTOS supports a variety of API calls, with which the developer can pause (block) a task if resources are unavailable. The task will resume when resources are available again. This mechanism is implemented in Nucleus SE and applies to a number of kernel objects: a task can be locked in a memory section, in an event group, in a mailbox, a queue, a channel, or a semaphore. But, like most tools in the Nucleus SE, it is optional and is defined by the symbol
NUSE_BLOCKING_ENABLE in
nuse_config.h . If set to
TRUE , then the array
NUSE_Task_Blocking_Return [] is defined, which contains the return code for each task; this can be
NUSE_SUCCESS or the
NUSE_MAILBOX_WAS_RESET code indicating that the object was reset when the task was blocked. When locking is enabled, the corresponding code is included in the API functions using conditional compilation.
Scheduler Counter
Nucleus RTOS calculates how many times a task has been scheduled since it was created and last reset. This feature is also implemented in the Nucleus SE, but is optional and is defined by the symbol
NUSE_SCHEDULE_COUNT_SUPPORT in
nuse_config.h . If set to
TRUE , an array of
NUSE_Task_Schedule_Count [] of type
U16 is created , which stores the counter of each task in the application.
Initial state of the task
When a task is created in the RTOS Nucleus, you can select its status: ready or suspended. In Nucleus SE, by default, all tasks are ready at startup. The option selected using the symbol
NUSE_INITIAL_TASK_STATE_SUPPORT in
nuse_config.h allows you to select the launch state. The
NUSE_Task_Initial_State [] array is defined in
nuse_config.c and requires the initialization of
NUSE_READY or
NUSE_PURE_SUSPEND for each task in the application.
Saving context
The idea of ​​saving the task context with any type of scheduler other than RTC (Run to Completion) was presented in article # 3 “Tasks and Planning”. As already mentioned, there are several ways to keep context. Given that the Nucleus SE is not intended for 32-bit processors, I chose to use tables rather than a stack to save the context.
A two-dimensional array of the type
ADDR NUSE_Task_Context [] [] is used to save the context for all tasks in the application. The rows are
NUSE_TASK_NUMBER (the number of tasks in the application), the columns are
NUSE_REGISTERS (the number of registers to be saved; depends on the processor and is set to
nuse_types.h) .
Of course, saving the context and restoring the code depends on the processor. And this is the only Nucleus SE code associated with a specific device (and development environment). I will give an example of a save / restore code for a ColdFire processor. Although this choice may seem strange due to an outdated processor, its assembler is easier to read than the assemblers of most modern processors. The code is fairly simple to use as the basis for creating a context switch for other processors:

When a context switch is required, this code is called in NUSE_Context_Swap. Two variables are used:
NUSE_Task_Active , the index of the current task, the context of which must be saved;
NUSE_Task_Next , the index of the task, the context of which must be downloaded (see section “Global data”).
The context preservation process works as follows:
- Registers A0 and D0 are temporarily stored on the stack;
- A0 is configured to point to an array of context blocks NUSE_Task_Context [] [] ;
- D0 is loaded using NUSE_Task_Active and multiplied by 72 (ColdFire has 18 registers requiring 72 bytes to store);
- then D0 is added to A0 , which now points to the context block for the current task;
- further registers are saved in the context block; first A0 and D0 (from the stack), then D1-D7 and A1-A6 , then SR and PC (from the stack, we will look at a quickly triggered context switch), and the stack pointer is saved at the end.
The process of loading the context is the same sequence of actions in the reverse order:
- A0 is configured to point to an array of context blocks NUSE_Task_Context [] [] ;
- D0 is loaded using NUSE_Task_Active , incremented and multiplied by 72;
- then D0 is added to A0 , which now points to the context block for the new task (since the context must be loaded in the reverse process of saving the sequence, the stack pointer is required first);
- further registers are restored from the context block; first the stack pointer, then PC and SR are pushed onto the stack, then D1-D7 and A1-A6 are loaded, and at the end D0 and A0 .
The difficulty in implementing context switching is difficult access to the state register for many processors (for ColdFire, this is
SR ). A common solution is to interrupt, i.e., a program interrupt or interrupt by conditional transition, which results in the
SR being loaded onto the stack along with the
PC . This is how Nucleus SE works on ColdFire. The macro
NUSE_CONTEXT_SWAP () is defined in
nuse_types.h , which expands to:
asm ("trap # 0");Below is the initialization code (
NUSE_Init_Task () in
nuse_init.c ) for context blocks:

This initializes the stack pointer,
PC and
SR . The first two have the values ​​set by the user in
nuse_config.c . The
SR value is defined as the
NUSE_STATUS_REGISTER character in
nuse_types.h . For ColdFire, this value is
0x40002000 .
Global data
The Nucleus SE scheduler requires very little memory to store data, but, of course, uses the data structures associated with the tasks, which will be discussed in detail in future articles.
RAM data
The scheduler does not use the data located in the ROM, and the RAM contains from 2 to 5 global variables (all set in
nuse_globals.c ), depending on which scheduler is used:
- NUSE_Task_Active - a variable of type U8 , containing the index of the current task;
- NUSE_Task_State - a variable of type U8 , containing a value indicating the status of the currently running code, which can be a task, an interrupt handler, or a launch code; possible values: NUSE_TASK_CONTEXT , NUSE_STARTUP_CONTEXT , NUSE_NISR_CONTEXT and NUSE_MISR_CONTEXT ;
- NUSE_Task_Saved_State is a U8 variable used to protect the NUSE_Task_State value in a controlled interrupt;
- NUSE_Task_Next is a variable of type U8 , containing the index of the next task to be scheduled for all schedulers except RTC;
- NUSE_Time_Slice_Ticks is a variable of type U16 containing a count of time slices ; Used only with TS Scheduler.
Data Footprint Data Scheduler
The Nucleus SE Scheduler does not use ROM data. The exact amount of RAM data varies depending on the scheduler used:
- for RTC - 2 bytes ( NUSE_Task_Active and NUSE_Task_State );
- for RR and Priority - 4 bytes ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State and NUSE_Task_Next );
- for TS - 6 bytes ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State , NUSE_Task_Next and NUSE_Time_Slice_Ticks ).
Implementation of other planning mechanisms
Although Nucleus SE offers a choice of 4 schedulers, covering most cases, the open architecture allows you to realize the possibilities for other cases.
Time slicing with background task
As discussed in
article # 3, “Tasks and Planning,” a simple time slice scheduler has limitations, because it limits the maximum time that a task can occupy a processor. A more difficult option would be to add support for the background task. Such a task could be scheduled on any slot allocated for suspended tasks, and run when the slot was partially released. This approach allows you to schedule tasks at regular intervals and the predicted amount of time the processor core to perform.
Priority and Round Robin (RR)
In most real-time kernels, the priority scheduler supports several tasks at each priority level, unlike Nucleus SE, where each task has a unique level. I prefer the latter because it greatly simplifies the data structures and, therefore, the scheduler code. To support more complex architectures, numerous ROM and RAM tables would be required.
About the author: Colin Walls has been working in the electronics industry for more than thirty years, spending a significant amount of time on embedded software. He is now an embedded software engineer in Mentor Embedded (a division of Mentor Graphics). Colin Walls often speaks at conferences and seminars, author of numerous technical articles and two books on embedded software. Lives in the UK.
Colin's professional
blog , e-mail: colin_walls@mentor.com.
On translation: this cycle of articles seemed interesting because, in spite of the outdated described approaches, the author introduces a little-prepared reader with real-time OS features in a very clear language. I myself belong to the team of creators of the
Russian RTOS , which we
intend to make free , and I hope that the cycle will be useful for novice developers.