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