Blob


1 /*
2 * Memory-only VtBlock cache.
3 *
4 * The cached Venti blocks are in the hash chains.
5 * The cached local blocks are only in the blocks array.
6 * The free blocks are in the heap, which is supposed to
7 * be indexed by second-to-last use but actually
8 * appears to be last use.
9 */
11 #include <u.h>
12 #include <libc.h>
13 #include <venti.h>
15 int vtcachenread;
16 int vtcachencopy;
17 int vtcachenwrite;
18 int vttracelevel;
20 enum {
21 BioLocal = 1,
22 BioVenti,
23 BioReading,
24 BioWriting,
25 BioEmpty,
26 BioVentiError
27 };
28 enum {
29 BadHeap = ~0
30 };
31 struct VtCache
32 {
33 QLock lk;
34 VtConn *z;
35 u32int now; /* ticks for usage time stamps */
36 VtBlock **hash; /* hash table for finding addresses */
37 int nhash;
38 VtBlock **heap; /* heap for finding victims */
39 int nheap;
40 VtBlock *block; /* all allocated blocks */
41 int nblock;
42 int (*write)(VtConn*, uchar[VtScoreSize], uint, uchar*, int);
43 VtBlock *dead; /* blocks we don't have memory for */
44 ulong mem;
45 ulong maxmem;
46 };
48 static void cachecheck(VtCache*);
50 VtCache*
51 vtcachealloc(VtConn *z, ulong maxmem)
52 {
53 VtCache *c;
54 int i;
55 int nblock;
56 VtBlock *b;
57 ulong maxmem0;
59 maxmem0 = maxmem;
60 c = vtmallocz(sizeof(VtCache));
61 nblock = maxmem/100/(sizeof(VtBlock)+2*sizeof(VtBlock*));
62 c->z = z;
63 c->nblock = nblock;
64 c->nhash = nblock;
65 c->hash = vtmallocz(nblock*sizeof(VtBlock*));
66 c->heap = vtmallocz(nblock*sizeof(VtBlock*));
67 c->block = vtmallocz(nblock*sizeof(VtBlock));
68 c->write = vtwrite;
69 maxmem -= nblock*(sizeof(VtBlock) + 2*sizeof(VtBlock*));
70 maxmem -= sizeof(VtCache);
71 if((long)maxmem < 0)
72 sysfatal("cache size far too small: %lud", maxmem0);
73 c->mem = maxmem;
75 for(i=0; i<nblock; i++){
76 b = &c->block[i];
77 b->addr = NilBlock;
78 b->c = c;
79 b->heap = i;
80 c->heap[i] = b;
81 }
82 c->nheap = nblock;
83 cachecheck(c);
84 return c;
85 }
87 /*
88 * BUG This is here so that vbackup can override it and do some
89 * pipelining of writes. Arguably vtwrite or vtwritepacket or the
90 * cache itself should be providing this functionality.
91 */
92 void
93 vtcachesetwrite(VtCache *c, int (*write)(VtConn*, uchar[VtScoreSize], uint, uchar*, int))
94 {
95 if(write == nil)
96 write = vtwrite;
97 c->write = write;
98 }
100 void
101 vtcachefree(VtCache *c)
103 int i;
105 qlock(&c->lk);
107 cachecheck(c);
108 for(i=0; i<c->nblock; i++) {
109 assert(c->block[i].data == nil || c->block[i].ref == 0);
110 vtfree(c->block[i].data);
113 vtfree(c->hash);
114 vtfree(c->heap);
115 vtfree(c->block);
116 vtfree(c);
119 static void
120 vtcachedump(VtCache *c)
122 int i;
123 VtBlock *b;
125 for(i=0; i<c->nblock; i++){
126 b = &c->block[i];
127 print("cache block %d: type %d score %V iostate %d addr %d ref %d nlock %d\n",
128 i, b->type, b->score, b->iostate, b->addr, b->ref, b->nlock);
132 static void
133 cachecheck(VtCache *c)
135 u32int now;
136 int i, k, refed;
137 VtBlock *b;
139 now = c->now;
141 for(i = 0; i < c->nheap; i++){
142 if(c->heap[i]->heap != i)
143 sysfatal("mis-heaped at %d: %d", i, c->heap[i]->heap);
144 if(i > 0 && c->heap[(i - 1) >> 1]->used - now > c->heap[i]->used - now)
145 sysfatal("bad heap ordering");
146 k = (i << 1) + 1;
147 if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now)
148 sysfatal("bad heap ordering");
149 k++;
150 if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now)
151 sysfatal("bad heap ordering");
154 refed = 0;
155 for(i = 0; i < c->nblock; i++){
156 b = &c->block[i];
157 if(b->ref && b->heap == BadHeap)
158 refed++;
159 else if(b->addr != NilBlock)
160 refed++;
162 assert(c->nheap + refed == c->nblock);
163 refed = 0;
164 for(i = 0; i < c->nblock; i++){
165 b = &c->block[i];
166 if(b->ref){
167 refed++;
172 static int
173 upheap(int i, VtBlock *b)
175 VtBlock *bb;
176 u32int now;
177 int p;
178 VtCache *c;
180 c = b->c;
181 now = c->now;
182 for(; i != 0; i = p){
183 p = (i - 1) >> 1;
184 bb = c->heap[p];
185 if(b->used - now >= bb->used - now)
186 break;
187 c->heap[i] = bb;
188 bb->heap = i;
190 c->heap[i] = b;
191 b->heap = i;
193 return i;
196 static int
197 downheap(int i, VtBlock *b)
199 VtBlock *bb;
200 u32int now;
201 int k;
202 VtCache *c;
204 c = b->c;
205 now = c->now;
206 for(; ; i = k){
207 k = (i << 1) + 1;
208 if(k >= c->nheap)
209 break;
210 if(k + 1 < c->nheap && c->heap[k]->used - now > c->heap[k + 1]->used - now)
211 k++;
212 bb = c->heap[k];
213 if(b->used - now <= bb->used - now)
214 break;
215 c->heap[i] = bb;
216 bb->heap = i;
218 c->heap[i] = b;
219 b->heap = i;
220 return i;
223 /*
224 * Delete a block from the heap.
225 * Called with c->lk held.
226 */
227 static void
228 heapdel(VtBlock *b)
230 int i, si;
231 VtCache *c;
233 c = b->c;
235 si = b->heap;
236 if(si == BadHeap)
237 return;
238 b->heap = BadHeap;
239 c->nheap--;
240 if(si == c->nheap)
241 return;
242 b = c->heap[c->nheap];
243 i = upheap(si, b);
244 if(i == si)
245 downheap(i, b);
248 /*
249 * Insert a block into the heap.
250 * Called with c->lk held.
251 */
252 static void
253 heapins(VtBlock *b)
255 assert(b->heap == BadHeap);
256 upheap(b->c->nheap++, b);
259 /*
260 * locate the vtBlock with the oldest second to last use.
261 * remove it from the heap, and fix up the heap.
262 */
263 /* called with c->lk held */
264 static VtBlock*
265 vtcachebumpblock(VtCache *c)
267 VtBlock *b;
269 /*
270 * locate the vtBlock with the oldest second to last use.
271 * remove it from the heap, and fix up the heap.
272 */
273 if(c->nheap == 0){
274 vtcachedump(c);
275 fprint(2, "vtcachebumpblock: no free blocks in vtCache");
276 abort();
278 b = c->heap[0];
279 heapdel(b);
281 assert(b->heap == BadHeap);
282 assert(b->ref == 0);
284 /*
285 * unchain the vtBlock from hash chain if any
286 */
287 if(b->prev){
288 *(b->prev) = b->next;
289 if(b->next)
290 b->next->prev = b->prev;
291 b->prev = nil;
295 if(0)fprint(2, "droping %x:%V\n", b->addr, b->score);
296 /* set vtBlock to a reasonable state */
297 b->ref = 1;
298 b->iostate = BioEmpty;
299 return b;
302 /*
303 * evict blocks until there is enough memory for size bytes.
304 */
305 static VtBlock*
306 vtcacheevict(VtCache *c, ulong size)
308 VtBlock *b;
310 /*
311 * If we were out of memory and put some blocks
312 * to the side but now we have memory, grab one.
313 */
314 if(c->mem >= size && c->dead) {
315 b = c->dead;
316 c->dead = b->next;
317 b->next = nil;
318 goto alloc;
321 /*
322 * Otherwise, evict until we have memory.
323 */
324 for(;;) {
325 b = vtcachebumpblock(c);
326 if(c->mem+b->size >= size)
327 break;
328 /*
329 * chain b onto dead list
330 */
331 free(b->data);
332 b->data = nil;
333 c->mem += b->size;
334 b->size = 0;
335 b->next = c->dead;
336 c->dead = b;
339 /*
340 * Allocate memory for block.
341 */
342 alloc:
343 if(size > b->size || size <= b->size/2) {
344 free(b->data);
345 c->mem += b->size;
346 c->mem -= size;
347 b->size = size;
348 b->data = vtmalloc(size);
350 return b;
353 /*
354 * fetch a local block from the memory cache.
355 * if it's not there, load it, bumping some other Block.
356 * if we're out of free blocks, we're screwed.
357 */
358 VtBlock*
359 vtcachelocal(VtCache *c, u32int addr, int type)
361 VtBlock *b;
363 if(addr == 0)
364 sysfatal("vtcachelocal: asked for nonexistent block 0");
365 if(addr > c->nblock)
366 sysfatal("vtcachelocal: asked for block #%ud; only %d blocks",
367 (uint)addr, c->nblock);
369 b = &c->block[addr-1];
370 if(b->addr == NilBlock || b->iostate != BioLocal)
371 sysfatal("vtcachelocal: block is not local");
373 if(b->type != type)
374 sysfatal("vtcachelocal: block has wrong type %d != %d", b->type, type);
376 qlock(&c->lk);
377 b->ref++;
378 qunlock(&c->lk);
380 qlock(&b->lk);
381 b->nlock = 1;
382 b->pc = getcallerpc(&c);
383 return b;
386 VtBlock*
387 vtcacheallocblock(VtCache *c, int type, ulong size)
389 VtBlock *b;
391 qlock(&c->lk);
392 b = vtcacheevict(c, size);
393 b->iostate = BioLocal;
394 b->type = type;
395 b->addr = (b - c->block)+1;
396 vtzeroextend(type, b->data, 0, size);
397 vtlocaltoglobal(b->addr, b->score);
398 qunlock(&c->lk);
400 qlock(&b->lk);
401 b->nlock = 1;
402 b->pc = getcallerpc(&c);
403 return b;
406 /*
407 * fetch a global (Venti) block from the memory cache.
408 * if it's not there, load it, bumping some other block.
409 */
410 VtBlock*
411 vtcacheglobal(VtCache *c, uchar score[VtScoreSize], int type, ulong size)
413 VtBlock *b;
414 ulong h;
415 int n;
416 u32int addr;
418 if(vttracelevel)
419 fprint(2, "vtcacheglobal %V %d from %p\n", score, type, getcallerpc(&c));
420 addr = vtglobaltolocal(score);
421 if(addr != NilBlock){
422 if(vttracelevel)
423 fprint(2, "vtcacheglobal %V %d => local\n", score, type);
424 b = vtcachelocal(c, addr, type);
425 if(b)
426 b->pc = getcallerpc(&c);
427 return b;
430 h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash;
432 /*
433 * look for the block in the cache
434 */
435 qlock(&c->lk);
436 for(b = c->hash[h]; b != nil; b = b->next){
437 if(b->addr != NilBlock || memcmp(b->score, score, VtScoreSize) != 0 || b->type != type)
438 continue;
439 heapdel(b);
440 b->ref++;
441 qunlock(&c->lk);
442 if(vttracelevel)
443 fprint(2, "vtcacheglobal %V %d => found in cache %p; locking\n", score, type, b);
444 qlock(&b->lk);
445 b->nlock = 1;
446 if(b->iostate == BioVentiError){
447 if(chattyventi)
448 fprint(2, "cached read error for %V\n", score);
449 if(vttracelevel)
450 fprint(2, "vtcacheglobal %V %d => cache read error\n", score, type);
451 vtblockput(b);
452 werrstr("venti i/o error");
453 return nil;
455 if(vttracelevel)
456 fprint(2, "vtcacheglobal %V %d => found in cache; returning\n", score, type);
457 b->pc = getcallerpc(&c);
458 return b;
461 /*
462 * not found
463 */
464 b = vtcacheevict(c, size);
465 b->addr = NilBlock;
466 b->type = type;
467 memmove(b->score, score, VtScoreSize);
468 /* chain onto correct hash */
469 b->next = c->hash[h];
470 c->hash[h] = b;
471 if(b->next != nil)
472 b->next->prev = &b->next;
473 b->prev = &c->hash[h];
475 /*
476 * Lock b before unlocking c, so that others wait while we read.
478 * You might think there is a race between this qlock(b) before qunlock(c)
479 * and the qlock(c) while holding a qlock(b) in vtblockwrite. However,
480 * the block here can never be the block in a vtblockwrite, so we're safe.
481 * We're certainly living on the edge.
482 */
483 if(vttracelevel)
484 fprint(2, "vtcacheglobal %V %d => bumped; locking %p\n", score, type, b);
485 qlock(&b->lk);
486 b->nlock = 1;
487 qunlock(&c->lk);
489 vtcachenread++;
490 n = vtread(c->z, score, type, b->data, size);
491 if(n < 0){
492 if(chattyventi)
493 fprint(2, "read %V: %r\n", score);
494 if(vttracelevel)
495 fprint(2, "vtcacheglobal %V %d => bumped; read error\n", score, type);
496 b->iostate = BioVentiError;
497 vtblockput(b);
498 return nil;
500 vtzeroextend(type, b->data, n, size);
501 b->iostate = BioVenti;
502 b->nlock = 1;
503 if(vttracelevel)
504 fprint(2, "vtcacheglobal %V %d => loaded into cache; returning\n", score, type);
505 b->pc = getcallerpc(&b);
506 return b;
509 /*
510 * The thread that has locked b may refer to it by
511 * multiple names. Nlock counts the number of
512 * references the locking thread holds. It will call
513 * vtblockput once per reference.
514 */
515 void
516 vtblockduplock(VtBlock *b)
518 assert(b->nlock > 0);
519 b->nlock++;
522 /*
523 * we're done with the block.
524 * unlock it. can't use it after calling this.
525 */
526 void
527 vtblockput(VtBlock* b)
529 VtCache *c;
531 if(b == nil)
532 return;
534 if(0)fprint(2, "vtblockput: %d: %x %d %d\n", getpid(), b->addr, c->nheap, b->iostate);
535 if(vttracelevel)
536 fprint(2, "vtblockput %p from %p\n", b, getcallerpc(&b));
538 if(--b->nlock > 0)
539 return;
541 /*
542 * b->nlock should probably stay at zero while
543 * the vtBlock is unlocked, but diskThread and vtSleep
544 * conspire to assume that they can just qlock(&b->lk); vtblockput(b),
545 * so we have to keep b->nlock set to 1 even
546 * when the vtBlock is unlocked.
547 */
548 assert(b->nlock == 0);
549 b->nlock = 1;
551 qunlock(&b->lk);
552 c = b->c;
553 qlock(&c->lk);
555 if(--b->ref > 0){
556 qunlock(&c->lk);
557 return;
560 assert(b->ref == 0);
561 switch(b->iostate){
562 case BioVenti:
563 /*if(b->addr != NilBlock) print("blockput %d\n", b->addr); */
564 b->used = c->now++;
565 /* fall through */
566 case BioVentiError:
567 heapins(b);
568 break;
569 case BioLocal:
570 break;
572 qunlock(&c->lk);
575 int
576 vtblockwrite(VtBlock *b)
578 uchar score[VtScoreSize];
579 VtCache *c;
580 uint h;
581 int n;
583 if(b->iostate != BioLocal){
584 werrstr("vtblockwrite: not a local block");
585 return -1;
588 c = b->c;
589 n = vtzerotruncate(b->type, b->data, b->size);
590 vtcachenwrite++;
591 if(c->write(c->z, score, b->type, b->data, n) < 0)
592 return -1;
594 memmove(b->score, score, VtScoreSize);
596 qlock(&c->lk);
597 b->addr = NilBlock; /* now on venti */
598 b->iostate = BioVenti;
599 h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash;
600 b->next = c->hash[h];
601 c->hash[h] = b;
602 if(b->next != nil)
603 b->next->prev = &b->next;
604 b->prev = &c->hash[h];
605 qunlock(&c->lk);
606 return 0;
609 VtBlock*
610 vtblockcopy(VtBlock *b)
612 VtBlock *bb;
614 vtcachencopy++;
615 bb = vtcacheallocblock(b->c, b->type, b->size);
616 if(bb == nil){
617 vtblockput(b);
618 return nil;
620 memmove(bb->data, b->data, b->size);
621 vtblockput(b);
622 bb->pc = getcallerpc(&b);
623 return bb;
626 void
627 vtlocaltoglobal(u32int addr, uchar score[VtScoreSize])
629 memset(score, 0, 16);
630 score[16] = addr>>24;
631 score[17] = addr>>16;
632 score[18] = addr>>8;
633 score[19] = addr;
637 u32int
638 vtglobaltolocal(uchar score[VtScoreSize])
640 static uchar zero[16];
641 if(memcmp(score, zero, 16) != 0)
642 return NilBlock;
643 return (score[16]<<24)|(score[17]<<16)|(score[18]<<8)|score[19];