| 1 | #include "types.h" |
| 2 | #include "defs.h" |
| 3 | #include "param.h" |
| 4 | #include "spinlock.h" |
| 5 | #include "sleeplock.h" |
| 6 | #include "fs.h" |
| 7 | #include "buf.h" |
| 8 | |
| 9 | // Simple logging that allows concurrent FS system calls. |
| 10 | // |
| 11 | // A log transaction contains the updates of multiple FS system |
| 12 | // calls. The logging system only commits when there are |
| 13 | // no FS system calls active. Thus there is never |
| 14 | // any reasoning required about whether a commit might |
| 15 | // write an uncommitted system call's updates to disk. |
| 16 | // |
| 17 | // A system call should call begin_op()/end_op() to mark |
| 18 | // its start and end. Usually begin_op() just increments |
| 19 | // the count of in-progress FS system calls and returns. |
| 20 | // But if it thinks the log is close to running out, it |
| 21 | // sleeps until the last outstanding end_op() commits. |
| 22 | // |
| 23 | // The log is a physical re-do log containing disk blocks. |
| 24 | // The on-disk log format: |
| 25 | // header block, containing block #s for block A, B, C, ... |
| 26 | // block A |
| 27 | // block B |
| 28 | // block C |
| 29 | // ... |
| 30 | // Log appends are synchronous. |
| 31 | |
| 32 | // Contents of the header block, used for both the on-disk header block |
| 33 | // and to keep track in memory of logged block# before commit. |
| 34 | struct { |
| 35 | int ; |
| 36 | int [LOGSIZE]; |
| 37 | }; |
| 38 | |
| 39 | struct log { |
| 40 | struct spinlock lock; |
| 41 | int start; |
| 42 | int size; |
| 43 | int outstanding; // how many FS sys calls are executing. |
| 44 | int committing; // in commit(), please wait. |
| 45 | int dev; |
| 46 | struct logheader lh; |
| 47 | }; |
| 48 | struct log log; |
| 49 | |
| 50 | static void recover_from_log(void); |
| 51 | static void commit(); |
| 52 | |
| 53 | void |
| 54 | initlog(int dev) |
| 55 | { |
| 56 | if (sizeof(struct logheader) >= BSIZE) |
| 57 | panic("initlog: too big logheader" ); |
| 58 | |
| 59 | struct superblock sb; |
| 60 | initlock(&log.lock, "log" ); |
| 61 | readsb(dev, &sb); |
| 62 | log.start = sb.logstart; |
| 63 | log.size = sb.nlog; |
| 64 | log.dev = dev; |
| 65 | recover_from_log(); |
| 66 | } |
| 67 | |
| 68 | // Copy committed blocks from log to their home location |
| 69 | static void |
| 70 | install_trans(void) |
| 71 | { |
| 72 | int tail; |
| 73 | |
| 74 | for (tail = 0; tail < log.lh.n; tail++) { |
| 75 | struct buf *lbuf = bread(log.dev, log.start+tail+1); // read log block |
| 76 | struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst |
| 77 | memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst |
| 78 | bwrite(dbuf); // write dst to disk |
| 79 | brelse(lbuf); |
| 80 | brelse(dbuf); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | // Read the log header from disk into the in-memory log header |
| 85 | static void |
| 86 | read_head(void) |
| 87 | { |
| 88 | struct buf *buf = bread(log.dev, log.start); |
| 89 | struct logheader *lh = (struct logheader *) (buf->data); |
| 90 | int i; |
| 91 | log.lh.n = lh->n; |
| 92 | for (i = 0; i < log.lh.n; i++) { |
| 93 | log.lh.block[i] = lh->block[i]; |
| 94 | } |
| 95 | brelse(buf); |
| 96 | } |
| 97 | |
| 98 | // Write in-memory log header to disk. |
| 99 | // This is the true point at which the |
| 100 | // current transaction commits. |
| 101 | static void |
| 102 | write_head(void) |
| 103 | { |
| 104 | struct buf *buf = bread(log.dev, log.start); |
| 105 | struct logheader *hb = (struct logheader *) (buf->data); |
| 106 | int i; |
| 107 | hb->n = log.lh.n; |
| 108 | for (i = 0; i < log.lh.n; i++) { |
| 109 | hb->block[i] = log.lh.block[i]; |
| 110 | } |
| 111 | bwrite(buf); |
| 112 | brelse(buf); |
| 113 | } |
| 114 | |
| 115 | static void |
| 116 | recover_from_log(void) |
| 117 | { |
| 118 | read_head(); |
| 119 | install_trans(); // if committed, copy from log to disk |
| 120 | log.lh.n = 0; |
| 121 | write_head(); // clear the log |
| 122 | } |
| 123 | |
| 124 | // called at the start of each FS system call. |
| 125 | void |
| 126 | begin_op(void) |
| 127 | { |
| 128 | acquire(&log.lock); |
| 129 | while(1){ |
| 130 | if(log.committing){ |
| 131 | sleep(&log, &log.lock); |
| 132 | } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){ |
| 133 | // this op might exhaust log space; wait for commit. |
| 134 | sleep(&log, &log.lock); |
| 135 | } else { |
| 136 | log.outstanding += 1; |
| 137 | release(&log.lock); |
| 138 | break; |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // called at the end of each FS system call. |
| 144 | // commits if this was the last outstanding operation. |
| 145 | void |
| 146 | end_op(void) |
| 147 | { |
| 148 | int do_commit = 0; |
| 149 | |
| 150 | acquire(&log.lock); |
| 151 | log.outstanding -= 1; |
| 152 | if(log.committing) |
| 153 | panic("log.committing" ); |
| 154 | if(log.outstanding == 0){ |
| 155 | do_commit = 1; |
| 156 | log.committing = 1; |
| 157 | } else { |
| 158 | // begin_op() may be waiting for log space, |
| 159 | // and decrementing log.outstanding has decreased |
| 160 | // the amount of reserved space. |
| 161 | wakeup(&log); |
| 162 | } |
| 163 | release(&log.lock); |
| 164 | |
| 165 | if(do_commit){ |
| 166 | // call commit w/o holding locks, since not allowed |
| 167 | // to sleep with locks. |
| 168 | commit(); |
| 169 | acquire(&log.lock); |
| 170 | log.committing = 0; |
| 171 | wakeup(&log); |
| 172 | release(&log.lock); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | // Copy modified blocks from cache to log. |
| 177 | static void |
| 178 | write_log(void) |
| 179 | { |
| 180 | int tail; |
| 181 | |
| 182 | for (tail = 0; tail < log.lh.n; tail++) { |
| 183 | struct buf *to = bread(log.dev, log.start+tail+1); // log block |
| 184 | struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block |
| 185 | memmove(to->data, from->data, BSIZE); |
| 186 | bwrite(to); // write the log |
| 187 | brelse(from); |
| 188 | brelse(to); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | static void |
| 193 | commit() |
| 194 | { |
| 195 | if (log.lh.n > 0) { |
| 196 | write_log(); // Write modified blocks from cache to log |
| 197 | write_head(); // Write header to disk -- the real commit |
| 198 | install_trans(); // Now install writes to home locations |
| 199 | log.lh.n = 0; |
| 200 | write_head(); // Erase the transaction from the log |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | // Caller has modified b->data and is done with the buffer. |
| 205 | // Record the block number and pin in the cache with B_DIRTY. |
| 206 | // commit()/write_log() will do the disk write. |
| 207 | // |
| 208 | // log_write() replaces bwrite(); a typical use is: |
| 209 | // bp = bread(...) |
| 210 | // modify bp->data[] |
| 211 | // log_write(bp) |
| 212 | // brelse(bp) |
| 213 | void |
| 214 | log_write(struct buf *b) |
| 215 | { |
| 216 | int i; |
| 217 | |
| 218 | if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) |
| 219 | panic("too big a transaction" ); |
| 220 | if (log.outstanding < 1) |
| 221 | panic("log_write outside of trans" ); |
| 222 | |
| 223 | acquire(&log.lock); |
| 224 | for (i = 0; i < log.lh.n; i++) { |
| 225 | if (log.lh.block[i] == b->blockno) // log absorbtion |
| 226 | break; |
| 227 | } |
| 228 | log.lh.block[i] = b->blockno; |
| 229 | if (i == log.lh.n) |
| 230 | log.lh.n++; |
| 231 | b->flags |= B_DIRTY; // prevent eviction |
| 232 | release(&log.lock); |
| 233 | } |
| 234 | |
| 235 | |