Uploaded by Harsh Karki

Lecture notes UNIT 2 (1)

advertisement
Introduction to C
C is a programming language developed at AT & T’s Bell Laboratories of USA in 1972. It was designed
and written by a man named Dennis Ritchie. In the late seventies C began to replace the more familiar
languages of that time like PL/I, ALGOL, etc
ANSI C standard emerged in the early 1980s, this book was split into two titles: The original was still
called Programming in C, and the title that covered ANSI C was called Programming in ANSI C. This was
done because it took several years for the compiler vendors to release their ANSI C compilers and for them
to become ubiquitous. It was initially designed for programming UNIX operating system. Now the
software tool as well as the C compiler is written in C. Major parts of popular operating systems like
Windows, UNIX, Linux is still written in C. This is because even today when it comes to performance
(speed of execution) nothing beats C. Moreover, if one is to extend the operating system to work with new
devices one needs to write device driver programs. These programs are exclusively written in C. C seems
so popular is because it is reliable, simple and easy to use. often heard today is – “C has been already
superceded by languages like C++, C# and Java.
Program
There is a close analogy between learning English language and learning C language. The classical method
of learning English is to first learn the alphabets used in the language, then learn to combine these
alphabets to form words, which in turn are combined to form sentences and sentences are combined to
form paragraphs. Learning C is similar and easier. Instead of straight-away learning how to write
programs, we must first know what alphabets, numbers and special symbols are used in C, then how using
them constants, variables and keywords are constructed, and finally how are these combined to form an
instruction. A group of instructions would be combined later on to form a program.
So a computer program is just a collection of the instructions necessary to solve a specific problem. The
basic operations of a computer system form what is known as the computer’s instruction set. And the
approach or method that is used to solve the problem is known as an algorithm. So for as programming
language concern these are of two types.
1) Low level language
2) High level language
Low level languages are machine level and assembly level language. In machine level language computer
only understand digital numbers i.e. in the form of 0 and 1. So, instruction given to the computer is in the
form binary digit, which is difficult to implement instruction in binary code. This type of program is not
portable, difficult to maintain and also error prone. The assembly language is on other hand modified
version of machine level language. Where instructions are given in English like word as ADD, SUM,
MOV etc. It is easy to write and understand but not understand by the machine. So the translator used here
is assembler to translate into machine level. Although language is bit easier, programmer has to know low
level details related to low level language. In the assembly level language the data are stored in the
computer register, which varies for different computer. Hence it is not portable.
High level language: These languages are machine independent, means it is portable. The language in this
category is Pascal, Cobol, Fortran etc. High level languages are understood by the machine. So it need to
translate by the translator into machine level. A translator is software which is used to translate high level
language as well as low level language in to machine level language.
Three types of translator are there:
Compiler
Interpreter
Assembler
Integrated Development Environments (IDE)
The process of editing, compiling, running, and debugging programs is often managed by a single
integrated application known as an Integrated Development Environment, or IDE for short. An IDE is a
windows-based program that allows us to easily manage large software programs, edit files in windows,
and compile, link, run, and debug programs. On Mac OS X, CodeWarrior and Xcode are two IDEs that are
used by many programmers. Under Windows, Microsoft Visual Studio is a good example of a popular
IDE. Kylix is a popular IDE for developing applications under Linux. Most IDEs also support program
development in several different programming languages in addition to C, such as C# and C++.
Structure of C Language program
1 ) Comment line
2) Preprocessor directive
3 ) Global variable declaration
4) main function( )
{
Local variables;
Statements;
}
User defined function
}
}
Comment line
It indicates the purpose of the program.
It is represented as /*……………………………..*/
Comment line is used for increasing the readability of the program. It is useful in explaining the program
and generally used for documentation. It is enclosed within the decimeters. Comment line can be single or
multiple line but should not be nested. It can be anywhere in the program except inside string constant &
character constant.
Preprocessor Directive:
#include tells the compiler to include information about the standard input/output library. It is also used in
symbolic constant such as #define PI 3.14(value). The stdio.h (standard input output header file) contains
definition &declaration of system defined function such as printf( ), scanf( ), pow( ) etc. Generally printf()
function used to display and scanf() function used to read value
Global Declaration:
This is the section where variable are declared globally so that it can be access by all the functions used in
the program. And it is generally declared outside the function :
main()
It is the user defined function and every function has one main() function from where actually program is
started and it is encloses within the pair of curly braces.
The main( ) function can be anywhere in the program but in general practice it is placed in the first
position.
Syntax :
main()
{
……..
……..
……..
}
The main( ) function return value when it declared by data type as
int main( )
{
return 0
}
The main function does not return any value when void (means null/empty) as
void main(void ) or void main()
{
printf (“C language”);
}
Output: C language
The program execution start with opening braces and end with closing brace.
And in between the two braces declaration part as well as executable part is mentioned. And at the end of
each line, the semi-colon is given which indicates statement termination.
Character set
A character denotes any alphabet, digit or special symbol used to represent information. Valid alphabets,
numbers and special symbols allowed in C are
Identifiers
Identifiers are user defined word used to name of entities like variables, arrays, functions, structures etc.
Rules for naming identifiers are:
1) name should only consists of alphabets (both upper and lower case), digits and underscore (_) sign.
2) first characters should be alphabet or underscore
3) name should not be a keyword
4) since C is a case sensitive, the upper case and lower case considered differently, for example code,
Code, CODE etc. are different identifiers.
5) identifiers are generally given in some meaningful name such as value, net_salary, age, data etc.
Variables
Variable is a data name which is used to store some data value or symbolic names for storing program
computations and results. The value of the variable can be change during the execution. The rule for
naming the variables is same as the naming identifier. Before used in the program it must be declared.
Declaration of variables specify its name, data types and range of the value that variables can store
depends upon its data types.
Syntax:
int a;
char c;
float f;
Variable initialization
When we assign any initial value to variable during the declaration, is called initialization of variables.
When variable is declared but contain undefined value then it is called garbage value. The variable is
initialized with the assignment operator such as
Data type variable name=constant;
Example:
int a=20;
Or
int a;
a=20;
statements
Constant
Constant is a any value that cannot be changed during program execution. In C, any number, single
character, or character string is known as a constant. A constant is an entity that doesn’t change whereas a
variable is an entity that may change. For example, the number 50 represents a constant integer value. The
character string "Programming in C is fun.\n" is an example of a constant character string. C constants can
be divided into two major categories:
Primary Constants
Secondary Constants
These constants are further categorized as
Numeric constant
Character constant
String constant
Numeric constant: Numeric constant consists of digits. It required minimum size of 2 bytes and max 4
bytes. It may be positive or negative but by default sign is always positive. No comma or space is allowed
within the numeric constant and it must have at least 1 digit. The allowable range for integer constants is 32768 to 32767. Truly speaking the range of an Integer constant depends upon the compiler. For a 16-bit
compiler like Turbo C or Turbo C++ the range is –32768 to 32767. For a 32-bit compiler the range would
be even greater. Mean by a 16-bit or a 32- bit compiler, what range of an Integer constant has to do with
the type of compiler.
It is categorized a integer constant and real constant. An integer constants are whole number which have
no decimal point. Types of integer constants are:
Decimal constant: 0-------9(base 10)
Octal constant: 0-------7(base 8)
Hexa decimal constant: 0----9, A------F(base 16)
In decimal constant first digit should not be zero unlike octal constant first digit must be zero(as 076, 0127)
and in hexadecimal constant first two digit should be 0x/ 0X (such as 0x24, 0x87A). By default type of
integer constant is integer but if the value of integer constant is exceeds range then value represented by
integer type is taken to be unsigned integer or long integer. It can also be explicitly mention integer and
unsigned integer type by suffix l/L and u/U.
Real constant is also called floating point constant. To construct real constant we must follow the rule of ,
-real constant must have at least one digit.
-It must have a decimal point.
-It could be either positive or negative.
-Default sign is positive.
-No commas or blanks are allowed within a real constant.
Ex.:
+325.34
426.0
-32.76
To express small/large real constant exponent(scientific) form is used where number is written in mantissa
and exponent form separated by e/E. Exponent can be positive or negative integer but mantissa can be
real/integer type, for example 3.6*105=3.6e+5. By default type of floating point constant is double, it can
also be explicitly defined it by suffix of f/F.
Author- Embedtornix
Source- Youtube
Help for current page
Modular programming is the process of subdividing a computer program into separate sub-programs. A
module is a separate software component. It can often be used in a variety of applications and functions
with other components of the system.



Some programs might have thousands or millions of lines and to manage such programs it becomes quite
difficult as there might be too many of syntax errors or logical errors present in the program, so to manage
such type of programs concept of modular programming approached.
Each sub-module contains something necessary to execute only one aspect of the desired functionality.
Modular programming emphasis on breaking of large programs into small problems to increase the
maintainability, readability of the code and to make the program handy to make any changes in future or to
correct the errors.
Points which should be taken care of prior to modular program development:
1. Limitations of each and every module should be decided.
2. In which way a program is to be partitioned into different modules.
3. Communication among different modules of the code for proper execution of the entire program.
Advantages of Using Modular Programming Approach –
1. Ease of Use :This approach allows simplicity, as rather than focusing on the entire thousands and millions
of lines code in one go we can access it in the form of modules. This allows ease in debugging the code
and prone to less error.
2. Reusability :It allows the user to reuse the functionality with a different interface without typing the whole
program again.
3. Ease of Maintenance : It helps in less collision at the time of working on modules, helping a team to work
with proper collaboration while working on a large application.
Example of Modular Programming in C
C is called a structured programming language because to solve a large problem, C programming language
divides the problem into smaller modules called functions or procedures each of which handles a particular
responsibility. The program which solves the entire problem is a collection of such functions.
Module is basically a set of interrelated files that share their implementation details but hide it from the
outside world. How can we implement modular programming in c? Each function defined in C by default
is globally accessible. This can be done by including the header file in which implementation of the
function is defined.
Suppose, we want to declare a Stack data type and at the same time want to hide the implementation,
including its data structure, from users. We can do this by first defining a public file called stack.h which
contains generic data Stack data type and the functions which are supported by the stack data type.
In the header file we must include only the definitions of constants, structures, variables and functions
with the name of the module, that makes easy to identify source of definition in a larger program with
many modules.
Keywords extern and static help in the implementation of modularity in C.
stack.h:
extern stack_var1;
extern int stack_do_something(void);
Now we can create a file named stack.c that contains implementation of stack data type:
stack.c
#include
int stack_var1;
static int stack_var2;
int stack_do_something(void)
{
stack_var1 = 2;
stack_var2 = 5;
}
The main file which may includes module stack
#include
int main(int argc, char*argv[]){
while(1){
stack_do_something();
}
}
Datatypes
Each variable in C has an associated data type. Each data type requires different amounts of memory and
has some specific operations which can be performed over it. Let us briefly describe them one by one:
Following are the examples of some very common data types used in C:




char: The most basic data type in C. It stores a single character and requires a single byte of memory in
almost all compilers.
int: As the name suggests, an int variable is used to store an integer.
float: It is used to store decimal numbers (numbers with floating point value) with single precision.
double: It is used to store decimal numbers (numbers with floating point value) with double precision.
Control Structures are just a way to specify flow of control in programs. Any algorithm or program can
be more clear and understood if they use self-contained modules called as logic or control structures. It
basically analyzes and chooses in which direction a program flows based on certain parameters or
conditions. There are three basic types of logic, or flow of control, known as:
1. Sequence logic, or sequential flow
2. Selection logic, or conditional flow
3. Iteration logic, or repetitive flow
Let us see them in detail:
1. Sequential Logic (Sequential Flow)
Sequential logic as the name suggests follows a serial or sequential flow in which the flow depends on the
series of instructions given to the computer. Unless new instructions are given, the modules are executed in
the obvious sequence. The sequences may be given, by means of numbered steps explicitly. Also,
implicitly follows the order in which modules are written. Most of the processing, even some complex
problems, will generally follow this elementary flow pattern.
1.
Selection Logic (Conditional Flow)
Selection Logic simply involves a number of conditions or parameters which decides one out of several
written modules. The structures which use these type of logic are known as Conditional Structures.
These structures can be of three types:




Single AlternativeThis structure has the form:
If (condition) then:
[Module A]
[End of If structure]
Implementation:






Double AlternativeThis structure has the form:
If (Condition), then:
[Module A]
Else:
[Module B]
[End if structure]
Implementation:










Multiple AlternativesThis structure has the form:
If (condition A), then:
[Module A]
Else if (condition B), then:
[Module B]
..
..
Else if (condition N), then:
[Module N]
[End If structure]
Implementation:
1. In this way, the flow of the program depends on the set of conditions that are written. This can be more
understood by the following flow charts:
2.
Double Alternative Control Flow
Iteration Logic (Repetitive Flow)
1. The Iteration logic employs a loop which involves a repeat statement followed by a module known as the
body of a loop.
 The two types of these structures are:Repeat-For Structure
 This structure has the form:
 Repeat for i = A to N by I:
 [Module]
 [End of loop]
 Here, A is the initial value, N is the end value and I is the increment. The loop ends when A>B. K
increases or decreases according to the positive and negative value of I respectively.

Repeat-For Flow
Implementation:





Repeat-While Structure
It also uses a condition to control the loop. This structure has the form:
Repeat while condition:
[Module]
[End of Loop]

Repeat While Flow
Although C does not provide direct support to error handling (or exception handling), there
are ways through which error handling can be done in C. A programmer has to prevent errors
at the first place and test return values from the functions.
A lot of C function calls return a -1 or NULL in case of an error, so quick test on these return
values are easily done with for instance an ‘if statement’. For example, In Socket
Programming, the returned value of the functions like socket(), listen() etc. are checked to see
if there is an error or not.
Example: Error handling in Socket Programming
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
Different methods of Error handling in C
Global Variable errno: When a function is called in C, a variable named as errno is
automatically assigned a code (value) which can be used to identify the type of error that has
been encountered. Its a global variable indicating the error occurred during any function call
and defined in the header file errno.h.
Different codes (values) for errno mean different types of errors. Below is a list of few
different errno values and its corresponding meaning:
errno value Error
1 /* Operation not permitted */
2 /* No such file or directory */
3 /* No such process */
4 /* Interrupted system call */
5 /* I/O error */
6 /* No such device or address */
7 /* Argument list too long */
8 /* Exec format error */
9 /* Bad file number */
10 /* No child processes */
11 /* Try again */
12 /* Out of memory */
13 /* Permission denied */
// C implementation to see how errno value is
// set in the case of any error in C #include <stdio.h> #include <errno.h> int main() {
// If a file is opened which does not exist,
// then it will be an error and corresponding
// errno value will be set
FILE * fp;
// opening a file which does
// not exist.
fp = fopen("GeeksForGeeks.txt", "r");
printf(" Value of errno: %d\n ", errno);
return 0; }
What are co-routines?
Co-routines are general control structures where flow control is cooperatively passed between two
different routines without returning.
If you have used Python or C#, you may know that there is a keyword called yield that allows loop back
and forth between the caller and the called function until the caller is not done with function or the function
terminates because of some logic it is given.
def rangeN(a, b):
i=a
while (i < b):
yield i
i += 1 # Next execution resumes
# from this point
for i in rangeN(1, 5):
print(i)
Output :
1
2
3
4
5
This code demonstrates how does the yield works and gives a brief idea about how the control is changed
between the caller and callee.
Why Coroutines are needed?
To read a file and parse it while reading into some meaningful data, you can either read step by step at
each line, which is fine. You may also load the entire content in memory, which won’t be recommended
for large text cases e.g text editors like Microsoft Word or in modern systems just use Pipes.
Interrupts:
An interrupt is a signal to the processor emitted by hardware or software indicating an event that needs
immediate attention. Whenever an interrupt occurs, the controller completes the execution of the current
instruction and starts the execution of an Interrupt Service Routine (ISR) or Interrupt Handler. ISR tells the
processor or controller what to do when the interrupt occurs. The interrupts can be either hardware
interrupts or software interrupts.
Hardware Interrupt
A hardware interrupt is an electronic alerting signal sent to the processor from an external device, like a
disk controller or an external peripheral. For example, when we press a key on the keyboard or move the
mouse, they trigger hardware interrupts which cause the processor to read the keystroke or mouse position.
Software Interrupt
A software interrupt is caused either by an exceptional condition or a special instruction in the instruction
set which causes an interrupt when it is executed by the processor. For example, if the processor's
arithmetic logic unit runs a command to divide a number by zero, to cause a divide-by-zero exception, thus
causing the computer to abandon the calculation or display an error message. Software interrupt
instructions work similar to subroutine calls.
Device handling:
When we say Input, it means to feed some data into a program. An input can be given in the form of a file
or from the command line. C programming provides a set of built-in functions to read the given input and
feed it to the program as per requirement.
When we say Output, it means to display some data on screen, printer, or in any file. C programming
provides a set of built-in functions to output the data on the computer screen as well as to save it in text or
binary files.
The int getchar(void) function reads the next available character from the screen and returns it as an
integer. This function reads only single character at a time. You can use this method in the loop in case you
want to read more than one character from the screen.
The int putchar(int c) function puts the passed character on the screen and returns the same character. This
function puts only single character at a time. You can use this method in the loop in case you want to
display more than one character on the screen.
Real Time System is a system that is put through real time which means response is obtained within a
specified timing constraint or system meets the specified deadline.Real time system is of two types – Hard
and Soft. Both are used in different cases. Hard real time systems are used where even the delay of some
nano or micro seconds are not allowed. Soft real time systems provide some relaxation in time expression.
Applications of Real-time System:
Real-time System has applications in various fields of the technology. Here we will discuss the important
applications of real-time system.
1. Industrial application:
Real-time system has a vast and prominent role in modern industries. Systems are made real time based so
that maximum and accurate output can be obtained. In order to such things real -time systems are used in
maximum industrial organizations. These system somehow lead to the better performance and high
productivity in less time. Some of the examples of industrial applications are: Automated Car Assembly
Plant, Chemical Plant etc.
2. Medical Science application:
In the field of medical science, real-time system has a huge impact on the human health and treatment. Due
to the introduction of real-time system in medical science, many lives are saved and treatment of complex
diseases has been turned down to easier ways. People specially related to medical, now feel more relaxed
due to these systems. Some of the examples of medical science applications are: Robot, MRI Scan,
Radiation therapy etc.
3. Peripheral Equipment applications:
Real-time system has made the printing of large banners and such things very easier. Once these systems
came into use, the technology world became more strong. Peripheral equipment are used for various
purposes. These systems are embedded with micro chips and perform accurately in order to get the desired
response. Some of the examples of peripheral equipment applications are: Laser printer, fax machine,
digital camera etc.
4. Telecommunication applications:
Real-time system map the world in such a way that it can be connected within a short time. Real-time
systems have enabled the whole world to connect via a medium across internet. These systems make the
people connect with each other in no time and feel the real environment of togetherness. Some examples of
telecommunication applications of real-time systems are: Video Conferencing, Cellular system etc.
5. Defense applications:
In the new era of atomic world, defense is able to produce the missiles which have the dangerous powers
and have the great destroying ability. All these systems are real-time system and it provides the system to
attack and also a system to defend. Some of the applications of defense using real time systems are:
Missile guidance system, anti-missile system, Satellite missile system etc.
6. Aerospace applications:
The most powerful use of real time system is in aerospace applications. Basically hard real time systems
are used in aerospace applications. here the delay of even some nano second is not allowed and if it
happens, system fails. Some of the applications of real-time systems in aerospace are: Satellite tracking
system, Avionics, Flight simulation etc.
Real Time Multi-Tasking OS
Multitasking and real-time, in the field of operating systems, are antonymous. Normally all generalpurpose operating systems, such as Windows and Mac OS, are multitasking and non real-time. Whereas in
the embedded system market operating systems exist that use any of the permutations of multitasking and
real-time. The computers used for real-time operating systems (RTOS), compared to most general-purpose
computers, need to more reliable and tolerant of changing environmental conditions and be able to cope
with varying computation loads.
In order to analyse the techniques used to achieve a multitasking RTOS it is first important to define the
key terms of this type of operating system (OS). Multitasking, as the name suggests, is a technique used to
handle the execution of multiple tasks. It can be defined as, “the execution of multiple software routines in
pseudo-parallel. Each routine represents a separate thread of execution” (Ganssle and Barr, 2003). The OS
simulates parallelism by dividing the CPU processing time to each individual thread. The second term to
define is real-time. A real-time system is a system based on stringent time constraints. For a system to be
real-time, there must be a guaranteed time frame where within a task must execute. In a multitasking
RTOS the task scheduling, switching and execution elements are key to the effectiveness of the OS in
achieving its real-time requirements.
Real-Time Operating Systems
A Real-Time OS (RTOS) is an OS with special features that make it suitable for building real time
computing applications also referred to as Real-Time Systems (RTS). An RTS is a (computing) system
where correctness of computing depends not only on the correctness of the logical result of the
computation, but also on the result delivery time. An RTS is expected to respond in a timely, predictable
way to unpredictable external stimuli.
Real-time systems can be categorized as Hard or Soft. For a Hard RTS, the system is taken to have failed if
a computing deadline is not met. In a Soft RTS, a limited extent of failure in meeting deadlines results in
degraded performance of the system and not catastrophic failure. The correctness and performance of an
RTS is therefore not measured in terms of parameters such as, average number of transactions per second
as in transactional systems such as databases.
A Good RTOS is one that enables bounded (predictable) behavior under all system load scenarios. Note
however, that the RTOS, by itself cannot guarantee system correctness, but only is an enabling technology.
That is, it provides the application programmer with facilities using which a correct application can be
built. Speed, although important for meeting the overall requirements, does not by itself meet the
requirements for an RTOS.
Multitasking
A multitasking environment allows applications to be constructed as a set of independent tasks, each with a
separate thread of execution and its own set of system resources. The inter-task communication facilities
allow these tasks to synchronize and coordinate their activity. Multitasking provides the fundamental
mechanism for an application to control and react to multiple, discrete real-world events and is therefore
essential for many real-time applications. Multitasking creates the appearance of many threads of
execution running concurrently when, in fact, the kernel interleaves their execution on the basis of a
scheduling algorithm. This also leads to efficient utilisation of the CPU time and is essential for many
embedded applications where processors are limited in computing speed due to cost, power, silicon area
and other constraints. In a multi-tasking operating system it is assumed that the various tasks are to
cooperate to serve the requirements of the overall system. Co-operation will require that the tasks
communicate with each other and share common data in an orderly an disciplined manner, without
creating undue contention and deadlocks. The way in which tasks communicate and share data is to be
regulated such that communication or shared data access error is prevented and data, which is private to a
task, is protected. Further, tasks may be dynamically created and terminated by other tasks, as and when
needed. To realise such a system, the following major functions are to be carried out.
A. Process Management
• interrupt handling
• task scheduling and dispatch
• create/delete, suspend/resume task
• manage scheduling information
– priority, scheduling policy, etc
B. Interprocess Communication and Synchronization
• Code, data and device sharing
• Synchronization, coordination and data exchange mechanisms
• Deadlock and Livelock detection
C. Memory Management
• dynamic memory allocation
• memory locking
• Services for file creation, deletion, reposition and protection
D. Input/Output Management
• Handles request and release functions and read, write functions for a variety of peripherals
The following are important requirements that an OS must meet to be considered an RTOS in the
contemporary sense.
A. The operating system must be multithreaded and preemptive. e.g. handle multiple threads and be able to
preempt tasks if necessary.
B. The OS must support priority of tasks and threads.
C. A system of priority inheritance must exist. Priority inheritance is a mechanism to ensure that lower
priority tasks cannot obstruct the execution of higher priority tasks.
D. The OS must support various types of thread/task synchronization mechanisms.
E. For predictable response :
a. The time for every system function call to execute should be predictable and independent of the number
of objects in the system.
b. Non preemptable portions of kernel functions necessary for interprocess synchronization and
communication are highly optimized, short and deterministic
c. Non-preemptable portions of the interrupt handler routines are kept small and deterministic
d. Interrupt handlers are scheduled and executed at appropriate priority
e. The maximum time during which interrupts are masked by the OS and by device drivers must be known.
f. The maximum time that device drivers use to process an interrupt, and specific IRQ information relating
to those device drivers, must be known.
g. The interrupt latency (the time from interrupt to task run) must be predictable and compatible with
application requirements
F. For fast response:
a. Run-time overhead is decreased by reducing the unnecessary context switch.
b. Important timings such as context switch time, interrupt latency, semaphore get/release latency must be
minimum
Scheduling Strategies, Priority Structures
Scheduling Algorithms
When switching between Tasks the RTOS has to choose the most appropriate task to load
next. There are several scheduling algorithms available, including Round Robin, Cooperative and Hybrid scheduling. However, to provide a responsive system most RTOS’s use
a pre-emptive scheduling algorithm.
In typical designs, a task has three states:
1. Running (executing on the CPU);
2. Ready (ready to be executed);
3. Blocked (waiting for an event, I/O for example).
Most tasks are blocked or ready most of the time because generally only one task can run at a
time per CPU. The number of items in the ready queue can vary greatly, depending on the
number of tasks the system needs to perform and the type of scheduler that the system uses.
On simpler non-preemptive but still multitasking systems, a task has to give up its time on the
CPU to other tasks, which can cause the ready queue to have a greater number of overall
tasks in the ready to be executed state .
Usually, the data structure of the ready list in the scheduler is designed to minimize the
worst-case length of time spent in the scheduler's critical section, during which preemption is
inhibited, and, in some cases, all interrupts are disabled, but the choice of data structure
depends also on the maximum number of tasks that can be on the ready list.
If there are never more than a few tasks on the ready list, then a doubly linked list of ready
tasks is likely optimal. If the ready list usually contains only a few tasks but occasionally
contains more, then the list should be sorted by priority. That way, finding the highest priority
task to run does not require iterating through the entire list. Inserting a task then requires
walking the ready list until reaching either the end of the list, or a task of lower priority than
that of the task being inserted.
Care must be taken not to inhibit preemption during this search. Longer critical sections
should be divided into small pieces. If an interrupt occurs that makes a high priority task
ready during the insertion of a low priority task, that high priority task can be inserted and run
immediately before the low priority task is inserted.
The critical response time, sometimes called the flyback time, is the time it takes to queue a
new ready task and restore the state of the highest priority task to running. In a well-designed
RTOS, readying a new task will take 3 to 20 instructions per ready-queue entry, and
restoration of the highest-priority ready task will take 5 to 30 instructions.
In more advanced systems, real-time tasks share computing resources with many non-realtime tasks, and the ready list can be arbitrarily long. In such systems, a scheduler ready list
implemented as a linked list would be inadequate.



















Round Robin Scheduling
Round Robin is the preemptive process scheduling algorithm.
Each process is provided a fix time to execute, it is called a quantum.
Once a process is executed for a given time period, it is preempted and other process executes for a
given time period.
Context switching is used to save states of preempted processes.
Priority Based Scheduling
Priority scheduling is a non-preemptive algorithm and one of the most common scheduling algorithms
in batch systems.
Each process is assigned a priority. Process with highest priority is to be executed first and so on.
Processes with same priority are executed on first come first served basis.
Priority can be decided based on memory requirements, time requirements or any other resource
requirement.
First Come First Serve (FCFS)
Jobs are executed on first come, first serve basis.
It is a non-preemptive, pre-emptive scheduling algorithm.
Easy to understand and implement.
Its implementation is based on FIFO queue.
Poor in performance as average wait time is high.
Shortest Job Next (SJN)
This is also known as shortest job first, or SJF
This is a non-preemptive, pre-emptive scheduling algorithm.
Best approach to minimize waiting time.
Easy to implement in Batch systems where required CPU time is known in advance.
Impossible to implement in interactive systems where required CPU time is not known.
The processer should know in advance how much time process will take.
Priority Structure:
To ensure that every event’s response is generated after tasks are executed within their
specified deadlines, the CPU and other core computation resources ought to be allocated to
different tasks according to their priority levels. A task or process priority may be static or
fixed and is calculated based on the computing time required, deadline or invocation
frequency.
Policies that allow dynamic task priority adjustment are considered more efficient because
priority is determined by how quickly a task responds to a specific event. The event could be
an activity within the task execution process or elapsing of the set amount of time. Tasks
belonging to one category are scheduled and dispatched with similar mechanisms. Tasks are
categorized into three main priority levels:
Interrupt level
Tasks under the interrupt level require a fast and frequent response that is measured in
milliseconds. No scheduler is required because immediate task execution follows an
interrupt. To meet task deadlines in a system, the context processing and switching time
requirements should be at the bare minimum level and highly predictable to ensure that the
entire system behavior remains predictable.
To maintain these high standards, the Interrupt Service Routine (ISR) operates in a special
fixed and common context like the common stacks. The ISRs are divided into two parts. One
part executes tasks immediately and spawns new tasks for the remaining processing time
before returning to the kernel. New tasks get executed as per the set scheduling policy and in
the course of time. Task priorities at the interrupt level are mainly ensured through the
processor’s software and hardware interrupt policy management system. Some interrupt
controllers mask low priority interrupts in the presence of high priority interrupts. The
frequency of task executions depends greatly on the period assigned to high priority clocklevel tasks.
Hard real-time level
This level handles periodic tasks such as control and sampling tasks and tasks requiring
accurate timing. As such, task scheduling at this level is implemented based on a real-time
system clock. The real-time system clock device is comprised of a timer queue, an interrupt
handler, and counter. The counter indicates the current time while the timer queue indicates
pending timers associated with the clock device.
A system clock interrupt under this level is referred to as a tick, and it represents the least
time interval recognized by the system. Since most RTOS programs utilize time, virtual
software delays and clocks can be formulated by tasks and associated with system clock
devices. A system clock device raises interrupts periodically while the kernel updates the
software clock as per the current timing. After a few clock cycles, a new task is dispatched as
per the adopted scheduling policy requirement.
Soft/Non-Real Time Level
Soft or non-real time level tasks are either allowed a wide margin of error in regards to timing
or have no deadlines. Therefore, they are deemed low priority tasks and are executed once all
the high priority tasks are completed. Tasks at this level may run at a single priority level or
may be allocated priorities. Soft or non-real time level tasks are initiated on demand as
opposed to a predetermined time interval. The base level scheduler is the lowest priority
clock level task. As such, the priorities of all base-level tasks must be lower than tasks at
clock level.
All three priority levels can handle multiple task priorities. For an RTOS to be deemed RTPOSIX standard compliant, it must support at least 32 priority levels. For example,
commercial RTOS supports priority levels that are as low as 8 for Windows CE and as high
as 256 for VxWorks. Round-robin policy and FIFO policies are applied when scheduling or
equal priority threads.
Scheduler and Real Time Clock Interrupt Handler
Schedulers
As we know, the illusion that all the tasks are running concurrently is achieved by allowing each to have a
share of the processor time. This is the core functionality of a kernel. The way that time is allocated
between tasks is termed “scheduling”. The scheduler is the software that determines which task should be
run next. The logic of the scheduler and the mechanism that determines when it should be run is the
scheduling algorithm. We will look at a number of scheduling algorithms in this section. Task scheduling
is actually a vast subject, with many whole books devoted to it. The intention here is to just give sufficient
introduction that you can understand what a given RTOS has to offer in this respect.
Run to Completion (RTC) Scheduler
RTC scheduling is very simplistic and uses minimal resources. It is, therefore, an ideal choice, if the
application’s needs are fulfilled. Here is the timeline for a system using RTC scheduling:
The scheduler simply calls the top level function of each task in turn. That task has control of the CPU
(interrupts aside) until the top level function executes a return statement. If the RTOS supports task
suspension, then any tasks that are currently suspended are not run. This is a topic discussed below;
see Task Suspend .
The big advantages of an RTC scheduler, aside from its simplicity, are the need for just a single stack and
the portability of the code (as no assembly language is generally required). The downside is that a task can
“hog” the CPU, so careful program design is required. Although each task is started “from the top” each
time it is scheduled – unlike other kinds of schedulers which allow the code to continue from where it left
off – greater flexibility may be programmed by use of static “state” variables, which determine the logic of
each sequential call.
Round Robin (RR) Scheduler
An RR scheduler is similar to RTC, but more flexible and, hence, more complex. In the same way, each
task is run in turn (allowing for task suspension), thus:
However, with the RR scheduler, the task does not need to execute a return in the top level function. It
can relinquish the CPU at any time by making a call to the RTOS. This call results in the kernel saving the
context (all the registers – including stack pointer and program counter) and loading the context of the next
task to be run. With some RTOSes, the processor may be relinquished – and the task suspended – pending
the availability of a kernel resource. This is more sophisticated, but the principle is the same.
The greater flexibility of the RR scheduler comes from the ability for the tasks to continue from where
they left off without any accommodation in the application code. The price for this flexibility is more
complex, less portable code and the need for a separate stack for each task.
Time Slice (TS) Scheduler
A TS scheduler is the next step in complexity from RR. Time is divided into “slots”, with each task being
allowed to execute for the duration of its slot, thus:
In addition to being able to relinquish the CPU voluntarily, a task is preempted by a scheduler call made
from a clock tick interrupt service routine. The idea of simply allocating each task a fixed time slice is very
appealing – for applications where it fits the requirements – as it is easy to understand and very
predictable.
The only downside of simple TS scheduling is the proportion of CPU time allocated to each task varies,
depending upon whether other tasks are suspended or relinquish part of their slots, thus:
A more predictable TS scheduler can be constructed if the concept of a “background” task is introduced.
The idea, shown here, is for the background task to be run instead of any suspended tasks and to be
allocated the remaining slot time when a task relinquishes (or suspends itself).
Obviously the background task should not do any time-critical work, as the amount of CPU time it is
allocated is totally unpredictable – it may never be scheduled at all.
This design means that each task can predict when it will be scheduled again. For example, if you have
10ms slots and 10 tasks, a task knows that, if it relinquishes, it will continue executing after 100ms. This
can lead to elegant timing loops in application tasks.
An RTOS may offer the possibility for different time slots for each task. This offers greater flexibility, but
is just as predictable as with fixed slot size. Another possibility is to allocate more than one slot to the
same task, if you want to increase its proportion of allocated processor time.
Priority Scheduler
Most RTOSes support Priority scheduling. The idea is simple: each task is allocated a priority and, at any
particular time, whichever task has the highest priority and is “ready” is allocated the CPU, thus:
The scheduler is run when any “event” occurs (e.g. interrupt or certain kernel service calls) that may cause
a higher priority task being made “ready”. There are broadly three circumstances that might result in the
scheduler being run:



The task suspends itself; clearly the scheduler is required to determine which task to run next.
The task readies another task (by means of an API call) of higher priority.
An interrupt service routine (ISR) readies another task of higher priority. This could be an input/output
device ISR or it may be the result of the expiration of a timer (which are supported my many RTOSes – we
will look at them in detail in a future article).
The number of levels of priority varies (from 8 to many hundreds) and the significance of higher and lower
values differs; some RTOSes use priority 0 as highest, others as lowest.
Some RTOSes only allow a single task at each priority level; others permit multiple tasks at each level,
which complicates the associated data structures considerably. Many OSes allow task priorities to be
changed at runtime, which adds further complexity.
Composite Scheduler
We have looked at RTC, RR, TS and Priority schedulers, but many commercial RTOS products offer more
sophisticated schedulers, which have characteristics of more than one of these algorithms. For example, an
RTOS may support multiple tasks at each priority level and then use time slicing to divide time between
multiple ready tasks at the highest level.
Interrupt handler
Interrupt handling is a key function in real-time software, and comprises interrupts and their handlers. Only
those physical interrupts which of high enough priority can be centered into system interrupt table. The
software assigns each interrupt to a handler in the interrupt table. An interrupt handler is just a routine
containing a sequence of operations. Each of these may request input and output while running.
The routine for handling a specific interrupt is known as the interrupt service routine for the specific
interrupt. The RTOS layer often stores a list of the pairs of interrupts and their handlers known as the
interrupt table. All interrupt handlers run constant within the background process. Most RTOS kernels
issue tasks which can be triggered and terminated at run-time by the context switch paradigm. In objectoriented programming languages (such as C++ and Java), tasks can be run as one or more threads. Thus, an
interrupt can be handled either as a thread or as a sub-process within a task or process.
Task Co-operation and Communication,
Mutual Exclusion
Visible to students
Although tasks in an embedded application have a degree of independence, it does not mean that they have
no “awareness” of one another. Although some tasks will be truly isolated from others, the requirement for
communication and synchronization between tasks is very common. This represents a key part of the
functionality provided by an RTOS. The actual range of options offered by a different RTOSes may vary
quite widely – as will some of the terminology – so the best we can do in this article is review the
commonly available facilities.
A Range of Options
There are three broad paradigms for inter-task communications and synchronization:



Task-owned facilities – attributes that an RTOS imparts to tasks that provide communication (input)
facilities. The example we will look at some more is signals.
Kernel objects – facilities provided by the RTOS which represent stand-alone communication or
synchronization facilities. Examples include: event flags, mailboxes, queues/pipes, semaphores and
mutexes.
Message passing – a rationalized scheme where an RTOS allows the creation of message objects, which
may be sent from one to task to another or to several others. This is fundamental to the kernel design and
leads to the description of such a product as being a “message passing RTOS”.
The facilities that are ideal for each application will vary. There is also some overlap in their capabilities
and some thought about scalability is worthwhile. For example, if an application needs several queues, but
just a single mailbox, it may be more efficient to realize the mailbox with a single-entry queue. This object
will be slightly non-optimal, but all the mailbox handling code will not be included in the application and,
hence, scalability will reduce the RTOS memory footprint.
Shared Variables or Memory Areas
A simplistic approach to inter-task communication is to just have variables or memory areas which are
accessible to all the tasks concerned. Whilst it is very primitive, this approach may be applicable to some
applications. There is a need to control access. If the variable is simply a byte, then a write or a read to it
will probably be an “atomic” (i.e. uninterruptible) operation, but care is needed if the processor allows
other operations on bytes of memory, as they may be interruptible and a timing problem could result. One
way to effect a lock/unlock is simply to disable interrupts for a short time.
If you are using a memory area, of course you still need locking. Using the first byte as a locking flag is a
possibility, assuming that the memory architecture facilitates atomic access to this byte. One task loads
data into the memory area, sets the flag and then waits for it to clear. The other task waits for the flag to be
set, reads the data and clears the flag. Using interrupt disable as a lock is less wise, as moving the whole
buffer of data may take time.
This type of shared memory usage is similar in style to the way many inter-processor communication
facilities are implemented in multicore systems. In some cases, a hardware lock and/or an interrupt are
incorporated into the inter-processor shared memory interface.
Signals
Signals are probably the simplest inter-task communication facility offered in conventional RTOSes. They
consist of a set of bit flags – there may be 8, 16 or 32, depending on the specific implementation – which is
associated with a specific task.
A signal flag (or several flags) may be set by any task using an OR type of operation. Only the task that
owns the signals can read them. The reading process is generally destructive – i.e. the flags are also
cleared.
In some systems, signals are implemented in a more sophisticated way such that a special function –
nominated by the signal owning task – is automatically executed when any signal flags are set. This
removes the necessity for the task to monitor the flags itself. This is somewhat analogous to an interrupt
service routine.
Event Flag Groups
Event flag groups are like signals in that they are a bit-oriented inter-task communication facility. They
may similarly be implemented in groups of 8, 16 or 32 bits. They differ from signals in being independent
kernel objects; they do not “belong” to any specific task.
Any task may set and clear event flags using OR and AND operations. Likewise, any task may interrogate
event flags using the same kind of operation. In many RTOSes, it is possible to make a blocking API call
on an event flag combination; this means that a task may be suspended until a specific combination of
event flags has been set. There may also be a “consume” option available, when interrogating event flags,
such that all read flags are cleared.
Semaphores
Semaphores are independent kernel objects, which provide a flagging mechanism that is generally used to
control access to a resource. There are broadly two types: binary semaphores (that just have two states) and
counting semaphores (that have an arbitrary number of states). Some processors support (atomic)
instructions that facilitate the easy implementation of binary semaphores. Binary semaphores may also be
viewed as counting semaphores with a count limit of 1.
Any task may attempt to obtain a semaphore in order to gain access to a resource. If the current semaphore
value is greater than 0, the obtain will be successful, which decrements the semaphore value. In many
OSes, it is possible to make a blocking call to obtain a semaphore; this means that a task may be suspended
until the semaphore is released by another task. Any task may release a semaphore, which increments its
value.
Mailboxes
Mailboxes are independent kernel objects, which provide a means for tasks to transfer messages. The
message size depends on the implementation, but will normally be fixed. One to four pointer-sized items
are typical message sizes. Commonly, a pointer to some more complex data is sent via a mailbox. Some
kernels implement mailboxes so that the data is just stored in a regular variable and the kernel manages
access to it. Mailboxes may also be called “exchanges”, though this name is now uncommon.
Any task may send to a mailbox, which is then full. If a task then tries to send to send to a full mailbox, it
will receive an error response. In many RTOSes, it is possible to make a blocking call to send to a
mailbox; this means that a task may be suspended until the mailbox is read by another task. Any task may
read from a mailbox, which renders it empty again. If a task tries read from an empty mailbox, it will
receive an error response. In many RTOSes, it is possible to make a blocking call to read from a mailbox;
this means that a task may be suspended until the mailbox is filled by another task.
Some RTOSes support a “broadcast” feature. This enables a message to be sent to all the tasks that are
currently suspended on reading a specific mailbox.
Certain RTOSes do not support mailboxes at all. The recommendation is to use a single-entry queue (see
below) instead. This is functionally equivalent, but carries additional memory and runtime overhead.
Queues
Queues are independent kernel objects, that provide a means for tasks to transfer messages. They are a
little more flexible and complex than mailboxes. The message size depends on the implementation, but
will normally be a fixed size and word/pointer oriented.
Any task may send to a queue and this may occur repeatedly until the queue is full, after which time any
attempts to send will result in an error. The depth of the queue is generally user specified when it is created
or the system is configured. In many RTOSes, it is possible to make a blocking call to send to a queue; this
means that, if the queue is full, a task may be suspended until the queue is read by another task. Any task
may read from a queue. Messages are read in the same order as they were sent – first in, first out (FIFO). If
a task tries to read from an empty queue, it will receive an error response. In many RTOSes, it is possible
to make a blocking call to read from a queue; this means that, if the queue is empty, a task may be
suspended until a message is sent to the queue by another task.
An RTOS will probably support the facility to send a message to the front of the queue – this is also
termed “jamming”. Some RTOSes also support a “broadcast” feature. This enables a message to be sent to
all the tasks that are suspended on reading a queue. Additionally, an RTOS may support the sending and
reading of messages of variable length; this gives greater flexibility, but carries some extra overhead.
Many RTOSes support another kernel object type called “pipes”. A pipe is essentially identical to a queue,
but processes byte-oriented data.
The internal operation of queues is not of interest here, but it should be understood that they have more
overheads in memory and runtime than mailboxes. This is primarily because two pointers – to the head and
tail of the queue – need to be maintained.
Mutexes
Mutual exclusion semaphores – mutexes – are independent kernel objects, which behave in a very similar
way to normal binary semaphores. They are slightly more complex and incorporate the concept of
temporary ownership (of the resource, access to which is being controlled). If a task obtains a mutex, only
that same task can release it again – the mutex (and, hence, the resource) is temporarily owned by the task.
Mutexes are not provided by all RTOSes, but it is quite straightforward to adapt a regular binary
semaphore. It would be necessary to write a “mutex obtain” function, which obtains the semaphore and
notes the task identifier. Then a complementary “mutex release” function would check the calling task’s
identifier and release the semaphore only if it matches the stored value, otherwise it would return an error.
Download