3

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

这里的问题是,在崩溃时,我的应用程序运行llvm-symbolizer需要一段时间(相对而言)并导致产量(由于clone + execve或超过线程的时间量,我在自己进行符号化时看到后者发生 -过程使用libLLVM)。这样做的原因是为了得到一个带有解扰符号和行/文件信息(存储在单独的 DWP 文件中)的堆栈跟踪。出于显而易见的原因,我不希望在我的SIGSEGV处理程序中发生屈服,因为我打算在应用程序(线程组)执行后终止它并且永远不会从信号处理程序返回。

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

头脑风暴,我有一些想法和缺点:

  • pthread_kill(<every other thread>, SIGSTOP)- 使用更多线程很麻烦,与信号处理程序交互,这似乎会产生意想不到的副作用。还需要拦截来自其他库的线程创建以跟踪线程列表,并增加每次系统调用抢占的机会。一旦它们停止指向系统调用exit存根或完全使用,甚至可能更改它们的上下文SIGKILL
  • 全局标志用作所有线程的取消点(有点像pthread_cancel/pthread_testcancel)。更安全,但需要大量维护,并且在大型代码库中它可能是地狱般的,除了轻微的性能开销。全局标志也可能导致错误级联,因为程序已经处于不可预测的状态,所以让任何其他线程在那里运行已经不是很好了。
  • “滥用”我当前选择的调度程序,我的实现是答案之一。切换到FIFO调度策略并提高优先级因此成为该组中唯一可运行的线程。
  • 核心转储不是一种选择,因为这里的目标是首先避免它们。除了符号器之外,我更希望不需要辅助程序。

Environment 是一个典型glibc的基于 Linux (4.4) 的发行版,带有NPTL.

我知道崩溃处理程序现在相当普遍,所以我相信我选择的方法都不是那么好,特别是考虑到我从未见过调度程序“hack”以这种方式使用过。因此,有没有人有比调度程序“hack”更清洁、风险更低的更好选择,我是否错过了关于信号的一般想法中的任何重要点?

编辑:似乎我并没有在这个等式中真正考虑过 MP(根据评论)以及其他线程在 MP 情况下仍然可以运行并且可以愉快地继续FIFO在不同处理器上与线程一起运行的事实。但是,我可以将进程的亲和性更改为仅在与崩溃线程相同的内核上执行,这基本上将有效地冻结调度边界处的所有其他线程。但是,这仍然会导致“由于阻塞 IO 导致 FIFO 线程屈服”场景处于打开状态。

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

4

2 回答 2

0

这是我能想到的最佳解决方案(为简洁起见省略了部分,但它显示了原理),我的基本假设是在这种情况下进程以 root 身份运行。man(7) sched如果事情变得非常糟糕并且需要特权(如果我正确理解页面),这种方法可能会导致资源匮乏OSSplHigh。这不是严格与 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任何线程。同样奇怪的是,与文档相反,系统调用 sched_setattr 和 sched_getattr或结构定义并未通过 glibc 公开。


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

于 2018-04-21T16:50:37.413 回答
0

崩溃时,我的应用程序运行 llvm-symbolizer

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

切换到 FIFO 调度策略并提高优先级因此成为该组中唯一可运行的线程。

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

TL;DR:没有简单的方法可以实现您想要的,而且无论如何似乎没有必要:您关心其他线程在崩溃线程完成其业务时继续运行吗?

于 2018-04-22T20:55:11.333 回答