Guest User

Untitled

a guest
Jul 17th, 2018
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.72 KB | None | 0 0
  1. ---- ALGORITHMS ----
  2.  
  3. >> C2: Describe how your cache replacement algorithm chooses a cache
  4. >> block to evict.
  5.  
  6. Cached sectors are evicted by the "evict_sector" function. Our cache
  7. replacement algorithm will always evict the least-recently-used (LRU) sector
  8. from the buffer cache, excluding sectors with pending disk IOs since those
  9. can never be evicted.
  10.  
  11. We can not evict sectors with pending disk IOs (i.e. sectors with state
  12. SECTOR_PENDING) since these sectors are either: 1) in the process of being
  13. read INTO the cache, or 2) in the process of being EVICTED from the cache.
  14.  
  15. Our eviction algorithm is made much simpler by virtue of the fact that we
  16. always keep cached sectors least-recently-used (LRU) order, with the most-
  17. recently-used sector at the back and the least-recently-used sector at the
  18. front of the list. Thus, anytime we touch (i.e. read or write) a cached
  19. sector, we remove it and re-insert it at the back of the list, thus
  20. maintaining LRU ordering at all times.
  21.  
  22. During eviction, we search for an evictable sector starting at the front of
  23. the sector list. We select the first sector with state != SECTOR_PENDING to
  24. evict. If the sector is dirty (i.e. it has been modified but not written
  25. back to disk), then we write the sector back to the disk before returning it.
  26.  
  27. If every sector in the cache is pending on disk IOs (i.e. state ==
  28. SECTOR_PENDING), then we can't evict a sector until at least one of them
  29. finishes and changes its state to something else. If this happens, the
  30. evict_sector function waits on the disk_io_finished condition which gets
  31. broadcast anytime a disk IO finishes. Then, the process wakes up and
  32. attempts to find an evictable sector once more.
  33.  
  34. >> C3: Describe your implementation of write-behind.
  35.  
  36. When data is written to disk sectors via cache_block_write, we read the
  37. sector into the cache (if it is not already there) and modify the cached
  38. sectors in-memory. Changes are not immediately written to disk, but saved in
  39. memory. We mark these modified sectors as "dirty" by setting their state to
  40. "SECTOR_USED_DIRTY" and keeping them in the cache. When a dirty sector is
  41. evicted, it written back to disk.
  42.  
  43. Additionally, when the buffer cache is initialized we create a new thread to
  44. asynchronously flush dirty sectors to disk on a regular interval. This
  45. thread sits in a while loop with a 30-second sleep between each iteration of
  46. the loop. On each iteration, the thread acquires the buffer_cache lock,
  47. increments the time counter, and writes all dirty sectors in the buffer
  48. cache back to the disk.
  49.  
  50. During flushing, we want to take a snapshot of the current buffer_cache
  51. state and write it all to disk. A simple solution is to acquire the
  52. buffer_cache lock, write all dirty sectors to disk one at a time, and then
  53. release the lock. This solution ensures that the other sectors can not be
  54. dirtied or evicted during our flush operation. However, it is sub-optimal
  55. because it requires holding the buffer_cache lock for a long period of
  56. time, during which other buffer cache operations can not proceed.
  57.  
  58. Therefore, we devised a better solution. We created a "time" counter which
  59. starts at 0 and gets incremented on each iteration of the
  60. "write_behind_thread" loop. Any time a sector is dirtied, we update the
  61. sector's "update_time" to the current time. Then, we allow flushing to
  62. proceed asynchronously -- that is we write dirtied sectors to disk one-at-a-
  63. time, taking care to release the buffer_cache lock before any blocking disk
  64. operations (so that other buffer_cache operation are free to proceed). After
  65. each disk operation finishes, we restart our search through the buffer_cache
  66. only looking for sectors with an update_time less than the current time.
  67. This way we only flush sectors which have been in memory for more than 30
  68. seconds.
  69.  
  70. When there are no more sectors with an update_time less than the current
  71. time, we sleep for 30 seconds and loop again.
  72.  
  73. Lastly, when Pintos is halted, we also write all dirty sectors back to disk
  74. in cache_flush_all. Since Pintos is shutting down anyway, we don't take any
  75. special care to release the buffer_cache lock during blocking writes.
  76.  
  77.  
  78. >> C4: Describe your implementation of read-ahead.
  79.  
  80. In order to make our implementation of read-ahead easier, we wrote a
  81. "monitor queue" which is basically a thread-safe implementation of a queue,
  82. useful for producer-consumer applications. It has "monitor_queue_put",
  83. "monitor_queue_get", and "monitor_queue_size" functions (see interface in
  84. lib/kernel/monitor_queue.h). One thread can put stuff into the queue while
  85. another thread gets stuff out of the queue.
  86.  
  87. The "put" function will always return in a timely fashion since there is no
  88. maximum number of entries that the queue can hold (i.e. there is no waiting
  89. on a space_available condition variable like in the example in lecture). On
  90. the other hand, "get" may wait on a condition variable until there are new
  91. items in the queue, since get must always return an item from the queue.
  92.  
  93. Since our queue implementation is thread-safe, we are free to call "put" and
  94. "get" without worrying about locking, which is handled by the implementation.
  95.  
  96. To implement read-ahead in our file reading code, we automatically fetch the
  97. next sector of a file into the cache whenever inode_read_at is invoked,
  98. since the next sector is likely to be read soon. We call cache_read_ahead on
  99. the next disk sector which adds the sector to a "read-ahead queue" (backed
  100. by our monitor queue implementation).
  101.  
  102. When the buffer cache is initialized we create a new thread to process "read-
  103. ahead jobs" from the read-ahead queue. This thread sits in a while loop and
  104. continually gets new sector_nums from the queue and optimistically caches
  105. them. Each time it gets a sector_num it adds it to the buffer cache by
  106. calling cache_sector(sector_num).
  107.  
  108. Note that the read-ahead thread does not busy-wait. The "monitor_queue_get"
  109. function waits on a condition when there are no read_ahead_jobs to consume
  110. from the queue. Thus, the file reading code can read the sector it
  111. immediately requires without waiting to also read the next sector.
  112.  
  113.  
  114. ---- SYNCHRONIZATION ----
  115.  
  116. >> C5: When one process is actively reading or writing data in a
  117. >> buffer cache block, how are other processes prevented from evicting
  118. >> that block?
  119.  
  120. Before a thread can evict a cached sector in our system, the thread must
  121. first acquire the buffer_cache lock.
  122.  
  123. When one process is actively reading or writing data from a cached sector,
  124. it first acquires the buffer_cache lock to prevent other processes from
  125. evicting the same block. Once the read or write is finished, the
  126. buffer_cache lock is released.
  127.  
  128. Note: The buffer_cache lock is NEVER held during disk IO operations. The
  129. lock is only held during reads and writes to sectors that are cached in
  130. memory. That is, the lock is only held while data is copied into and out of
  131. the cached sector using MEMCPY, which is fast).
  132.  
  133. >> C6: During the eviction of a block from the cache, how are other
  134. >> processes prevented from attempting to access the block?
  135.  
  136. Before a thread can evict a cached sector in our system, the thread must
  137. first acquire the buffer_cache lock. As discussed in "Section C2", to evict
  138. a sector, we search for a sector with state != SECTOR_PENDING. We always
  139. exclude sectors with SECTOR_PENDING in our eviction algorithm since these
  140. sectors have pending disk IOs and should not be evicted.
  141.  
  142. If the sector we want to evict is not dirty, then we can immediately return
  143. it and release the buffer_cache lock. There is no way for other processes to
  144. access the sector because the current process holds the buffer_cache lock.
  145.  
  146. However, if the sector we wish to evict is dirty, then we change the
  147. sector's state to SECTOR_PENDING to indicate that the sector is waiting on a
  148. disk IO, release the lock, and write the sector to disk (which is a blocking
  149. operation). During this write operation, other processes are free to acquire
  150. the buffer_cache lock and do reads/writes on other cached sectors. All code
  151. in our system is careful not to use or modify sectors with state
  152. SECTOR_PENDING. Finally, when the write operation for the evicted sector
  153. completes, the process re-acquires the buffer_cache lock and is free to use
  154. the evicted sector (changing it's state to something other than
  155. SECTOR_PENDING before actually using it).
  156.  
  157.  
  158. ---- RATIONALE ----
  159.  
  160. >> C7: Describe a file workload likely to benefit from buffer caching,
  161. >> and workloads likely to benefit from read-ahead and write-behind.
  162.  
  163. Repeatedly reading or writing a small set of disk sectors (e.g. during a
  164. directory lookup, each directory entry is read sequentially) will be much
  165. faster with buffer caching. The speedup is even greater when the sectors are
  166. physically far from each other on disk because seeks are very costly.
  167.  
  168. Repeatedly writing a small set of disk sectors (e.g. writing to a file one
  169. byte at a time) will be much faster with write-behind. This is because the
  170. sectors can be repeatedly updated in memory (perhaps thousands of times) and
  171. written to disk only a single time.
  172.  
  173. Reading or writing a file sequentially (e.g. opening or saving a document in
  174. a text editor) will be much faster with read-ahead. This is because we
  175. optimistically and asynchronously cache the next sector of a file when one
  176. sector is read. Due to the locality properties of file reading, it is
  177. usually the case that if one file sector is read, the following sector is
  178. likely to be read in the near future.
Add Comment
Please, Sign In to add comment