분명히 동일한 쉘이 동일한 서버에 대해 여러 SSH 연결을 시작하면 주어진 명령을 실행한 후 반환되지 않고 Stopped (tty input)
영원히 정지됩니다( ). 표시하려면:
#!/bin/bash
ssh localhost sleep 2
echo "$$ DONE!"
위 스크립트를 백그라운드에서 여러 번 실행하면 절대 종료되지 않습니다.
$ for i in {1..3}; do foo.sh & done
[1] 28695
[2] 28696
[3] 28697
$ ## Hit enter
[1] Stopped foo.sh
[2]- Stopped foo.sh
[3]+ Stopped foo.sh
$ ## Hit enter again
$ jobs -l
[1] 28695 Stopped (tty input) foo.sh
[2]- 28696 Stopped (tty input) foo.sh
[3]+ 28697 Stopped (tty input) foo.sh
세부 사항
- Perl 스크립트에서 ssh를 통해 명령을 실행하고 있었기 때문에 이것을 발견했습니다. Perl을 사용하여
system()
launch를 호출할 때도 동일한 동작이 발생합니다ssh
. - 대신 Perl 모듈을 사용할 때 시도해
system()
보았습니다Net::SSH::Perl
.Net:SSH2
Net::OpenSSH
- 다른 셸에서 여러 ssh 명령을 실행하면(여러 터미널 열기) 예상대로 작동합니다.
SSH 연결 디버그 정보에는 분명히 유용한 것이 없습니다.
OpenSSH_7.5p1, OpenSSL 1.1.0f 25 May 2017 debug1: Reading configuration data /home/terdon/.ssh/config debug1: Reading configuration data /etc/ssh/ssh_config debug2: resolving "localhost" port 22 debug2: ssh_connect_direct: needpriv 0 debug1: Connecting to localhost [::1] port 22. debug1: Connection established. debug1: identity file /home/terdon/.ssh/id_rsa type 1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_rsa-cert type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_dsa type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_dsa-cert type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ecdsa type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ecdsa-cert type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ed25519 type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ed25519-cert type -1 debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_7.5 debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5 debug1: match: OpenSSH_7.5 pat OpenSSH* compat 0x04000000 debug2: fd 3 setting O_NONBLOCK debug1: Authenticating to localhost:22 as 'terdon' debug3: hostkeys_foreach: reading file "/home/terdon/.ssh/known_hosts" debug3: record_hostkey: found key type ECDSA in file /home/terdon/.ssh/known_hosts:47 debug3: load_hostkeys: loaded 1 keys from localhost debug3: order_hostkeyalgs: prefer hostkeyalgs: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521 debug3: send packet: type 20 debug1: SSH2_MSG_KEXINIT sent debug3: receive packet: type 20 debug1: SSH2_MSG_KEXINIT received debug2: local client KEXINIT proposal debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c debug2: host key algorithms: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,[email protected],[email protected],ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: compression ctos: none,[email protected],zlib debug2: compression stoc: none,[email protected],zlib debug2: languages ctos: debug2: languages stoc: debug2: first_kex_follows 0 debug2: reserved 0 debug2: peer server KEXINIT proposal debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1 debug2: host key algorithms: ssh-rsa,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519 debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected] debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected] debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: compression ctos: none,[email protected] debug2: compression stoc: none,[email protected] debug2: languages ctos: debug2: languages stoc: debug2: first_kex_follows 0 debug2: reserved 0 debug1: kex: algorithm: curve25519-sha256 debug1: kex: host key algorithm: ecdsa-sha2-nistp256 debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none debug3: send packet: type 30 debug1: expecting SSH2_MSG_KEX_ECDH_REPLY debug3: receive packet: type 31 debug1: Server host key: ecdsa-sha2-nistp256 SHA256:uxhkh+gGPiCJQPaP024WXHth382h3BTs7QdGMokB9VM debug3: hostkeys_foreach: reading file "/home/terdon/.ssh/known_hosts" debug3: record_hostkey: found key type ECDSA in file /home/terdon/.ssh/known_hosts:47 debug3: load_hostkeys: loaded 1 keys from localhost debug1: Host 'localhost' is known and matches the ECDSA host key. debug1: Found key in /home/terdon/.ssh/known_hosts:47 debug3: send packet: type 21 debug2: set_newkeys: mode 1 debug1: rekey after 134217728 blocks debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug3: receive packet: type 21 debug1: SSH2_MSG_NEWKEYS received debug2: set_newkeys: mode 0 debug1: rekey after 134217728 blocks debug2: key: /home/terdon/.ssh/id_rsa (0x555a5e4b5060) debug2: key: /home/terdon/.ssh/id_dsa ((nil)) debug2: key: /home/terdon/.ssh/id_ecdsa ((nil)) debug2: key: /home/terdon/.ssh/id_ed25519 ((nil)) debug3: send packet: type 5 debug3: receive packet: type 7 debug1: SSH2_MSG_EXT_INFO received debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521> debug3: receive packet: type 6 debug2: service_accept: ssh-userauth debug1: SSH2_MSG_SERVICE_ACCEPT received debug3: send packet: type 50 debug3: receive packet: type 51 debug1: Authentications that can continue: publickey,password debug3: start over, passed a different list publickey,password debug3: preferred publickey,keyboard-interactive,password debug3: authmethod_lookup publickey debug3: remaining preferred: keyboard-interactive,password debug3: authmethod_is_enabled publickey debug1: Next authentication method: publickey debug1: Offering RSA public key: /home/terdon/.ssh/id_rsa debug3: send_pubkey_test debug3: send packet: type 50 debug2: we sent a publickey packet, wait for reply debug3: receive packet: type 60 debug1: Server accepts key: pkalg rsa-sha2-512 blen 279 debug2: input_userauth_pk_ok: fp SHA256:OGvtyUIFJw426w/FK/RvIhsykeP8kIEAtAeZwYBIzok debug3: sign_and_send_pubkey: RSA SHA256:OGvtyUIFJw426w/FK/RvIhsykeP8kIEAtAeZwYBIzok debug3: send packet: type 50 debug3: receive packet: type 52 debug1: Authentication succeeded (publickey). Authenticated to localhost ([::1]:22). debug2: fd 6 setting O_NONBLOCK debug1: channel 0: new [client-session] debug3: ssh_session2_open: channel_new: 0 debug2: channel 0: send open debug3: send packet: type 90 debug1: Requesting [email protected] debug3: send packet: type 80 debug1: Entering interactive session. debug1: pledge: network debug3: receive packet: type 80 debug1: client_input_global_request: rtype [email protected] want_reply 0 debug3: receive packet: type 91 debug2: callback start debug2: fd 3 setting TCP_NODELAY debug3: ssh_packet_set_tos: set IPV6_TCLASS 0x08 debug2: client_session2_setup: id 0 debug1: Sending command: sleep 2 debug2: channel 0: request exec confirm 1 debug3: send packet: type 98 debug2: callback done debug2: channel 0: open confirm rwindow 0 rmax 32768 debug2: channel 0: rcvd adjust 2097152 debug3: receive packet: type 99 debug2: channel_input_status_confirm: type 99 id 0 debug2: exec request accepted on channel 0
이것은 내 설정에 의존하지 않습니다
~/.ssh/config
. 파일 이름을 바꾸면 아무 것도 변경되지 않습니다.- 이는 여러 컴퓨터에서 발생합니다. 최신 Ubuntu 및 Arch 배포판을 실행하는 4~5개의 다른 컴퓨터를 사용해 보았습니다.
- 명령(
sleep
더미 예제에서는 더 복잡하지만 실제 생활에서는 더 복잡함)이 성공적으로 종료되고 예상된 작업을 수행합니다. 이는 실행 중인 명령에 의존하지 않으며 SSH 문제입니다. - 그 중 최악은 다음과 같습니다.일관성이 없다. 때때로 인스턴스 중 하나가 종료되고 제어권이 상위 스크립트로 반환됩니다. 하지만 항상 그런 것은 아니며 어떤 패턴도 식별할 수 없습니다.
- 이름을 바꿔도
~/.bashrc
아무런 차이가 없습니다. 또한 Ubuntu(기본 로그인 쉘dash
) 및 Arch(기본 로그인 쉘bash
, 이라고 함 )를 실행하는sh
시스템에서 이를 실행했습니다 . - Enter흥미롭게도 문제는 루프를 시작한 후 첫 번째 스크립트가 종료되기 전에 아무 키나 누르는 경우에만 발생합니다(예: 아무 키나 작동하는 것 같습니다). 터미널을 그대로 두면 예상대로 완료됩니다.
어떻게 되어가나요? 이것은 ssh의 버그입니까? 옵션을 설정해야 하나요? 동일한 셸에서 SSH를 통해 명령을 실행하는 스크립트의 여러 인스턴스를 어떻게 시작할 수 있나요?
답변1
포그라운드 프로세스 및 터미널 접근 제어
무슨 일이 일어나고 있는지 이해하려면 공유 터미널에 대해 조금 알아야 합니다. 두 프로그램이 동시에 동일한 터미널에서 읽으려고 하면 어떻게 됩니까? 각 입력 바이트는 프로그램 중 하나로 무작위로 들어갑니다. (커널에서 RNG를 사용하여 결정된 대로 무작위가 아니라 실제로 예측할 수 없으므로 무작위입니다.) 두 프로그램이 파이프나 다른 파일 유형에서 읽을 때(한 위치에서 바이트 스트림 이동), 무슨 일이 발생합니까? 모든 바이트를 여러 번 읽을 수 있는 바이트 배열(일반 파일, 블록 장치)과 달리 다른 장치(소켓, 문자 장치 등)에서 발생합니다. 예를 들어 터미널에서 셸을 실행하려면 터미널 이름을 찾아 실행합니다 cat
.
$ tty
/dev/pts/18
$ cat
그런 다음 다른 터미널에서 실행합니다 cat /dev/pts/18
. 이제 터미널에 입력하고 때로는 cat
프로세스 중 하나로 이동하고 때로는 다른 프로세스로 이동하는 줄을 확인합니다. 터미널이 베이킹 모드에 있으면 라인이 전체적으로 예약됩니다. 터미널을 원시 모드로 전환하면 각 바이트가 독립적으로 전달됩니다.
지저분해요. 확실히 하나의 프로그램이 터미널을 얻고 다른 프로그램은 그렇지 않다는 것을 결정하는 메커니즘이 있어야 합니다. 글쎄요! 일반적인 상황에서는 발생하지만 위에서 설정한 시나리오에서는 발생하지 않습니다. 이 상황은 cat /dev/pts/18
처음부터 시작되지 않기 때문에 일반적이지 않습니다 /dev/pts/18
. 단말기 내에서 실행되지 않은 프로그램에서 단말기에 접속하는 경우가 흔하지 않습니다. 일반적인 상황에서는 터미널에서 셸을 실행한 다음 해당 셸에서 프로그램을 실행합니다. 그러면 포그라운드에 있는 프로그램이 터미널을 가져오지만 백그라운드에 있는 프로그램은 터미널을 얻지 못하는 것이 규칙입니다. 이것은 ... 불리운다터미널 출입통제. 작동 방식은 다음과 같습니다.
- 모든 프로세스에는제어 터미널(또는 일반적으로 터미널로 열린 파일 설명자가 없기 때문에 그렇지 않습니다.)
- 프로세스가 제어 터미널에 액세스하려고 시도할 때 프로세스가 포그라운드에 있지 않으면 커널은 이를 차단합니다. (제한사항이 적용됩니다. 다른 단말기에 대한 접근은 제한되지 않습니다.)
- 쉘은 누가 포그라운드 프로세스인지 결정합니다. (실제로는 포그라운드 프로세스 그룹입니다.)
tcsetpgrp
누가 전경에 있어야 하는지 커널에 알립니다.
이는 일반적인 경우에 작동합니다. 셸에서 프로그램을 실행하면 해당 프로그램이 포그라운드 프로세스가 됩니다. 백그라운드에서 프로그램을 실행하면( 사용 &
) 프로그램이 포그라운드에서 실행되지 않습니다. 쉘이 프롬프트를 표시하면 쉘은 자신을 전경으로 가져옵니다. 일시 중지된 작업 재개를 사용하면 fg
작업이 포그라운드에 있게 됩니다. 의 bg
경우에는 그렇지 않습니다.
백그라운드 프로세스가 터미널에서 데이터를 읽으려고 시도하면 커널은 SIGTTIN 신호를 보냅니다. 이 신호의 기본 작업은 프로세스를 일시 중지하는 것입니다(예: SIGSTOP). 프로세스의 부모는 호출을 통해 이를 배울 수 있습니다.waitpid
플래그를 사용하면 WSTOPPED
자식 프로세스가 정지 신호를 받으면 waitpid
부모 프로세스의 호출이 반환되어 부모 프로세스에 신호가 무엇인지 알립니다. 이것이 쉘이 "중지됨(tty 입력)"을 인쇄하는 방법을 아는 방법입니다. SIGTTIN으로 인해 작업이 일시 중단되었음을 알려줍니다.
프로세스가 일시 중지되었으므로 재개되거나 종료될 때까지 아무 일도 일어나지 않습니다(프로세스에 신호 처리기가 설정된 경우 프로세스가 일시 중지된 이후로 실행되지 않으므로 프로세스에서 신호를 포착하지 못합니다). SIGCONT를 전송하여 프로세스를 재개할 수 있지만 프로세스가 터미널에서 데이터를 읽는 경우 아무런 효과가 없으며 즉시 다른 SIGTTIN을 받게 됩니다. 재개 프로세스를 사용하면 fg
포그라운드로 이동하여 읽기가 성공합니다.
cat
이제 백그라운드에서 실행될 때 어떤 일이 발생하는지 알 수 있습니다.
$ cat &
$
[1] + Stopped (tty input) cat
$
SSH의 경우
이제 SSH를 사용하여 동일한 작업을 수행해 보겠습니다.
$ ssh localhost sleep 999999 &
$
$
$
[1] + Stopped (tty input) ssh localhost sleep 999999
$
키를 누르면 Enter때로는 쉘(포그라운드)로 이동하고 때로는 SSH 프로세스로 이동합니다(이 경우 SIGTTIN에 의해 중지됨). 왜? 터미널에서 읽는 경우 ssh
즉시 SIGTTIN을 수신해야 합니다. 그렇지 않은 경우 SIGTTIN을 수신하는 이유는 무엇입니까?
무슨 일이 일어나는지는 SSH 프로세스가 호출된다는 것입니다.select
관심 있는 파일에 대한 입력이 언제 사용 가능한지(또는 출력 파일이 더 많은 데이터를 수신할 준비가 되었는지) 알기 위한 시스템 호출입니다. 입력 소스에는 최소한 터미널과 네트워크 소켓이 포함됩니다. 와 달리 read
백그라운드 select
프로세스는 비활성화되지 않으며 ssh
호출 시 SIGTTIN을 수신하지 않습니다 select
. 목표 select
는 아무것도 파괴하지 않고 데이터를 사용할 수 있는지 확인하는 것입니다. 이상적으로는 select
시스템 상태가 전혀 변경되지 않지만 실제로는 이것이 전적으로 사실이 아닙니다. select
터미널 파일 설명자에서 입력을 사용할 수 있음을 SSH 프로세스에 알릴 때 read
프로세스가 이후에 입력을 호출하면 커널은 입력 전송을 커밋해야 합니다. (그렇지 않은 경우 프로세스가 호출하면 read
해당 시점에 사용 가능한 입력이 없을 수 있으므로 반환 값은 select
거짓말이 됩니다.) 따라서 커널이 일부 입력을 SSH 프로세스로 라우팅하기로 결정하면 시스템이 언제 실행되는지 결정됩니다. 전화가 돌아옵니다 select
. 그런 다음 SSH가 호출되고 read
, 이 시점에서 커널은 백그라운드 프로세스가 터미널에서 데이터를 읽으려고 시도하는 것을 확인하고 SIGTTIN으로 이를 일시 중지합니다.
동일한 서버에 대해 여러 연결을 시작할 필요는 없습니다. 하나면 충분합니다. 여러 연결로 인해 문제가 발생할 가능성이 높아집니다.
해결책: 터미널에서 읽지 마십시오.
터미널에서 데이터를 읽기 위해 SSH 세션이 필요한 경우 포그라운드에서 실행하세요.
터미널에서 데이터를 읽는 데 SSH 세션이 필요하지 않은 경우 입력이 터미널에서 나오지 않는지 확인하세요. 이를 수행하는 방법에는 두 가지가 있습니다.
입력을 리디렉션할 수 있습니다.
ssh … </dev/null
-n
터미널 연결을 전달하지 않도록 SSH를 사용하거나 지시할 수 있습니다-f
. (-n
와 동일합니다</dev/null
.-f
SSH 자체가 터미널에서 읽을 수 있도록 허용합니다(예: 비밀번호 읽기). 그러나 명령 자체는 터미널을 열지 않습니다.)ssh -n …
터미널과 SSH 간의 연결 끊김은 클라이언트 측에서 발생해야 합니다. 서버에서 실행되는 프로세스는 sleep
터미널에서 데이터를 읽지 않지만 SSH는 이를 알 수 없습니다. 클라이언트가 표준 입력에서 입력을 받으면 이를 서버로 전달해야 합니다. 그러면 애플리케이션이 이를 읽기로 결정한 경우 버퍼에서 데이터를 사용할 수 있게 됩니다. 애플리케이션이 를 호출하면 select
데이터를 사용할 수 있다는 알림을 받게 됩니다. ).
답변2
맨 페이지에서 도움말을 찾을 수 있습니다.
-n Redirects stdin from /dev/null (actually, prevents reading from stdin). This must be used when ssh is run in the
background. A common trick is to use this to run X11 programs on a remote machine. For example, ssh -n
shadows.cs.hut.fi emacs & will start an emacs on shadows.cs.hut.fi, and the X11 connection will be automatically
forwarded over an encrypted channel. The ssh program will be put in the background. (This does not work if ssh
needs to ask for a password or passphrase; see also the -f option.)
-T
그래도 문제가 해결되지 않으면 즉흥적으로 (가상 tty 할당을 비활성화) 시도해 보겠습니다 .
답변3
분명히 동일한 쉘이 동일한 서버에 대해 여러 SSH 연결을 시작하면 주어진 명령을 실행한 후 반환되지 않고 영원히 정지됩니다(중지됨(tty 입력)).
이는 TTY에 대한 동시 액세스의 일반적인 동작입니다. 전체 프로세스는 이미 백그라운드에서 실행 중이고 출력을 쓰려고 할 때 TTY에 대한 액세스가 허용되지 않고 프로세스가 포착 SIGTTOU
하지 못하는 신호( )를 수신하므로 기본 작업( )이 수행됩니다.bash
Stop
이 -n
옵션은 아래에 설명되어 있습니다.다른 답변또는 IO를 일부 파일로 리디렉션하면 도움이 될 것입니다. 더 설명할 내용이 있는지는 모르겠지만, 있다면 설명해 주세요.