奇妙な点:バックグラウンドbashを終了すると親プロセスは終了しますか?

奇妙な点:バックグラウンドbashを終了すると親プロセスは終了しますか?

答えが本当に簡単であれば謝罪します。 Linuxサーバーにログインし、さまざまなタスク制御の組み込み機能を練習してから、次のステップに達しました。停止する注文する。好奇心で私は誰でもできる最初のことをしました。 「pause」と入力し、何が起こったかを確認することでした。

user@server:~$ suspend
-bash: suspend: cannot suspend a login shell

そのため、サブシェルを作成して一時停止しようとしました。

user@server:~$ bash
user@server:~$ suspend

[1]+ Stopped   bash
user@server:~$ 

これはいいですね。それとも私はそう思いました! hangコマンドが機能することに満足していたので、サブシェルを終了することにしました。

user@server:~$ kill %1

[1]+ Stopped   bash
user@server:~$ user@server:~$

変だと思った実際にサブシェルを終了できなかったという事実を無視し、その行に2つのプロンプトが表示されました。それで、より簡潔なプロンプトを表示するためにEnterキーを押しました。

user@server:~$ user@server:~$ logout
user@server:~$ Connection to server closed.
user@client:~$

これは驚くべきことです。ローカル端末でも動作するため、リモートサーバーに接続する必要はありません。ローカル端末がログインプロンプトに戻ります。デスクトップセッションの端末が閉じます。

それでは、背景のサブシェルを殺そうとすると、どのように親シェルが死ぬのでしょうか。

ベストアンサー1

Ubuntu 16では、次のように再現できます。

  • 新しいGnome端末ウィンドウを作成します。

  • bashそれから子供を産むsuspend

  • kill %1

窓が死んだ。 修正する:これを使用すると、kill -KILL再びこれは起こりません!

TL;博士:

以下の分析に基づいて、私の現在の仮説(完全に結論的ではない)は、子bashがそれを受信したときにSIGTERMフォアグラウンドプロセスグループに強制的に入り、端末を占有していることです。親BashはSIGTTINシグナルをブロックし、TTYがシグナルをread受信して​​からEIOシャットダウンできるようにします。bashがsuspend致命的なシグナルのために実行を再開した場合、フォアグラウンドに一時停止してはいけません。

より多くの情報を得るために、親strace -f -p <pid>シェルに接続してシステムコールを確認しました。

何らかの理由でstdinから-1を返す、readつまりstdinのI / Oエラーを受け取るために終了するようです。errnoEIO

これはログの尾ですstrace。 PID18860は親で18910子です。

子供の脱退に関する結論:

18910 exit_group(0)                     = ?
18910 +++ exited with 0 +++

親TTYは、次の方法readで再起動可能な方法で中断されますSIGCHLD

18860 <... read resumed> 0x7ffe891c6717, 1) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
18860 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=18910, si_uid=1001, si_status=0, si_utime=0, si_stime=1} ---

親のシグナルハンドラが呼び出され、wait4子を収集します。

18860 wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED|WCONTINUED, NULL) = 18910
18860 wait4(-1, 0x7ffe891c6010, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)

親実行はシグナルハンドラからカーネルに返されます。

18860 rt_sigreturn({mask=[]})           = 0

これで奇妙なことが現れました。それは何ですか?回復後のreadI/Oエラー:

18860 read(0, 0x7ffe891c6717, 1)        = -1 EIO (Input/output error)

親が終了し始めます。

18860 ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
18860 ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig -icanon -echo ...}) = 0
18860 ioctl(0, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
[ ... ]
18860 write(2, "exit\n", 5)             = 5
18860 rt_sigaction(SIGINT, {0x460390, [], SA_RESTORER, 0x7f598a157860}, {0x460390, [], SA_RESTORER, 0x7f598a157860}, 8) = 0
18860 stat("/local/home/kaz/.bash_history", {st_mode=S_IFREG|0600, st_size=57362, ...}) = 0
18860 open("/local/home/kaz/.bash_history", O_WRONLY|O_APPEND) = 3
18860 write(3, "echo $$\nbash\nkill %1\n", 21) = 21
18860 close(3)                          = 0
[ ... ]
etc.

出口は入出力エラーに対する応答のように見えますが、これはほとんど予期しないことです。

もしそうなら、問題は、後続のI / Oエラーを引き起こした子プロセスの終了が何でしたかということです。子プロセスが何もする機会を得ていない場合(kill -KILL %1)、再び表示されません。これは、子プロセスがbashTTYを作成した状態にするためにいくつかの手順を実行したことを示します-1/EIO

カーネルが可能な根本原因でこれと関連があるように見えます。

また、何度も試してみました。ioctl(0, ...)終了時に親が行った呼び出しも表示で失敗することが-1/EIOあります。

カーネルでは、tty_readいくつかの理由で中断が発生する可能性があります。EIO次のステップは、printkデバッグを追加してそれがどれかを確認することです。これはfree-electrons.comが提供する4.12.2の内容です。

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
            loff_t *ppos)
{
    int i;
    struct inode *inode = file_inode(file);
    struct tty_struct *tty = file_tty(file);
    struct tty_ldisc *ld;

    if (tty_paranoia_check(tty, inode, "tty_read"))
        return -EIO;
    if (!tty || tty_io_error(tty))
        return -EIO;

    /* We want to wait for the line discipline to sort out in this
       situation */
    ld = tty_ldisc_ref_wait(tty);
    if (!ld)
        return hung_up_tty_read(file, buf, count, ppos);
    if (ld->ops->read)
        i = ld->ops->read(tty, file, buf, count);
    else
        i = -EIO;
    tty_ldisc_deref(ld);

    if (i > 0)
        tty_update_time(&inode->i_atime);

    return i;
}

これは、ラインルールが機能しないためではない可能性がほとんど確実ですread(最後のルールEIO)。妄想検査が失敗した、空でttyある、またはtty_io_error本当です。

これは発生時に警告メッセージを記録するので、編集証チェックではありません。カーネルログにはこの情報は表示されません。このチェックはコンパイル時に有効にする必要があり、ttyポインタがnullかどうかをチェックします。tty何らかの理由で空であることも排除できません。

tty_io_errorTTY構造のフラグをテストします。

static inline bool tty_io_error(struct tty_struct *tty)
{
    return test_bit(TTY_IO_ERROR, &tty->flags);
}

これが何らかの方法で設定されると、tryや他のシステムコールから継続的なリターンを受け取りますEIOreadただし、これはシリアルコードなどの低レベルTTYドライバによって決まります。

そのため、ld->ops->read(tty, file, buf, count);最初の懲戒処分が再開される可能性があります-EIO。 TTYは常にPOSIX回線規則に従って番号を付ける必要がありますN_TTY。私はそのファイル名が20年間変わっていないことを発見しましたn_tty.c。私たちは欲しいn_tty_read

唯一のケースがありますEIO

            if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
                retval = -EIO;
                break;
            }

ただし、このフラグはTTY / PTY相互作用に関連しています。ここで、PTYはgnome端末によって制御されるデバイスでなければなりません。この場合、閉じる理由はありません。

ああ、しかし、入ると何が起こるのか見てください。n_tty_read:

c = job_control(tty, file);
if (c < 0)
    return c;

これが私が「スモーキングガン」を強く疑う部分です。このコードにはEIO戻りがあり、ジョブ制御に関連しています。これはsig引数を使用して次の関数で終わりますSIGTTIN

int __tty_check_change(struct tty_struct *tty, int sig)
{
    unsigned long flags;
    struct pid *pgrp, *tty_pgrp;
    int ret = 0;

    if (current->signal->tty != tty)
        return 0;

    rcu_read_lock();
    pgrp = task_pgrp(current);

    spin_lock_irqsave(&tty->ctrl_lock, flags);
    tty_pgrp = tty->pgrp;
    spin_unlock_irqrestore(&tty->ctrl_lock, flags);

    if (tty_pgrp && pgrp != tty->pgrp) {
        if (is_ignored(sig)) {
            if (sig == SIGTTIN)
                ret = -EIO;
        } else if (is_current_pgrp_orphaned())
            ret = -EIO;
        else {
            kill_pgrp(pgrp, sig, 1);
            set_thread_flag(TIF_SIGPENDING);
            ret = -ERESTARTSYS;
        }
    }
    rcu_read_unlock();

    if (!tty_pgrp)
        tty_warn(tty, "sig=%d, tty->pgrp == NULL!\n", sig);

    return ret;
}

これには2つの条件がありますEIO。 1つは、TTYから読み取ろうとする呼び出し操作がフォアグラウンドプロセスグループにはなく、信号がSIGTTIN無視されることです。

これはPOSIX(2016年7号)に完全に準拠しています。

バックグラウンド・プロセス・グループ内のプロセスが制御端末からデータを読み取ろうとすると、次のような特別なケースが適用されない限り、SIGTTIN信号がプロセス・グループに送信される。読み取りプロセスがSIGTTIN信号または読み取りスレッドを無視している場合、SIGTTIN信号をブロックしている場合、または読み取りプロセスのプロセスグループが孤立している場合、read()は-1を返し、errnoを[EIO]に設定し、信号は送信されません。 SIGTTIN信号の基本的な動作は、信号が送信されるプロセスを停止することです。[11.1.3 制御端子]

問題は、私たちが親シェルが孤児になることを望まないということです。

終了する子bashが終了時に強制的に前景に入り、親プロセスが誤ってバックグラウンドに残ることは可能ですか?

実際にstraceログの1つで見たのは、親bashが終了しているということでした。今後子供もそのうちの一つであるが、子供がすることはtcsetpgrp自分を有望株にすることだ。つまり、場合によっては親がシグナルを受け取らず、SIGCHLDTTY干渉によってI / Oエラーが発生し、子プロセスから抜け出します。その後、サブプロセスの終了が完了する。

おすすめ記事