与螺纹的叉和核心转储

在这里和这里提出类似的问题之前,我知道谷歌coredump库(我已经评价和发现缺乏,但如果我更好地理解问题,我可以尝试和工作)。

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

if (!fork()) { abort(); } 

由于分叉进程获得原始进程内存的固定快照副本,我应该得到一个完整的核心转储,并且由于该副本使用了写入时复制,所以通常应该是便宜的。 然而,这种方法的一个关键缺点是fork()只会分叉当前线程,而原始进程的所有其他线程都不会存在于分叉拷贝中。

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

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

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

  3. 与存储的线程基地址,你能读出分叉进程中的每个原始线程的相关数据?

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

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

我想获得一个正在运行的Linux进程的核心转储,而不中断进程[和]以某种方式获得其他原始线程的相关数据。

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

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

fork()仅保留进行实际调用的线程,其余线程的堆栈都将丢失。

假设CRIU是不合适的,这里是我使用的程序:

  • 有一个父进程生成子进程的核心转储,每当孩子停止。 (请注意,可能会生成多个连续的停止事件;只有第一个到下一个继续事件才会生效。)

    您可以使用waitpid(child,,WUNTRACED|WCONTINUED)来检测停止/继续事件。

  • 可选:使用sched_setaffinity()将进程限制为单个CPU, sched_setscheduler() (也可能使用sched_setparam() )将进程优先级降为IDLE

    你可以从父进程完成,只需要CAP_SYS_NICE能力(如果你像大多数当前Linux发行版那样启用文件系统功能,你可以使用setcap 'cap_sys_nice=pe' parent-binary给父setcap 'cap_sys_nice=pe' parent-binary ),在有效的和允许的集合中。

    其目的是在线程决定它想要一个快照/转储的时刻和所有线程已经停止的时刻之间使其他线程的进程最小化。 我还没有测试这些变化需要多长时间才能生效 – 当然,它们只能在最早的时间片结束时才会发生。 所以,这一步应该事先做一下。

    就个人而言,我不打扰。 在我的四核机器上,下面的SIGSTOP单独产生线程之间的相似的等待时间作为互斥体或信号量,所以我不认为有任何需要争取更好的同步。

  • 当子进程中的线程决定要为自己创建一个快照时,它会向自己发送一个SIGSTOP (通过kill(getpid(), SIGSTOP) )。 这将停止进程中的所有线程。

    父进程将收到孩子停止的通知。 它将首先检查/proc/PID/task/来获取子进程的每个线程的TID(也许/proc/PID/task/TID/ pseudofiles用于其他信息),然后使用ptrace(PTRACE_ATTACH, TID)附加到每个TID ptrace(PTRACE_ATTACH, TID) 。 显然, ptrace(PTRACE_GETREGS, TID, ...)将获得每个线程的寄存器状态,可以和/proc/PID/task/TID/smaps/proc/PID/task/TID/mem一起使用获取每个线程的堆栈跟踪以及其他任何您感兴趣的信息(例如,您可以为每个线程创建一个与调试器兼容的核心文件)。

    当父进程完成抓取转储时,它会让子进程继续。 我相信你需要发送一个单独的SIGCONT信号让整个子进程继续,而不是仅仅依靠ptrace(PTRACE_CONT, TID) ,但是我没有检查过这个; 请确认这一点。

我相信,上述过程会在停止过程中的线程之间产生挂钟时间的最小延迟。 Xubuntu上的AMD Athlon II X4 640和3.8.0-29-通用内核的快速测试表明,在其他线程中,紧密的循环增加了一个volatile变量,这只会使计数器提前数千个,这取决于线程的数量(太多了噪音在我做的几个测试中更具体的说)。

将进程限制为单个CPU,甚至是空闲优先级,将会进一步大大减少延迟。 CAP_SYS_NICE能力使得父母不仅可以减少子进程的优先级,还可以将优先级提升回原来的级别; 文件系统功能意味着父进程甚至不需要setuid,因为CAP_SYS_NICE就足够了。 (我认为这将足够安全 – 在家长计划中进行一些检查 – 安装在大学计算机上,学生们在这里非常积极地寻找有趣的方式来利用已安装的程序。)

有可能创建一个内核补丁(或模块),提供一个提升kill(getpid(), SIGSTOP) ,它也尝试从运行CPU中启动其他线程,从而尝试使线程之间的延迟停止更小。 就个人而言,我不会打扰。 即使没有CPU /优先级操作,我也能得到足够的同步(线程停止的时间之间足够小的延迟)。

你需要一些示例代码来说明我的想法吗?

如果你打算把核心文件放到非特定的位置,只要得到正在运行的进程的核心映像而不杀掉,那么你可以使用gcore 。

如果你打算在特定的位置(条件)获得核心文件,并继续运行这个过程 – 一个粗糙的方法是从该位置以编程方式执行gcore 。

一个更经典,干净的方法是检查gcore使用的API并将其嵌入到应用程序中 – 但是与大多数情况下的需求相比,这将是一个太多的努力。

HTH!

当你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它们之前你不能识别它们的栈ptrace

是的,您可以完全访问分叉进程中的其他线程堆栈快照(请参见上文)。 确定它们并不是微不足道的,但是如果核心文件大小允许的话,它们就会进入核心转储。 你最好的办法是把它们保存在一个全球可访问的结构中,如果你可以创建的话。

如果你的目标是快照整个过程,以便了解在一个特定点的所有线程的确切状态,那么我看不到任何方式来做到这一点,不需要某种中断服务程序。 您必须暂停所有处理器并记录每个线程的当前状态。

我不知道有任何系统提供这种完整的进程核心转储。 这个过程的粗略概述是:

  1. 在所有CPU(逻辑和物理内核)上发出中断。
  2. 忙等待所有内核同步(这不应该花很长时间)。
  3. 克隆所需进程的内存空间:复制页表并将所有页面标记为复制写入。
  4. 让每个处理器检查其当前线程是否在目标进程中。 如果这样记录该线程的当前堆栈指针。
  5. 为其他线程检查当前堆栈指针的线程数据块并记录它。
  6. 创建一个内核线程来保存复制的内存空间和线程堆栈指针
  7. 恢复所有内核。

这应该捕获整个进程状态,包括发出内部处理器中断时正在运行的任何进程的快照。 由于所有线程都被中断(通过标准的调度程序暂停进程或通过我们的自定义中断进程),所有寄存器状态将在进程内存中的某个栈中。 然后你只需要知道每个线程堆栈的顶部在哪里。 使用copy on write机制克隆页表允许透明的保存,同时允许原始进程恢复。

这是一个相当重量级的选项,因为它的主要功能需要暂停所有处理器大量时间(同步,克隆,遍历所有线程)。 但是,这应该允许您精确地捕获所有线程的状态,并确定在达到检查点时哪些线程正在运行(以及哪些CPU)。 我会假设这个过程的一些框架存在(例如CRIU)。 当然,恢复进程将导致页面分配风暴,因为写入时复制机制保护检查指向的系统状态。