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.
34struct logheader {
35 int n;
36 int block[LOGSIZE];
37};
38
39struct 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};
48struct log log;
49
50static void recover_from_log(void);
51static void commit();
52
53void
54initlog(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
69static void
70install_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
85static void
86read_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.
101static void
102write_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
115static void
116recover_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.
125void
126begin_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.
145void
146end_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.
177static void
178write_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
192static void
193commit()
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)
213void
214log_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