如何确保信号处理程序永远不会屈服于同一进程组中的线程?

How to ensure a signal handler never yields to a thread within the same process group?

这是一个元问题,因为我认为我有一个适合我的解决方案,但它有其自身的缺点和优点。我需要做一件相当常见的事情,在一个线程上捕获 SIGSEGV(没有专门的崩溃处理线程),转储一些调试信息并退出。

这里的问题是,在崩溃时,我的应用程序 运行s llvm-symbolizer 需要一段时间(相对而言)并导致收益(由于 clone + execve 或超过线程的时间量,我已经看到后者在使用 libLLVM 在进程中自己进行符号化时发生。这样做的原因是为了获得一个堆栈跟踪,其中包含 demangled 符号和 line/file 信息(存储在单独的 DWP 文件中)。出于显而易见的原因,我不希望在我的 SIGSEGV 处理程序中发生 yield,因为我打算在它执行后终止应用程序(线程组)并且从不从信号处理程序 return。

我不太熟悉 Linux 信号处理和 glibc 的包装器在它们周围施展魔法,不过,我知道基本的陷阱,但没有太多关于信号处理细节的信息,比如是否同步信号处理程序在调度方面获得任何类型的特殊优先级。

集思广益,我有一些想法和缺点:

环境是典型的基于 glibc 的 Linux (4.4) 分布 NPTL

我知道崩溃处理程序现在相当普遍,所以我相信 none 我选择的方式非常棒,特别是考虑到我从未见过调度程序 "hack" 曾经以这种方式使用.因此,有没有人有比调度程序更清洁、风险更低的更好替代方案 "hack",我是否遗漏了关于信号的一般想法中的任何重要点?

编辑: 看来我并没有真正考虑这个等式中的 MP(根据评论),事实上其他线程仍然 运行nable in一个 MP 情况,并且可以愉快地继续 运行ning 与 FIFO 线程一起在不同的处理器上。但是,我可以将进程的亲和性更改为仅在与崩溃线程相同的核心上执行,这基本上会有效地在调度边界处冻结所有其他线程。但是,这仍然使 "FIFO thread yielding due to blocking IO" 场景处于打开状态。

似乎 FIFO + SIGSTOP 选项是最好的选项,但我想知道是否有任何其他技巧可以使线程无法调度,而不是使用 SIGSTOP。从文档来看,似乎不可能将线程的 CPU 亲和力设置为零(使其处于技术上 运行 可用的边缘状态,除了没有处理器可用于 运行在)。

这是我能想到的最好的解决方案(为简洁起见省略了部分,但它显示了原理),我的基本假设是在这种情况下进程 运行s 作为 root。如果事情变得非常糟糕并且需要特权(如果我正确理解man(7) sched页面),这种方法可能导致资源匮乏我运行信号处理程序的一部分在[=12=下导致抢占] 守卫并尽快退出范围。这不是严格的 C++ 相关,因为同样可以用 C 或任何其他本地语言完成。

void spl_get(spl_t& O)
{
    os_assert(syscall(__NR_sched_getattr,
        0, &O, sizeof(spl_t), 0) == 0);
}

void spl_set(spl_t& N)
{
    os_assert(syscall(__NR_sched_setattr,
        0, &N, 0) == 0);
}

void splx(uint32_t PRI, spl_t& O) {
    spl_t PL = {0};

    PL.size = sizeof(PL);
    PL.sched_policy = SCHED_FIFO;
    PL.sched_priority = PRI;

    spl_set(PL, O);
}

class OSSplHigh {
    os::spl_t OldPrioLevel;

public:
    OSSplHigh() {
        os::splx(2, OldPrioLevel);
    }

    ~OSSplHigh() {
        os::spl_set(OldPrioLevel);
    }
};

处理程序本身使用 sigaltstacksigaction 非常简单,但我不会在任何线程上阻塞 SIGSEGV。同样奇怪的是 syscalls sched_setattr and sched_getattr 或者结构定义没有通过 glibc 公开,这与文档相反。


后期编辑:最好的解决方案是向所有线程发送SIGSTOP(通过链接器的--wrap选项拦截pthread_create)以保持所有 运行ning 线程的分类帐,感谢您在评论中的建议。

upon crash, my application runs llvm-symbolizer

这很可能会导致死锁。我找不到任何关于 llvm-symbolizer 是异步信号安全的声明。它可能会调用 malloc,如果崩溃也发生在 malloc 内(例如,由于其他地方的堆损坏),肯定会死锁。

Switching to FIFO scheduling policy and raising priority therefore becoming the only runnable thread in that group.

我相信你错了:一个 SCHED_FIFO 线程将 运行 只要它是 运行nable(即不发出任何阻塞的系统调用)。如果线程确实发出这样的调用(它必须:例如 open 单独的 .dwp 文件),它将阻塞并且其他线程将变为 运行nable.

TL;DR:没有简单的方法可以实现您想要的,而且似乎也没有必要:当崩溃的线程完成其业务时,其他线程继续 运行ning 有什么关系?