Appearance
DMTCP:透明检查点与重启(Checkpoint/Restart)的原理与功能
想象你能把一台正在狂奔的列车连车带人"冻结"成一张照片,过几天在另一座城市的轨道上"解冻"继续跑——DMTCP 做的就是这件事,只不过对象是正在运行的进程(甚至一整棵进程树)。本文详细论述它的功能边界、整体架构,以及它如何在不改内核、不重编程序的前提下,把内存、寄存器、文件描述符、线程、TCP 连接、信号全部"拍快照"再原样恢复。
阅读时间:2026-08-05
1. 本篇要回答的问题
- DMTCP 到底能做什么?和"暂停/恢复进程"(
SIGSTOP/SIGCONT)有什么本质区别? - 它是怎么做到"对应用完全透明"的?应用代码一行都不用改?
- 为什么能跨机器恢复?(迁移/migration)
- 内存、寄存器、文件描述符、线程、TCP socket、共享内存——这些状态分别怎么保存和恢复?
- DMTCP 和 CRIU 有什么不同?各自适用什么场景?
2. DMTCP 是什么:一句话定位
DMTCP(Distributed MultiThreaded CheckPointing) 是一个用户态、对应用透明的检查点/重启工具:
- 它能把整个进程树(含多线程、子进程、PID 命名空间关系)的运行时状态序列化到磁盘;
- 之后可以在任意时刻、同一台或另一台兼容机器上把状态完整还原,进程从被冻结的那一刻继续执行;
- 无需内核补丁、无需 root、无需重新编译/链接应用——典型用法只是把程序用
dmtcp_launch包一层启动。

与
SIGSTOP/SIGCONT的本质区别:SIGSTOP只是暂停调度,进程仍在原机原内存里,断电/退出即丢失;DMTCP 是把全部状态导出到持久化介质,可以关机、换机器、几天后再恢复——这是"检查点",不是"暂停"。
3. 功能全景
| 能力 | 说明 | 典型场景 |
|---|---|---|
| Checkpoint | 把进程树完整状态写到磁盘 | 长任务的中间存档 |
| Restart | 从快照原样恢复执行 | 断点续算、崩溃后回滚 |
| Migration(迁移) | 在另一台机器 restart | 负载均衡、腾挪节点 |
| Fault tolerance | 定时 checkpoint,故障后回滚到最近快照 | HPC 长作业防失效 |
| 批处理集成 | 配合 SLURM / Torque / PBS 的抢占式调度 | 集群上被抢占的任务续跑 |
| 分布式协调 | 多进程/多机通过 coordinator 同步 checkpoint | MPI 作业、多节点训练 |
4. 整体架构:coordinator + worker + libdmtcp

- Coordinator(协调者):一个独立进程,负责协调多个被检查点进程在同一全局点一起停、一起存、一起恢复。分布式场景(多机 MPI)必须依赖它做全局屏障(barrier)。它本也可以被 checkpoint,实现"嵌套检查点"。
- libdmtcp.so(注入库):通过
LD_PRELOAD预加载进每个被检查点进程,是 DMTCP 的"手脚"——负责拦截系统调用、执行真正的快照/恢复逻辑。 - worker 进程:即你的应用,被
libdmtcp.so包裹运行。
5. 原理:它如何"无侵入"地拍下进程的完整状态
DMTCP 的魔法全部来自一个事实:它用 LD_PRELOAD 提前把 libdmtcp.so 注入进程,劫持了所有与"外部状态"相关的系统调用。这样当检查点发生时,它早已知道每个 FD 背后是什么、每个线程在哪。
5.1 预加载劫持(透明性的根基)
应用调用 open/socket/pipe/fork/exec/connect 等时,实际先经过 libdmtcp.so 的包装函数。包装函数:
- 正常完成原 syscall;
- 顺便记录元数据——例如
open返回 fd 时,记下路径、flags、当前文件偏移;socket时记下协议、对端地址、本端地址;pipe时记下管道对的标识。
这些元数据就是恢复时的"说明书"。无需读内核内部,光靠 syscall 返回值就重建了外部状态。
5.2 全局停点(quiesce):让所有线程在同一刻静止
不能边跑边拍——必须让进程所有线程同时静止在某个一致点。DMTCP 的做法:
- coordinator 发 checkpoint 请求;
libdmtcp.so用信号(内部信号,类比SIGUSR1的私有信号)中断每个线程;- 被中断的线程在 DMTCP 的"检查点处理程序"里互相协作,通过屏障把所有线程汇合到同一个静止点(quiesce)。此时:
- 没有线程还在执行应用代码;
- 所有线程的寄存器、栈、TLS 都已稳定可读。
5.3 保存:把"易失"变成"持久"
| 状态类别 | 怎么保存 | 恢复要点 |
|---|---|---|
| 内存映像 | 读取进程整个地址空间(通过 /proc/pid/mem 或 ptrace 读,或直接读 /proc/pid/maps 列出的匿名/文件映射区) | restart 时重新 mmap 同样布局,把数据写回对应虚拟地址 |
| 寄存器 | 线程静止后,从内核 ptrace 或 getcontext 拿到每个线程的 pt_regs/上下文 | restart 时 setcontext 还原 |
| 文件描述符 | 靠 §5.1 记录的元数据:路径+offset、pipe 标识、socket 四元组 | restart 时重新 open/pipe/socket+connect,并把 fd 设回原编号、恢复文件偏移(lseek) |
| 线程 | 记录线程数、各线程栈、TLS、起始函数 | restart 时重新 pthread_create 等价物,再 setcontext 让每个线程"以为自己从未停过" |
| TCP/UDP socket | 记录对端地址、连接状态、pending 数据 | restart 时重新建连(DMTCP 在 socket 层做透明重协商,对端无感知) |
| 信号 | 记录 pending 信号、handler 地址、sigaction 设置 | restart 时重新 sigaction 注册、重投 pending 信号 |
| 共享内存 | 记录 shm/mmap MAP_SHARED 段标识 | restart 时重新 attach 同一段(多进程间保持一致) |
| 进程树关系 | 记录父子、PGID/SID、PID 映射 | restart 时重建关系,必要时做 PID 重映射 |
5.4 最难的两关:FD 与 socket 的"复活"
- 普通文件 FD:还好办——重新
open同一路径、dup2到原 fd、再用lseek恢复偏移,应用完全无感。 - TCP socket:无法"冻结一个活连接再原样搬走"(对端不认)。DMTCP 的真正技巧是在 checkpoint 前,让应用层"优雅"地停掉连接或在 socket 层做透明重连:DMTCP 拦截
connect/accept,记录连接上下文,restart 后自动重新connect到原对端,使上层应用看到"连接还在"。这正是它名字里 "MultiThreaded" + "Distributed" 的含金量——要协调多进程把 socket 状态对齐。 - pipe / FIFO:记录管道标识,restart 时重建一对 pipe 并连通对应进程的两端。
5.5 恢复(restart):重演一次"假启动"
restart 进程启动后:
libdmtcp.so重新初始化,读入快照文件;- 按记录的布局重新
mmap内存、重建所有 FD、重连 socket、重建共享内存; - 重新创建线程,用
setcontext把每个线程的寄存器/栈/TLS 还原到冻结瞬间; - 重新注册信号处理、重投 pending 信号;
- 所有线程放开屏障,从冻结点继续执行——应用代码对此一无所知。

6. DMTCP vs CRIU:两种哲学
| 维度 | DMTCP | CRIU |
|---|---|---|
| 实现层级 | 纯用户态,LD_PRELOAD 注入 | 内核辅助(/proc、ptrace、memfd、socket diag、kcmp) |
| 是否需要 root | 否(普通用户可用) | 通常需 root 或特定 capability |
| 是否改应用 | 否(wrapper 启动即可) | 否 |
| 内核版本依赖 | 极低(不依赖新内核特性) | 较高(依赖多个较新内核接口) |
| 跨版本/跨机迁移 | 强(用户态抽象,可移植性好) | 弱(内核状态耦合,要求同内核特性) |
| 性能开销 | 较高(syscall 包装拦截、全量内存拷贝) | 较低(直接读内核结构) |
| 分布式/多进程协调 | 原生 coordinator 支持 | 需外部编排(如 P.Haul) |
| 典型用途 | HPC、MPI、长作业、教学研究 | 容器热迁移、CRIU+Docker/runc、LXD |
一句话:CRIU 快但"贴着内核",DMTCP 慢但" portable 且无需特权",在老内核、无 root、分布式 MPI 场景里 DMTCP 往往是唯一选择。
7. 基本用法
bash
# 1) 用 dmtcp 启动程序(透明,不改代码)
dmtcp_launch ./my_long_running_app
# 2) 另开终端发 checkpoint 指令(默认连本机 coordinator)
dmtcp_command -c # -c = checkpoint
# 3) 之后恢复(生成了 ckpt_*.dmtcp)
dmtcp_restart ckpt_*.dmtcp
# 4) 分布式/多机:先起 coordinator,各节点指向它
dmtcp_coordinator & # 协调者
dmtcp_launch --coord-host=hostA --coord-port=7779 mpi_app8. 局限与适用边界
- 不是所有状态都能完美复活:某些强依赖硬件/内核内部态的场景(如已
mmap的设备寄存器、内核态 RDMA 队列、某些加密硬件上下文)无法透明恢复; - 性能敏感型 syscall 会被拦截:
libdmtcp.so包装 syscall 会引入少量开销,对极端性能敏感程序需评估; - 多线程竞态:检查点必须发生在所有线程一致静止点,若应用用不安全的
fork多线程(见 fork-and-threads.md),会放大风险; - 大内存进程:快照体积 ≈ 进程常驻内存,checkpoint 时间与磁盘 IO 成正比。
9. 与本项目其它文档的关系
- 进程资源视图:DMTCP 保存的每一项(FD、内存、信号、线程),都能在 task-struct.md 与其子目录 task-resources/ 找到对应的内核对象;
- FD 重建:靠 vfs-binding.md 里讲的
files_struct/路径解析; - 线程复活:对应 thread-creation.md 与 signal-multithread.md;
- socket 重连:与 shared-memory.md 同属"跨进程/跨机状态"主题;
- 拦截 syscall:其
LD_PRELOAD手法与 library-injection.md 的注入原理一脉相承(区别:DMTCP 是"善意的、为了保存状态"的注入)。
10. 一句话总结
DMTCP 是一个纯用户态、对应用透明的检查点/重启工具:它用 LD_PRELOAD 把 libdmtcp.so 注入进程劫持所有外部状态相关的 syscall,靠 coordinator 把整棵进程树汇合到全局静止点,把内存、寄存器、FD、线程、TCP 连接、信号、共享内存全部序列化,之后再在同机或异机原样重演"假启动"恢复执行;相比 CRIU 它慢但无需 root、不挑内核、原生支持分布式,是 HPC/MPI 长作业断点续算与故障回滚的利器。
关联文档
- task-struct.md —— 进程的内核资源全景,DMTCP 保存的每一项都映射到这里
- fork-and-threads.md —— 多线程 fork 陷阱,影响 checkpoint 安全性
- thread-creation.md —— 线程创建,restart 时如何重建线程
- signal-multithread.md —— 多线程信号投递,pending 信号如何恢复
- vfs-binding.md —— FD 与 VFS 绑定,checkpoint 时如何记录/恢复文件偏移
- library-injection.md —— LD_PRELOAD 注入原理,DMTCP 透明性的技术根基
- shared-memory.md —— 共享内存,checkpoint 时如何保持多进程一致