1 #include "types.h" 2 #include "defs.h" 3 #include "param.h" 4 #include "mmu.h" 5 #include "proc.h" 6 #include "x86.h" 7 #include "spinlock.h" 8 #include "fs.h" 9 #include "buf.h" 10 11 // Simple logging. Each system call that might write the file system 12 // should be surrounded with begin_trans() and commit_trans() calls. 13 // 14 // The log holds at most one transaction at a time. Commit forces 15 // the log (with commit record) to disk, then installs the affected 16 // blocks to disk, then erases the log. begin_trans() ensures that 17 // only one system call can be in a transaction; others must wait. 18 // 19 // Allowing only one transaction at a time means that the file 20 // system code doesn't have to worry about the possibility of 21 // one transaction reading a block that another one has modified, 22 // for example an i-node block. 23 // 24 // Read-only system calls don't need to use transactions, though 25 // this means that they may observe uncommitted data. I-node 26 // and buffer locks prevent read-only calls from seeing inconsistent data. 27 // 28 // The log is a physical re-do log containing disk blocks. 29 // The on-disk log format: 30 // header block, containing sector #s for block A, B, C, ... 31 // block A 32 // block B 33 // block C 34 // ... 35 // Log appends are synchronous. 36 37 // Contents of the header block, used for both the on-disk header block 38 // and to keep track in memory of logged sector #s before commit. 39 struct logheader { 40 int n; 41 int sector[LOGSIZE]; 42 }; 43 44 struct { 45 struct spinlock lock; 46 int start; 47 int size; 48 int intrans; 49 int dev; 50 struct logheader lh; 51 } log; 52 53 static void recover_from_log(void); 54 55 void 56 initlog(void) 57 { 58 if (sizeof(struct logheader) >= BSIZE) 59 panic("initlog: too big logheader"); 60 61 struct superblock sb; 62 initlock(&log.lock, "log"); 63 readsb(ROOTDEV, &sb); 64 log.start = sb.size - sb.nlog; 65 log.size = sb.nlog; 66 log.dev = ROOTDEV; 67 recover_from_log(); 68 } 69 70 // Copy committed blocks from log to their home location 71 static void 72 install_trans(void) 73 { 74 int tail; 75 76 //if (log.lh.n > 0) 77 // cprintf("install_trans %d\n", log.lh.n); 78 for (tail = 0; tail < log.lh.n; tail++) { 79 struct buf *lbuf = bread(log.dev, log.start+tail+1); // read log block 80 struct buf *dbuf = bread(log.dev, log.lh.sector[tail]); // read dst 81 memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst 82 bwrite(dbuf); // flush dst to disk 83 brelse(lbuf); 84 brelse(dbuf); 85 } 86 } 87 88 // Read the log header from disk into the in-memory log header 89 static void 90 read_head(void) 91 { 92 struct buf *buf = bread(log.dev, log.start); 93 struct logheader *lh = (struct logheader *) (buf->data); 94 int i; 95 log.lh.n = lh->n; 96 for (i = 0; i < log.lh.n; i++) { 97 log.lh.sector[i] = lh->sector[i]; 98 } 99 brelse(buf); 100 //if (log.lh.n > 0) 101 // cprintf("read_head: %d\n", log.lh.n); 102 } 103 104 // Write in-memory log header to disk, committing log entries till head 105 static void 106 write_head(void) 107 { 108 // if (log.lh.n > 0) 109 // cprintf("write_head: %d\n", log.lh.n); 110 111 struct buf *buf = bread(log.dev, log.start); 112 struct logheader *hb = (struct logheader *) (buf->data); 113 int i; 114 hb->n = log.lh.n; 115 for (i = 0; i < log.lh.n; i++) { 116 hb->sector[i] = log.lh.sector[i]; 117 } 118 bwrite(buf); 119 brelse(buf); 120 } 121 122 static void 123 recover_from_log(void) 124 { 125 read_head(); 126 install_trans(); // if committed, copy from log to disk 127 log.lh.n = 0; 128 write_head(); // clear the log 129 } 130 131 void 132 begin_trans(void) 133 { 134 acquire(&log.lock); 135 while (log.intrans) { 136 sleep(&log, &log.lock); 137 } 138 log.intrans = 1; 139 release(&log.lock); 140 } 141 142 void 143 commit_trans(void) 144 { 145 if (log.lh.n > 0) { 146 write_head(); // Causes all blocks till log.head to be commited 147 install_trans(); // Install all the transactions till head 148 log.lh.n = 0; 149 write_head(); // Reclaim log 150 } 151 152 acquire(&log.lock); 153 log.intrans = 0; 154 wakeup(&log); 155 release(&log.lock); 156 } 157 158 // Caller has modified b->data and is done with the buffer. 159 // Append the block to the log and record the block number, 160 // but don't write the log header (which would commit the write). 161 // log_write() replaces bwrite(); a typical use is: 162 // bp = bread(...) 163 // modify bp->data[] 164 // log_write(bp) 165 // brelse(bp) 166 void 167 log_write(struct buf *b) 168 { 169 int i; 170 171 if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) 172 panic("too big a transaction"); 173 if (!log.intrans) 174 panic("write outside of trans"); 175 176 // cprintf("log_write: %d %d\n", b->sector, log.lh.n); 177 178 for (i = 0; i < log.lh.n; i++) { 179 if (log.lh.sector[i] == b->sector) // log absorbtion? 180 break; 181 } 182 log.lh.sector[i] = b->sector; 183 struct buf *lbuf = bread(b->dev, log.start+i+1); 184 memmove(lbuf->data, b->data, BSIZE); 185 bwrite(lbuf); 186 brelse(lbuf); 187 if (i == log.lh.n) 188 log.lh.n++; 189 } 190