C - Switch Context and Store Old in Queue

71 views Asked by At

I'm trying to implement a thread switcher in c using the ucontext.h library.

I am doing this using a queue system where the next thread to run is at the front of the queue. When a thread pauses; I pop the node at the top of the queue, switch the running context with the one stored in that node, and push the node back onto the queue. This looks something like this:

I have a test case where I place two threads in the queue. Each thread prints its ID before pausing. I expect the two to alternate. Instead the firs thread runs to completion and the second does not run. I've narrowed this problem down to the way I swap the threads:

QManager.c

void pause () {
    struct ThreadNode* t= QManagerPop(Q);
    swapcontext(t->context, t->context);
    QManagerPush(t);
}

Seemingly; this does not switch the running context with the one stored in `t->context'.

I am looking for a way to do this; place the current context in a variable `t->context' while at the same time resuming the context that was stored in that variable.

I tried the following based on some information I found online

QManager.c

void switch (ucontext_t* c) {
    volatile int isSwap = 0;
    int isGet = getcontext(c);
    if (!isSwap && isGet ==0) {
        isSwap = 1;
        setcontext(c);
    }
}

void pause () {
    struct ThreadNode* t= QManagerPop(Q);
    switch(t->context);
    QManagerPush(t);
}

This produces the same behaviour (with an additional segfault at the end).

Is there some way to do what I am looking to do? Maybe something with a temp variable? How would I deal with this variable when the context switches?

2

There are 2 answers

0
ikegami On BEST ANSWER
  • The two arguments to swapcontext context can't be the same pointer since the parameters are restrict.
  • You can't push your old context onto the queue after swapping. That "thread" is no longer executing! It needs to be done before the swap.

Instead of allocating and deallocating all those threads, you could work with thread ids, which is to say indexes into an array of structures that contains the context and other data to a thread.

Then, switching context context becomes

void switch_to_thread( ThreadId new_thread_id ) {
   ThreadId old_thread_id = current_thread_id;
   current_thread_id = new_thread_id;
   swapcontext(
      &( threads[ old_thread_id ].context ),
      &( threads[ new_thread_id ].context )
   );
}

and pause becomes

void yield( void ) {
   QManagerEnqueue( q, current_thread_id );
   ThreadId new_thread_id = QManagerDequeue( q );
   switch_to_thread( new_thread_id );
}

Actually, if all the threads are in an array, then we don't need queue. We just need to find the next thread in the array.

ThreadId get_next_thread( void ) {
   ThreadId thread_id = current_thread_id;
   while ( 1 ) {
      thread_id = ( thread_id + 1 ) % MAX_THREADS;

      int state = threads[ thread_id ].state;
      if ( state == STATE_CREATED || state == STATE_RUNNING )
         return thread_id;
   }
}

void yield( void ) {
   switch_to_thread( get_next_thread() );
}

This code comes from this earlier answer of mine, where you can see these functions used.


Note these fixes:

  • void f() doesn't mean the function takes no argument.

Note these renames:

  • Qq.
    It's not a module name or constant.
  • QManagerPushQManagerEnqueue and
    QManagerPopQManagerDequeue.
    Push and pop are stack operations, not queue operations.
  • pauseyield.
    yield or cede are more appropriate names.
1
An Assembler On

Welp I solved my problem. Thank you to @ikegami for pointing out that i cant call swapcontext with the same arguments nor expect the function to push after swapping. my code now looks like

void pause () {
    struct ThreadNode* t= QManagerPop(Q);
    struct ThreadNode* s= malloc(sizeof(struct ThreadNode));
    s -> context = malloc(sizeof(ucontext_t));
    QManagerPush(s);
    swapcontext(QManagerTail(Q)->context, t->context);
}