| #include <u.h> |
| #include <libc.h> |
| #include <diskfs.h> |
| |
| /* |
| * Disk cache. Caches by offset, so higher levels have |
| * to deal with alignment issues (if we get asked for the |
| * blocks at offsets 0 and 1, we'll do two reads). |
| */ |
| |
| typedef struct DiskCache DiskCache; |
| typedef struct DiskCacheBlock DiskCacheBlock; |
| |
| struct DiskCache |
| { |
| Disk disk; |
| Disk *subdisk; |
| DiskCacheBlock **h; |
| DiskCacheBlock *lruhead; |
| DiskCacheBlock *lrutail; |
| int nhash; |
| int blocksize; |
| Lock lk; |
| }; |
| |
| struct DiskCacheBlock |
| { |
| Block block; |
| Block *subblock; |
| Lock lk; |
| int ref; |
| DiskCache *dc; |
| DiskCacheBlock *next; |
| DiskCacheBlock *lrunext; |
| DiskCacheBlock *lruprev; |
| u64int offset; |
| }; |
| |
| static void |
| addtohash(DiskCache *d, DiskCacheBlock *b, u64int offset) |
| { |
| int h; |
| |
| if(b->offset != ~(u64int)0){ |
| fprint(2, "bad offset in addtohash\n"); |
| return; |
| } |
| b->offset = offset; |
| h = offset % d->nhash; |
| b->next = d->h[h]; |
| d->h[h] = b; |
| } |
| |
| static void |
| delfromhash(DiskCache *d, DiskCacheBlock *b) |
| { |
| int h; |
| DiskCacheBlock **l; |
| |
| if(b->offset == ~(u64int)0) |
| return; |
| |
| h = b->offset % d->nhash; |
| for(l=&d->h[h]; *l; l=&(*l)->next) |
| if(*l == b){ |
| *l = b->next; |
| b->offset = ~(u64int)0; |
| return; |
| } |
| fprint(2, "delfromhash: didn't find in hash table\n"); |
| return; |
| } |
| |
| static void |
| putmru(DiskCache *d, DiskCacheBlock *b) |
| { |
| b->lruprev = nil; |
| b->lrunext = d->lruhead; |
| d->lruhead = b; |
| if(b->lrunext == nil) |
| d->lrutail = b; |
| else |
| b->lrunext->lruprev = b; |
| } |
| |
| static void |
| putlru(DiskCache *d, DiskCacheBlock *b) |
| { |
| b->lruprev = d->lrutail; |
| b->lrunext = nil; |
| d->lrutail = b; |
| if(b->lruprev == nil) |
| d->lruhead = b; |
| else |
| b->lruprev->lrunext = b; |
| } |
| |
| static void |
| delfromlru(DiskCache *d, DiskCacheBlock *b) |
| { |
| if(b->lruprev) |
| b->lruprev->lrunext = b->lrunext; |
| else |
| d->lruhead = b->lrunext; |
| if(b->lrunext) |
| b->lrunext->lruprev = b->lruprev; |
| else |
| d->lrutail = b->lruprev; |
| } |
| |
| static DiskCacheBlock* |
| getlru(DiskCache *d) |
| { |
| DiskCacheBlock *b; |
| |
| b = d->lrutail; |
| if(b){ |
| delfromlru(d, b); |
| delfromhash(d, b); |
| blockput(b->subblock); |
| b->subblock = nil; |
| } |
| return b; |
| } |
| |
| static DiskCacheBlock* |
| findblock(DiskCache *d, u64int offset) |
| { |
| int h; |
| DiskCacheBlock *b; |
| |
| h = offset % d->nhash; |
| for(b=d->h[h]; b; b=b->next) |
| if(b->offset == offset) |
| return b; |
| return nil; |
| } |
| |
| static DiskCacheBlock* |
| diskcachereadbig(DiskCache *d, u64int offset) |
| { |
| Block *b; |
| DiskCacheBlock *dcb; |
| |
| lock(&d->lk); |
| dcb = findblock(d, offset); |
| if(dcb){ |
| /*fprint(2, "found %llud in cache %p\n", (uvlong)offset, dcb);*/ |
| if(dcb->ref++ == 0) |
| delfromlru(d, dcb); |
| unlock(&d->lk); |
| return dcb; |
| } |
| |
| dcb = getlru(d); |
| unlock(&d->lk); |
| if(dcb == nil){ |
| fprint(2, "diskcacheread: all blocks in use\n"); |
| return nil; |
| } |
| |
| b = diskread(d->subdisk, d->blocksize, offset); |
| lock(&d->lk); |
| if(b == nil){ |
| putlru(d, dcb); |
| dcb = nil; |
| }else{ |
| /*fprint(2, "read %llud from disk %p\n", (uvlong)offset, dcb); */ |
| dcb->subblock = b; |
| dcb->ref++; |
| addtohash(d, dcb, offset); |
| } |
| unlock(&d->lk); |
| return dcb; |
| } |
| |
| static void |
| diskcacheblockclose(Block *bb) |
| { |
| DiskCacheBlock *b = bb->priv; |
| |
| lock(&b->dc->lk); |
| if(--b->ref == 0) |
| putmru(b->dc, b); |
| unlock(&b->dc->lk); |
| free(bb); |
| } |
| |
| static Block* |
| diskcacheread(Disk *dd, u32int len, u64int offset) |
| { |
| int frag, dlen; |
| DiskCache *d = (DiskCache*)dd; |
| DiskCacheBlock *dcb; |
| Block *b; |
| |
| if(offset/d->blocksize != (offset+len-1)/d->blocksize){ |
| fprint(2, "diskBigRead: request for block crossing big block boundary\n"); |
| return nil; |
| } |
| |
| b = mallocz(sizeof(Block), 1); |
| if(b == nil) |
| return nil; |
| |
| frag = offset%d->blocksize; |
| |
| dcb = diskcachereadbig(d, offset-frag); |
| if(dcb == nil){ |
| free(b); |
| return nil; |
| } |
| b->priv = dcb; |
| b->_close = diskcacheblockclose; |
| b->data = dcb->subblock->data+frag; |
| |
| dlen = dcb->subblock->len; |
| if(frag+len >= dlen){ |
| if(frag >= dlen){ |
| blockput(b); |
| return nil; |
| } |
| len = dlen-frag; |
| } |
| b->len = len; |
| /*fprint(2, "offset %llud at pointer %p %lux\n", (uvlong)offset, b->data, *(ulong*)(b->data+4)); */ |
| return b; |
| } |
| |
| /* |
| * It's okay to remove these from the hash table. |
| * Either the block is in use by someone or it is on |
| * the lru list. If it's in use it will get put on the lru |
| * list once the refs go away. |
| */ |
| static int |
| diskcachesync(Disk *dd) |
| { |
| DiskCache *d = (DiskCache*)dd; |
| DiskCacheBlock *b, *nextb; |
| int i; |
| |
| lock(&d->lk); |
| for(i=0; i<d->nhash; i++){ |
| for(b=d->h[i]; b; b=nextb){ |
| nextb = b->next; |
| b->next = nil; |
| b->offset = ~(u64int)0; |
| } |
| d->h[i] = nil; |
| } |
| unlock(&d->lk); |
| return disksync(d->subdisk); |
| } |
| |
| static void |
| diskcacheclose(Disk *dd) |
| { |
| DiskCacheBlock *b; |
| DiskCache *d = (DiskCache*)dd; |
| |
| diskclose(d->subdisk); |
| for(b=d->lruhead; b; b=b->lrunext) |
| blockput(b->subblock); |
| free(d); |
| } |
| |
| /* needn't be fast */ |
| static int |
| isprime(int n) |
| { |
| int i; |
| |
| for(i=2; i*i<=n; i++) |
| if(n%i == 0) |
| return 0; |
| return 1; |
| } |
| |
| Disk* |
| diskcache(Disk *subdisk, uint blocksize, uint ncache) |
| { |
| int nhash, i; |
| DiskCache *d; |
| DiskCacheBlock *b; |
| |
| nhash = ncache; |
| while(nhash > 1 && !isprime(nhash)) |
| nhash--; |
| d = mallocz(sizeof(DiskCache)+ncache*sizeof(DiskCacheBlock)+nhash*sizeof(DiskCacheBlock*), 1); |
| if(d == nil) |
| return nil; |
| |
| b = (DiskCacheBlock*)&d[1]; |
| d->h = (DiskCacheBlock**)&b[ncache]; |
| d->nhash = nhash; |
| d->blocksize = blocksize; |
| d->subdisk = subdisk; |
| d->disk._read = diskcacheread; |
| d->disk._sync = diskcachesync; |
| d->disk._close = diskcacheclose; |
| |
| for(i=0; i<ncache; i++){ |
| b[i].block._close = diskcacheblockclose; |
| b[i].offset = ~(u64int)0; |
| b[i].dc = d; |
| putlru(d, &b[i]); |
| } |
| |
| return &d->disk; |
| } |