単純なbashコマンドに明白な複製や分岐がないのはなぜですか?

単純なbashコマンドに明白な複製や分岐がないのはなぜですか?

以下(sh存在する/bin/dash)を検討してください。

$ strace -e trace=process sh -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
execve("/bin/sh", ["sh", "-c", "grep \"^Pid:\" /proc/self/status /"...], [/* 47 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7fcc8b661540) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fcc8b661810) = 24865
wait4(-1, /proc/self/status:Pid:    24865
/proc/24864/status:Pid: 24864
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 24865
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=24865, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
exit_group(0)                           = ?
+++ exited with 0 +++

珍しい点はありません。grepフォークされたプロセス(ここで行われます)はメインシェルプロセスで置き換えられます。clone()今まではそんなに良くなった。

ここでbash 4.4を使用します。

$ strace -e trace=process bash -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
execve("/bin/bash", ["bash", "-c", "grep \"^Pid:\" /proc/self/status /"...], [/* 47 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f8416b88740) = 0
execve("/bin/grep", ["grep", "^Pid:", "/proc/self/status", "/proc/25798/status"], [/* 47 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f8113358b80) = 0
/proc/self/status:Pid:  25798
/proc/25798/status:Pid: 25798
exit_group(0)                           = ?
+++ exited with 0 +++

ここで明らかなのは、grepシェルプロセスのpidを仮定し、明示的な呼び出しfork()はないということですclone()。もしそうなら、問題はどのようにbash電話なしでそのような曲芸を達成できるかということです。

ただし、clone()コマンドにシェルリダイレクトが含まれていると、システムコールが発生します。df > /dev/null

答え1

sh -c 'command line'一般的にsystem("command line")、、、、ssh host 'command line'およびviより!一般的にコマンドラインを解釈するために使用されるすべての項目に使用されるため、cronできるだけ効率的にすることが重要です。

フォークはCPU時間、メモリ、割り当てられたファイル記述子の面で高価です。あるシェルプロセスが終了する前に別のプロセスを待つのは、リソースの無駄です。また、コマンドを実行する個々のプロセスの終了ステータス(プロセスが終了した場合など)を正しく報告することも困難になります。

多くのシェルは、最適化のためにフォークの数を最小化しようとします。最適化されていないシェルでもbashorケースでこれを行うのが好きです。 ksh や zsh とは異なり、これはこれを行いません (サブシェルでも同じ)。 ksh93は最もフォークに強いシェルです。sh -c cmd(cmd in subshell)bash -c 'cmd > redir'bash -c 'cmd1; cmd2'

次の場合は最適化できません。

sh < file

shそのコマンドの実行中にスクリプトにテキストを追加できるため、最後のコマンドのブランチをスキップする方法はありません。検索できないファイルの場合、ファイルの終わりを検出できません。これは、ファイル内の内容があまりにも早すぎることを意味する可能性があるためです。

または:

sh -c 'trap "echo Ouch" INT; cmd'

「最後」コマンドを実行した後、シェルは追加のコマンドを実行する必要があります。

答え2

Bashのソースコードを研究しながらパイプやリダイレクトがない場合、Bashは実際にフォークを無視することを発見しました。Execute_cmd.cの1601行目:

  /* If this is a simple command, tell execute_disk_command that it
     might be able to get away without forking and simply exec.
     This means things like ( sleep 10 ) will only cause one fork.
     If we're timing the command or inverting its return value, however,
     we cannot do this optimization. */
  if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
      ((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
      ((tcom->flags & CMD_INVERT_RETURN) == 0))
    {
      tcom->flags |= CMD_NO_FORK;
      if (tcom->type == cm_simple)
    tcom->value.Simple->flags |= CMD_NO_FORK;
    }

後でフラグがexecute_disk_command()機能し始め、これが設定されました。ノーフォーク整数変数、後でフォークする前に確認してください。実際のコマンド自体はexecve()ラッパーによって実行されます。関数 shell_execve()分岐プロセスまたは親プロセス(この場合は実際の親プロセス)から。

このメカニズムの理由はスティーブンの答え


注意事項はこの質問の範囲外です。シェルがインタラクティブか実行されるかは明らかに重要です-c。コマンドを実行する前にフォークが発生します。これは、strace対話型シェル()で実行して出力ファイルを調べるとstrace -e trace=process -f -o test.trace bash明らかになります。

19607 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_t
idptr=0x7f2d35e93a10) = 19628
19607 wait4(-1,  <unfinished ...>
19628 execve("/bin/true", ["/bin/true"], [/* 47 vars */]) = 0

また、見ることができますbashが単純なコマンドのサブシェルを生成しないのはなぜですか?

関連情報