Commit d28acc42 authored by Russ Cox's avatar Russ Cox

first cut at multithreading. works on Linux.

* kick off new os procs (machs) as needed
* add sys·sleep for testing
* add Lock, Rendez
* properly lock mal, sys·newproc, scheduler
* linux syscall arg #4 is in R10, not CX
* chans are not multithread-safe yet
* multithreading disabled by default;
  set $gomaxprocs=2 (or 1000) to turn it on

This should build on OS X but may not.
Rob and I will fix soon after submitting.

TBR=r
OCL=13784
CL=13842
parent f4392990
...@@ -64,6 +64,7 @@ func writefile(string, string) (bool); // write string into file; boolean status ...@@ -64,6 +64,7 @@ func writefile(string, string) (bool); // write string into file; boolean status
func bytestorune(*byte, int32, int32) (int32, int32); // convert bytes to runes func bytestorune(*byte, int32, int32) (int32, int32); // convert bytes to runes
func stringtorune(string, int32, int32) (int32, int32); // convert bytes to runes func stringtorune(string, int32, int32) (int32, int32); // convert bytes to runes
func sleep(ms int64);
func exit(int32); func exit(int32);
export export
...@@ -136,5 +137,6 @@ export ...@@ -136,5 +137,6 @@ export
stringtorune stringtorune
// system calls // system calls
sleep
exit exit
; ;
This diff is collapsed.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
/* /*
* System structs for Darwin, amd64 * System structs for Linux, amd64
*/ */
typedef uint64 dev_t; typedef uint64 dev_t;
...@@ -22,6 +22,11 @@ struct timespec { ...@@ -22,6 +22,11 @@ struct timespec {
int64 tv_nsec; int64 tv_nsec;
}; };
struct timeval {
time_t tv_sec;
int64 tv_usec;
};
struct stat { struct stat {
dev_t st_dev; /* ID of device containing file */ dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */ ino_t st_ino; /* inode number */
...@@ -40,3 +45,9 @@ struct stat { ...@@ -40,3 +45,9 @@ struct stat {
}; };
#define O_CREAT 0100 #define O_CREAT 0100
// Linux-specific system calls
int64 futex(uint32*, int32, uint32, struct timespec*, uint32*, uint32);
int64 clone(int32, void*, M*, G*, void(*)(void*), void*);
int64 select(int32, void*, void*, void*, void*);
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "runtime.h" #include "runtime.h"
// TODO locking of select
static int32 debug = 0; static int32 debug = 0;
typedef struct Hchan Hchan; typedef struct Hchan Hchan;
...@@ -30,6 +32,7 @@ struct WaitQ ...@@ -30,6 +32,7 @@ struct WaitQ
struct Hchan struct Hchan
{ {
Lock;
uint32 elemsize; uint32 elemsize;
uint32 dataqsiz; // size of the circular q uint32 dataqsiz; // size of the circular q
uint32 qcount; // total data in the q uint32 qcount; // total data in the q
...@@ -159,6 +162,7 @@ sendchan(Hchan *c, byte *ep, bool *pres) ...@@ -159,6 +162,7 @@ sendchan(Hchan *c, byte *ep, bool *pres)
prints("\n"); prints("\n");
} }
lock(c);
if(c->dataqsiz > 0) if(c->dataqsiz > 0)
goto asynch; goto asynch;
...@@ -169,7 +173,8 @@ sendchan(Hchan *c, byte *ep, bool *pres) ...@@ -169,7 +173,8 @@ sendchan(Hchan *c, byte *ep, bool *pres)
gp = sg->g; gp = sg->g;
gp->param = sg; gp->param = sg;
gp->status = Grunnable; unlock(c);
ready(gp);
if(pres != nil) if(pres != nil)
*pres = true; *pres = true;
...@@ -177,6 +182,7 @@ sendchan(Hchan *c, byte *ep, bool *pres) ...@@ -177,6 +182,7 @@ sendchan(Hchan *c, byte *ep, bool *pres)
} }
if(pres != nil) { if(pres != nil) {
unlock(c);
*pres = false; *pres = false;
return; return;
} }
...@@ -187,18 +193,24 @@ sendchan(Hchan *c, byte *ep, bool *pres) ...@@ -187,18 +193,24 @@ sendchan(Hchan *c, byte *ep, bool *pres)
g->param = nil; g->param = nil;
g->status = Gwaiting; g->status = Gwaiting;
enqueue(&c->sendq, sg); enqueue(&c->sendq, sg);
unlock(c);
sys·gosched(); sys·gosched();
lock(c);
sg = g->param; sg = g->param;
freesg(c, sg); freesg(c, sg);
unlock(c);
return; return;
asynch: asynch:
while(c->qcount >= c->dataqsiz) { while(c->qcount >= c->dataqsiz) {
// (rsc) should check for pres != nil
sg = allocsg(c); sg = allocsg(c);
g->status = Gwaiting; g->status = Gwaiting;
enqueue(&c->sendq, sg); enqueue(&c->sendq, sg);
unlock(c);
sys·gosched(); sys·gosched();
lock(c);
} }
if(ep != nil) if(ep != nil)
c->elemalg->copy(c->elemsize, c->senddataq->elem, ep); c->elemalg->copy(c->elemsize, c->senddataq->elem, ep);
...@@ -209,8 +221,10 @@ asynch: ...@@ -209,8 +221,10 @@ asynch:
if(sg != nil) { if(sg != nil) {
gp = sg->g; gp = sg->g;
freesg(c, sg); freesg(c, sg);
gp->status = Grunnable; unlock(c);
} ready(gp);
}else
unlock(c);
} }
static void static void
...@@ -225,6 +239,7 @@ chanrecv(Hchan* c, byte *ep, bool* pres) ...@@ -225,6 +239,7 @@ chanrecv(Hchan* c, byte *ep, bool* pres)
prints("\n"); prints("\n");
} }
lock(c);
if(c->dataqsiz > 0) if(c->dataqsiz > 0)
goto asynch; goto asynch;
...@@ -234,7 +249,8 @@ chanrecv(Hchan* c, byte *ep, bool* pres) ...@@ -234,7 +249,8 @@ chanrecv(Hchan* c, byte *ep, bool* pres)
gp = sg->g; gp = sg->g;
gp->param = sg; gp->param = sg;
gp->status = Grunnable; unlock(c);
ready(gp);
if(pres != nil) if(pres != nil)
*pres = true; *pres = true;
...@@ -242,6 +258,7 @@ chanrecv(Hchan* c, byte *ep, bool* pres) ...@@ -242,6 +258,7 @@ chanrecv(Hchan* c, byte *ep, bool* pres)
} }
if(pres != nil) { if(pres != nil) {
unlock(c);
*pres = false; *pres = false;
return; return;
} }
...@@ -250,11 +267,14 @@ chanrecv(Hchan* c, byte *ep, bool* pres) ...@@ -250,11 +267,14 @@ chanrecv(Hchan* c, byte *ep, bool* pres)
g->param = nil; g->param = nil;
g->status = Gwaiting; g->status = Gwaiting;
enqueue(&c->recvq, sg); enqueue(&c->recvq, sg);
unlock(c);
sys·gosched(); sys·gosched();
lock(c);
sg = g->param; sg = g->param;
c->elemalg->copy(c->elemsize, ep, sg->elem); c->elemalg->copy(c->elemsize, ep, sg->elem);
freesg(c, sg); freesg(c, sg);
unlock(c);
return; return;
asynch: asynch:
...@@ -262,7 +282,9 @@ asynch: ...@@ -262,7 +282,9 @@ asynch:
sg = allocsg(c); sg = allocsg(c);
g->status = Gwaiting; g->status = Gwaiting;
enqueue(&c->recvq, sg); enqueue(&c->recvq, sg);
unlock(c);
sys·gosched(); sys·gosched();
lock(c);
} }
c->elemalg->copy(c->elemsize, ep, c->recvdataq->elem); c->elemalg->copy(c->elemsize, ep, c->recvdataq->elem);
c->recvdataq = c->recvdataq->link; c->recvdataq = c->recvdataq->link;
...@@ -271,8 +293,10 @@ asynch: ...@@ -271,8 +293,10 @@ asynch:
if(sg != nil) { if(sg != nil) {
gp = sg->g; gp = sg->g;
freesg(c, sg); freesg(c, sg);
gp->status = Grunnable; unlock(c);
} ready(gp);
}else
unlock(c);
} }
// chansend1(hchan *chan any, elem any); // chansend1(hchan *chan any, elem any);
...@@ -571,6 +595,8 @@ sys·selectgo(Select *sel) ...@@ -571,6 +595,8 @@ sys·selectgo(Select *sel)
} }
// send and recv paths to sleep for a rendezvous // send and recv paths to sleep for a rendezvous
// (rsc) not correct to set Gwaiting after queueing;
// might already have been readied.
g->status = Gwaiting; g->status = Gwaiting;
sys·gosched(); sys·gosched();
...@@ -619,7 +645,7 @@ gotr: ...@@ -619,7 +645,7 @@ gotr:
c->elemalg->copy(c->elemsize, cas->u.elemp, sg->elem); c->elemalg->copy(c->elemsize, cas->u.elemp, sg->elem);
gp = sg->g; gp = sg->g;
gp->param = sg; gp->param = sg;
gp->status = Grunnable; ready(gp);
goto retc; goto retc;
gots: gots:
...@@ -636,7 +662,7 @@ gots: ...@@ -636,7 +662,7 @@ gots:
c->elemalg->copy(c->elemsize, sg->elem, cas->u.elem); c->elemalg->copy(c->elemsize, sg->elem, cas->u.elem);
gp = sg->g; gp = sg->g;
gp->param = sg; gp->param = sg;
gp->status = Grunnable; ready(gp);
retc: retc:
if(sel->ncase >= 1 && sel->ncase < nelem(selfree)) { if(sel->ncase >= 1 && sel->ncase < nelem(selfree)) {
......
...@@ -4,18 +4,58 @@ ...@@ -4,18 +4,58 @@
#include "runtime.h" #include "runtime.h"
typedef struct Sched Sched;
M m0;
G g0; // idle goroutine for m0
// Maximum number of os procs (M's) to kick off.
// Can override with $gomaxprocs environment variable.
// For now set to 1 (single-threaded), because not
// everything is properly locked (e.g., chans) and because
// Darwin's multithreading code isn't implemented.
int32 gomaxprocs = 1;
static int32 debug = 0; static int32 debug = 0;
struct Sched {
G *runhead;
G *runtail;
int32 nwait;
int32 nready;
int32 ng;
int32 nm;
M *wait;
Lock;
};
Sched sched;
void void
sys·goexit(void) sys·goexit(void)
{ {
//prints("goexit goid="); if(debug){
//sys·printint(g->goid); prints("goexit goid=");
//prints("\n"); sys·printint(g->goid);
prints("\n");
}
g->status = Gdead; g->status = Gdead;
sys·gosched(); sys·gosched();
} }
void
schedinit(void)
{
byte *p;
extern int32 getenvc(void);
p = getenv("gomaxprocs");
if(p && '0' <= *p && *p <= '9')
gomaxprocs = atoi(p);
sched.nm = 1;
sched.nwait = 1;
}
void void
sys·newproc(int32 siz, byte* fn, byte* arg0) sys·newproc(int32 siz, byte* fn, byte* arg0)
{ {
...@@ -64,10 +104,13 @@ sys·newproc(int32 siz, byte* fn, byte* arg0) ...@@ -64,10 +104,13 @@ sys·newproc(int32 siz, byte* fn, byte* arg0)
newg->sched.SP = sp; newg->sched.SP = sp;
newg->sched.PC = fn; newg->sched.PC = fn;
lock(&sched);
sched.ng++;
goidgen++; goidgen++;
newg->goid = goidgen; newg->goid = goidgen;
unlock(&sched);
newg->status = Grunnable; ready(newg);
//prints(" goid="); //prints(" goid=");
//sys·printint(newg->goid); //sys·printint(newg->goid);
...@@ -80,7 +123,7 @@ tracebackothers(G *me) ...@@ -80,7 +123,7 @@ tracebackothers(G *me)
G *g; G *g;
for(g = allg; g != nil; g = g->alllink) { for(g = allg; g != nil; g = g->alllink) {
if(g == me) if(g == me || g->status == Gdead)
continue; continue;
prints("\ngoroutine "); prints("\ngoroutine ");
sys·printint(g->goid); sys·printint(g->goid);
...@@ -89,46 +132,175 @@ tracebackothers(G *me) ...@@ -89,46 +132,175 @@ tracebackothers(G *me)
} }
} }
void newmach(void);
static void
readylocked(G *g)
{
g->status = Grunnable;
if(sched.runhead == nil)
sched.runhead = g;
else
sched.runtail->runlink = g;
sched.runtail = g;
g->runlink = nil;
sched.nready++;
// Don't wake up another scheduler.
// This only gets called when we're
// about to reschedule anyway.
}
static Lock print;
void
ready(G *g)
{
M *mm;
// gp might be running on another scheduler.
// (E.g., it queued and then we decided to wake it up
// before it had a chance to sys·gosched().)
// Grabbing the runlock ensures that it is not running elsewhere.
// You can delete the if check, but don't delete the
// lock/unlock sequence (being able to grab the lock
// means the proc has gone to sleep).
lock(&g->runlock);
if(g->status == Grunnable || g->status == Grunning)
*(int32*)0x1023 = 0x1023;
lock(&sched);
g->status = Grunnable;
if(sched.runhead == nil)
sched.runhead = g;
else
sched.runtail->runlink = g;
sched.runtail = g;
g->runlink = nil;
unlock(&g->runlock);
sched.nready++;
if(sched.nready > sched.nwait)
if(gomaxprocs == 0 || sched.nm < gomaxprocs){
if(debug){
prints("new scheduler: ");
sys·printint(sched.nready);
prints(" > ");
sys·printint(sched.nwait);
prints("\n");
}
sched.nwait++;
newmach();
}
if(sched.wait){
mm = sched.wait;
sched.wait = mm->waitlink;
rwakeupandunlock(&mm->waitr);
}else
unlock(&sched);
}
extern void p0(void), p1(void);
G* G*
nextgoroutine(void) nextgoroutine(void)
{ {
G *gp; G *gp;
gp = m->lastg; while((gp = sched.runhead) == nil){
if(gp == nil) if(debug){
gp = allg; prints("nextgoroutine runhead=nil ng=");
sys·printint(sched.ng);
for(gp=gp->alllink; gp!=nil; gp=gp->alllink) { prints("\n");
if(gp->status == Grunnable) {
m->lastg = gp;
return gp;
} }
if(sched.ng == 0)
return nil;
m->waitlink = sched.wait;
m->waitr.l = &sched.Lock;
sched.wait = m;
sched.nwait++;
if(sched.nm == sched.nwait)
prints("all goroutines are asleep - deadlock!\n");
rsleep(&m->waitr);
sched.nwait--;
} }
for(gp=allg; gp!=nil; gp=gp->alllink) { sched.nready--;
if(gp->status == Grunnable) { sched.runhead = gp->runlink;
m->lastg = gp; return gp;
return gp;
}
}
return nil;
} }
void void
scheduler(void) scheduler(void)
{ {
G* gp; G* gp;
m->pid = getprocid();
gosave(&m->sched); gosave(&m->sched);
lock(&sched);
if(m->curg == nil){
// Brand new scheduler; nwait counts us.
// Not anymore.
sched.nwait--;
}else{
gp = m->curg;
gp->m = nil;
switch(gp->status){
case Gdead:
sched.ng--;
if(debug){
prints("sched: dead: ");
sys·printint(sched.ng);
prints("\n");
}
break;
case Grunning:
readylocked(gp);
break;
case Grunnable:
// don't want to see this
*(int32*)0x456 = 0x234;
break;
}
unlock(&gp->runlock);
}
gp = nextgoroutine(); gp = nextgoroutine();
if(gp == nil) { if(gp == nil) {
// prints("sched: no more work\n"); // prints("sched: no more work\n");
sys·exit(0); sys·exit(0);
} }
unlock(&sched);
lock(&gp->runlock);
gp->status = Grunning;
m->curg = gp; m->curg = gp;
gp->m = m;
g = gp; g = gp;
gogo(&gp->sched); gogo(&gp->sched);
} }
void
newmach(void)
{
M *mm;
byte *stk, *stktop;
int64 ret;
sched.nm++;
if(!(sched.nm&(sched.nm-1))){
sys·printint(sched.nm);
prints(" threads\n");
}
mm = mal(sizeof(M)+sizeof(G)+1024+104);
sys·memclr((byte*)mm, sizeof(M));
mm->g0 = (G*)(mm+1);
sys·memclr((byte*)mm->g0, sizeof(G));
stk = (byte*)mm->g0 + 104;
stktop = stk + 1024;
mm->g0->stackguard = stk;
mm->g0->stackbase = stktop;
newosproc(mm, mm->g0, stktop, (void(*)(void*))scheduler, nil);
}
void void
gom0init(void) gom0init(void)
{ {
...@@ -138,10 +310,11 @@ gom0init(void) ...@@ -138,10 +310,11 @@ gom0init(void)
void void
sys·gosched(void) sys·gosched(void)
{ {
if(gosave(&g->sched)) if(gosave(&g->sched) == 0){
return; // (rsc) signal race here?
g = m->g0; g = m->g0;
gogo(&m->sched); gogo(&m->sched);
}
} }
// //
......
...@@ -14,9 +14,9 @@ TEXT _rt0_amd64(SB),7,$-8 ...@@ -14,9 +14,9 @@ TEXT _rt0_amd64(SB),7,$-8
MOVQ AX, 16(SP) MOVQ AX, 16(SP)
MOVQ BX, 24(SP) MOVQ BX, 24(SP)
// allocate the per-user and per-mach blocks // set the per-goroutine and per-mach registers
LEAQ m0<>(SB), R14 // dedicated m. register LEAQ m0(SB), R14 // dedicated m. register
LEAQ g0(SB), R15 // dedicated g. register LEAQ g0(SB), R15 // dedicated g. register
MOVQ R15, 0(R14) // m has pointer to its g0 MOVQ R15, 0(R14) // m has pointer to its g0
...@@ -33,8 +33,9 @@ TEXT _rt0_amd64(SB),7,$-8 ...@@ -33,8 +33,9 @@ TEXT _rt0_amd64(SB),7,$-8
MOVQ 24(SP), AX // copy argv MOVQ 24(SP), AX // copy argv
MOVQ AX, 8(SP) MOVQ AX, 8(SP)
CALL args(SB) CALL args(SB)
CALL schedinit(SB)
CALL main·init_function(SB) // initialization CALL main·init_function(SB) // initialization
// create a new goroutine to start program // create a new goroutine to start program
PUSHQ $main·main(SB) // entry PUSHQ $main·main(SB) // entry
...@@ -102,4 +103,22 @@ TEXT setspgoto(SB), 7, $0 ...@@ -102,4 +103,22 @@ TEXT setspgoto(SB), 7, $0
POPQ AX POPQ AX
RET RET
GLOBL m0<>(SB),$64 // bool cas(int32 *val, int32 old, int32 new)
// Atomically:
// if(*val == old){
// *val = new;
// return 1;
// }else
// return 0;
TEXT cas(SB), 7, $0
MOVQ 8(SP), BX
MOVL 16(SP), AX
MOVL 20(SP), CX
LOCK
CMPXCHGL CX, 0(BX)
JZ 3(PC)
MOVL $0, AX
RET
MOVL $1, AX
RET
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#include "runtime.h" #include "runtime.h"
#include "signals.h" #include "signals.h"
typedef uint64 __uint64_t; typedef uint64 __uint64_t;
/* From /usr/include/mach/i386/_structs.h */ /* From /usr/include/mach/i386/_structs.h */
...@@ -174,3 +173,75 @@ initsig(void) ...@@ -174,3 +173,75 @@ initsig(void)
sys·sigaction(i, &a, (void*)0); sys·sigaction(i, &a, (void*)0);
} }
} }
static void
unimplemented(int8 *name)
{
prints(name);
prints(" not implemented\n");
*(int32*)1231 = 1231;
}
void
sys·sleep(int64 ms)
{
unimplemented("sleep");
}
void
lock(Lock *l)
{
if(xadd(&l->key, 1) == 1)
return;
unimplemented("lock wait");
}
void
unlock(Lock *l)
{
if(xadd(&l->key, -1) == 0)
return;
unimplemented("unlock wakeup");
}
void
rsleep(Rendez *r)
{
unimplemented("rsleep");
// dumb implementation:
r->sleeping = 1;
unlock(r->l);
while(r->sleeping)
;
lock(r->l);
}
void
rwakeup(Rendez *r)
{
unimplemented("rwakeup");
// dumb implementation:
r->sleeping = 0;
}
void
rwakeupandunlock(Rendez *r)
{
// dumb implementation:
rwakeup(r);
unlock(r->l);
}
void
newosproc(M *mm, G *gg, void *stk, void (*fn)(void*), void *arg)
{
unimplemented("newosproc");
}
int32
getprocid(void)
{
return 0;
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
#include "runtime.h" #include "runtime.h"
#include "amd64_linux.h"
#include "signals.h" #include "signals.h"
/* From /usr/include/asm-x86_64/sigcontext.h */ /* From /usr/include/asm-x86_64/sigcontext.h */
...@@ -161,7 +162,7 @@ sighandler(int32 sig, siginfo* info, void** context) ...@@ -161,7 +162,7 @@ sighandler(int32 sig, siginfo* info, void** context)
} }
sigaction a; static sigaction a;
void void
initsig(void) initsig(void)
...@@ -177,3 +178,235 @@ initsig(void) ...@@ -177,3 +178,235 @@ initsig(void)
sys·rt_sigaction(i, &a, (void*)0, 8); sys·rt_sigaction(i, &a, (void*)0, 8);
} }
} }
// Linux futex. The simple cases really are simple:
//
// futex(addr, FUTEX_WAIT, val, duration, _, _)
// Inside the kernel, atomically check that *addr == val
// and go to sleep for at most duration.
//
// futex(addr, FUTEX_WAKE, val, _, _, _)
// Wake up at least val procs sleeping on addr.
//
// (Of course, they have added more complicated things since then.)
enum
{
FUTEX_WAIT = 0,
FUTEX_WAKE = 1,
EINTR = 4,
EAGAIN = 11,
};
// TODO(rsc) I tried using 1<<40 here but it woke up (-ETIMEDOUT).
// I wonder if the timespec that gets to the kernel
// actually has two 32-bit numbers in it, so that
// a 64-bit 1<<40 ends up being 0 seconds,
// 1<<8 nanoseconds.
static struct timespec longtime =
{
1<<30, // 34 years
0
};
static void
efutex(uint32 *addr, int32 op, int32 val, struct timespec *ts)
{
int64 ret;
again:
ret = futex(addr, op, val, ts, nil, 0);
// These happen when you use a debugger, among other times.
if(ret == -EAGAIN || ret == -EINTR){
// If we were sleeping, it's okay to wake up early.
if(op == FUTEX_WAIT)
return;
// If we were waking someone up, we don't know
// whether that succeeded, so wake someone else up too.
if(op == FUTEX_WAKE){
prints("futexwake ");
sys·printint(ret);
prints("\n");
goto again;
}
}
if(ret < 0){
prints("futex error addr=");
sys·printpointer(addr);
prints(" op=");
sys·printint(op);
prints(" val=");
sys·printint(val);
prints(" ts=");
sys·printpointer(ts);
prints(" returned ");
sys·printint(-ret);
prints("\n");
*(int32*)101 = 202;
}
}
// Lock and unlock.
// A zeroed Lock is unlocked (no need to initialize each lock).
// The l->key is either 0 (unlocked), 1 (locked), or >=2 (contended).
void
lock(Lock *l)
{
uint32 v;
if(l->key != 0) *(int32*)0x1001 = 0x1001;
l->key = 1;
return;
for(;;){
// Try for lock. If we incremented it from 0 to 1, we win.
if((v=xadd(&l->key, 1)) == 1)
return;
// We lose. It was already >=1 and is now >=2.
// Use futex to atomically check that the value is still
// what we think it is and go to sleep.
efutex(&l->key, FUTEX_WAIT, v, &longtime);
}
}
void
unlock(Lock *l)
{
uint32 v;
if(l->key != 1) *(int32*)0x1002 = 0x1002;
l->key = 0;
return;
// Unlock the lock. If we decremented from 1 to 0, wasn't contended.
if((v=xadd(&l->key, -1)) == 0)
return;
// The lock was contended. Mark it as unlocked and wake a waiter.
l->key = 0;
efutex(&l->key, FUTEX_WAKE, 1, nil);
}
// Sleep and wakeup (see description in runtime.h)
void
rsleep(Rendez *r)
{
// Record that we're about to go to sleep and drop the lock.
r->sleeping = 1;
unlock(r->l);
// Go to sleep if r->sleeping is still 1.
efutex(&r->sleeping, FUTEX_WAIT, 1, &longtime);
// Reacquire the lock.
lock(r->l);
}
void
rwakeup(Rendez *r)
{
if(!r->sleeping)
return;
// Clear the sleeping flag in case sleeper
// is between unlock and futex.
r->sleeping = 0;
// Wake up if actually made it to sleep.
efutex(&r->sleeping, FUTEX_WAKE, 1, nil);
}
// Like rwakeup(r), unlock(r->l), but drops the lock before
// waking the other proc. This reduces bouncing back and forth
// in the scheduler: the first thing the other proc wants to do
// is acquire r->l, so it helps to unlock it before we wake him.
void
rwakeupandunlock(Rendez *r)
{
int32 wassleeping;
if(!r->sleeping){
unlock(r->l);
return;
}
r->sleeping = 0;
unlock(r->l);
efutex(&r->sleeping, FUTEX_WAKE, 1, nil);
}
enum
{
CLONE_VM = 0x100,
CLONE_FS = 0x200,
CLONE_FILES = 0x400,
CLONE_SIGHAND = 0x800,
CLONE_PTRACE = 0x2000,
CLONE_VFORK = 0x4000,
CLONE_PARENT = 0x8000,
CLONE_THREAD = 0x10000,
CLONE_NEWNS = 0x20000,
CLONE_SYSVSEM = 0x40000,
CLONE_SETTLS = 0x80000,
CLONE_PARENT_SETTID = 0x100000,
CLONE_CHILD_CLEARTID = 0x200000,
CLONE_UNTRACED = 0x800000,
CLONE_CHILD_SETTID = 0x1000000,
CLONE_STOPPED = 0x2000000,
CLONE_NEWUTS = 0x4000000,
CLONE_NEWIPC = 0x8000000,
};
void
newosproc(M *mm, G *gg, void *stk, void (*fn)(void*), void *arg)
{
int64 ret;
int32 flags;
flags = CLONE_PARENT /* getppid doesn't change in child */
| CLONE_VM /* share memory */
| CLONE_FS /* share cwd, etc */
| CLONE_FILES /* share fd table */
| CLONE_SIGHAND /* share sig handler table */
| CLONE_PTRACE /* revisit - okay for now */
| CLONE_THREAD /* revisit - okay for now */
;
if(0){
prints("newosproc stk=");
sys·printpointer(stk);
prints(" mm=");
sys·printpointer(mm);
prints(" gg=");
sys·printpointer(gg);
prints(" fn=");
sys·printpointer(fn);
prints(" arg=");
sys·printpointer(arg);
prints(" clone=");
sys·printpointer(clone);
prints("\n");
}
ret = clone(flags, stk, mm, gg, fn, arg);
if(ret < 0)
*(int32*)123 = 123;
}
void
sys·sleep(int64 ms)
{
struct timeval tv;
tv.tv_sec = ms/1000;
tv.tv_usec = ms%1000 * 1000;
select(0, nil, nil, nil, &tv);
}
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
#include "runtime.h" #include "runtime.h"
G g0; // idle goroutine
int32 debug = 0; int32 debug = 0;
void void
...@@ -24,10 +23,6 @@ sys·panicl(int32 lno) ...@@ -24,10 +23,6 @@ sys·panicl(int32 lno)
sys·exit(2); sys·exit(2);
} }
static uint8* hunk;
static uint32 nhunk;
static uint64 nmmap;
static uint64 nmal;
enum enum
{ {
NHUNK = 20<<20, NHUNK = 20<<20,
...@@ -76,42 +71,51 @@ rnd(uint32 n, uint32 m) ...@@ -76,42 +71,51 @@ rnd(uint32 n, uint32 m)
return n; return n;
} }
static byte* static void*
brk(uint32 n) brk(uint32 n)
{ {
byte* v; byte *v;
v = sys·mmap(nil, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0); v = sys·mmap(nil, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0);
sys·memclr(v, n); m->mem.nmmap += n;
nmmap += n;
return v; return v;
} }
void* void*
mal(uint32 n) mal(uint32 n)
{ {
byte* v; byte* v;
Mem *mem;
// round to keep everything 64-bit aligned // round to keep everything 64-bit aligned
n = rnd(n, 8); n = rnd(n, 8);
nmal += n;
// do we have enough in contiguous hunk
if(n > nhunk) {
// if it is big allocate it separately // be careful. calling any function might invoke
if(n > NHUNK) // mal to allocate more stack.
return brk(n); if(n > NHUNK) {
// this call is okay - calling mal recursively
// allocate a new contiguous hunk // won't change anything we depend on.
hunk = brk(NHUNK); v = brk(n);
nhunk = NHUNK; } else {
// allocate a new hunk if this one is too small
if(n > m->mem.nhunk) {
// better not to call brk here - it might grow the stack,
// causing a call to mal and the allocation of a
// new hunk behind our backs. then we'd toss away
// almost all of that new hunk and replace it.
// that'd just be a memory leak - the code would still run.
m->mem.hunk =
sys·mmap(nil, NHUNK, PROT_READ|PROT_WRITE,
MAP_ANON|MAP_PRIVATE, 0, 0);
m->mem.nhunk = NHUNK;
m->mem.nmmap += NHUNK;
}
v = m->mem.hunk;
m->mem.hunk += n;
m->mem.nhunk -= n;
} }
m->mem.nmal += n;
// allocate from the contiguous hunk
v = hunk;
hunk += n;
nhunk -= n;
return v; return v;
} }
...@@ -491,6 +495,44 @@ args(int32 c, uint8 **v) ...@@ -491,6 +495,44 @@ args(int32 c, uint8 **v)
; ;
} }
int32
getenvc(void)
{
return envc;
}
byte*
getenv(int8 *s)
{
int32 i, j, len;
byte *v, *bs;
bs = (byte*)s;
len = findnull(s);
for(i=0; i<envc; i++){
v = envv[i];
for(j=0; j<len; j++)
if(bs[j] != v[j])
goto nomatch;
if(v[len] != '=')
goto nomatch;
return v+len+1;
nomatch:;
}
return nil;
}
int32
atoi(byte *p)
{
int32 n;
n = 0;
while('0' <= *p && *p <= '9')
n = n*10 + *p++ - '0';
return n;
}
//func argc() int32; // return number of arguments //func argc() int32; // return number of arguments
void void
sys·argc(int32 v) sys·argc(int32 v)
...@@ -579,9 +621,35 @@ check(void) ...@@ -579,9 +621,35 @@ check(void)
if(sizeof(k) != 8) throw("bad k"); if(sizeof(k) != 8) throw("bad k");
if(sizeof(l) != 8) throw("bad l"); if(sizeof(l) != 8) throw("bad l");
// prints(1"check ok\n"); // prints(1"check ok\n");
uint32 z;
z = 1;
if(!cas(&z, 1, 2))
throw("cas1");
if(z != 2)
throw("cas2");
z = 4;
if(cas(&z, 5, 6))
throw("cas3");
if(z != 4)
throw("cas4");
initsig(); initsig();
} }
uint32
xadd(uint32 *val, uint32 delta)
{
uint32 v;
for(;;){
v = *val;
if(cas(val, v, v+delta))
return v+delta;
}
}
/* /*
* map and chan helpers for * map and chan helpers for
* dealing with unknown types * dealing with unknown types
......
...@@ -40,8 +40,11 @@ typedef struct Map Map; ...@@ -40,8 +40,11 @@ typedef struct Map Map;
typedef struct Gobuf Gobuf; typedef struct Gobuf Gobuf;
typedef struct G G; typedef struct G G;
typedef struct M M; typedef struct M M;
typedef struct Stktop Stktop; typedef struct Stktop Stktop;
typedef struct Alg Alg; typedef struct Alg Alg;
typedef struct Lock Lock;
typedef struct Rendez Rendez;
typedef struct Mem Mem;
/* /*
* per cpu declaration * per cpu declaration
...@@ -57,6 +60,7 @@ enum ...@@ -57,6 +60,7 @@ enum
// G status // G status
Gidle, Gidle,
Grunnable, Grunnable,
Grunning,
Gwaiting, Gwaiting,
Gdead, Gdead,
}; };
...@@ -69,6 +73,15 @@ enum ...@@ -69,6 +73,15 @@ enum
/* /*
* structures * structures
*/ */
struct Lock
{
uint32 key;
};
struct Rendez
{
Lock* l;
uint32 sleeping; // someone is sleeping (Linux)
};
struct String struct String
{ {
int32 len; int32 len;
...@@ -111,6 +124,16 @@ struct G ...@@ -111,6 +124,16 @@ struct G
int16 status; int16 status;
int32 goid; int32 goid;
int32 selgen; // valid sudog pointer int32 selgen; // valid sudog pointer
G* runlink;
Lock runlock;
M* m; // for debuggers
};
struct Mem
{
uint8* hunk;
uint32 nhunk;
uint64 nmmap;
uint64 nmal;
}; };
struct M struct M
{ {
...@@ -124,6 +147,10 @@ struct M ...@@ -124,6 +147,10 @@ struct M
byte* moresp; byte* moresp;
int32 siz1; int32 siz1;
int32 siz2; int32 siz2;
Rendez waitr;
M* waitlink;
int32 pid; // for debuggers
Mem mem;
}; };
struct Stktop struct Stktop
{ {
...@@ -161,6 +188,7 @@ extern string emptystring; ...@@ -161,6 +188,7 @@ extern string emptystring;
M* allm; M* allm;
G* allg; G* allg;
int32 goidgen; int32 goidgen;
extern int32 gomaxprocs;
/* /*
* common functions and data * common functions and data
...@@ -195,6 +223,37 @@ int32 read(int32, void*, int32); ...@@ -195,6 +223,37 @@ int32 read(int32, void*, int32);
int32 write(int32, void*, int32); int32 write(int32, void*, int32);
void close(int32); void close(int32);
int32 fstat(int32, void*); int32 fstat(int32, void*);
bool cas(uint32*, uint32, uint32);
uint32 xadd(uint32*, uint32);
void exit1(int32);
void ready(G*);
byte* getenv(int8*);
int32 atoi(byte*);
void newosproc(M *mm, G *gg, void *stk, void (*fn)(void*), void *arg);
int32 getprocid(void);
/*
* mutual exclusion locks. in the uncontended case,
* as fast as spin locks (just a few user-level instructions),
* but on the contention path they sleep in the kernel.
*/
void lock(Lock*);
void unlock(Lock*);
void lockinit(Lock*);
/*
* sleep and wakeup.
* a Rendez is somewhere to sleep. it is protected by the lock r->l.
* the caller must acquire r->l, check the condition, and if the
* condition is false, call rsleep. rsleep will atomically drop the lock
* and go to sleep. a subsequent rwakeup (caller must hold r->l)
* will wake up the guy who is rsleeping. the lock keeps rsleep and
* rwakeup from missing each other.
* n.b. only one proc can rsleep on a given rendez at a time.
*/
void rsleep(Rendez*);
void rwakeup(Rendez*);
void rwakeupandunlock(Rendez*);
/* /*
* low level go -called * low level go -called
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// System calls and other sys.stuff for AMD64, Darwin // System calls and other sys.stuff for AMD64, Darwin
// //
// TODO(rsc): Either sys·exit or exit1 is wrong!
TEXT sys·exit(SB),1,$-8 TEXT sys·exit(SB),1,$-8
MOVL 8(SP), DI // arg 1 exit status MOVL 8(SP), DI // arg 1 exit status
MOVL $(0x2000000+1), AX // syscall entry MOVL $(0x2000000+1), AX // syscall entry
...@@ -13,6 +14,13 @@ TEXT sys·exit(SB),1,$-8 ...@@ -13,6 +14,13 @@ TEXT sys·exit(SB),1,$-8
CALL notok(SB) CALL notok(SB)
RET RET
TEXT exit1(SB),1,$-8
MOVL 8(SP), DI // arg 1 exit status
MOVL $(0x2000000+1), AX // syscall entry
SYSCALL
CALL notok(SB)
RET
TEXT sys·write(SB),1,$-8 TEXT sys·write(SB),1,$-8
MOVL 8(SP), DI // arg 1 fid MOVL 8(SP), DI // arg 1 fid
MOVQ 16(SP), SI // arg 2 buf MOVQ 16(SP), SI // arg 2 buf
...@@ -80,7 +88,7 @@ TEXT sigtramp(SB),1,$24 ...@@ -80,7 +88,7 @@ TEXT sigtramp(SB),1,$24
CALL sighandler(SB) CALL sighandler(SB)
RET RET
TEXT sys·mmap(SB),1,$-8 TEXT sys·mmap(SB),7,$-8
MOVQ 8(SP), DI // arg 1 addr MOVQ 8(SP), DI // arg 1 addr
MOVL 16(SP), SI // arg 2 len MOVL 16(SP), SI // arg 2 len
MOVL 20(SP), DX // arg 3 prot MOVL 20(SP), DX // arg 3 prot
...@@ -98,7 +106,7 @@ TEXT notok(SB),1,$-8 ...@@ -98,7 +106,7 @@ TEXT notok(SB),1,$-8
MOVQ BP, (BP) MOVQ BP, (BP)
RET RET
TEXT sys·memclr(SB),1,$-8 TEXT sys·memclr(SB),7,$-8
MOVQ 8(SP), DI // arg 1 addr MOVQ 8(SP), DI // arg 1 addr
MOVL 16(SP), CX // arg 2 count MOVL 16(SP), CX // arg 2 count
ADDL $7, CX ADDL $7, CX
......
...@@ -8,7 +8,13 @@ ...@@ -8,7 +8,13 @@
TEXT sys·exit(SB),1,$0-8 TEXT sys·exit(SB),1,$0-8
MOVL 8(SP), DI MOVL 8(SP), DI
MOVL $60, AX MOVL $231, AX // force all os threads to exit
SYSCALL
RET
TEXT exit1(SB),1,$0-8
MOVL 8(SP), DI
MOVL $60, AX // exit the current os thread
SYSCALL SYSCALL
RET RET
...@@ -61,8 +67,7 @@ TEXT sys·rt_sigaction(SB),1,$0-32 ...@@ -61,8 +67,7 @@ TEXT sys·rt_sigaction(SB),1,$0-32
MOVL 8(SP), DI MOVL 8(SP), DI
MOVQ 16(SP), SI MOVQ 16(SP), SI
MOVQ 24(SP), DX MOVQ 24(SP), DX
MOVQ 32(SP), CX MOVQ 32(SP), R10
MOVL CX, R10
MOVL $13, AX // syscall entry MOVL $13, AX // syscall entry
SYSCALL SYSCALL
RET RET
...@@ -74,11 +79,11 @@ TEXT sigtramp(SB),1,$24-16 ...@@ -74,11 +79,11 @@ TEXT sigtramp(SB),1,$24-16
CALL sighandler(SB) CALL sighandler(SB)
RET RET
TEXT sys·mmap(SB),1,$0-32 TEXT sys·mmap(SB),7,$0-32
MOVQ 8(SP), DI MOVQ 8(SP), DI
MOVL 16(SP), SI MOVL 16(SP), SI
MOVL 20(SP), DX MOVL 20(SP), DX
MOVL 24(SP), CX MOVL 24(SP), R10
MOVL 28(SP), R8 MOVL 28(SP), R8
MOVL 32(SP), R9 MOVL 32(SP), R9
...@@ -102,7 +107,7 @@ TEXT notok(SB),7,$0 ...@@ -102,7 +107,7 @@ TEXT notok(SB),7,$0
MOVQ BP, (BP) MOVQ BP, (BP)
RET RET
TEXT sys·memclr(SB),1,$0-16 TEXT sys·memclr(SB),7,$0-16
MOVQ 8(SP), DI // arg 1 addr MOVQ 8(SP), DI // arg 1 addr
MOVL 16(SP), CX // arg 2 count (cannot be zero) MOVL 16(SP), CX // arg 2 count (cannot be zero)
ADDL $7, CX ADDL $7, CX
...@@ -123,3 +128,74 @@ TEXT sys·setcallerpc+0(SB),1,$0 ...@@ -123,3 +128,74 @@ TEXT sys·setcallerpc+0(SB),1,$0
MOVQ x+8(FP), BX MOVQ x+8(FP), BX
MOVQ BX, -8(AX) // set calling pc MOVQ BX, -8(AX) // set calling pc
RET RET
// int64 futex(int32 *uaddr, int32 op, int32 val,
// struct timespec *timeout, int32 *uaddr2, int32 val2);
TEXT futex(SB),1,$0
MOVQ 8(SP), DI
MOVL 16(SP), SI
MOVL 20(SP), DX
MOVQ 24(SP), R10
MOVQ 32(SP), R8
MOVL 40(SP), R9
MOVL $202, AX
SYSCALL
RET
// int64 clone(int32 flags, void *stack, M *m, G *g, void (*fn)(void*), void *arg);
TEXT clone(SB),7,$0
MOVL 8(SP), DI
MOVQ 16(SP), SI
// Copy m, g, fn, arg off parent stack for use by child.
// Careful: Linux system call clobbers CX and R11.
MOVQ 24(SP), R8
MOVQ 32(SP), R9
MOVQ 40(SP), R12
MOVQ 48(SP), R13
MOVL $56, AX
SYSCALL
// In parent, return.
CMPQ AX, $0
JEQ 2(PC)
RET
// In child, call fn(arg) on new stack
MOVQ SI, SP
MOVQ R8, R14 // m
MOVQ R9, R15 // g
PUSHQ R13
CALL R12
// It shouldn't return. If it does, exit
MOVL $111, DI
MOVL $60, AX
SYSCALL
JMP -3(PC) // keep exiting
// int64 select(int32, void*, void*, void*, void*)
TEXT select(SB),1,$0
MOVL 8(SP), DI
MOVQ 16(SP), SI
MOVQ 24(SP), DX
MOVQ 32(SP), R10
MOVQ 40(SP), R8
MOVL $23, AX
SYSCALL
RET
// Linux allocates each thread its own pid, like Plan 9.
// But the getpid() system call returns the pid of the
// original thread (the one that exec started with),
// no matter which thread asks. This system call,
// which Linux calls gettid, returns the actual pid of
// the calling thread, not the fake one.
//
// int32 getprocid(void)
TEXT getprocid(SB),1,$0
MOVL $186, AX
SYSCALL
RET
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
// //
// func Syscall(trap int64, a1, a2, a3 int64) (r1, r2, err int64); // func Syscall(trap int64, a1, a2, a3 int64) (r1, r2, err int64);
// Trap # in AX, args in DI SI DX, return in AX DX // Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.
TEXT syscall·Syscall(SB),7,$-8 TEXT syscall·Syscall(SB),7,$-8
MOVQ 16(SP), DI MOVQ 16(SP), DI
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment