暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

SSH 注销后如何确保任务持续运行?

watson 2024-11-13
213

1. 写在前面

在 Linux 系统中,执行一些运行时间比较长的任务时,必须等待执行完毕才能断开 ssh 连接或关闭客户端软件,否则可能会导致执行中断。本文将介绍几种实用的技巧和工具(nohup、setsid、()、disown等),确保退出 ssh 登录后任务持续运行。

2. 为什么 ssh 注销后任务终止?

原因分析:

当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)/ SIGHUP 信号从而关闭其所有子进程。 

jpzhang-dev:~$ ping www.bing.com > /dev/null &
[1] 1631074
jpzhang-dev:~$ pstree -H 1631074
systemd─┬─ModemManager───2*[{ModemManager}]
├─NetworkManager───2*[{NetworkManager}]
├─accounts-daemon───2*[{accounts-daemon}]
├─snapd───23*[{snapd}]
├─sshd───sshd───sshd───bash─┬─ping
│ └─pstree
... ...

从上面的例子:

... ...

├─sshd───sshd───sshd───bash─┬─ping
│ └─pstree
... ...

可以看出当前所处的 bash 是 sshd 的子进程,当 ssh 断开连接时,HUP 信号会影响到它下面的所有子进程,包括示例中的 ping 进程,指示它们终止,这是为了释放系统资源并确保没有孤立进程在运行。

HUP (hangup) 信号:

在 Linux 和 Unix 系统中,HUP(Hangup)信号是一个重要的信号,通常被表示为 SIGHUP。通常在用户注销、终端关闭或网络连接断开时发送。

SIGHUP 的主要作用:

  • 通知进程终止:当终端连接中断或用户注销时,系统会向与该终端相关联的所有进程发送 SIGHUP 信号,默认情况下,这些进程会收到该信号并终止。

  • 重启进程:许多守护进程(daemon)会捕获 SIGHUP 信号,以便在收到信号时重新加载配置文件,而不是完全停止进程。例如,nginx、httpd 等服务会在接收到 SIGHUP 信号后重读其配置。

  • 实现进程管理:系统管理员可以使用 SIGHUP 信号来控制服务的行为,例如通过命令 kill -HUP向特定进程发送 SIGHUP,使其重新加载配置或优雅地结束。

进程可以通过设置信号处理程序来捕获 SIGHUP 信号。以下是一个简单的示例(使用 Python):

py_signal.py:

import os
import signal
import time

def handle_sighup(signum, frame):
print("Received SIGHUP signal!")

# 注册信号处理器
signal.signal(signal.SIGHUP, handle_sighup)

# 模拟进程持续运行
print("Process is running. PID:", os.getpid())
while True:
time.sleep(1)

终端1: 运行脚本

jpzhang-dev:~/workspace/linux$ python3 py_signal.py
Process is running. PID: 1633852
Received SIGHUP signal!

终端2: 发送 SIGHUP

kill -HUP 1633852

终端1,输出 "Received SIGHUP signal!"

查询更多信号:kill -lman signalman 7 signal

解决思路:

  • 让进程运行在新的 session(会话)里,即不属于此终端的子进程;

  • 让进程忽略 HUP 信号;

3. 技巧 & 工具

【注意】下文提到的快捷键,不区分字母大小写;

3.1 nohup 命令

nohup(no hang up)命令用于在 Linux/Unix 系统中运行进程,使其在用户注销或终端关闭后继续运行。默认情况下,nohup 会忽略 SIGHUP 信号,从而确保进程不会被终止。

语法:

nohup command [arguments] &
  • command:要执行的命令或程序;

  • [arguments]:传递给命令的参数(可选);

  • &:将命令放入后台执行,允许你继续使用终端;

示例:

  • 启动一个长时间运行的命令,比如 ping,并使用 nohup:

    nohup ping www.bing.com &

    关闭此终端,打开另一个终端使用 ps 命令,查看到后台有 ping 进程在运行,终端输出(stdout、stderr)将附加到当前目录的 nohup.out 文件中。如果当前目录的 nohup.out 文件不可写,输出将重定向到 $HOME/nohup.out 文件中。

    jpzhang-dev:~$ ps -ef | grep ping
    root 1643510 1638994 0 11:06 pts/4 00:00:00 ping www.bing.com
    jpzhang-dev:~$ tail -f nohup.out
    64 字节,来自 202.89.233.100 (202.89.233.100): icmp_seq=53 ttl=116 时间=23.9 毫秒
    64 字节,来自 202.89.233.100 (202.89.233.100): icmp_seq=54 ttl=116 时间=23.2 毫秒
    ... ...
  • 重定向输出:

    nohup ping www.bing.com > output.log 2>&1 &
    • > output.log: 将标准输出重定向到 output.log 文件;

    • 2>&1: 将标准错误重定向到标准输出,使所有输出都写入同一个文件;

3.2 setsid 命令

setsid 命令用于在 Linux/Unix 系统中创建一个新的会话,并将指定的程序在新的会话中运行,脱离当前终端,使其不再受到终端的控制和影响。

语法:

setsid command [arguments]
  • command:要执行的命令或程序;

  • [arguments]:传递给命令的参数(可选);

示例:

setsid ping www.bing.com > /dev/null

setsid ping www.bing.com > output.log 2>&1
jpzhang-dev:/home/workspace/linux# ps -ef | grep ping
root 1645405 1 0 11:21 ? 00:00:00 ping www.bing.com

从上例可以看出 ping 进程的 PID 是 1645405,进程的父 ID(PPID) 是 init(1) 而不是当前终端的进程 ID,可与 nohup 比较。

3.3 将 "&" 放入 “()” 内执行命令

在 Linux/Unix 系统中,将命令放在 () 中并使用 & 可以创建一个子进程,并在后台执行该命令。这种方法可以在新的子 Shell 中运行多个命令,而不会影响当前的 Shell 环境。

语法:

( command1; command2; ... ) &
  • command1; command2; ...:可以是多个命令,使用分号 ";" 分隔;

  • ():表示在子 Shell 中执行命令;

  • &:将整个命令组放入后台执行;

示例:

(ping www.bing.com > /dev/null &)


(ping www.bing.com > output.log 2>&1 &)

jpzhang-dev:/home/workspace/linux# (ping www.bing.com > /dev/null &)
jpzhang-dev:/home/workspace/linux# ps -ef | grep ping
root 1645585 1 0 11:22 pts/4 00:00:00 ping www.bing.com

进程的父 ID 是 init(1) 而不是当前终端的进程 ID,因此关闭当前终端无任何影响。

3.4 disown 命令

disown 命令的功能是用于从 Shell 终端中移除作业,是一个 bash 解释器的内置命令。disown 命令能够从 Shell 终端中移除指定的作业,正在运行的作业也可以被移除,亦可以对作业进行标记,使其在当前 Shell 终端窗口关闭后依然不会结束。

【重点】disown 命令可以移除当前正在运行的作业(后台作业)或者通过 “Ctrl + Z”,确保它在终端关闭或 ssh 会话断开后继续运行;

语法:

disown [options] [job_spec]
  • options:

    • -h:标记作业使其结束 Shell 终端窗口时不退出;

    • -a:移除所有的作业;

    • -r:移除运行的作业;

    • -ah:所有的作业都忽略 HUP 信号;

    • -rh:正在运行的作业忽略 HUP 信号;

  • job_spec:作业编号(例如 %1、%2)或作业名称。可以使用 jobs 命令查看后台作业编号。如果不指定 job_spec,disown 会移除当前 Shell 的最后一个作业。

当使用过 disown 命令后,会将把目标作业从作业列表中移除,不能再使用 jobs 来查看它,但是依然能够用 ps -ef 查找到它。

如下示例所示:

jpzhang-dev:~# ping www.bing.com > /dev/null &
[1] 1665969
jpzhang-dev:~# jobs
[1]+ 运行中 ping www.bing.com > /dev/null &
jpzhang-dev:~# disown %1
jpzhang-dev:~# jobs
jpzhang-dev:~# ps -ef | grep ping
root 1665969 1642922 0 14:00 pts/5 00:00:00 ping www.bing.com

关闭终端,再次打开查询进程,可以看到进程的父 ID(PPID) 是 init(1):

jpzhang-dev:~# ps -ef | grep ping
root 1665969 1 0 14:00 ? 00:00:00 ping www.bing.com

但是还有一个问题,这种方法的操作对象是作业,如果我们在运行命令时在结尾加了 "&" 来使它成为一个作业并在后台运行,那就没问题,可以通过 jobs 命令来得到所有作业的列表。

但是如果并没有把当前命令作为作业来运行,如何才能得到它的作业号呢?答案就是用 "Ctrl + Z"(按住 Ctrl 键的同时按住 Z 键)。

如下示例所示:

# Ctrl + Z 将程序放入后台,暂停执行(挂起进程)
jpzhang-dev:~# ping www.bing.com > /dev/null
^Z
[1]+ 已停止 ping www.bing.com > /dev/null
jpzhang-dev:~# jobs
[1]+ 已停止 ping www.bing.com > /dev/null

# 继续执行后台的作业
jpzhang-dev:~# bg %1
[1]+ ping www.bing.com > /dev/null &
jpzhang-dev:~# jobs
[1]+ 运行中 ping www.bing.com > /dev/null &

# 此时 jobs 还是能看到 ping 后台任务的。
jpzhang-dev:~# ps -ef | grep ping
root 1667480 1665681 0 14:12 pts/7 00:00:00 ping www.bing.com

# 标记作业使其结束 Shell 终端窗口时不退出
jpzhang-dev:~# disown -h %1

# 需要断开 ssh 链接后,重新登录
jpzhang-dev:~# ps -ef | grep ping
root 1667480 1 0 14:12 ? 00:00:00 ping www.bing.com

可以看到进程的父 ID(PPID) 是 init(1)。

Ctrl + Z 的用途就是将当前进程挂起(Suspend),然后我们就可以用 jobs 命令来查询它的作业号,再用 bg jobspec 来将它放入后台并继续运行。需要注意的是,如果挂起会影响当前进程的运行结果,请慎用此方法。

命令整理:

$ sleep 500

# 假设有一个程序在前台运行,请按 Ctrl + Z(挂起):
^Z
[1]+ Stopped myprogram

# 标记作业使其结束 Shell 终端窗口时不退出,如果只有一个作业,则无需指定作业编号:disown -h
$ disown -h %1

# 继续执行后台的作业,如果只有一个作业,则无需指定作业编号:bg;("fg %1" 重新回到前台运行)
$ bg %1
[1]+ sleep 500 &

$ logout

$ ps -ef | grep sleep
root 1668456 1 0 14:18 pts/4 00:00:00 sleep 500

3.5 screen 命令

screen 是 Linux/Unix 系统中的一个多路复用终端工具,允许用户在一个终端会话中运行多个程序,尤其是在 ssh 连接断开或关闭终端时,它能够保持会话和进程的运行状态。

功能:

  • 保持会话活跃:即使 ssh 连接断开或终端关闭,正在运行的任务依旧继续;

  • 恢复会话:断开后可以重新连接到同一个会话,查看任务执行情况;

  • 多任务管理:在一个 screen 会话中可以运行多个终端任务,类似于多标签的终端操作;

  • 会话分离与恢复:可以随时将会话分离(detach),稍后再恢复(reattach);

安装:

# CentOS
$ yum install screen

# Debian/Ubuntu
$ apt install screen

语法:

screen [options] [command]
  • screen:启动一个新的 screen 会话;

  • screen -S session_name:创建一个带有自定义名称的会话;

  • screen -r [session_name]:恢复一个分离的会话;

  • screen -ls:列出当前存在的所有 screen 会话;

  • Ctrl + A + D:分离当前会话,保持程序运行但退出到普通终端;

示例:

  • 运行长时间任务并确保不中断

    # 进入 screen 后运行 ping 任务
    screen -S ping_session



    # 直接在启动 screen 时指定要运行的命令
    screen -S ping_session ping www.bing.com > /dev/null

    Ctrl + A + D:分离当前会话,保持程序运行但退出到普通终端。

    列出所有 screen 会话:

    jpzhang-dev:~# screen -ls
    There are screens on:
    1673330.ping_session (10/10/24 14:56:40) (Detached)
    3424088.pts-4.jpzhang-dev (09/27/24 09:52:59) (Detached)
    2 Sockets in /run/screen/S-root.

    恢复分离的 screen 会话:

    # 如果只有一个分离(Detached)的 screen 会话,可以直接使用 screen -r 来恢复
    screen -r 1673330

    screen -r ping_session

    关闭 screen 会话:

    • 在 screen 会话中执行 exit,会话会自动关闭;

    • 若没有使用分离,直接关闭终端连接时,screen 会话和进程也会结束;

  • 多任务并行管理

    启动 screen 会话后,你可以通过 Ctrl + A + C 创建多个窗口,在不同窗口中运行多个任务。例如,窗口 1 :代码编译、窗口 2:监控日志,窗口 3:网络测试。

    screen -S multitask
    • 新建窗口:在 screen 会话内按 Ctrl + A + C 创建新窗口,并在每个窗口中执行不同的任务;

    • 切换窗口:Ctrl + A + N(下一个窗口)或 Ctrl + A + P(上一个窗口);

    • 分离会话:Ctrl + A + D 返回到普通终端;

3.6 tmux 命令

tmux 是一款强大的终端多路复用器(terminal multiplexer),类似于 screen,它允许在单个终端窗口中运行多个会话、分屏显示、共享会话等。

功能:

  • 会话持久性:ssh 断开后,tmux 会话及其运行中的任务依然保留,可以重新连接;

  • 会话管理:可以创建多个会话,每个会话可以包含多个窗口和子任务;

  • 窗口和分屏:一个 tmux 会话中可以打开多个窗口,每个窗口可以再分成多个分屏(pane),让你同时运行和监控多个任务;

  • 会话共享:你可以和其他用户共享 tmux 会话,进行协作;

安装:

# Ubuntu 或 Debian
$ apt-get install tmux

# CentOS 或 Fedora
$ yum install tmux

语法:

tmux [options] [command]
  • tmux:启动一个新的 tmux 会话;

  • tmux new -s session_name:创建一个带有自定义名称的会话;

  • tmux attach -t session_name:重新连接到一个已有的会话;

  • tmux ls:列出所有当前存在的 tmux 会话;

  • tmux detach:分离当前会话(保持任务运行);

示例:

  • 使用 tmux 保持 ssh 会话

    tmux new -s long_task
    # 执行任务: ping www.bing.com

    Ctrl + B, D # 分离会话: 先按 Ctrl + B, 在键入 D(注:字母键,不区分大小写)

    # 断开 SSH,通过以下命令恢复
    tmux attach -t long_task

    # 关闭 tmux 会话
    # 在 tmux 会话内执行 exit 命令
  • 创建多个窗口和分屏

    tmux new -s dev

    # 创建新窗口
    Ctrl + B, C

    # 在当前窗口垂直分屏
    Ctrl + B, %

    # 在当前窗口水平分屏
    Ctrl + B, "

    # 切换窗口
    Ctrl + B, N/P # 上下窗口
    Ctrl + B, 0-9 # 切换到指定编号的窗口

    # 切换分屏
    Ctrl + B, O
    Ctrl + B, [方向键]

    # 关闭分屏,分屏内执行 exit 命令

tmux、screen 功能对比:

功能tmuxscreen
基本功能提供终端多路复用、分屏、会话保持等功能提供终端多路复用、分屏、会话保持等功能
会话管理支持命名会话,便于管理和恢复也支持命名会话,但语法和管理方式不如 tmux 灵活
分屏管理支持窗口内的水平和垂直分屏,操作简单支持窗口分屏,但控制较少,分屏操作不如 tmux 灵活
窗口管理直接支持多窗口、多个子任务也支持多窗口管理,但操作上相对复杂
快捷键默认快捷键组合以 Ctrl + B 开头,可自定义。字母键,不区分大小写默认快捷键组合以 Ctrl + A 开头,可自定义。字母键,不区分大小写
滚动缓冲更易用的滚动缓冲区,支持快捷键或鼠标滚动支持滚动缓冲区,默认情况下需要进入复制模式
会话共享支持多个用户同时连接并操作同一会话也支持,但操作复杂
配置文件配置文件更简洁灵活,支持脚本化的自动化配置配置文件较复杂,功能较为基础

4. 总结

现在几种方法已经介绍完毕,可以根据不同的场景来选择不同的方案:

  • &: 将进程置于后台,使 Shell 不用等待它的结束而继续接受用户输入 (stdin);

  • nohup/setsid/(): 无疑是临时需要时最方便的方法;

  • disown: 能帮助我们事后补救当前已经在运行了的作业,若需要 Ctrl + Z 挂起进程,需考虑是否影响当前进程的运行结果;

  • tmuxscreen: 则是在大批量操作时不二的选择了;

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论