Embedded System Design Module 5
Embedded System Design Module 5
Real-Time Operating
System (RTOS) based
10 Embedded System
Design
LEARNING OBJECTIVES
LO 1 Understand the basics of an opera ng system and the need for an opera ng system
Learn the basic kernel services of an opera ng system
LO 2 Classify the types of opera ng systems
Learn the internals of Real-Time Opera ng System and the fundamentals of RTOS
based embedded firmware design
Learn about the different real- me kernels and the features that make a kernel
Real-Time
LO 3 Discuss tasks, processes and threads in the opera ng system context
Learn about the structure of a process, the different states of a process, process
life cycle and process management
Learn the concept of mul threading, thread standards and thread scheduling
LO 4 Understand the difference between mul processing and mul tasking
Learn about the different types of mul tasking (Co-opera ve, Preemp ve and
Non-preemp ve
LO 5 Describe the FCFS/FIFO, LCFS/LIFO, SJF and priority based task/process scheduling
Learn about the Shortest Remaining Time (SRT), Round Robin and priority based
preemp ve task/process scheduling
LO 6 Explain the different Inter Process Communica on (IPC) mechanisms used by tasks/
process to communicate and co-operate each other in a mul tasking environment
LO 7 Iden fy the RPC based Inter Process Communica on
Learn the different types of shared memory techniques (Pipes, memory mapped
object, etc.) for IPC
Learn the different types of message passing techniques (Message queue, mailbox,
signals, etc.) for IPC
LO 8 State the need for task synchronisa on in a mul tasking environment
Learn the different issues related to the accessing of a shared resource by mul ple
processes concurrently
388 Introduc on to Embedded Systems
In the previous chapter, we discussed about the Super loop based task execution model for firmware
execution. The super loop executes the tasks sequentially in the order in which the tasks are listed within the
loop. Here every task is repeated at regular intervals and the task execution is non-real time. As the number
of task increases, the time intervals at which a task gets serviced also increases. If some of the tasks involve
waiting for external events or I/O device usage, the task execution time also gets pushed off in accordance
with the ‘wait’ time consumed by the task. The priority in which a task is to be executed is fixed and is
determined by the task placement within the loop, in a super loop based execution. This type of firmware
execution is suited for embedded devices where response time for a task is not time critical. Typical examples
are electronic toys and video gaming devices. Here any response delay is acceptable and it will not create any
operational issues or potential hazards. Whereas certain applications demand time critical response to tasks/
events and any delay in the response may become catastrophic. Flight Control systems, Air bag control and
Anti-lock Brake System (ABS) systems for vehicles, Nuclear monitoring devices, etc. are typical examples
of applications/devices demanding time critical task response.
How the increasing need for time critical response for tasks/events is addressed in embedded applications?
Well the answer is
1. Assign priority to tasks and execute the high priority task when the task is ready to execute.
2. Dynamically change the priorities of tasks if required on a need basis.
3. Schedule the execution of tasks based on the priorities.
4. Switch the execution of task when a task is waiting for an external event or a system resource including
I/O device operation.
The introduction of operating system based firmware execution in embedded devices can address these
needs to a greater extent.
Real-Time Opera ng System (RTOS) based Embedded System Design 389
User Applications
Application
programming
Memory management interface (API)
Kernel Services
Process management
Time management
File system management
I/O system management
Device driver
interface
Underlying hardware
Fig. 10.1 The Operating System Architecture
File System Management File is a collection of related information. A file could be a program (source code
or executable), text files, image files, word documents, audio/video files, etc. Each of these files differ in the
kind of information they hold and the way in which the information is stored. The file operation is a useful
service provided by the OS. The file system management service of Kernel is responsible for
∑ The creation, deletion and alteration of files
∑ Creation, deletion and alteration of directories
∑ Saving of files in the secondary storage memory (e.g. Hard disk storage)
∑ Providing automatic allocation of file space based on the amount of free space available
∑ Providing a flexible naming convention for the files
The various file system management operations are OS dependent. For example, the kernel of Microsoft®
DOS OS supports a specific set of file system management operations and they are not the same as the file
system operations supported by UNIX Kernel.
I/O System (Device) Management Kernel is responsible for routing the I/O requests coming from different
user applications to the appropriate I/O devices of the system. In a well-structured OS, the direct accessing
of I/O devices are not allowed and the access to them are provided through a set of Application Programming
Interfaces (APIs) exposed by the kernel. The kernel maintains a list of all the I/O devices of the system. This
list may be available in advance, at the time of building the kernel. Some kernels, dynamically updates the
list of available devices as and when a new device is installed (e.g. Windows NT kernel keeps the list updated
when a new plug ‘n’ play USB device is attached to the system). The service ‘Device Manager’ (Name may
vary across different OS kernels) of the kernel is responsible for handling all I/O device related operations.
The kernel talks to the I/O device through a set of low-level systems calls, which are implemented in a
service, called device drivers. The device drivers are specific to a device or a class of devices. The Device
Manager is responsible for
∑ Loading and unloading of device drivers
∑ Exchanging information and the system specific control signals to and from the device
Secondary Storage Management The secondary storage management deals with managing the secondary
storage memory devices, if any, connected to the system. Secondary memory is used as backup medium for
programs and data since the main memory is volatile. In most of the systems, the secondary storage is kept in
disks (Hard Disk). The secondary storage management service of kernel deals with
∑ Disk storage allocation
∑ Disk scheduling (Time interval at which the disk is activated to backup data)
∑ Free Disk space management
Protec on Systems Most of the modern operating systems are designed in such a way to support multiple
users with different levels of access permissions (e.g. Windows 10 with user permissions like ‘Administrator’,
‘Standard’, ‘Restricted’, etc.). Protection deals with implementing the security policies to restrict the access
to both user and system resources by different applications or processes or users. In multiuser supported
operating systems, one user may not be allowed to view or modify the whole/portions of another user’s data
or profile details. In addition, some application may not be granted with permission to make use of some of
the system resources. This kind of protection is provided by the protection services running within the kernel.
Interrupt Handler Kernel provides handler mechanism for all external/internal interrupts generated by the
system.
These are some of the important services offered by the kernel of an operating system. It does not mean
that a kernel contains no more than components/services explained above. Depending on the type of the
Real-Time Opera ng System (RTOS) based Embedded System Design 391
operating system, a kernel may contain lesser number of components/services or more number of components/
services. In addition to the components/services listed above, many operating systems offer a number of add-
on system components/services to the kernel. Network communication, network management, user-interface
graphics, timer services (delays, timeouts, etc.), error handler, database management, etc. are examples for
such components/services. Kernel exposes the interface to the various kernel applications/services, hosted by
kernel, to the user applications through a set of standard Application Programming Interfaces (APIs). User
applications can avail these API calls to access the various kernel application/services.
[Link] Kernel Space and User Space
As we discussed in the earlier section, the applications/services are classified into two categories, namely:
user applications and kernel applications. The program code corresponding to the kernel applications/services
are kept in a contiguous area (OS dependent) of primary (working) memory and is protected from the un-
authorised access by user programs/applications. The memory space at which the kernel code is located is
known as ‘Kernel Space’. Similarly, all user applications are loaded to a specific area of primary memory
and this memory area is referred as ‘User Space’. User space is the memory area where user applications
are loaded and executed. The partitioning of memory into kernel and user space is purely Operating System
dependent. Some OS implements this kind of partitioning and protection whereas some OS do not segregate
the kernel and user application code storage into two separate areas. In an operating system with virtual
memory support, the user applications are loaded into its corresponding virtual memory space with demand
paging technique; Meaning, the entire code for the user application need not be loaded to the main (primary)
memory at once; instead the user application code is split into different pages and these pages are loaded into
and out of the main memory area on a need basis. The act of loading the code into and out of the main memory
is termed as ‘Swapping’. Swapping happens between the main (primary) memory and secondary storage
memory. Each process run in its own virtual memory space and are not allowed accessing the memory space
corresponding to another processes, unless explicitly requested by the process. Each process will have certain
privilege levels on accessing the memory of other processes and based on the privilege settings, processes can
request kernel to map another process’s memory to its own or share through some other mechanism. Most
of the operating systems keep the kernel application code in main memory and it is not swapped out into the
secondary memory.
[Link] Monolithic Kernel and Microkernel
As we know, the kernel forms the heart of an operating system. Different approaches are adopted for building
an Operating System kernel. Based on the kernel design, kernels can be classified into ‘Monolithic’ and
‘Micro’.
Monolithic Kernel In monolithic kernel architecture, all
kernel services run in the kernel space. Here all kernel Applications
modules run within the same memory space under a
single kernel thread. The tight internal integration of
kernel modules in monolithic kernel architecture allows
the effective utilisation of the low-level features of the
underlying system. The major drawback of monolithic
kernel is that any error or failure in any one of the Monolithic kernel with all
kernel modules leads to the crashing of the entire kernel operating system services
application. LINUX, SOLARIS, MS-DOS kernels running in kernel space
are examples of monolithic kernel. The architecture
representation of a monolithic kernel is given in Fig. 10.2. Fig. 10.2 The Monolithic Kernel Model
392 Introduc on to Embedded Systems
rules concerning time-critical allocation of a system’s resources. The RTOS decides which applications should
run in which order and how much time needs to be allocated for each application. Predictable performance
is the hallmark of a well-designed RTOS. This is best achieved by the consistent application of policies and
rules. Policies guide the design of an RTOS. Rules implement those policies and resolve policy conflicts.
Windows Embedded Compact, QNX, VxWorks MicroC/OS-II etc are examples of Real Time Operating
Systems (RTOS).
[Link] The Real-Time Kernel
The kernel of a Real-Time Operating System is referred as Real. Time kernel. In complement to the
conventional OS kernel, the Real-Time kernel is highly specialised and it contains only the minimal set of
services required for running the user applications/tasks. The basic functions of a Real-Time kernel are listed
below:
∑ Task/Process management
∑ Task/Process scheduling
∑ Task/Process synchronisation
∑ Error/Exception handling
∑ Memory management
∑ Interrupt handling
∑ Time management
Task/Process management Deals with setting up the memory space for the tasks, loading the task’s
code into the memory space, allocating system resources, setting up a Task Control Block (TCB) for the
task and task/process termination/deletion. A Task Control Block (TCB) is used for holding the information
corresponding to a task. TCB usually contains the following set of information.
Task ID: Task Identification Number
Task State: The current state of the task (e.g. State = ‘Ready’ for a task which is ready to execute)
Task Type: Task type. Indicates what is the type for this task. The task can be a hard real time or soft real
time or background task.
Task Priority: Task priority (e.g. Task priority = 1 for task with priority = 1)
Task Context Pointer: Context pointer. Pointer for context saving
Task Memory Pointers: Pointers to the code memory, data memory and stack memory for the task
Task System Resource Pointers: Pointers to system resources (semaphores, mutex, etc.) used by the task
Task Pointers: Pointers to other TCBs (TCBs for preceding, next and waiting tasks)
Other Parameters: Other relevant task parameters
The parameters and implementation of the TCB is kernel dependent. The TCB parameters vary across
different kernels, based on the task management implementation. Task management service utilises the TCB
of a task in the following way
∑ Creates a TCB for a task on creating a task
∑ Delete/remove the TCB of a task when the task is terminated or deleted
∑ Reads the TCB to get the state of a task
∑ Update the TCB with updated parameters on need basis (e.g. on a context switch)
∑ Modify the TCB to change the priority of the task dynamically
Task/Process Scheduling Deals with sharing the CPU among various tasks/processes. A kernel application
called ‘Scheduler’ handles the task scheduling. Scheduler is nothing but an algorithm implementation, which
394 Introduc on to Embedded Systems
performs the efficient and optimal scheduling of tasks to provide a deterministic behaviour. We will discuss
the various types of scheduling in a later section of this chapter.
Task/Process Synchronisa on Deals with synchronising the concurrent access of a resource, which is
shared across multiple tasks and the communication between various tasks. We will discuss the various
synchronisation techniques and inter task /process communication in a later section of this chapter.
Error/Excep on Handling Deals with registering and handling the errors occurred/exceptions raised during
the execution of tasks. Insufficient memory, timeouts, deadlocks, deadline missing, bus error, divide by zero,
unknown instruction execution, etc. are examples of errors/exceptions. Errors/Exceptions can happen at the
kernel level services or at task level. Deadlock is an example for kernel level exception, whereas timeout is
an example for a task level exception. The OS kernel gives the information about the error in the form of a
system call (API). GetLastError() API provided by Windows CE/Embedded Compact RTOS is an example
for such a system call. Watchdog timer is a mechanism for handling the timeouts for tasks. Certain tasks may
involve the waiting of external events from devices. These tasks will wait infinitely when the external device
is not responding and the task will generate a hang-up behaviour. In order to avoid these types of scenarios,
a proper timeout mechanism should be implemented. A watchdog is normally used in such situations. The
watchdog will be loaded with the maximum expected wait time for the event and if the event is not triggered
within this wait time, the same is informed to the task and the task is timed out. If the event happens before
the timeout, the watchdog is resetted.
Memory Management Compared to the General Purpose Operating Systems, the memory management
function of an RTOS kernel is slightly different. In general, the memory allocation time increases
depending on the size of the block of memory needs to be allocated and the state of the allocated memory
block (initialised memory block consumes more allocation time than un-initialised memory block). Since
predictable timing and deterministic behaviour are the primary focus of an RTOS, RTOS achieves this
by compromising the effectiveness of memory allocation. RTOS makes use of ‘block’ based memory
allocation technique, instead of the usual dynamic memory allocation techniques used by the GPOS.
RTOS kernel uses blocks of fixed size of dynamic memory and the block is allocated for a task on a need
basis. The blocks are stored in a ‘Free Buffer Queue’. To achieve predictable timing and avoid the timing
overheads, most of the RTOS kernels allow tasks to access any of the memory blocks without any memory
protection. RTOS kernels assume that the whole design is proven correct and protection is unnecessary.
Some commercial RTOS kernels allow memory protection as optional and the kernel enters a fail-safe
mode when an illegal memory access occurs.
A few RTOS kernels implement Virtual Memory* concept for memory allocation if the system supports
secondary memory storage (like HDD and FLASH memory). In the ‘block’ based memory allocation, a block
of fixed memory is always allocated for tasks on need basis and it is taken as a unit. Hence, there will not
be any memory fragmentation issues. The memory allocation can be implemented as constant functions and
thereby it consumes fixed amount of time for memory allocation. This leaves the deterministic behaviour of
the RTOS kernel untouched. The ‘block’ memory concept avoids the garbage collection overhead also. (We
will explore this technique under the MicroC/OS-II kernel in a latter chapter).The ‘block’ based memory
* Virtual Memory is an imaginary memory supported by certain operating systems. Virtual memory expands the address space available
to a task beyond the actual physical memory (RAM) supported by the system. Virtual memory is implemented with the help of a
Memory Management Unit (MMU) and ‘memory paging’. The program memory for a task can be viewed as different pages and the
page corresponding to a piece of code that needs to be executed is loaded into the main physical memory (RAM). When a memory page
is no longer required, it is moved out to secondary storage memory and another page which contains the code snippet to be executed is
loaded into the main memory. This memory movement technique is known as demand paging. The MMU handles the demand paging
and converts the virtual address of a location in a page to corresponding physical address in the RAM.
Real-Time Opera ng System (RTOS) based Embedded System Design 395
allocation achieves deterministic behaviour with the trade-of limited choice of memory chunk size and
suboptimal memory usage.
Interrupt Handling Deals with the handling of various types of interrupts. Interrupts provide Real-Time
behaviour to systems. Interrupts inform the processor that an external device or an associated task requires
immediate attention of the CPU. Interrupts can be either Synchronous or Asynchronous. Interrupts which
occurs in sync with the currently executing task is known as Synchronous interrupts. Usually the software
interrupts fall under the Synchronous Interrupt category. Divide by zero, memory segmentation error, etc. are
examples of synchronous interrupts. For synchronous interrupts, the interrupt handler runs in the same context
of the interrupting task. Asynchronous interrupts are interrupts, which occurs at any point of execution of any
task, and are not in sync with the currently executing task. The interrupts generated by external devices (by
asserting the interrupt line of the processor/controller to which the interrupt line of the device is connected)
connected to the processor/controller, timer overflow interrupts, serial data reception/ transmission interrupts,
etc. are examples for asynchronous interrupts. For asynchronous interrupts, the interrupt handler is usually
written as separate task (Depends on OS kernel implementation) and it runs in a different context. Hence,
a context switch happens while handling the asynchronous interrupts. Priority levels can be assigned to the
interrupts and each interrupts can be enabled or disabled individually. Most of the RTOS kernel implements
‘Nested Interrupts’ architecture. Interrupt nesting allows the pre-emption (interruption) of an Interrupt Service
Routine (ISR), servicing an interrupt, by a high priority interrupt.
Time Management Accurate time management is essential for providing precise time reference for all
applications. The time reference to kernel is provided by a high-resolution Real-Time Clock (RTC) hardware
chip (hardware timer). The hardware timer is programmed to interrupt the processor/controller at a fixed rate.
This timer interrupt is referred as ‘Timer tick’. The ‘Timer tick’ is taken as the timing reference by the kernel.
The ‘Timer tick’ interval may vary depending on the hardware timer. Usually the ‘Timer tick’ varies in the
microseconds range. The time parameters for tasks are expressed as the multiples of the ‘Timer tick’.
The System time is updated based on the ‘Timer tick’. If the System time register is 32 bits wide and the
‘Timer tick’ interval is 1 microsecond, the System time register will reset in
232 * 10–6/ (24 * 60 * 60) = 49700 Days = ~ 0.0497 Days = 1.19 Hours
If the ‘Timer tick’ interval is 1 millisecond, the system time register will reset in
232 * 10–3 / (24 * 60 * 60) = 497 Days = 49.7 Days = ~ 50 Days
The ‘Timer tick’ interrupt is handled by the ‘Timer Interrupt’ handler of kernel. The ‘Timer tick’ interrupt
can be utilised for implementing the following actions.
∑ Save the current context (Context of the currently executing task).
∑ Increment the System time register by one. Generate timing error and reset the System time register if
the timer tick count is greater than the maximum range available for System time register.
∑ Update the timers implemented in kernel (Increment or decrement the timer registers for each timer
depending on the count direction setting for each register. Increment registers with count direction
setting = ‘count up’ and decrement registers with count direction setting = ‘count down’).
∑ Activate the periodic tasks, which are in the idle state.
∑ Invoke the scheduler and schedule the tasks again based on the scheduling algorithm.
∑ Delete all the terminated tasks and their associated data structures (TCBs)
∑ Load the context for the first task in the ready queue. Due to the re-scheduling, the ready task might be
changed to a new one from the task, which was preempted by the ‘Timer Interrupt’ task.
Apart from these basic functions, some RTOS provide other functionalities also (Examples are file
management and network functions). Some RTOS kernel provides options for selecting the required kernel
396 Introduc on to Embedded Systems
functions at the time of building a kernel. The user can pick the required functions from the set of available
functions and compile the same to generate the kernel binary. Windows CE is a typical example for such an
RTOS. While building the target, the user can select the required components for the kernel.
[Link] Hard Real-Time
Real-Time Operating Systems that strictly adhere to the timing constraints for a task is referred as ‘Hard
Real-Time’ systems. A Hard Real-Time system must meet the deadlines for a task without any slippage.
Missing any deadline may produce catastrophic results for Hard Real-Time Systems, including permanent
data lose and irrecoverable damages to the system/users. Hard Real-Time systems emphasise the principle ‘A
late answer is a wrong answer’. A system can have several such tasks and the key to their correct operation
lies in scheduling them so that they meet their time constraints. Air bag control systems and Anti-lock Brake
Systems (ABS) of vehicles are typical examples for Hard Real-Time Systems. The Air bag control system
should be into action and deploy the air bags when the vehicle meets a severe accident. Ideally speaking, the
time for triggering the air bag deployment task, when an accident is sensed by the Air bag control system,
should be zero and the air bags should be deployed exactly within the time frame, which is predefined for
the air bag deployment task. Any delay in the deployment of the air bags makes the life of the passengers
under threat. When the air bag deployment task is triggered, the currently executing task must be pre-empted,
the air bag deployment task should be brought into execution, and the necessary I/O systems should be
made readily available for the air bag deployment task. To meet the strict deadline, the time between the
air bag deployment event triggering and start of the air bag deployment task execution should be minimum,
ideally zero. As a rule of thumb, Hard Real-Time Systems does not implement the virtual memory model for
handling the memory. This eliminates the delay in swapping in and out the code corresponding to the task
to and from the primary memory. In general, the presence of Human in the loop (HITL) for tasks introduces
unexpected delays in the task execution. Most of the Hard Real-Time Systems are automatic and does not
contain a ‘human in the loop’.
[Link] So Real-Time
Real-Time Operating System that does not guarantee meeting deadlines, but offer the best effort to meet the
deadline are referred as ‘Soft Real-Time’ systems. Missing deadlines for tasks are acceptable for a Soft Real-
time system if the frequency of deadline missing is within the compliance limit of the Quality of Service
(QoS). A Soft Real-Time system emphasises the principle ‘A late answer is an acceptable answer, but it could
have done bit faster’. Soft Real-Time systems most often have a ‘human in the loop (HITL)’. Automatic Teller
Machine (ATM) is a typical example for Soft-Real-Time System. If the ATM takes a few seconds more than
the ideal operation time, nothing fatal happens. An audio-video playback system is another example for Soft
Real-Time system. No potential damage arises if a sample comes late by fraction of a second, for playback.
10.3.1 Process
A ‘Process’ is a program, or part of it, in execution. Process is also known as an instance of a program in
execution. Multiple instances of the same program can execute simultaneously. A process requires various
system resources like CPU for executing the process, memory for storing the code corresponding to the process
and associated variables, I/O devices for information exchange, etc. A process is sequential in execution.
[Link] The Structure of a Process
The concept of ‘Process’ leads to concurrent execution (pseudo parallelism) of tasks and thereby the efficient
utilisation of the CPU and other system resources. Concurrent execution is achieved through the sharing of
CPU among the processes. A process mimics a processor in properties and holds a set of registers, process
status, a Program Counter (PC) to point to the next executable instruction of the process, a stack for holding the
local variables associated with the process and the code corresponding to the process. This can be visualised
as shown in Fig. 10.4.
A process which inherits all the properties of the CPU can be considered as a virtual processor, awaiting
its turn to have its properties switched into the physical processor. When the process gets its turn, its registers
and the program counter register becomes mapped to the physical registers of the CPU. From a memory
perspective, the memory occupied by the process is segregated into three regions, namely, Stack memory,
Data memory and Code memory (Fig. 10.5).
Process
Stack Memory
Stack
(Stack pointer) Stack memory grows
downwards
Working registers
Data memory grows
Status registers upwards
Data Memory
Code memory
corresponding to the
Process Code Memory
The ‘Stack’ memory holds all temporary data such as variables local to the process. Data memory holds
all global data for the process. The code memory contains the program code (instructions) corresponding
to the process. On loading a process into the main memory, a specific area of memory is allocated for the
process. The stack memory usually starts (OS Kernel implementation dependent) at the highest memory
address from the memory area allocated for the process. Say for example, the memory map of the memory
area allocated for the process is 2048 to 2100, the stack memory starts at address 2100 and grows downwards
to accommodate the variables local to the process.
398 Introduc on to Embedded Systems
Scheduled for
Interrupted or
what it is allowed to do next. Figure 10.6 represents
Execution
Preempted
the various states associated with a process. Blocked
The state at which a process is being created is
referred as ‘Created State’. The Operating System Wait
Wait ing fo
recognises a process in the ‘Created State’ but no ing fo r I/O
r Sha
red R
resources are allocated to the process. The state, esour
ce Running
where a process is incepted into the memory and
awaiting the processor time for execution, is known
as ‘Ready State’. At this stage, the process is placed Execution Completion
in the ‘Ready list’ queue maintained by the OS.
The state where in the source code instructions
corresponding to the process is being executed is Completed
called ‘Running State’. Running state is the state
at which the process execution happens. ‘Blocked
State/Wait State’ refers to a state where a running Fig. 10.6 Process states and state transition representation
process is temporarily suspended from execution
and does not have immediate access to resources. The blocked state might be invoked by various conditions
like: the process enters a wait state for an event to occur (e.g. Waiting for user inputs such as keyboard input)
or waiting for getting access to a shared resource (will be discussed at a later section of this chapter). A state
where the process completes its execution is known as ‘Completed State’. The transition of a process from
one state to another is known as ‘State transition’. When a process changes its state from Ready to running
or from running to blocked or terminated or from blocked to running, the CPU allocation for the process may
also change.
It should be noted that the state representation for a process/task mentioned here is a generic representation.
The states associated with a task may be known with a different name or there may be more or less number
of states than the one explained here under different OS kernel. For example, under VxWorks’ kernel, the
tasks may be in either one or a specific combination of the states READY, PEND, DELAY and SUSPEND.
The PEND state represents a state where the task/process is blocked on waiting for I/O or system resource.
The DELAY state represents a state in which the task/process is sleeping and the SUSPEND state represents
a state where a task/process is temporarily suspended from execution and not available for execution. Under
MicroC/OS-II kernel, the tasks may be in one of the states, DORMANT, READY, RUNNING, WAITING
or INTERRUPTED. The DORMANT state represents the ‘Created’ state and WAITING state represents the
state in which a process waits for shared resource or I/O access. We will discuss about the states and state
transition for tasks under VxWorks and uC/OS-II kernel in a later chapter.
Real-Time Opera ng System (RTOS) based Embedded System Design 399
10.3.2 Threads
A thread is the primitive that can execute code. A
thread is a single sequential flow of control within Stack memory for Thread 1
a process. ‘Thread’ is also known as lightweight
process. A process can have many threads of Stack memory for Thread 2
Stack Memory
execution. Different threads, which are part of a for Process
process, share the same address space; meaning
they share the data memory, code memory and heap
memory area. Threads maintain their own thread
status (CPU register values), Program Counter (PC) Data memory for process
and stack. The memory model for a process and its
associated threads are given in Fig. 10.7. Code memory for process
[Link] The Concept of Mul threading
Fig. 10.7 Memory organisation of a Process and its
A process/task in embedded application may be a
associated Threads
complex or lengthy one and it may contain various
suboperations like getting input from I/O devices connected to the processor, performing some internal
calculations/operations, updating some I/O devices etc. If all the subfunctions of a task are executed in
sequence, the CPU utilisation may not be efficient. For example, if the process is waiting for a user input,
the CPU enters the wait state for the event, and the process execution also enters a wait state. Instead of this
single sequential execution of the whole process, if the task/process is split into different threads carrying
out the different subfunctionalities of the process, the CPU can be effectively utilised and when the thread
corresponding to the I/O operation enters the wait state, another threads which do not require the I/O event
for their operation can be switched into execution. This leads to more speedy execution of the process and
the efficient utilisation of the processor time and resources. The multithreaded architecture of a process can
be better visualised with the thread-process diagram shown in Fig. 10.8.
If the process is split into multiple threads, which executes a portion of the process, there will be a main
thread and rest of the threads will be created within the main thread. Use of multiple threads to execute a
process brings the following advantage.
∑ Better memory utilisation. Multiple threads of the same process share the address space for data
memory. This also reduces the complexity of inter thread communication since variables can be shared
across the threads.
∑ Since the process is split into different threads, when one thread enters a wait state, the CPU can be
utilised by other threads of the process that do not require the event, which the other thread is waiting,
for processing. This speeds up the execution of the process.
∑ Efficient CPU utilisation. The CPU is engaged all time.
400 Introduc on to Embedded Systems
Task/Process
Code memory
Data memory
function the thread is going to execute and arguments is the arguments for ‘start_function’ (It is a void * in
the above example). On successful creation of a Pthread, pthread_create() associates the Thread Control
Block (TCB) corresponding to the newly created thread to the variable of type pthread_t (new_thread_ID in
our example).
The primitive
int pthread_join(pthread_t new_thread,void * *thread_status);
blocks the current thread and waits until the completion of the thread pointed by it (In this example new_
thread )
All the POSIX ‘thread calls’ returns an integer. A return value of zero indicates the success of the call. It
is always good to check the return value of each call.
Example 1
Write a multithreaded application to print “Hello I’m in main thread” from the main thread and “Hello I’m in
new thread” 5 times each, using the pthread_create() and pthread_join() POSIX primitives.
//Assumes the application is running on an OS where POSIX library is
//available
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
//******************************************************************
//New thread function for printing “Hello I’m in new thread”
void *new_thread( void *thread_args )
{
int i, j;
for( j= 0; j < 5; j++ )
{
printf(“Hello I’m in new thread\n” );
//Wait for some time. Do nothing
//The following line of code can be replaced with
//OS supported delay function like sleep(), delay () etc…
for( i= 0; i < 10000; i++ );
}
return NULL;
}
//******************************************************************
//Start of main thread
int main( void )
{
int i, j;
pthread_t tcb;
//Create the new thread for executing new_thread function
if (pthread_create( &tcb, NULL, new_thread, NULL ))
{
//New thread creation failed
printf(“Error in creating new thread\n” );
402 Introduc on to Embedded Systems
return -1;
}
for( j= 0; j < 5; j++ )
{
printf(“Hello I’m in main thread\n” );
//Wait for some time. Do nothing
//The following line of code can be replaced with
//OS supported delay function like sleep(), delay etc…
for( i= 0; i < 10000; i++ );
}
if (pthread_join(tcb, NULL ))
{
//Thread join failed
printf(“Error in Thread join\n” );
return -1;
}
return 1;
}
You can compile this application using the gcc compiler. Examine the output to figure out the thread
execution switching. The lines printed will give an idea of the order in which the thread execution is switched
between. The pthread_join call forces the main thread to wait until the completion of the thread tcb, if the
main thread finishes the execution first.
The termination of a thread can happen in different ways. The thread can terminate either by completing
its execution (natural termination) or by a forced termination. In a natural termination, the thread completes
its execution and returns back to the main thread through a simple return or by executing the pthread_exit()
call. Forced termination can be achieved by the call pthread_cancel() or through the termination of the main
thread with exit or exec functions. pthread_cancel() call is used by a thread to terminate another thread.
pthread_exit() call is used by a thread to explicitly exit after it completes its work and is no longer required
to exist. If the main thread finishes before the threads it has created, and exits with pthread_exit(), the other
threads continue to execute. If the main thread uses exit call to exit the thread, all threads created by the main
thread is terminated forcefully. Exiting a thread with the call pthread_exit() will not perform a cleanup. It
will not close any files opened by the thread and files will remain in the open status even after the thread
terminates. Calling pthread_join at the end of the main thread is the best way to achieve synchronisation and
proper cleanup. The main thread, after finishing its task waits for the completion of other threads, which were
joined to it using the pthread_join call. With a pthread_join call, the main thread waits other threads, which
were joined to it, and finally merges to the single main thread. If a new thread spawned by the main thread
is still not joined to the main thread, it will be counted against the system’s maximum thread limit. Improper
cleanup will lead to the failure of new thread creation.
Win32 Threads Win32 threads are the threads supported by various flavours of Windows Operating
Systems. The Win32 Application Programming Interface (Win32 API) libraries provide the standard set of
Win32 thread creation and management functions. Win32 threads are created with the API
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD
dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
DWORD dwCreationFlags, LPDWORD lpThreadId );
Real-Time Opera ng System (RTOS) based Embedded System Design 403
The parameter lpThreadAttributes defines the security attributes for the thread and dwStackSize defines the
stack size for the thread. These two parameters are not supported by the Windows CE/Embedded Compact
Real-Time Operating Systems and it should be kept as NULL and 0 respectively in a CreateThread API Call.
The other parameters are
lpStartAddress: Pointer to the function which is to be executed by the thread.
lpParameter: Parameter specifying an application-defined value that is passed to the thread routine.
dwCreationFlags: Defines the state of the thread when it is created. Usually it is kept as 0 or CREATE_
SUSPENDED implying the thread is created and kept at the suspended state.
lpThreadId: Pointer to a DWORD that receives the identifier for the thread.
On successful creation of the thread, CreateThread returns the handle to the thread and the thread
identifier.
The API GetCurrentThread(void) returns the handle of the current thread and GetCurrentThreadId(void)
returns its ID. GetThreadPriority (HANDLE hThread) API returns an integer value representing the current
priority of the thread whose handle is passed as hThread. Threads are always created with normal priority
(THREAD_PRIORITY_NORMAL. Refer MSDN documentation for the different thread priorities and their
meaning). SetThreadPriority (HANDLE hThread, int nPriority) API is used for setting the priority of a thread.
The first parameter to this function represents the thread handle and the second one the thread priority.
For Win32 threads, the normal thread termination happens when an exception occurs in the thread, or when
the thread’s execution is completed or when the primary thread or the process to which the thread is associated
is terminated. A thread can exit itself by calling the ExitThread (DWORD dwExitCode) API. The parameter
dwExitCode sets the exit code for thread termination. Calling ExitThread API frees all the resources utilised
by the thread. The exit code of a thread can be checked by other threads by calling the GetExitCodeThread
(HANDLE hThread, LPDWORD lpExitCode). TerminateThread (HANDLE hThread, DWORD dwExitCode)
API is used for terminating a thread from another thread. The handle hThread indicates which thread is
to be terminated and dwExitCode sets the exit code for the thread. This API will not execute the thread
termination and clean up code and may not free the resources occupied by the thread. TerminateThread is a
potentially dangerous call and it should not be used in normal conditions as a mechanism for terminating a
thread. Use this call only as a final choice. When a thread is terminated through TerminateThread method, the
system releases the thread’s initial stack and the thread will not get a chance to execute any user-mode code.
Also any dynamic link libraries (dlls) attached to the thread are not notified that the thread is terminating.
TerminateThread can lead to potential issues like: Non-releasing of the critical section object, any, owned
by the thread, non-releasing of heap lock, if the thread is allocating memory from the heap, inconsistencies
of the kernel32 state for the thread’s process if the thread was executing certain kernel32 call when it is
terminated, issues in shared dll functions, if the thread was manipulating the global state of a shared dll
when it is terminated etc. SuspendThread(HANDLE hThread) API can be used for suspending a thread
from execution provided the handle hThread possesses THREAD_SUSPEND_RESUME access right. If the
SuspendThread API call succeeds, the thread stops executing and increments its internal suspend count. The
thread becomes suspended if its suspend count is greater than zero. The SuspendThread function is primarily
designed for use by debuggers. One must be cautious in using this API for the reason it may cause deadlock
condition if the thread is suspended at a stage where it acquired a mutex or shared resource and another
thread tries to access the same. The ResumeThread(HANDLE hThread) API is used for resuming a suspended
thread. The ResumeThread API checks the suspend count of the specified thread. A suspend count of zero
indicates that the specified thread is not currently in the suspended mode. If the count is not zero, the count is
decremented by one and if the resulting count value is zero, the thread is resumed. The API Sleep (DWORD
404 Introduc on to Embedded Systems
dwMilliseconds) can be used for suspending a thread for the duration specified in milliseconds by the Sleep
function. The Sleep call is initiated by the thread.
Example 2
Write a multithreaded application using Win32 APIs to set up a counter in the main thread and secondary
thread to count from 0 to 10 and print the counts from both the threads. Put a delay of 500 ms in between the
successive printing in both the threads.
#include “stdafx.h”
#include “windows.h”
#include “stdio.h”
//******************************************************************
//Child thread
//******************************************************************
void ChildThread(void)
{
char i;
for (i = 0; i <= 10; ++i)
{
printf(“Executing Child Thread : Counter = %d\n”, i);
Sleep(500);
}
}
//******************************************************************
//Primary thread
//******************************************************************
int main(int argc, char* argv[])
{
HANDLE hThread;
DWORD dwThreadID;
char i;
hThread = CreateThread(NULL, 1000, (LPTHREAD_START_ROUTINE)ChildThread,
NULL, 0, &dwThreadID);
if (hThread == NULL)
{
printf(“Thread Creation Failed\nError No : %d\n”, GetLastError());
return 1;
}
for (i = 0; i <= 10; ++i)
{
printf(“Executing Main Thread : Counter = %d\n”, i);
Sleep(500);
}
return 0;
}
Real-Time Opera ng System (RTOS) based Embedded System Design 405
To execute this program, create a new Win32 Console Application with Microsoft Visual Studio using
Visual C++ and add the above piece of code to it and compile. The output obtained on running this application
on a machine with Windows 10 operating system is given in Fig. 10.9.
The above piece of code creates a new class MyThread by extending the base class Thread. It also
overrides the run() method inherited from the base class with its own run() method. The run() method of
MyThread implements all the task for the MyThread thread. The method start() moves the thread to a pool
of threads waiting for their turn to be picked up for execution by the scheduler. The thread is said to be in the
‘Ready’ state at this stage. The scheduler picks the threads for execution from the pool based on the thread
priorities.
E.g. [Link]();
The output of the above piece of code when executed on Windows 10 platform is given in Fig. 10.10.
Invoking the static method yield() voluntarily give up the execution of the thread and the thread is moved
to the pool of threads waiting to get their turn for execution, i.e. the thread enters the ‘Ready’ state.
E.g. [Link]();
The static method sleep() forces the thread to sleep for the duration mentioned by the sleep call, i.e. the
thread enters the ‘Suspend’ mode. Once the sleep period is expired, the thread is moved to the pool of threads
waiting to get their turn for execution, i.e. the thread enters the ‘Ready’ state. The method sleep() only
guarantees that the thread will sleep for the minimum period mentioned by the argument to the call. It will not
guarantee anything on the resume of the thread after the sleep period. It is dependent on the scheduler.
E.g. [Link](100); Sleep for 100 milliseconds.
Calling a thread Object’s wait() method causes the thread object to wait. The thread will remain in the
‘Wait’ state until another thread invokes the notify() or notifyAll() method of the thread object which is
waiting. The thread enters the ‘Blocked’ state when waiting for input from I/O devices or waiting for object
lock in case of accessing shared resources. The thread is moved to the ‘Ready’ state on receiving the I/O
input or on acquiring the object lock. The thread enters the ‘Finished/Dead’ state on completion of the task
assigned to it or when the stop() method is explicitly invoked. The thread may also enter this state if it is
terminated by an unrecoverable error condition.
For more information on Java threads, visit Sun Micro System’s tutorial on Threads, available at http://
[Link]/tutorial/applet/overview/[Link]
Summary So far we discussed about the various thread classes available for creation and management of
threads in a multithreaded system in a General Purpose Operating System’s perspective. From an RTOS
perspective, POSIX threads and Win32 threads are the most commonly used thread class libraries for thread
creation and management. Many non-standard, proprietary thread classes are also used by some proprietary
RTOS. Portable threads (Pth), a very portable POSIX/ANSI-C based library from GNU, may be the “next
generation” threads library. Pth provides non-preemptive priority based scheduling for multiple threads inside
event driven applications. Visit [Link] for more details on GNU Portable threads.
Real-Time Opera ng System (RTOS) based Embedded System Design 407
A thread cannot live independently; it lives within the A process contains at least one thread.
process.
There can be multiple threads in a process. The first thread Threads within a process share the code, data and heap
(main thread) calls the main function and occupies the start memory. Each thread holds separate memory area for stack
of the stack memory of the process. (shares the total stack memory of the process).
Threads are very inexpensive to create Processes are very expensive to create. Involves many OS
overhead.
Context switching is inexpensive and fast Context switching is complex and involves lot of OS over-
head and is comparatively slower.
If a thread expires, its stack is reclaimed by the process. If a process dies, the resources allocated to it are reclaimed
by the OS and all the associated threads of the process
also dies.
any point of time, he throws only one ball and catches only one per hand. However, the speed at which he is
switching the balls for throwing and catching creates the illusion, he is throwing and catching multiple balls
or using more than two hands ☺ simultaneously, to the spectators.
‘Context Switching’
Processes
Time
or system resource. The co-operative and non-preemptive multitasking differs in their behaviour when they
are in the ‘Blocked/Wait’ state. In co-operative multitasking, the currently executing process/task need not
relinquish the CPU when it enters the ‘Blocked/Wait’ state, waiting for an I/O, or a shared resource access or
an event to occur whereas in non-preemptive multitasking the currently executing task relinquishes the CPU
when it waits for an I/O or system resource or an event to occur.
The Operating System maintains various queues† in connection with the CPU scheduling, and a process
passes through these queues during the course of its admittance to execution completion.
The various queues maintained by OS in association with CPU scheduling are:
Job Queue: Job queue contains all the processes in the system
Ready Queue: Contains all the processes, which are ready for execution and waiting for CPU to get their
turn for execution. The Ready queue is empty when there is no process ready for running.
Device Queue: Contains the set of processes, which are waiting for an I/O device.
A process migrates through all these queues during its journey from ‘Admitted’ to ‘Completed’ stage. The
following diagrammatic representation (Fig. 10.12) illustrates the transition of a process through the various
queues.
Based on the scheduling algorithm used, the scheduling can be classified into the following categories.
† Queue is a special kind of arrangement of a collection of objects. In the operating system context queue is considered as a buffer.
412 Introduc on to Embedded Systems
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 10, 5, 7 milliseconds respectively
enters the ready queue together in the order P1, P2, P3. Calculate the waiting time and Turn Around Time
(TAT) for each process and the average waiting time and Turn Around Time (Assuming there is no I/O
waiting for the processes).
The sequence of execution of the processes by the CPU is represented as
P1 P2 P3
0 10 15 22
10 5 7
Assuming the CPU is readily available at the time of arrival of P1, P1 starts executing without any waiting in
the ‘Ready’ queue. Hence the waiting time for P1 is zero. The waiting time for all processes are given as
Waiting Time for P1 = 0 ms (P1 starts executing first)
Waiting Time for P2 = 10 ms (P2 starts executing after completing P1)
Waiting Time for P3 = 15 ms (P3 starts executing after completing P1 and P2)
Average waiting time = (Waiting time for all processes) / No. of Processes
= (Waiting time for (P1+P2+P3)) / 3
= (0+10+15)/3 = 25/3
= 8.33 milliseconds
Turn Around Time (TAT) for P1 = 10 ms (Time spent in Ready Queue + Execution Time)
Turn Around Time (TAT) for P2 = 15 ms (-Do-)
Turn Around Time (TAT) for P3 = 22 ms (-Do-)
Average Turn Around Time = (Turn Around Time for all processes) / No. of Processes
= (Turn Around Time for (P1+P2+P3)) / 3
= (10+15+22)/3 = 47/3
= 15.66 milliseconds
Average Turn Around Time (TAT) is the sum of average waiting time and average execution time.
Average Execution Time = (Execution time for all processes)/No. of processes
= (Execution time for (P1+P2+P3))/3
= (10+5+7)/3 = 22/3
= 7.33
Average Turn Around Time = Average waiting time + Average execution time
= 8.33 + 7.33
= 15.66 milliseconds
Real-Time Opera ng System (RTOS) based Embedded System Design 413
Example 2
Calculate the waiting time and Turn Around Time (TAT) for each process and the Average waiting time and
Turn Around Time (Assuming there is no I/O waiting for the processes) for the above example if the process
enters the ‘Ready’ queue together in the order P2, P1, P3.
The sequence of execution of the processes by the CPU is represented as
P2 P1 P3
0 5 15 22
5 10 7
Assuming the CPU is readily available at the time of arrival of P2, P2 starts executing without any waiting in
the ‘Ready’ queue. Hence the waiting time for P2 is zero. The waiting time for all processes is given as
Waiting Time for P2 = 0 ms (P2 starts executing first)
Waiting Time for P1 = 5 ms (P1 starts executing after completing P2)
Waiting Time for P3 = 15 ms (P3 starts executing after completing P2 and P1)
Average waiting time = (Waiting time for all processes) / No. of Processes
= (Waiting time for (P2+P1+P3)) / 3
= (0+5+15)/3 = 20/3
= 6.66 milliseconds
Turn Around Time (TAT) for P2 = 5 ms (Time spent in Ready Queue + Execution Time)
Turn Around Time (TAT) for P1 = 15 ms (-Do-)
Turn Around Time (TAT) for P3 = 22 ms (-Do-)
Average Turn Around Time = (Turn Around Time for all processes) / No. of Processes
= (Turn Around Time for (P2+P1+P3)) / 3
= (5+15+22)/3 = 42/3
= 14 milliseconds
The Average waiting time and Turn Around Time (TAT) depends on the order in which the processes
enter the ‘Ready’ queue, regardless there estimated completion time.
From the above two examples it is clear that the Average waiting time and Turn Around Time improve if
the process with shortest execution completion time is scheduled first.
The major drawback of FCFS algorithm is that it favours monopoly of process. A process, which does
not contain any I/O operation, continues its execution until it finishes its task. If the process contains any
I/O operation, the CPU is relinquished by the process. In general, FCFS favours CPU bound processes and
I/O bound processes may have to wait until the completion of CPU bound process, if the currently executing
process is a CPU bound process. This leads to poor device utilisation. The average waiting time is not
minimal for FCFS scheduling algorithm.
[Link] Last-Come-First Served (LCFS)/LIFO Scheduling
The Last-Come-First Served (LCFS) scheduling algorithm also allocates CPU time to the processes based
on the order in which they are entered in the ‘Ready’ queue. The last entered process is serviced first. LCFS
scheduling is also known as Last In First Out (LIFO) where the process, which is put last into the ‘Ready’
queue, is serviced first.
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 10, 5, 7 milliseconds respectively
enters the ready queue together in the order P1, P2, P3 (Assume only P1 is present in the ‘Ready’ queue when
414 Introduc on to Embedded Systems
the scheduler picks it up and P2, P3 entered ‘Ready’ queue after that). Now a new process P4 with estimated
completion time 6 ms enters the ‘Ready’ queue after 5 ms of scheduling P1. Calculate the waiting time and
Turn Around Time (TAT) for each process and the Average waiting time and Turn Around Time (Assuming
there is no I/O waiting for the processes). Assume all the processes contain only CPU operation and no I/O
operations are involved.
Initially there is only P1 available in the Ready queue and the scheduling sequence will be P1, P3, P2. P4
enters the queue during the execution of P1 and becomes the last process entered the ‘Ready’ queue. Now the
order of execution changes to P1, P4, P3, and P2 as given below.
P1 P4 P3 P2
0 10 16 23 28
10 6 7 5
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 10, 5, 7 milliseconds respectively
enters the ready queue together. Calculate the waiting time and Turn Around Time (TAT) for each process
Real-Time Opera ng System (RTOS) based Embedded System Design 415
and the Average waiting time and Turn Around Time (Assuming there is no I/O waiting for the processes) in
SJF algorithm.
The scheduler sorts the ‘Ready’ queue based on the shortest estimated completion time and schedules the
process with the least estimated completion time first and the next least one as second, and so on. The order
in which the processes are scheduled for execution is represented as
P2 P3 P1
0 5 12 22
5 7 10
The estimated execution time of P2 is the least (5 ms) followed by P3 (7 ms) and P1 (10 ms).
The waiting time for all processes are given as
Waiting Time for P2 = 0 ms (P2 starts executing first)
Waiting Time for P3 = 5 ms (P3 starts executing after completing P2)
Waiting Time for P1 = 12 ms (P1 starts executing after completing P2 and P3)
Average waiting time = (Waiting time for all processes) / No. of Processes
= (Waiting time for (P2+P3+P1)) / 3
= (0+5+12)/3 = 17/3
= 5.66 milliseconds
Turn Around Time (TAT) for P2 = 5 ms (Time spent in Ready Queue + Execution Time)
Turn Around Time (TAT) for P3 = 12 ms (-Do-)
Turn Around Time (TAT) for P1 = 22 ms (-Do-)
Average Turn Around Time = (Turn Around Time for all processes) / No. of Processes
= (Turn Around Time for (P2+P3+P1)) / 3
= (5+12+22)/3 = 39/3
= 13 milliseconds
Average Turn Around Time (TAT) is the sum of average waiting time and average execution time.
The average Execution time = (Execution time for all processes)/No. of processes
= (Execution time for (P1+P2+P3))/3
= (10+5+7)/3 = 22/3 = 7.33
Average Turn Around Time = Average Waiting time + Average Execution time
= 5.66 + 7.33
= 13 milliseconds
From this example, it is clear that the average waiting time and turn around time is much improved with the
SJF scheduling for the same processes when compared to the FCFS algorithm.
Example 2
Calculate the waiting time and Turn Around Time (TAT) for each process and the Average waiting time and
Turn Around Time for the above example if a new process P4 with estimated completion time 2 ms enters
the ‘Ready’ queue after 2 ms of execution of P2. Assume all the processes contain only CPU operation and
no I/O operations are involved.
At the beginning, there are only three processes (P1, P2 and P3) available in the ‘Ready’ queue and
the SJF scheduler picks up the process with the least execution completion time (In this example P2 with
416 Introduc on to Embedded Systems
execution completion time 5 ms) for scheduling. The execution sequence diagram for this is same as that of
Example 1.
Now process P4 with estimated execution completion time 2 ms enters the ‘Ready’ queue after 2 ms of
start of execution of P2. Since the SJF algorithm is non-preemptive and process P2 does not contain any I/O
operations, P2 continues its execution. After 5 ms of scheduling, P2 terminates and now the scheduler again
sorts the ‘Ready’ queue for process with least execution completion time. Since the execution completion
time for P4 (2 ms) is less than that of P3 (7 ms), which was supposed to be run after the completion of P2
as per the ‘Ready’ queue available at the beginning of execution scheduling, P4 is picked up for executing.
Due to the arrival of the process P4 with execution time 2 ms, the ‘Ready’ queue is re-sorted in the order P2,
P4, P3, P1. At the beginning it was P2, P3, P1. The execution sequence now changes as per the following
diagram
P2 P4 P3 P1
0 5 7 14 24
5 2 7 10
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 10, 5, 7 milliseconds and priorities
0, 3, 2 (0—highest priority, 3—lowest priority) respectively enters the ready queue together. Calculate the
waiting time and Turn Around Time (TAT) for each process and the Average waiting time and Turn Around
Time (Assuming there is no I/O waiting for the processes) in priority based scheduling algorithm.
The scheduler sorts the ‘Ready’ queue based on the priority and schedules the process with the highest
priority (P1 with priority number 0) first and the next high priority process (P3 with priority number 2) as
second, and so on. The order in which the processes are scheduled for execution is represented as
P1 P3 P2
0 10 17 22
10 7 5
Example 2
Calculate the waiting time and Turn Around Time (TAT) for each process and the Average waiting time and
Turn Around Time for the above example if a new process P4 with estimated completion time 6 ms and
priority 1 enters the ‘Ready’ queue after 5 ms of execution of P1. Assume all the processes contain only CPU
operation and no I/O operations are involved.
At the beginning, there are only three processes (P1, P2 and P3) available in the ‘Ready’ queue and the
scheduler picks up the process with the highest priority (In this example P1 with priority 0) for scheduling.
The execution sequence diagram for this is same as that of Example 1. Now process P4 with estimated
execution completion time 6 ms and priority 1 enters the ‘Ready’ queue after 5 ms of execution of P1. Since
the scheduling algorithm is non-preemptive and process P1 does not contain any I/O operations, P1 continues
its execution. After 10 ms of scheduling, P1 terminates and now the scheduler again sorts the ‘Ready’ queue
for process with highest priority. Since the priority for P4 (priority 1) is higher than that of P3 (priority 2),
which was supposed to be run after the completion of P1 as per the ‘Ready’ queue available at the beginning
of execution scheduling, P4 is picked up for executing. Due to the arrival of the process P4 with priority 1,
the ‘Ready’ queue is resorted in the order P1, P4, P3, P2. At the beginning it was P1, P3, P2. The execution
sequence now changes as per the following diagram
P1 P4 P3 P2
0 10 16 23 28
10 6 7 5
The waiting time for all the processes are given as
Waiting time for P1 = 0 ms (P1 starts executing first)
Waiting time for P4 = 5 ms (P4 starts executing after completing P1. But P4 arrived after 5 ms of execution of
P1. Hence its waiting time = Execution start time – Arrival Time = 10 – 5 = 5)
Waiting time for P3 = 16 ms (P3 starts executing after completing P1 and P4)
Waiting time for P2 = 23 ms (P2 starts executing after completing P1, P4 and P3)
Average waiting time = (Waiting time for all processes) / No. of Processes
= (Waiting time for (P1+P4+P3+P2)) / 4
= (0 + 5 + 16 + 23)/4 = 44/4
= 11 milliseconds
Turn Around Time (TAT) for P1 = 10 ms (Time spent in Ready Queue + Execution Time)
Turn Around Time (TAT) for P4 = 11 ms (Time spent in Ready Queue + Execution
Time = (Execution Start Time – Arrival Time) + Estimated
Execution Time = (10 – 5) + 6 = 5 + 6)
Turn Around Time (TAT) for P3 = 23 ms (Time spent in Ready Queue + Execution Time)
Turn Around Time (TAT) for P2 = 28 ms (Time spent in Ready Queue + Execution Time)
Average Turn Around Time = (Turn Around Time for all processes) / No. of Processes
= (Turn Around Time for (P2 + P4 + P3 + P1)) / 4
= (10 + 11 + 23 + 28)/4 = 72/4
= 18 milliseconds
Real-Time Opera ng System (RTOS) based Embedded System Design 419
Similar to SJF scheduling algorithm, non-preemptive priority based algorithm also possess the drawback
of ‘Starvation’ where a process whose priority is low may not get a chance to execute if more and more
processes with higher priorities enter the ‘Ready’ queue before the process with lower priority started its
execution. ‘Starvation’ can be effectively tackled in priority based non-preemptive scheduling by dynamically
raising the priority of the low priority task/process which is under starvation (waiting in the ready queue for a
longer time for getting the CPU time). The technique of gradually raising the priority of processes which are
waiting in the ‘Ready’ queue as time progresses, for preventing ‘Starvation’, is known as ‘Aging’.
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 10, 5, 7 milliseconds respectively
enters the ready queue together. A new process P4 with estimated completion time 2 ms enters the ‘Ready’
queue after 2 ms. Assume all the processes contain only CPU operation and no I/O operations are involved.
At the beginning, there are only three processes (P1, P2 and P3) available in the ‘Ready’ queue and
the SRT scheduler picks up the process with the shortest remaining time for execution completion (In this
example, P2 with remaining time 5 ms) for scheduling. The execution sequence diagram for this is same as
that of example 1 under non-preemptive SJF scheduling.
Now process P4 with estimated execution completion time 2 ms enters the ‘Ready’ queue after 2 ms of
420 Introduc on to Embedded Systems
start of execution of P2. Since the SRT algorithm is preemptive, the remaining time for completion of process
P2 is checked with the remaining time for completion of process P4. The remaining time for completion of
P2 is 3 ms which is greater than that of the remaining time for completion of the newly entered process P4
(2 ms). Hence P2 is preempted and P4 is scheduled for execution. P4 continues its execution to finish since
there is no new process entered in the ‘Ready’ queue during its execution. After 2 ms of scheduling P4
terminates and now the scheduler again sorts the ‘Ready’ queue based on the remaining time for completion
of the processes present in the ‘Ready’ queue. Since the remaining time for P2 (3 ms), which is preempted
by P4 is less than that of the remaining time for other processes in the ‘Ready’ queue, P2 is scheduled for
execution. Due to the arrival of the process P4 with execution time 2 ms, the ‘Ready’ queue is re-sorted in
the order P2, P4, P2, P3, P1. At the beginning it was P2, P3, P1. The execution sequence now changes as per
the following diagram
P2 P4 P2 P3 P1
0 2 4 7 14 24
2 2 3 7 10
This reveals that the Average waiting Time and Turn Around Time (TAT) improves significantly with
preemptive SJF scheduling.
[Link] Round Robin (RR) Scheduling
The term Round Robin is very popular among the sports and games activities. You might have heard about
‘Round Robin’ league or ‘Knock out’ league associated with any football or cricket tournament. In the ‘Round
Robin’ league each team in a group gets an equal chance to play against the rest of the teams in the same
group whereas in the ‘Knock out’ league the losing team in a match moves out of the tournament ☺.
In the process scheduling context also, ‘Round Robin’ brings the same message “Equal chance to all”.
In Round Robin scheduling, each process in the ‘Ready’ queue is executed for a pre-defined time slot. The
execution starts with picking up the first process in the ‘Ready’ queue (see Fig. 10.13). It is executed for a
pre-defined time and when the pre-defined time elapses or the process completes (before the pre-defined time
slice), the next process in the ‘Ready’ queue is selected for execution. This is repeated for all the processes
in the ‘Ready’ queue. Once each process in the ‘Ready’ queue is executed for the pre-defined time period,
the scheduler comes back and picks the first process in the ‘Ready’ queue again for execution. The sequence
is repeated. This reveals that the Round Robin scheduling is similar to the FCFS scheduling and the only
difference is that a time slice based preemption is added to switch the execution between the processes in
the ‘Ready’ queue. The ‘Ready’ queue can be considered as a circular queue in which the scheduler picks
up the first process for execution and moves to the next till the end of the queue and then comes back to the
beginning of the queue to pick up the first process.
Process 1
Process 4 Process 2
Execution Switch
Execution Switch
Process 3
The time slice is provided by the timer tick feature of the time management unit of the OS kernel (Refer the
Time management section under the subtopic ‘The Real-Time kernel’ for more details on Timer tick). Time
slice is kernel dependent and it varies in the order of a few microseconds to milliseconds. Certain OS kernels
may allow the time slice as user configurable. Round Robin scheduling ensures that every process gets a fixed
amount of CPU time for execution. When the process gets its fixed time for execution is determined by the
422 Introduc on to Embedded Systems
FCFS policy (That is, a process entering the Ready queue first gets its fixed execution time first and so on…).
If a process terminates before the elapse of the time slice, the process releases the CPU voluntarily and the
next process in the queue is scheduled for execution by the scheduler. The implementation of RR scheduling
is kernel dependent. The following code snippet illustrates the RR scheduling implementation for RTX51
Tiny OS, an 8bit OS for 8051 microcontroller from Keil Software ([Link]), an ARM® Company.
#include <rtx51tny.h> /* Definitions for RTX51 Tiny */
int counter0;
int counter1;
job0 () _task_ 0 {
os_create_task (1); /* Mark task 1 as “ready” */
The os_send_signal (Task ID) kernel call sends a signal to task Task ID. If the specified task is already
waiting for a signal, this function call readies the task for execution but does not start it. The os_wait1
(event) kernel call halts the current task and waits for an event to occur. The event argument specifies the
event to wait for and may have only the value K_SIG which waits for a signal. RTX51 uses the Timer 0 of
8051 for time slice generation. The time slice can be configured by the user by changing the time slice related
parameters in the RTX51 Tiny OS configuration file CONF_TNY.A51 file which is located in the \Keil_v5\
C51\RtxTiny2\SourceCode\ folder. Configuration options in CONF_TNY.A51 allow users to:
∑ Specify the Timer Tick Interrupt Register Bank.
∑ Specify the Timer Tick Interval (in 8051 machine cycles).
∑ Specify user code to execute in the Timer Tick Interrupt.
∑ Specify the Round-Robin Timeout.
∑ Enable or disable Round-Robin Task Switching.
∑ Specify that your application includes long duration interrupts.
∑ Specify whether or not code banking is used.
∑ Define the top of the RTX51 Tiny stack.
∑ Specify the minimum stack space required.
∑ Specify code to execute in the event of a stack error.
∑ Define idle task operations.
The RTX51 kernel provides a set of task management functions for managing the tasks. At any point of
time each RTX51 task is exactly in any one of the following state.
Task State State Description
RUNNING The task that is currently running is in the RUNNING State. Only one task at a time may be
in this state. The os_running_task_id kernel call returns the task number (ID) of the currently
executing task.
READY Tasks which are ready to run are in the READY State. Once the Running task has completed
processing, RTX51 Tiny selects and starts the next Ready task. A task may be made ready im-
mediately (even if the task is waiting for a timeout or signal) by setting its ready flag using the
os_set_ready or isr_set_ready kernel functions.
WAITING Tasks which are waiting for an event are in the WAITING State. Once the event occurs, the task
is switched to the READY State. The os_wait function is used for placing a task in the WAITING
State.
DELETED Tasks which have not been started or tasks which have been deleted are in the DELETED State.
The os_delete_task routine places a task that has been started (with os_create_task) into the
DELETED State.
TIME-OUT Tasks which were interrupted by a Round-Robin Time-Out are in the TIME-OUT State. This state
is equivalent to the READY State for Round-Robin programs.
Refer the documentation available with RTX51 Tiny OS for more information on the various RTX51 task
management kernel functions and their usage.
RR scheduling with interrupts is a good choice for the design of comparatively less complex Real-Time
Embedded Systems. In this approach, the tasks which require less Real-Time attention can be scheduled with
Round Robin scheduling and the tasks which require Real-Time attention can be scheduled through Interrupt
Service Routines. RTX51 Tiny supports Interrupts with RR scheduling. For RTX51 the time slice for RR
scheduling is provided by the Timer interrupt and if the interrupt is of high priority than that of the timer
interrupt and if its service time (ISR) is longer than the timer tick interval, the RTX51 timer interrupt may
426 Introduc on to Embedded Systems
be interrupted by the ISR and it may be reentered by a subsequent RX51 Tiny timer interrupt. Hence proper
care must be taken to limit the ISR time within the timer tick interval or to protect the timer tick interrupt
code from reentrancy. Otherwise unexpected results may occur. The limitations of RR with interrupt generic
approach are the limited number of interrupts supported by embedded processors and the interrupt latency
happening due to the context switching overhead.
RR can also be used as technique for resolving the priority in scheduling among the tasks with same
level of priority. We will discuss about how RR scheduling can be used for resolving the priority among
equal tasks under the VxWorks kernel in a later chapter.
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 6, 4, 2 milliseconds respectively,
enters the ready queue together in the order P1, P2, P3. Calculate the waiting time and Turn Around Time
(TAT) for each process and the Average waiting time and Turn Around Time (Assuming there is no I/O
waiting for the processes) in RR algorithm with Time slice = 2 ms.
The scheduler sorts the ‘Ready’ queue based on the FCFS policy and picks up the first process P1 from the
‘Ready’ queue and executes it for the time slice 2 ms. When the time slice is expired, P1 is preempted and P2
is scheduled for execution. The Time slice expires after 2ms of execution of P2. Now P2 is preempted and P3
is picked up for execution. P3 completes its execution within the time slice and the scheduler picks P1 again
for execution for the next time slice. This procedure is repeated till all the processes are serviced. The order
in which the processes are scheduled for execution is represented as
P1 P2 P3 P1 P2 P1
0 2 4 6 8 10 12
2 2 2 2 2 2
Average Turn Around Time = (Turn Around Time for all the processes) / No. of Processes
= (Turn Around Time for (P1 + P2 + P3))/3
= (12 + 10 + 6)/3 = 28/3
= 9.33 milliseconds
Average Turn Around Time (TAT) is the sum of average waiting time and average execution time.
Average Execution time = (Execution time for all the process)/No. of processes
= (Execution time for (P1 + P2 + P3))/3
= (6 + 4 + 2)/3 = 12/3 = 4
Average Turn Around Time = Average Waiting time + Average Execution time
= 5.33 + 4
= 9.33 milliseconds
RR scheduling involves lot of overhead in maintaining the time slice information for every process which
is currently being executed.
[Link] Priority Based Scheduling
Priority based preemptive scheduling algorithm is same as that of the non-preemptive priority based
scheduling except for the switching of execution between tasks. In preemptive scheduling, any high priority
process entering the ‘Ready’ queue is immediately scheduled for execution whereas in the non-preemptive
scheduling any high priority process entering the ‘Ready’ queue is scheduled only after the currently executing
process completes its execution or only when it voluntarily relinquishes the CPU. The priority of a task/
process in preemptive scheduling is indicated in the same way as that of the mechanism adopted for non-
preemptive multitasking. Refer the non-preemptive priority based scheduling discussed in an earlier section
of this chapter for more details.
Example 1
Three processes with process IDs P1, P2, P3 with estimated completion time 10, 5, 7 milliseconds and
priorities 1, 3, 2 (0—highest priority, 3—lowest priority) respectively enters the ready queue together. A new
process P4 with estimated completion time 6 ms and priority 0 enters the ‘Ready’ queue after 5 ms of start of
execution of P1. Assume all the processes contain only CPU operation and no I/O operations are involved.
At the beginning, there are only three processes (P1, P2 and P3) available in the ‘Ready’ queue and the
scheduler picks up the process with the highest priority (In this example P1 with priority 1) for scheduling.
Now process P4 with estimated execution completion time 6 ms and priority 0 enters the ‘Ready’ queue
after 5 ms of start of execution of P1. Since the scheduling algorithm is preemptive, P1 is preempted by P4
and P4 runs to completion. After 6 ms of scheduling, P4 terminates and now the scheduler again sorts the
‘Ready’ queue for process with highest priority. Since the priority for P1 (priority 1), which is preempted
by P4 is higher than that of P3 (priority 2) and P2 ((priority 3), P1 is again picked up for execution by the
scheduler. Due to the arrival of the process P4 with priority 0, the ‘Ready’ queue is resorted in the order P1,
P4, P1, P3, P2. At the beginning it was P1, P3, P2. The execution sequence now changes as per the following
diagram
P1 P4 P1 P3 P2
0 5 11 16 23 28
5 6 5 7 5
428 Introduc on to Embedded Systems