使用SIGALRM切换线程上下文



我有一个问题。我需要实现一个程序,使用定时器和SIGALRM切换上下文线程,但当我使用我的evict_thread函数切换线程时,我得到一个分段错误。我认为这是竞争条件的结果,因为它发生在程序执行期间的不同时间。这是我的evt_thread

void evict_thread(int signal)
{   
// Check that there is more than one thread in the queue
if ((int)list_length(runqueue) > 1)
{
    // Remove the currently executing thread from the runqueue and store its id
    int evict_thread_id = list_shift_int(runqueue);
    // Place the thread at the back of the run queue
    list_append_int(runqueue, evict_thread_id);
    // Get the id of the thread that is now at the head of the run queue
    int exec_thread_id = list_item_int(runqueue, 0);
    // Set the start time for new thread to the current time
    clock_gettime(CLOCK_REALTIME, &thread_table[exec_thread_id]->start);
    printf("Switching context from %s to %sn",
        thread_table[evict_thread_id]->thread_name,
        thread_table[exec_thread_id]->thread_name);
    // Execute the thread at the head of the run queue
    if (swapcontext(&thread_table[evict_thread_id]->context, &thread_table[exec_thread_id]->context) == -1)
    {
        perror("swapcontext failedn");
        printf("errno: %d.n", errno);
        return;
    }   
}
return;     
}

上面的函数以如下方式调用

// Set the SIGALRM
if (sigset(SIGALRM, evict_thread) == -1)
{
    perror("sigset failedn");
    printf("errno: %d.n", errno);
    return;
}
// Initialize timer
thread_switcher.it_interval.tv_sec  = 0;
thread_switcher.it_interval.tv_usec = quantum_size;
thread_switcher.it_value.tv_sec = 0;
thread_switcher.it_value.tv_usec =  quantum_size;
setitimer(ITIMER_REAL, &thread_switcher, 0);

运行队列是一个整数的全局列表,是指向uncontext线程的指针的全局表的索引。该列表使用来自libslack.org

的C通用实用程序库的列表数据结构实现。

当我禁用定时器并让每个线程在切换上下文之前运行到完成时,程序可以正常运行,但是当线程在执行期间切换时,我在80%的时间内得到分段错误。

当我尝试使用gdb来回溯分割错误时,它说它发生在一个系统调用中。

请记住,信号处理程序与主代码是异步运行的。man 7 signal页面值得仔细阅读,以确保您遵守了指导方针。例如,在Async-signal-safe-functions一节中没有提到printf,也没有提到swapcontext等其他功能。这意味着你不能可靠地从信号处理程序调用这些函数。

一般来说,在你的信号处理程序中尽量少做一些工作。通常这只意味着在信号处理程序中设置一个类型为sig_atomic_t的标志,然后在主循环中检查该标志的状态。

也许可以重新安排代码,使上下文切换发生在主循环中,而不是在信号处理程序中。您可以在主循环中使用sigwait来等待计时器信号。

我不能给你任何关于如何使它工作的建议,但这里有一些不工作的要点:

信号处理程序相对于其他代码异步运行。例如,当一些代码更新你的runqueue时,信号可能会启动,当信号处理程序运行时list_append_int(runqueue, evict_thread_id);你有一个相当严重的竞争条件。

不应该在信号处理程序中调用

printf(),它可能会死锁或更糟。下面是在信号处理程序中可以安全调用的函数列表。setcontext/swapcontext没有提到在信号处理程序中调用是安全的,尽管它的linux手册页说你可以在信号处理程序中调用setcontext() -我不确定什么是权威的。

还要注意setcontext()的手册说:

当一个信号出现时,当前的用户上下文被保存,一个新的上下文是由内核为信号处理程序创建的。

因此,当您发出swapcontext()时,您可能会保存信号处理程序的上下文,而不是在信号启动之前正在运行的当前上下文。

作为一种猜测:您正在向内核传递一些不可见的东西,因为您切换了上下文。您正在询问段错误,但您的代码正在做有趣的事情。

也许如果你考虑一个更标准的线程调度模型,你可以避免这些问题。除了尝试使用上下文切换来调度线程之外,还有其他方法可以做到这一点。你可以从你的驱逐线程调用它们,使用你完全相同的当前程序模型。

有些建议是系统特有的。如果你能告诉我们你的操作系统是什么,我们可以找到一些适合你的情况。或者你也可以自己去看看。

阅读POSIX线程调度。特别注意SCHED_FIFO,它将与您的模型一起工作。

https://computing.llnl.gov/tutorials/pthreads/man/sched_setscheduler.txt

这通常适用于使用POSIX线程库来调度线程,而不是您尝试使用困难的方法。

最新更新