CS372H Spring 2010 Homework 7 Solutions

Problem 1:

The MegaGiga hard disk rotates at 5400 rpm with an arm seek time given by = 4 + 0.05t msec, where t is the number of tracks the arm seeks. Assume a block size of 512 bytes, and 1024 tracks with 512 sector/track. The disk controller and DMA read or write data from/to disk at a rate of 4MB/sec.
  1. What is the storage capacity of the disk?
    Solution:
    The storage capacity of the disk is
    (sector size) * (number of sectors/track) * (number of tracks)

  2. Assume that we are reading a 16K-bytes file where the sectors happen to be allocated contiguously on the same track. Compute the maximum and minimum disk throughput that is possible while reading the file?
    Solution:
    Maximum disk throughput occurs when the head is right on top of the required sector. In this case we can transfer the 32 sectors that constitute the file immediately without seeking or paying rotational latency overhead. But we do not write instantaneously, we need to compute the time it takes for the head to travel over the 32 sectors. This time can be computed as follows:
    It takes the rotational latency for the head to travel 512 sectors (sectors per track).
    Therefore from one sector to the next = rotational latency/512
    = (60/5400) / 512
    = 22 microseconds (rounded up)

    Therefore, to get the 16K bytes out of the disk, it takes 32 sectors * 22 microseconds/sector = 694 microseconds. Note that the bandwidth of the disk itself is therefore about 23MB/s. But, we also have to pay the DMA overhead, which is

    16K/(4*1024*1024) = 3.9msec

    So, the total time is 3.9 ms + .694 ms = 4.59 ms. The transfer time is dominated by the DMA overhead, and the throughput is slightly less than the 4MB/sec rate of the DMA controller (4.59 ms per 16KB = 3.49 MB/s).

    Moral of the story: When we make a transfer and the disk head does not have to move, then we transfer data at the maximum speed that the disk controller electronics allow.

    Minimum throughput: This will occur when the disk head has to travel the maximum distance to get to the data. In this case, at worst, it has to go over the entire platter, and then wait for an entire rotation before it gets to the beginning of the file. The time to do this is:

    seek time + latency time = (4 + 0.05 * 1024) + (60/5400)
    =  55.2 (seek) + 11.11 (rotation)
    = 66 msec

    The DMA overhead is the same as above. Therefore, the overhead is dominated by the cost of seeking and waiting for the head to get to the right sector on the track. The result is 16 KB/66ms = 247KB/sec (about 7% of the sequential throughput).

    Moral of the story: When the head has to move, the throughput drops considerably, and you can see that the effect may produce only 7% of the controller bandwidth on a read or write (and with a better DMA controller the peak bandwidth would be higher and the long seek and rotation would reduce the bandwidth to about 1% of peak).
    Exercise: Compute the same if the sectors were not contiguous. You will see that 7% of the max bandwidth may not be even reachable if the sectors are scattered throughout the disk. Sequential allocation is good!!

Problem 2:

You are designing a file system from scratch. The disk driver allows you complete control over the placement of data on the disk. Assuming that you have settled for a File Allocation Table (FAT) architecture, where would be the best place to store the table on disk?

Solution:
The best place would be the middle track within the disk. The reason is that under an elevator scheduling policy, the middle track would be visited by the disk head twice in one round trip, with the time between the two visits being the maximum for any track. This is the most desirable condition for a track that will be heavily accessed.

Problem 3:

The FAT file system uses 16-bit numbers to represent the cluster number that starts the linked list of the clusters that are implementing a file. Explain the implications of limiting the cluster numbers to 16 bits. Modify the FAT file system to use 32-bit numbers to represent the clusters and show how the limitations of FAT can be lifted.

Solution:
Using 16-bit numbers to express the clusters allows only up to 32K clusters per disk (because negative numbers are needed to express end of file and end of table symbols).. For a large disk the cluster size, which is the unit of allocation for files,  must become excessively large (e.g. a cluster must be 64K in a 2GB disk paritition.). This results in tremendous internal fragmentation for files. Also, it limits the size of a disk partition using FAT to only 2GB.
To lift these restrictions, we use 32-bit numbers to express the clusters. However, one must be carefuly because this arrangement potentially allows up to 2G entries in the FAT table, each consisting of 4 bytes! Therefore, we need to store the actual size of the FAT table somewhere on disk, and use only the minimum number of entries that would allow the FAT table to use 32-bit numbers for a desired cluster size. For example, a 4GB disk can be subdivided to 8M clusters of 512 bytes each. The size of the table required would be 32MB, or about less than 1% of the disk size.

Problem 4:

Contiguous allocation of files leads to disk fragmentation. Is this internal or external fragmentation?

Solution:
Both! A file is still allocated in disk block units, which means the last block in a file would be half empty (internal fragmentation). Additionally, contiguous allocation results in external fragmentation because it could be the case that there many small unallocated fragments between files and they cannot be used to allocate a file whose size is greater than the size of any fragment, but less than the total size of the free space.

Problem 5 (review multi-threads programming)

We have 3 teams: red, green, and blue. A team member calls Game::myTurn(int color) when she or he wants to do something and calls Game::doneTurn() when she or he is done. At most one team may be doing something at a time. Each team gets a turn in the order red, green, blue, red, green, blue, and so on. If a team is not ready when the previous team is done with its turn, the team is skipped and the next ready team gets a chance. If no team is ready, the first team to request a turn gets the next turn. Note that many team members of the same color may want to take a turn; model each as an independent thread. Write the Game class using condition variables and locks. Follow the coding standards specified in class.

Solution 1:
List Game's synchronization and state variable here:

Mutex lock;
Cond donePlaying;

public:
static const int RED = 0;
static const int GREEN = 1;
static const int BLUE = 2;
static const int NCOLORS = 3;

private:
int waiting[NCOLORS];
int previousColor;
bool busy;

public
Game::Game() // constructor
{
  waiting[0] = waiting[1] = waiting[2] = 0;
  previousColor = BLUE;
  busy = FALSE;
}

public
Game::myTurn(int color)
{
  lock.acquire();
  waiting[color]++;
  while (iShouldWait(color) ) {
    donePlaying.wait(&lock);
  }
  busy = TRUE;
  waiting[color]--;
  previousColor = color;
  lock.release();
}

public
Game::doneTurn()
{
  lock.acquire();
  busy = false;
  donePlaying.broadcast(&lock);
  }

private:
int Game::iShouldWait(int color)
{
    assert(lock is held);
    if(busy){
       return TRUE;
    }
    if(color == (previousColor + 1) % NCOLORS){
        return FALSE;
    }
    if((color == (previousColor + 2) % NCOLORS) && (waiting[(previousColor + 1) % NCOLORS] == 0){
         return FALSE;
    }
    if((color == (previousColor + 3) % NCOLORS) && (waiting[(previousColor + 2) % NCOLORS] == 0){
         assert(color == previousColor);
         assert((waiting[(color + 1) % NCOLORS] == 0) && (waiting[(color + 1) % NCOLORS] == 0));
         return FALSE;
    }
    return TRUE;
}

 

 

 

Solution 2:

List Game's synchronization and state variable here:

Mutex lock;
Cond No_One_Play;

Boolean Busy;
Queue q[3];
int Id_Count;
int nextId;
int previous_color;

Game::Game() // constructor
{
  Busy = false;
  q[0] = q[1] = q[2] = NULL;
  Id_Count = 0;
  nextId = -1;  // -1 means no one is waiting
  previous_color = -1;
}

Game::myTurn(int color)
{
  int myId;

  lock.acquire();

  myId = Id_Count;
  Id_Count++;
  q[color].enque(myId);
  while (Busy || ((nextId >= 0) && (myId !=  nextId)) )
  {
    No_One_Play.wait(&lock);
  }
  q[color].deque();
  Busy = true;
  previous_color = color;

  lock.release();
}

Game::doneTurn()
{
  lock.acquire();
  Busy = false;

  //compute the next one who should go if there's anyone waiting.
  next_id = -1;

  int next_color = (previous_color+1)mod 3;
  if ( !q[next_color].isEmpty()){
    nextId = q[next_color].front();
  } else{
    next_color = (next_color+1)mod 3;
    if ( !q[next_color].isEmpty()){
    nextId = q[next_color].front();
    } else 
    if(!q[previous_color].isEmpty())
   {
     nextId = q[previous_color].front();
   }
 }

 No_One_Play.broadcast(&lock);
  lock.release();
}