⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cache.c

📁 这是一个同样来自贝尔实验室的和UNIX有着渊源的操作系统, 其简洁的设计和实现易于我们学习和理解
💻 C
📖 第 1 页 / 共 3 页
字号:
	}	/* 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 + -