#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#define STACK_SIZE 16384 /* Stack size for each thread */
/* Contexts for main and two threads */
ucontext_t contexts[3]; /* 0: main, 1: thread 1, 2: thread 2 */
int current_thread = 1; /* Tracks the current thread */
volatile int num_threads = 0; /* Number of threads */
void
scheduler(int signum)
{
if (num_threads < 2) {
printf("Too few threads to schedule\n");
return;
}
int prev_thread = current_thread;
/* Toggle between thread 1 and thread 2 */
current_thread = (current_thread == 1) ? 2 : 1;
printf("-- Switching from thread %d to thread %d\n", prev_thread,
current_thread);
/* Swap context to the next thread */
swapcontext(&contexts[prev_thread], &contexts[current_thread]);
}
void
thread1(void)
{
for (int i = 1; i <= 42; i++) {
printf("Thread 1: %d\n", i);
usleep(100000); /* Simulate work */
}
printf("Thread 1 exiting\n");
--num_threads;
}
void
thread2(void)
{
for (int i = 1; i <= 42; i++) {
printf("Thread 2: %d\n", i);
usleep(100000); /* Simulate work */
}
printf("Thread 2 exiting\n");
--num_threads;
}
void
newthread(void (*entrypoint)(void), ucontext_t *contextptr)
{
void *stack = malloc(STACK_SIZE);
if (stack == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
if (getcontext(contextptr) == -1) {
perror("getcontext");
exit(EXIT_FAILURE);
}
contextptr->uc_stack.ss_sp = stack;
contextptr->uc_stack.ss_size = STACK_SIZE;
contextptr->uc_link = &contexts[0]; /* Return to main if thread exits */
makecontext(contextptr, entrypoint, 0);
++num_threads;
}
int
main(void)
{
struct sigaction sa;
newthread(thread1, &contexts[1]);
newthread(thread2, &contexts[2]);
/* Set up the SIGALRM signal handler */
sa.sa_handler = scheduler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* Restart interrupted system calls */
if (sigaction(SIGALRM, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
/* Configure the timer to trigger SIGALRM every 0.5 seconds */
ualarm(500000, 500000);
/* Start the first thread */
current_thread = 1;
swapcontext(&contexts[0], &contexts[1]);
/* If our scheduler wasn't running, we'd run the first thread all
* the way to completion and but since it is, both threads will run
* concurrently. When one of them exits (we don't know which one will
* exit first), we'll return here. */
/* Finish the other thread thread */
current_thread = (current_thread == 1) ? 2 : 1;
swapcontext(&contexts[0], &contexts[current_thread]);
printf("Main thread exiting\n");
return 0;
}