29f60cace1
Previously, mmurelease() was always called with palloc spinlock held. This is unneccesary for some mmurelease() implementations as they wont release pages to the palloc pool. This change removes pagechainhead() and pagechaindone() and replaces them with just freepages() call, which aquires the palloc lock internally as needed. freepages() avoids holding the palloc lock while walking the linked list of pages, avoding some lock contention.
564 lines
9.4 KiB
C
564 lines
9.4 KiB
C
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "../port/error.h"
|
|
|
|
enum
|
|
{
|
|
Qtopdir,
|
|
Qsegdir,
|
|
Qctl,
|
|
Qdata,
|
|
};
|
|
|
|
#define TYPE(x) (int)( (c)->qid.path & 0x7 )
|
|
#define SEG(x) ( ((c)->qid.path >> 3) & 0x3f )
|
|
#define PATH(s, t) ( ((s)<<3) | (t) )
|
|
|
|
typedef struct Globalseg Globalseg;
|
|
struct Globalseg
|
|
{
|
|
Ref;
|
|
|
|
char *name;
|
|
char *uid;
|
|
vlong length;
|
|
long perm;
|
|
|
|
Segio;
|
|
};
|
|
|
|
static Globalseg *globalseg[100];
|
|
static Lock globalseglock;
|
|
|
|
|
|
Segment* (*_globalsegattach)(char*);
|
|
static Segment* globalsegattach(char *name);
|
|
|
|
static Segment* fixedseg(uintptr va, ulong len);
|
|
|
|
/*
|
|
* returns with globalseg incref'd
|
|
*/
|
|
static Globalseg*
|
|
getgseg(Chan *c)
|
|
{
|
|
int x;
|
|
Globalseg *g;
|
|
|
|
x = SEG(c);
|
|
lock(&globalseglock);
|
|
if(x >= nelem(globalseg))
|
|
panic("getgseg");
|
|
g = globalseg[x];
|
|
if(g != nil)
|
|
incref(g);
|
|
unlock(&globalseglock);
|
|
if(g == nil)
|
|
error("global segment disappeared");
|
|
return g;
|
|
}
|
|
|
|
static void
|
|
putgseg(Globalseg *g)
|
|
{
|
|
if(g == nil || decref(g))
|
|
return;
|
|
if(g->s != nil)
|
|
putseg(g->s);
|
|
segio(g, nil, nil, 0, 0, 0);
|
|
free(g->name);
|
|
free(g->uid);
|
|
free(g);
|
|
}
|
|
|
|
static int
|
|
segmentgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
|
|
{
|
|
Qid q;
|
|
Globalseg *g;
|
|
uintptr size;
|
|
|
|
switch(TYPE(c)) {
|
|
case Qtopdir:
|
|
if(s == DEVDOTDOT){
|
|
q.vers = 0;
|
|
q.path = PATH(0, Qtopdir);
|
|
q.type = QTDIR;
|
|
devdir(c, q, "#g", 0, eve, 0777, dp);
|
|
break;
|
|
}
|
|
|
|
if(s >= nelem(globalseg))
|
|
return -1;
|
|
|
|
lock(&globalseglock);
|
|
g = globalseg[s];
|
|
if(g == nil){
|
|
unlock(&globalseglock);
|
|
return 0;
|
|
}
|
|
q.vers = 0;
|
|
q.path = PATH(s, Qsegdir);
|
|
q.type = QTDIR;
|
|
kstrcpy(up->genbuf, g->name, sizeof up->genbuf);
|
|
devdir(c, q, up->genbuf, 0, g->uid, 0777, dp);
|
|
unlock(&globalseglock);
|
|
break;
|
|
case Qsegdir:
|
|
if(s == DEVDOTDOT){
|
|
q.vers = 0;
|
|
q.path = PATH(0, Qtopdir);
|
|
q.type = QTDIR;
|
|
devdir(c, q, "#g", 0, eve, 0777, dp);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case Qctl:
|
|
case Qdata:
|
|
switch(s){
|
|
case 0:
|
|
g = getgseg(c);
|
|
q.vers = 0;
|
|
q.path = PATH(SEG(c), Qctl);
|
|
q.type = QTFILE;
|
|
devdir(c, q, "ctl", 0, g->uid, g->perm, dp);
|
|
putgseg(g);
|
|
break;
|
|
case 1:
|
|
g = getgseg(c);
|
|
q.vers = 0;
|
|
q.path = PATH(SEG(c), Qdata);
|
|
q.type = QTFILE;
|
|
if(g->s != nil)
|
|
size = g->s->top - g->s->base;
|
|
else
|
|
size = 0;
|
|
devdir(c, q, "data", size, g->uid, g->perm, dp);
|
|
putgseg(g);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
segmentinit(void)
|
|
{
|
|
_globalsegattach = globalsegattach;
|
|
}
|
|
|
|
static Chan*
|
|
segmentattach(char *spec)
|
|
{
|
|
return devattach('g', spec);
|
|
}
|
|
|
|
static Walkqid*
|
|
segmentwalk(Chan *c, Chan *nc, char **name, int nname)
|
|
{
|
|
return devwalk(c, nc, name, nname, 0, 0, segmentgen);
|
|
}
|
|
|
|
static int
|
|
segmentstat(Chan *c, uchar *db, int n)
|
|
{
|
|
return devstat(c, db, n, 0, 0, segmentgen);
|
|
}
|
|
|
|
static Chan*
|
|
segmentopen(Chan *c, int omode)
|
|
{
|
|
Globalseg *g;
|
|
|
|
switch(TYPE(c)){
|
|
case Qsegdir:
|
|
omode &= ~ORCLOSE;
|
|
case Qtopdir:
|
|
if(omode != 0)
|
|
error(Eisdir);
|
|
break;
|
|
case Qctl:
|
|
case Qdata:
|
|
g = getgseg(c);
|
|
if(waserror()){
|
|
putgseg(g);
|
|
nexterror();
|
|
}
|
|
devpermcheck(g->uid, g->perm, omode);
|
|
if(TYPE(c) == Qdata && g->s == nil)
|
|
error("segment not yet allocated");
|
|
c->aux = g;
|
|
poperror();
|
|
break;
|
|
default:
|
|
panic("segmentopen");
|
|
}
|
|
c->mode = openmode(omode);
|
|
c->flag |= COPEN;
|
|
c->offset = 0;
|
|
|
|
return c;
|
|
}
|
|
|
|
static void
|
|
segmentremove(Chan *c)
|
|
{
|
|
Globalseg *g;
|
|
int x;
|
|
|
|
if(TYPE(c) != Qsegdir)
|
|
error(Eperm);
|
|
lock(&globalseglock);
|
|
x = SEG(c);
|
|
g = globalseg[x];
|
|
globalseg[x] = nil;
|
|
unlock(&globalseglock);
|
|
if(g != nil)
|
|
putgseg(g);
|
|
}
|
|
|
|
static void
|
|
segmentclose(Chan *c)
|
|
{
|
|
if(c->flag & COPEN){
|
|
switch(TYPE(c)){
|
|
case Qsegdir:
|
|
if(c->flag & CRCLOSE)
|
|
segmentremove(c);
|
|
break;
|
|
case Qctl:
|
|
case Qdata:
|
|
putgseg(c->aux);
|
|
c->aux = nil;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static Chan*
|
|
segmentcreate(Chan *c, char *name, int omode, ulong perm)
|
|
{
|
|
int x, xfree;
|
|
Globalseg *g;
|
|
|
|
if(TYPE(c) != Qtopdir)
|
|
error(Eperm);
|
|
|
|
if(strlen(name) >= sizeof(up->genbuf))
|
|
error(Etoolong);
|
|
|
|
if(findphysseg(name) != nil)
|
|
error("name collision with physical segment");
|
|
|
|
if((perm & DMDIR) == 0)
|
|
error(Ebadarg);
|
|
|
|
g = smalloc(sizeof(Globalseg));
|
|
g->ref = 1;
|
|
g->perm = 0660;
|
|
kstrdup(&g->name, name);
|
|
kstrdup(&g->uid, up->user);
|
|
|
|
lock(&globalseglock);
|
|
if(waserror()){
|
|
unlock(&globalseglock);
|
|
putgseg(g);
|
|
nexterror();
|
|
}
|
|
xfree = -1;
|
|
for(x = 0; x < nelem(globalseg); x++){
|
|
if(globalseg[x] == nil){
|
|
if(xfree < 0)
|
|
xfree = x;
|
|
} else if(strcmp(globalseg[x]->name, name) == 0)
|
|
error(Eexist);
|
|
}
|
|
if(xfree < 0)
|
|
error("too many global segments");
|
|
globalseg[xfree] = g;
|
|
unlock(&globalseglock);
|
|
poperror();
|
|
|
|
c->qid.path = PATH(xfree, Qsegdir);
|
|
c->qid.type = QTDIR;
|
|
c->qid.vers = 0;
|
|
c->mode = openmode(omode);
|
|
c->flag |= COPEN;
|
|
c->offset = 0;
|
|
|
|
return c;
|
|
}
|
|
|
|
static long
|
|
segmentread(Chan *c, void *a, long n, vlong voff)
|
|
{
|
|
Globalseg *g;
|
|
char buf[128];
|
|
|
|
if(c->qid.type == QTDIR)
|
|
return devdirread(c, a, n, (Dirtab *)0, 0L, segmentgen);
|
|
|
|
g = c->aux;
|
|
switch(TYPE(c)){
|
|
case Qctl:
|
|
if(g->s == nil)
|
|
error("segment not yet allocated");
|
|
if((g->s->type&SG_TYPE) == SG_FIXED)
|
|
snprint(buf, sizeof(buf), "va %#p %#p fixed %#p\n", g->s->base, g->s->top-g->s->base,
|
|
g->s->map[0]->pages[0]->pa);
|
|
else if((g->s->type&SG_TYPE) == SG_STICKY)
|
|
snprint(buf, sizeof(buf), "va %#p %#p sticky\n", g->s->base, g->s->top-g->s->base);
|
|
else
|
|
snprint(buf, sizeof(buf), "va %#p %#p\n", g->s->base, g->s->top-g->s->base);
|
|
return readstr(voff, a, n, buf);
|
|
case Qdata:
|
|
return segio(g, g->s, a, n, voff, 1);
|
|
default:
|
|
panic("segmentread");
|
|
}
|
|
return 0; /* not reached */
|
|
}
|
|
|
|
static long
|
|
segmentwrite(Chan *c, void *a, long n, vlong voff)
|
|
{
|
|
Cmdbuf *cb;
|
|
Globalseg *g;
|
|
uintptr va, top, len;
|
|
|
|
if(c->qid.type == QTDIR)
|
|
error(Eperm);
|
|
|
|
g = c->aux;
|
|
switch(TYPE(c)){
|
|
case Qctl:
|
|
cb = parsecmd(a, n);
|
|
if(waserror()){
|
|
free(cb);
|
|
nexterror();
|
|
}
|
|
if(cb->nf > 0 && strcmp(cb->f[0], "va") == 0){
|
|
if(g->s != nil)
|
|
error("already has a virtual address");
|
|
if(cb->nf < 3)
|
|
error(Ebadarg);
|
|
va = strtoull(cb->f[1], 0, 0);
|
|
len = strtoull(cb->f[2], 0, 0);
|
|
top = PGROUND(va + len);
|
|
va = va&~(BY2PG-1);
|
|
if(va == 0 || top > USTKTOP || top <= va)
|
|
error(Ebadarg);
|
|
len = top - va;
|
|
if(len > SEGMAXSIZE)
|
|
error(Enovmem);
|
|
if(cb->nf >= 4 && strcmp(cb->f[3], "fixed") == 0){
|
|
if(!iseve())
|
|
error(Eperm);
|
|
g->s = fixedseg(va, len/BY2PG);
|
|
} else if(cb->nf >= 4 && strcmp(cb->f[3], "sticky") == 0){
|
|
Segment *s;
|
|
|
|
if(!iseve())
|
|
error(Eperm);
|
|
s = newseg(SG_STICKY, va, len/BY2PG);
|
|
if(waserror()){
|
|
putseg(s);
|
|
nexterror();
|
|
}
|
|
for(; va < s->top; va += BY2PG)
|
|
segpage(s, newpage(1, nil, va));
|
|
poperror();
|
|
g->s = s;
|
|
} else
|
|
g->s = newseg(SG_SHARED, va, len/BY2PG);
|
|
} else
|
|
error(Ebadctl);
|
|
free(cb);
|
|
poperror();
|
|
return n;
|
|
case Qdata:
|
|
return segio(g, g->s, a, n, voff, 0);
|
|
default:
|
|
panic("segmentwrite");
|
|
}
|
|
return 0; /* not reached */
|
|
}
|
|
|
|
static int
|
|
segmentwstat(Chan *c, uchar *dp, int n)
|
|
{
|
|
Globalseg *g;
|
|
Dir *d;
|
|
|
|
if(c->qid.type == QTDIR)
|
|
error(Eperm);
|
|
|
|
g = getgseg(c);
|
|
if(waserror()){
|
|
putgseg(g);
|
|
nexterror();
|
|
}
|
|
if(strcmp(g->uid, up->user) && !iseve())
|
|
error(Eperm);
|
|
d = smalloc(sizeof(Dir)+n);
|
|
if(waserror()){
|
|
free(d);
|
|
nexterror();
|
|
}
|
|
n = convM2D(dp, n, &d[0], (char*)&d[1]);
|
|
if(n == 0)
|
|
error(Eshortstat);
|
|
if(!emptystr(d->uid))
|
|
kstrdup(&g->uid, d->uid);
|
|
if(d->mode != ~0UL)
|
|
g->perm = d->mode&0777;
|
|
free(d);
|
|
poperror();
|
|
putgseg(g);
|
|
poperror();
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* called by segattach()
|
|
*/
|
|
static Segment*
|
|
globalsegattach(char *name)
|
|
{
|
|
int x;
|
|
Globalseg *g;
|
|
Segment *s;
|
|
|
|
lock(&globalseglock);
|
|
if(waserror()){
|
|
unlock(&globalseglock);
|
|
nexterror();
|
|
}
|
|
for(x = 0; x < nelem(globalseg); x++){
|
|
g = globalseg[x];
|
|
if(g != nil && strcmp(g->name, name) == 0)
|
|
goto Found;
|
|
}
|
|
unlock(&globalseglock);
|
|
poperror();
|
|
return nil;
|
|
Found:
|
|
devpermcheck(g->uid, g->perm, ORDWR);
|
|
s = g->s;
|
|
if(s == nil)
|
|
error("global segment not assigned a virtual address");
|
|
incref(s);
|
|
unlock(&globalseglock);
|
|
poperror();
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* allocate a fixed segment with sequential run of of adjacent
|
|
* user memory pages.
|
|
*/
|
|
static Segment*
|
|
fixedseg(uintptr va, ulong len)
|
|
{
|
|
KMap *k;
|
|
Segment *s;
|
|
Page **f, *p, *l, *h, *t;
|
|
ulong n, i;
|
|
int color;
|
|
|
|
s = newseg(SG_FIXED, va, len);
|
|
if(waserror()){
|
|
putseg(s);
|
|
nexterror();
|
|
}
|
|
lock(&palloc);
|
|
i = 0;
|
|
l = palloc.pages;
|
|
color = getpgcolor(va);
|
|
for(n = palloc.user; n >= len; n--, l++){
|
|
if(l->ref != 0 || i != 0 && (l[-1].pa+BY2PG) != l->pa || i == 0 && l->color != color){
|
|
Retry:
|
|
i = 0;
|
|
continue;
|
|
}
|
|
if(++i < len)
|
|
continue;
|
|
|
|
i = 0;
|
|
h = t = nil;
|
|
f = &palloc.head;
|
|
while((p = *f) != nil){
|
|
if(p > &l[-len] && p <= l){
|
|
*f = p->next;
|
|
if((p->next = h) == nil)
|
|
t = p;
|
|
h = p;
|
|
if(++i < len)
|
|
continue;
|
|
break;
|
|
}
|
|
f = &p->next;
|
|
}
|
|
|
|
if(i != len){
|
|
if(h != nil){
|
|
t->next = palloc.head;
|
|
palloc.head = h;
|
|
}
|
|
goto Retry;
|
|
}
|
|
palloc.freecount -= i;
|
|
unlock(&palloc);
|
|
|
|
p = &l[-len];
|
|
do {
|
|
p++;
|
|
p->ref = 1;
|
|
p->va = va;
|
|
p->modref = 0;
|
|
p->txtflush = ~0;
|
|
|
|
k = kmap(p);
|
|
memset((void*)VA(k), 0, BY2PG);
|
|
kunmap(k);
|
|
|
|
segpage(s, p);
|
|
va += BY2PG;
|
|
} while(p != l);
|
|
poperror();
|
|
return s;
|
|
}
|
|
unlock(&palloc);
|
|
error(Enomem);
|
|
return nil;
|
|
}
|
|
|
|
Dev segmentdevtab = {
|
|
'g',
|
|
"segment",
|
|
|
|
devreset,
|
|
segmentinit,
|
|
devshutdown,
|
|
segmentattach,
|
|
segmentwalk,
|
|
segmentstat,
|
|
segmentopen,
|
|
segmentcreate,
|
|
segmentclose,
|
|
segmentread,
|
|
devbread,
|
|
segmentwrite,
|
|
devbwrite,
|
|
segmentremove,
|
|
segmentwstat,
|
|
};
|
|
|