Longjmping back, and back again; Coroutines in C

Jerker W}gberg jerker at enea.se
Sat Nov 18 08:06:30 AEST 1989


I am trying to implement coroutines using plain C. My
application is not time critical, so there is no need for speed,
being portable is far more important. It would be great if there
was a way to switch stacks of the processes using just C.

I have figured out a way to implement this that works fine on a PC
with MSC 5.1, but wreaks havoc when run on a SUN4. The idea is that
instead of actually switching stacks, I use the "real" stack but
swap it in and out of malloced memory.

1. Can somebody explain why this fails on a SUN4. This is the first
   program that I ever have tried on a SUN4 so I don't have any
   idea what happens inside the CPU. Could it be that the SUN4 have
   some peculiar registers that must be restored but isn't below?

2. Are there more machines out there that probably will throw up
   when executing this code, apart from CPU's where the stack is
   growing upward instead of downward. This can be taken care of,
   but I did not want to clutter the example below.

3. Does anyone know of a better way to do coroutines in C ?

Like to test it yourself ? Here is a barebones version:

------------------------------ cut here -------------------------------------
/*
** Test of coroutines.
** Outputs :
**	func1
**	func2
**	func1
**	.
**	.
**      ad inf
*/

#include <stdio.h>
#include <setjmp.h>
#include <malloc.h>

char *stack_bottom;
jmp_buf main_buf;
int cc;			/* Current coroutine */

func_1()
    {
    int foo[50];   /* To get different stack depths at dispatch. If the	*/
    		   /* stack depths are equal at dispatch the program	*/
		   /* just continues with func2 on my SUN4. If stacks 	*/
		   /* are unequal, I get segmentation fault.		*/
    for (;;)
        {
	printf("func1\n");
	dispatch();
	}
    }

func_2()
    {
    for (;;)
        {
	printf("func2\n");
	dispatch();
	}
    }

struct sav		/* Control block for each coroutine		*/
    {
    char *stack;	/* Pointer to saved stack			*/
    int stack_len;	/* Lenght of stack				*/
    int (*func)();	/* Pointer to each coroutines start address	*/
    jmp_buf retbuf;	/* SP and PC of each coroutine			*/
    } sav[2] = 
    	{
    	  {NULL, 0, func_1}
	, {NULL, 0, func_2}
	};

main()
    {
    char stack_mark;

    stack_bottom = &stack_mark;

    /*
    ** Here we go. Run a coroutine until it dispatches then
    ** run the other.
    */

    for (;;cc ^= 1)
	{
	if (setjmp(main_buf) == 0)
	    if (sav[cc].stack == NULL)
		sav[cc].func();			/* First time	*/
	    else
		longjmp(sav[cc].retbuf, 1);	/* Continuation	*/
        }
    }

dispatch()
    {
    char stack_mark;
    jmp_buf disp_buf;

    if (setjmp(disp_buf) == 0)
	{

	/*
	** Save the jmp_buf so that we can return.
	** Save the stack into mallocated memory and
	** longjmp back to main
	*/

	memcpy(sav[cc].retbuf, disp_buf, sizeof(jmp_buf));
	sav[cc].stack_len = stack_bottom - &stack_mark;
	sav[cc].stack = malloc(sav[cc].stack_len);
	memcpy(sav[cc].stack, &stack_mark, sav[cc].stack_len);
	longjmp(main_buf, 1);
	}
    else
	{

	/*
	** The longjmp from main set SP ok but there is just
	** garbage from the previous coroutine on the stack. Copy
	** the stack from previous run and return to caller of
	** dispatch.
	*/

	memcpy(&stack_mark, sav[cc].stack, sav[cc].stack_len);
	free(sav[cc].stack);
	}
    }



More information about the Comp.lang.c mailing list