Windows端fork子进程SSH到Linux执行脚本偶发卡住问题求助
故障背景
- 代码运行逻辑:fork生成子进程后,子进程从Windows侧SSH登录Linux机器qwelnx45,运行
script1_bkt.csh脚本,日志输出到Windows路径$AngleLogFilePath\Angle_${date}.log,对应实际路径为V:\ricky_angle_testing_63.1\depot\vfr\63.10\main\logs\angleLogsrickyPersonal\Angle_${date}.log。 - 前台父进程
trialSet.pl、trialSetDepWint.pl运行状态正常。 - V盘为映射
/dv/lmv/mentor/路径的CIFS网络存储。
问题现象
- fork生成的子进程从Windows SSH到Linux运行
script1_bkt.csh时偶发卡住,非必现。 - 关键特征:Linux端
script1_bkt.csh的PID运行一段时间后消失,说明脚本执行完成,但Windows侧触发脚本的ssh.exePID仍存在,对应命令$GoToUnix cd $ClientAltRoot/lkg ; source script1_bkt.csh未正常结束,陷入卡住状态。脚本正常执行耗时约3小时,偶发会无限卡住。 - 关联现象:Windows端子进程卡住时,即便Linux端脚本已执行完毕,对应的日志文件内容不完整,缺少脚本输出的部分数据,疑似进程卡住后停止写入日志。
相关代码
use File::Path qw( make_path ); use POSIX qw(strftime); my $ClientAltRoot = "/dv/lmv/mentor/ricky_angle_testing_63.1/depot/vfr/63.10/main/"; my $GoToUnix = "C:\\cygwin\\bin\\ssh.exe qwelnx45"; my $AngleLogFilePath = "V:\\ricky_angle_testing_63.1\\depot\\vfr\\63.10\\main\\logs\\angleLogsrickyPersonal"; my $date = strftime("%Y%m%d_%H%M%S", localtime(time)); make_path("$AngleLogFilePath") or warn "Failed to create dir $AngleLogFilePath"; my $aqpid; # fork angle process if ($aqpid = fork()) { $SIG{CHLD} = 'DEFAULT'; } elsif (defined ($aqpid)) { sleep 10; print "Angle child started\n"; $angleReturnStatus = system ("$GoToUnix cd $ClientAltRoot/lkg ; source script1_bkt.csh > $AngleLogFilePath\\Angle_${date}.log "); $angleFailed += 1 if ($angleReturnStatus > 0); exit 0; } print "##### Starting the foreground script ###### \n"; system "$GoToUnix \"cd /home/ricky/; echo abc ; /home/ricky/trialSet.pl > setTrial_ricky/set_${date}.log\" "; print "Ended SetDep\n"; print "Waiting as child process has not ended"; 1 while (wait() != -1); system ( "perl C:\\ricky\\Testing\\trialSetDepWint.pl "); print "Demo script ended\n";
根因分析
- SSH流缓冲区阻塞:当前命令仅重定向了标准输出到CIFS路径的日志文件,未处理标准错误流。当脚本产生大量stderr输出时,SSH缓冲区被占满会导致进程挂起,即便远程脚本执行完毕,SSH也不会退出,因为缓冲区还有未写入的数据。再加上CIFS网络存储本身存在写入抖动,日志写入出现短暂超时时,会进一步加剧缓冲区无法清空的问题,最终导致进程卡住。
- Cygwin SSH终端适配问题:Cygwin版本的SSH默认远程执行命令时不会分配伪终端,若脚本在特定分支逻辑下尝试读取终端输入(比如隐性调用read、弹出交互确认、密码提示),会因无终端可用而挂起,这也是问题偶发的核心诱因之一。
- SSH连接超时僵死:脚本执行时长长达3小时,若中间网络出现短暂波动、防火墙会话超时,会导致SSH连接被静默中断,但TCP层未检测到断连,SSH进程就会陷入僵死状态无法退出。
- Windows Perl fork模拟缺陷:Windows无原生fork机制,Perl的fork是用线程模拟实现的,当前设置的
$SIG{CHLD} = 'DEFAULT'信号处理逻辑在模拟场景下可能存在异常,导致子进程退出后状态未被正确回收,或子进程内部的system调用信号传递失败。
解决方案
快速验证修复
首先修改子进程内的system调用,补充SSH参数和流重定向逻辑:
$angleReturnStatus = system ("$GoToUnix -n -o BatchMode=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=10 \"cd $ClientAltRoot/lkg ; source script1_bkt.csh 2>&1\" > $AngleLogFilePath\\Angle_${date}.log ");
参数说明:
-n:将SSH标准输入重定向到/dev/null,避免脚本尝试读取输入导致挂起-o BatchMode=yes:禁用所有交互提示,遇到需要密码/确认的场景直接报错退出,不会卡住-o ServerAliveInterval=30:每30秒向服务端发送保活包-o ServerAliveCountMax=10:连续10次保活失败自动断开SSH连接,避免僵死2>&1:将标准错误合并到标准输出,避免stderr缓冲区满导致的阻塞
如果修改后仍偶发卡住,可以临时把日志输出路径改到Windows本地磁盘而非CIFS存储,验证是否是CIFS写入抖动导致的问题。如果本地路径无故障,说明CIFS是核心诱因,可以调整为先写本地磁盘,脚本执行完成后再异步同步到CIFS路径。
进阶优化
- 用IPC::Run替代system调用执行SSH命令,主动设置超时阈值,超过3.5小时就强制杀掉SSH进程,避免无限卡住:
use IPC::Run qw( run timeout ); my $cmd = [split(/ /, $GoToUnix), '-n', '-o', 'BatchMode=yes', '-o', 'ServerAliveInterval=30', '-o', 'ServerAliveCountMax=10', "cd $ClientAltRoot/lkg ; source script1_bkt.csh 2>&1"]; run $cmd, '>', "$AngleLogFilePath\\Angle_${date}.log", timeout(3.5*3600) or do { $angleFailed +=1; };
- 在
script1_bkt.csh末尾添加exit 0,强制脚本执行完后主动关闭会话。 - 父进程增加子进程运行状态监控,定时检查子进程运行时长,超过阈值就强制杀掉子进程和对应的SSH进程,避免阻塞后续任务执行。
内容的提问来源于stack exchange,提问作者AskQuestionsP
相关产品推荐
相关产品推荐

