@www.csse.monash.edu.au |
| Assessment | Contacts | Laboratories | Lectures | Resources | Timetables | Tutorials | Unit Outline |
| Last modified: 20080918:092523/initial version for 2008 | FIT2022 AJH-2008-39 |
Objectives and Outcomes | Introduction | I/O Device Models | Generating the Block Requests | A Shortest Seek Time First Scheduler
At the end of this lab session, you should:
Your demonstrator may ask you questions about these points, and will only give you a "satisfactory" for the lab if you can respond correctly.
We develop a model of I/O device access characteristics, and use this to build a simulation of disk accessing, using a variety of disk scheduling algorithms.
You can download all files generated by this document in the tarfile lab6.tar to use in the following exercises.
I/O devices are typically orders of magnitude slower than CPU and memory components, and hence require careful management if their performance is not to unduly affect overall system performance. What we strive to do in I/O subsystem design is to balance the performance of the I/O components against that of the CPU and memory, so that neither unduly delays the other.
A major factor affecting I/O device performance is that they generally are not true random access devices. Access times to data stored in an I/O device (either reading or writing) comprise two basic factors: latency, which is the time taken to start a data transfer, and data transfer rate, which is the raw speed at which data can be moved into or out of the device.
For reading, the generic process consists of supplying an address to the device, starting the read operation, and then transferring the data from the device as it becomes available. For example, a scanner may take some time to warm up the lamp and move the carriage to the start of the original before it can start delivering data. This is the latency time. Once the data starts being delivered by the device, it may be constrained by the bandwidth of the data channel, or, more usually, by the intrinsic delivery rate of the device. For a scanner, this will be limited by the physical speed of the scanner head.
For writing, the generic process requires the initial supply of both address and data to the device, although the data may be delayed until the latency time has elapsed. For example, a printer may have to pick a sheet of paper from the stock hopper, and transport it until it is under the print head before the data to print actually needs to be available.
For both read and writing, a single user level request ("scan a print") may be broken into a number of address/data requests at the device level. (We have already seen this with file reads and writes in Lab 5 requiring multiple disk block accesses.)
We shall in this lab gloss over the differences between reading and writing, as they tend to be dominated by the other factors.
"iodev.py" 3.1 =The iodev module defines 3 classes of IO devices:
The Scale factor determines the simulation slow down factor. Devices run a hundred times slower than their actual speed, so that they can be observed in real time.
The generic device is straightforward. Its seek time or latency is constant, and the data transfer rate is effectively instantaneous.
<class IODevice definitions 3.2> =Initialize the generic IO device with a default size of 100,000 blocks. The current_block and previous_block variables are used in computing the latency. Call this class in the form
d = IODevice(20000)to create an instance d of a generic IO device with size 20,000 blocks (the block size is not specified).<class IODevice seek 3.4> =
This routine simulates a generic IO device seek, takingIODevice.SeekTime seconds for all accesses (the calculation is independent of the actual block sought).
Note that we first check that the block address is within range, and then record the current block address, before simulating a response time of SeekTime for all accesses, regardless of address or current position.
The Tape device is characterized by a seek time that is proportional to the distance between the current block, and the desired block.
<class Tape definitions 3.5> =The Tape device inherits from the class IODevice, indicated by the use of the IODevice class name in the "parameter list" of the class.
Here we use inheritance to say "a Tape device is an example of an IO device. It has slightly different characteristics in terms of its seek time, but it can be used in a similar fashion to the class IODevice. Everything that you can do with an IODevice, you can do with a Tape device." In computer-speak, it "isa" IODevice.
<class Tape initialization 3.6> =Define the default size of a tape device to be somewhat larger (100,000,000 blocks) than the generic device. Otherwise the initialization is exactly the same as for the generic device, indicated by the superclass call on the parent class initialization method.
<class Tape seek 3.7> =Check that the requested block is in range. The seek time is assumed to be directly proportional to the distance between the new target block (current_block) and the previous block accessed (previous_block). Simulate this seek time with a sleep operation.
The Disk device is characterized by a latency that has two parts: moving the read/write head to the appropriate cylinder, called the head seek time (typically 10's of mS, depending upon the distance travelled); and the time taken for the required sector to rotate under the read head, called the rotational latency. For a disk rotating at 3600 rpm, this means an average rotational latency of 1/2 of the time for one revolution, or 0.00833 seconds. (We are using old technology here!)
<class Disk definitions 3.8> =We assume that there are 1000 sectors to a cylinder, making the rotational latency 0.01667/1000 = 0.0000167 seconds per sector.
<class Disk initialization 3.9> =As before, the initialization of the disk calls the parent class initialization, augmented by the additional parameter setting of the number of cylinders on the disk.
<class Disk seek 3.10> =check for address in range and determine the new current block address, as given by the actual parameter
<class Disk seek 3.11> =compute the new cylinder and sector information, as well as (re)compute the old values of the same. These are given by splitting an address into two parts, using a divide and remainder algorithm. (In practice, if the cylinder size is a power of two, shift and mask can be used.)
<class Disk seek 3.12> =Compute the seek distances across the tracks (cylinders to cylinders) and around the track (sector to sector).
<class Disk seek 3.13> =Compute the total seek time and simulate it.
OK, let's try these out. To do this, create a little test file that creates instances of each of these things, and then calls them with a variety of short and long seek times for each type.
"test-dev.py" 3.14 =We need some things to drive the IO devices. We will model three different styles of address generation: random, as generated by a binary chop algorithm, and as generated by a column major array access. A practical example of the last one is an image that is accessed in vertical strips, rather than in horizontal strips. This means the array of pixels is accessed down the columns, rather than across the rows (which would be called Row Major order access).
"simprocess.py" 4.1 =We define three processes that simulate the generation of a sequence of block requests. Each of these processes is modelled by a thread, which is a quasi-parallel sequence of execution, as per the lecture discussion for slides 4.16-18. The plan is to run each of these processes in parallel, so that their sequence of disk requests is interleaved with each other, exactly as occurs in real life.
The first class defined here is the parent class of the block request generation, and serves to define common features of the process invocation and sequencing mechanisms.
<class Proc start 4.3> =A process thread is started by this call. Note that the actual starting is done by the parent, which also invokes the run method for this object (see following). Here we keep track of the number of active block address generators, by incrementing a shared variable. Note the critical section surrounding it.
<class Proc run 4.4> =The run method actually runs the process within the currently active thread. It is invoked automagically by the start method. The run method is actually going to be overridden by later definitions, but it serves to define a default mechanism, viz. (L. videre licet, it is permitted to see, see also, namely), scan through from start to finish and read every 100th block.
<class Proc finish 4.5> =Here we terminate the process and its thread, and decrement the number of processes, again in a critical section.
<class Proc initialize 4.6> =Some initialization stuff. Not worth explaining here, but interested readers are referred to the library documentation.
Now for something more subtle: interleaved seeks!
"test-proc2.py" 4.10 =The start methods fire up each of the respective threads: the corresponding join methods wait for each thread to terminate. Note that you could change the c.run in Exercise 3 to a c.start(); c.join() for exactly the same effect. What is the difference between these two forms?
The random access pattern is handled by repeated calls on the random method that returns an integer in the specified range (s..e). Initialization is handled through the parent, and the start and finish methods are inherited from the parent.
OK, run this and see what you get. Is the pattern what you would expect? What can you say about it? Try changing the limit from 1000 to 10,000 or even 1,000,000.
"test-chop.py" 4.14 =OK, run this and see what you get. Is the pattern what you would expect? What can you say about it? Try changing the number of iterations.
"test-col.py" 4.16 =Now for the real crunch: running all these methods in parallel!
"test-par1.py" 4.17 =The requests generated so far have simply been fed straight to the IO device. Particularly for disks, there is an advantage on ordering the requests so that extreme movements of the head are avoided. To do this, we interpose a scheduler between the source of disk addresses and the disk device itself.
"sstf.py" 5.1 =Our new design introduces a scheduler class, SSTF, described below. In order to see how the scheduler can improve matters, this time we run a number of chop processes, started en masse.
<sstf start a range of block request processes 5.2> =Here's how we fire off 10 instances of our chopper processes ...
<sstf wait for all processes to complete 5.3> =... and then wait for them all to finish.
The shortest seek time first scheduler swallows up block requests and returns immediately, through its accept_req interface. However, to make sure that a process requesting a block does not continue on until the request is completed, we pass in a synchronization lock lk as part of the request. The tuple of block number and lock (b,lk) is then appended to a queue for servicing by the disk when it is next free.
The scheduler itself runs as a separate process, and hence the additional housekeeping activities to initialize and start the process. str_q is used to return a string representing the block requests in the queue, for tracing purposes.
<sstf disk request 5.5> =Here is the other side of synchronizing with the scheduler. Each process requesting a block calls this routine, which creates an event lock, initially cleared. We then pass the request on to the scheduler, along with this lock. Since the accept_req interface routine returns immediately, we must then wait for the event to be signalled, through the complete event flag.
<class SSTF run method 5.6> =The guts of the SSTF scheduler. We run an outer loop, that remains whilever there are still address generating processes in the system. Since within this loop we update shared variables (can you identify them?), we whack a mutual exclusion around it, pausing only to issue a sleep(0) call which has the effect of allowing any other ready process to run.
<class SSTF run method find closest block 5.7> =And the real guts: we scan through the current queue of block requests, looking for the closest block to the current block, since that will have the shortest seek time.
| 20080918:092523 | 2.0.0 | ajh | initial version for 2008 |
| This page maintained by John Hurst. Copyright Monash University Copyright Policy |
| ||
Generated at 20090705:1404 from an XML file modified on 20080918:0927 | |||