make 构建卡死无响应超时 hang 速查
更新时间:2026-10-24。FAQ 速查页,面向用 GNU make 构建工程的同学。make 构建卡死是最折磨人的一类故障:终端最后停在某条命令上,几分钟到几小时没有新输出,Ctrl-C 之前你不知道它是「在编译一个超大文件」还是「已经死了只是没说」。卡死的本质是 make 在等——等 CPU、等锁、等子进程、等网络文件系统、甚至等一个根本不存在的键盘输入。和报错类故障不同,卡死没有现成的报错原文当锚点,起手式必须是「先看它到底在等什么」。本篇给出完整的定位路径;如果你的 make 是直接报错退出而非卡住,请转 Makefile missing separator 语法解析报错速查 或 make: No rule to make target 报错速查。
本文要回答的问题
- make 卡住时怎么判断它是「在干活」还是「死等」?
-j并行度怎么会把机器拖到整体卡死?- make 会死锁吗?循环依赖为什么不报错而是卡住?
- 卡在一条看似无害的命令上,其实是在等什么?
make -d的海量输出里该 grep 什么?
一、典型现象与定位起手式
卡死的典型长相(工程路径以 /proj/demo 占位)——输出停在某一行,光标静止,风扇可能狂转也可能完全安静:
$ make -j32
g++ -O2 -c src/engine.cpp -o build/engine.o
g++ -O2 -c src/render.cpp -o build/render.o
(此后 40 分钟无任何输出)第一招永远是另开一个终端看进程状态,分清「忙等」与「死等」:
$ ps aux | grep -E "make|cc1plus|ld" | grep -v grep
dev 3101 187 4.2 2133912 852440 ? R 14:02 38:12 cc1plus ...
dev 3100 0 0.1 15320 5120 pts/0 S+ 14:02 0:00 make -j32
$ cat /proc/3101/status | grep -E "State|VmRSS"
State: R (running)
VmRSS: 831856 kB状态 R(running)且 CPU 占用高——它在真干活,只是慢(超大文件或机器被拖垮);状态 S/D 且 CPU 近零——它在死等,继续往下查等什么。D(不可中断睡眠)几乎一定是卡在 IO 上(NFS、磁盘);S 且没有任何子进程在活动,八成在等锁或等输入。再用 pstree -p <make的PID> 看进程树:make 下面挂着什么、挂着的进程又在干什么,一层层看到叶子。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
-j32 全机卡死,鼠标都卡 | 并行度超内存,swap 颠簸 | free -h 看 swap 用满、dmesg 有 OOM 记录 | 降 -j 至 内存GB/2,或 -j 配 -l 负载阈值 |
| make 无输出且无任何编译进程 | 循环依赖,作业互为前提谁也不先动 | make -d 输出尾部反复出现同一组目标 | 断开环:找出被互相依赖的伪目标改写 |
停在 ar / 链接 / 自定义脚本上,CPU 为零 | 配方里的命令在等 stdin(如 read、未加 -y 的确认提示) | ls -l /proc/<PID>/fd/0 看是否指向终端;strace -p 看是否 read(0) | 给命令加非交互参数或 < /dev/null |
| 输出停在旧目标上,文件时间戳看着正常 | NFS/共享盘上时钟漂移,make 误判「永远不需要重建」或无限等待 | stat 对比服务端与客户端文件 mtime | 校准 NTP;touch 刷新;避免跨机共享构建目录 |
| 递归 make 卡住,顶层在等子 make | 子 make 未继承 jobserver,或子 make 自己卡死 | pstree 看子 make 下是否有活动进程 | 子 make 用 + 前缀与 $(MAKE),修子 make 的卡点 |
| 同一构建目录两个 make 实例 | 锁文件/部分写入的 .o 互相等待 | ps aux | grep make 看是否多实例 | 杀残留实例,构建脚本加 flock |
三、排查决策图
下面这张图按「make 在等什么」分岔:红是卡死入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓一条主线:先分「忙等 / 死等」再下手。忙等(R 状态、CPU 满)基本是 -j 算术问题,降并行立竿见影;死等里再按等什么分三路——等 IO(D 状态查 NFS 与磁盘)、等输入(strace 看到 read 卡在 fd 0)、等彼此(make -d 里目标打转或两个 make 实例抢同一目录)。所有修法都以 make -j1 顺序构建能完整走完为验收底线,顺序能过而并行卡,问题就锁定在资源与并发而非 makefile 逻辑。
四、逐条排查展开
-j 拖垮整机是最常见的「卡死」。 并行度超过内存承载后,内核开始疯狂 swap,所有进程一起爬行,表象就是「make 卡死了」——其实它没死,是每个 cc1plus 都在排队等内存页换入。判据:free -h 里 swap 用量顶格、vmstat 1 的 si/so 列持续非零。修法立竿见影:make -j4(经验上限 内存GB/2)重跑;更精细的写法是 make -j16 -l8,让 make 在系统负载超 8 时暂缓开新作业。这类「假卡死」与真编译器崩溃的鉴别见 make 编译器内部崩溃速查。
循环依赖为什么不报错。 GNU make 对显式环会给警告 Circular x <- y dependency dropped 然后丢弃一条边继续走——但涉及模式规则、伪目标、或递归 make 时,环可能表现为一组目标反复「考虑—推迟—再考虑」,作业图收敛不下来,输出上看就是卡住。make -d 的海量输出里 grep 三样:Circular(显式环实锤)、Must remake target 的反复出现(同一目标被反复考虑)、最后一行停在哪个 Considering target file。断环方法:把「为了方便互相 include 依赖」的伪目标拆开,用 order-only 依赖(| 之后)表达「先要它存在但不要因它重建」。
等 stdin 是最隐蔽的死等。 配方里任何可能交互的命令在非交互环境都会卡死:脚本里的 read -p、没加 -y 的包管理器、ssh 首次连接的主机确认、rm -i 别名被带进 make。定位:strace -p <卡住的PID> 看到 read(0, 就实锤。修法两条路并行:命令全面非交互化(apt-get -y、yes |、删掉 read),并且给配方统一兜底 cmd < /dev/null,让任何残留交互企图立刻报错而不是傻等。
NFS 时钟漂移的灵异表现。 构建目录挂 NFS、客户端与服务端时钟差几秒时,make 依据 mtime 的判断全部失真:刚生成的 .o 时间戳在「未来」,make 认为它永远新鲜,或反复重建同一批文件。判据:在客户端 date 与 stat 服务端文件对比时间;构建日志里同一目标被无理由反复重编也是信号。修法:所有相关机器强制 NTP 校时;构建目录尽量放本地盘,NFS 只放源码只读挂载。
多实例与残留进程。 CI 上「上一次构建没死透」是经典剧情:残留的 make 占着构建目录里的部分写入文件,新 make 的编译进程打开同名 .o 时互相等待。构建脚本入口加 flock /proj/demo/.build.lock make ... 一行根治,顺手在 CI 超时钩子里 pkill -f 清场。
make -d 的正确读法。 输出量极大,直接翻会淹死。管道过滤:make -d 2>&1 | grep -E "Circular|Must remake|Considering|Finished",卡住时 Ctrl-C,最后出现的 Considering target file 'xxx' 就是病灶所在;配合 make --trace(4.x)能直接看到每个目标的命中规则与行号。
五、规范做法与预防
- CI 必设超时与看门狗:
timeout 30m make ...,超时即杀并保留ps与dmesg快照,卡死从「人肉发现」变「自动报警」。 - 配方命令禁交互白名单:团队约定配方内命令必须可在
< /dev/null下跑通,code review 对read、无-y的安装命令一票否决。 - 并行度按机器算:构建脚本用
nproc与内存取小值,不硬编码-j32这类拍脑袋数字。 - 构建目录本地化:NFS/共享盘只挂源码,
.o与临时文件落本地盘,NTP 校时纳入装机清单。 - 递归 make 守规矩:子目录构建一律
+$(MAKE) -C subdir,保住 jobserver 传递,杜绝顶层等子、子等顶层。
这一篇在知识体系里的位置
本篇是 Makefile 排错速查簇的卡死/超时篇,定位「make 不报错也不前进」:编译器进程被杀或崩溃的看 make 编译器内部崩溃速查;解析阶段 Stop 的看 Makefile missing separator 语法解析报错速查;规则缺失的看 make: No rule to make target 报错速查;头文件问题的看 fatal error 头文件找不到速查;类型报错的看 error: cannot convert 类型不匹配速查;链接期问题的看 undefined reference 链接错误速查;权限问题的看 构建 Permission denied 报错速查;输出乱码的看 make 构建输出乱码速查;CMake 侧的看 CMake 版本不兼容报错速查 与 CMake FetchContent 拉取失败速查。「先分忙等死等、strace 看 read(0)」这一套对任何进程卡死类故障通用。
一句话总结
make 构建卡死先另开终端分清忙等与死等:ps 看到 R 状态 CPU 满且 swap 顶格就是 -j 超内存,降到 内存GB/2 或配 -l 负载阈值;零 CPU 的死等再分三路——strace 看到 read(0, 是配方在等 stdin(命令非交互化并重定向 /dev/null)、make -d 尾部同一批目标打转是循环依赖(断环、用 order-only 依赖)、D 状态卡 IO 查 NFS 时钟漂移与磁盘。CI 加 timeout 看门狗与 flock 防多实例,卡死即可从「玄学等待」变成「三分钟定位」。