peter@sugar.UUCP (Peter da Silva) (03/25/88)
In article <1139@PT.CS.CMU.EDU>, edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes: >> >> The debate about CLU iterators misses an important point: CLU iterators are >>coroutines, which C does not have. Coroutines in 'C' are easy to implement, though. Why, this whole O/S we're reading news on (UNIX) is written as a set of coroutines in 'C'. (yes, it's a simplification... but not much of one). I'd like to see the following functions become standard in 'C': COROUTINE -- Build a jmp_buf for a coroutine. int coroutine(jump, entry, stack, stacksize); struct jmp_buf *jump; void (*entry)(); char *stack; int stacksize; This sets up the stack and jmp_buf so that a call to "longjmp(jmp_buf)" will appear to be a call to entry(). It will return an error only if the stack isn't large enough for a small routine that does nothing but call the following function: int switch(from, to, status) struct jmp_buf *from, *to; int status; { int code; if(!(code=setjmp(from))) longjmp(to, status); return code; } Voila! Co-routines! Lightweight processes (best if you have the Berkeley signal handler, I guess, so you could run it off alarms...): struct proc { struct proc *next; struct proc *prev; char *stack; struct jmp_buf context; }; struct proc *runq; /* doubly linked circular queue */ sleep() { struct proc *self; /* do nothing if no procs or I'm alone */ if(!runq) return; if(runq->next == runq) return; self = runq; runq = runq->next; switch(&self->context, &runq->context, 1); } int spawn(entry, stacksize) void (*entry)(); int stacksize; { struct proc *p; if(!(p = malloc(sizeof *p))) return 0; if(!(p->stack = malloc(stacksize))) { free(p); return 0; } if(!coroutine(p, entry, p->stack, stacksize)) { free(p->stack); free(p); return 0; } p->stacksize = p; p->next = runq->next; p->prev = runq; runq->next->prev = p; runq->next = p; return p; } int delete(p) /* note, this version doesn't allow a process to delete itself! */ struct process *p; { if(p==runq) return 0; p->next->prev = p->prev; p->prev->next = p->next; free(p->stack); free(p); } -- -- Peter da Silva `-_-' ...!hoptoad!academ!uhnix1!sugar!peter -- Disclaimer: These U aren't mere opinions... these are *values*.
james@cantuar.UUCP (J. Collier) (03/29/88)
Expires: Sender: Followup-To: Distribution: Keywords: Peter da Silva (peter@sugar.UUCP) writes: >.... >I'd like to see the following functions become standard in 'C': >.... >COROUTINE -- Build a jmp_buf for a coroutine. >.... >This sets up the stack and jmp_buf so that a call to "longjmp(jmp_buf)" >will appear to be a call to entry(). > [implementation outlines deleted] I seem to remember a minor war in the letters to 'Software Practice and Experience' [I think] on this subject a couple of years back. (Wasn't it peaceful in the days when it took a month or two to get the next installment..) Correct me if I'm wrong, but I find that on some machines (well, on BSD Vaxen anyway) setjmp()/longjmp() can't be used to implement coroutines because longjmp() unwinds the stack destructively while checking for botches. A small amount of in-line assembly language is therefore necessary for transferring control. I agree with Peter's view that coroutines should be supported in the C library. As he says, coroutine packages are quite easy to write and they are indispensible for certain classes of application (I wanted one originally for a window server/multiplexor). The current situation where everybody brews their own isn't really acceptable. The programs aren't portable, and the semantics differ sufficiently to make things confusing for the reader. The details will have to be thrashed out before a standard is defined. Pre-empting threads which share a common data space are probably not a good idea for most purposes - the synchronisation problems usually outweigh any advantages. Leave true multitasking to the operating system and keep to protected data spaces if possible, unless you enjoy wrapping semaphore primitives around every second line of code. (OK, I've just spent 2 years doing this on the Mac, but that's different - no OS processes). Threads with explicit sleep() calls and round-robin transfer - as suggested by Peter - are one way of organising things. I suppose it's a matter of personal taste, but I prefer a system where the 'new_coroutine()' call returns a pointer to a structure which contains at least the context information and the stack base pointer, a reduced form of Peter's 'proc' structure. This makes it easier to tailor your use of coroutines; sometimes you want to transfer control explicitly rather than set up a threads system, and you therefore need some way to identify coroutines throughout their lifespan. The switch()/transfer()/resume() call keeps track of the current coroutine through a static pointer, and hence needs no 'from' parameter. There are other issues, such as how best to set up the routines so that they exit cleanly, whether the original context should be set up as a coroutine, and whether to support special transfers such as resume_caller() or resume_<some default>(). Comments? ------------------------- James Collier Internet(ish): james@cantuar.uucp Computer Science Dept., UUCP: {watmath,munnari,mcvax}!cantuar!james University of Canterbury, Spearnet/Janet: j.collier@nz.ac.canty Christchurch, New Zealand. Office: +64 3 482 009 x8356 Home: +64 3 554 025