1 | /* The file system maintains a buffer cache to reduce the number of disk
|
---|
2 | * accesses needed. Whenever a read or write to the disk is done, a check is
|
---|
3 | * first made to see if the block is in the cache. This file manages the
|
---|
4 | * cache.
|
---|
5 | *
|
---|
6 | * The entry points into this file are:
|
---|
7 | * get_block: request to fetch a block for reading or writing from cache
|
---|
8 | * put_block: return a block previously requested with get_block
|
---|
9 | * alloc_zone: allocate a new zone (to increase the length of a file)
|
---|
10 | * free_zone: release a zone (when a file is removed)
|
---|
11 | * invalidate: remove all the cache blocks on some device
|
---|
12 | *
|
---|
13 | * Private functions:
|
---|
14 | * rw_block: read or write a block from the disk itself
|
---|
15 | */
|
---|
16 |
|
---|
17 | #include "fs.h"
|
---|
18 | #include <minix/com.h>
|
---|
19 | #include "buf.h"
|
---|
20 | #include "file.h"
|
---|
21 | #include "fproc.h"
|
---|
22 | #include "super.h"
|
---|
23 |
|
---|
24 | FORWARD _PROTOTYPE( void rm_lru, (struct buf *bp) );
|
---|
25 | FORWARD _PROTOTYPE( int rw_block, (struct buf *, int) );
|
---|
26 |
|
---|
27 | /*===========================================================================*
|
---|
28 | * get_block *
|
---|
29 | *===========================================================================*/
|
---|
30 | PUBLIC struct buf *get_block(dev, block, only_search)
|
---|
31 | register dev_t dev; /* on which device is the block? */
|
---|
32 | register block_t block; /* which block is wanted? */
|
---|
33 | int only_search; /* if NO_READ, don't read, else act normal */
|
---|
34 | {
|
---|
35 | /* Check to see if the requested block is in the block cache. If so, return
|
---|
36 | * a pointer to it. If not, evict some other block and fetch it (unless
|
---|
37 | * 'only_search' is 1). All the blocks in the cache that are not in use
|
---|
38 | * are linked together in a chain, with 'front' pointing to the least recently
|
---|
39 | * used block and 'rear' to the most recently used block. If 'only_search' is
|
---|
40 | * 1, the block being requested will be overwritten in its entirety, so it is
|
---|
41 | * only necessary to see if it is in the cache; if it is not, any free buffer
|
---|
42 | * will do. It is not necessary to actually read the block in from disk.
|
---|
43 | * If 'only_search' is PREFETCH, the block need not be read from the disk,
|
---|
44 | * and the device is not to be marked on the block, so callers can tell if
|
---|
45 | * the block returned is valid.
|
---|
46 | * In addition to the LRU chain, there is also a hash chain to link together
|
---|
47 | * blocks whose block numbers end with the same bit strings, for fast lookup.
|
---|
48 | */
|
---|
49 |
|
---|
50 | int b;
|
---|
51 | register struct buf *bp, *prev_ptr;
|
---|
52 |
|
---|
53 | /* Search the hash chain for (dev, block). Do_read() can use
|
---|
54 | * get_block(NO_DEV ...) to get an unnamed block to fill with zeros when
|
---|
55 | * someone wants to read from a hole in a file, in which case this search
|
---|
56 | * is skipped
|
---|
57 | */
|
---|
58 | if (dev != NO_DEV) {
|
---|
59 | b = (int) block & HASH_MASK;
|
---|
60 | bp = buf_hash[b];
|
---|
61 | while (bp != NIL_BUF) {
|
---|
62 | if (bp->b_blocknr == block && bp->b_dev == dev) {
|
---|
63 | /* Block needed has been found. */
|
---|
64 | if (bp->b_count == 0) rm_lru(bp);
|
---|
65 | bp->b_count++; /* record that block is in use */
|
---|
66 |
|
---|
67 | return(bp);
|
---|
68 | } else {
|
---|
69 | /* This block is not the one sought. */
|
---|
70 | bp = bp->b_hash; /* move to next block on hash chain */
|
---|
71 | }
|
---|
72 | }
|
---|
73 | }
|
---|
74 |
|
---|
75 | /* Desired block is not on available chain. Take oldest block ('front'). */
|
---|
76 | if ((bp = front) == NIL_BUF) panic(__FILE__,"all buffers in use", NR_BUFS);
|
---|
77 | rm_lru(bp);
|
---|
78 |
|
---|
79 | /* Remove the block that was just taken from its hash chain. */
|
---|
80 | b = (int) bp->b_blocknr & HASH_MASK;
|
---|
81 | prev_ptr = buf_hash[b];
|
---|
82 | if (prev_ptr == bp) {
|
---|
83 | buf_hash[b] = bp->b_hash;
|
---|
84 | } else {
|
---|
85 | /* The block just taken is not on the front of its hash chain. */
|
---|
86 | while (prev_ptr->b_hash != NIL_BUF)
|
---|
87 | if (prev_ptr->b_hash == bp) {
|
---|
88 | prev_ptr->b_hash = bp->b_hash; /* found it */
|
---|
89 | break;
|
---|
90 | } else {
|
---|
91 | prev_ptr = prev_ptr->b_hash; /* keep looking */
|
---|
92 | }
|
---|
93 | }
|
---|
94 |
|
---|
95 | /* If the block taken is dirty, make it clean by writing it to the disk.
|
---|
96 | * Avoid hysteresis by flushing all other dirty blocks for the same device.
|
---|
97 | */
|
---|
98 | if (bp->b_dev != NO_DEV) {
|
---|
99 | if (bp->b_dirt == DIRTY) flushall(bp->b_dev);
|
---|
100 | #if ENABLE_CACHE2
|
---|
101 | put_block2(bp);
|
---|
102 | #endif
|
---|
103 | }
|
---|
104 |
|
---|
105 | /* Fill in block's parameters and add it to the hash chain where it goes. */
|
---|
106 | bp->b_dev = dev; /* fill in device number */
|
---|
107 | bp->b_blocknr = block; /* fill in block number */
|
---|
108 | bp->b_count++; /* record that block is being used */
|
---|
109 | b = (int) bp->b_blocknr & HASH_MASK;
|
---|
110 | bp->b_hash = buf_hash[b];
|
---|
111 | buf_hash[b] = bp; /* add to hash list */
|
---|
112 |
|
---|
113 | /* Go get the requested block unless searching or prefetching. */
|
---|
114 | if (dev != NO_DEV) {
|
---|
115 | #if ENABLE_CACHE2
|
---|
116 | if (get_block2(bp, only_search)) /* in 2nd level cache */;
|
---|
117 | else
|
---|
118 | #endif
|
---|
119 | if (only_search == PREFETCH) bp->b_dev = NO_DEV;
|
---|
120 | else
|
---|
121 | if (only_search == NORMAL) {
|
---|
122 | rw_block(bp, READING);
|
---|
123 | }
|
---|
124 | }
|
---|
125 | return(bp); /* return the newly acquired block */
|
---|
126 | }
|
---|
127 |
|
---|
128 | /*===========================================================================*
|
---|
129 | * put_block *
|
---|
130 | *===========================================================================*/
|
---|
131 | PUBLIC void put_block(bp, block_type)
|
---|
132 | register struct buf *bp; /* pointer to the buffer to be released */
|
---|
133 | int block_type; /* INODE_BLOCK, DIRECTORY_BLOCK, or whatever */
|
---|
134 | {
|
---|
135 | /* Return a block to the list of available blocks. Depending on 'block_type'
|
---|
136 | * it may be put on the front or rear of the LRU chain. Blocks that are
|
---|
137 | * expected to be needed again shortly (e.g., partially full data blocks)
|
---|
138 | * go on the rear; blocks that are unlikely to be needed again shortly
|
---|
139 | * (e.g., full data blocks) go on the front. Blocks whose loss can hurt
|
---|
140 | * the integrity of the file system (e.g., inode blocks) are written to
|
---|
141 | * disk immediately if they are dirty.
|
---|
142 | */
|
---|
143 | if (bp == NIL_BUF) return; /* it is easier to check here than in caller */
|
---|
144 |
|
---|
145 | bp->b_count--; /* there is one use fewer now */
|
---|
146 | if (bp->b_count != 0) return; /* block is still in use */
|
---|
147 |
|
---|
148 | bufs_in_use--; /* one fewer block buffers in use */
|
---|
149 |
|
---|
150 | /* Put this block back on the LRU chain. If the ONE_SHOT bit is set in
|
---|
151 | * 'block_type', the block is not likely to be needed again shortly, so put
|
---|
152 | * it on the front of the LRU chain where it will be the first one to be
|
---|
153 | * taken when a free buffer is needed later.
|
---|
154 | */
|
---|
155 | if (bp->b_dev == DEV_RAM || (block_type & ONE_SHOT)) {
|
---|
156 | /* Block probably won't be needed quickly. Put it on front of chain.
|
---|
157 | * It will be the next block to be evicted from the cache.
|
---|
158 | */
|
---|
159 | bp->b_prev = NIL_BUF;
|
---|
160 | bp->b_next = front;
|
---|
161 | if (front == NIL_BUF)
|
---|
162 | rear = bp; /* LRU chain was empty */
|
---|
163 | else
|
---|
164 | front->b_prev = bp;
|
---|
165 | front = bp;
|
---|
166 | } else {
|
---|
167 | /* Block probably will be needed quickly. Put it on rear of chain.
|
---|
168 | * It will not be evicted from the cache for a long time.
|
---|
169 | */
|
---|
170 | bp->b_prev = rear;
|
---|
171 | bp->b_next = NIL_BUF;
|
---|
172 | if (rear == NIL_BUF)
|
---|
173 | front = bp;
|
---|
174 | else
|
---|
175 | rear->b_next = bp;
|
---|
176 | rear = bp;
|
---|
177 | }
|
---|
178 |
|
---|
179 | /* Some blocks are so important (e.g., inodes, indirect blocks) that they
|
---|
180 | * should be written to the disk immediately to avoid messing up the file
|
---|
181 | * system in the event of a crash.
|
---|
182 | */
|
---|
183 | if ((block_type & WRITE_IMMED) && bp->b_dirt==DIRTY && bp->b_dev != NO_DEV) {
|
---|
184 | rw_block(bp, WRITING);
|
---|
185 | }
|
---|
186 | }
|
---|
187 |
|
---|
188 | /*===========================================================================*
|
---|
189 | * alloc_zone *
|
---|
190 | *===========================================================================*/
|
---|
191 | PUBLIC zone_t alloc_zone(dev, z)
|
---|
192 | dev_t dev; /* device where zone wanted */
|
---|
193 | zone_t z; /* try to allocate new zone near this one */
|
---|
194 | {
|
---|
195 | /* Allocate a new zone on the indicated device and return its number. */
|
---|
196 |
|
---|
197 | int major, minor;
|
---|
198 | bit_t b, bit;
|
---|
199 | struct super_block *sp;
|
---|
200 |
|
---|
201 | /* Note that the routine alloc_bit() returns 1 for the lowest possible
|
---|
202 | * zone, which corresponds to sp->s_firstdatazone. To convert a value
|
---|
203 | * between the bit number, 'b', used by alloc_bit() and the zone number, 'z',
|
---|
204 | * stored in the inode, use the formula:
|
---|
205 | * z = b + sp->s_firstdatazone - 1
|
---|
206 | * Alloc_bit() never returns 0, since this is used for NO_BIT (failure).
|
---|
207 | */
|
---|
208 | sp = get_super(dev);
|
---|
209 |
|
---|
210 | /* If z is 0, skip initial part of the map known to be fully in use. */
|
---|
211 | if (z == sp->s_firstdatazone) {
|
---|
212 | bit = sp->s_zsearch;
|
---|
213 | } else {
|
---|
214 | bit = (bit_t) z - (sp->s_firstdatazone - 1);
|
---|
215 | }
|
---|
216 | b = alloc_bit(sp, ZMAP, bit);
|
---|
217 | if (b == NO_BIT) {
|
---|
218 | err_code = ENOSPC;
|
---|
219 | major = (int) (sp->s_dev >> MAJOR) & BYTE;
|
---|
220 | minor = (int) (sp->s_dev >> MINOR) & BYTE;
|
---|
221 | printf("No space on %sdevice %d/%d\n",
|
---|
222 | sp->s_dev == root_dev ? "root " : "", major, minor);
|
---|
223 | return(NO_ZONE);
|
---|
224 | }
|
---|
225 | if (z == sp->s_firstdatazone) sp->s_zsearch = b; /* for next time */
|
---|
226 | return(sp->s_firstdatazone - 1 + (zone_t) b);
|
---|
227 | }
|
---|
228 |
|
---|
229 | /*===========================================================================*
|
---|
230 | * free_zone *
|
---|
231 | *===========================================================================*/
|
---|
232 | PUBLIC void free_zone(dev, numb)
|
---|
233 | dev_t dev; /* device where zone located */
|
---|
234 | zone_t numb; /* zone to be returned */
|
---|
235 | {
|
---|
236 | /* Return a zone. */
|
---|
237 |
|
---|
238 | register struct super_block *sp;
|
---|
239 | bit_t bit;
|
---|
240 |
|
---|
241 | /* Locate the appropriate super_block and return bit. */
|
---|
242 | sp = get_super(dev);
|
---|
243 | if (numb < sp->s_firstdatazone || numb >= sp->s_zones) return;
|
---|
244 | bit = (bit_t) (numb - (sp->s_firstdatazone - 1));
|
---|
245 | free_bit(sp, ZMAP, bit);
|
---|
246 | if (bit < sp->s_zsearch) sp->s_zsearch = bit;
|
---|
247 | }
|
---|
248 |
|
---|
249 | /*===========================================================================*
|
---|
250 | * rw_block *
|
---|
251 | *===========================================================================*/
|
---|
252 | PRIVATE int rw_block(bp, rw_flag)
|
---|
253 | register struct buf *bp; /* buffer pointer */
|
---|
254 | int rw_flag; /* READING or WRITING */
|
---|
255 | {
|
---|
256 | /* Read or write a disk block. This is the only routine in which actual disk
|
---|
257 | * I/O is invoked. If an error occurs, a message is printed here, but the error
|
---|
258 | * is not reported to the caller. If the error occurred while purging a block
|
---|
259 | * from the cache, it is not clear what the caller could do about it anyway.
|
---|
260 | */
|
---|
261 |
|
---|
262 | int r, op;
|
---|
263 | off_t pos;
|
---|
264 | dev_t dev;
|
---|
265 | int block_size;
|
---|
266 |
|
---|
267 | block_size = get_block_size(bp->b_dev);
|
---|
268 |
|
---|
269 | if ( (dev = bp->b_dev) != NO_DEV) {
|
---|
270 | pos = (off_t) bp->b_blocknr * block_size;
|
---|
271 | op = (rw_flag == READING ? DEV_READ : DEV_WRITE);
|
---|
272 | r = dev_io(op, dev, FS_PROC_NR, bp->b_data, pos, block_size, 0);
|
---|
273 | if (r != block_size) {
|
---|
274 | if (r >= 0) r = END_OF_FILE;
|
---|
275 | if (r != END_OF_FILE)
|
---|
276 | printf("Unrecoverable disk error on device %d/%d, block %ld\n",
|
---|
277 | (dev>>MAJOR)&BYTE, (dev>>MINOR)&BYTE, bp->b_blocknr);
|
---|
278 | bp->b_dev = NO_DEV; /* invalidate block */
|
---|
279 |
|
---|
280 | /* Report read errors to interested parties. */
|
---|
281 | if (rw_flag == READING) rdwt_err = r;
|
---|
282 | }
|
---|
283 | }
|
---|
284 |
|
---|
285 | bp->b_dirt = CLEAN;
|
---|
286 |
|
---|
287 | return OK;
|
---|
288 | }
|
---|
289 |
|
---|
290 | /*===========================================================================*
|
---|
291 | * invalidate *
|
---|
292 | *===========================================================================*/
|
---|
293 | PUBLIC void invalidate(device)
|
---|
294 | dev_t device; /* device whose blocks are to be purged */
|
---|
295 | {
|
---|
296 | /* Remove all the blocks belonging to some device from the cache. */
|
---|
297 |
|
---|
298 | register struct buf *bp;
|
---|
299 |
|
---|
300 | for (bp = &buf[0]; bp < &buf[NR_BUFS]; bp++)
|
---|
301 | if (bp->b_dev == device) bp->b_dev = NO_DEV;
|
---|
302 |
|
---|
303 | #if ENABLE_CACHE2
|
---|
304 | invalidate2(device);
|
---|
305 | #endif
|
---|
306 | }
|
---|
307 |
|
---|
308 | /*===========================================================================*
|
---|
309 | * flushall *
|
---|
310 | *===========================================================================*/
|
---|
311 | PUBLIC void flushall(dev)
|
---|
312 | dev_t dev; /* device to flush */
|
---|
313 | {
|
---|
314 | /* Flush all dirty blocks for one device. */
|
---|
315 |
|
---|
316 | register struct buf *bp;
|
---|
317 | static struct buf *dirty[NR_BUFS]; /* static so it isn't on stack */
|
---|
318 | int ndirty;
|
---|
319 |
|
---|
320 | for (bp = &buf[0], ndirty = 0; bp < &buf[NR_BUFS]; bp++)
|
---|
321 | if (bp->b_dirt == DIRTY && bp->b_dev == dev) dirty[ndirty++] = bp;
|
---|
322 | rw_scattered(dev, dirty, ndirty, WRITING);
|
---|
323 | }
|
---|
324 |
|
---|
325 | /*===========================================================================*
|
---|
326 | * rw_scattered *
|
---|
327 | *===========================================================================*/
|
---|
328 | PUBLIC void rw_scattered(dev, bufq, bufqsize, rw_flag)
|
---|
329 | dev_t dev; /* major-minor device number */
|
---|
330 | struct buf **bufq; /* pointer to array of buffers */
|
---|
331 | int bufqsize; /* number of buffers */
|
---|
332 | int rw_flag; /* READING or WRITING */
|
---|
333 | {
|
---|
334 | /* Read or write scattered data from a device. */
|
---|
335 |
|
---|
336 | register struct buf *bp;
|
---|
337 | int gap;
|
---|
338 | register int i;
|
---|
339 | register iovec_t *iop;
|
---|
340 | static iovec_t iovec[NR_IOREQS]; /* static so it isn't on stack */
|
---|
341 | int j, r;
|
---|
342 | int block_size;
|
---|
343 |
|
---|
344 | block_size = get_block_size(dev);
|
---|
345 |
|
---|
346 | /* (Shell) sort buffers on b_blocknr. */
|
---|
347 | gap = 1;
|
---|
348 | do
|
---|
349 | gap = 3 * gap + 1;
|
---|
350 | while (gap <= bufqsize);
|
---|
351 | while (gap != 1) {
|
---|
352 | gap /= 3;
|
---|
353 | for (j = gap; j < bufqsize; j++) {
|
---|
354 | for (i = j - gap;
|
---|
355 | i >= 0 && bufq[i]->b_blocknr > bufq[i + gap]->b_blocknr;
|
---|
356 | i -= gap) {
|
---|
357 | bp = bufq[i];
|
---|
358 | bufq[i] = bufq[i + gap];
|
---|
359 | bufq[i + gap] = bp;
|
---|
360 | }
|
---|
361 | }
|
---|
362 | }
|
---|
363 |
|
---|
364 | /* Set up I/O vector and do I/O. The result of dev_io is OK if everything
|
---|
365 | * went fine, otherwise the error code for the first failed transfer.
|
---|
366 | */
|
---|
367 | while (bufqsize > 0) {
|
---|
368 | for (j = 0, iop = iovec; j < NR_IOREQS && j < bufqsize; j++, iop++) {
|
---|
369 | bp = bufq[j];
|
---|
370 | if (bp->b_blocknr != bufq[0]->b_blocknr + j) break;
|
---|
371 | iop->iov_addr = (vir_bytes) bp->b_data;
|
---|
372 | iop->iov_size = block_size;
|
---|
373 | }
|
---|
374 | r = dev_io(rw_flag == WRITING ? DEV_SCATTER : DEV_GATHER,
|
---|
375 | dev, FS_PROC_NR, iovec,
|
---|
376 | (off_t) bufq[0]->b_blocknr * block_size, j, 0);
|
---|
377 |
|
---|
378 | /* Harvest the results. Dev_io reports the first error it may have
|
---|
379 | * encountered, but we only care if it's the first block that failed.
|
---|
380 | */
|
---|
381 | for (i = 0, iop = iovec; i < j; i++, iop++) {
|
---|
382 | bp = bufq[i];
|
---|
383 | if (iop->iov_size != 0) {
|
---|
384 | /* Transfer failed. An error? Do we care? */
|
---|
385 | if (r != OK && i == 0) {
|
---|
386 | printf(
|
---|
387 | "fs: I/O error on device %d/%d, block %lu\n",
|
---|
388 | (dev>>MAJOR)&BYTE, (dev>>MINOR)&BYTE,
|
---|
389 | bp->b_blocknr);
|
---|
390 | bp->b_dev = NO_DEV; /* invalidate block */
|
---|
391 | }
|
---|
392 | break;
|
---|
393 | }
|
---|
394 | if (rw_flag == READING) {
|
---|
395 | bp->b_dev = dev; /* validate block */
|
---|
396 | put_block(bp, PARTIAL_DATA_BLOCK);
|
---|
397 | } else {
|
---|
398 | bp->b_dirt = CLEAN;
|
---|
399 | }
|
---|
400 | }
|
---|
401 | bufq += i;
|
---|
402 | bufqsize -= i;
|
---|
403 | if (rw_flag == READING) {
|
---|
404 | /* Don't bother reading more than the device is willing to
|
---|
405 | * give at this time. Don't forget to release those extras.
|
---|
406 | */
|
---|
407 | while (bufqsize > 0) {
|
---|
408 | put_block(*bufq++, PARTIAL_DATA_BLOCK);
|
---|
409 | bufqsize--;
|
---|
410 | }
|
---|
411 | }
|
---|
412 | if (rw_flag == WRITING && i == 0) {
|
---|
413 | /* We're not making progress, this means we might keep
|
---|
414 | * looping. Buffers remain dirty if un-written. Buffers are
|
---|
415 | * lost if invalidate()d or LRU-removed while dirty. This
|
---|
416 | * is better than keeping unwritable blocks around forever..
|
---|
417 | */
|
---|
418 | break;
|
---|
419 | }
|
---|
420 | }
|
---|
421 | }
|
---|
422 |
|
---|
423 | /*===========================================================================*
|
---|
424 | * rm_lru *
|
---|
425 | *===========================================================================*/
|
---|
426 | PRIVATE void rm_lru(bp)
|
---|
427 | struct buf *bp;
|
---|
428 | {
|
---|
429 | /* Remove a block from its LRU chain. */
|
---|
430 | struct buf *next_ptr, *prev_ptr;
|
---|
431 |
|
---|
432 | bufs_in_use++;
|
---|
433 | next_ptr = bp->b_next; /* successor on LRU chain */
|
---|
434 | prev_ptr = bp->b_prev; /* predecessor on LRU chain */
|
---|
435 | if (prev_ptr != NIL_BUF)
|
---|
436 | prev_ptr->b_next = next_ptr;
|
---|
437 | else
|
---|
438 | front = next_ptr; /* this block was at front of chain */
|
---|
439 |
|
---|
440 | if (next_ptr != NIL_BUF)
|
---|
441 | next_ptr->b_prev = prev_ptr;
|
---|
442 | else
|
---|
443 | rear = prev_ptr; /* this block was at rear of chain */
|
---|
444 | }
|
---|