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

时间:2018-04-21 16:50:37

标签: linux multithreading signals glibc nptl

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

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

我对Linux信号处理并不熟悉,并且glibc的包装器在它们周围做着魔术,但是,我知道基本的问题,但是关于处理信号的细节没有太多信息,例如同步信号处理程序是否有任何类型在调度方面具有特殊优先权。

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

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

环境是基于glibc的典型Linux(4.4)版本,NPTL

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

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

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

2 个答案:

答案 0 :(得分: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。同样奇怪的是,syscalls sched_setattr and sched_getattr或结构定义没有通过glibc公开,与文档相反。

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

答案 1 :(得分:0)

  

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

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

  

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

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

TL; DR:没有简单的方法来实现你想要的东西,而且似乎没有必要:在崩溃的线程完成业务的同时,你还关心其他线程继续运行?