在断开 ssh 连接后执行的任务就会收到 SIGHUP
,默认情况下就会退出,除非添加 nohup
或者 setsid
之类的命令,下面简单分析 SIGHUP
的产生过程以及这类命令的工作原理。
参考《unix环境高级编程》第九章 进程关系
会话、进程组、进程之间关系图
进程组(process group)包含一个或多个进程,每个进程组可能有一个组长进程(process group leader),pgid = pid, shell 中作业操作(Job control)以进程组为单位。
会话(session)包含一个或者多个进程组的。
控制终端与会话、前/后台进程组、进程之间的关系
会话首进程(session leader),创建该会话的的进程,其中 sid = pid,一个会话可以包含多个进程组。
控制终端(controlling terminal),指登录到该会话的终端设备(tty)或网络登录的伪终端设备(pseudo-tty),终端断开产生的信号(SIGHUP)会发送给会话首进程。
控制进程(controlling process),与控制终端连接的会话首进程(session leader)。
会话中的进程组分为一个前台进程组(foreground process group)和一个或多个后台进程组(backgrond process group),终端中的操作信号均是发送给的前台进程组的所有进程。
sshd 服务的进程组结构
伪终端(pty)分为主设备(ptm)和从设备(pts),并且成对出现,主从设备之间连接在可以简单看作双向管道。终端行规程(line discipline)主要用于翻译特殊字符成特定操作,如 CTRL-C 转化成中断。
sshd 子进程将 ssh 客户端的输入转发到 pty 另一端作为终端输入,同时将终端输出转发给 ssh 客户端作为标准输出, ssh 客户端作为标准输入输出的控制终端
ssh-client <-> [socket] <-> sshd <-> [ptm] <-> [pts] <-> shell
多个 ssh 客户端之间发送信息可以向对方的 pts 设备直接写数据,对应 ssh 客户端则会收到消息
shell[root@CentOS6 ~]# echo "Hello pts0" >/dev/pts/0
ssh 连接下的会话、进程、进程组信息
[root@CentOS6 ~]# sleep 60 &[1] 1673[root@CentOS6 ~]# sleep 70 | sleep 71 & [2] 1675[root@CentOS6 ~]# ps -o user,tty,sid,pid,ppid,pgid,cmd USER TT SID PID PPID PGID CMDroot pts/0 1519 1519 1517 1519 -bashroot pts/0 1519 1673 1519 1673 sleep 60root pts/0 1519 1674 1519 1674 sleep 70root pts/0 1519 1675 1519 1674 sleep 71root pts/0 1519 1676 1519 1676 ps -o user,tty,sid,pid,ppid,pgid,cmd
当前 ssh 连接会话为 1519
-bash
进程为 1519 会话控制进程(pid = sid)
sleep 60
属于进程组 1673,sleep 70
和 sleep 71
属于进程组 1474
执行的 sleep 命令都属于 1519 会话,终端设备都为 pts/0
我们开启三个终端连接,第一个用于测试终端断开,第二个用于跟踪终端进程信号,第三个用于跟踪终端中作业进程信号。
终端1(这里省略了其它无关进程信息)
[root@CentOS6 ~]# ps -o pid,ppid,pgid,sid,cmd PID PPID PGID SID CMD 1042 1 1042 1042 /usr/sbin/sshd22668 1042 22668 22668 sshd: root@pts/0 22670 22668 22670 22670 -bash22720 22670 22720 22670 sleep 2h
其中 pid 1042 为 sshd 服务器后端进程,pid 22668 为 ssh-client 连接启动的 sshd 子进程,pid 22670 为登录的 shell 进程,也是该连接会话(pid=sid=22670)的控制进程,所以当网络连接断开,系统将发送SIGHUP
信号给该进程。pid 22720 为在终端 1 中运行的后台作业,它与终端进程属于同一个会话。
终端2,通过 strace
命令跟踪会话控制进程 -bash
的信号
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22670 Process 22670 attached
终端3,跟踪后台作业进程 sleep 2h
的信号
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22720Process 22720 attached
断开终端1连接,会话控制进程将收到系统发送的 SIGHUP
,同时根据 bash manual , bash
退出之前将收到 SIGHUP
将转发给作业列表中的所有作业进程
The shell exits by default upon receipt of a
SIGHUP
. Before exiting, an interactive shell resends theSIGHUP
to all jobs, running or stopped. Stopped jobs are sentSIGCONT
to ensure that they receive theSIGHUP
. To prevent the shell from sending theSIGHUP
signal to a particular job, it should be removed from the jobs table with thedisown
builtin (see Job Control Builtins) or marked to not receiveSIGHUP
usingdisown -h
.
终端2上可以看到,bash
进程收到系统发送的 SIGHUP
并向进程组发送SIGHUP
,最后向自身发送 SIGHUP
退出
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22670Process 22670 attached15:30:09.872582 --- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL, si_value={int=0, ptr=0x100000000}} --- //收到系统 SIGHUP15:30:09.872799 --- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL, si_value={int=0, ptr=0x100000000}} ---15:30:09.874105 kill(4294944576, SIGHUP) = 0 <0.000035> // 向进程组 22720 发送 SIGHUP15:30:09.874200 rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], 8) = 0 <0.000039>15:30:09.874537 rt_sigprocmask(SIG_SETMASK, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], NULL, 8) = 0 <0.000141>15:30:09.874735 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=22720, si_status=SIGHUP, si_utime=0, si_stime=0} --- //子进程退出15:30:09.874988 rt_sigreturn() = 0 <0.000034>15:30:09.875136 rt_sigaction(SIGHUP, {SIG_DFL, [], SA_RESTORER, 0x7fadec1c2660}, {0x452300, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x7fadec1c2660}, 8) = 0 <0.000040>15:30:09.875243 kill(22670, SIGHUP) = 0 <0.000035> //向自身进程发送 SIGHUP15:30:09.875328 rt_sigreturn() = -1 EIO (Input/output error) <0.000032>15:30:09.875425 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=22670, si_uid=0} --- //收到自己的 SIGHUP15:30:09.875714 +++ killed by SIGHUP +++
kill(4294944576, SIGHUP)
为 kill(-22720, SIGHUP)
,表示向 222720 进程组发送 SIGHUP
,参考 man kill
If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid.
终端3,后台作业进程也收到 bash
(si_pid=22670)发送的 SIGHUP
后退出
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22720Process 22720 attached15:30:09.874246 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=22670, si_uid=0} ---15:30:09.874470 +++ killed by SIGHUP +++
另外 bash
自身进程退出是否会发送SIGHUP
,这个取决于 huponexit
选项设置
If the
huponexit
shell option has been set withshopt
(see The Shopt Builtin), Bash sends aSIGHUP
to all jobs when an interactive login shell exits.
在我们测试环境中(CentOS6.5)中 huponexit
选项默认为关闭,所以 bash
自身的退出不会产生 SIGHUP
[root@CentOS6 ~]# shopt huponexit huponexit off
所以当我们通过 exit
退出 bash 时,后台任务并不会结束
[root@CentOS6 ~]# sleep 1h & [1] 5343[root@CentOS6 ~]# exitlogout
新打开一个终端,可以看到作业仍在运行
[root@CentOS6 ~]# pgrep sleep5343
disown
VS nohup
VS setsid
这是我们常用让程序在终端断开或退出后也能保持后台运行的几个命令,这些命令执行后都能让程序保持运行,但是它们内部工作原理并不一样。
disown
,参考 bash manual 中的介绍,disown 只是将程序移出 shell 的作业列表,从而不会向其发送SIGHUP
,当我们手动发送一个 SIGHUP
给该进程,进程同样会退出。
终端1,在 sleep 1h
命令后添加 disown
将其移出作业列表,执行 jobs
命令无法看到。
sleep 1h & disown [1] 22763[root@CentOS6 ~]# jobs[root@CentOS6 ~]# kill -SIGHUP 22763
终端2,执行 kill
命令后程序收到 SIGHUP
退出。
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22763Process 22763 attached16:20:04.453605 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=22687, si_uid=0} ---16:20:04.453874 +++ killed by SIGHUP +++
nohup
则是通过忽略掉收到 SIGHUP
信号来保持进程运行,参考 coreutils 中部分代码,会先忽略 SIGHUP
再通过 exec
调用执行命令。
....signal (SIGHUP, SIG_IGN);char **cmd = argv + optind;execvp (*cmd, cmd);...
终端1,通过 nohup
执行 sleep 1h
命令,进程可以在作业列表中查看到。通过 ps
中 ignored
列可以看到进程忽略了 SIGHUP
(SIGHUP 数值表示为 1,参考 singal(7))。
[root@CentOS6 ~]# nohup sleep 1h &[1] 22767[root@CentOS6 ~]# nohup: ignoring input and appending output to `nohup.out'[root@CentOS6 ~]# jobs[1]+ Running nohup sleep 1h &[root@CentOS6 ~]# ps -o pid,ppid,pgid,sid,ignored,cmd -p 22767 PID PPID PGID SID IGNORED CMD22767 22687 22767 22687 0000000000000001 sleep 1h
终端2,断开终端1连接和手动发送 SIGHUP
,进程都能收到 SIGHUP
,但并不会退出。
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22767Process 22767 attached16:53:42.935575 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=22687, si_uid=0} ---16:54:22.096888 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=22865, si_uid=0} ---^CProcess 22767 detached
setsid
通过创建一个新的会话来执行命令,让进程与终端进程处于不同会话,这样也不会收到 shell
转发的SIGHUP
信号,但是未屏蔽 SIGHUP
,进程如果收到 其它发送的 SIGHUP
仍然会退出。
setsid runs a program in a new session. The command calls fork(2) if already a process group leader. Otherwise, it executes a program in the current process. This default behavior is possible to override by the --fork option.
终端1 ,通过 setsid
执行的 sleep 1h
进程会话 ID 变成了22794,也不在 bash
的作业列表里了。
[root@CentOS6 ~]# setsid sleep 1h &[1] 22793[root@CentOS6 ~]# jobs[root@CentOS6 ~]# ps -o pid,ppid,pgid,sid,ignored,cmd PID PPID PGID SID IGNORED CMD 1042 1 1042 1042 0000000000001000 /usr/sbin/sshd22685 1042 22685 22685 0000000000001000 sshd: root@pts/1 22687 22685 22687 22687 0000000000384004 -bash22794 1 22794 22794 0000000000000000 sleep 1h
bash
创建程序时会为其设置新的进程组(job 管理基于进程组),新创建的 setsid
程序(pid 22793)为 process group leader, fork
一个子进程(pid 22794)来执行 sleep 1h
命令,并调用 setsid()
为其设置了新的会话(sid 22794)。创建好后父进程退出(为什么 jobs 命令看不到),子进程变成孤儿进程,被 init
(pid 1)接管。
终端2,断开终端1连接,进程未收到任何信号。
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22794Process 22794 attached
终端2,手动发送 SIGHUP
后,进程收到 SIGHUP
信号并退出。
[root@CentOS6 ~]# strace -ttT -e trace=signal -p 22794Process 22794 attached17:48:57.162818 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=22812, si_uid=0} ---17:48:57.163155 +++ killed by SIGHUP +++
《UNIX环境高级编程》
联系客服