cache.c (12782B)
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 */ 10 11 #include <u.h> 12 #include <libc.h> 13 #include <venti.h> 14 15 int vtcachenread; 16 int vtcachencopy; 17 int vtcachenwrite; 18 int vttracelevel; 19 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 }; 47 48 static void cachecheck(VtCache*); 49 50 VtCache* 51 vtcachealloc(VtConn *z, ulong maxmem) 52 { 53 VtCache *c; 54 int i; 55 int nblock; 56 VtBlock *b; 57 ulong maxmem0; 58 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; 74 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 } 86 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 } 99 100 void 101 vtcachefree(VtCache *c) 102 { 103 int i; 104 105 qlock(&c->lk); 106 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); 111 } 112 113 vtfree(c->hash); 114 vtfree(c->heap); 115 vtfree(c->block); 116 vtfree(c); 117 } 118 119 static void 120 vtcachedump(VtCache *c) 121 { 122 int i; 123 VtBlock *b; 124 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); 129 } 130 } 131 132 static void 133 cachecheck(VtCache *c) 134 { 135 u32int now; 136 int i, k, refed; 137 VtBlock *b; 138 139 now = c->now; 140 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"); 152 } 153 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++; 161 } 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++; 168 } 169 } 170 } 171 172 static int 173 upheap(int i, VtBlock *b) 174 { 175 VtBlock *bb; 176 u32int now; 177 int p; 178 VtCache *c; 179 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; 189 } 190 c->heap[i] = b; 191 b->heap = i; 192 193 return i; 194 } 195 196 static int 197 downheap(int i, VtBlock *b) 198 { 199 VtBlock *bb; 200 u32int now; 201 int k; 202 VtCache *c; 203 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; 217 } 218 c->heap[i] = b; 219 b->heap = i; 220 return i; 221 } 222 223 /* 224 * Delete a block from the heap. 225 * Called with c->lk held. 226 */ 227 static void 228 heapdel(VtBlock *b) 229 { 230 int i, si; 231 VtCache *c; 232 233 c = b->c; 234 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); 246 } 247 248 /* 249 * Insert a block into the heap. 250 * Called with c->lk held. 251 */ 252 static void 253 heapins(VtBlock *b) 254 { 255 assert(b->heap == BadHeap); 256 upheap(b->c->nheap++, b); 257 } 258 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) 266 { 267 VtBlock *b; 268 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(); 277 } 278 b = c->heap[0]; 279 heapdel(b); 280 281 assert(b->heap == BadHeap); 282 assert(b->ref == 0); 283 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; 292 } 293 294 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; 300 } 301 302 /* 303 * evict blocks until there is enough memory for size bytes. 304 */ 305 static VtBlock* 306 vtcacheevict(VtCache *c, ulong size) 307 { 308 VtBlock *b; 309 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; 319 } 320 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; 337 } 338 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); 349 } 350 return b; 351 } 352 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) 360 { 361 VtBlock *b; 362 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); 368 369 b = &c->block[addr-1]; 370 if(b->addr == NilBlock || b->iostate != BioLocal) 371 sysfatal("vtcachelocal: block is not local"); 372 373 if(b->type != type) 374 sysfatal("vtcachelocal: block has wrong type %d != %d", b->type, type); 375 376 qlock(&c->lk); 377 b->ref++; 378 qunlock(&c->lk); 379 380 qlock(&b->lk); 381 b->nlock = 1; 382 b->pc = getcallerpc(&c); 383 return b; 384 } 385 386 VtBlock* 387 vtcacheallocblock(VtCache *c, int type, ulong size) 388 { 389 VtBlock *b; 390 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); 399 400 qlock(&b->lk); 401 b->nlock = 1; 402 b->pc = getcallerpc(&c); 403 return b; 404 } 405 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) 412 { 413 VtBlock *b; 414 ulong h; 415 int n; 416 u32int addr; 417 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; 428 } 429 430 h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash; 431 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; 454 } 455 if(vttracelevel) 456 fprint(2, "vtcacheglobal %V %d => found in cache; returning\n", score, type); 457 b->pc = getcallerpc(&c); 458 return b; 459 } 460 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]; 474 475 /* 476 * Lock b before unlocking c, so that others wait while we read. 477 * 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); 488 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; 499 } 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; 507 } 508 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) 517 { 518 assert(b->nlock > 0); 519 b->nlock++; 520 } 521 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) 528 { 529 VtCache *c; 530 531 if(b == nil) 532 return; 533 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)); 537 538 if(--b->nlock > 0) 539 return; 540 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; 550 551 qunlock(&b->lk); 552 c = b->c; 553 qlock(&c->lk); 554 555 if(--b->ref > 0){ 556 qunlock(&c->lk); 557 return; 558 } 559 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; 571 } 572 qunlock(&c->lk); 573 } 574 575 int 576 vtblockwrite(VtBlock *b) 577 { 578 uchar score[VtScoreSize]; 579 VtCache *c; 580 uint h; 581 int n; 582 583 if(b->iostate != BioLocal){ 584 werrstr("vtblockwrite: not a local block"); 585 return -1; 586 } 587 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; 593 594 memmove(b->score, score, VtScoreSize); 595 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; 607 } 608 609 VtBlock* 610 vtblockcopy(VtBlock *b) 611 { 612 VtBlock *bb; 613 614 vtcachencopy++; 615 bb = vtcacheallocblock(b->c, b->type, b->size); 616 if(bb == nil){ 617 vtblockput(b); 618 return nil; 619 } 620 memmove(bb->data, b->data, b->size); 621 vtblockput(b); 622 bb->pc = getcallerpc(&b); 623 return bb; 624 } 625 626 void 627 vtlocaltoglobal(u32int addr, uchar score[VtScoreSize]) 628 { 629 memset(score, 0, 16); 630 score[16] = addr>>24; 631 score[17] = addr>>16; 632 score[18] = addr>>8; 633 score[19] = addr; 634 } 635 636 637 u32int 638 vtglobaltolocal(uchar score[VtScoreSize]) 639 { 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]; 644 }