同じシェルで複数の接続が開始された場合、バックグラウンドで実行されているSSH接続は終了しません。

同じシェルで複数の接続が開始された場合、バックグラウンドで実行されているSSH接続は終了しません。

明らかに、同じシェルが同じサーバーへの複数の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::PerlNet:SSH2Net::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の問題です。
  • そのうち最悪は次のとおりです。一貫性がない。場合によっては、インスタンスの1つがシャットダウンされ、コントロールが親スクリプトに返されます。しかし、必ずしもそうではなく、どのパターンも識別できません。
  • 名前を変更しても~/.bashrc違いはありません。また、Ubuntu(デフォルトのログインシェルdash)とArch(デフォルトのログインシェルbashと呼ばれる)を実行しているshシステムでもこれを実行しました。
  • Enter興味深いことに、問題はループの開始後に最初のスクリプトが終了する前に任意のキーを押すと発生します(たとえば、キーが機能しているようです)。端末をそのままにすると、期待どおりに完了します。

どうなりますか?これはsshのバグですか?オプションを設定する必要がありますか?同じシェルでSSHを介してコマンドを実行するスクリプトの複数のインスタンスをどのように起動できますか?

答え1

フォアグラウンドプロセスと端末アクセス制御

何が起こっているのかを理解するには、共有端末について少し知っておく必要があります。両方のプログラムが同時に同じ端末で読み取ろうとするとどうなりますか?各入力バイトはプログラムの一つにランダムに入ります。 (カーネルでRNGを使用して決定されたとおり、ランダムではなく実際には予測できないため、ランダムです。)2つのプログラムがパイプまたは別のファイルタイプから読み取ると(ある場所でバイトストリームを移動する)、何が起こりますか?すべてのバイトを複数回読み取ることができるバイト配列(通常のファイル、ブロックデバイス)とは異なり、他のデバイス(ソケット、文字デバイスなど)で発生します。たとえば、端末でシェルを実行するには、端末名を見つけて実行しますcat

$ tty
/dev/pts/18
$ cat

その後、別の端末で実行しますcat /dev/pts/18。端末に入力し、時にはcatプロセスの1つに移動し、時には別のプロセスに移動する行を確認します。ターミナルがベーキングモードの場合、ラインは完全に予約されます。端末をネイティブモードに切り替えると、各バイトは独立して転送されます。

厄介です。確かに、あるプログラムが端末を取得し、他のプログラムがそうでないことを決定するメカニズムが必要です。まあ!一般的な状況では発生しますが、上記で設定したシナリオでは発生しません。この状況は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セッションが必要ない場合は、入力が端末から出ていないことを確認してください。これを行う方法は2つあります。

  • 入力をリダイレクトできます。

    ssh … </dev/null
    
  • -n端末接続を転送しないようにSSHを使用または指示できます-f。 (-nと同じです</dev/null。SSH-f自体が端末から読み取ることができます(パスワードを読むなど)。ただし、コマンド自体は端末を開けません。)

    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ない信号()を受け取るため、基本操作()が実行されます。bashStop

この-nオプションについては後述する。その他の回答あるいは、IOをいくつかのファイルにリダイレクトすると便利です。もっと説明する内容があるかどうかはわかりませんが、あれば説明してください。

関連情報