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 // cprintf("put entry %d to disk block %d\n", tail, log.lh.sector[tail]); 80 struct buf *lbuf = bread(log.dev, log.start+tail+1); // read i'th block from log 81 struct buf *dbuf = bread(log.dev, log.lh.sector[tail]); // read dst block 82 memmove(dbuf->data, lbuf->data, BSIZE); 83 bwrite(dbuf); 84 brelse(lbuf); 85 brelse(dbuf); 86 } 87 } 88 89 // Read the log header from disk into the in-memory log header 90 static void 91 read_head(void) 92 { 93 struct buf *buf = bread(log.dev, log.start); 94 struct logheader *lh = (struct logheader *) (buf->data); 95 int i; 96 log.lh.n = lh->n; 97 for (i = 0; i < log.lh.n; i++) { 98 log.lh.sector[i] = lh->sector[i]; 99 } 100 brelse(buf); 101 //if (log.lh.n > 0) 102 // cprintf("read_head: %d\n", log.lh.n); 103 } 104 105 // Write the in-memory log header to disk, committing log entries till head 106 static void 107 write_head(void) 108 { 109 // if (log.lh.n > 0) 110 // cprintf("write_head: %d\n", log.lh.n); 111 112 struct buf *buf = bread(log.dev, log.start); 113 struct logheader *hb = (struct logheader *) (buf->data); 114 int i; 115 hb->n = log.lh.n; 116 for (i = 0; i < log.lh.n; i++) { 117 hb->sector[i] = log.lh.sector[i]; 118 } 119 bwrite(buf); 120 brelse(buf); 121 } 122 123 static void 124 recover_from_log(void) 125 { 126 read_head(); 127 install_trans(); // if committed, copy from log to disk 128 log.lh.n = 0; 129 write_head(); // clear the log 130 } 131 132 void 133 begin_trans(void) 134 { 135 acquire(&log.lock); 136 while (log.intrans) { 137 sleep(&log, &log.lock); 138 } 139 log.intrans = 1; 140 release(&log.lock); 141 } 142 143 void 144 commit_trans(void) 145 { 146 write_head(); // This 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 acquire(&log.lock); 152 log.intrans = 0; 153 wakeup(&log); 154 release(&log.lock); 155 } 156 157 // Caller has modified b->data and is done with the buffer. 158 // Append the block to the log and record the block number, 159 // but don't write the log header (which would commit the write). 160 // log_write() replaces bwrite(); a typical use is: 161 // bp = bread(...) 162 // modify bp->data[] 163 // log_write(bp) 164 // brelse(bp) 165 void 166 log_write(struct buf *b) 167 { 168 int i; 169 170 if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) 171 panic("too big a transaction"); 172 if (!log.intrans) 173 panic("write outside of trans"); 174 175 // cprintf("log_write: %d %d\n", b->sector, log.lh.n); 176 177 for (i = 0; i < log.lh.n; i++) { 178 if (log.lh.sector[i] == b->sector) // log absorbtion? 179 break; 180 } 181 log.lh.sector[i] = b->sector; 182 struct buf *lbuf = bread(b->dev, log.start+i+1); 183 memmove(lbuf->data, b->data, BSIZE); 184 bwrite(lbuf); 185 brelse(lbuf); 186 if (i == log.lh.n) 187 log.lh.n++; 188 } 189