为何wait $PID收到SIGTERM后立即返回而非等待进程终止?
信号传播场景下wait命令的异常行为与解决方法
问题概述
在shell脚本中运行子进程并传播终止信号(SIGINT/SIGTERM/SIGQUIT)时,发现使用wait $CHILD_PID会在陷阱处理函数收到信号的瞬间立即返回,必须再次执行wait $CHILD_PID才能获取子进程的实际退出码:
- 第一次
wait返回状态码为128+信号值(如SIGINT对应130) - 第二次
wait返回子进程的实际退出码
测试环境与脚本
信号传播脚本(signal-propagate)
#!/bin/sh _term() { signal=$1 echo "🔴 caught $signal, sending it to child pid $CHILD..." kill -$signal $CHILD 2>/dev/null } # 注册终止信号 trap "_term TERM" SIGTERM trap "_term QUIT" SIGQUIT trap "_term INT" SIGINT # 后台启动命令 $@ & # 获取子进程PID CHILD=$! # 等待子进程结束 wait $CHILD status=$? echo "1 First Wait: child pid $CHILD exited with status $status" wait $CHILD status=$? echo "2 Second wait: child pid $CHILD exited with status $status" exit $status
测试用子进程脚本(work.php)
<?php function sig_handler($signo) { echo "🟡Signaler Handler Invoked! \n"; switch ($signo) { case SIGINT: echo "Caught SIGINT CTRL-C...\n"; echo "Shutdown Function Invoked, sleeping \n"; sleep(5); echo "Shutdown Function done \n"; exit(12); break; default: // 处理其他信号 } } // 启用异步信号处理 pcntl_async_signals(true); // 设置信号处理函数 pcntl_signal(SIGINT, "sig_handler"); echo "Doing some work... \n"; sleep(1000); ?>
测试执行与输出
执行命令:
./signal-propagate php work.php
输出结果:
Doing some work... ^C🟡Signaler Handler Invoked! Caught SIGINT CTRL-C... Shutdown Function Invoked, sleeping 🔴 caught INT, sending it to child pid 47270... 1 First Wait: child pid 47270 exited with status 130 Shutdown Function done 2 Second wait: child pid 47270 exited with status 12
问题原因分析
根据POSIX标准对wait的定义,wait会等待指定进程终止,但当shell收到信号并触发陷阱函数时,正在阻塞的wait命令会被中断,提前返回并给出128+信号值的状态码,此时子进程只是收到了信号,还在执行优雅终止逻辑并未真正退出。后续再次调用wait $CHILD_PID时,shell会读取子进程已经缓存的退出状态,返回其实际退出码。
解决方案:循环等待直到子进程真正终止
为确保脚本等待子进程完全终止并获取正确的退出码,需将wait放在循环中,直到子进程状态被正确获取(即wait返回的状态码不是信号中断导致的128+值)。修正后的脚本如下:
#!/bin/sh _term() { signal=$1 echo "🔴 caught $signal, sending it to child pid $CHILD..." kill -$signal $CHILD 2>/dev/null } # 注册终止信号 trap "_term TERM" SIGTERM trap "_term QUIT" SIGQUIT trap "_term INT" SIGINT # 后台启动命令 $@ & # 获取子进程PID CHILD=$! # 循环等待直到子进程真正终止 while :; do wait $CHILD status=$? # 检查是否是信号中断导致的返回(状态码>=128) if [ $status -lt 128 ]; then break fi # 否则继续等待子进程退出 done echo "Final child exit status: $status" exit $status
说明
- 循环中每次调用
wait,如果是被信号中断返回(状态码>=128),则继续等待;直到子进程真正退出,wait返回其实际退出码(<128),才退出循环。 - 该方案兼容
sh/bash/zsh,符合仅支持sh的运行环境要求,同时满足循环运行子进程的实际场景需求。
内容的提问来源于stack exchange,提问作者Sherif Abdel-Naby
相关产品推荐
相关产品推荐

