xref: /xv6-public/log.c (revision e25b74ca)
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