CS452 - Real-Time Programming - Fall 2008
Lecture 5 - Kernel, Tasks
Questions & Comments
- Lab will be ready some time during the week-end for: Assignment 2 (aka
1)
Kernel of a Real-time Operating System
Diagram
What Does the Kernel Provide
Tasks
- Provide applications with modularity, which is a higher level at which
they can be organized
- Consist of
- instructions, common to all tasks
- global state, common to all tasks
- local state, different state in different tasks
- How tasks work together
- synchronization
- communication
- combined into one mechanism: message passing
Communication
Communication has two sides
- sharing information
- handshaking
We use Send/Receive/Reply (SRR) to do both.
- Send blocks
- Receive blocks: is synchronous with the call to send
- Reply doesn't block: is synchronous with the return from send
Diagram
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
Kernel Structure
The kernel is just a function like any other, but which runs forever.
kernel( ) {
initialize( );
FOREVER {
request = getNextRequest( );
handle( request );
}
}
This hides all the interesting stuff inside
int getNextRequest( ) {
active = schedule( ); //active is a pointer to a TD
nextRequest = activate( active ); //the active task doesn't change
return nextRequest);
}
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
- 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
How I Get Started Writing a Context Switch
1. Start with a program that calls a function
void func( ) {
printf( "func: running\n" );
}
void main( ) {
printf( "main: beginning\n" );
func( );
printf( "main: ending\n" );
}
- Compile and run it.
- Compile with the -S flag and look at the assembly code.
2. Find out how to put a PC into interrupt vector n
- Add to the assembly code a line that puts the address of the first
instruction of
func( ) into the interrupt vector
- Compile the assembly code and run it.
3. Change to calling func( ) using an interrupt
- Replace the instruction in main that calls func( ) to
int
n
- Replace the
ret instruction in func( ) with
iret.
4. You have just written a context switch, now you need to dress it
up.
- Add stuff to
main( ) so that it has context
- Save the context of
main( ) on the stack just before
int n.
- Restore the context of
main( ) from the stack just after
int n.
5. Add a return value from func( ) and pick it up in main( ).
To go beyond this we need to think about tasks.
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 normall
contains
- The task's stack pointer, which points to a private stack 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
Possible states of the task
- Active: running or about to run
- Ready: can run if scheduled
- Blocked: waiting for something to happen
Diagram
Return to: