1 | #include "types.h" |
2 | #include "defs.h" |
3 | #include "param.h" |
4 | #include "spinlock.h" |
5 | #include "sleeplock.h" |
6 | #include "fs.h" |
7 | #include "buf.h" |
8 | |
9 | // Simple logging that allows concurrent FS system calls. |
10 | // |
11 | // A log transaction contains the updates of multiple FS system |
12 | // calls. The logging system only commits when there are |
13 | // no FS system calls active. Thus there is never |
14 | // any reasoning required about whether a commit might |
15 | // write an uncommitted system call's updates to disk. |
16 | // |
17 | // A system call should call begin_op()/end_op() to mark |
18 | // its start and end. Usually begin_op() just increments |
19 | // the count of in-progress FS system calls and returns. |
20 | // But if it thinks the log is close to running out, it |
21 | // sleeps until the last outstanding end_op() commits. |
22 | // |
23 | // The log is a physical re-do log containing disk blocks. |
24 | // The on-disk log format: |
25 | // header block, containing block #s for block A, B, C, ... |
26 | // block A |
27 | // block B |
28 | // block C |
29 | // ... |
30 | // Log appends are synchronous. |
31 | |
32 | // Contents of the header block, used for both the on-disk header block |
33 | // and to keep track in memory of logged block# before commit. |
34 | struct { |
35 | int ; |
36 | int [LOGSIZE]; |
37 | }; |
38 | |
39 | struct log { |
40 | struct spinlock lock; |
41 | int start; |
42 | int size; |
43 | int outstanding; // how many FS sys calls are executing. |
44 | int committing; // in commit(), please wait. |
45 | int dev; |
46 | struct logheader lh; |
47 | }; |
48 | struct log log; |
49 | |
50 | static void recover_from_log(void); |
51 | static void commit(); |
52 | |
53 | void |
54 | initlog(int dev) |
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(dev, &sb); |
62 | log.start = sb.logstart; |
63 | log.size = sb.nlog; |
64 | log.dev = dev; |
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.block[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.block[i] = lh->block[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->block[i] = log.lh.block[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 | // called at the start of each FS system call. |
125 | void |
126 | begin_op(void) |
127 | { |
128 | acquire(&log.lock); |
129 | while(1){ |
130 | if(log.committing){ |
131 | sleep(&log, &log.lock); |
132 | } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){ |
133 | // this op might exhaust log space; wait for commit. |
134 | sleep(&log, &log.lock); |
135 | } else { |
136 | log.outstanding += 1; |
137 | release(&log.lock); |
138 | break; |
139 | } |
140 | } |
141 | } |
142 | |
143 | // called at the end of each FS system call. |
144 | // commits if this was the last outstanding operation. |
145 | void |
146 | end_op(void) |
147 | { |
148 | int do_commit = 0; |
149 | |
150 | acquire(&log.lock); |
151 | log.outstanding -= 1; |
152 | if(log.committing) |
153 | panic("log.committing" ); |
154 | if(log.outstanding == 0){ |
155 | do_commit = 1; |
156 | log.committing = 1; |
157 | } else { |
158 | // begin_op() may be waiting for log space, |
159 | // and decrementing log.outstanding has decreased |
160 | // the amount of reserved space. |
161 | wakeup(&log); |
162 | } |
163 | release(&log.lock); |
164 | |
165 | if(do_commit){ |
166 | // call commit w/o holding locks, since not allowed |
167 | // to sleep with locks. |
168 | commit(); |
169 | acquire(&log.lock); |
170 | log.committing = 0; |
171 | wakeup(&log); |
172 | release(&log.lock); |
173 | } |
174 | } |
175 | |
176 | // Copy modified blocks from cache to log. |
177 | static void |
178 | write_log(void) |
179 | { |
180 | int tail; |
181 | |
182 | for (tail = 0; tail < log.lh.n; tail++) { |
183 | struct buf *to = bread(log.dev, log.start+tail+1); // log block |
184 | struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block |
185 | memmove(to->data, from->data, BSIZE); |
186 | bwrite(to); // write the log |
187 | brelse(from); |
188 | brelse(to); |
189 | } |
190 | } |
191 | |
192 | static void |
193 | commit() |
194 | { |
195 | if (log.lh.n > 0) { |
196 | write_log(); // Write modified blocks from cache to log |
197 | write_head(); // Write header to disk -- the real commit |
198 | install_trans(); // Now install writes to home locations |
199 | log.lh.n = 0; |
200 | write_head(); // Erase the transaction from the log |
201 | } |
202 | } |
203 | |
204 | // Caller has modified b->data and is done with the buffer. |
205 | // Record the block number and pin in the cache with B_DIRTY. |
206 | // commit()/write_log() will do the disk write. |
207 | // |
208 | // log_write() replaces bwrite(); a typical use is: |
209 | // bp = bread(...) |
210 | // modify bp->data[] |
211 | // log_write(bp) |
212 | // brelse(bp) |
213 | void |
214 | log_write(struct buf *b) |
215 | { |
216 | int i; |
217 | |
218 | if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) |
219 | panic("too big a transaction" ); |
220 | if (log.outstanding < 1) |
221 | panic("log_write outside of trans" ); |
222 | |
223 | acquire(&log.lock); |
224 | for (i = 0; i < log.lh.n; i++) { |
225 | if (log.lh.block[i] == b->blockno) // log absorbtion |
226 | break; |
227 | } |
228 | log.lh.block[i] = b->blockno; |
229 | if (i == log.lh.n) |
230 | log.lh.n++; |
231 | b->flags |= B_DIRTY; // prevent eviction |
232 | release(&log.lock); |
233 | } |
234 | |
235 | |