Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 24 Jan 2002 01:41:22 -0800 (PST)
From:      Julian Elischer <julian@elischer.org>
To:        arch@freebsd.org
Subject:   simple KSE test program
Message-ID:  <Pine.BSF.4.21.0201240046440.25428-100000@InterJet.elischer.org>

next in thread | raw e-mail | index | archive | help

Several people have asked how a KSE program would look.

Here is a program I am writing for testing KSEs:
I hope to be using it within a day or two.
it includes 2 new syscalls (kse_new() and kse_yield()).
One function not yet written is the function loadthread()
which reads the registers in from the saved context in a user_thread
structure, in a similar way to how longjmp() does, but with 
more to load. I need to add that in a .s file.


First I include the kse include file:
  
#ifndef SYS_KSE_H
#define SYS_KSE_H
  
/*
 * This file defines the structures needed for communication between 
 * the userland and the kernel when running a KSE-based threading system.
 * The only programs that should see this file are the UTS and the kernel.
 */
 
/*
 * Each userland thread has one of these buried in it's
 * Thread control structure somewhere.
 */
struct thread_mailbox
{
        struct thread_mailbox *next_completed;
        unsigned int    flags;
        void            *UTS_handle; /* UTS can use this for anything */
        mcontext_t      ctx;      /* thread's saved context goes here. */
/* The ctx field will become a union of types trapframe and mcontext_t
aligned accordingly. */
};
 
 
/*
 * You need to supply one of these as the argument to the
 * kse_new() system call.
 */
struct kse_mailbox
{  
        struct thread_mailbox *current_thread;
        struct thread_mailbox *completed_threads;
        unsigned int    flags;
        void            *UTS_handle;    /* The UTS can use this for
anything */
};
#define KEMBXF_CRITICAL 0x00000001
 
struct kse_global_mailbox
{  
        unsigned int    flags;
};
#define GMBXF_CRITICAL 0x00000001
 
/* some provisional sycalls: */

int     kse_new(struct kse_mailbox *mbx, int new_grp_flag);
int     kse_exit(void);                 /* last will clear KSE mode */
int     thread_wakeup(struct thread_mailbox *tmbx); /* make it complete */
int     kse_wakeup(void);               /* wake any idle KSEs */
void	kse_yield(void);		/* this kse can become idle */

---------------------------------------------------------------
and here is the program.

Only the last 4 functions should be in the program itself.
All the rest make up the basis of what would be in a library.
In fact some of what is done in main() might even be abstracted too.

I hope to be testing this within a couple of days (given a few cleanups
and loose-ends to fix)
----------

 
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <sys/kse.h>
 
/*************************************************************  
 * These should probably be in a .h file
 **************************************************************/
typedef void thread_fn(void *arg);

struct user_thread {
        struct thread_mailbox mbox;
        char            *stack;
        int             stack_size;
        struct user_thread *runq_next; 
};

struct per_kse {
        struct kse_mailbox *mbox;
        struct user_thread *curthread
};      
/*************************************************************
 * Debug stuff
 **************************************************************/

#if 0
jmp_buf jb3;
#define DUMPREGS(desc) do {_setjmp(jb3); printjb(jb3, desc); } while (0)
                 
char *regname[] = {"%eip","%ebx","%esp","%ebp",
                        "%esi","%edi","fpcr","MSK0",
                        "MSK1","MSK2","MSK3"};
         

static
printjb(struct _jmp_buf *jb, char *desc)
{
 
        int i;
        printf("jb (%s) is at 0x%x\n", desc, jb);
        for( i = 0; i< _JBLEN-4; i++) {
                printf("jb[%d] (%s) = 0x%x\n", i, regname[i],
jb[0]._jb[i]);
        }
}
#endif
/*************************************************************
 * Globals
 **************************************************************/
struct per_kse first_kse; /* for NOW cheat and make it global */
struct user_thread *runqueue;
struct user_thread **runq_last;
/*************************************************************
 * Implementation parameters
 **************************************************************/
#define T_STACKSIZE     (16*4096)       /* thread stacksize */
#define K_STACKSIZE     (1*4096)        /* KSE (UTS) stacksize */
        
/*************************************************************
 * UTS funcions.
 * Simple round_robin for now.
 **************************************************************/
static void
runq_insert(struct user_thread *thread)
{
        thread->runq_next = NULL;
        *runq_last = thread;
        runq_last = &thread->runq_next;
}
         
static struct user_thread *
select_thread(void)
{
        struct user_thread *thread;
        if ((thread = runqueue)) {
                if ((runqueue = thread->runq_next) == NULL) {
                        runq_last = &runqueue;
                }
        }
        return (thread);
}

/*************************************************************
 * The UTS upcall entrypoint
 * Called once on startup (and left by longjump)
 * and there-after, returned to by the upcall many times.
 **************************************************************/
static void
UTS(struct _jmp_buf *jb1, struct per_kse *ksedata, int newgroup)
{
        struct kse_mailbox ke_mbox;
        struct user_thread *thread;
        struct thread_mailbox *completed;

        /* Let the caller know where our mailbox is */
        ksedata->mbox = &ke_mbox;
        ke_mbox.UTS_handle = ksedata;

        if (kse_new(&ke_mbox, newgroup)) {      /* initial call returns */
                _longjmp(jb1, 1); /* go back to caller's stack and caller
*/
                /* NOTREACHED */
        }

        /**********************************/
        /* UTS upcall starts running here. */
        /**********************************/
        /**********************************/
        printf("we are in the UTS with mailbox at 0x%x\n", &ke_mbox);

        /* If there are returned syscall threads, put them on the run
queue */
        if ((completed = ke_mbox.completed_threads)) {
                ke_mbox.completed_threads = NULL;
                while (completed) {
                        thread = completed->UTS_handle;
                        completed = completed->next_completed;
                        runq_insert(thread);
                }
        }

        /* find highest priority thread and load it */
        if ((thread = select_thread())) {

                ksedata->curthread = thread;
                ke_mbox.current_thread = &thread->mbox;
                loadthread(thread) /* loads context similar to
longjmp() */
                /* NOTREACHED */
        }
        kse_yeild();     /* in the kernel it does a thread_exit() */
        /* NOTREACHED */
}


/*************************************************************
 * Startup mechanism functions
 **************************************************************/
static int;
kickkse(struct per_kse *ksedata, int newgroup)
{
        char * newstack;
        jmp_buf jb1;
        jmp_buf jb2;
        struct kse_mailbox *mboxaddr;
        int i;

        newstack = malloc(K_STACKSIZE);
        printf("newstack is at 0x%x-0x%x\n",
		newstack, newstack + K_STACKSIZE);
        if (_setjmp(jb1) == 0) {
                if (_setjmp(jb2) == 0) {
                        jb2[0]._jb[2] = &newstack[K_STACKSIZE - 16];
                        _longjmp(jb2, 1);
                }
                /* running with a different SP */
                {
                        /* Get all the rest set up. */
                        UTS(&jb1[0], ksedata, newgroup);
                        /* NOTREACHED */
                }
        }
        return(1);
}


static struct kse_mailbox *
startkse(struct per_kse *ksedata)
{
        return (kickkse(ksedata, 0));
}

static struct kse_mailbox *
startksegrp(struct per_kse *ksedata);
{
        return(kickkse(ksedata, 1));
}

void badreturn()
{
        printf("thread returned when shouldn't\n");
        exit(1);
}

__inline__ void
pushontostack(struct user_thread *tcb, int value)
{
        int *SP;

        SP = (int *)(tcb->mbox.ctx.trapframe.tf_isp);
        *--SP = value;
        tcb->mbox.ctx.trapframe.tf.tf_isp = (int)SP;
}

struct user_thread *
makethread(thread_fn *fn, int arg1, void *arg2)
{
        struct user_thread *tcb;

        /* We could combine these mallocs */
        tcb = malloc(sizeof *tcb);
        bzero(tcb, sizeof(*tcb));
        tcb->mbox.UTS_handle = tcb; /* back pointer */

        /* malloc the thread's stack */
        /* We COULD mmap it with STACK characteristics */
        /* Then we could add a guard page. */
        tcb->stack_size = T_STACKSIZE; /* set the size we want */
        tcb->stack = malloc(tcb->stack_size);


        /* set the PC to the fn */
        tcb->mbox.ctx.trapframe.tf_eip = fn;

        /* Set the stack and push on the args and a dammy return address
*/
        tcb->mbox.ctx.trapframe.tf_isp = tcb->stack + tcb->stack_size -
16;
        pushontostack(tcb, (int)arg2);
        pushontostack(tcb, (int)arg1);
        pushontostack(tcb, (int)&badreturn); /* safety return address */
}

/*************************************************************
 * code for three separate threads. (so we can see if it works)
 *************************************************************/
static void
thread1_code(void *arg)
{
        for(;;) {
                sleep (1);
                write(1,".",1);
        }
}

static void
thread2_code(void *arg)
{
        for(;;) {
                sleep (3);
                write(1,"+",1);
        }
}

static void
thread3_code(void *arg)
{
        for(;;) {
                sleep (5);
                write(1,"=",1);
        }
}



static struct thread_mailbox *
main()
{
        struct kse_mailbox *k_mbox;

        /* set up global structures */
        runq_last = &runqueue;
        runqueue = NULL;

        /* define two threads to run, they are runnable but not yet
running */
        runq_insert( makethread(&thread1_code, 0, NULL));
        runq_insert( makethread(&thread2_code, 0, NULL));
        /* and one which we will run ourself */
        firstkse->curthread = makethread(&thread3_code, 0, NULL);

        /* start two KSEs in different KSEGRPs */
        k_mbox = startkse(&first_kse);

        /* startksegrp(&second_kse); */ /* we can't do 2 KSEs yet */
        /* One will be sufficient */

        /* we are a thread, start the ball rolling */
        /* let the kernel know we are it */
        firstkse->mbox->current_thread = curthread->mbox;
        thread3_code(NULL);
}




To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-arch" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.BSF.4.21.0201240046440.25428-100000>