📄 cache.c
字号:
} /* NOT REACHED */}/* * allocate a new on-disk block and load it into the memory cache. * BUG: if the disk is full, should we flush some of it to Venti? */static u32int lastAlloc;Block *cacheAllocBlock(Cache *c, int type, u32int tag, u32int epoch, u32int epochLow){ FreeList *fl; u32int addr; Block *b; int n, nwrap; Label lab; n = c->size / LabelSize; fl = c->fl; vtLock(fl->lk); addr = fl->last; b = cacheLocal(c, PartLabel, addr/n, OReadOnly); if(b == nil){ fprint(2, "cacheAllocBlock: xxx %R\n"); vtUnlock(fl->lk); return nil; } nwrap = 0; for(;;){ if(++addr >= fl->end){ addr = 0; if(++nwrap >= 2){ blockPut(b); fl->last = 0; vtSetError("disk is full"); fprint(2, "cacheAllocBlock: xxx1 %R\n"); vtUnlock(fl->lk); return nil; } } if(addr%n == 0){ blockPut(b); b = cacheLocal(c, PartLabel, addr/n, OReadOnly); if(b == nil){ fl->last = addr; fprint(2, "cacheAllocBlock: xxx2 %R\n"); vtUnlock(fl->lk); return nil; } } if(!labelUnpack(&lab, b->data, addr%n)) continue; if(lab.state == BsFree) goto Found; if(lab.state&BsClosed) if(lab.epochClose <= epochLow || lab.epoch==lab.epochClose) goto Found; }Found: blockPut(b); b = cacheLocal(c, PartData, addr, OOverWrite); if(b == nil){ fprint(2, "cacheAllocBlock: xxx3 %R\n"); return nil; }assert(b->iostate == BioLabel || b->iostate == BioClean); fl->last = addr; lab.type = type; lab.tag = tag; lab.state = BsAlloc; lab.epoch = epoch; lab.epochClose = ~(u32int)0; if(!blockSetLabel(b, &lab, 1)){ fprint(2, "cacheAllocBlock: xxx4 %R\n"); blockPut(b); return nil; } vtZeroExtend(vtType[type], b->data, 0, c->size);if(0)diskWrite(c->disk, b);if(0)fprint(2, "fsAlloc %ud type=%d tag = %ux\n", addr, type, tag); lastAlloc = addr; fl->nused++; vtUnlock(fl->lk); b->pc = getcallerpc(&c); return b;}intcacheDirty(Cache *c){ return c->ndirty;}voidcacheCountUsed(Cache *c, u32int epochLow, u32int *used, u32int *total, u32int *bsize){ int n; u32int addr, nused; Block *b; Label lab; FreeList *fl; fl = c->fl; n = c->size / LabelSize; *bsize = c->size; vtLock(fl->lk); if(fl->epochLow == epochLow){ *used = fl->nused; *total = fl->end; vtUnlock(fl->lk); return; } b = nil; nused = 0; for(addr=0; addr<fl->end; addr++){ if(addr%n == 0){ blockPut(b); b = cacheLocal(c, PartLabel, addr/n, OReadOnly); if(b == nil){ fprint(2, "flCountUsed: loading %ux: %R\n", addr/n); break; } } if(!labelUnpack(&lab, b->data, addr%n)) continue; if(lab.state == BsFree) continue; if(lab.state&BsClosed) if(lab.epochClose <= epochLow || lab.epoch==lab.epochClose) continue; nused++; } blockPut(b); if(addr == fl->end){ fl->nused = nused; fl->epochLow = epochLow; } *used = nused; *total = fl->end; vtUnlock(fl->lk); return;}static FreeList *flAlloc(u32int end){ FreeList *fl; fl = vtMemAllocZ(sizeof(*fl)); fl->lk = vtLockAlloc(); fl->last = 0; fl->end = end; return fl;}static voidflFree(FreeList *fl){ vtLockFree(fl->lk); vtMemFree(fl);}u32intcacheLocalSize(Cache *c, int part){ return diskSize(c->disk, part);}/* * The thread that has locked b may refer to it by * multiple names. Nlock counts the number of * references the locking thread holds. It will call * blockPut once per reference. */voidblockDupLock(Block *b){ assert(b->nlock > 0); b->nlock++;}/* * we're done with the block. * unlock it. can't use it after calling this. */voidblockPut(Block* b){ Cache *c; if(b == nil) return;if(0)fprint(2, "blockPut: %d: %d %x %d %s\n", getpid(), b->part, b->addr, c->nheap, bioStr(b->iostate)); if(b->iostate == BioDirty) bwatchDependency(b); if(--b->nlock > 0) return; /* * b->nlock should probably stay at zero while * the block is unlocked, but diskThread and vtSleep * conspire to assume that they can just vtLock(b->lk); blockPut(b), * so we have to keep b->nlock set to 1 even * when the block is unlocked. */ assert(b->nlock == 0); b->nlock = 1;// b->pc = 0; bwatchUnlock(b); vtUnlock(b->lk); c = b->c; vtLock(c->lk); if(--b->ref > 0){ vtUnlock(c->lk); return; } assert(b->ref == 0); switch(b->iostate){ default: b->used = c->now++; heapIns(b); break; case BioEmpty: case BioLabel: if(c->nheap == 0) b->used = c->now++; else b->used = c->heap[0]->used; heapIns(b); break; case BioDirty: break; } vtUnlock(c->lk);}/* * set the label associated with a block. */Block*_blockSetLabel(Block *b, Label *l){ int lpb; Block *bb; u32int a; Cache *c; c = b->c; assert(b->part == PartData); assert(b->iostate == BioLabel || b->iostate == BioClean || b->iostate == BioDirty); lpb = c->size / LabelSize; a = b->addr / lpb; bb = cacheLocal(c, PartLabel, a, OReadWrite); if(bb == nil){ blockPut(b); return nil; } b->l = *l; labelPack(l, bb->data, b->addr%lpb); blockDirty(bb); return bb;}intblockSetLabel(Block *b, Label *l, int allocating){ Block *lb; Label oldl; oldl = b->l; lb = _blockSetLabel(b, l); if(lb == nil) return 0; /* * If we're allocating the block, make sure the label (bl) * goes to disk before the data block (b) itself. This is to help * the blocks that in turn depend on b. * * Suppose bx depends on (must be written out after) b. * Once we write b we'll think it's safe to write bx. * Bx can't get at b unless it has a valid label, though. * * Allocation is the only case in which having a current label * is vital because: * * - l.type is set at allocation and never changes. * - l.tag is set at allocation and never changes. * - l.state is not checked when we load blocks. * - the archiver cares deeply about l.state being * BaActive vs. BaCopied, but that's handled * by direct calls to _blockSetLabel. */ if(allocating) blockDependency(b, lb, -1, nil, nil); blockPut(lb); return 1;}/* * Record that bb must be written out before b. * If index is given, we're about to overwrite the score/e * at that index in the block. Save the old value so we * can write a safer ``old'' version of the block if pressed. */voidblockDependency(Block *b, Block *bb, int index, uchar *score, Entry *e){ BList *p; if(bb->iostate == BioClean) return; /* * Dependencies for blocks containing Entry structures * or scores must always be explained. The problem with * only explaining some of them is this. Suppose we have two * dependencies for the same field, the first explained * and the second not. We try to write the block when the first * dependency is not written but the second is. We will roll back * the first change even though the second trumps it. */ if(index == -1 && bb->part == PartData) assert(b->l.type == BtData); if(bb->iostate != BioDirty){ fprint(2, "%d:%x:%d iostate is %d in blockDependency\n", bb->part, bb->addr, bb->l.type, bb->iostate); abort(); } p = blistAlloc(bb); if(p == nil) return; assert(bb->iostate == BioDirty);if(0)fprint(2, "%d:%x:%d depends on %d:%x:%d\n", b->part, b->addr, b->l.type, bb->part, bb->addr, bb->l.type); p->part = bb->part; p->addr = bb->addr; p->type = bb->l.type; p->vers = bb->vers; p->index = index; if(p->index >= 0){ /* * This test would just be b->l.type==BtDir except * we need to exclude the super block. */ if(b->l.type == BtDir && b->part == PartData) entryPack(e, p->old.entry, 0); else memmove(p->old.score, score, VtScoreSize); } p->next = b->prior; b->prior = p;}/* * Mark an in-memory block as dirty. If there are too many * dirty blocks, start writing some out to disk. * * If there were way too many dirty blocks, we used to * try to do some flushing ourselves, but it's just too dangerous -- * it implies that the callers cannot have any of our priors locked, * but this is hard to avoid in some cases. */intblockDirty(Block *b){ Cache *c; c = b->c; assert(b->part != PartVenti); if(b->iostate == BioDirty) return 1; assert(b->iostate == BioClean); vtLock(c->dirtylk); vtLock(c->lk); b->iostate = BioDirty; c->ndirty++; if(c->ndirty > (c->maxdirty>>1)) vtWakeup(c->flush); vtUnlock(c->lk); vtUnlock(c->dirtylk); return 1;}/* * We've decided to write out b. Maybe b has some pointers to blocks * that haven't yet been written to disk. If so, construct a slightly out-of-date * copy of b that is safe to write out. (diskThread will make sure the block * remains marked as dirty.) */uchar *blockRollback(Block *b, uchar *buf){ u32int addr; BList *p; Super super; /* easy case */ if(b->prior == nil) return b->data; memmove(buf, b->data, b->c->size); for(p=b->prior; p; p=p->next){ /* * we know p->index >= 0 because blockWrite has vetted this block for us. */ assert(p->index >= 0); assert(b->part == PartSuper || (b->part == PartData && b->l.type != BtData)); if(b->part == PartSuper){ assert(p->index == 0); superUnpack(&super, buf); addr = globalToLocal(p->old.score); if(addr == NilBlock){ fprint(2, "rolling back super block: bad replacement addr %V\n", p->old.score); abort(); } super.active = addr; superPack(&super, buf); continue; } if(b->l.type == BtDir) memmove(buf+p->index*VtEntrySize, p->old.entry, VtEntrySize); else memmove(buf+p->index*VtScoreSize, p->old.score, VtScoreSize); } return buf;}/* * Try to write block b. * If b depends on other blocks: * * If the block has been written out, remove the dependency. * If the dependency is replaced by a more recent dependency, * throw it out. * If we know how to write out an old version of b that doesn't * depend on it, do that. * * Otherwise, bail. */intblockWrite(Block *b){ uchar *dmap; Cache *c; BList *p, **pp; Block *bb; int lockfail; c = b->c; if(b->iostate != BioDirty) return 1; dmap = b->dmap; memset(dmap, 0, c->ndmap); pp = &b->prior; for(p=*pp; p; p=*pp){ if(p->index >= 0){ /* more recent dependency has succeeded; this one can go */ if(dmap[p->index/8] & (1<<(p->index%8))) goto ignblock; } lockfail = 0; bb = _cacheLocalLookup(c, p->part, p->addr, p->vers, 0, &lockfail); if(bb == nil){ if(lockfail) return 0; /* block not in cache => was written already */ dmap[p->index/8] |= 1<<(p->index%8); goto ignblock; } /* * same version of block is still in cache. * * the assertion is true because the block still has version p->vers, * which means it hasn't been written out since we last saw it. */ if(bb->iostate != BioDirty){ fprint(2, "%d:%x:%d iostate is %d in blockWrite\n", bb->part, bb->addr, bb->l.type, bb->iostate); /* probably BioWriting if it happens? */ if(bb->iostate == BioClean) goto ignblock; } blockPut(bb); if(p->index < 0){ /* * We don't know how to temporarily undo * b's dependency on bb, so just don't write b yet. */ if(0) fprint(2, "blockWrite skipping %d %x %d %d; need to write %d %x %d\n", b->part, b->addr, b->vers, b->l.type, p->part, p->addr, bb->vers); return 0; } /* keep walking down the list */ pp = &p->next; continue;ignblock: *pp = p->next; blistFree(c, p); continue; } /* * DiskWrite must never be called with a double-locked block. * This call to diskWrite is okay because blockWrite is only called * from the cache flush thread, which never double-locks a block. */ diskWrite(c->disk, b); return 1;}/* * Change the I/O state of block b. * Just an assignment except for magic in * switch statement (read comments there). */voidblockSetIOState(Block *b, int iostate){ int dowakeup; Cache *c; BList *p, *q;if(0) fprint(2, "iostate part=%d addr=%x %s->%s\n", b->part, b->addr, bioStr(b->iostate), bioStr(iostate)); c = b->c; dowakeup = 0; switch(iostate){ default: abort(); case BioEmpty: assert(!b->uhead); break; case BioLabel: assert(!b->uhead); break; case BioClean: bwatchDependency(b); /* * If b->prior is set, it means a write just finished. * The prior list isn't needed anymore. */ for(p=b->prior; p; p=q){ q = p->next; blistFree(c, p); } b->prior = nil; /* * Freeing a block or just finished a write. * Move the blocks from the per-block unlink * queue to the cache unlink queue. */ if(b->iostate == BioDirty || b->iostate == BioWriting){ vtLock(c->lk); c->ndirty--; b->iostate = iostate; /* change here to keep in sync with ndirty */ b->vers = c->vers++; if(b->uhead){ /* add unlink blocks to unlink queue */ if(c->uhead == nil){ c->uhead = b->uhead; vtWakeup(c->unlink); }else c->utail->next = b->uhead; c->utail = b->utail; b->uhead = nil; } vtUnlock(c->lk); } assert(!b->uhead); dowakeup = 1; break; case BioDirty: /* * Wrote out an old version of the block (see blockRollback). * Bump a version count, leave it dirty. */ if(b->iostate == BioWriting){ vtLock(c->lk); b->vers = c->vers++; vtUnlock(c->lk); dowakeup = 1; } break; case BioReading: case BioWriting: /* * Adding block to disk queue. Bump reference count. * diskThread decs the count later by calling blockPut. * This is here because we need to lock c->lk to * manipulate the ref count. */ vtLock(c->lk); b->ref++; vtUnlock(c->lk); break; case BioReadError: case BioVentiError: /* * Oops. */ dowakeup = 1; break; } b->iostate = iostate; /* * Now that the state has changed, we can wake the waiters. */ if(dowakeup) vtWakeupAll(b->ioready);}/* * The active file system is a tree of blocks. * When we add snapshots to the mix, the entire file system * becomes a dag and thus requires a bit more care. * * The life of the file system is divided into epochs. A snapshot * ends one epoch and begins the next. Each file system block * is marked with the epoch in which it was created (b.epoch). * When the block is unlinked from the file system (closed), it is marked * with the epoch in which it was removed (b.epochClose). * Once we have discarded or archived all snapshots up to * b.epochClose, we can reclaim the block. * * If a block was created in a past epoch but is not yet closed, * it is treated as copy-on-write. Of course, in order to insert the * new pointer into the tree, the parent must be made writable, * and so on up the tree. The recursion stops because the root * block is always writable. * * If blocks are never closed, they will never be reused, and * we will run out of disk space. But marking a block as closed * requires some care about dependencies and write orderings. * * (1) If a block p points at a copy-on-write block b and we * copy b to create bb, then p must be written out after bb and * lbb (bb's label block). * * (2) We have to mark b as closed, but only after we switch * the pointer, so lb must be written out after p. In fact, we * can't even update the in-memory copy, or the cache might * mistakenly give out b for reuse before p gets written. * * CacheAllocBlock's call to blockSetLabel records a "bb after lbb" dependency. * The caller is expected to record a "p after bb" dependency * to finish (1), and also expected to call blockRemoveLink * to arrange for (2) to happen once p is written. * * Until (2) happens, some pieces of the code (e.g., the archiver) * still need to know whether a block has been copied, so we * set the BsCopied bit in the label and force that to disk *before* * the copy gets written out. */Block*blockCopy(Block *b, u32int tag, u32int ehi, u32int elo){ Block *bb, *lb; Label l; if((b->l.state&BsClosed) || b->l.epoch >= ehi) fprint(2, "blockCopy %#ux %L but fs is [%ud,%ud]\n", b->addr, &b->l, elo, ehi); bb = cacheAllocBlock(b->c, b->l.type, tag, ehi, elo); if(bb == nil){ blockPut(b); return nil; } /* * Update label so we know the block has been copied.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -