Why user thread 2 is not working? How fix it?

133 views Asked by At
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sched.h>

#define MAX_THREADS 4
#define STACK_SIZE 8192

typedef struct {
    ucontext_t context;
    int active;
    void (*func)(void);
} Thread;

Thread threads[MAX_THREADS];
int current_thread = 0;

void schedule() {
    int next_thread = (current_thread + 1) % MAX_THREADS;
    int original_thread = current_thread;
    do {
        if (threads[next_thread].active) {
            // Save the current context to the current thread's context
            getcontext(&threads[current_thread].context);

            // Set the current_thread to the next thread
            current_thread = next_thread;

            // Swap the context to the next thread's context
            swapcontext(&threads[original_thread].context, &threads[current_thread].context);

            // The code below will only execute if we return to this thread
            break;
        }
        next_thread = (next_thread + 1) % MAX_THREADS;
    } while (next_thread != original_thread);
}

void thread_func1() {
    printf("Thread 1 is running...\n");
    threads[current_thread].active = 0;
}

void thread_func2() {
    printf("Thread 2 is running...\n");
    threads[current_thread].active = 0;
}

void monitor_thread(void* arg) {
    while (1) {
        if (!threads[current_thread].active) {
            schedule();
            threads[current_thread].func();
        }
    }
}

int create_thread(void (*func)(void)) {
    int thread_id = -1;
    for (int i = 0; i < MAX_THREADS; i++) {
        if (!threads[i].active) {
            thread_id = i;
            break;
        }
    }
    if (thread_id == -1) {
        return -1;
    }

    getcontext(&threads[thread_id].context);
    threads[thread_id].context.uc_stack.ss_sp = malloc(STACK_SIZE);
    threads[thread_id].context.uc_stack.ss_size = STACK_SIZE;
    makecontext(&threads[thread_id].context, (void (*)())func, 0);
    threads[thread_id].func = func;
    threads[thread_id].active = 1;
    return thread_id;
}

int main() {
    create_thread(thread_func1);
    create_thread(thread_func2);
    int pid = clone((int (*)(void *))monitor_thread, malloc(STACK_SIZE) + STACK_SIZE, CLONE_VM | CLONE_THREAD, NULL);
    setcontext(&threads[current_thread].context);
    return 0;
}

i try to debug in gdb, but i dont understand why thread_func2 is not running i want to create manager thread which run my user threads(round robin algorithm and clear resources stack for example) how to debug ucontext? can edb help me? how not set manually completed in thread routine funcs? who should clean thread manager resources?

1

There are 1 answers

0
ikegami On

You didn't say what the threads are supposed to be doing even when prompted, so let's say we wanted to create two threads that each write something 5 times one second apart.

First, we need a way of creating threads.

ThreadId create_thread( ThreadFunc *func, void *arg ) {
   Thread *thread;
   ThreadId thread_id = 0;
   while ( 1 ) {
      thread = &( threads[ thread_id ] );
      if ( thread->state == STATE_FREE )
         break;

      ++thread_id;
   }

   ucontext_t *cx = &( thread->context );
   getcontext( cx );
   cx->uc_stack.ss_sp = malloc( STACK_SIZE );
   cx->uc_stack.ss_size = STACK_SIZE;
   cx->uc_link = &on_exit_cx;
   makecontext( cx, ( void (*)( void ) )launch_thread, 1, thread_id );

   thread->func = func;
   thread->arg_rv = arg;
   thread->state = STATE_CREATED;

   return thread_id;
}

void launch_thread( ThreadId thread_id ) {
   Thread *thread = &( threads[ thread_id ] );
   thread->state  = STATE_RUNNING;
   thread->arg_rv = thread->func( thread->arg_rv );
   thread->state  = STATE_ZOMBIE;
}

We need to handle destroyed threads.

int main( void ) {
   for (ThreadId thread_id = MAX_THREADS; thread_id--; )
      threads[ thread_id ].state = STATE_FREE;

   bool started = false;
   ThreadId main_thread_id;
   getcontext( &on_exit_cx );

   if ( !started ) {
      started = true;
      main_thread_id = create_thread( main_thread, NULL );
      current_thread_id = main_thread_id;
      setcontext( &( threads[ main_thread_id ].context ) );
   }

   if ( current_thread_id != main_thread_id ) {
      // We get here  (via `on_exit_cx`) if a thread other than the main thread ends.
      while ( ( current_thread_id = get_next_thread() ) != MAX_THREADS ) {
         setcontext( &( threads[ current_thread_id ].context ) );
      }
   }

   // We get here if the main thread exits, or if no live threads remain.
}

We need a way of switching threads.

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

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 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 )
   );
}

We need a way to wait for a thread to complete.

void *wait_for_thread( ThreadId thread_id ) {
   Thread *thread = &( threads[ thread_id ] );

   // This trivially becomes a busy wait. But that's fine for this.
   while ( thread->state != STATE_ZOMBIE ) {
      yield();
   }

   thread->state = STATE_FREE;
   return thread->arg_rv;
}

To achieve what they want to achieve, our threads are going to need to sleep.

void thread_sleep( time_t seconds ) {
   // This is quite imprecise. But that's fine for this.
   time_t sleep_until = time( NULL ) + seconds;

   // This trivially becomes a busy wait. But that's fine for this.
   while ( time( NULL ) < sleep_until ) {
      yield();
   }
}

Now our threads. There are actually three, with the code in main being one of them.

void *main_thread( void* dummy ) {
   (void)dummy;

   // Create the threads.
   ThreadId thread1_id = create_thread( func1, NULL );
   ThreadId thread2_id = create_thread( func2, NULL );

   // Wait for them to finish.
   wait_for_thread( thread1_id );
   wait_for_thread( thread2_id );

   return NULL;
}

void *func1( void *dummy ) {
   (void)dummy;

   for ( int i=5; i--; ) {
      printf( "func1\n" );
      if ( i )
         thread_sleep( 1 );
   }

   return NULL;
}

void *func2( void *dummy ) {
   (void)dummy;

   for ( int i=5; i--; ) {
      printf( "func2\n" );
      if ( i )
         thread_sleep( 1 );
   }

   return NULL;
}

All together:

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ucontext.h>

// Including the main thread.
#define MAX_THREADS 4

#define STACK_SIZE (1024*1024)

// It must be an `int` or `unsigned int` since
// it's used as a parameter to `makecontext`.
typedef unsigned int ThreadId;

typedef void *ThreadFunc( void * );

typedef struct {
   ucontext_t context;
   int state;
   ThreadFunc *func;
   void *arg_rv;
} Thread;

enum {
   STATE_FREE,
   STATE_CREATED,
   STATE_RUNNING,
   STATE_ZOMBIE,
};

static ucontext_t on_exit_cx;
static Thread threads[ MAX_THREADS ];
static ThreadId current_thread_id = 0;

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 )
   );
}

ThreadId get_next_thread( void ) {
   ThreadId thread_id = current_thread_id;
   for ( ThreadId i = MAX_THREADS; i--; ) {
      thread_id = ( thread_id + 1 ) % MAX_THREADS;

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

   return MAX_THREADS;
}

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

void launch_thread( ThreadId thread_id ) {
   Thread *thread = &( threads[ thread_id ] );
   thread->state  = STATE_RUNNING;
   thread->arg_rv = thread->func( thread->arg_rv );
   thread->state  = STATE_ZOMBIE;
}

ThreadId create_thread( ThreadFunc *func, void *arg ) {
   Thread *thread;
   ThreadId thread_id = 0;
   while ( 1 ) {
      thread = &( threads[ thread_id ] );
      if ( thread->state == STATE_FREE )
         break;

      ++thread_id;
   }

   ucontext_t *cx = &( thread->context );
   getcontext( cx );
   cx->uc_stack.ss_sp = malloc( STACK_SIZE );
   cx->uc_stack.ss_size = STACK_SIZE;
   cx->uc_link = &on_exit_cx;
   makecontext( cx, ( void (*)( void ) )launch_thread, 1, thread_id );

   thread->func = func;
   thread->arg_rv = arg;
   thread->state = STATE_CREATED;

   return thread_id;
}

void *wait_for_thread( ThreadId thread_id ) {
   Thread *thread = &( threads[ thread_id ] );

   // This trivially becomes a busy wait. But that's fine for this.
   while ( thread->state != STATE_ZOMBIE ) {
      yield();
   }

   thread->state = STATE_FREE;
   return thread->arg_rv;
}

void thread_sleep( time_t seconds ) {
   // This is quite imprecise. But that's fine for this.
   time_t sleep_until = time( NULL ) + seconds;

   // This trivially becomes a busy wait. But that's fine for this.
   while ( time( NULL ) < sleep_until ) {
      yield();
   }
}

void *func1( void *dummy ) {
   (void)dummy;

   for ( int i=5; i--; ) {
      printf( "func1\n" );
      if ( i )
         thread_sleep( 1 );
   }

   return NULL;
}

void *func2( void *dummy ) {
   (void)dummy;

   for ( int i=5; i--; ) {
      printf( "func2\n" );
      if ( i )
         thread_sleep( 1 );
   }

   return NULL;
}

void *main_thread( void* dummy ) {
   (void)dummy;

   // Create the threads.
   ThreadId thread1_id = create_thread( func1, NULL );
   ThreadId thread2_id = create_thread( func2, NULL );

   // Wait for them to finish.
   wait_for_thread( thread1_id );
   wait_for_thread( thread2_id );

   return NULL;
}

int main( void ) {
   for (ThreadId thread_id = MAX_THREADS; thread_id--; )
      threads[ thread_id ].state = STATE_FREE;

   bool started = false;
   ThreadId main_thread_id;
   getcontext( &on_exit_cx );

   if ( !started ) {
      started = true;
      main_thread_id = create_thread( main_thread, NULL );
      current_thread_id = main_thread_id;
      setcontext( &( threads[ main_thread_id ].context ) );
   }

   if ( current_thread_id != main_thread_id ) {
      // We get here  (via `on_exit_cx`) if a thread other than the main thread ends.
      while ( ( current_thread_id = get_next_thread() ) != MAX_THREADS ) {
         setcontext( &( threads[ current_thread_id ].context ) );
      }
   }

   // We get here if the main thread exits, or if no live threads remain.
}
$ gcc -Wall -Wextra -pedantic a.c -o a && ./a
func1
func2
func1
func2
func2
func1
func2
func1
func2
func1

Notes:

  • Error checking needs to be added.
  • Some function are implemented naïvely as indicated in comments.
  • Note the lack of clone.