CS452 - Real-Time Programming - Winter 2018
Lecture 4 - Tasks & Kernels
Public Service Annoucements
- Marking of a0.
-
Due date for kernel 1: 26 January, 2018
-
Change to combination for the lab.
A few interesting things about RedBoot
What's in the flash ROM of the board is a partial implementation
of Redboot. Redboot is a product of the ecos project, initiated
in the distant past by Cygnus to create a set of components from
which a real-time OS could be instantiated. The boot loader parts
became RedBoot when Red Hat was acquired by Cygnus. What Technologic
calls RedBoot is a collection of components that they put together
that offers a particular set of operations at the RedBoot prompt. The
"useful" ones are
- fconfig :: NO! NOT! NEVER!
- load (tftp)
- go [address]
- examine, copy, fill memory
You can, for example, send a byte through a UART by reading the
status register, checking the ready bit, then writing the data
register.
The program you wrote for a0 puts its local variables
on a stack?
-
What stack?
-
Where is it in memory?
This enables you to get a clean return to RedBoot when your program
terminates.
When your program crashes it's because an exception has been
generated because of, for example,
-
an illegal instruction,
-
an unhandled interrupt,
-
an invalid memory reference (seg fault)
Redboot catches the exception, and puts out some information onto
which gcc might hook. (ecos and RedHat are strongly connected to
the GNU project. (Don't bother trying to use gdb!)
You see the information on the bottom two lines of the terminal
window. It's useless except for one 8 digit number starting 00218
or 00219. This is the address of the last instruction the CPU
attempted to execute. Using the load map and the assembly code
you can find exactly how far the program got before crashing.
Unfortunately, the error may have occurred long before execution
stopped. The amount a program runs after the error occurred increases as the
term goes on.
Kernel of a Real-time Operating System
Introduction
The base unit of a polling loop is
if ( condition ) action;
We generalize this structure only a little in constructing the kernel
of our operating system: the kernel determines when the condition
is true, then readies the task that executes the action, which
terminates by informing the kernel of the condition for its next
execution.
It cannot be emphasized too strongly that the kernel may --
but is NOT guaranteed to -- run a task immediately
after it is made ready. The kernel's task handling code must be
correct whether the task is run immediately or only after other
tasks have run. The bugs created when this is wrong are neither
subtle nor easy to repair. They should de designed out.
In the next few weeks we will build, from scratch, a micro-kernel.
The micro-kernel supervises the execution of tasks, which are the
elements of our systems. A task consists of
-
a set of instructions that it executes,
-
memory in which it stores data, and
-
state, such as processor state, kernel state, and so on.
The kernel provides support for a running task (the active task),
allowing it to
-
create a new task,
-
send a message to another task,
-
receive a message from another task, and
-
wait for an interrupt to occur.
These basic needs are provided by the kernel of every operating
system. The kernel we create in cs452 is a microkernel, because
it provides these capabilities and nothing more.
We build the microkernel in four assignments
- task creation and scheduling
- inter-task communication
- the interrupt primitive
- complex servers
The real-time operating system we build consists of
-
an uninterruptible microkernel, plus
-
interruptible device-handling server tasks that run in user-space.
What Does a Microkernel Provide?
Tasks
A program is conceived as a collection of co-operating tasks.
Tasks provide applications with modularity. Task structure as a
method of program organization will be discussed about the time
you are finishing the OS.
A task comprises
-
instructions, common to all tasks of the same kind,
-
global constants, such as strings used for
formatting messages, which is also common to all tasks of
the same kind,
-
static write once variables that read as the
same value in every instance of tasks that share the same code,
and
-
state local to the individual task.
Every task instance has its own local state. Two tasks can have
the same set of instructions but no two tasks share local state.
Why are tasks important?
-
Thinking about one thing at a time is easy.
-
Listen to me lecturing to you.
-
Thinking about more than one thing at a time is hard.
-
Keep on listening, and at the same time think about what
frustrated you most during a0.
-
Trying to solve more than one real-time problem at a time
by thinking, is very hard
-
think about turning the wheel, peddling and balancing
while learning to ride a bicycle
-
How did you learn to coordinate all these activities
in real-time?
-
Tasks allow a programmer to reduce parts of a activity into
sequences of instructions that communicate with other
sequences.
The programmer can then separate reasoning about his or her program
into reasoning about single-threaded task execution (easy, provided
that ...) and inter-task communication (easy, provided that ...).
Communication
Communication has two aspects
- sharing information and
- synchronization.
We use Send/Receive/Reply (SRR) to do both.
- Send blocks
- Receive blocks: synchronizes with the call to send
-
Reply doesn't block: synchronizes its return with the return
from send.
Sharing data
Data is usually shared in the form of a struct
, which
requires a common name space of types, but removes parsing and
formating. The kernel copies the data from the memory of one task
to the memory of another.
Two transfers of data occur between the call to send and the
return from it. (The idea is that Send requests something without
which it cannot continue computing.) Send does transfers both
ways at once. Receive and Reply both have one transfer of data
between the call and the return.
Synchronization
- Between tasks
- Coordination of execution in co-operating tasks
- Uses SRR
- With internal events
- Real-time by synchronizing with a real-time clock: e.g. clock
server
- Ordering execution: e.g. name server, bounded buffer
- Uses SRR
- With external events
Interrupts
In the polling loop you polled a register
-
to discover if input had arrived
-
to discover if you could send output
Almost every test failed, which wasted CPU. (Exercise for the
reader. For each condition/action pair in a0, what proportion of
the condition tests evaluated to false?) Even worse, it increases
the response time, the time from when the event occurs until the
first instruction of user code responding to it occurs.
You can get rid of the polling tests if you program the hardware
to send the CPU a signal when the condition for which you are
waiting occurs. Such a signal is called an exception and the CPU
goes into a privileged state when the interrupt occurs.
The kernel responds to the exception by reading volatile data and
readying the task that is waiting on the interrupt. This task is
essentially the action that was conditioned on the event in the
polling loop.
Tasks
The first part of the kernel requires you to create, schedule and
run a collection of tasks. What is a task?
-
A set of instructions
-
The current state, which is changed by executing instructions.
The state of a task includes which includes
-
values of its local variables, which are automatic variables
maintained on the stack
-
contents of its registers
-
other processor state such as the PSR
- processor mode
- condition codes
- etc.
-
its run state and other things maintained by the kernel.
These things are normally packed together in a task
descriptor (TD), of which there is one per task.
When a task enters the kernel its state must be saved completely,
so that when it begins executing again, the kernel can restore
its state exactly. The analogy to how a function is called
and returned from is important to understand.
Two tasks can use the same set of instructions, but every task
has its own local state, which is maintained on its private stack.
(To provide each task with a private stack the kernel must split
up memory among tasks.) There is some memory that is shared among
tasks with the same set of instructions.
-
constants located with the instructions, such as format
strings for printf and its relatives, and
-
variables of the static storage class, which are placed
wherever your linker script tells them to go.
Static variables may be useful but they are dangerous. Good
programming practice says that they should be written once and
after that only read.
The kernel keeps track of every task's state
-
In essence, servicing a request amounts to changing the state
of one or more tasks.
-
Kernel maintains a task descriptor (TD) for each created task.
-
That is, to create a task the kernel must allocate a TD and
initialize it.
The TD normally contains
-
The task's stack pointer, which points to a private stack,
in the memory of the task, containing
- PC
- other registers
- local variables
all ready to be reloaded whenever the task next runs.
-
Possibly the return value for when the task is next activated
-
The task's parent
-
The task's state
-
Links to queues on which the task is located
-
The kernel uses these to find the task when it is servicing
a request
Possible states of the task
-
Active: If the system is in user space, the active task is in the
CPU, running. If the system is in the kernel the active task
has either just finished running or is about to run. You are
in control of when it changes from one to the other.
-
On a single processor system only one task can ever be
active.
-
But we would like to build our kernel so that it can
scale to more than one processor.
-
Ready: Will be made act
-
Need a queue used by the scheduler when deciding which
task should be the next active task
-
Blocked: waiting for something to happen
-
Need several queues, one for each thing that could happen
The single blocked, zombie, state will differentiate into several
different blocked states as the kernel develops.
Kernel Structure
The kernel is just a function (with no arguments and no return
value like a task), and which runs forever. It runs forever because like most
real programs it terminates when the user decides it should terminate.
void main( ) {
initialize( ); // includes starting the first user task
FOREVER {
active = schedule( );
request = activate( active );
handle( request );
}
}
Where is the OS?
- the small function called
main( )
- initialized immediately after
main( )
is called
- starts the first user task
- entered from user tasks by SWI
- requests come from running user tasks
- one type of request creates a task
- There needs to be a first task that gets everything going
Where are the user tasks?
-
The active task is started inside
activate( )
, and
-
termineates inside
activate( active )
,
-
after which its request is handled by the kernel.
What's inside activate( active )
?
-
transfer of control to the active task
-
execution to completion of the active task
-
`to completion' means until the active task sends a request
to the kernel, OR
-
until an interrupt occurs.
-
transfer of control back to the kernel
-
getting the request
The hard part to get right is "transfer of control" which we call
a context switch.
The Hardware/Software Provided in the Undergraduate Environment
TS-7200
Specific documentation from Technologic
System on Chip (SoC)
EP9302, designed and manufactured by Cirrus semiconductor
Memory
Byte addressable, word size 32 bits
Hardware configuration created by RedBoot
- 32 Mbytes of RAM, starting at
0x00000000
- 4 Mbytes of flash RAM, starting at
0x60000000
-
Contains RedBoot, which is loaded into RAM early in the
boot sequence.
- Special locations at low addresses
- interrupt vectors
- memory management tables
- Special locations above
0x80000000
Two types of special location
- Supplied by Technologic:
0x80840000
to
0x80840047
- Suppied by Cirrus:
0x80010000
to 0x8081ffff
0x808a0000
to 0x80900023
Separate instruction and data caches
`COM' ports
Connected to two UARTs implemented on the SoC
- RS-232
- Actual UART hardware is on the EP9302
- Really only two, stay away from what looks the third one, which is
probably only there to work around a bug in the SoC.
Reset switch
The reset switch may be red or black, even though documentation
says black.
EP-9302
Specific documentation from Cirrus
System on chip
ARM 920T core
Specific documentation from ARM, which covers
- ARM v4T instruction set
- 32 bit word, 32 bit bus
- all instructions one word
- except thumb instructions (T suffix), which you shouldn't
use
- in-order instruction issue
- some instructions privileged
-
Only one instruction that that changes state from unprivileged
to privileged state: SWI -- software interrupt
- Core includes
- CPU
- MMU
- L1 cache
- Co-processor interface
- System control co-processor
Return to: