| #include "stdinc.h" |
| #include "vac.h" |
| #include "dat.h" |
| #include "fns.h" |
| |
| typedef struct Label Label; |
| |
| enum { |
| BadHeap = ~0, |
| }; |
| |
| /* |
| * the plan is to store data to the cache in c->size blocks |
| * with the block zero extended to fill it out. When writing to |
| * venti, the block will be zero truncated. The walker will also check |
| * that the block fits within psize or dsize as the case may be. |
| */ |
| |
| struct Cache |
| { |
| VtLock *lk; |
| VtSession *z; |
| u32int now; /* ticks for usage timestamps */ |
| int size; /* max. size of any block; allocated to each block */ |
| Lump **heads; /* hash table for finding address */ |
| int nheap; /* number of available victims */ |
| Lump **heap; /* heap for locating victims */ |
| long nblocks; /* number of blocks allocated */ |
| Lump *blocks; /* array of block descriptors */ |
| u8int *mem; /* memory for all block descriptors */ |
| Lump *free; /* free list of lumps */ |
| |
| long hashSize; |
| }; |
| |
| /* |
| * the tag for a block is hash(index, parent tag) |
| */ |
| |
| struct Label { |
| uchar gen[4]; |
| uchar state; |
| uchar type; /* top bit indicates it is part of a directory */ |
| uchar tag[4]; /* tag of file it is in */ |
| }; |
| |
| |
| static char ENoDir[] = "directory entry is not allocated"; |
| |
| static void fixHeap(int si, Lump *b); |
| static int upHeap(int i, Lump *b); |
| static int downHeap(int i, Lump *b); |
| static char *lumpState(int); |
| static void lumpSetState(Lump *u, int state); |
| |
| Cache * |
| cacheAlloc(VtSession *z, int blockSize, long nblocks) |
| { |
| int i; |
| Cache *c; |
| Lump *b; |
| |
| c = vtMemAllocZ(sizeof(Cache)); |
| |
| c->lk = vtLockAlloc(); |
| c->z = z; |
| c->size = blockSize; |
| c->nblocks = nblocks; |
| c->hashSize = nblocks; |
| c->heads = vtMemAllocZ(c->hashSize*sizeof(Lump*)); |
| c->heap = vtMemAllocZ(nblocks*sizeof(Lump*)); |
| c->blocks = vtMemAllocZ(nblocks*sizeof(Lump)); |
| c->mem = vtMemAllocZ(nblocks * blockSize); |
| for(i = 0; i < nblocks; i++){ |
| b = &c->blocks[i]; |
| b->lk = vtLockAlloc(); |
| b->c = c; |
| b->data = &c->mem[i * blockSize]; |
| b->addr = i+1; |
| b->state = LumpFree; |
| b->heap = BadHeap; |
| b->next = c->free; |
| c->free = b; |
| } |
| c->nheap = 0; |
| |
| return c; |
| } |
| |
| long |
| cacheGetSize(Cache *c) |
| { |
| return c->nblocks; |
| } |
| |
| int |
| cacheGetBlockSize(Cache *c) |
| { |
| return c->size; |
| } |
| |
| int |
| cacheSetSize(Cache *c, long nblocks) |
| { |
| USED(c); |
| USED(nblocks); |
| return 0; |
| } |
| |
| void |
| cacheFree(Cache *c) |
| { |
| int i; |
| |
| for(i = 0; i < c->nblocks; i++){ |
| assert(c->blocks[i].ref == 0); |
| vtLockFree(c->blocks[i].lk); |
| } |
| vtMemFree(c->heads); |
| vtMemFree(c->blocks); |
| vtMemFree(c->mem); |
| vtMemFree(c); |
| } |
| |
| static u32int |
| hash(Cache *c, uchar score[VtScoreSize], int type) |
| { |
| u32int h; |
| uchar *p = score + VtScoreSize-4; |
| |
| h = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; |
| h += type; |
| return h % c->hashSize; |
| } |
| |
| static void |
| findLump(Cache *c, Lump *bb) |
| { |
| Lump *b, *last; |
| int h; |
| |
| last = nil; |
| h = hash(c, bb->score, bb->type); |
| for(b = c->heads[h]; b != nil; b = b->next){ |
| if(last != b->prev) |
| vtFatal("bad prev link"); |
| if(b == bb) |
| return; |
| last = b; |
| } |
| vtFatal("block missing from hash table"); |
| } |
| |
| void |
| cacheCheck(Cache *c) |
| { |
| u32int size, now; |
| int i, k, refed, free; |
| static uchar zero[VtScoreSize]; |
| Lump *p; |
| |
| size = c->size; |
| now = c->now; |
| |
| free = 0; |
| for(p=c->free; p; p=p->next) |
| free++; |
| for(i = 0; i < c->nheap; i++){ |
| if(c->heap[i]->heap != i) |
| vtFatal("mis-heaped at %d: %d", i, c->heap[i]->heap); |
| if(i > 0 && c->heap[(i - 1) >> 1]->used2 - now > c->heap[i]->used2 - now) |
| vtFatal("bad heap ordering"); |
| k = (i << 1) + 1; |
| if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now) |
| vtFatal("bad heap ordering"); |
| k++; |
| if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now) |
| vtFatal("bad heap ordering"); |
| } |
| |
| refed = 0; |
| for(i = 0; i < c->nblocks; i++){ |
| if(c->blocks[i].data != &c->mem[i * size]) |
| vtFatal("mis-blocked at %d", i); |
| if(c->blocks[i].ref && c->blocks[i].heap == BadHeap){ |
| refed++; |
| } |
| if(memcmp(zero, c->blocks[i].score, VtScoreSize)) |
| findLump(c, &c->blocks[i]); |
| } |
| if(refed > 0)fprint(2, "cacheCheck: nheap %d refed %d free %d\n", c->nheap, refed, free); |
| assert(c->nheap + refed + free == c->nblocks); |
| refed = 0; |
| for(i = 0; i < c->nblocks; i++){ |
| if(c->blocks[i].ref) { |
| if(1)fprint(2, "%d %V %d %s\n", c->blocks[i].type, c->blocks[i].score, c->blocks[i].ref, lumpState(c->blocks[i].state)); |
| refed++; |
| } |
| } |
| if(refed > 0)fprint(2, "cacheCheck: in used %d\n", refed); |
| } |
| |
| /* |
| * delete an arbitrary block from the heap |
| */ |
| static void |
| delHeap(Lump *db) |
| { |
| fixHeap(db->heap, db->c->heap[--db->c->nheap]); |
| db->heap = BadHeap; |
| } |
| |
| static void |
| fixHeap(int si, Lump *b) |
| { |
| int i; |
| |
| i = upHeap(si, b); |
| if(i == si) |
| downHeap(i, b); |
| } |
| |
| static int |
| upHeap(int i, Lump *b) |
| { |
| Lump *bb; |
| u32int now; |
| int p; |
| Cache *c; |
| |
| c = b->c; |
| now = c->now; |
| for(; i != 0; i = p){ |
| p = (i - 1) >> 1; |
| bb = c->heap[p]; |
| if(b->used2 - now >= bb->used2 - now) |
| break; |
| c->heap[i] = bb; |
| bb->heap = i; |
| } |
| c->heap[i] = b; |
| b->heap = i; |
| |
| return i; |
| } |
| |
| static int |
| downHeap(int i, Lump *b) |
| { |
| Lump *bb; |
| u32int now; |
| int k; |
| Cache *c; |
| |
| c = b->c; |
| now = c->now; |
| for(; ; i = k){ |
| k = (i << 1) + 1; |
| if(k >= c->nheap) |
| break; |
| if(k + 1 < c->nheap && c->heap[k]->used2 - now > c->heap[k + 1]->used2 - now) |
| k++; |
| bb = c->heap[k]; |
| if(b->used2 - now <= bb->used2 - now) |
| break; |
| c->heap[i] = bb; |
| bb->heap = i; |
| } |
| c->heap[i] = b; |
| b->heap = i; |
| return i; |
| } |
| |
| |
| /* called with c->lk held */ |
| Lump * |
| cacheBumpLump(Cache *c) |
| { |
| Lump *b; |
| |
| /* |
| * missed: locate the block with the oldest second to last use. |
| * remove it from the heap, and fix up the heap. |
| */ |
| if(c->free) { |
| b = c->free; |
| c->free = b->next; |
| } else { |
| for(;;){ |
| if(c->nheap == 0) { |
| cacheCheck(c); |
| assert(0); |
| return nil; |
| } |
| b = c->heap[0]; |
| delHeap(b); |
| if(b->ref == 0) |
| break; |
| } |
| |
| /* |
| * unchain the block from hash chain |
| */ |
| if(b->prev == nil) |
| c->heads[hash(c, b->score, b->type)] = b->next; |
| else |
| b->prev->next = b->next; |
| if(b->next != nil) |
| b->next->prev = b->prev; |
| |
| } |
| |
| /* |
| * the new block has no last use, so assume it happens sometime in the middle |
| */ |
| b->used = (b->used2 + c->now) / 2; |
| b->asize = 0; |
| |
| return b; |
| } |
| |
| Lump * |
| cacheAllocLump(Cache *c, int type, int size, int dir) |
| { |
| Lump *b; |
| ulong h; |
| |
| assert(size <= c->size); |
| |
| again: |
| vtLock(c->lk); |
| b = cacheBumpLump(c); |
| if(b == nil) { |
| vtUnlock(c->lk); |
| fprint(2, "cache is full\n"); |
| /* XXX should be better */ |
| sleep(100); |
| goto again; |
| } |
| |
| vtLock(b->lk); |
| |
| assert(b->ref == 0); |
| b->ref++; |
| b->used2 = b->used; |
| b->used = c->now++; |
| |
| /* convert addr into score */ |
| memset(b->score, 0, VtScoreSize-4); |
| b->score[VtScoreSize-4] = b->addr>>24; |
| b->score[VtScoreSize-3] = b->addr>>16; |
| b->score[VtScoreSize-2] = b->addr>>8; |
| b->score[VtScoreSize-1] = b->addr; |
| |
| b->dir = dir; |
| b->type = type; |
| b->gen = 0; |
| b->asize = size; |
| b->state = LumpFree; |
| |
| h = hash(c, b->score, b->type); |
| |
| /* chain onto correct hash */ |
| b->next = c->heads[h]; |
| c->heads[h] = b; |
| if(b->next != nil) |
| b->next->prev = b; |
| b->prev = nil; |
| |
| vtUnlock(c->lk); |
| |
| vtZeroExtend(type, b->data, 0, size); |
| lumpSetState(b, LumpActive); |
| |
| return b; |
| } |
| |
| int |
| scoreIsLocal(uchar score[VtScoreSize]) |
| { |
| static uchar zero[VtScoreSize]; |
| |
| return memcmp(score, zero, VtScoreSize-4) == 0; |
| } |
| |
| Lump * |
| cacheGetLump(Cache *c, uchar score[VtScoreSize], int type, int size) |
| { |
| Lump *b; |
| ulong h; |
| int n; |
| static uchar zero[VtScoreSize]; |
| |
| assert(size <= c->size); |
| |
| h = hash(c, score, type); |
| |
| again: |
| /* |
| * look for the block in the cache |
| */ |
| vtLock(c->lk); |
| for(b = c->heads[h]; b != nil; b = b->next){ |
| if(memcmp(b->score, score, VtScoreSize) == 0 && b->type == type) |
| goto found; |
| } |
| |
| /* should not be looking for a temp block */ |
| if(scoreIsLocal(score)) { |
| if(memcmp(score, zero, VtScoreSize) == 0) |
| vtSetError("looking for zero score"); |
| else |
| vtSetError("missing local block"); |
| vtUnlock(c->lk); |
| return nil; |
| } |
| |
| b = cacheBumpLump(c); |
| if(b == nil) { |
| vtUnlock(c->lk); |
| sleep(100); |
| goto again; |
| } |
| |
| /* chain onto correct hash */ |
| b->next = c->heads[h]; |
| c->heads[h] = b; |
| if(b->next != nil) |
| b->next->prev = b; |
| b->prev = nil; |
| |
| memmove(b->score, score, VtScoreSize); |
| b->type = type; |
| b->state = LumpFree; |
| |
| found: |
| b->ref++; |
| b->used2 = b->used; |
| b->used = c->now++; |
| if(b->heap != BadHeap) |
| fixHeap(b->heap, b); |
| |
| vtUnlock(c->lk); |
| |
| vtLock(b->lk); |
| if(b->state != LumpFree) |
| return b; |
| |
| n = vtRead(c->z, score, type, b->data, size); |
| if(n < 0) { |
| lumpDecRef(b, 1); |
| return nil; |
| } |
| if(!vtSha1Check(score, b->data, n)) { |
| vtSetError("vtSha1Check failed"); |
| lumpDecRef(b, 1); |
| return nil; |
| } |
| vtZeroExtend(type, b->data, n, size); |
| b->asize = size; |
| lumpSetState(b, LumpVenti); |
| |
| return b; |
| } |
| |
| static char * |
| lumpState(int state) |
| { |
| switch(state) { |
| default: |
| return "Unknown!!"; |
| case LumpFree: |
| return "Free"; |
| case LumpActive: |
| return "Active"; |
| case LumpSnap: |
| return "Snap"; |
| case LumpZombie: |
| return "Zombie"; |
| case LumpVenti: |
| return "Venti"; |
| } |
| } |
| |
| static void |
| lumpSetState(Lump *u, int state) |
| { |
| // if(u->state != LumpFree) |
| // fprint(2, "%V: %s -> %s\n", u->score, lumpState(u->state), lumpState(state)); |
| u->state = state; |
| } |
| |
| int |
| lumpGetScore(Lump *u, int offset, uchar score[VtScoreSize]) |
| { |
| uchar *sp; |
| VtRoot root; |
| VtEntry dir; |
| |
| vtLock(u->lk); |
| |
| switch(u->type) { |
| default: |
| vtSetError("bad type"); |
| goto Err; |
| case VtPointerType0: |
| case VtPointerType1: |
| case VtPointerType2: |
| case VtPointerType3: |
| case VtPointerType4: |
| case VtPointerType5: |
| case VtPointerType6: |
| if((offset+1)*VtScoreSize > u->asize) |
| sp = nil; |
| else |
| sp = u->data + offset*VtScoreSize; |
| break; |
| case VtRootType: |
| if(u->asize < VtRootSize) { |
| vtSetError("runt root block"); |
| goto Err; |
| } |
| if(!vtRootUnpack(&root, u->data)) |
| goto Err; |
| sp = root.score; |
| break; |
| case VtDirType: |
| if((offset+1)*VtEntrySize > u->asize) { |
| vtSetError(ENoDir); |
| goto Err; |
| } |
| if(!vtEntryUnpack(&dir, u->data, offset)) |
| goto Err; |
| if(!dir.flags & VtEntryActive) { |
| vtSetError(ENoDir); |
| goto Err; |
| } |
| sp = dir.score; |
| break; |
| } |
| |
| if(sp == nil) |
| memmove(score, vtZeroScore, VtScoreSize); |
| else |
| memmove(score, sp, VtScoreSize); |
| |
| vtUnlock(u->lk); |
| return !scoreIsLocal(score); |
| Err: |
| vtUnlock(u->lk); |
| return 0; |
| } |
| |
| Lump * |
| lumpWalk(Lump *u, int offset, int type, int size, int readOnly, int lock) |
| { |
| Lump *v, *vv; |
| Cache *c; |
| uchar score[VtScoreSize], *sp; |
| VtRoot root; |
| VtEntry dir; |
| int split, isdir; |
| |
| c = u->c; |
| vtLock(u->lk); |
| |
| Again: |
| v = nil; |
| vv = nil; |
| |
| isdir = u->dir; |
| switch(u->type) { |
| default: |
| vtSetError("bad type"); |
| goto Err; |
| case VtPointerType0: |
| case VtPointerType1: |
| case VtPointerType2: |
| case VtPointerType3: |
| case VtPointerType4: |
| case VtPointerType5: |
| case VtPointerType6: |
| if((offset+1)*VtScoreSize > u->asize) |
| sp = nil; |
| else |
| sp = u->data + offset*VtScoreSize; |
| break; |
| case VtRootType: |
| if(u->asize < VtRootSize) { |
| vtSetError("runt root block"); |
| goto Err; |
| } |
| if(!vtRootUnpack(&root, u->data)) |
| goto Err; |
| sp = root.score; |
| break; |
| case VtDirType: |
| if((offset+1)*VtEntrySize > u->asize) { |
| vtSetError(ENoDir); |
| goto Err; |
| } |
| if(!vtEntryUnpack(&dir, u->data, offset)) |
| goto Err; |
| if(!(dir.flags & VtEntryActive)) { |
| vtSetError(ENoDir); |
| goto Err; |
| } |
| isdir = (dir.flags & VtEntryDir) != 0; |
| // sp = dir.score; |
| sp = u->data + offset*VtEntrySize + 20; |
| break; |
| } |
| |
| if(sp == nil) |
| memmove(score, vtZeroScore, VtScoreSize); |
| else |
| memmove(score, sp, VtScoreSize); |
| |
| vtUnlock(u->lk); |
| |
| |
| if(0)fprint(2, "lumpWalk: %V:%s %d:%d-> %V:%d\n", u->score, lumpState(u->state), u->type, offset, score, type); |
| v = cacheGetLump(c, score, type, size); |
| if(v == nil) |
| return nil; |
| |
| split = 1; |
| if(readOnly) |
| split = 0; |
| |
| switch(v->state) { |
| default: |
| assert(0); |
| case LumpFree: |
| fprint(2, "block is free %V!\n", v->score); |
| vtSetError("phase error"); |
| goto Err2; |
| case LumpActive: |
| if(v->gen < u->gen) { |
| print("LumpActive gen\n"); |
| lumpSetState(v, LumpSnap); |
| v->gen = u->gen; |
| } else |
| split = 0; |
| break; |
| case LumpSnap: |
| case LumpVenti: |
| break; |
| } |
| |
| /* easy case */ |
| if(!split) { |
| if(!lock) |
| vtUnlock(v->lk); |
| return v; |
| } |
| |
| if(sp == nil) { |
| vtSetError("bad offset"); |
| goto Err2; |
| } |
| |
| vv = cacheAllocLump(c, v->type, size, isdir); |
| /* vv is locked */ |
| vv->gen = u->gen; |
| memmove(vv->data, v->data, v->asize); |
| if(0)fprint(2, "split %V into %V\n", v->score, vv->score); |
| |
| lumpDecRef(v, 1); |
| v = nil; |
| |
| vtLock(u->lk); |
| if(u->state != LumpActive) { |
| vtSetError("bad parent state: can not happen"); |
| goto Err; |
| } |
| |
| /* check that nothing changed underfoot */ |
| if(memcmp(sp, score, VtScoreSize) != 0) { |
| lumpDecRef(vv, 1); |
| fprint(2, "lumpWalk: parent changed under foot\n"); |
| goto Again; |
| } |
| |
| /* XXX - hold Active blocks up - will go eventually */ |
| lumpIncRef(vv); |
| |
| /* change the parent */ |
| memmove(sp, vv->score, VtScoreSize); |
| |
| vtUnlock(u->lk); |
| |
| if(!lock) |
| vtUnlock(vv->lk); |
| return vv; |
| Err: |
| vtUnlock(u->lk); |
| lumpDecRef(v, 0); |
| lumpDecRef(vv, 1); |
| return nil; |
| Err2: |
| lumpDecRef(v, 1); |
| return nil; |
| |
| } |
| |
| void |
| lumpFreeEntry(Lump *u, int entry) |
| { |
| uchar score[VtScoreSize]; |
| int type; |
| ulong gen; |
| VtEntry dir; |
| Cache *c; |
| |
| c = u->c; |
| vtLock(u->lk); |
| if(u->state == LumpVenti) |
| goto Exit; |
| |
| switch(u->type) { |
| default: |
| fprint(2, "freeing bad lump type: %d\n", u->type); |
| return; |
| case VtPointerType0: |
| if((entry+1)*VtScoreSize > u->asize) |
| goto Exit; |
| memmove(score, u->data + entry*VtScoreSize, VtScoreSize); |
| memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize); |
| type = u->dir?VtDirType:VtDataType; |
| break; |
| case VtPointerType1: |
| case VtPointerType2: |
| case VtPointerType3: |
| case VtPointerType4: |
| case VtPointerType5: |
| case VtPointerType6: |
| if((entry+1)*VtScoreSize > u->asize) |
| goto Exit; |
| memmove(score, u->data + entry*VtScoreSize, VtScoreSize); |
| memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize); |
| type = u->type-1; |
| break; |
| case VtDirType: |
| if((entry+1)*VtEntrySize > u->asize) |
| goto Exit; |
| if(!vtEntryUnpack(&dir, u->data, entry)) |
| goto Exit; |
| if(!dir.flags & VtEntryActive) |
| goto Exit; |
| gen = dir.gen; |
| if(gen != ~0) |
| gen++; |
| if(dir.depth == 0) |
| type = (dir.flags&VtEntryDir)?VtDirType:VtDataType; |
| else |
| type = VtPointerType0 + dir.depth - 1; |
| memmove(score, dir.score, VtScoreSize); |
| memset(&dir, 0, sizeof(dir)); |
| dir.gen = gen; |
| vtEntryPack(&dir, u->data, entry); |
| break; |
| case VtDataType: |
| type = VtErrType; |
| break; |
| } |
| vtUnlock(u->lk); |
| if(type == VtErrType || !scoreIsLocal(score)) |
| return; |
| |
| u = cacheGetLump(c, score, type, c->size); |
| if(u == nil) |
| return; |
| lumpDecRef(u, 1); |
| /* XXX remove extra reference */ |
| lumpDecRef(u, 0); |
| return; |
| Exit: |
| vtUnlock(u->lk); |
| return; |
| |
| } |
| |
| void |
| lumpCleanup(Lump *u) |
| { |
| int i, n; |
| |
| switch(u->type) { |
| default: |
| return; |
| case VtPointerType0: |
| case VtPointerType1: |
| case VtPointerType2: |
| case VtPointerType3: |
| case VtPointerType4: |
| case VtPointerType5: |
| case VtPointerType6: |
| n = u->asize/VtScoreSize; |
| break; |
| case VtDirType: |
| n = u->asize/VtEntrySize; |
| break; |
| } |
| |
| for(i=0; i<n; i++) |
| lumpFreeEntry(u, i); |
| } |
| |
| |
| void |
| lumpDecRef(Lump *b, int unlock) |
| { |
| int i; |
| Cache *c; |
| |
| if(b == nil) |
| return; |
| |
| if(unlock) |
| vtUnlock(b->lk); |
| |
| c = b->c; |
| vtLock(c->lk); |
| if(--b->ref > 0) { |
| vtUnlock(c->lk); |
| return; |
| } |
| assert(b->ref == 0); |
| |
| switch(b->state) { |
| default: |
| fprint(2, "bad state: %s\n", lumpState(b->state)); |
| assert(0); |
| case LumpActive: |
| /* hack - but will do for now */ |
| b->ref++; |
| vtUnlock(c->lk); |
| lumpCleanup(b); |
| vtLock(c->lk); |
| b->ref--; |
| lumpSetState(b, LumpFree); |
| break; |
| case LumpZombie: |
| lumpSetState(b, LumpFree); |
| break; |
| case LumpFree: |
| case LumpVenti: |
| break; |
| } |
| |
| /* |
| * reinsert in the free heap |
| */ |
| if(b->heap == BadHeap) { |
| i = upHeap(c->nheap++, b); |
| c->heap[i] = b; |
| b->heap = i; |
| } |
| |
| vtUnlock(c->lk); |
| } |
| |
| Lump * |
| lumpIncRef(Lump *b) |
| { |
| Cache *c; |
| |
| c = b->c; |
| |
| vtLock(c->lk); |
| assert(b->ref > 0); |
| b->ref++; |
| vtUnlock(c->lk); |
| return b; |
| } |