0

I am using <ucontext.h> to swap between different tasks in my "task manager" program. The tasks (functions) are in a linked list and a timer is sending signals in regular intervals to a signal handler which swaps the current context to the next task in the list.

The problem is that I need an infinite loop in order to keep the program running, because otherwise it terminates before the timer has a chance to send the next signal. The way I am dealing with this is slapping an infinite cycle at the end of each task (function) so the timer has time to send a signal. I don't think this is a good way to keep the program running, so I want to know if there is a way to create some sort of "global" loop which keeps going irrespective of the current context I am currently in. Putting it in the "main" function did not work, because I leave the main context in order to enter the other ones.

#include <malloc.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <ucontext.h>
#include <unistd.h>

#define FIBER_STACK 1024 * 64

ucontext_t uctx_func1, uctx_func2, uctx_func3, uctx_main;

struct elem {
    ucontext_t context;
    struct elem *prev;
    struct elem *next;
};

struct elem *start = NULL;
struct elem *current = NULL;

void signal_handler();
void push(ucontext_t context);  // end
void pop();  // start
void fiber1();  // these are my tasks
void fiber2();  // these are my tasks
void fiber3();  // these are my tasks
void create_task(void (*start_routine)(), ucontext_t context);
void setHandler(struct sigaction sa);
void setTimer(struct itimerval timer);

void main() {
    struct sigaction sa;
    struct itimerval timer;

    create_task(fiber1, uctx_func1);
    create_task(fiber2, uctx_func2);
    create_task(fiber3, uctx_func3);

    setHandler(sa);
    setTimer(timer);

    current = start;

    swapcontext(&uctx_main, &current->context);
}

void signal_handler() {
    struct elem *temp = current;

    current = current->next;

    pop();

    push(temp->context);

    swapcontext(&temp->context, &current->context);
}

void create_task(void (*start_routine)(), ucontext_t context) {
    getcontext(&context);

    context.uc_link = NULL;
    context.uc_stack.ss_sp = malloc(FIBER_STACK);
    context.uc_stack.ss_size = FIBER_STACK;
    context.uc_stack.ss_flags = 0;

    makecontext(&context, start_routine, 0);

    push(context);
}

void fiber1() {
    printf("++++++++++\n");

    while (true) {
    }  // these are the infinite loops I want to get rid of
}

void fiber2() {
    printf("----------\n");

    while (true) {
    }  // these are the infinite loops I want to get rid of
}

void fiber3() {
    printf("**********\n");

    while (true) {
    }  // these are the infinite loops I want to get rid of
}

void push(ucontext_t context) {
    struct elem *p = start, *q;

    if (start) {
        q = (struct elem *)malloc(sizeof(struct elem));

        q->context = context;
        q->next = NULL;

        while (p->next)
            p = p->next;

        p->next = q;

        q->prev = p;

    }

    else {
        start = (struct elem *)malloc(sizeof(struct elem));

        start->context = context;

        start->prev = NULL;

        start->next = p;
    }

    // printf ("Element Added.\n");
}

void pop() {
    if (start) {
        start = start->next;

        // printf ("Element Deleted.\n");
    }

    else
        printf("Queue is empty.\n");
}

void setHandler(struct sigaction sa) {
    sa.sa_handler = signal_handler;
    sa.sa_flags = 0;

    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGALRM, &sa, NULL) == -1)
        perror("sigaction");
}

void setTimer(struct itimerval timer) {
    timer.it_interval.tv_sec = 0;
    timer.it_value.tv_sec = 0;
    timer.it_interval.tv_usec = 10000;
    timer.it_value.tv_usec = 10000;

    if (setitimer(ITIMER_REAL, &timer, NULL) == -1)
        perror("setitimer");
}
Oka
  • 23,367
  • 6
  • 42
  • 53
  • 2
    Your tasks when finished should cause "unscheduled" context switch regardless of timer. That would be the right solution. – Eugene Sh. May 23 '23 at 16:15
  • 1
    In any case, you can create a wrapper function `task(void (*actual_task)())` which will call the actual function and then do the housekeeping when it is finished, then in your `create_task` you will pass `task` as your start routine with argument `start_routine` – Eugene Sh. May 23 '23 at 16:22
  • 1
    You could at least call `pause()` in those infinite loops. – Ian Abbott May 23 '23 at 16:24
  • It sounds like you already effectively have just such a loop as you describe. The problem is not creating that, but rather that your tasks are breaking out of it when they finish. – John Bollinger May 23 '23 at 16:31
  • 2
    Note that your signal handler invokes undefined behaviour by calling functions that call async-signal-unsafe functions. – Harith May 23 '23 at 16:31
  • Do you understand why the process terminates when one of these returns? – user253751 May 23 '23 at 17:11

1 Answers1

1

Create a wrapper function:

void task(void (*actual_task)())
{
    // Call the actual task function
    actual_task();
    // Do the housekeeping
    while(1);
}

Then change your create_task as follows:

void create_task(void (*start_routine)(), ucontext_t context) {
    // ......
    makecontext(&context, task, 1, start_routine);
    // ......
}
  • But I would suggest to replace the loop with a signal to trigger an "unscheduled" context switch instead of waiting for the timer.

EDIT: As was pointed in the comments, the variadic argument to makecontext can't be a function pointer and must be an int. To work around it one can create a global array of "tasks" - that is function pointers, and pass the array index as the argument. So amending the answer it will look like:

void (*task_func[])() = {
    fiber1,
    fiber2,
    fiber3
};
//.....

void task(int task_index)
{
    // Call the actual task function
    (task_func[task_index])();
    // Do the housekeeping
    while(1);
}
//......
void create_task(int task_index, ucontext_t context)
{
    // ......
    makecontext(&context, task, 1, task_index);
    // ......
}
Eugene Sh.
  • 17,802
  • 8
  • 40
  • 61