log.c (4545B)
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. Each system call that might write the file system 9 // should be surrounded with begin_trans() and commit_trans() calls. 10 // 11 // The log holds at most one transaction at a time. Commit forces 12 // the log (with commit record) to disk, then installs the affected 13 // blocks to disk, then erases the log. begin_trans() ensures that 14 // only one system call can be in a transaction; others must wait. 15 // 16 // Allowing only one transaction at a time means that the file 17 // system code doesn't have to worry about the possibility of 18 // one transaction reading a block that another one has modified, 19 // for example an i-node block. 20 // 21 // Read-only system calls don't need to use transactions, though 22 // this means that they may observe uncommitted data. I-node and 23 // buffer locks prevent read-only calls from seeing inconsistent data. 24 // 25 // The log is a physical re-do log containing disk blocks. 26 // The on-disk log format: 27 // header block, containing sector #s for block A, B, C, ... 28 // block A 29 // block B 30 // block C 31 // ... 32 // Log appends are synchronous. 33 34 // Contents of the header block, used for both the on-disk header block 35 // and to keep track in memory of logged sector #s before commit. 36 struct logheader { 37 int n; 38 int sector[LOGSIZE]; 39 }; 40 41 struct log { 42 struct spinlock lock; 43 int start; 44 int size; 45 int busy; // a transaction is active 46 int dev; 47 struct logheader lh; 48 }; 49 struct log log; 50 51 static void recover_from_log(void); 52 53 void 54 initlog(void) 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(ROOTDEV, &sb); 62 log.start = sb.size - sb.nlog; 63 log.size = sb.nlog; 64 log.dev = ROOTDEV; 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.sector[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.sector[i] = lh->sector[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->sector[i] = log.lh.sector[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 void 125 begin_trans(void) 126 { 127 acquire(&log.lock); 128 while (log.busy) { 129 sleep(&log, &log.lock); 130 } 131 log.busy = 1; 132 release(&log.lock); 133 } 134 135 void 136 commit_trans(void) 137 { 138 if (log.lh.n > 0) { 139 write_head(); // Write header to disk -- the real commit 140 install_trans(); // Now install writes to home locations 141 log.lh.n = 0; 142 write_head(); // Erase the transaction from the log 143 } 144 145 acquire(&log.lock); 146 log.busy = 0; 147 wakeup(&log); 148 release(&log.lock); 149 } 150 151 // Caller has modified b->data and is done with the buffer. 152 // Append the block to the log and record the block number, 153 // but don't write the log header (which would commit the write). 154 // log_write() replaces bwrite(); a typical use is: 155 // bp = bread(...) 156 // modify bp->data[] 157 // log_write(bp) 158 // brelse(bp) 159 void 160 log_write(struct buf *b) 161 { 162 int i; 163 164 if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) 165 panic("too big a transaction"); 166 if (!log.busy) 167 panic("write outside of trans"); 168 169 for (i = 0; i < log.lh.n; i++) { 170 if (log.lh.sector[i] == b->sector) // log absorbtion? 171 break; 172 } 173 log.lh.sector[i] = b->sector; 174 struct buf *lbuf = bread(b->dev, log.start+i+1); 175 memmove(lbuf->data, b->data, BSIZE); 176 bwrite(lbuf); 177 brelse(lbuf); 178 if (i == log.lh.n) 179 log.lh.n++; 180 b->flags |= B_DIRTY; // XXX prevent eviction 181 } 182 183 //PAGEBREAK! 184 // Blank page. 185