#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" enum { NHASH = 128, NFILE = 4093, /* should be prime */ MAXCACHE = 8*1024*1024, MAPBITS = 8*sizeof(ulong), NBITMAP = (PGROUND(MAXCACHE)/BY2PG + MAPBITS-1) / MAPBITS, }; /* devmnt.c: parallel read ahread implementation */ extern void mntrahinit(Mntrah *rah); extern long mntrahread(Mntrah *rah, Chan *c, uchar *buf, long len, vlong off); typedef struct Mntcache Mntcache; struct Mntcache { Qid qid; int dev; int type; QLock; Proc *locked; ulong nlocked; Mntcache *hash; Mntcache *prev; Mntcache *next; /* page bitmap of valid pages */ ulong bitmap[NBITMAP]; /* read ahead state */ Mntrah rah; }; typedef struct Cache Cache; struct Cache { Lock; Mntcache *alloc; Mntcache *head; Mntcache *tail; Mntcache *hash[NHASH]; }; Image fscache; static Cache cache; void cinit(void) { int i; Mntcache *m; m = xalloc(sizeof(Mntcache)*NFILE); if (m == nil) panic("cinit: no memory"); cache.alloc = m; cache.head = m; for(i = 0; i < NFILE-1; i++) { m->next = m+1; m->prev = m-1; m++; } cache.tail = m; cache.tail->next = nil; cache.head->prev = nil; fscache.notext = 1; } static uintptr cacheaddr(Mntcache *m, ulong pn) { uintptr da = pn * NFILE + (m - cache.alloc); return (da << PGSHIFT) | (da >> (sizeof(da)*8 - PGSHIFT)); } static void cnodata(Mntcache *m) { memset(m->bitmap, 0, sizeof(m->bitmap)); } static void ctail(Mntcache *m) { /* Unlink and send to the tail */ if(m->prev != nil) m->prev->next = m->next; else cache.head = m->next; if(m->next != nil) m->next->prev = m->prev; else cache.tail = m->prev; if(cache.tail != nil) { m->prev = cache.tail; cache.tail->next = m; m->next = nil; cache.tail = m; } else { cache.head = m; cache.tail = m; m->prev = nil; m->next = nil; } } /* called with cache locked */ static Mntcache* clookup(Chan *c, int skipvers) { Mntcache *m; for(m = cache.hash[c->qid.path%NHASH]; m != nil; m = m->hash) if(eqchantdqid(c, m->type, m->dev, m->qid, skipvers) && c->qid.type == m->qid.type) return m; return nil; } /* * resursive Mntcache locking. Mntcache.rah is protected by the * same lock and we want to call cupdate() from mntrahread() * while holding the lock. */ static int cancachelock(Mntcache *m) { if(m->locked == up || canqlock(m)){ m->locked = up; m->nlocked++; return 1; } return 0; } static void cachelock(Mntcache *m) { if(m->locked != up){ qlock(m); assert(m->nlocked == 0); m->locked = up; } m->nlocked++; } static void cacheunlock(Mntcache *m) { assert(m->locked == up); if(--m->nlocked == 0){ m->locked = nil; qunlock(m); } } /* return locked Mntcache if still valid else reset mcp */ static Mntcache* ccache(Chan *c) { Mntcache *m; m = c->mcp; if(m != nil) { cachelock(m); if(eqchantdqid(c, m->type, m->dev, m->qid, 0) && c->qid.type == m->qid.type) return m; c->mcp = nil; cacheunlock(m); } return nil; } int copen(Chan *c) { Mntcache *m, *f, **l; /* directories aren't cacheable */ if(c->qid.type&QTDIR){ c->mcp = nil; return 0; } lock(&cache); m = clookup(c, 0); if(m != nil){ ctail(m); unlock(&cache); c->mcp = m; return 1; } m = clookup(c, 1); if(m == nil) m = cache.head; ctail(m); l = &cache.hash[m->qid.path%NHASH]; for(f = *l; f != nil; f = f->hash) { if(f == m) { *l = m->hash; break; } l = &f->hash; } if(!cancachelock(m)){ unlock(&cache); cachelock(m); lock(&cache); f = clookup(c, 0); if(f != nil) { /* * someone got there first while cache lock * was released and added a updated Mntcache * for us. update LRU and use it. */ ctail(f); unlock(&cache); cacheunlock(m); c->mcp = f; return 1; } } m->qid = c->qid; m->dev = c->dev; m->type = c->type; l = &cache.hash[c->qid.path%NHASH]; m->hash = *l; *l = m; unlock(&cache); m->rah.vers = m->qid.vers; mntrahinit(&m->rah); cnodata(m); cacheunlock(m); c->mcp = m; return 0; } enum { VABITS = 8*sizeof(uintptr) - 2*PGSHIFT, VAMASK = (((uintptr)1 << VABITS)-1) << PGSHIFT, }; static Page* cpage(Mntcache *m, ulong pn, ulong *po, ulong *pe) { ulong b; Page *p; b = 1 << (pn%MAPBITS); if((m->bitmap[pn/MAPBITS] & b) == 0) return nil; p = lookpage(&fscache, cacheaddr(m, pn)); if(p == nil){ m->bitmap[pn/MAPBITS] &= ~b; return nil; } *po = p->va & (BY2PG-1); *pe = 1 + (p->va >> (PGSHIFT+VABITS)); assert(*po < *pe); return p; } static void cpageset(Page *p, ulong po, ulong pe) { assert(po < pe); p->va = po | (p->va & VAMASK) | ((uintptr)pe - 1) << (PGSHIFT+VABITS); } int cread(Chan *c, uchar *buf, int len, vlong off) { KMap *k; Page *p; Mntcache *m; int l, tot; ulong offset, pn, po, pe; if(len <= 0) return 0; m = ccache(c); if(m == nil) return 0; if(waserror()){ cacheunlock(m); nexterror(); } tot = 0; if(off >= MAXCACHE) goto Prefetch; offset = off; if(offset+len > MAXCACHE) len = MAXCACHE - offset; pn = offset / BY2PG; offset &= (BY2PG-1); while(len > 0){ p = cpage(m, pn, &po, &pe); if(p == nil) break; if(offset < po || offset >= pe){ putpage(p); break; } l = pe - offset; if(l > len) l = len; k = kmap(p); if(waserror()) { kunmap(k); putpage(p); nexterror(); } memmove(buf, (uchar*)VA(k) + offset, l); kunmap(k); putpage(p); poperror(); tot += l; buf += l; len -= l; offset += l; offset &= (BY2PG-1); if(offset != 0) break; pn++; } Prefetch: if(len > 0){ if(m->rah.vers != m->qid.vers){ mntrahinit(&m->rah); m->rah.vers = m->qid.vers; } off += tot; tot += mntrahread(&m->rah, c, buf, len, off); } cacheunlock(m); poperror(); return tot; } /* invalidate pages in page bitmap */ static void invalidate(Mntcache *m, ulong offset, int len) { ulong pn; for(pn = offset/BY2PG; len > 0; pn++, len -= BY2PG) m->bitmap[pn/MAPBITS] &= ~(1 << (pn%MAPBITS)); } /* replace buf data from [off, off+len) in the cache or invalidate */ static void cachedata(Mntcache *m, uchar *buf, int len, vlong off) { int l; Page *p; KMap *k; ulong offset, pn, po, pe; if(off >= MAXCACHE || len <= 0){ cacheunlock(m); return; } offset = off; if(offset+len > MAXCACHE) len = MAXCACHE - offset; pn = offset / BY2PG; offset &= (BY2PG-1); while(len > 0){ l = BY2PG - offset; if(l > len) l = len; p = cpage(m, pn, &po, &pe); if(p != nil){ if(offset > pe || (offset+l) < po){ /* cached range not extendable, set new cached range */ po = offset; pe = offset+l; } else { /* extend cached range */ if(offset < po) po = offset; if((offset+l) > pe) pe = offset+l; } } else { if(needpages(nil)){ invalidate(m, offset + pn*BY2PG, len); break; } p = newpage(0, nil, pn*BY2PG); p->daddr = cacheaddr(m, pn); cachedel(&fscache, p->daddr); cachepage(p, &fscache); m->bitmap[pn/MAPBITS] |= 1 << (pn%MAPBITS); po = offset; pe = offset+l; } cpageset(p, po, pe); k = kmap(p); if(waserror()) { kunmap(k); putpage(p); invalidate(m, offset + pn*BY2PG, len); cacheunlock(m); nexterror(); } memmove((uchar*)VA(k) + offset, buf, l); poperror(); kunmap(k); putpage(p); offset = 0; pn++; buf += l; len -= l; } cacheunlock(m); } void cupdate(Chan *c, uchar *buf, int len, vlong off) { Mntcache *m; m = ccache(c); if(m == nil) return; cachedata(m, buf, len, off); } void cwrite(Chan* c, uchar *buf, int len, vlong off) { Mntcache *m; m = ccache(c); if(m == nil) return; m->qid.vers++; c->qid.vers++; if(c->qid.type&QTAPPEND){ cacheunlock(m); return; } cachedata(m, buf, len, off); } void ctrunc(Chan *c) { Mntcache *m; if(c->qid.type&QTDIR) return; if((c->flag&COPEN) == 0){ lock(&cache); c->mcp = clookup(c, 0); unlock(&cache); } m = ccache(c); if(m == nil) return; mntrahinit(&m->rah); cnodata(m); cacheunlock(m); if((c->flag&COPEN) == 0) c->mcp = nil; } void cclunk(Chan *c) { Mntcache *m; m = ccache(c); if(m == nil) return; mntrahinit(&m->rah); cacheunlock(m); c->mcp = nil; }