简体   繁体   中英

Making yield function with timer in C

I want to write a code to switch between threads every 10 microseconds. But the problem is in the yield function. I get an interrupt while running the timer handler. So it doesn't finish properly. This is the code I have for initializing the timer:

signal(SIGALRM, &time_handler);

struct itimerval t1;
t1.it_interval.tv_sec = INTERVAL_SEC;
t1.it_interval.tv_usec = INTERVAL_USEC;
t1.it_value.tv_sec = INTERVAL_SEC;
t1.it_value.tv_usec = INTERVAL_USEC;
setitimer(ITIMER_REAL, &t1, NULL);

And this is the code for the handler function:

void time_handler(int signo)
{
    write(STDOUT_FILENO, "interrupt\n", sizeof("interrupt\n"));
    green_yield();
}

And this is what I do in the yield function: a queue from which we get the thread to run next. The problem is at any moment before I swap context between threads, I can get an interrupt. Especially because I swap the context at the end of this function.

int green_yield(){

green_t *susp = running ;
// add susp to ready queue
// ===========================
enQueue(ready_queue, susp);
// ===========================
// select the next thread for execution
// ===========================
green_t * next = deQueue(ready_queue);
running = next;
// ===========================
// save current state into susp->context and switch to next->context
// ===========================  
swapcontext(susp->context, next->context);
return 0;}

What can I do to make sure that I first complete the yield function and then get the interrupt?

Foreword: Depending on your system hardware, a write() system call into stdout may take longer than 10 us. So, calling this from the SIGALRM handler with a cyclic timer of 10 us may be wrong.

In GLIBC, signal( SIGALRM , time_handler) is equivalent to sigaction() with SA_RESTART flag. SIGALRM signal is blocked during the execution of the handler. So, you will not receive a signal while running the handler. It is implicitly blocked during handler execution and unblocked after it finishes. Since the latter calls green_yield() , you will not get a signal while running inside green_yield() .

As getcontext() saves the signal mask with SIGALRM unblocked (as I guess you call it at the beginning of your program when you create the threads), when you swap the context to go from one interrupted threads running the signal handler to the next schedulable thread, the newly running thread:

  • At 1st scheduling time, returns from its getcontext() (the thread creation point). This restores the signal mask even if the previous thread did not return from the signal handler because the context contains a signal mask with SIGALRM unblocked. When the timer elapses again, SIGALRM will come again to interrupt the newly running thread which will yield the CPU in the signal handler calling swapcontext() . This time the saved context contains a signal mask with a blocked SIGALRM ;
  • At subsequent scheduling time, returns from swapcontext() as it was interrupted by the signal and so was running the end of the signal handler. The context restores a blocked SIGALRM signal but this will be unblocked as part of the execution of the signal handler since its execution restarts from the end of the signal handler.

Even if the preceding is supposed to work, note that when a signal is raised, the system creates a stack frame on the top of the current process stack to make the signal handler appear as a function called by the user program and returning at the interruption point. This frame on the stack must not be corrupted by threads running from any point on the global process stack. The use of sigaltstack() may be considered (see notes below).

What about your thread implementation? They all share the same stack (the process stack). When you create them, they all save their context with getcontext() nearly at the same point in the global process stack. So, when you switch from one thread to another, the newly running thread may screw up the stack frames of the previously running threads... I think this is the point on which you should focus: arrange your threads to make them run with their own global stack zone or with their own stack using something like makecontext() . The manual of the latter provides an example to create several threads of execution with separate stacks.

Side note:

  • swapcontext() is not part of the allowed function calls in the signal handlers: cf. man 7 signal-safety . So, it is not safe to call it from there. But at the same time, we can see that non-local gotos (ie longjmp() ) can safely be called from the signal handler. Since swapcontext() looks like a non local goto, it may be safe to call it under the same conditions as longjmp() ...
  • The manual of sigaltstack() provides some tips to use swapcontext() from signal handlers

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM