[9] | 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 | }
|
---|