如何挂起另一个线程(而不是当前线程)?

5
我正在尝试实现一个微控制器的模拟。这个模拟并不是要精确地表示某个特定微控制器的时钟周期,而是检查代码的总体正确性。
我考虑让一个“主线程”执行正常代码,另一个线程执行ISR代码。每当需要运行ISR时,ISR线程会暂停“主线程”。
当然,我希望有一个阻止中断的功能。我想通过使用互斥锁来解决这个问题,ISR线程在执行ISR代码时持有该锁,而主线程在“中断被阻止”的情况下持有该锁。
这样就可以通过终止主线程(并启动执行POR函数的新线程)来实现POR(上电复位)。
Windows API提供了必要的函数。但似乎无法在Linux上使用posix线程实现以上功能。
我不想改变实际的硬件无关微控制器代码。因此,在检查挂起中断的任何事项之前插入任何内容都不是一个选择。
在非规范点接收中断是可取的,因为这也会发生在微控制器上(除非你阻止中断)。
在Linux上有一种暂停另一个线程的方法吗?(调试器肯定以某种方式使用了该选项。)
请不要告诉我这是个坏主意。我知道在大多数情况下这是真的。但主要代码不使用标准库或锁/互斥锁/信号量。

哦,亲爱的。杀死其他线程是非常危险的事情,可能会永久地破坏程序中的任何锁或其他资源。在任意点挂起另一个线程并不那么糟糕,但仍然有风险... - ephemient
主线程代码不会使用任何锁(除了防止其被挂起或终止的锁)或分配动态资源。它不使用标准库。 - Werner Mathé
这个问题也在glibc邮件列表中被提出过这里,但是没有任何结果。 - Albert
5个回答

15

SIGSTOP不起作用 - 它总是停止整个进程。 相反,您可以使用其他信号,例如SIGUSR1进行暂停和SIGUSR2进行恢复:

// at process start call init_pthread_suspending to install the handlers
// to suspend a thread use pthread_kill(thread_id, SUSPEND_SIG)
// to resume a thread use pthread_kill(thread_id, RESUME_SIG)

#include <signal.h>

#define RESUME_SIG SIGUSR2
#define SUSPEND_SIG SIGUSR1

static sigset_t wait_mask;
static __thread int suspended; // per-thread flag

void resume_handler(int sig)
{
    suspended = 0;
}

void suspend_handler(int sig)
{
    if (suspended) return;
    suspended = 1;
    do sigsuspend(&wait_mask); while (suspended);
}

void init_pthread_suspending()
{
    struct sigaction sa;

    sigfillset(&wait_mask);
    sigdelset(&wait_mask, SUSPEND_SIG)
    sigdelset(&wait_mask, RESUME_SIG);

    sigfillset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = resume_handler;
    sigaction(RESUME_SIG, &sa, NULL);

    sa.sa_handler = suspend_handler;
    sigaction(SUSPEND_SIG, &sa, NULL);
}
我非常烦恼看到像“你不应该挂起另一个线程,那是不好的”这样的回复。 为什么你们要假设别人是白痴,不知道自己在做什么?想象一下,其他人也听说过死锁,但仍然在完全清醒的情况下想要挂起其他线程。 如果你没有真正的答案来回答他们的问题,为什么要浪费你和读者的时间。
是的,我认为pthread是非常短视的API,是POSIX的耻辱。

1
我想在C++环境中使用它,通过注入异常来中断线程,从而清理RAII对象。我正在尝试从Windows移植,其中我挂起问题线程并修改指令指针到异常抛出的开始位置,然后恢复执行。它运行良好,大多数情况下,即使它们进入无限循环,我也可以在重新启动子系统后进行恢复,只需最少量的手动清理共享状态(例如重置互斥锁)。如何将该功能移植到我的Linux版本?我看到的大多数文章都指定要避免从处理程序中抛出异常,因为处理程序堆栈帧会被破坏。 - Display Name
一个问题在于,在信号处理程序中使用线程本地变量不是“信号安全”的:http://eel.is/c++draft/support.runtime#support.signal-3.2 另一个问题是静态变量的初始化:http://eel.is/c++draft/support.runtime#support.signal-3.7 - NuPagadi

5
热点JAVA VM在Linux上使用SIGUSR2来实现JAVA线程的暂停/恢复。基于SIGUSR2信号处理程序的过程可能如下所示:提供SIGUSR2的信号处理程序允许线程请求一个已被信号发送线程获取的锁,这将使线程处于暂停状态。一旦暂停线程释放了锁,信号处理程序可以(而且会?)获得该锁。信号处理程序立即释放锁并离开信号处理程序,这将恢复线程。可能需要引入控制变量,以确保主线程在开始实际ISR处理之前位于信号处理程序中。(详细信息取决于信号处理程序是同步调用还是异步调用。)我不知道Java VM是否确切地是这样做的,但我认为上述过程可以达到我所需的效果。

3
一些方式我认为发送其他线程SIGSTOP可以起作用。但是,编写涉及互斥锁和全局变量的一些线程通信要好得多。你看,如果在malloc()中挂起另一个线程,并且调用malloc() -> 死锁。我提到了许多C标准库函数,更不用说您使用的其他库将在您的背后调用malloc()。编辑:嗯,没有标准库代码。也许使用信号处理程序中的setjmp/longjump()模拟POR和信号处理程序模拟中断。对于那些继续投反对票的人:接受了EDIT后的内容的答案,这是一个特定的场景,不能在任何其他场景中使用。

理论上存在可重入内存分配器,您的libc甚至可能默认使用其中之一。但是,是的,OP的请求非常危险。 - ephemient
我不打算使用任何标准库。用于模拟的代码是一个完整的迷你操作系统,不基于C标准库。 - Werner Mathé
据我所知,在Linux(内核2.6)中,不可能单独向一个线程发送SIGSTOP信号。而是要停止一个进程的所有线程。这并不能解决我的问题。 - Werner Mathé
Werner Mathe:内核代码中似乎没有任何阻止您使用pthread_kill向单个线程发送SIGSTOP的内容。 - caf
不幸的是,如果实现符合POSIX标准,这将暂停整个进程,包括调用线程。据我所知,Linux中的实现符合该标准。 - Werner Mathé

1

Solaris有thr_suspend(3C)调用,可以实现您想要的功能。 转换到Solaris是否可行?

除此之外,您可能需要使用互斥锁和/或信号量进行一些操作。 问题在于,只有在检查互斥锁时才会暂停,这可能是在一个良好的点上。 根据您实际想要实现的目标,这可能不是理想的选择。


我在家使用ubuntu。如果必须切换到其他操作系统,我可能会切换到Windows。但还是谢谢你的提示。互斥锁和信号量仅影响当前线程。我不想为模拟插入工具代码。 - Werner Mathé

1

让主线程执行中断服务程序(ISRs)更有意义,因为这就是真正的控制器工作方式(大概是这样)。只需在每个模拟指令后检查是否存在挂起的中断以及当前启用了中断 - 如果是,则模拟调用ISR。

第二个线程仍然被使用 - 但它只是监听导致中断的条件,并将相关中断标记为挂起状态(供其他线程稍后处理)。


当然,我考虑过使用单独的“通信线程”,但它们对于一般流程并不重要。我不想在代码中添加检查中断的任何仪器。在微控制器上也没有这样的东西。我不想模拟特定的微控制器。因此,不存在微控制器的模拟指令。 - Werner Mathé
顺便说一下,这不是我的问题的答案。 - Werner Mathé

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接