0
|
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 initlog(void)
|
|
54 {
|
|
55 struct superblock sb;
|
|
56
|
|
57 if (sizeof(struct logheader) >= BSIZE) {
|
|
58 panic("initlog: too big logheader");
|
|
59 }
|
|
60
|
|
61 initlock(&log.lock, "log");
|
|
62 readsb(ROOTDEV, &sb);
|
|
63 log.start = sb.size - sb.nlog;
|
|
64 log.size = sb.nlog;
|
|
65 log.dev = ROOTDEV;
|
|
66 recover_from_log();
|
|
67 }
|
|
68
|
|
69 // Copy committed blocks from log to their home location
|
|
70 static void install_trans(void)
|
|
71 {
|
|
72 int tail;
|
|
73 struct buf *lbuf;
|
|
74 struct buf *dbuf;
|
|
75
|
|
76 for (tail = 0; tail < log.lh.n; tail++) {
|
|
77 lbuf = bread(log.dev, log.start+tail+1); // read log block
|
|
78 dbuf = bread(log.dev, log.lh.sector[tail]); // read dst
|
|
79
|
|
80 memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
|
|
81
|
|
82 bwrite(dbuf); // write 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 read_head(void)
|
|
90 {
|
|
91 struct buf *buf;
|
|
92 struct logheader *lh;
|
|
93 int i;
|
|
94
|
|
95 buf = bread(log.dev, log.start);
|
|
96 lh = (struct logheader *) (buf->data);
|
|
97 log.lh.n = lh->n;
|
|
98
|
|
99 for (i = 0; i < log.lh.n; i++) {
|
|
100 log.lh.sector[i] = lh->sector[i];
|
|
101 }
|
|
102
|
|
103 brelse(buf);
|
|
104 }
|
|
105
|
|
106 // Write in-memory log header to disk.
|
|
107 // This is the true point at which the
|
|
108 // current transaction commits.
|
|
109 static void write_head(void)
|
|
110 {
|
|
111 struct buf *buf;
|
|
112 struct logheader *hb;
|
|
113 int i;
|
|
114
|
|
115 buf = bread(log.dev, log.start);
|
|
116 hb = (struct logheader *) (buf->data);
|
|
117
|
|
118 hb->n = log.lh.n;
|
|
119
|
|
120 for (i = 0; i < log.lh.n; i++) {
|
|
121 hb->sector[i] = log.lh.sector[i];
|
|
122 }
|
|
123
|
|
124 bwrite(buf);
|
|
125 brelse(buf);
|
|
126 }
|
|
127
|
|
128 static void recover_from_log(void)
|
|
129 {
|
|
130 read_head();
|
|
131 install_trans(); // if committed, copy from log to disk
|
|
132 log.lh.n = 0;
|
|
133 write_head(); // clear the log
|
|
134 }
|
|
135
|
|
136 void begin_trans(void)
|
|
137 {
|
|
138 acquire(&log.lock);
|
|
139
|
|
140 while (log.busy) {
|
|
141 sleep(&log, &log.lock);
|
|
142 }
|
|
143
|
|
144 log.busy = 1;
|
|
145 release(&log.lock);
|
|
146 }
|
|
147
|
|
148 void commit_trans(void)
|
|
149 {
|
|
150 if (log.lh.n > 0) {
|
|
151 write_head(); // Write header to disk -- the real commit
|
|
152 install_trans(); // Now install writes to home locations
|
|
153 log.lh.n = 0;
|
|
154 write_head(); // Erase the transaction from the log
|
|
155 }
|
|
156
|
|
157 acquire(&log.lock);
|
|
158 log.busy = 0;
|
|
159 wakeup(&log);
|
|
160 release(&log.lock);
|
|
161 }
|
|
162
|
|
163 // Caller has modified b->data and is done with the buffer.
|
|
164 // Append the block to the log and record the block number,
|
|
165 // but don't write the log header (which would commit the write).
|
|
166 // log_write() replaces bwrite(); a typical use is:
|
|
167 // bp = bread(...)
|
|
168 // modify bp->data[]
|
|
169 // log_write(bp)
|
|
170 // brelse(bp)
|
|
171 void log_write(struct buf *b)
|
|
172 {
|
|
173 struct buf *lbuf;
|
|
174 int i;
|
|
175
|
|
176 if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) {
|
|
177 panic("too big a transaction");
|
|
178 }
|
|
179
|
|
180 if (!log.busy) {
|
|
181 panic("write outside of trans");
|
|
182 }
|
|
183
|
|
184 for (i = 0; i < log.lh.n; i++) {
|
|
185 if (log.lh.sector[i] == b->sector) { // log absorbtion?
|
|
186 break;
|
|
187 }
|
|
188 }
|
|
189
|
|
190 log.lh.sector[i] = b->sector;
|
|
191 lbuf = bread(b->dev, log.start+i+1);
|
|
192
|
|
193 memmove(lbuf->data, b->data, BSIZE);
|
|
194 bwrite(lbuf);
|
|
195 brelse(lbuf);
|
|
196
|
|
197 if (i == log.lh.n) {
|
|
198 log.lh.n++;
|
|
199 }
|
|
200
|
|
201 b->flags |= B_DIRTY; // XXX prevent eviction
|
|
202 }
|
|
203
|
|
204 //PAGEBREAK!
|
|
205 // Blank page.
|
|
206
|