~~NOTOC~~
====== Lecture 11 Scribe Notes ======
By Sona Chaudhuri, Stephen Oakley and Patrick Hunt
===Let's talk about the midterm:===
Average: 68.8
Median: 70
Range: 44-94
Std dev = 12.74
**Synchronization Problems:**\\ \\
Problem 12:
typedef struct{
semaphore_t sem;
int n;
semaphore_t m;
}
void cond_notify(condvar_t *cv){
/*supposed to wake up anyone who is waiting*/
P(cv->m);
if(cv->n){
V(cv->sem);
cv->n--;
}
V(cv->m);
}
void cond_var(condvar_t *cv, semaphore_t *m){
P(cv->m);
V(m);
cv->n++;
V(cv->m);
P(cv-> sem);
/* P(cv->m);
cv->n--;
V(cv->m);*/
P(m);
}
Note that the decrement must be in the notify to avoid "pre-notification" if the wait function does not complete before the next notify.
Last Problem:
struct mutex{
int fd[2];
}
mutex_init(mutex_t *m){
pipe(&m->fd[0]);
write(m->fd[1], “Y”, 1);
}
The approach works due to the synchronization and blocking properties of pipes that we observed in class.
/* One critical assumption: there are no signals. */
=== File Systems: ===
Topics in this unit:
-Disks and persistent storage
-File system designs
-How to make a disk available to applications
File systems give the operating system the power to turn off and help to increase overall robustness and versatility. By implementing file systems, we can now assert that the operating system is robust against power failure. It is also important for us to look at this implementation in the terms of cost effectiveness. The price of memory is significantly higher than that of disk. While the price of memory is $0.09/MB, the price of disk is only $0.0007/MB. By looking at these comparisons, we see that it is in our best interest to maximize the usage of disk, while minimizing the amount of memory we need to use/buy. A file system implementation allows us the versatility to work with data that is larger than memory. Thus, we can also have program inputs and outputs that are larger than memory, thereby increasing our overall versatility.
==A file system is an abstraction of a hard disk.==
Here is a diagram of a typical hard drive:\\
{{http://www.read.cs.ucla.edu/111/_media/2007spring/notes/harddisk.jpg}}
There are several key parts:
*The hard disk is composed of platters.
*These platters are divided into tracks (concentric rings). Data is stored on tracks.
*An arm extends to each platter (often only one platter in a disk).
*At the tip of an arm is a head, and this is what actually reads data.
*The controller handles interaction between the actual hard disk and the CPU. There is a cache in the controller.
==Reading from a hard disk==
One of the key issues with a hard disk is the time it takes to read data off of it. Here are the speeds for finding data from a 2007 Seagate Barracuda 7200rpm:
*Before reading data, the hard disk needs to find it. The time this takes is limited by two main physical constraints--moving the head onto the correct track (seek time), and rotating the platter so that the correct portion is underneath the head ("rotational latency"). The **average seek time is 8.5ms**, and the **average rotational latency is 4.16ms**. This is very slow. For reference, on a 1Ghz machine, the CPU will execute approximately 12.5 million instructions in this time.
*Once the hard disk has found the data, it needs to fetch it. On the Barracuda, the **average sustained data transfer rate is 58 MB/s** and the **peak transfer rate is 150 MB/s**. This is assuming that the data is on the disk sequentially and that we don't need to do any seeks when we're reading. If we had to do a seek and rotation for each byte we read, we'd only be able to read around 100 bytes per second!
Now, need to be able to refer to file system: interface, implementation:
==File System Interface:==
Firstly, we must determine how processes will access stable storage, and also what the best way to store the file system will be. It is most intuitive to create a HIERARCHICAL structure in which there is a tree of DIRECTORIES (a group of files). This hierarchical tree normally begins with a root directory that is considered the 'home'. Having such a structure definitely aids in simplicity and allows humans to access each file in a timely fashion.
==File System Implementation:==
The implementation deals with how the contents of the file system are laid out on the disk and how the kernel handles it. We now know two things: that file systems are supposed to be robust to power failure and that they are to use things that are cheaper than main memory. Now, we must concern ourselves with how to compensate for the fact that disks are cheap, but really really slow.
=== Performance Enhancement Strategies: ===
**Problem:** Due to seek times, rotational latency, and slow bus speeds, disk access is extremely slow relative to the processor.\\
**Solution:** Try to reduce the number of requests
**1.Caching:** A p.e.s. where a program makes a temporary local copy of a request's results to avoid future requests.\\
**2.Batching:** A p.e.s. where a program combines several requests into one to reduce per request overhead.\\
**3.Speculation:** A p.e.s. where a program performs operations in advance of requests so that results are immediately available when they're needed. This works because of __Locality of Reference__: References close to one another in time are likely to refer to data close to one another in space.
Speculation for read requests is called __PREFETCHING__. (Prefetching is safe for reads because reads don't change the disk)\\
**4.Dallying:** A p.e.s. that delays requests to create opportunities for batching.
=== Buffered I/O Layer ===
-Improve performance by using disks smarter
{{notes:bufcache2.jpg|}}
-Read more bytes than we need; (even if you only need 1 byte, keep the extra 511 Bytes attained when reading the sector in a local CACHE, in case you need them in the future)
*Read several sectors at a time that way continuous reads are fast. In addition ensure that files are laid out sequentially on disk. Seek times will kill performance.
*Note: Most memory management in modern operating systems is maintaining caches and application memory.
Make adjustment to read() in kernel:
Is the result in cache?
(Does the result fall in the last sector read?)
If yes, use cache
If no, make request to disk
-This is CACHE COHERENT because of PROCESS ISOLATION\\
-Can further improve performance by making the cache bigger and read more data than we need.\\
-The buffer cache can be huge\\
-But, how can we improve the performance of writes?\\
~Instead of writing directly to disk, the kernel waits to write the data from the buffer cache, that is to use dallying\\
A good illustration courtesy of Spring 07 Scribe notes:
{{http://www.read.cs.ucla.edu/111/_media/2007spring/notes/dallying.jpg}}
~Now one write to disk can be made, but this makes it become a little bit less robust for prevention of system failure on power\\
-'sync' is a blocking system call that flushes the buffer cache\\
===Cache Coherence===
The idea of a local cache containing the most up-to-date version of the data, (a cache is coherent if it contains the up-to-date version). The importance of cache coherence is easily seen in multi-processor systems, where each processor has its own local cache.
{{notes:cachecoherency.png|}}
A sequence with two processors (P1 and P2) showing a failure in cache coherence:\\
1. P1 locks the file con.txt for writing.\\
2. P1 loads the contents of con.txt into its cache.\\
3. P2 attempts to read from con.txt, but blocks, waiting on P1.\\
4. P1 makes numerous changes to con.txt, **but changes are only made to cache** so that they may be later committed in a batch to save disk access overhead.\\
5. P1 finishes writing, and releases the lock on con.txt.\\
6. P2 wakes up, acquires a read lock on con.txt and **reads data from disk before P1's changes were committed to disk**.
A simple fix for the above example would be for the kernel to sync() whenever an attempt is made to read().\\
(A smarter version would keep track of uncommitted writes, and only ensure the data is committed far enough through the cache hierarchy to be seen by the reader. Meaning, if P1 reads after it writes, the read will come from the local cache which is already up to date, so no committing is required. However, if P2 reads after P1 writes, the data would be required to commit from P1's local cache, to main system memory, where P2 can then read it. If the commit is too large for system memory, it would have to be committed all the way to the disk before P2 could read it.)
===Storage Hierarchy===
|{{notes:storagehierarchy.png|obligatory cs/ce diagram}}|Storage follows a general rule, the faster it is, the more expensive it is. Thus to acquire the necessary amounts of long term storage, we have to use a lot of cheap, but slow, memory (hard disks). This is the reason for caching, to put our limited amounts of expensive, high performance memory to their best use possible, and minimize our use and dependence on the slowest memory.|