C++ 带线程的Fork和core转储

C++ 带线程的Fork和core转储,c++,c,linux,pthreads,coredump,C++,C,Linux,Pthreads,Coredump,之前和之前都有人提出过与此问题类似的观点,我知道Google coredump库(我对其进行了评估,发现其不足,但如果我能更好地理解这个问题,我可能会尝试进行这方面的工作) 我希望在不中断进程的情况下获得正在运行的Linux进程的核心转储。自然的方法是说: if (!fork()) { abort(); } 由于分叉进程获得了原始进程内存的固定快照拷贝,我应该得到一个完整的内核转储,并且由于拷贝使用了写时拷贝,所以通常应该比较便宜。但是,这种方法的一个关键缺点是fork()只对当前线程进行分叉

之前和之前都有人提出过与此问题类似的观点,我知道Google coredump库(我对其进行了评估,发现其不足,但如果我能更好地理解这个问题,我可能会尝试进行这方面的工作)

我希望在不中断进程的情况下获得正在运行的Linux进程的核心转储。自然的方法是说:

if (!fork()) { abort(); }
由于分叉进程获得了原始进程内存的固定快照拷贝,我应该得到一个完整的内核转储,并且由于拷贝使用了写时拷贝,所以通常应该比较便宜。但是,这种方法的一个关键缺点是
fork()
只对当前线程进行分叉,而分叉副本中不存在原始进程的所有其他线程

我的问题是,是否有可能以某种方式获得其他原始线程的相关数据。我不完全确定如何解决这个问题,但我提出了几个子问题:

  • 在分叉进程中,包含所有线程堆栈的内存是否仍然可用并可访问

  • 是否可以(快速)枚举原始进程中所有正在运行的线程并存储其堆栈的基地址?据我所知,Linux上线程堆栈的基础包含一个指向内核线程簿记数据的指针,因此

  • 使用存储的线程基址,您能读出分叉进程中每个原始线程的相关数据吗


  • 如果这是可能的,那么可能只需要将其他线程的数据附加到核心转储。但是,如果该数据已经在fork点丢失,那么这种方法似乎没有任何希望。

    如果您打算在非特定位置获取核心文件,并且只获取正在运行的进程的核心映像而不终止,那么您可以使用


    如果您打算在特定位置(条件)获取核心文件,并且仍然继续运行该流程,那么一种粗略的方法是从该位置以编程方式执行

    一种更经典、更干净的方法是检查gcore使用的API并将其嵌入到应用程序中,但与大多数情况下的需要相比,这太费劲了


    当您使用
    fork
    时,您将获得正在运行的进程内存的完整副本。这包括所有线程堆栈(毕竟,您可以将有效指针插入它们)。但只有调用线程继续在子线程中执行

    你可以很容易地测试这个。制作一个多线程程序并运行:

    pid_t parent_pid = getpid();
    
    if (!fork()) {
        kill(parent_pid, SIGSTOP);
    
        char buffer[0x1000];
    
        pid_t child_pid = getpid();
        sprintf(buffer, "diff /proc/%d/maps /proc/%d/maps", parent_pid, child_pid);
    
        system(buffer);
    
        kill(parent_pid, SIGTERM);
    
        return 0;
    } else for (;;);
    
    因此,所有内存都在那里,当您创建核心转储时,它将包含所有其他线程堆栈(前提是您的最大核心文件大小允许)。唯一缺少的是它们的寄存器集。如果您需要这些,您必须
    ptrace
    您的家长才能获得它们

    您应该记住,核心转储的设计目的并不是包含多个线程的运行时信息——导致核心转储的线程

    要回答您的其他一些问题:

    您可以通过执行
    /proc/[pid]/tasks
    来枚举线程,但在
    ptrace
    之前,您无法识别它们的堆栈基础


    是的,您可以从分叉进程完全访问其他线程堆栈快照(见上文)。确定它们并不简单,但只要核心文件大小允许,它们确实会被放入核心转储。如果可以的话,最好在创建时将它们保存在全局可访问的结构中。

    您熟悉进程检查点重新启动吗?特别是?在我看来,这可能为你提供一个简单的选择

    我希望在不中断进程的情况下获取正在运行的Linux进程的核心转储,并以某种方式获取其他原始线程的相关数据

    忘记不要打断这个过程。如果您仔细想想,核心转储必须在转储期间中断进程;因此,您的真正目标必须是尽量缩短这种中断的持续时间。您最初使用
    fork()
    的想法确实会中断这个过程,它只会在很短的时间内中断

  • 在分叉进程中,包含所有线程堆栈的内存是否仍然可用并可访问
  • 否。
    fork()
    只保留执行实际调用的线程,其余线程的堆栈丢失

    假设CRIU不合适,我将使用以下程序:

    • 有一个父进程,每当子进程停止时,它都会生成子进程的核心转储。(请注意,可能会生成多个连续停止事件;应仅对第一个停止事件执行操作,直到下一个继续事件。)

      您可以使用检测停止/继续事件

    • 可选:用于将进程限制为单个CPU,以及(或者)将进程优先级降低为
      空闲

      您可以从父进程执行此操作,它只需要有效和允许的集合中的
      CAP\u SYS\u NICE
      功能(您可以使用
      setcap'CAP\u SYS\u NICE=pe'父二进制文件赋予它,如果您像大多数当前Linux发行版一样启用了文件系统功能)

      这样做的目的是在一个线程决定它想要快照/转储的时刻和所有线程停止的时刻之间最小化其他线程的进度。我还没有测试这些更改需要多长时间才能生效——当然,它们最早只发生在当前时间段的末尾。因此,这一步可能应该提前一点完成

      就我个人而言,我不介意。在我的四核机器上,仅以下
      SIGSTOP
      就可以在线程之间产生类似于互斥或信号量的延迟,因此我看不到任何延迟