为什么后台 ssh 可以从 Bash 接管 tty?
Why a background ssh can take over the tty from Bash?
(我在 Debian 8 上使用 Bash 4.4.12。问题也在 bash mailing list 中提出。)
请参阅以下步骤重现该问题。
来自 tty #1 (pts/2
):
[STEP 101] # tty
/dev/pts/2
[STEP 102] # ssh -o ControlMaster=yes -o ControlPath=/tmp/socket.ssh -N -f 127.0.0.1
[STEP 103] # ps -C ssh u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1390 0.0 0.0 36440 656 ? Ss 11:33 0:00 ssh -o ControlMaster=yes -o ControlPath=/tmp/so
[STEP 104] #
[STEP 105] # ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh \
127.0.0.1 sleep 3600 &
[1] 1396
[STEP 106] # <-- Here I cannot input anything except <CTRL-C>
STEP 102 启动了多路复用 SSH 连接 运行ning 作为守护进程。 STEP 105 尝试使用多路复用连接到 运行 一个 sleep
命令。但是后来我无法在当前 shell 中输入任何内容。如果我终止 ssh ... sleep &
进程,那么 Bash 将能够再次接受我的输入。似乎所有输入都被后台 ssh
进程消耗了。
转到 tty #2 (pts/3
):
[STEP 201] # tty
/dev/pts/3
[STEP 202] # ps t pts/2 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
723 1353 1353 1353 pts/2 1353 Ss+ 0 0:00 bash
1353 1396 1396 1353 pts/2 1353 S 0 0:00 ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600
[STEP 203] # ps s 1396
UID PID PENDING BLOCKED IGNORED CAUGHT STAT TTY TIME COMMAND
0 1396 00000000 00000000 00001000 188004003 S pts/2 0:00 ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600
[STEP 204] #
我解码了信号掩码:
PENDING (00000000):
BLOCKED (00000000):
IGNORED (00001000):
13 PIPE
CAUGHT (188004003):
1 HUP
2 INT
15 TERM
28 WINCH
32
33
这里我们可以看到ssh进程没有捕捉到SIGTTIN
信号。这就是让我感到困惑的原因,因为后台作业(进程组)应该接收 SIGTTIN
并在它尝试从 tty 读取时停止。
我想我已经弄清楚发生了什么。让我解释一下。
来自 tty #1 (pts/2
):
[STEP 300] # tty
/dev/pts/2
[STEP 301] # ssh -o ControlMaster=yes -o ControlPath=/tmp/socket.ssh -N -f 127.0.0.1 < /dev/null >& /dev/null
[STEP 302] # ps -C ssh j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 4052 4052 4052 ? -1 Ss 0 0:00 ssh -o ControlMaster=yes -o ControlPath=/tmp/socket.ssh -N -f 127.0.0.1
[STEP 303] # ls -l /proc/4052/fd/
total 0
lr-x------ 1 root root 64 2017-06-12 22:59 0 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 1 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 2 -> /dev/null
lrwx------ 1 root root 64 2017-06-12 22:59 3 -> socket:[370151]
lrwx------ 1 root root 64 2017-06-12 22:59 4 -> socket:[370201]
[STEP 304] # ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600 &
[1] 4062
[STEP 305] # <-- Cannot input anything
去去 tty #2 (pts/3
):
[STEP 401] # tty
/dev/pts/3
[STEP 402] # ps t pts/2 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
579 3552 3552 3552 pts/2 3552 Ss+ 0 0:00 bash
3552 4062 4062 3552 pts/2 3552 S 0 0:00 ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600
[STEP 403] # ls -l /proc/4062/fd/ # The `ssh ... sleep' process
total 0
lrwx------ 1 root root 64 2017-06-12 23:00 0 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:00 1 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:00 2 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:00 3 -> socket:[370349]
[STEP 404] # ls -l /proc/4052/fd/ # The `ssh -o ControlMaster=yes' process
total 0
lr-x------ 1 root root 64 2017-06-12 22:59 0 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 1 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 2 -> /dev/null
lrwx------ 1 root root 64 2017-06-12 22:59 3 -> socket:[370151]
lrwx------ 1 root root 64 2017-06-12 22:59 4 -> socket:[370201]
lrwx------ 1 root root 64 2017-06-12 23:02 5 -> socket:[370350]
lrwx------ 1 root root 64 2017-06-12 23:02 6 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:02 7 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:02 8 -> /dev/pts/2
[STEP 405] #
步骤 403's output shows that the ssh ... sleep
process' stdin/stdout/stderr 在 pts/2
上打开。这是正常的。
但是 STEP 404 的输出(与 STEP 303 相比)显示 ssh -o ControlMaster=yes
进程也在打开 pts/2
。我相信这就是多路复用 SSH 的工作方式——新的 ssh ... sleep
进程通过 UNIX 域套接字 (-o ControlPath=/tmp/socket.ssh
) 将其打开的文件描述符传递给 ssh -o ControlMaster=yes
进程。所以实际上是 ssh -o ControlMaster=yes
进程消耗了来自 pts/2
的所有输入。由于 ssh -o ControlMaster=yes
进程与 bash
进程(和 ssh ... sleep
)不在同一个进程会话中,因此作业控制机制不适用于它,即使它在 运行背景(作为守护进程)并从 pts/2
.
中读取
换句话说:SIGTTIN
仅发送到作为后台作业运行并尝试从其 控制终端 读取的进程。这里 ssh -o ControlMaster=yes
进程在后台 运行 但它不是 bash
进程会话 的工作并且它没有 控制终端。
关于通过 UNIX 域套接字在进程之间传递 FD 的更多信息(来自 Wikipedia):
In addition to sending data, processes may send file descriptors across a Unix domain socket connection using the sendmsg()
and recvmsg()
system calls. This allows the sending processes to grant the receiving process access to a file descriptor for which the receiving process otherwise does not have access.
(我在 Debian 8 上使用 Bash 4.4.12。问题也在 bash mailing list 中提出。)
请参阅以下步骤重现该问题。
来自 tty #1 (pts/2
):
[STEP 101] # tty
/dev/pts/2
[STEP 102] # ssh -o ControlMaster=yes -o ControlPath=/tmp/socket.ssh -N -f 127.0.0.1
[STEP 103] # ps -C ssh u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1390 0.0 0.0 36440 656 ? Ss 11:33 0:00 ssh -o ControlMaster=yes -o ControlPath=/tmp/so
[STEP 104] #
[STEP 105] # ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh \
127.0.0.1 sleep 3600 &
[1] 1396
[STEP 106] # <-- Here I cannot input anything except <CTRL-C>
STEP 102 启动了多路复用 SSH 连接 运行ning 作为守护进程。 STEP 105 尝试使用多路复用连接到 运行 一个 sleep
命令。但是后来我无法在当前 shell 中输入任何内容。如果我终止 ssh ... sleep &
进程,那么 Bash 将能够再次接受我的输入。似乎所有输入都被后台 ssh
进程消耗了。
转到 tty #2 (pts/3
):
[STEP 201] # tty
/dev/pts/3
[STEP 202] # ps t pts/2 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
723 1353 1353 1353 pts/2 1353 Ss+ 0 0:00 bash
1353 1396 1396 1353 pts/2 1353 S 0 0:00 ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600
[STEP 203] # ps s 1396
UID PID PENDING BLOCKED IGNORED CAUGHT STAT TTY TIME COMMAND
0 1396 00000000 00000000 00001000 188004003 S pts/2 0:00 ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600
[STEP 204] #
我解码了信号掩码:
PENDING (00000000):
BLOCKED (00000000):
IGNORED (00001000):
13 PIPE
CAUGHT (188004003):
1 HUP
2 INT
15 TERM
28 WINCH
32
33
这里我们可以看到ssh进程没有捕捉到SIGTTIN
信号。这就是让我感到困惑的原因,因为后台作业(进程组)应该接收 SIGTTIN
并在它尝试从 tty 读取时停止。
我想我已经弄清楚发生了什么。让我解释一下。
来自 tty #1 (pts/2
):
[STEP 300] # tty
/dev/pts/2
[STEP 301] # ssh -o ControlMaster=yes -o ControlPath=/tmp/socket.ssh -N -f 127.0.0.1 < /dev/null >& /dev/null
[STEP 302] # ps -C ssh j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 4052 4052 4052 ? -1 Ss 0 0:00 ssh -o ControlMaster=yes -o ControlPath=/tmp/socket.ssh -N -f 127.0.0.1
[STEP 303] # ls -l /proc/4052/fd/
total 0
lr-x------ 1 root root 64 2017-06-12 22:59 0 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 1 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 2 -> /dev/null
lrwx------ 1 root root 64 2017-06-12 22:59 3 -> socket:[370151]
lrwx------ 1 root root 64 2017-06-12 22:59 4 -> socket:[370201]
[STEP 304] # ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600 &
[1] 4062
[STEP 305] # <-- Cannot input anything
去去 tty #2 (pts/3
):
[STEP 401] # tty
/dev/pts/3
[STEP 402] # ps t pts/2 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
579 3552 3552 3552 pts/2 3552 Ss+ 0 0:00 bash
3552 4062 4062 3552 pts/2 3552 S 0 0:00 ssh -o ControlMaster=no -o ControlPath=/tmp/socket.ssh 127.0.0.1 sleep 3600
[STEP 403] # ls -l /proc/4062/fd/ # The `ssh ... sleep' process
total 0
lrwx------ 1 root root 64 2017-06-12 23:00 0 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:00 1 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:00 2 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:00 3 -> socket:[370349]
[STEP 404] # ls -l /proc/4052/fd/ # The `ssh -o ControlMaster=yes' process
total 0
lr-x------ 1 root root 64 2017-06-12 22:59 0 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 1 -> /dev/null
l-wx------ 1 root root 64 2017-06-12 22:59 2 -> /dev/null
lrwx------ 1 root root 64 2017-06-12 22:59 3 -> socket:[370151]
lrwx------ 1 root root 64 2017-06-12 22:59 4 -> socket:[370201]
lrwx------ 1 root root 64 2017-06-12 23:02 5 -> socket:[370350]
lrwx------ 1 root root 64 2017-06-12 23:02 6 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:02 7 -> /dev/pts/2
lrwx------ 1 root root 64 2017-06-12 23:02 8 -> /dev/pts/2
[STEP 405] #
步骤 403's output shows that the ssh ... sleep
process' stdin/stdout/stderr 在 pts/2
上打开。这是正常的。
但是 STEP 404 的输出(与 STEP 303 相比)显示 ssh -o ControlMaster=yes
进程也在打开 pts/2
。我相信这就是多路复用 SSH 的工作方式——新的 ssh ... sleep
进程通过 UNIX 域套接字 (-o ControlPath=/tmp/socket.ssh
) 将其打开的文件描述符传递给 ssh -o ControlMaster=yes
进程。所以实际上是 ssh -o ControlMaster=yes
进程消耗了来自 pts/2
的所有输入。由于 ssh -o ControlMaster=yes
进程与 bash
进程(和 ssh ... sleep
)不在同一个进程会话中,因此作业控制机制不适用于它,即使它在 运行背景(作为守护进程)并从 pts/2
.
换句话说:SIGTTIN
仅发送到作为后台作业运行并尝试从其 控制终端 读取的进程。这里 ssh -o ControlMaster=yes
进程在后台 运行 但它不是 bash
进程会话 的工作并且它没有 控制终端。
关于通过 UNIX 域套接字在进程之间传递 FD 的更多信息(来自 Wikipedia):
In addition to sending data, processes may send file descriptors across a Unix domain socket connection using the
sendmsg()
andrecvmsg()
system calls. This allows the sending processes to grant the receiving process access to a file descriptor for which the receiving process otherwise does not have access.