首页 > 解决方案 > 在C中使用计时器制作产量函数

问题描述

我想编写一个代码,每 10 微秒在线程之间切换一次。但问题在于产量函数。运行计时器处理程序时出现中断。所以它没有正确完成。这是我用于初始化计时器的代码:

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

这是处理函数的代码:

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

这就是我在 yield 函数中所做的:一个队列,我们​​从中获取线程接下来运行。问题是在我在线程之间交换上下文之前的任何时刻,我都可以获得中断。特别是因为我在这个函数结束时交换了上下文。

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

我可以做些什么来确保我首先完成了 yield 函数然后得到了中断?

标签: cpthreadssignalsyielducontext

解决方案


前言:根据您的系统硬件,对 stdout 的write()系统调用可能需要超过 10 us 的时间。因此,从SIGALRM处理程序中使用 10 us 的循环计时器调用它可能是错误的。

在 GLIBC 中,signal( SIGALRM , time_handler)等价于带有SA_RESTART标志的sigaction() 。SIGALRM信号在处理程序执行期间被阻塞。因此,您在运行处理程序时不会收到信号。它在处理程序执行期间被隐式阻塞,并在完成后解除阻塞。由于后者调用green_yield() ,因此在green_yield()内运行时您不会收到信号。

由于getcontext()将信号掩码保存为SIGALRM未阻塞(我猜您在创建线程时在程序开始时调用它),当您交换上下文以从运行信号处理程序的一个中断线程转到下一个可调度线程时线程,新运行的线程:

  • 在第一个调度时间,从它的getcontext()(线程创建点)返回。即使前一个线程没有从信号处理程序返回,这也会恢复信号掩码,因为上下文包含一个未阻塞SIGALRM的信号掩码。当计时器再次过去时,SIGALRM将再次中断新运行的线程,这将在调用swapcontext()的信号处理程序中产生 CPU 。这次保存的上下文包含一个带有阻塞SIGALRM的信号掩码;
  • 在随后的调度时间,从swapcontext()返回,因为它被信号中断,因此正在运行信号处理程序的末尾。上下文恢复阻塞的SIGALRM信号,但这将作为信号处理程序执行的一部分被解除阻塞,因为它的执行从信号处理程序的末尾重新开始。

即使前面应该工作,请注意,当产生信号时,系统会在当前进程堆栈的顶部创建一个堆栈帧,以使信号处理程序显示为由用户程序调用并在中断时返回的函数观点。堆栈上的这个帧不能被从全局进程堆栈上的任何点运行的线程破坏。可以考虑使用sigaltstack() (见下面的注释)。

你的线程实现呢?它们都共享同一个堆栈(进程堆栈)。当您创建它们时,它们几乎都在全局进程堆栈中的同一点使用getcontext()保存它们的上下文。所以,当你从一个线程切换到另一个线程时,新运行的线程可能会搞砸先前运行的线程的堆栈帧......我认为这是你应该关注的一点:安排你的线程,让它们运行自己的全局堆栈区域或使用类似makecontext()的自己的堆栈。后者的手册提供了一个使用单独的堆栈创建多个执行线程的示例。

边注:

  • swapcontext()不是信号处理程序中允许的函数调用的一部分:cf。man 7 信号安全。因此,从那里调用它是不安全的。但同时,我们可以看到可以从信号处理程序安全地调用非本地 goto(即longjmp() )。由于swapcontext()看起来像一个非本地 goto,因此在与longjmp()相同的条件下调用它可能是安全的......
  • sigaltstack()的手册提供了一些从信号处理程序中使用swapcontext()的技巧

推荐阅读