CMSC 412
|
NOTE 3
|
Feb 27, 1999
|
Toy OS with Asynchronous IO
1. Overview
We modify the toy OS given in note 2 to provide asynchronous io; that is,
when a user process makes an io request, the request is entered in a queue
(ioRequestQ) and the process is free to resume execution. The queued requests
are handled by an OS pcb process (called ioServer). [Asynchronous io service
can be provided without introducing an OS pcb process.]
ioServer is a process that never terminates. It sleeps at ioWaitQ and
wakes up for the following two events:
-
When an io request is issued and no io is ongoing, ioServer wakes up and
starts the io operation, and goes back to sleep.
-
When the io device interrupts, ioServer informs completion of the io request
to the issuing user process (e.g., via a pointer parameter of the io request)
and starts the next io operation if any, and goes back to sleep.
The OS has the following interrupt handlers:
-
Syscall( PROC_TERM ): as in Note 2.
-
Syscall( IO, params ): io system call handler. Puts the io request in ioRequestQ,
wakes up ioServer if this is the only request in the queue, and returns.
-
Syscall( IOWAIT ): system call used within OS. Execution causes process
to wait in ioWaitQ.
-
Trap( INVALID_OP ): as in Note 2.
-
HwIntrpt( TIMER ): as in Note 2.
-
HwIntrpt( IO, params ): Wakes up ioServer.
The system has the following (pcb) processes:
-
Zero or more user processes. Each user process has its code, data and stack
in its own memory area.
-
One OS process, called ioServer. Its code (ioServerProgram), data and stack
are inside OS memory space. Executes in OS mode with interrupts disabled.
2. Data structures
- type IoRequest {
parameters of io request
// e.g. disk address, transfer size, memory address
// user memory location for signalling io completion
}
- var runQ // as in note 2.
- var readyQ // as in note 2.
- var ioWaitQ // as in note 2 except that now it holds
// at most one PCB, that of the ioServer
- ioRequestQ: queue of IoRequest /* The pending io requests.
The request (if any) at the head is currently being
handled by io device. Note this is not a queue of PCBs. */
- Initially, each user process pcb is as in Note 2.
- Initially, ioServer pcb is in readyQ and is as follows:
status = READY,
cpuState.gpr = arbitrary,
cpuState.sp = address of top of ioServer stack,
cpuState.hi = arbitrary,
cpuState.lo = arbitrary,
cpuState.pc = address of start of ioServerProgram,
cpuState.ps.mode = OS,
cpuState.ps.intrptEnabled = OFF,
ioParams = EMPTY // no io pending
- Initially, runQ is empty (as in Note 2).
- Initially, ioRequesttQ is empty.
- Initially, cpu is executing Scheduler (as in Note 2)
3. Macros, Functions, Interrupt Handlers
macro UpdateRunQPCB() // as in Note 2
function Scheduler() // as in Note 2
interruptHandler Syscall( PROC_TERM ) // as in Note 2
interruptHandler Syscall( IO, params ) {
if ioRequestQ is empty
then // no io ongoing, ioServer asleep in ioWaitQ, wake it
move PCB from ioWaitQ to readyQ ;
add ioRequest( params ) to ioRequestQ ;
Return_from_Interrupt ;
}
interruptHandler Syscall( IOWAIT ) {
// wait in ioWaitQ. Called only by ioServer
UpdateRunQPCB() ;
move runQ.PCB to ioWaitQ ;
Scheduler() ;
}
interruptHandler Trap( INVALID_OP ) // as in Note 2
interruptHandler HwIntrpt( TIMER ) // as in Note 2
interruptHandler HwIntrpt(IO) {
/* control here after io device interrupts.
Interrupt indicates completion of request at head of
ioRequestQ. ioServer is asleep in ioWaitQ. Wake it up.
move PCB from ioWaitQ to readyQ ;
Return_from_Interrupt ;
}
function IoServerProgram() {
/* program executed by the OS process ioServer.
runs in OS mode (to access io device) and with interrupts
disabled (to ensure atomic access). Non-terminating.
*/
forever do {
if ioRequestQ is EMPTY
then Syscall( IOWAIT ) ;
// ioReqQ not empty (otherwise would still be asleep).
if ioDevice.status is busy
then Syscall(IOWAIT) ;
// ioReqQ not empty, io device idle. Start io operation
ioDevice.controlReg := ioRequestQ.head.pcb.ioParams ;
Syscall(IOWAIT) ;
// io operation completed, communicate this to user
InformUserIoOver( ioRequest.head.params ) ;
remove head request in ioRequestQ ;
// handle next request if any
}
4. Comments
-
The above assumes that ioRequestQ is never full. Where is this assumption
used? Modify the specs so that this assumption is not used.
-
Examine the IoServerProgram carefully. It may not work under all conditions.
-
What would happen if the body of Syscall( IOWAIT ) was called as a regular
function or as a macro, rather than via a system call.
-
The scheduling discipline for ioRequestQ (and for ioWaitQ in Note 2) can
be chosen to optimize some criteria. We will see examples later in the
course.
-
Disabling interrupts to protect OS resources (e.g., ioRequestQ, ioWaitQ,
etc.) is not always efficient. This also blocks interrupts whose processing
does not involve these resources (e.g., a timer interrupt whose handling
would have switched between user processes that are not currently doing
io). This leads to poor performance.
-
With the current OS structure, if a user process wants to wait for the
io completion signal, it can do this only by busy waiting. To do a nonbusy
wait would require introducing another process queue specifically for this
condition, which then leads us to another issue, namely, should this queue
be in user space or OS space (pros and cons?). We need more elegant langauge
mechanisms (as opposed to OS mechanisms).