CONTEXT SWITCHING
PREEMPTIVE
REAL-TIME OPERATING SYSTEMS
ARTOS
executes processes based upon timing constraints provided by the system
designer. The most reliable way to meet timing constraints accurately is to
build a preemptive OS and to use priorities to control what process runs at any
given time. We will use these two concepts to build up a basic RTOS. We will
use as our example OS Free RTOS. org[Bar07]. This operating system runs on many
different plat forms.
1 Preemption
Preemption
is an alternative to the C function call as away to control execution. To be
able to take full advantage of the timer, we must change our notion of a
process as something more than a function call. We must, infact, break the
assumptions of our high-level programming language. We will create new routines
that allow us to jump from one subroutine to another at any point in the
program. That, together with the timer, will allow us to move between functions
whenever necessary based upon the system’s timing constraints.
We want
to share the CPU a cross two processes. The kernel is the part of the OS that
determines what process is running. The kernel is activated periodically by the
timer. The length of the timer period is known as the time quantum because it
is the smallest increment in which we can control CPU activity. The kernel
determines what process will run next and causes that process to run. On the
next timer interrupt, the kernel may pick the same processor another process to
run.
Note that
this use of the timer is very different from our use of the timer in the last
section. Before, we used the timer to control loop iterations, with one loop
Iteration including the execution of several complete processes. Here, the time
quantum is in general smaller than the execution time of any of the processes.
How do we switch between processes before the process is done? We cannot rely
on C-level mechanisms to do so. We can, however, use assembly language to
switch between processes. The timer interrupt causes control to change from the
currently executing process to the kernel; assembly language can be used to
save and restore registers. We can similarly use assembly language to restore
registers not from the process that was interrupted by the timer but to use
registers from any process we want. These to F registers
that
define a process are known as its con- text and switching from one process’s
register set to another is known as context switching. The data structure that
holds the state of the process is known as the process control block.
2 Priorities
How does
the kernel determine what process will run next? We want a mechanism that
executes quickly so that we don’t spend all our time in the kernel and starve
out the processes that do the useful work. If we assign each task a numerical
priority, then the kernel cans imply look at the processes and their
priorities, see which ones actually want to execute (some may be waiting for
data or for some event), and select the highest priority process that is ready
to run. This mechanism is both flexible and fast. The priority is a
non-negative integer value. The exact value of the priority is not as important
as the relative priority of different processes. In this book, we will
generally use priority 1 as the highest priority, but it is equally reasonable
to use
■A
process continues execution until it completes or it is pre empted by a
higher-priority process. Let’s define a simple system with three processes as
seen below. Process Priority
Execution
time
P1 1 10
P2 2 30
P3 3 20
In
addition to describing the properties of the processes in general, we need to
know the Environmental setup. We assume that P2 is ready to run when the system
is started, P1 is released at time 15, and P3 is released at time 18. Once we
know the process properties and the environment, we can use the pri-orities to
determine which process is running throughout the complete execution of the
system. To understand the basics of a context switch, let’s assume that these
to f tasks is in steady state: Everything has been initialized, the OS is
running, and we are ready for a timer interrupt. This diagram shows ,the
hardware timer, and all
The
functions in the kernel that are involved in the context switch:
/*Acknowledge
the interrupt at AIC level... */
AT91C_BASE_AIC->AIC_EOICR=portCLEAR_AIC_INTERRUPT;
/*Restore
the context of the new task. */
portRESTORE_CONTEXT();
}
Pre
emptive Tick () has been declared as an aked function; this means that it does
not use the
normal
procedure entry and exit code that is generated by the compiler. Because the
function is
naked,
the registers for the process that was interrupted are still available; v Pre
emptive Tick () doesn’t have to go to the procedure call stack to get their
values. This is particularly handy since the procedure mechanism would save
only part of the process state, making the state- saving code a little more
complex.
The first
thing that this routine must do is save the context of the task that was
interrupted. To do this, it uses the routine port SAVE_CONTEXT(), which saves
all the context of the stack. It the n performs some house keeping, such as
incre- menting the tick count. The tick count is the internal timer that is
used to determine deadlines. After the tick is incremented, some tasks may have
become ready as they passed their deadlines.
Next, the
OS determines which task to run next using the routine vTaskSwitchContext().
After some more house keeping, it uses port RESTORE_CONTEXT() to restore the
context of the task that was selected by vTaskSwitchContext(). The action of
portRESTORE_CONTEXT() can uses control to transfer to that task with out using
the standard C return mechanism.
The code
for portSAVE_CONTEXT(), in the file port macro.h, is defined as a macro and not
as a
C
function. It is structured in this way so that it does n’t disturb the register
values that need to be saved. Because it is a macro, it has to be written in a
hard-to-read way—all code must be on the same line or end-of-line continuations
(back slashes) must be used. Here is the code in more readable form, with the
end-of-line continuations removed and the assembly language that is the heart
of this routine temporarily removed.:
#defineportSAVE_CONTEXT()
{
Extern
volatilevoid *volatile pxCurrentTCB;
Extern
volatile unsigned portLONGulCriticalNesting;
/*PushR0aswearegoingtousetheregister.
*/
Asm
volatile( /*assembly languagecodehere */ );
(void )
ulCriticalNesting;
(void )
pxCurrentTCB;
}
The asm
statement allows assembly language code to be introduced in-line in to the C
program.
The keyword
volatile tells the compiler that the assembly language may change register
values,
which
means that many compiler optimizations can not be performed across the assembly
language code. The code uses ulCriticalNesting and pxCurrentTCB simply to avoid
compiler warnings about un used variables— the variables are actually used in
the assembly code, but the compiler can not see that. The asm statement
requires that the assembly language be entered as strings, one string per line,
which makes the code hard to read. The fact that the code is included in a
#define makes it even harder to read. Here is a clean ed-up version of the
assembly language code from the asm volatile()statement:
STMDB SP!, {R0}
/*SetR0topointtothetaskstackpointer.
*/
STMDB SP, {SP}^
NOP
SUB SP, SP,
#4
LDMIA SP!,{R0}
/*Pushthereturnaddressontothestack.
*/
STMDB R0!, {LR}
/*NowwehavesavedLRwecanuse
it insteadofR0. */
MOV LR,R0
/*PopR0sowecansave
itontothesystemmodestack. */
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.