xref: /xv6-public/log.c (revision 2e590463)
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();  // Install all transactions till head
128   log.lh.n = 0;
129   write_head();     //  Reclaim 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