如何使fork子进程的程序在无终端SSH调用时释放连接?
问题分析
你的程序在无终端SSH调用时阻塞的核心原因有两点:
- 主进程通过
os.wait()等待第一个子进程退出,即便第一个子进程很快会fork孙子进程后终止,这段等待期间SSH会话仍会绑定主进程的输出管道; - 子进程仅关闭
stdin/stdout/stderr文件对象,未彻底将这些流重定向到/dev/null,可能导致SSH管道的写端未完全释放,sshd会持续等待直到所有关联描述符关闭。
解决方案
通过调整主进程逻辑、完善子进程的daemon化步骤即可解决问题:
修改后的代码
#! /usr/bin/env python3 import os, time, sys print("Hello world", flush=True) if os.fork() == 0: # 子进程:完成标准daemon化流程 # 1. 重定向标准流到/dev/null(替代直接关闭,避免文件描述符复用风险) devnull = os.open('/dev/null', os.O_RDWR) os.dup2(devnull, sys.stdin.fileno()) os.dup2(devnull, sys.stdout.fileno()) os.dup2(devnull, sys.stderr.fileno()) os.close(devnull) # 2. 创建新会话,脱离原进程组与控制终端 os.setsid() # 3. 二次fork,确保孙子进程无法成为会话领头进程,彻底脱离关联 if os.fork() > 0: os.exit(0) # 4. 切换工作目录到根目录,避免挂载点被占用 os.chdir('/') # 5. 重置umask,保证子进程创建文件的权限正常 os.umask(0) # 业务逻辑:此处替换为实际需要后台运行的代码 time.sleep(30) os.exit(0) # 主进程无需等待子进程,直接输出后退出,立即释放SSH连接 print("bye, world", flush=True)
关键调整说明
- 移除
os.wait():主进程fork子进程后无需等待,直接输出并终止,让SSH会话立即释放连接; - 重定向标准流到
/dev/null:替代直接关闭标准流,确保子进程的所有标准流完全脱离SSH管道,切断与sshd的关联; - 补充标准daemon化步骤:添加切换工作目录、重置umask等操作,让子进程完全符合后台daemon的规范,避免意外资源占用。
测试验证
修改后重新测试三个场景:
- 直接运行:
./test-prog.py→ 立即退出,孙子进程后台运行30秒; - 带终端SSH调用:
ssh -t server ./test-prog.py→ 正常输出并退出,孙子进程后台运行; - 无终端SSH调用:
ssh server ./test-prog.py→ 输出两行后立即释放连接,孙子进程后台运行30秒。
内容的提问来源于stack exchange,提问作者ChewbaccaKL
相关产品推荐
相关产品推荐

